Skip to content

Java分布式架构面试题库(第N轮) ​

生成时间:2026-05-23 15:59


【面试题】1. CAP定理中为什么只能"三选二"?能否同时满足CA? ​

【标准答案】CAP定理指出分布式系统无法同时满足一致性(C)、可用性(A)、分区容错性(P)。

为什么只能三选二? 核心原因是网络分区是不可避免的。在分布式系统中,网络故障、节点宕机随时可能发生,P是必须面对的现实问题。一旦发生分区,系统必须在C和A之间做选择:

  • 选择CP:分区期间拒绝请求,保证一致性但牺牲可用性
  • 选择AP:分区期间继续响应,保证可用性但可能出现数据不一致

能否同时满足CA? 只有在单机系统或不存在网络分区的理想环境下才能实现。真实分布式环境中网络分区不可避免,所以CA在分布式场景下是伪命题。比如传统的单机数据库如MySQL在单节点时是CA,一旦做了主从复制就变成了CP或AP。


【面试题】2. Redis分布式锁用SET NX EX实现有什么缺陷?Redisson如何解决? ​

【标准答案】SET NX EX的三大缺陷:

  1. 锁超时问题:业务执行时间超过锁过期时间,锁自动释放,其他线程获得锁导致并发问题
  2. 误删锁:线程A的锁过期后,线程B获得锁,线程A执行完毕删除了B的锁
  3. 不可重入:同一线程无法多次获取同一把锁

Redisson的解决方案:

  1. 看门狗机制:后台线程自动续期,默认锁30秒,每10秒续期一次,解决锁超时问题
  2. Lua脚本原子操作:加锁时写入线程标识,解锁时校验是不是自己的锁再删除,解决误删问题
  3. Hash结构实现可重入:锁结构为{lock_name: {thread_id: count}},同一线程多次获取时计数+1,释放时-1

实际生产环境推荐用Redisson,不建议自己手写SET NX。


【面试题】3. TCC模式和Saga模式的核心区别是什么?各自适用场景? ​

【标准答案】核心区别:

维度TCCSaga
补偿时机主动补偿,每个分支事务都有Confirm/Cancel逆向补偿,失败时执行反向操作
一致性强一致,所有分支同时提交或回滚最终一致,逐个提交或补偿
实现复杂度高,每个操作都要写三个接口中,只需正向+补偿
性能高,预留资源后异步确认更高,无资源锁定

适用场景:

  • TCC:对一致性要求高、资金类业务、库存扣减场景。比如转账、下单扣库存,需要强一致保证
  • Saga:长流程业务、跨系统编排、对一致性要求稍低场景。比如订机票+订酒店+租车,流程长但允许短时不一致

实际选型:金融核心用TCC,业务编排用Saga。Seata框架对两种模式都有支持。


【面试题】4. Snowflake雪花算法ID重复的根本原因是什么?如何避免? ​

【标准答案】ID结构(64位): 1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号

重复的根本原因:

  1. 时钟回拨:服务器时间被回调,时间戳变小,可能生成已存在的ID。这是最常见的问题
  2. 机器ID重复:多台机器配置相同workerId,导致ID冲突
  3. 序列号溢出:单毫秒内生成超过4096个ID,序列号回绕

解决方案:

  1. 时钟回拨处理:

    • 小幅回拨(<5ms):等待时间追平
    • 中度回拨:使用备用workerId或拒绝服务
    • 大幅回拨:报警并停止服务
  2. 机器ID分配:用Zookeeper或Redis集中分配workerId,避免重复

  3. 序列号溢出:溢出时等待下一毫秒

生产方案:用美团Leaf或百度UidGenerator,它们对时钟回拨做了完善处理。Leaf-segment模式不依赖时间,彻底解决时钟回拨问题。


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

【标准答案】三种问题本质不同:

问题现象原因
穿透大量请求查询不存在的key恶意攻击或爬虫
击穿热点key过期瞬间大量请求打到DB热点key过期
雪崩大量key同时过期key设置相同过期时间

解决方案:

  1. 穿透:

    • 布隆过滤器:请求前先判断key是否存在
    • 空值缓存:查询为空也缓存,设短过期时间
    • 推荐布隆过滤器,能过滤99%无效请求
  2. 击穿:

    • 互斥锁:只让一个线程查DB,其他等待
    • 逻辑过期:不设置TTL,后台异步更新
    • 推荐逻辑过期方案,不阻塞请求
  3. 雪崩:

    • 过期时间加随机值,避免同时失效
    • 多级缓存:本地缓存+Redis
    • 熔断降级:DB压力过大时直接拒绝

【面试题】6. Redis Cluster的数据分片原理是什么?客户端如何路由? ​

【标准答案】数据分片原理:

Redis Cluster采用虚拟槽分区,共16384个槽位。每个节点负责一部分槽位,比如3节点集群:Node1负责0-5460,Node2负责5461-10922,Node3负责10923-16383。

key映射规则: slot = CRC16(key) % 16384

客户端路由方式:

  1. MOVED重定向:客户端请求任意节点,若key不在该节点,返回MOVED响应告诉客户端正确节点,客户端重新请求

  2. ASK重定向:槽位迁移过程中,请求旧节点返回ASK,客户端临时请求新节点

  3. Smart客户端:Jedis、Lettuce等客户端缓存槽位映射,直接请求目标节点,减少重定向。当收到MOVED时更新本地映射

生产建议: 用Smart客户端+连接池,禁用keys/flushall等危险命令,配置读写分离时注意从节点可能数据延迟。


【面试题】7. Raft算法的Leader选举过程是怎样的?什么情况下会触发重新选举? ​

【标准答案】角色状态: Leader、Follower、Candidate

Leader选举过程:

  1. 初始状态:所有节点都是Follower,启动随机超时时间(150-300ms)

  2. 超时触发:Follower在选举超时内没收到Leader心跳,转变为Candidate

  3. 发起投票:Candidate增加任期号,给自己投票,向其他节点发送RequestVote RPC

  4. 投票规则:

    • 一个任期内只能投一票
    • 先到先得,但候选人日志至少要和投票者一样新
    • 获得多数票(N/2+1)则当选Leader
  5. 成为Leader:立即发送心跳通知所有节点,阻止新选举

触发重新选举的情况:

  1. Leader宕机或网络隔离
  2. Leader发送心跳超时
  3. 多个Candidate同时发起选举导致选票瓜分,无人获得多数(等待超时后重新选举)

优化点:随机超时时间避免同时触发选举,加快选举收敛。


【面试题】8. Seata的AT模式为什么能实现无侵入分布式事务?原理是什么? ​

【标准答案】AT模式核心:自动补偿 + 一阶段提交

为什么无侵入? 开发者只需加@GlobalTransactional注解,不需要写Commit/Cancel逻辑,Seata框架自动生成反向SQL。

工作原理:

  1. 一阶段:

    • 解析业务SQL,记录前后镜像到undo_log表
    • 执行业务SQL并提交本地事务
    • 向TC注册分支事务
  2. 二阶段提交:异步删除undo_log,无需业务干预

  3. 二阶段回滚:

    • 取出undo_log中的前后镜像
    • 校验当前数据是否被修改(脏写检测)
    • 生成反向SQL并执行(insert→delete,update→update为旧值,delete→insert)
    • 删除undo_log

关键设计:

  • 全局锁:防止其他事务修改正在处理的数据
  • undo_log:存储数据快照用于回滚
  • TC协调器:管理全局事务状态

限制:只支持关系型数据库,依赖本地事务ACID特性。


【面试题】9. ZooKeeper的临时节点和持久节点有什么区别?在分布式锁中如何应用? ​

【标准答案】节点类型对比:

类型生命周期用途
持久节点永久存在,主动删除配置存储、目录结构
临时节点会话结束自动删除锁、服务注册、选举
持久顺序永久+自动递增序号分布式队列
临时顺序会话结束删除+自动序号公平锁、选举

分布式锁实现:

  1. 排他锁:用临时节点

    • 创建临时节点/lock,成功则获得锁
    • 失败则watch该节点,删除时重新竞争
    • 会话断开自动释放,避免死锁
  2. 公平锁:用临时顺序节点

    • 在/lock下创建临时顺序节点
    • 检查自己序号是否最小,最小获得锁
    • 否则watch前一个节点,前一个删除时被唤醒
    • 实现FIFO公平竞争

优势:临时节点+会话绑定,客户端宕机自动释放锁,不会死锁。比Redis方案更可靠,但性能稍低。


【面试题】10. 分布式链路追踪中TraceId如何跨服务传递?异步调用如何处理? ​

【标准答案】TraceId传递机制:

  1. 同步调用:通过HTTP Header或RPC上下文传递

    • HTTP:放在Header中,如X-Trace-Id、X-Span-Id
    • Dubbo:放在RpcContext的Attachment中
    • gRPC:放在Metadata中
  2. 传递流程:

    • 入口服务生成TraceId
    • 下游服务从Header中提取并继承
    • 每个服务创建新Span,SpanId递增,ParentSpanId指向上游

异步调用处理:

  1. 线程池异步:

    • 主线程通过InheritableThreadLocal自动传递
    • 或手动将TraceId放入Runnable/Callable
  2. 消息队列异步:

    • 发送时TraceId放入消息Header
    • 消费时从Header提取并设置到当前上下文
    • RocketMQ通过Message.setProperty传递
  3. Spring异步:

    • 使用@Async时配置TaskDecorator
    • 在Decorator中复制父线程的Trace上下文

关键点:异步调用是链路追踪最容易断链的地方,需要框架层面统一处理。SkyWalking通过Java Agent自动注入,无侵入解决异步传递问题。