Skip to content

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

【面试题】1. 请解释CAP定理,为什么分布式系统只能三选二? ​

【标准答案】CAP定理是分布式系统设计的核心理论基础,C代表一致性(Consistency),A代表可用性(Availability),P代表分区容错性(Partition Tolerance)。

为什么只能三选二? 因为网络分区是分布式系统的客观现实,P是必须保证的。一旦发生分区,系统要么选择一致性(拒绝部分请求),要么选择可用性(接受可能不一致的请求)。所以实际选择是CP或AP。

CP场景:银行转账、金融交易系统——宁可系统不可用,也不能出现数据不一致。 AP场景:社交媒体点赞、商品浏览量——允许短暂不一致,但要保证用户体验流畅。

面试时要强调:没有绝对的CAP选择,更多是根据业务场景做权衡,比如BASE理论就是对CAP中AP方案的延伸。


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

【标准答案】原生SET NX EX的三大问题:

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

Redisson解决方案:

  • Watch Dog看门狗机制:后台线程定期续期(默认每10秒续期30秒),业务没执行完锁不会过期。
  • Lua脚本原子删除:删除锁时校验锁的持有者,只能删除自己的锁。
  • Hash结构实现可重入:锁名作为key,value用Hash存储线程ID和重入次数。

实际项目中,如果只是简单防重,SET NX EX够用;但涉及复杂业务、长时间任务,必须用Redisson。


【面试题】3. TCC分布式事务模式如何保证数据一致性?有什么缺点? ​

【标准答案】TCC工作原理:

  • Try阶段:预留资源,做业务检查,不真正执行业务。比如转账场景,冻结金额而非直接扣款。
  • Confirm阶段:确认执行,真正执行业务操作,使用Try阶段预留的资源。
  • Cancel阶段:取消执行,释放Try阶段预留的资源,做回滚操作。

一致性保证:

  1. 幂等性设计:Confirm/Cancel可能重试,必须幂等。
  2. 空回滚处理:Try没执行成功,收到Cancel请求要能处理。
  3. 悬挂问题:Try比Cancel先到(网络原因),Cancel后要记录状态,拒绝后续Try。

缺点:

  • 业务侵入性强,每个接口都要写三套逻辑。
  • 开发成本高,需要设计补偿逻辑。
  • 空回滚、悬挂等边界情况处理复杂。

适用场景:强一致性要求、跨数据库、跨服务、长事务场景。如果是简单场景,推荐用Seata的AT模式。


【面试题】4. Snowflake雪花算法生成分布式ID,时钟回拨问题如何解决? ​

【标准答案】雪花算法结构:1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号,理论上支持每毫秒4096个ID,69年不重复。

时钟回拨问题:机器时间被回调,可能导致ID重复或乱序。

解决方案:

  1. 等待策略:检测到时间回拨,线程等待直到时间追上上次ID生成时间。适合小幅度回拨(毫秒级)。
  2. 拒绝策略:回拨超过阈值直接报错,依赖外部监控告警。
  3. 时钟回拨保护:美团的Leaf、百度的UidGenerator都有实现,记录上次生成时间戳,回拨时借用未来时间或走备用方案。

生产建议:

  • 机器ID从配置中心或Zookeeper分配,避免冲突。
  • 关闭NTP自动同步,或使用渐进式时间调整。
  • 监控ID生成器的时钟偏差,及时告警。

面试时可以补充:雪花ID不是严格递增的,是趋势递增,适合MySQL主键,但不适合做排序依据。


【面试题】5. 缓存穿透、缓存击穿、缓存雪崩分别是什么?解决方案? ​

【标准答案】三种缓存失效场景:

问题定义核心原因
缓存穿透查询不存在的数据,缓存和数据库都没有恶意请求、爬虫
缓存击穿热点Key过期瞬间,大量请求穿透到数据库热点数据过期
缓存雪崩大量Key同时过期,或Redis宕机过期时间集中、单点故障

解决方案:

缓存穿透:

  1. 布隆过滤器:拦截不存在的Key(推荐RedisBloom模块)。
  2. 空值缓存:查不到的数据也缓存,设置较短过期时间。
  3. 参数校验:在入口层拦截明显非法请求。

缓存击穿:

  1. 热点数据永不过期:设置为逻辑过期,后台异步刷新。
  2. 互斥锁:只允许一个线程查库,其他等待。
  3. 提前预热:热点数据提前加载。

缓存雪崩:

  1. 过期时间随机化:基础过期时间+随机值,避免集中失效。
  2. 多级缓存:本地缓存+分布式缓存。
  3. Redis高可用:哨兵或Cluster模式。
  4. 熔断降级:数据库压力过大时熔断,返回降级数据。

【面试题】6. Raft算法的Leader选举过程是怎样的?如何保证日志一致性? ​

【标准答案】Raft节点状态:Follower(追随者)、Candidate(候选人)、Leader(领导者)。

Leader选举过程:

  1. 初始都是Follower,设置随机超时时间(150-300ms)。
  2. Follower超时未收到Leader心跳,转为Candidate,任期+1,投自己一票。
  3. Candidate向其他节点发送RequestVote请求。
  4. 获得多数票(N/2+1)成为Leader,立即发送心跳确立地位。
  5. 如果选举超时,任期+1重新选举。

日志一致性保证:

  1. Leader接收客户端请求,追加到本地日志,并行发送AppendEntries给所有Follower。
  2. 多数Follower确认后,Leader提交日志,响应客户端。
  3. Leader维护nextIndex和matchIndex,处理日志不一致:
    • Follower日志落后:Leader发送缺失的日志。
    • Follower日志冲突:Follower删除冲突日志,同步Leader日志。
  4. 已提交的日志不会丢失,最多一个任期一个Leader。

面试要点:Raft比Paxos更易理解和实现,是工程实践的首选,广泛应用于Etcd、Consul等系统。


【面试题】7. 分布式Session共享有哪些方案?各自的优缺点? ​

【标准答案】四种主流方案:

方案原理优点缺点
Session复制集群中每台服务器复制Session简单、无单点内存消耗大、延迟高
Session粘性负载均衡固定用户到同一服务器无需改动代码服务器宕机Session丢失
Session集中存储Session存到Redis/Memcached可扩展、支持故障转移增加网络开销
JWT无状态Token存客户端,服务端无状态无存储压力、天然分布式无法主动失效、Token较大

生产推荐方案:

  1. Spring Session + Redis:业务无感知,自动将Session存Redis,支持故障转移。
  2. JWT + 刷新机制:Access Token短期+Refresh Token长期,适合前后端分离架构。

JWT方案细节:

  • Access Token有效期15-30分钟,Refresh Token有效期7-30天。
  • Refresh Token存Redis,可主动吊销。
  • 登出时将Refresh Token加入黑名单。

面试时强调:没有完美方案,根据业务规模和团队技术栈选择。


【面试题】8. 令牌桶和漏桶算法的区别?分布式限流如何实现? ​

【标准答案】两种算法对比:

特性令牌桶漏桶
处理突发流量允许(桶内有令牌时)不允许(恒定速率)
流量整形不严格,允许短期超限严格,强制恒定速率
实现复杂度中等较低
适用场景API限流、允许突发保护后端服务、流量平滑

令牌桶原理:系统以固定速率生成令牌放入桶中,请求到来时从桶取令牌,有令牌则通过,无令牌则拒绝或等待。桶满则令牌丢弃。

漏桶原理:请求进入漏桶,漏桶以固定速率流出请求。桶满则拒绝新请求。

分布式限流实现:

  1. Redis + Lua脚本:原子操作,高性能。
lua
-- 简化版令牌桶
local tokens = redis.call('GET', KEYS[1])
if not tokens then tokens = ARGV[1] end
if tonumber(tokens) >= 1 then
    redis.call('DECR', KEYS[1])
    return 1
end
return 0
  1. Sentinel/Resilience4j:成熟的限流框架,支持多种规则配置。
  2. Gateway层限流:在API网关统一限流,减少后端压力。

生产建议:热点接口用Redis+Lua,全局限流用Sentinel,网关层做兜底保护。


【面试题】9. Zookeeper的Watch机制有什么特点?为什么不适合做消息队列? ​

【标准答案】Watch机制特点:

  1. 一次性触发:Watch被触发后立即失效,需要重新注册。这是性能优化设计,避免大量持久监听。
  2. 客户端回调:Watch事件通知到达客户端后,客户端才收到回调,存在网络延迟。
  3. 异步通知:服务端状态变更到客户端收到通知是异步的,不能保证实时性。
  4. 轻量级:Watch只通知事件类型,不传数据,减少网络开销。

Watch使用场景:

  • 配置中心:配置变更通知。
  • 服务注册发现:服务上下线通知。
  • 分布式锁:锁释放通知。
  • Leader选举:节点变更通知。

为什么不适合做消息队列:

  1. Watch一次性:消息可能丢失,没有持久化确认机制。
  2. 容量有限:ZNode有大小限制(默认1MB),不适合存大量数据。
  3. 性能瓶颈:ZK集群写入性能有限(单机几千TPS),无法支撑高吞吐。
  4. 无消息模型:缺少消费组、消息重试、死信队列等消息队列必备特性。
  5. 通知延迟:Watch通知延迟不固定,不适合实时消息场景。

正确选择:配置管理、分布式协调用ZK,消息队列用Kafka/RocketMQ/Pulsar。


【面试题】10. XXL-Job分布式任务调度的执行器集群是如何工作的?任务分片怎么做? ​

【标准答案】XXL-Job架构:

  • Admin:调度中心,负责任务调度、触发、监控。
  • Executor:执行器,执行具体任务,可集群部署。

执行器集群工作原理:

  1. 执行器启动时向Admin注册(心跳续期)。
  2. Admin根据路由策略选择执行器下发任务。
  3. 执行器执行任务,回调结果给Admin。
  4. 执行器宕机时Admin自动剔除,故障转移。

路由策略:

  • 轮询:依次分配,适合任务量均匀场景。
  • 随机:随机选择执行器。
  • 一致性哈希:同一任务ID始终路由到同一执行器。
  • 分片广播:所有执行器都执行,配合分片参数做数据分片。
  • 故障转移:按顺序尝试,失败则切换。

任务分片实现:

java
@XxlJob("dataProcessJob")
public void dataProcessJob() {
    int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号
    int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
    
    // 根据分片参数处理数据
    List<Data> dataList = queryData(shardIndex, shardTotal);
    processData(dataList);
}

分片广播原理:

  1. Admin广播任务到所有执行器。
  2. 每个执行器获得不同的shardIndex(0, 1, 2...)。
  3. 执行器根据分片参数处理各自的数据子集。
  4. 各执行器独立执行,互不干扰。

适用场景:大数据批量处理、报表生成、数据同步等可拆分的任务。


生成时间:2026-05-21 22:41