本轮 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的三大问题:
- 锁续期问题:业务执行时间超过锁过期时间,锁自动释放,其他线程获得锁,导致并发问题。
- 误删锁问题:线程A的锁过期自动释放,线程B获得锁,线程A执行完删除了线程B的锁。
- 不可重入:同一线程无法重复获取同一把锁。
Redisson解决方案:
- Watch Dog看门狗机制:后台线程定期续期(默认每10秒续期30秒),业务没执行完锁不会过期。
- Lua脚本原子删除:删除锁时校验锁的持有者,只能删除自己的锁。
- Hash结构实现可重入:锁名作为key,value用Hash存储线程ID和重入次数。
实际项目中,如果只是简单防重,SET NX EX够用;但涉及复杂业务、长时间任务,必须用Redisson。
【面试题】3. TCC分布式事务模式如何保证数据一致性?有什么缺点?
【标准答案】TCC工作原理:
- Try阶段:预留资源,做业务检查,不真正执行业务。比如转账场景,冻结金额而非直接扣款。
- Confirm阶段:确认执行,真正执行业务操作,使用Try阶段预留的资源。
- Cancel阶段:取消执行,释放Try阶段预留的资源,做回滚操作。
一致性保证:
- 幂等性设计:Confirm/Cancel可能重试,必须幂等。
- 空回滚处理:Try没执行成功,收到Cancel请求要能处理。
- 悬挂问题:Try比Cancel先到(网络原因),Cancel后要记录状态,拒绝后续Try。
缺点:
- 业务侵入性强,每个接口都要写三套逻辑。
- 开发成本高,需要设计补偿逻辑。
- 空回滚、悬挂等边界情况处理复杂。
适用场景:强一致性要求、跨数据库、跨服务、长事务场景。如果是简单场景,推荐用Seata的AT模式。
【面试题】4. Snowflake雪花算法生成分布式ID,时钟回拨问题如何解决?
【标准答案】雪花算法结构:1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号,理论上支持每毫秒4096个ID,69年不重复。
时钟回拨问题:机器时间被回调,可能导致ID重复或乱序。
解决方案:
- 等待策略:检测到时间回拨,线程等待直到时间追上上次ID生成时间。适合小幅度回拨(毫秒级)。
- 拒绝策略:回拨超过阈值直接报错,依赖外部监控告警。
- 时钟回拨保护:美团的Leaf、百度的UidGenerator都有实现,记录上次生成时间戳,回拨时借用未来时间或走备用方案。
生产建议:
- 机器ID从配置中心或Zookeeper分配,避免冲突。
- 关闭NTP自动同步,或使用渐进式时间调整。
- 监控ID生成器的时钟偏差,及时告警。
面试时可以补充:雪花ID不是严格递增的,是趋势递增,适合MySQL主键,但不适合做排序依据。
【面试题】5. 缓存穿透、缓存击穿、缓存雪崩分别是什么?解决方案?
【标准答案】三种缓存失效场景:
| 问题 | 定义 | 核心原因 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,缓存和数据库都没有 | 恶意请求、爬虫 |
| 缓存击穿 | 热点Key过期瞬间,大量请求穿透到数据库 | 热点数据过期 |
| 缓存雪崩 | 大量Key同时过期,或Redis宕机 | 过期时间集中、单点故障 |
解决方案:
缓存穿透:
- 布隆过滤器:拦截不存在的Key(推荐RedisBloom模块)。
- 空值缓存:查不到的数据也缓存,设置较短过期时间。
- 参数校验:在入口层拦截明显非法请求。
缓存击穿:
- 热点数据永不过期:设置为逻辑过期,后台异步刷新。
- 互斥锁:只允许一个线程查库,其他等待。
- 提前预热:热点数据提前加载。
缓存雪崩:
- 过期时间随机化:基础过期时间+随机值,避免集中失效。
- 多级缓存:本地缓存+分布式缓存。
- Redis高可用:哨兵或Cluster模式。
- 熔断降级:数据库压力过大时熔断,返回降级数据。
【面试题】6. Raft算法的Leader选举过程是怎样的?如何保证日志一致性?
【标准答案】Raft节点状态:Follower(追随者)、Candidate(候选人)、Leader(领导者)。
Leader选举过程:
- 初始都是Follower,设置随机超时时间(150-300ms)。
- Follower超时未收到Leader心跳,转为Candidate,任期+1,投自己一票。
- Candidate向其他节点发送RequestVote请求。
- 获得多数票(N/2+1)成为Leader,立即发送心跳确立地位。
- 如果选举超时,任期+1重新选举。
日志一致性保证:
- Leader接收客户端请求,追加到本地日志,并行发送AppendEntries给所有Follower。
- 多数Follower确认后,Leader提交日志,响应客户端。
- Leader维护nextIndex和matchIndex,处理日志不一致:
- Follower日志落后:Leader发送缺失的日志。
- Follower日志冲突:Follower删除冲突日志,同步Leader日志。
- 已提交的日志不会丢失,最多一个任期一个Leader。
面试要点:Raft比Paxos更易理解和实现,是工程实践的首选,广泛应用于Etcd、Consul等系统。
【面试题】7. 分布式Session共享有哪些方案?各自的优缺点?
【标准答案】四种主流方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Session复制 | 集群中每台服务器复制Session | 简单、无单点 | 内存消耗大、延迟高 |
| Session粘性 | 负载均衡固定用户到同一服务器 | 无需改动代码 | 服务器宕机Session丢失 |
| Session集中存储 | Session存到Redis/Memcached | 可扩展、支持故障转移 | 增加网络开销 |
| JWT无状态 | Token存客户端,服务端无状态 | 无存储压力、天然分布式 | 无法主动失效、Token较大 |
生产推荐方案:
- Spring Session + Redis:业务无感知,自动将Session存Redis,支持故障转移。
- JWT + 刷新机制:Access Token短期+Refresh Token长期,适合前后端分离架构。
JWT方案细节:
- Access Token有效期15-30分钟,Refresh Token有效期7-30天。
- Refresh Token存Redis,可主动吊销。
- 登出时将Refresh Token加入黑名单。
面试时强调:没有完美方案,根据业务规模和团队技术栈选择。
【面试题】8. 令牌桶和漏桶算法的区别?分布式限流如何实现?
【标准答案】两种算法对比:
| 特性 | 令牌桶 | 漏桶 |
|---|---|---|
| 处理突发流量 | 允许(桶内有令牌时) | 不允许(恒定速率) |
| 流量整形 | 不严格,允许短期超限 | 严格,强制恒定速率 |
| 实现复杂度 | 中等 | 较低 |
| 适用场景 | API限流、允许突发 | 保护后端服务、流量平滑 |
令牌桶原理:系统以固定速率生成令牌放入桶中,请求到来时从桶取令牌,有令牌则通过,无令牌则拒绝或等待。桶满则令牌丢弃。
漏桶原理:请求进入漏桶,漏桶以固定速率流出请求。桶满则拒绝新请求。
分布式限流实现:
- Redis + 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- Sentinel/Resilience4j:成熟的限流框架,支持多种规则配置。
- Gateway层限流:在API网关统一限流,减少后端压力。
生产建议:热点接口用Redis+Lua,全局限流用Sentinel,网关层做兜底保护。
【面试题】9. Zookeeper的Watch机制有什么特点?为什么不适合做消息队列?
【标准答案】Watch机制特点:
- 一次性触发:Watch被触发后立即失效,需要重新注册。这是性能优化设计,避免大量持久监听。
- 客户端回调:Watch事件通知到达客户端后,客户端才收到回调,存在网络延迟。
- 异步通知:服务端状态变更到客户端收到通知是异步的,不能保证实时性。
- 轻量级:Watch只通知事件类型,不传数据,减少网络开销。
Watch使用场景:
- 配置中心:配置变更通知。
- 服务注册发现:服务上下线通知。
- 分布式锁:锁释放通知。
- Leader选举:节点变更通知。
为什么不适合做消息队列:
- Watch一次性:消息可能丢失,没有持久化确认机制。
- 容量有限:ZNode有大小限制(默认1MB),不适合存大量数据。
- 性能瓶颈:ZK集群写入性能有限(单机几千TPS),无法支撑高吞吐。
- 无消息模型:缺少消费组、消息重试、死信队列等消息队列必备特性。
- 通知延迟:Watch通知延迟不固定,不适合实时消息场景。
正确选择:配置管理、分布式协调用ZK,消息队列用Kafka/RocketMQ/Pulsar。
【面试题】10. XXL-Job分布式任务调度的执行器集群是如何工作的?任务分片怎么做?
【标准答案】XXL-Job架构:
- Admin:调度中心,负责任务调度、触发、监控。
- Executor:执行器,执行具体任务,可集群部署。
执行器集群工作原理:
- 执行器启动时向Admin注册(心跳续期)。
- Admin根据路由策略选择执行器下发任务。
- 执行器执行任务,回调结果给Admin。
- 执行器宕机时Admin自动剔除,故障转移。
路由策略:
- 轮询:依次分配,适合任务量均匀场景。
- 随机:随机选择执行器。
- 一致性哈希:同一任务ID始终路由到同一执行器。
- 分片广播:所有执行器都执行,配合分片参数做数据分片。
- 故障转移:按顺序尝试,失败则切换。
任务分片实现:
@XxlJob("dataProcessJob")
public void dataProcessJob() {
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号
int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
// 根据分片参数处理数据
List<Data> dataList = queryData(shardIndex, shardTotal);
processData(dataList);
}分片广播原理:
- Admin广播任务到所有执行器。
- 每个执行器获得不同的shardIndex(0, 1, 2...)。
- 执行器根据分片参数处理各自的数据子集。
- 各执行器独立执行,互不干扰。
适用场景:大数据批量处理、报表生成、数据同步等可拆分的任务。
生成时间:2026-05-21 22:41