Skip to content

Java分布式架构面试题库 ​

生成时间: 2026-05-23 23:27 题目数量: 10道


本轮 Java分布式架构面试题库(带答案) ​

【面试题】1. CAP定理中的"三选二"在实际分布式系统中如何做权衡选择? ​

【标准答案】CAP定理说的是一致性、可用性、分区容错性三者只能满足两个。实际场景中,分区容错性P是必须保证的,因为网络分区是客观存在的,所以真正的选择是在C和A之间。

典型选型策略:

  • 选CP放弃A:适合金融交易、订单系统、库存扣减等强一致性场景。比如Redis Cluster、HBase、MongoDB,网络分区时会拒绝写入,牺牲可用性保证一致性
  • 选CA放弃P:理论上只存在于单机系统,分布式环境无法避免网络分区,所以CA在实际分布式系统中不现实
  • 选AP放弃C:适合社交动态、用户评论、商品浏览等对一致性要求不高的场景。比如Cassandra、DynamoDB,允许短暂数据不一致,优先保证可用性

实际落地:我们项目里,核心交易链路用CP架构(Redis Cluster主从同步),用户中心、推荐系统用AP架构(Cassandra多数据中心)。关键是要根据业务场景评估数据不一致的容忍度和故障恢复时间(RTO),不能一刀切。


【面试题】2. Redis分布式锁的SET NX EX方式有什么缺陷?Redisson如何解决这些问题? ​

【标准答案】SET NX EX方式是最简单的Redis分布式锁实现,命令是SET lock:resource value NX EX 30,但有几个明显缺陷:

缺陷一:锁无法续期

  • 问题:业务执行时间超过锁过期时间,锁自动释放,其他线程拿到锁,导致并发问题
  • 解决:Redisson引入Watchdog机制,后台线程每10秒(默认过期时间1/3)自动续期,业务未完成前锁不会失效

缺陷二:锁误删

  • 问题:线程A的锁过期释放,线程B拿到锁,线程A执行完删除了线程B的锁
  • 解决:Redisson用Lua脚本保证原子性,删除时校验锁的value(UUID+线程ID),只删自己的锁

缺陷三:不可重入

  • 问题:同一线程多次获取同一把锁会死锁
  • 解决:Redisson通过Hash结构记录重入次数,支持可重入锁

缺陷四:主从切换丢锁

  • 问题:主节点加锁成功但未同步到从节点,主节点宕机,从节点升为主,锁丢失
  • 解决:Redisson的Redlock算法,向多个独立Redis节点申请锁,超过半数成功才算加锁成功

我们生产环境用的是Redisson的RLock,配置了Watchdog超时30秒,关键业务还会加Redlock防止主从切换丢锁。


【面试题】3. TCC分布式事务模式和2PC有什么区别?什么场景适合用TCC? ​

【标准答案】TCC和2PC都是分布式事务解决方案,但设计思路完全不同:

核心区别:

  • 2PC是强一致:准备阶段锁定资源,提交阶段释放,所有参与者必须同时成功或失败,有阻塞性能问题
  • TCC是最终一致:Try阶段预留资源(冻结),Confirm阶段真正执行,Cancel阶段回滚预留,整个过程无阻塞

TCC三阶段详解:

  • Try:业务检查+资源预留,比如扣减余额时先冻结金额,订单状态改为"支付中"
  • Confirm:确认执行,真正扣减余额,订单状态改为"已支付",无需再检查
  • Cancel:取消预留,解冻余额,订单状态改为"已取消"

适用场景:

  1. 对性能要求高:TCC无阻塞,吞吐量比2PC高很多,适合高并发场景
  2. 业务可拆分:Try/Confirm/CANCEL三个逻辑能清晰划分,比如支付、库存、积分
  3. 允许最终一致:短暂数据不一致可接受,比如秒杀场景

不适用场景:

  • 业务逻辑简单,无法拆分Try/Confirm/Cancel
  • 对一致性要求极高,不允许任何中间状态(用2PC或Seata AT模式)

我们项目用Seata的TCC模式,支付场景:Try冻结余额,Confirm扣减余额并增加流水,Cancel解冻余额。关键是要保证幂等性,Confirm和Cancel可能重试多次,必须用唯一业务ID去重。


【面试题】4. Snowflake雪花算法生成分布式ID有什么缺点?Leaf号段模式如何优化? ​

【标准答案】Snowflake是64位ID生成算法(1位符号位+41位时间戳+10位机器ID+12位序列号),优点是高性能、趋势递增,但有明显缺点:

Snowflake缺点:

  1. 时钟回拨问题:机器时钟回拨会生成重复ID,严重导致服务不可用
  2. 机器ID分配:需要独立服务分配机器ID,或配置文件手动配置,集群扩容麻烦
  3. 数据倾斜:时间戳在高位,按ID排序是按时间排序,但不同机器并发写入可能导致索引分裂

Leaf号段模式优化方案:

Leaf Segment(号段模式):

  • 原理:数据库记录max_id和step,每次批量获取一个号段(如1000个ID)缓存在内存
  • 优势:减少数据库访问频率,吞吐量从几百提升到几万QPS
  • 缺点:ID不连续,重启会浪费部分ID

Leaf Snowflake优化版:

  • 时钟回拨:启动时记录last_timestamp,发现回拨立即报警并拒绝服务
  • 机器ID:使用Zookeeper顺序节点自动分配,重启后复用原workId
  • 双buffer优化:一个buffer用完后异步加载下一个buffer,无卡顿

对比选型:

  • Snowflake:适合对ID连续性要求不高,机器数量少的场景
  • Leaf Segment:适合高并发场景,对ID连续性不敏感
  • Leaf Snowflake:适合大规模集群,需要趋势递增

我们订单系统用Leaf Segment,每批次获取2000个ID,QPS峰值能到5万,关键是要监控号段剩余量,低于阈值提前加载。


【面试题】5. 缓存穿透、缓存击穿、缓存雪崩的区别和解决方案? ​

【标准答案】这三个是缓存最经典的故障场景,必须清晰区分:

缓存穿透:

  • 定义:查询一个根本不存在的数据,缓存没命中,数据库也没有,导致每次都打到数据库
  • 典型场景:恶意攻击查询id=-1、id=999999999等不存在的key
  • 解决方案:
    1. 布隆过滤器:提前加载所有有效key的bitmap,查询前先判断,不存在的直接返回
    2. 空值缓存:查询数据库为空时,也缓存一个null值,设置较短过期时间(如5分钟)
    3. 接口层校验:参数合法性校验,过滤明显非法请求

缓存击穿:

  • 定义:某个热点key过期瞬间,大量并发请求同时访问,全部打到数据库
  • 典型场景:秒杀商品信息、热门文章详情等高并发key
  • 解决方案:
    1. 互斥锁:只允许一个线程查数据库并回写缓存,其他线程等待
    2. 逻辑过期:不设置过期时间,value中包含逻辑过期时间,后台异步刷新
    3. 热点数据永不过期:配置中心手动触发刷新

缓存雪崩:

  • 定义:大量key同时过期,或Redis宕机,导致所有请求打到数据库
  • 典型场景:缓存批量设置相同过期时间、Redis集群故障
  • 解决方案:
    1. 过期时间加随机值:避免同时过期,比如基础时间30分钟+随机0-5分钟
    2. 多级缓存:本地缓存(Caffeine)+分布式缓存(Redis),Redis挂了还有本地缓存
    3. 熔断降级:数据库压力过大时触发熔断,返回降级数据

我们项目里,热门商品用逻辑过期方案,秒杀场景预热时不设置过期时间,活动结束后手动清理;普通商品用随机过期时间防止雪崩;不存在的商品用布隆过滤器拦截。


【面试题】6. Raft算法的Leader选举过程是怎样的?如果出现多个Candidate怎么办? ​

【标准答案】Raft算法的核心是强Leader模式,所有日志复制都通过Leader进行,Leader选举过程:

正常选举流程:

  1. Follower超时:Follower在election timeout(150-300ms随机)内未收到Leader心跳,转为Candidate
  2. 发起选举:Candidate增加term,给自己投票,向其他节点发送RequestVote RPC
  3. 投票规则:每个term每个节点只能投一票,先到先得,候选人日志必须比自己新(至少不旧)
  4. 获得多数票:获得超过半数节点投票的Candidate成为Leader,立即发送心跳确立权威

多Candidate冲突处理:

  • 场景:网络分区导致多个Follower同时超时,出现多个Candidate
  • 解决:Split Vote机制
    1. 多个Candidate瓜分选票,无人获得多数票
    2. 选举超时,自动进入下一轮选举
    3. 由于election timeout随机,大概率下一轮只有一个Candidate胜出
  • 优化:PreVote机制,Candidate先确认能获得多数票再发起选举,减少无效选举

Leader宕机恢复:

  • 新Leader选举产生后,对比日志差异,Follower日志落后会被覆盖,多余日志会被截断
  • 强一致性保证:Leader不会覆盖或删除自己的日志,只做追加

我们用Etcd做配置中心,底层是Raft协议,3节点集群,选举超时配置1秒,关键是节点数必须是奇数(3/5/7),保证能获得多数票。


【面试题】7. XXL-Job分布式调度的架构设计是怎样的?如何保证任务不重复执行? ​

【标准答案】XXL-Job是国内最流行的分布式任务调度平台,架构设计非常清晰:

核心架构:

  • 调度中心(Admin):负责任务调度、触发、监控,管理界面
  • 执行器(Executor):嵌入到业务应用中,接收调度指令并执行任务
  • 通信方式:调度中心通过Netty HTTP协议触发执行器,执行器注册到调度中心

任务不重复执行机制:

方案一:分片广播:

  • 调度中心广播任务到所有执行器,每个执行器获取分片参数(分片序号/总分片数)
  • 业务逻辑根据分片参数处理不同数据,如id % totalShard == shardIndex
  • 适合大数据量任务并行处理

方案二:故障转移:

  • 调度中心记录每个任务的执行状态(Running/Success/Fail)
  • 任务触发时检查上次执行状态,Running状态的任务不再触发
  • 执行器故障时,调度中心自动路由到健康执行器

方案三:并发策略:

  • 单机串行:同一执行器上任务排队执行
  • 丢弃后续调度:上次执行未完成,新调度直接丢弃
  • 覆盖之前调度:停止上次执行,执行新调度

错过重做机制:

  • 调度中心记录任务触发记录,调度失败的任务标记为"错过"
  • 后台线程定期扫描错过的任务,重新触发执行

我们项目用XXL-Job处理订单超时取消,配置分片广播+单机串行,每台机器处理部分订单,避免重复。关键是要给任务加分布式锁(Redis SET NX),防止调度异常导致重复执行。


【面试题】8. 分布式链路追踪的TraceId和SpanId如何传递?异步调用如何保证链路完整? ​

【标准答案】链路追踪的核心是TraceId全链路传递,SpanId标记调用层级:

TraceId传递机制:

  • 入口生成:网关或第一个服务生成全局唯一TraceId(如UUID或Snowflake)
  • HTTP传递:通过Header传递,如X-Trace-Id、X-Span-Id
  • RPC传递:Dubbo通过RpcContext,Feign通过RequestInterceptor,将TraceId注入到请求头
  • 线程池传递:通过TransmittableThreadLocal(TTL)或MDC(Mapped Diagnostic Context)传递到子线程

SpanId层级关系:

  • SpanId格式:0.1、0.1.1、0.1.1.1,表示调用层级
  • 每次调用生成子SpanId,如父SpanId=0.1,子SpanId=0.1.1
  • 记录调用关系:Parent SpanId → Current SpanId → Child SpanId

异步调用链路追踪难点:

场景一:线程池异步执行

  • 问题:主线程的TraceId无法自动传递到线程池的工作线程
  • 解决:使用TransmittableThreadLocal(TTL),自动复制父线程上下文到子线程
  • 代码示例:TtlExecutors.getTtlExecutorService(executor)

场景二:消息队列异步

  • 问题:发送消息时TraceId丢失,消费端无法关联
  • 解决:将TraceId作为消息Header传递(RocketMQ的Message.getProperties())
  • 消费端:消费时从Header提取TraceId,放入当前上下文

场景三:异步回调

  • 问题:回调执行时原始上下文已清除
  • 解决:回调前保存上下文,回调时恢复上下文(如Reactor的Context)

我们项目用SkyWalking做链路追踪,通过Java Agent字节码增强自动埋点,业务代码无侵入。异步调用需要手动传递TraceId,关键是所有中间件(MQ、Redis、RPC)都要支持上下文传递。


【面试题】9. Nacos作为配置中心如何实现配置热更新?配置变更如何通知所有节点? ​

【标准答案】Nacos配置中心的核心能力是动态配置,配置变更实时生效:

配置热更新机制:

方案一:长轮询(推荐):

  • 客户端发起长轮询请求,timeout=30秒
  • 服务端挂起请求,配置变更时立即返回,无变更则30秒后返回
  • 客户端收到响应后立即发起新的长轮询,实现准实时推送

方案二:UDP推送:

  • 客户端注册UDP端口到服务端
  • 配置变更时服务端通过UDP推送到所有客户端
  • UDP可靠性低,作为长轮询的补充

配置变更通知流程:

  1. 用户在Nacos控制台修改配置,点击发布
  2. 服务端更新配置内容,记录版本号(MD5)
  3. 服务端通知所有长轮询客户端(或UDP推送)
  4. 客户端收到通知,对比本地MD5,发现变化则拉取最新配置
  5. 客户端触发@RefreshScope注解的Bean重新加载
  6. 应用使用最新配置,无需重启

多环境管理:

  • Namespace隔离环境:dev、test、prod
  • Group分组业务:database、mq、redis
  • Data ID标识配置:application.yml、application-dev.yml

配置回滚机制:

  • Nacos保留30天配置历史,可一键回滚到任意版本
  • 配置变更记录操作人、时间、变更内容

高可用设计:

  • Nacos集群部署(至少3节点),数据存储到MySQL
  • 客户端缓存本地配置快照,Nacos宕机时使用本地缓存
  • 配置变更失败时自动重试,保证最终一致性

我们项目用Nacos Config管理数据库连接、Redis配置、业务参数等,关键配置用@RefreshScope实时刷新,非关键配置启动时加载。生产环境配置变更加审批流程,避免误操作。


【面试题】10. 服务网格Istio的Sidecar模式如何实现流量管理?和传统微服务框架对比有什么优势? ​

【标准答案】Istio是服务网格的代表性实现,核心是Sidecar代理模式:

Sidecar架构:

  • 每个Pod注入一个Envoy容器作为Sidecar
  • 所有进出Pod的流量都经过Envoy代理
  • Envoy拦截网络流量,执行路由、负载均衡、熔断、限流等策略
  • 控制面(Istiod)下发配置到Envoy,数据面(Envoy)执行流量管理

流量管理实现:

路由规则:

  • VirtualService定义路由规则:如weight: 90、v1版本路由90%流量
  • DestinationRule定义子集:如v1、v2、canary
  • 路由条件:Header、URI、权重等

灰度发布:

  • 金丝雀发布:新版本灰度10%流量,逐步放量
  • 蓝绿部署:全量切换到新版本,一键回滚
  • A/B测试:根据Header路由到不同版本

故障注入:

  • 延迟注入:模拟网络延迟,测试超时处理
  • 中止注入:模拟服务故障,测试熔断降级

与传统微服务框架对比:

维度传统框架(Spring Cloud)服务网格(Istio)
实现方式业务代码集成SDKSidecar代理,业务无感知
语言绑定绑定Java,多语言需重复实现语言无关,支持多语言
运维成本SDK升级需重新部署Sidecar升级透明
性能损耗SDK直接调用,损耗低多一层代理,延迟增加1-3ms
学习成本需要掌握框架API学习Istio配置即可
灵活性业务代码可定制化配置驱动,灵活性受限

优势场景:

  1. 多语言微服务:Java/Go/Python/Node混合,统一治理
  2. 遗留系统改造:老系统无法改造,通过Sidecar注入治理能力
  3. 运维友好:配置变更无需重启,监控可观测性开箱即用

劣势场景:

  1. 性能敏感:高频调用场景,Sidecar延迟不可接受
  2. 小规模系统:架构复杂度过高,杀鸡用牛刀

我们新项目尝试用Istio,主要看重多语言支持和运维友好性,但老项目还是用Spring Cloud。迁移时可以逐步试点,一个服务一个服务地迁移,新旧架构并存。


总结: 本轮题目覆盖理论基础(CAP、Raft)、架构设计(TCC、Snowflake、缓存)、场景实操(分布式锁、链路追踪)、故障排查(缓存三大问题)、性能优化(调度、配置中心)、面试情景(服务网格)。答案聚焦面试口语表达,简洁落地,突出实战经验。