每年到面试季,总能看到同一类求助:分布式这块知识点太散,Netty、分布式锁、分布式事务、分库分表、分布式 ID 各看各的,看完就忘。这次整理的是一套分布式方向的高频面试题合集,一共 65 问,按六个模块组织,覆盖分布式基础与高频场景、Netty 与网络编程、分布式锁、分布式事务、分库分表、分布式 ID。每一问都给出核心回答框架和加分点,重点不是让你背答案,而是让你知道面试官问“CAP 为什么无法三者兼得”“雪花算法时钟回拨怎么办”时,真正想听到的是哪一层内容。
如果你正在准备 2026 年的后端开发、分布式中间件或高级 Java 岗位面试,这套题单可以作为自测清单直接使用;如果你已经工作两年以上,想系统检查自己对分布式体系的掌握情况,用它做查漏补缺也很合适。全文按“整体结构 -> 分模块真题解析 -> 自我测试与复习路线”组织,建议先看答案解析,再脱离答案逐题口述。
文中对每个模块都会先给出考查方向和难度,再挑最常考的几题做深度解析,最后列清该模块剩下的题目清单。下面的内容会直接进入正题,不铺垫背景,也不绕弯子。
1. 65 问整体结构与能力覆盖
| 模块 | 题目数 | 难度 | 考查频率 | 核心考点 |
|---|---|---|---|---|
| 分布式基础与高频场景 | 10 | 低到中 | 必考 | CAP、BASE、共识算法、Session、幂等 |
| Netty 与网络编程 | 10 | 中到高 | 高 | 线程模型、粘包拆包、零拷贝、心跳 |
| 分布式锁 | 10 | 中 | 必考 | Redis、Redisson、ZooKeeper 实现与选型 |
| 分布式事务 | 15 | 高 | 高 | 2PC、TCC、SAGA、Seata、事务消息 |
| 分库分表 | 12 | 中到高 | 高 | 路由策略、跨库查询、扩容、中间件 |
| 分布式 ID | 8 | 中 | 必考 | 雪花、号段、时钟回拨 |
从结构上能看出三个规律。第一,分布式基础概念题数量不大,但它是其他所有模块的地基,建议最先过一遍;第二,分布式事务是题目最重的模块,因为它的变体最多,2PC、TCC、SAGA、本地消息表、事务消息、Seata 每一种都得说清原理差异和适用场景;第三,分布式锁和分布式 ID 属于“必考但题量小”的模块,适合用碎片时间反复刷。
这套题单的能力覆盖可以按四个层级划分。第一层是概念理解,比如 CAP 的定义;第二层是原理推导,比如为什么 2PC 会被 3PC 改进;第三层是方案对比,比如 Redis 锁和 ZooKeeper 锁怎么选;第四层是工程落地,比如分库分表后怎么平滑扩容。面试里从第二层开始才是分水岭,只停留在第一层概念记忆,大概率过不了二面。
2. 第一模块:分布式基础与高频场景(10 问)
这个模块是所有分布式知识的入口。面试官用这几道题判断你的基本功是否扎实,回答要求是“定义准确、例子常见、取舍清楚”。
2.1 CAP 为什么无法三者兼得
问题 1:CAP 定理怎么理解?为什么无法同时满足?
先给出定义:C 是一致性,所有节点在同一时刻看到同一份数据;A 是可用性,每个请求都能收到非错误的响应;P 是分区容错性,网络分区发生时系统依然能工作。核心观点是:分布式系统中网络分区是统计学上必然出现的事件,P 必须选,因此真正的问题变成分区发生时选 C 还是选 A。如果选 C,分区期间需要拒绝部分请求来等待数据对齐,可用性受损;如果选 A,各分区继续独立服务,数据可能不一致。
加分点是补一句“CAP 不是三选二,而是分区发生时二选一;系统正常运行时 C 和 A 可以同时满足”。再带上常见例子:ZooKeeper 是 CP 倾向,Eureka 是 AP 倾向,RocketMQ 在有主从副本策略时也需要做一致性取舍。扣分点是把 CAP 说成“三个只能选两个”,这是最常见的错误理解。
2.2 Raft 与 Paxos 的区别
问题 2:Raft 和 Paxos 的定位有什么区别?为什么推荐先学 Raft?
Paxos 是 Leslie Lamport 提出的共识算法家族,理论严谨但实现难度极高,原始论文里 Multi-Paxos 的细节需要自行推导,工业落地时每个团队的理解可能都不一样。Raft 把共识问题分解成领导者选举、日志复制、安全性三个子问题,并引入任期和随机超时选举机制,让算法可理解和可实现。回答时要指出一个关键差异:Paxos 允许任意节点发起提案,多提案需要 quorum 仲裁;Raft 强制同一任期只有一个 Leader,所有写请求经由 Leader 复制到 Follower,因此工程上更可控。
面试官通常会追加一题:Raft 怎么保证日志不会丢失?回答方向是“Leader 选举时候选者必须包含全部已提交日志,日志提交需要大多数节点确认,Follower 落后会持续追赶日志”。能把这个追加题答好,说明你对共识不是停留在概念记忆。
2.3 脑裂与缓存一致性
问题 3:分布式系统为什么会脑裂?Raft 如何解决?
脑裂指节点间网络中断后,多个分区同时认为自己是主节点。Raft 的解决思路是任期与多数派:每个任期最多一个 Leader 能获得大多数节点投票,网络分区后少数派无法获得过半数赞成,也就无法产生有效 Leader;同时旧 Leader 收到更高任期的请求后会主动让位。实践中还会搭配租约、fencing token 等机制,防止旧 Leader 在网络恢复前继续写数据。
问题 4:缓存与数据库的一致性怎么保证?
这是高频场景题。读路径用 Cache Aside 模式,先读缓存,未命中则读数据库并回填;写路径先更新数据库再删除缓存,而不是先删缓存再更新数据库,因为后一种会引入更大的并发窗口。更严格的做法是延迟双删,或删除失败时通过 MQ 重试;追求强一致则用 Canal 订阅 binlog 同步缓存,并接受引入组件的复杂度。加分点:说明删除缓存优于更新缓存,原因在于更新缓存需要处理并发写覆盖和缓存与库的不一致窗口。
2.4 本模块完整题目清单
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | 什么是分布式系统,它要解决什么问题? | 口述 3 分钟 |
| 2 | CAP 为什么无法三者兼得? | 需要讲出分区案例 |
| 3 | BASE 理论和 ACID 是什么关系? | 对应到具体系统 |
| 4 | Raft 和 Paxos 的区别? | 讲出 Leader 与任期 |
| 5 | 脑裂是怎么发生的?怎么解决? | 结合 Raft 回答 |
| 6 | 数据副本同步的几种方式? | 同步/异步/半同步 |
| 7 | 分布式 Session 有哪些方案? | Redis、粘性、复制 |
| 8 | 缓存与数据库一致性怎么保证? | Cache Aside + 删缓存 |
| 9 | 接口幂等怎么设计? | 唯一索引、状态机 |
| 10 | 负载均衡策略有哪些? | 结合网关场景说明 |
3. 第二模块:Netty 与网络编程(10 问)
Netty 题面试官一般用来考查候选人对高性能网络编程的理解,回答重点从“会用 API”上升到“理解线程模型和内存模型”。这组题不是背 API,而是讲清楚 Netty 为什么快、快在哪里、边界在哪里。
3.1 线程模型与 Reactor
问题 1:Netty 相比 Java NIO 解决了什么问题?
Java NIO 的 Selector 只提供了多路复用基础能力,使用起来要自己处理线程模型、ByteBuffer 复用、半包读写等问题,稍不注意还会出现线程安全和内存释放错误。Netty 在此基础上封装了完整的 Reactor 线程模型、责任链 Pipeline、自动拆包解码器以及池化内存管理,让开发者关注业务编解码而不是底层轮询细节。回答时建议从三方面对比:API 友好度、线程模型升级、内存管理能力。
问题 2:Netty 的线程模型是什么?
核心是 Reactor 模型,常见三种形态。单线程 Reactor:一个线程同时处理 accept、read、write,适合连接数极少的场景;多线程 Reactor:一个 acceptor 线程接受连接,一组 worker 线程处理 IO;主从 Reactor:对应 Netty 的 BossGroup 和 WorkerGroup,Boss 负责 accept,Worker 负责 read/write,业务解码再丢到单独的业务线程池。加分点:说明 Netty 的每个 EventLoop 绑定一个线程,且一个 Channel 生命周期内的 IO 事件都在同一个 EventLoop 上执行,因此不需要加锁;同时强调不要在 IO 线程中做耗时业务,否则会阻塞该 EventLoop 上的所有 Channel。
3.2 粘包拆包与解码器
问题 3:TCP 粘包拆包是什么原因?Netty 怎么解决?
粘包指多个消息被合并在一个包中,拆包指一个消息被拆到多个包中,根因是 TCP 是字节流协议,应用层没有消息边界。Netty 解决方式是使用解码器:LineBasedFrameDecoder 按换行符切分,DelimiterBasedFrameDecoder 按自定义分隔符切分,FixedLengthFrameDecoder 按固定长度切分,LengthFieldBasedFrameDecoder 在消息头存储消息长度,是最通用的方案。回答时重点说最后一种,并说明 lengthFieldOffset、lengthFieldLength 等关键参数配置;加分点是结合自定义协议设计,比如 4 字节 magic + 1 字节版本 + 4 字节长度 + body。
3.3 零拷贝与心跳机制
问题 4:Netty 的零拷贝体现在哪些方面?
在 Netty 语境下零拷贝至少包含三层意思。第一层是 ByteBuf 的引用计数和池化复用,避免反复创建和释放直接内存;第二层是 CompositeByteBuf 可以把多个缓冲区组合成一个逻辑缓冲区,避免多次遍历;第三层是 FileRegion 配合 NIO 的 transferTo,让文件发送从用户态直接到内核态,减少拷贝次数。加分点:把零拷贝和内核态、用户态结合说明,面试官会更认可深度。
问题 5:心跳和断线重连怎么设计?
心跳用 IdleStateHandler 检测读写空闲,空闲超时后触发事件,由 ChannelHandler 关闭异常连接或发送 Ping 探测。断线重连一般放在客户端:捕获连接断开异常后使用指数退避策略重连,同时注意连接状态标记,避免重复发起重连。更完整的方案是服务端定期清理死连接、客户端与服务端互相 ping-pong,并配合重连次数上限。
3.4 本模块完整题目清单
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | Netty 相比 Java NIO 的优势? | 线程模型 + 内存 |
| 2 | Reactor 模型三种形态分别适用什么场景? | 画图说明 |
| 3 | Channel、Pipeline、ChannelHandler 的关系? | 责任链思想 |
| 4 | TCP 粘包拆包的原因和解决方式? | LengthField 必会 |
| 5 | Netty 零拷贝体现在哪些方面? | 至少说三层 |
| 6 | 心跳与断线重连怎么实现? | 指数退避 |
| 7 | ByteBuf 相比 ByteBuffer 优势? | 动态扩容、池化 |
| 8 | EventLoop 绑定线程有什么好处? | 无锁串行 |
| 9 | 什么是背压,Netty 怎么处理? | 写缓冲高水位 |
| 10 | 为什么 RPC 框架用 Netty 做通信? | 性能 + 扩展性 |
4. 第三模块:分布式锁(10 问)
分布式锁是工作中用得最多、面试最常考的模块,考查重点集中在三件事:为什么加过期时间、释放锁为什么用 Lua、Redis 和 ZooKeeper 怎么选。
4.1 Redis 锁的基础实现
问题 1:基于 Redis 的分布式锁怎么实现?
最基础方案是 SET key value NX PX 30000:NX 保证只有 key 不存在时才能设置成功,PX 设置过期时间避免死锁,value 使用客户端唯一标识。加锁成功后返回业务执行;释放锁时必须先比较 value 是否自己的锁,匹配才删除,直接给出 Lua 脚本:
-- 释放锁:先校验 value,再删除,保证原子性 if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end强调为什么不用先 get 再 del:两条命令中间可能插入其他请求的删除操作,导致把自己的锁误删。这也是 Lua 脚本在这个场景存在的意义。value 里建议放 UUID 加线程号组成的唯一标识,保证只能释放自己持有的锁。
问题 2:锁过期了业务没执行完怎么办?
这是必搭追问。基础方案是把过期时间设长,或临结束时检查续期;标准方案是 Redisson 的看门狗机制,默认每 10 秒对锁续期一次,把过期时间重置为 30 秒,业务线程退出时自动释放。加分点:说明看门狗同样有边界问题,如果主进程发生长 GC 或网络分区,续期也可能失败,所以任何分布式锁都不能 100% 保证互斥,只能通过 fencing token 等手段规避极端情况。
4.2 ZooKeeper 锁与选型
问题 3:ZooKeeper 分布式锁怎么实现?
利用临时顺序节点:客户端创建 /locks/lock_ 临时顺序节点,然后检查自己是不是序号最小的节点,是则获得锁;不是则监听前一个节点,前一个节点删除后重新尝试。临时节点的好处是客户端异常退出时节点自动删除,不会出现 Redis 的“锁记录持久存在”问题;同时可以通过 watch 机制实现锁释放通知。缺点是频繁的节点创建删除和 watch 触发对 ZooKeeper 压力较大,性能一般不如 Redis。
4.3 本模块完整题目清单
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | 什么场景需要分布式锁? | 秒杀、任务调度、幂等写 |
| 2 | SETNX 实现锁的核心步骤? | SET NX PX |
| 3 | 为什么必须设置过期时间? | 防死锁 |
| 4 | 锁过期业务没结束怎么办? | 看门狗续期 |
| 5 | 释放锁为什么用 Lua? | 原子性比对删除 |
| 6 | Redisson 可重入锁实现? | Hash + 计数 |
| 7 | RedLock 是什么,有什么问题? | 时钟与多数派 |
| 8 | ZooKeeper 临时顺序节点加锁流程? | 监听前节点 |
| 9 | Redis 和 ZooKeeper 锁怎么选? | 性能 vs 可靠性 |
| 10 | 分布式锁怎么设计续期和容错? | 看门狗 + 降级 |
选型结论可以概括为一句话:业务要求高性能、能接受极端情况小概率失效,选 Redis;业务要求强可靠、服务治理基础设施里已有 ZooKeeper,选 ZooKeeper。无论选哪种,都不能替代业务层的幂等设计,锁只是第一道防线。
5. 第四模块:分布式事务(15 问)
分布式事务是 65 问里分量最重的一块,15 道题覆盖从理论到框架的全链路。这里的总纲是:先判断业务要强一致还是最终一致,再选方案。
5.1 2PC 与 TCC 的原理和问题
问题 1:2PC 的流程是什么?有什么缺陷?
两阶段提交分为准备阶段和提交阶段。协调者先向所有参与者发 prepare,参与者执行事务但先不提交,返回 ready 或 abort;如果全部 ready,协调者发 commit,否则发 rollback。缺陷从三方面说:一是同步阻塞,prepare 之后参与者占用资源等待协调者决策;二是协调者单点,协调者宕机时参与者卡在阻塞状态;三是数据不一致,commit 阶段网络断裂时部分参与者提交、部分没有提交,无法通过 2PC 自身修复。
问题 2:TCC 模式的核心思想和常见坑?
TCC 把每个业务操作拆成 Try、Confirm、Cancel 三步。Try 阶段检查资源并预留,Confirm 阶段真正执行,Cancel 阶段释放预留资源。三个高频坑:空回滚,Try 没执行但 Cancel 被调用,需要根据事务状态判断是否回滚;幂等,Confirm 和 Cancel 可能被重复调用,必须有幂等控制;悬挂,Cancel 先于 Try 执行,导致资源永远无法释放。加分点:说明 TCC 的代码侵入性很高,每个业务接口都要写三个方法,一般只在核心链路使用。
5.2 消息事务与 Seata
问题 3:本地消息表和 RocketMQ 事务消息的区别?
本地消息表是经典方案:业务数据与消息表在同一个本地数据库事务中落库,再通过定时任务扫描消息表发送消息,并根据回执更新消息状态。事务消息方案是 RocketMQ 提供的半消息机制:先发送 half 消息,broker 暂不投递;本地执行完业务后提交二次确认,broker 才真正投递;如果长时间没确认,broker 反向查询业务状态。两者都解决“发消息和本地事务不一致”的问题,区别是本地消息表依赖定时任务有延迟,事务消息由 MQ 端保证状态查询,延迟更小。
应用发送 half 消息 -> 本地业务事务 -> commit/rollback -> broker 投递/删除 -> 未确认时回查业务状态真实业务里还需要配合消费幂等,否则 MQ 重复投递会造成数据重复写入。判断方案优劣时要结合已有中间件:已经引入 RocketMQ,优先事务消息;依赖较少时,本地消息表用数据库就能实现。
问题 4:Seata AT 模式原理是什么?
Seata 是一款分布式事务框架,AT 模式基于数据源代理自动生成 undo_log:业务 SQL 执行前记录前镜像和后镜像,分支事务注册后参与全局提交;回滚时根据 undo_log 反向生成补偿 SQL。和 TCC 相比,AT 模式对业务代码侵入小,只需要在入口方法加 @GlobalTransactional 注解,但要求数据库支持本地事务,且全局锁和 undo_log 会带来额外开销。加分点:说明 AT 模式也有隔离性问题,Seata 通过全局锁避免写冲突,但长时间持有全局锁会影响并发。
5.3 本模块完整题目清单
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | 分布式事务产生的本质原因? | 跨资源无法本地原子 |
| 2 | 2PC 的流程和缺陷? | 阻塞、单点、不一致 |
| 3 | 3PC 解决了 2PC 的哪些问题? | 引入超时避免阻塞 |
| 4 | TCC 空回滚、悬挂、幂等怎么处理? | 事务状态表 |
| 5 | SAGA 模式怎么编排补偿? | 正向 + 反向补偿 |
| 6 | 本地消息表怎么实现? | 同库 + 定时扫描 |
| 7 | RocketMQ 事务消息原理? | half + 回查 |
| 8 | Seata AT 模式如何用 undo_log 回滚? | 前后镜像 |
| 9 | AT 和 TCC 怎么选? | 侵入性 vs 灵活性 |
| 10 | 事务消息和本地消息表的差异? | 延迟与依赖 |
| 11 | 怎么保证事务消息不丢失? | ack + 重试 + 告警 |
| 12 | 事务中的幂等控制? | 唯一键、状态位 |
| 13 | Seata 全局锁与隔离级别? | 写冲突保护 |
| 14 | @GlobalTransactional 完整流程? | TM/RM/TC 交互 |
| 15 | 强一致与最终一致如何权衡? | 业务场景判断 |
6. 第五模块:分库分表(12 问)
分库分表题考查数据库架构设计能力和权衡能力。面试官最关心三个点:什么时候做、怎么路由、拆了之后新问题怎么解决。只背一个“用户表按 ID 取模”是远远不够的。
6.1 拆分策略与分片键
问题 1:什么时候需要分库分表?
先给触发条件:单表数据量超过千万级、写入并发超过单库瓶颈、单库连接数不够用;再强调不要过早拆分,优先考虑索引优化、读写分离、缓存、归档。分库分表是最后手段,因为它带来查询复杂度上升、分布式事务和扩容成本。加分点:说明“阈值本身不是硬性标准,还要看数据增长速度和查询模式”。
问题 2:分片键怎么选?
分片键选择影响所有路由。原则是选择查询频率最高、值分布均匀的字段,例如订单表按 user_id 分片,那所有按 user_id 的查询都能定位到单表;如果业务经常按商家维度和用户维度双向查询,通常选一个主维度分片,另一个维度通过冗余或搜索引擎解决。扣分点:如果分片键是“订单号”而业务大量按“用户 ID”查,查询就会变成全表广播。
6.2 跨库查询和扩容
问题 3:跨库 join 怎么解决?
标准回答从外到内:优先做字段冗余,把关联字段冗余到查询表中;其次做应用层组装,分别在多个分片查询后在应用内合并;再次引入搜索服务,复杂查询交给 Elasticsearch。反例是使用跨库 join,会让分库失去扩展意义。加分点:说明分页排序也是同类问题,limit offset 在分片环境下要对多个分片的结果做二次归并。
问题 4:数据平滑扩容怎么做?
一次到位式的“翻倍扩容 + 重新哈希”在线上不可接受,因为迁移过程需要停机或大量双写。常见思路是使用一致性哈希或范围分片减少迁移范围;更工程化的方案是提前建立双分片键映射,或用影子表迁移、发布订阅同步新库,灰度切换读流量到新库,验证稳定后再切写流量。加分点:强调扩容一定要配合数据校验和回滚预案。
6.3 中间件与完整题目清单
ShardingSphere 是当前最常用的分库分表中间件之一,配置上通过 YAML 声明分片规则:
rules: - !SHARDING tables: t_order: actualDataNodes: ds_${0..1}.t_order_${0..1} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: t_order_inline keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: t_order_inline: type: INLINE props: algorithm-expression: t_order_${user_id % 2} keyGenerators: snowflake: type: SNOWFLAKE注意:示例中的路由表达式需要按实际表名和分片键替换,真实项目还要补充绑定表、广播表规则,并考虑分片键变更和扩容方案。
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | 什么情况下才需要分库分表? | 先优化再拆 |
| 2 | 垂直拆分和水平拆分的区别? | 按业务 vs 按行 |
| 3 | 常见路由策略有哪些? | 取模、范围、一致性哈希 |
| 4 | 分片键怎么选? | 高频 + 均匀 |
| 5 | 跨库 join 怎么办? | 冗余、组装、搜索 |
| 6 | 分库分表后主键怎么生成? | 雪花或号段 |
| 7 | 全局表和广播表怎么处理? | 所有节点冗余 |
| 8 | 分页排序怎么做? | 归并排序 |
| 9 | 数据平滑扩容方案? | 双写、影子表、灰度 |
| 10 | ShardingSphere 和 MyCat 怎么选? | 标准 SQL vs 代理 |
| 11 | 读写分离和分库分表怎么组合? | 主写从读 |
| 12 | 分库分表引入的新问题? | 事务、join、ID、扩容 |
7. 第六模块:分布式 ID(8 问)
分布式 ID 题通常作为分库分表题的延伸,也可以单独出道。核心答法是“先列需求,再对比方案,最后深入雪花算法”。
7.1 需求与方案对比
问题 1:分布式 ID 需要满足什么要求?
全局唯一、趋势递增、高可用、高吞吐,如果 ID 会暴露在订单号或用户号里,还要考虑安全性,不能让人从相邻 ID 推测出业务量。回答时强调“趋势递增”而不是“严格递增”,因为严格递增在高并发下会成为单点瓶颈。
问题 2:UUID、数据库自增、Redis INCR 各自有什么问题?
UUID 无需部署但太长、无序,不利于数据库索引;数据库自增实现简单,但单库有吞吐瓶颈,跨库时需要分段设置步长;Redis INCR 性能高,但依赖 Redis 可用性,且 ID 连续可被猜测。正是这些问题推动了雪花算法的流行。
7.2 雪花算法详解与时钟回拨
问题 3:雪花算法的结构和优缺点?
标准雪花 ID 共 64 位:1 位符号位 + 41 位时间戳 + 5 位数据中心 + 5 位机器号 + 12 位序列号。同一毫秒内可生成 4096 个 ID,整体趋势递增。给出一个教学用最小实现:
public class SnowflakeIdGenerator { private static final long SEQUENCE_BITS = 12L; private static final long WORKER_ID_BITS = 5L; private static final long DATACENTER_ID_BITS = 5L; private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; private static final long TIMESTAMP_SHIFT = DATACENTER_ID_SHIFT + DATACENTER_ID_BITS; private static final long EPOCH = 1288834974657L; private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = System.currentTimeMillis(); if (timestamp < lastTimestamp) { // 时钟回拨:根据回拨范围选择等待、抛异常或使用备用时钟 throw new IllegalStateException("Clock moved backwards"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & MAX_SEQUENCE; if (sequence == 0) { timestamp = waitNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) | (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence; } private long waitNextMillis(long lastTimestamp) { long timestamp = System.currentTimeMillis(); while (timestamp <= lastTimestamp) { timestamp = System.currentTimeMillis(); } return timestamp; } }生产环境还要补充时钟回拨策略、Worker ID 分配、监控和日志,不能直接拿最小实现上线。
问题 4:时钟回拨怎么处理?
标准思路有四种。小回拨,比如几十到几百毫秒,可以等待时钟追平再继续;中量回拨,可以记录上次发号时间,回拨期间序列号不从 0 开始;大量回拨,只能标识异常并暂停发号;更保险的方案是在发号服务里增加备用时钟对比,或者使用雪花 ID 加号段模式混合。加分点:说明美团 Leaf 在纯雪花方案之外提供的号段模式,就是为了规避时钟和服务依赖问题。
7.3 本模块完整题目清单
| 题号 | 题目 | 自测结果 |
|---|---|---|
| 1 | 分布式 ID 必须满足哪些要求? | 全局唯一、趋势递增 |
| 2 | UUID 为什么不适合当主键? | 无序、太长 |
| 3 | 数据库自增方案的优缺点? | 简单但单点 |
| 4 | Redis INCR 方案的问题? | 依赖 Redis |
| 5 | 雪花算法的位结构? | 41 + 5 + 5 + 12 |
| 6 | 时钟回拨怎么处理? | 等待、异常、备用 |
| 7 | 号段模式原理? | 批量取号、内存发放 |
| 8 | 美团 Leaf 和 UidGenerator 对比? | 号段 vs 雪花变体 |
8. 自我测试方法与答题验证
这套题单不能用“看一遍”的方式学。建议用“脱离答案口述 + 录音回听”的方式自测:每题给自己 3 分钟,说清楚三个层次——定义是什么、为什么这样设计、换了场景怎么变。说不出来的题标记为核心弱点,集中复习后再测一次。只看不说的效果很差,因为面试本质是口头表达,不是选择题。
推荐的答题框架是“定义-取舍-场景”三层法。第一层直接给结论,比如“分布式锁就是跨进程互斥”;第二层说取舍,比如“Redis 锁性能高但极端情况可靠性弱,ZooKeeper 锁可靠但性能一般”;第三层落到场景,比如“秒杀链路用 Redis 锁,配置中心变更用 ZooKeeper 锁”。这套方法能覆盖大部分面试追问。六本模块的题目清单就是天然的抽查表,打乱顺序逐题口述,比按顺序背更接近真实面试。
建议每次模拟面试后更新一张自查表:
| 题目类别 | 当前得分 | 最大问题 | 下次复习时间 |
|---|---|---|---|
| CAP 与共识算法 | 2/3 | 讲不出脑裂案例 | 第 3 天 |
| Netty 线程模型 | 1/3 | 画不出主从 Reactor | 第 5 天 |
| 分布式事务选型 | 2/3 | TCC 悬挂没讲清 | 第 7 天 |
自查表的作用不是给你成就感,而是告诉你第二遍复习时重点看什么。每次只解决三到五个最弱的问题,比把整份题单重读一遍更有效。
9. 复习路线与避坑清单
如果目标是两周内吃掉这套题,推荐顺序是:分布式基础 -> 分布式 ID -> 分布式锁 -> 分布式事务 -> 分库分表 -> Netty。前三个模块适合先建立体系,题量小、见效快;分布式事务内容最多,建议单独安排三天;Netty 放在最后,因为它的前置是 Java 并发和 NIO 基础。每天先过提问清单再看解析,再脱离答案口述一次,错题反复练。面试前三天再按模块快速过一遍完整题目清单,重点回看自查表里得分最低的部分。
避坑清单里最值得注意的五条。第一,不要把 CAP 说成“三选二”,要说是“分区发生时二选一”。第二,不要认为 Redis 锁绝对安全,任何锁都可能因 GC、网络分区失效,要配合幂等和 fencing token。第三,不要把 TCC 和 2PC 混为一谈,TCC 是业务级方案,2PC 是资源级方案。第四,分库分表不是越早越好,拆完之后的事务代价和查询复杂度要一起讲清楚。第五,雪花算法看似简单,但生产环境必须处理时钟回拨和 Worker ID 分配,只写核心几行代码会给面试官留下缺乏工程经验的印象。
面到项目部分时,如果简历里有分布式项目,尽量把题单里的方案和项目结合:比如你的系统用了 Seata,就主动讲 AT 模式的 undo_log 流程和隔离性取舍;用了分库分表,就讲分片键选择和扩容遇到的坑。把“答题模式”切换成“项目模式”,技术深度会明显更好。
10. 最后总结
这 65 问的价值不在一题一题背出来,而在帮你把分布式体系的骨架理顺:基础概念定方向,Netty 管通信,分布式锁解决资源竞争,分布式事务保证多资源一致性,分库分表应对数据增长,分布式 ID 解决全局主键。每个模块单独看都有大量资料,但放进同一个问题清单里,才能形成面试时的应答网络。
建议收藏备用。先按第 8 节的自测方法过一轮,找出三到五个薄弱点,再对照对应模块的题目精读;第二轮复习时尝试不看书,把六张题目清单里的所有题目口述一遍,能完成这一步,这套题基本就吃透了。