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的三大增强:
- 看门狗机制:后台线程自动续期,业务没执行完锁不会过期(默认续期到30秒)
- Lua脚本原子操作:加锁、解锁用Lua保证原子性,避免误删别人的锁
- 可重入锁支持:同一线程可多次获取同一把锁,避免死锁
为什么生产环境用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,时钟回拨会导致:
- ID重复:回拨后时间戳变小,可能生成与之前相同的ID
- 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为例):
- 用户访问系统A,系统A检测未登录,302重定向到SSO认证中心
- SSO认证中心检测未登录,展示登录页面
- 用户输入账号密码,认证成功,SSO生成全局Ticket(TGT),写入SSO的Cookie(SSO-SessionId)
- SSO携带Ticket重定向回系统A(携带参数ticket=xxx)
- 系统A拿Ticket去SSO验证,验证成功后,系统A生成自己的Token,写入系统A的Cookie
- 用户再访问系统B,系统B检测未登录,重定向到SSO
- SSO检测已有全局会话(SSO-SessionId存在),直接签发Ticket给系统B
- 系统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)
选举触发:
- Follower在选举超时时间内没有收到Leader心跳,转为Candidate
- Candidate自增Term(任期号),给自己投一票
- 向其他节点发送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的问题:
- 事件丢失风险:Watcher触发后,重新注册前,中间发生的数据变化无法感知
- 并发注册问题:多线程环境下,Watcher触发和重新注册之间有间隙
持续监听的设计方案:
方案1:Watcher回调中重新注册
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封装了持续监听,内部自动处理重新注册:
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):
- 先限流:QPS超阈值,直接拒绝,保护系统不过载
- 再熔断:错误率超阈值,熔断下游,防止故障蔓延
- 最后降级:熔断或限流触发后,返回兜底数据,保证用户体验
一句话总结:限流防过载,熔断防蔓延,降级保体验。三者层层递进,共同守护系统稳定性。
题目统计:本次生成10道题目,覆盖:
- 分布式理论:CAP定理取舍
- 分布式锁:Redisson增强原理
- 分布式事务:TCC失败处理
- 分布式ID:时钟回拨解决
- 分布式缓存:穿透/击穿/雪崩
- 分布式会话:SSO流程+Token刷新
- 分布式限流:三种算法对比
- 一致性算法:Raft选举流程
- 分布式协调服务:Zookeeper持续监听
- 微服务治理:熔断/降级/限流对比