Skip to content

Java分布式架构面试题库 ​

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


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

【面试题】1. CAP定理中,为什么分布式系统最多只能同时满足两个特性?请结合实际场景说明如何取舍。 ​

【标准答案】CAP定理的核心原因是网络分区(P)是分布式系统必须面对的现实,不是可选项——网络总会出问题。

当发生网络分区时,系统被分成两个独立区域,这时候必须在C(一致性)和A(可用性)之间二选一:

  • 选C:拒绝部分请求,保证数据一致,但牺牲可用性(比如银行转账,宁可失败也不能错)
  • 选A:继续响应请求,保证服务可用,但可能出现数据不一致(比如点赞数,短暂不一致可接受)

实际取舍场景:

  • CP架构:Zookeeper、HBase、Redis Sentinel——数据一致性优先,比如金融账户、库存扣减
  • AP架构:Cassandra、Eureka、DNS——可用性优先,比如社交动态、商品推荐、浏览记录

面试加分项:强调"BASE理论是对CAP的补充",AP系统通过最终一致性在可用性和一致性之间找到平衡点。


【面试题】2. Redis分布式锁用SET NX EX实现,Redisson又做了哪些增强?为什么生产环境推荐用Redisson? ​

【标准答案】原生SET NX EX有三个致命问题:

问题1:锁超时误释放 业务执行时间超过锁过期时间,锁自动释放,其他线程拿到锁,导致并发问题。

问题2:锁误删 线程A的锁过期释放后,线程A去删锁,实际删的是线程B的锁。

Redisson的三大增强:

  1. 看门狗机制:后台线程自动续期,业务没执行完锁不会过期(默认续期到30秒)
  2. Lua脚本原子操作:加锁、解锁用Lua保证原子性,避免误删别人的锁
  3. 可重入锁支持:同一线程可多次获取同一把锁,避免死锁

为什么生产环境用Redisson:

  • 开箱即用,不用手写复杂的续期逻辑
  • 支持多种锁类型:公平锁、读写锁、红锁(Redlock)
  • 内置连接池、重试机制,性能更优

一句话总结:SET NX EX能跑Demo,Redisson才能跑生产。


【面试题】3. TCC分布式事务模式下,Try阶段失败后,是直接回滚还是等Confirm?如果Confirm也失败了怎么处理? ​

【标准答案】TCC的核心是"预留资源+最终确认",每个阶段的失败处理不同:

Try阶段失败:

  • Try失败说明资源预留不成功,直接返回失败,不进入Confirm
  • 业务层面回滚,撤销已做的Try操作
  • 不需要调用Cancel,因为Try失败了,资源没预留成功

Confirm阶段失败:

  • Confirm失败是TCC最复杂的情况,不能简单回滚(因为Try已经成功,资源已预留)
  • 解决方案:重试机制——事务协调器不断重试Confirm,直到成功或超过最大重试次数
  • 最终兜底:超过重试次数后,转人工处理或触发报警

Cancel阶段失败:

  • 同样需要重试机制,不断重试Cancel
  • 记录失败日志,用于后续补偿或人工介入

关键设计要点:

  • Confirm和Cancel必须幂等——重试不会产生副作用
  • Try、Confirm、Cancel都要有明确的业务含义,不是简单的begin/commit/rollback
  • 补偿逻辑要考虑并发场景,避免数据不一致

一句话总结:Try失败直接回滚,Confirm/Cancel失败要重试到底,幂等是底线。


【面试题】4. Snowflake雪花算法生成ID,时钟回拨会导致什么问题?线上出现时钟回拨怎么解决? ​

【标准答案】时钟回拨的问题: Snowflake依赖时间戳生成ID,时钟回拨会导致:

  1. ID重复:回拨后时间戳变小,可能生成与之前相同的ID
  2. ID不递增:违背ID单调递增的期望,影响数据库索引性能(B+树页分裂)

解决方案:

方案1:拒绝生成,直接报错 检测到回拨后,拒绝生成ID,抛异常或返回错误码。简单粗暴,但影响可用性。

方案2:等待追平 回拨幅度小(比如<5ms),让线程等待,等时间追上上次生成时间再生成ID。适合短暂回拨。

方案3:回拨补偿(推荐生产使用)

  • 维护上次生成时间戳lastTimestamp
  • 回拨时,使用lastTimestamp + 1作为当前时间戳,保证ID递增
  • 设置最大回拨阈值(比如5ms),超过则报错

方案4:WorkerId动态调整 回拨时,临时切换到备用WorkerId,避免ID冲突。适合多机房部署。

生产环境最佳实践:

  • 禁用NTP自动同步,改为手动同步
  • 使用改进版雪花算法:百度UidGenerator、美团Leaf,内置时钟回拨处理
  • 监控时钟偏差,设置告警阈值

【面试题】5. 缓存穿透、缓存击穿、缓存雪崩,这三者的区别是什么?分别怎么解决? ​

【标准答案】三者的核心区别:

问题场景根因
缓存穿透查询不存在的数据,缓存和数据库都没有恶意攻击、爬虫
缓存击穿热点key过期,大量请求同时击穿到数据库热点数据过期
缓存雪崩大量key同时过期,或Redis宕机过期时间集中、Redis故障

解决方案:

缓存穿透:

  • 布隆过滤器:请求前先判断数据是否存在,不存在直接返回
  • 空值缓存:查询为空也缓存,设置较短TTL(比如2分钟)
  • 参数校验:接口层过滤非法请求(比如id=-1)

缓存击穿:

  • 热点数据永不过期:逻辑过期,后台异步更新
  • 互斥锁:只让一个请求去查数据库,其他等待
  • 提前预热:活动前主动加载热点数据

缓存雪崩:

  • 过期时间随机化:基础时间+随机偏移,避免集中过期
  • 多级缓存:本地缓存(Caffeine)+ 分布式缓存(Redis)
  • Redis高可用:主从+哨兵/Cluster,避免单点故障
  • 熔断降级:数据库压力过大时,触发熔断保护

一句话总结:穿透加布隆过滤器,击穿加锁或永不过期,雪崩要分散过期+多级缓存。


【面试题】6. 单点登录SSO的完整流程是怎样的?Token过期后如何无感刷新? ​

【标准答案】SSO完整流程(以CAS为例):

  1. 用户访问系统A,系统A检测未登录,302重定向到SSO认证中心
  2. SSO认证中心检测未登录,展示登录页面
  3. 用户输入账号密码,认证成功,SSO生成全局Ticket(TGT),写入SSO的Cookie(SSO-SessionId)
  4. SSO携带Ticket重定向回系统A(携带参数ticket=xxx)
  5. 系统A拿Ticket去SSO验证,验证成功后,系统A生成自己的Token,写入系统A的Cookie
  6. 用户再访问系统B,系统B检测未登录,重定向到SSO
  7. SSO检测已有全局会话(SSO-SessionId存在),直接签发Ticket给系统B
  8. 系统B验证Ticket,生成自己的Token,用户无感登录成功

Token无感刷新机制:

方案1:双Token机制(Access Token + Refresh Token)

  • Access Token:短有效期(比如2小时),用于接口访问
  • Refresh Token:长有效期(比如7天),用于刷新Access Token
  • Access Token快过期时,前端用Refresh Token换取新的Access Token
  • Refresh Token过期后,需要重新登录

方案2:Token快过期自动续期

  • 后端记录Token最后访问时间
  • 每次请求检查:距离过期时间<30分钟,自动续期(延长过期时间)
  • 用户活跃期间Token不会过期,长时间不活跃才过期

方案3:滑动窗口机制

  • 每次请求刷新Cookie中的过期时间
  • 类似Redis的expire命令,持续访问就持续续期

生产建议:双Token方案安全性更高,适合金融场景;自动续期方案用户体验更好,适合内容型应用。


【面试题】7. 分布式限流,固定窗口和滑动窗口的区别?为什么生产环境推荐用令牌桶? ​

【标准答案】三种限流算法对比:

固定窗口计数器:

  • 原理:每分钟重置计数器,请求次数超过阈值则拒绝
  • 问题:临界时刻突刺——第59秒来了100个请求,第1秒又来100个,实际QPS=200/秒,超过阈值
  • 适用场景:对精度要求不高的简单限流

滑动窗口:

  • 原理:将时间窗口分成多个小格子(比如1分钟分成6个10秒格子),窗口向前滑动
  • 解决突刺问题:更精确统计最近1分钟的请求数
  • 问题:内存占用高(每个格子都要维护计数器),实现复杂

令牌桶(推荐生产使用):

  • 原理:以固定速率向桶中放令牌,请求到来时从桶中取令牌,取到则通过,取不到则拒绝
  • 允许突发流量:桶中累积的令牌可以一次性消耗,应对瞬时高并发
  • 实现简单:Guava RateLimiter、Redis+Lua都能轻松实现
  • 参数灵活:QPS=令牌生成速率,burst=桶容量

漏桶算法:

  • 原理:请求先进队列,以固定速率从队列中取出处理
  • 强制平滑流量:无法应对突发,适合保护下游系统(比如数据库)
  • 与令牌桶区别:令牌桶允许突发,漏桶强制匀速

生产选择:

  • 令牌桶:网关层限流、API限流(允许一定突发)
  • 漏桶:数据库保护、下游系统保护(强制匀速)
  • 滑动窗口:精确限流、防止临界突刺

一句话总结:固定窗口简单但不准,滑动窗口准但复杂,令牌桶兼顾精度和性能,生产首选。


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

【标准答案】Raft Leader选举流程:

初始状态:所有节点都是Follower,每个节点维护一个选举超时时间(随机150-300ms)

选举触发:

  1. Follower在选举超时时间内没有收到Leader心跳,转为Candidate
  2. Candidate自增Term(任期号),给自己投一票
  3. 向其他节点发送RequestVote RPC(请求投票)

投票规则:

  • 每个Term每个Follower只能投一票,先到先得
  • Candidate的日志必须比Follower新(至少不落后),Follower才投票
  • 获得多数票(N/2+1)的Candidate成为Leader

Leader上任后:

  • 立即向所有节点发送心跳(AppendEntries RPC),确立权威
  • 后续所有客户端请求都通过Leader处理

多个Candidate同时选举(Split Vote):

  • 场景:多个Follower同时超时,转为Candidate,票数分散,无人获得多数票
  • 解决:选举超时时间随机化(150-300ms随机),大概率某个Candidate先超时完成选举
  • 如果真的平票:Term自增,重新选举(概率极低)

关键设计:

  • 随机超时时间:避免所有Follower同时超时,减少选举冲突
  • Term单调递增:过期的Leader收到更高Term的消息会自动降级
  • 日志完整性校验:保证新Leader的日志最完整

一句话总结:超时转Candidate→拉票→多数通过→心跳确立。随机超时时间避免Split Vote,日志校验保证Leader数据完整。


【面试题】9. Zookeeper的Watcher监听机制是一次性的吗?如果需要持续监听怎么设计? ​

【标准答案】Watcher是一次性的:

官方设计如此:Watcher触发一次后就会失效,需要重新注册。原因是避免频繁的事件通知导致性能问题。

一次性Watcher的问题:

  1. 事件丢失风险:Watcher触发后,重新注册前,中间发生的数据变化无法感知
  2. 并发注册问题:多线程环境下,Watcher触发和重新注册之间有间隙

持续监听的设计方案:

方案1:Watcher回调中重新注册

java
zk.getData(path, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
        // 处理事件
        handleEvent(event);
        // 重新注册Watcher
        zk.getData(path, this, null, null);
    }
}, null, null);

问题:重新注册和下一次事件之间仍有微小时间窗口。

方案2:使用Curator的NodeCache(推荐) Curator封装了持续监听,内部自动处理重新注册:

java
NodeCache nodeCache = new NodeCache(client, path);
nodeCache.getListenable().addListener(() -> {
    // 持续监听,不用手动重新注册
    ChildData data = nodeCache.getCurrentData();
});
nodeCache.start();

方案3:双重保障——Watcher + 定时轮询

  • Watcher处理实时事件
  • 定时轮询兜底,防止Watcher失效期间的事件丢失
  • 适合关键业务数据监听

方案4:持久化监听(Zookeeper 3.6+新特性)

  • addWatch API支持持久化监听,不需要重新注册
  • 需要Zookeeper版本支持

生产建议:用Curator的NodeCache/TreeCache,开箱即用,不用手写复杂的重新注册逻辑。


【面试题】10. 微服务架构中,服务熔断、服务降级、服务限流,三者的区别和使用场景是什么? ​

【标准答案】三者核心区别:

机制目的触发条件效果
熔断保护系统,防止故障蔓延下游服务错误率/超时率超过阈值快速失败,不再调用下游
降级保核心业务,牺牲非核心系统负载高、资源紧张返回兜底数据或简化逻辑
限流防止流量过载QPS超过系统承载能力拒绝部分请求或排队等待

熔断器原理(类似电路保险丝):

  • 关闭状态:正常调用下游服务
  • 打开状态:错误率超阈值,熔断器打开,后续请求直接返回失败,不再调用下游
  • 半开状态:熔断一段时间后,放少量请求试探,成功则关闭,失败则重新打开

降级的常见场景:

  • 页面降级:推荐服务挂了,展示热门商品兜底
  • 接口降级:评论服务挂了,返回"评论加载中"或关闭评论入口
  • 读降级:读从库失败,读本地缓存

限流的典型场景:

  • 秒杀活动:限制每秒处理1000个请求,超出的返回"排队中"
  • API网关:防止恶意爬虫或DDoS攻击
  • 数据库保护:限制数据库连接数,避免连接池耗尽

三者配合使用(Sentinel/Hystrix):

  1. 先限流:QPS超阈值,直接拒绝,保护系统不过载
  2. 再熔断:错误率超阈值,熔断下游,防止故障蔓延
  3. 最后降级:熔断或限流触发后,返回兜底数据,保证用户体验

一句话总结:限流防过载,熔断防蔓延,降级保体验。三者层层递进,共同守护系统稳定性。


题目统计:本次生成10道题目,覆盖:

  • 分布式理论:CAP定理取舍
  • 分布式锁:Redisson增强原理
  • 分布式事务:TCC失败处理
  • 分布式ID:时钟回拨解决
  • 分布式缓存:穿透/击穿/雪崩
  • 分布式会话:SSO流程+Token刷新
  • 分布式限流:三种算法对比
  • 一致性算法:Raft选举流程
  • 分布式协调服务:Zookeeper持续监听
  • 微服务治理:熔断/降级/限流对比