你存进数据库的每一笔订单,其实都躺在不止一台机器上。那问题来了:其中一台突然宕机,剩下几台手里各有一份数据,到底听谁的?凭什么保证所有副本最后长得一模一样?
这就是分布式一致性 要回答的问题。而 Raft,是今天最流行的那份答案之一------etcd、TiDB、Kafka 的新一代控制器,底层都在用它。它的设计初衷特别朴素:让一致性这件事,普通人也能看懂、能推演。
一、为什么不能简单"少数服从多数"就完事
直觉上,让所有机器投票,过半同意就执行,不就行了?没那么简单,因为有三件事同时捣乱:
- 消息可能乱序、重复、丢失;
- 机器可能随时宕机又随时复活;
- 网络分区时,一半机器互相连不上,却各自为政。
在这种环境下,"多数"也得有个明确的裁决者 来组织投票、拍板执行,否则人人都是裁判,最终必然各写各的。Raft 的做法很直接:先选出一个 Leader(领导者),所有写操作都经由它统一排序,其他节点只负责跟从和备份。
把"多个决策者"收敛成"一个",一致性就从"怎么达成共识"退化成了"怎么选对人 + 怎么把命令同步过去"。这步收敛,是 Raft 好懂的根源。
二、三个角色,一个任期
Raft 里每个节点在任意时刻处于三种角色之一:
- Leader:唯一的带头大哥,负责接收客户端请求、排序、复制日志;
- Follower:小弟,只响应 Leader 和 Candidate,自己不主动发号施令;
- Candidate:候选人,想当 Leader 时短暂进入的角色。
切换角色的时钟是任期(Term) ,一个单调递增的整数。每开启一轮选举,任期 +1。任期有两个作用:一是标志"时代" ------任期号更大的日志更新,冲突时谁任期大谁说了算;二是防止脑裂------两个 Leader 不可能在同一个任期里合法共存,因为当选者必须拿到"过半"的票。
三、领导者选举:靠"心跳 + 随机超时"选人
一切平静时,Leader 周期性地给 Follower 发心跳(其实就是一条空的日志复制请求),意思是"我还活着,你们别闹"。
如果 Follower 一段时间没收到心跳,就会怀疑"大哥是不是没了",于是把自己的任期 +1,变成 Candidate,发起选举,先投自己一票,再向其他节点拉票:
- 拿到过半(N/2+1)的票 → 当选 Leader;
- 期间收到"任期更大的领导者"的消息 → 乖乖退回 Follower;
- 票数没够,且选举超时 → 任期再 +1,重新开选。
那个"随机超时"是关键细节:每个节点等待心跳的时长带点随机抖动,避免所有节点同时醒来、同时参选、把票打散谁都选不上。这个小小的随机数,解决了分布式系统里最经典的一类"活锁"。
为什么一定要过半 (Quorum)而不是全体一致?因为只要系统里还有超过半数节点活着,就最多只能有一个 Candidate 拿到过半票------过半是保证"任一时刻至多一个 Leader"的数学底线。而允许少数节点宕机,则让系统在损失不到一半机器时仍能继续服务。
四、日志复制:大哥先记,小弟照抄
Leader 接到一条客户端命令,做三件事:
- 先把命令作为一条新日志条目追加到自己日志末尾(未提交);
- 把这条日志发给所有 Follower,等它们各自追加;
- 一旦过半节点都确认复制成功,Leader 就**提交(commit)**这条日志,并把它应用到状态机(真正执行),再通知 Follower 也提交。
这个"过半确认再提交"的机制,保证了哪怕 Leader 提交后立刻宕机,新选出的 Leader 手里必然也持有这条已提交的日志------因为新 Leader 也得拿到过半票,而这两组"过半"节点必然有交集。
于是,日志就成了一份有序、一致、可回放的记录。每个节点的状态机按同样的顺序执行同样的命令,副本自然长得一模一样。
五、为什么说它"好懂"这件事本身就很值钱
Raft 之前的 Paxos,正确性没问题,但极其难懂------连提出者 Lesie Lamport 都开玩笑说"没人理解 Paxos,甚至包括我"。Raft 的作者 Diego Ongaro 在设计时立了个死规矩:任何一个能看懂它的读者,都应该能把整个算法完整地推演一遍、讲给别人听。
为了这份"可理解性",Raft 做了很多减法:把一致性拆成"选主、日志复制、安全"三个正交子问题;用任期把时序说清楚;用过半把"至多一个 Leader"变成显然。它不是靠更复杂的机制赢的,是靠把复杂的问题拆到人脑能装下赢的。
这其实给每个做系统的人提了个醒:能被人理解,本身就是一种工程价值。 一份没人敢动的代码、一个没人敢改的协议,再正确也会在维护中慢慢腐烂。
分布式一致性不在 408 统考大纲的直接考查范围内,属于进阶/延伸 内容,但它和操作系统、数据库里的"并发、事务、复制"一脉相承。想从根上补这些底子的,可以先看我的这两篇:为什么会有脏读、幻读(事务隔离本质) 和 为什么系统要加一层消息队列?------解耦、削峰、异步的本质,一次讲透。