
⏱️ 阅读时长约15分钟 | 难度进阶标签分布式系统一致性协议RaftPaxosZooKeeper开场白想象一下你和五个朋友决定周末聚餐。有人想吃火锅有人想吃烧烤还有人坚持日料。如果每个人都按自己的想法行动结果要么是六个人去了六个地方要么是在餐厅门口吵到打起来。分布式系统里的多个节点就像这群各怀心思的朋友——一致性协议就是让他们达成共识、不打架的规则。在分布式系统的世界里数据分散在成百上千台机器上。如何让这些机器对某条数据是什么达成一致这就是分布式一致性协议要解决的核心问题。今天我们就用最接地气的方式把 CAP 定理、2PC、Paxos、Raft 这些听起来高大上的概念掰开了揉碎了讲清楚。一、CAP 定理与 BASE 理论分布式世界的不可能三角1.1 CAP 定理鱼与熊掌不可兼得2000年UC Berkeley 的 Eric Brewer 教授提出了著名的 CAP 定理。它告诉我们分布式系统最多只能同时满足一致性Consistency、可用性Availability、分区容错性Partition Tolerance这三项中的两项。生活化理解想象你开了一家连锁奶茶店有三家分店。CAP 就像是C一致性所有店的菜单和价格必须完全一样A可用性每家店必须随时能接单不能关门P分区容错性即使某两家店之间的电话线断了它们也要能独立运营问题来了如果两家店之间的通信断了P 发生你要么让其中一家暂停营业保证数据一致牺牲 A要么允许两家店暂时数据不一致但都能营业牺牲 C。三者不可兼得这就是分布式系统的宿命。┌─────────────────────────────────────────────────────────┐ │ CAP 定理示意图 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ / 一致性(C) \ │ │ / Consistency \ │ │ / \ │ │ / ╔═══════════╗ \ │ │ / ║ 不可能 ║ \ │ │ / ║ 三角 ║ \ │ │ / ╚═══════════╝ \ │ │ / \ │ │ / 可用性(A) 分区容错(P) \ │ │ / Availability Partition \ │ │ / \ │ │ └────────────────────────────────────┘ │ │ │ │ 常见组合 │ │ • CP 系统ZooKeeper、etcd、HBase │ │ • AP 系统Cassandra、DynamoDB、Eureka │ │ • CA 系统传统单机数据库非分布式 │ └─────────────────────────────────────────────────────────┘1.2 BASE 理论退一步海阔天空既然 CAP 告诉我们强一致性很难那能不能退而求其次BASE 理论应运而生Basically Available基本可用系统出现故障时允许损失部分可用性但核心功能必须可用Soft state软状态允许系统中的数据存在中间状态不影响整体可用性Eventually consistent最终一致性不保证实时一致但保证最终所有节点数据一致核心思想BASE 是对 CAP 中 AP 方案的延伸强调高可用优先一致性可以稍后再补。就像网购时你先看到库存充足下单了实际上系统后台可能正在同步库存数据——这就是软状态和最终一致性的体现。二、2PC 与 3PC分布式事务的举手表决2.1 两阶段提交2PC先举手再行动2PCTwo-Phase Commit是最经典的分布式事务协议。它把事务提交分成两个阶段┌────────────────────────────────────────────────────────────┐ │ 2PC 两阶段提交流程 │ ├────────────────────────────────────────────────────────────┤ │ │ │ 协调者(Coordinator) 参与者(Participants) │ │ │ │ │ │ Phase 1 │ 1. Can you commit? ─────▶│ │ │ (投票) │ │ │ │ │◀──────── 2. Yes/No ─────────│ │ │ │ (所有参与者回复) │ │ │ │ │ │ │ │ 如果全部 Yes: │ │ │ Phase 2 │ │ │ │ (执行) │ 3. Commit ──────────────▶│ │ │ │ │ │ │ │◀──────── 4. ACK ────────────│ │ │ │ │ │ │ │ 如果有 No: │ │ │ │ 3. Rollback ────────────▶│ │ │ │ │ │ └────────────────────────────────────────────────────────────┘第一阶段投票阶段协调者询问所有参与者你能执行这个操作吗参与者执行本地事务但不提交然后回复 Yes 或 No。第二阶段执行阶段如果所有参与者都回复 Yes协调者发送 Commit 指令如果有任何参与者回复 No协调者发送 Rollback 指令。聚餐决策版你们六个人决定去哪吃饭第一阶段群主协调者问每个人你能接受吃火锅吗“每个人心里盘算一下能接受的举手说行”不能接受的举手说不行。第二阶段如果所有人都说行群主宣布那就这么定了大家都去火锅店如果有人说了不行群主宣布这个方案作废我们再想别的。2PC 的致命缺陷同步阻塞参与者需要锁定资源等待协调者指令性能差单点故障协调者挂了整个事务就卡住了数据不一致风险如果协调者在第二阶段挂了部分参与者可能提交了部分没提交2.2 三阶段提交3PC再加一道保险3PC 在 2PC 的基础上增加了一个阶段试图解决 2PC 的阻塞问题CanCommit 阶段协调者询问参与者你理论上能不能执行不锁定资源PreCommit 阶段协调者通知参与者准备执行预锁定资源DoCommit 阶段真正提交或回滚3PC 的改进引入了超时机制。如果参与者超时没收到协调者的指令可以根据当前状态自己决定是提交还是回滚。这就像聚餐时群主失联了大家约定如果已经说准备去了就默认去如果只是理论上能去就默认不去。但说实话3PC 并没有完全解决 2PC 的问题只是降低了阻塞概率。在网络分区的情况下仍然可能出现数据不一致。所以实际生产中2PC/3PC 主要用于数据库内部的分布式事务如 MySQL XA很少直接用于微服务架构。三、Paxos 算法议会投票的民主典范3.1 Paxos 是什么Paxos 算法由 Leslie Lamport 于 1990 年提出论文直到 1998 年才发表因为审稿人觉得太无聊。它是分布式一致性算法的鼻祖Raft、ZAB 等都是它的变种或简化版。Paxos 的核心比喻Paxos 就像古希腊城邦的议会投票有一个提议者Proposer相当于议员负责提出议案有一群接受者Acceptor相当于议会成员负责投票有若干学习者Learner相当于吃瓜群众只负责知道最终结果议会要通过一个决议必须获得**多数派超过半数**的同意。而且一旦决议通过就不可更改。3.2 Paxos 的两个阶段Paxos 算法分为两个阶段每个阶段都是多数派投票┌─────────────────────────────────────────────────────────────┐ │ Paxos 算法流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Phase 1: Prepare准备阶段 │ │ ═══════════════════════════ │ │ │ │ Proposer Acceptors │ │ │ 1. Prepare(n) ─────┬─────▶ │ │ │ │ │ │ │ │◀──────────┴──── Promise ──┘ │ │ │ (承诺不接受编号n的提案) │ │ │ │ │ Phase 2: Accept接受阶段 │ │ ══════════════════════════ │ │ │ │ │ 2. Accept(n,v) ─────┬─────▶ │ │ │ │ │ │ │ │◀──────────┴──── Accepted ─┘ │ │ │ (多数派接受后v被选定) │ │ │ │ 关键约束 │ │ • 每个 Acceptor 必须接受它收到的第一个提案 │ │ • 如果已经接受过提案Promise 时要告知 Proposer │ │ • Proposer 收到多数 Promise 后才能进入 Phase 2 │ └─────────────────────────────────────────────────────────────┘Phase 1 - Prepare 阶段Proposer 选择一个提案编号 n向超过半数的 Acceptor 发送 Prepare(n) 请求Acceptor 收到 Prepare(n) 后如果 n 大于它之前承诺过的所有编号就承诺不再接受编号小于 n 的提案并返回它已经接受过的最大编号的提案如果有Phase 2 - Accept 阶段如果 Proposer 收到了多数 Acceptor 的 Promise 回复它就选择一个值 v如果 Acceptor 返回了已接受的值就用那个值否则用自己的值发送 Accept(n, v) 请求Acceptor 收到 Accept(n, v) 后如果不违反之前的承诺就接受这个提案一旦某个值被多数 Acceptor 接受它就被选定了3.3 为什么 Paxos 这么难理解Paxos 被称为唯一正确的一致性算法但也被称为最难理解的算法。难点在于活锁问题多个 Proposer 同时提案可能导致互相抢占谁也成功不了实现复杂Multi-Paxos连续多个值的共识需要额外的 Leader 选举机制边界情况多网络分区、节点宕机、消息丢失等各种异常情况都要处理著名的 Paxos 笑话Lamport 后来写了一篇《Paxos Made Simple》Paxos made simple但据说能看懂的人依然寥寥无几。有人说“世界上有两种分布式一致性协议一种是 Paxos另一种是 Paxos 的简化版。”四、Raft 算法选班长的民主简化版4.1 为什么需要 Raft2013年斯坦福大学的 Diego Ongaro 和 John Ousterhout 发表了 Raft 论文。他们的目标是设计一个和 Paxos 一样正确但更容易理解的共识算法。Raft 做到了。它把复杂的共识问题拆解成三个相对独立的子问题领导者选举Leader Election谁当老大日志复制Log Replication老大怎么把命令同步给所有人安全性Safety保证各种异常情况下的正确性Raft 的核心比喻Raft 就像班级选班长领导者Leader班长所有决策由班长下达跟随者Follower普通同学听从班长指挥候选人Candidate想当班长的人需要拉票班级里只能有一个班长。班长负责传达老师的指令同学们记录在本子上。如果班长请假了大家就重新选举。4.2 领导者选举谁当老大Raft 使用任期Term的概念每个任期最多只有一个 Leader┌─────────────────────────────────────────────────────────────┐ │ Raft 领导者选举流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Follower Candidate Leader │ │ (跟随者) (候选人) (领导者) │ │ │ │ │ │ │ │ 超时没收到心跳 │ │ │ │ │──────────────▶ │ │ │ │ │ 增加任期号 │ │ │ │ │ 给自己投票 │ │ │ │ │ │ │ │ │ │◀───────── RequestVote ──────────│ │ │ │ │ (向所有节点拉票) │ │ │ │ │ │ │ │ │──────── Vote ─▶│ │ │ │ │ (同意投票) │ │ │ │ │ │ │ │ │ │ │ 获得多数票 │ │ │ │ │──────────────▶│ │ │ │ │ 成为 Leader │ │ │ │ │ 发送心跳包 │ │ │ │◀──────────── Heartbeat ────────│ │ │ │ │ │ │ │ 选举规则 │ │ • 每个任期每个节点最多投一票 │ │ • 先到先得Candidate 先请求就投给谁 │ │ • 任期号大的优先过时的 Leader 自动下台 │ └─────────────────────────────────────────────────────────────┘选举过程每个 Follower 有一个随机的选举超时时间150-300ms如果超时还没收到 Leader 的心跳就变成 Candidate增加任期号给自己投票Candidate 向所有其他节点发送 RequestVote 请求如果收到超过半数的投票就成为 Leader新 Leader 立即发送心跳包阻止新的选举4.3 日志复制班长怎么传达指令Leader 负责接收客户端的请求然后把操作日志复制给所有 Follower// Leader 收到客户端写请求 function clientRequest(command) { // 1. 先把命令追加到自己的日志 log.append({term: currentTerm, command: command}) // 2. 向所有 Follower 发送 AppendEntries RPC for each follower in followers: send AppendEntries { term: currentTerm, leaderId: self, prevLogIndex: nextIndex[follower] - 1, prevLogTerm: log[prevLogIndex].term, entries: log[nextIndex[follower]:], leaderCommit: commitIndex } // 3. 如果收到多数成功回复就提交 if majoritySuccess(): commitIndex applyToStateMachine() replyToClient(success) } // Follower 处理 AppendEntries function handleAppendEntries(args) { if args.term currentTerm: return reject // 拒绝过期的 Leader // 检查 prevLog 是否匹配 if log[args.prevLogIndex].term ! args.prevLogTerm: return reject // 日志不一致需要 Leader 回退 // 追加新日志条目 log.append(args.entries) // 更新 commitIndex if args.leaderCommit commitIndex: commitIndex min(args.leaderCommit, log.lastIndex()) return success }关键机制日志匹配特性如果两个日志在相同索引位置的任期号相同那么它们之前的所有条目都相同提交规则Leader 只能提交自己任期内的日志条目旧任期的日志需要等新日志一起提交一致性检查AppendEntries 携带 prevLogIndex 和 prevLogTermFollower 检查是否匹配4.4 安全性保证Raft 通过以下机制保证安全选举限制Candidate 必须包含所有已提交的日志才能当选RequestVote 时比较日志的最后任期和索引提交规则不能直接提交旧任期的日志必须等到当前任期有日志提交时一起提交状态机安全一旦某个日志条目被提交所有节点最终都会应用相同的命令Raft 的优势相比 PaxosRaft 的 Leader 机制让算法更容易理解——所有写操作都经过 Leader避免了 Paxos 中多 Proposer 冲突的活锁问题。etcd、Consul、Nacos 等知名项目都使用 Raft 作为共识算法。五、ZAB 协议ZooKeeper 的原子广播5.1 ZAB 是什么ZABZooKeeper Atomic Broadcast是 ZooKeeper 使用的原子广播协议。它专门为 ZooKeeper 设计目标是保证顺序一致性客户端的更新按发送顺序应用原子性更新要么成功要么失败没有中间状态单一系统映像客户端无论连到哪个服务器看到的数据都一样可靠性一旦更新被应用它会被持久化实时性客户端在一定时间内能读到最新数据5.2 ZAB 的两个阶段ZAB 协议把运行时分两个阶段┌─────────────────────────────────────────────────────────────┐ │ ZAB 协议运行阶段 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 崩溃恢复 │─────────────▶│ 消息广播 │ │ │ │ (Recovery) │ Leader 选举 │ (Broadcast) │ │ │ └──────────────┘ 完成 └──────────────┘ │ │ ▲ │ │ │ │ │ Leader 崩溃 │ │ └────────────────────────────┘ │ │ │ │ 崩溃恢复阶段 │ │ • 选举新的 Leader拥有最大 zxid 的节点优先 │ │ • Leader 同步数据给 Follower │ │ • 确保过半节点数据一致后才对外服务 │ │ │ │ 消息广播阶段 │ │ • 类似两阶段提交 │ │ • Leader 生成全局唯一的 zxid │ │ • 过半节点 ACK 后才提交 │ └─────────────────────────────────────────────────────────────┘崩溃恢复阶段Leader 选举选择拥有最大 ZXID事务ID的节点作为 Leader数据同步Leader 确保 Follower 和自己数据一致加入集群同步完成后Follower 正式加入集群消息广播阶段Leader 收到写请求生成全局唯一的 ZXIDLeader 发送 Proposal 给所有 FollowerFollower 写入本地日志发送 ACKLeader 收到过半 ACK发送 Commit所有节点提交事务5.3 ZAB vs RaftZAB 和 Raft 很相似但有一些关键区别特性ZABRaft设计目标专门为 ZooKeeper 设计通用共识算法日志流向Leader → Follower单向Leader → Follower单向事务 IDZXID高32位是任期低32位是计数任期索引二元组Leader 选举优先选数据最新的随机超时先到先得读操作可以从 Follower 读可能读到旧数据可以从 Follower 读可能读到旧数据六、Gossip 协议最终一致性的八卦传播6.1 什么是 Gossip 协议Gossip 协议八卦协议是一种基于流行病传播思想的分布式协议。节点之间像传播八卦一样随机交换信息最终达到全网一致。八卦传播版想象你在一个聚会上听到了一个惊天大瓜你随机找两个人告诉他们这两个人又各自随机找两个人很快整个聚会的人都知道了这个瓜即使有人中途离场或者有人没听清楚最终大多数人还是会知道这个消息。这就是 Gossip 协议的精髓。6.2 Gossip 的工作原理┌─────────────────────────────────────────────────────────────┐ │ Gossip 协议传播过程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Round 1: Round 2: Round 3: │ │ │ │ A A A │ │ / \ /|\ /|\ │ │ B C B C D B C D E F │ │ / \ / \ /|\ /|\ │ │ D E E F G H I J K L │ │ │ │ 传播特点 │ │ • 每个周期随机选择 k 个邻居交换信息 │ │ • 信息像病毒一样指数级传播 │ │ • 容错性强部分节点失效不影响整体 │ │ • 最终一致性不保证实时一致 │ └─────────────────────────────────────────────────────────────┘Gossip 的两种模式反熵Anti-Entropy节点定期随机选择另一个节点交换完整的副本数据消除差异谣言传播Rumor Mongering新信息像谣言一样传播节点收到后标记为热点继续传播给其他节点直到全网都知道6.3 Gossip 的应用场景Gossip 协议适合以下场景集群成员发现Redis Cluster、Consul 用 Gossip 发现节点配置传播Cassandra 用 Gossip 传播节点状态故障检测通过心跳缺失发现节点故障分布式数据库DynamoDB、Cassandra 的数据同步import random import time from threading import Thread class GossipNode: def __init__(self, node_id, peers): self.node_id node_id self.peers peers # 邻居节点列表 self.data {} # 本地数据 self.version {} # 数据版本号 def update(self, key, value): 更新本地数据 self.data[key] value self.version[key] self.version.get(key, 0) 1 print(f[{self.node_id}] Updated {key}{value}, version{self.version[key]}) def gossip(self): 随机选择邻居交换数据 if not self.peers: return # 随机选择 k 个邻居这里 k1 peer random.choice(self.peers) # 交换数据实际实现会用网络通信 print(f[{self.node_id}] Gossiping with {peer.node_id}) self.merge_data(peer) peer.merge_data(self) def merge_data(self, other): 合并对方的数据按版本号 for key, value in other.data.items(): other_ver other.version.get(key, 0) my_ver self.version.get(key, 0) if other_ver my_ver: self.data[key] value self.version[key] other_ver print(f[{self.node_id}] Merged {key}{value} from {other.node_id}) # 模拟三个节点 node_a GossipNode(A, []) node_b GossipNode(B, []) node_c GossipNode(C, []) # 设置邻居关系 node_a.peers [node_b, node_c] node_b.peers [node_a, node_c] node_c.peers [node_a, node_b] # A 更新数据 node_a.update(config, v1) # 模拟多轮 gossip for i in range(5): print(f\n Round {i1} ) node_a.gossip() node_b.gossip() node_c.gossip() time.sleep(0.1) print(\n Final State ) for node in [node_a, node_b, node_c]: print(f{node.node_id}: {node.data})Gossip 的优缺点优点去中心化、容错性强、可扩展性好、网络开销小缺点最终一致性有延迟、收敛时间不确定、消息可能重复传播七、一致性协议选型指南没有银弹7.1 各种协议对比协议一致性级别可用性性能复杂度典型应用2PC/3PC强一致低阻塞低中数据库 XA 事务Paxos强一致中中极高Chubby、PaxosStoreRaft强一致中中中etcd、Consul、NacosZAB强一致中中中ZooKeeperGossip最终一致高高低Cassandra、Consul7.2 选型决策树┌─────────────────────────────────────────────────────────────┐ │ 一致性协议选型决策树 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 开始选型 │ │ │ │ │ ┌────────────┴────────────┐ │ │ ▼ ▼ │ │ 必须强一致 可以接受最终一致 │ │ │ │ │ │ 是 ┌─┘ 是 ┌─┘ │ │ ▼ ▼ │ │ 需要事务 Gossip 协议 │ │ │ • Cassandra │ │ 是 ┌─┘ • Consul 服务发现 │ │ ▼ │ │ 2PC/3PC │ │ • 数据库分布式事务 │ │ │ │ 否 ┌─────────────────────────┐ │ │ ▼ ▼ │ │ 追求简单实现 追求极致性能 │ │ │ │ │ │ 是 ┌┘ 是 ┌┘ │ │ ▼ ▼ │ │ Raft Paxos │ │ • etcd • 大规模系统 │ │ • Consul • 有专业团队维护 │ │ • Nacos │ │ │ │ 否 ┌─────────────────────────┐ │ │ ▼ │ │ ZAB / 定制协议 │ │ • ZooKeeper │ │ • 需要顺序保证的场景 │ └─────────────────────────────────────────────────────────────┘7.3 实战建议选型黄金法则配置中心/服务发现etcdRaft或 ConsulRaft Gossip分布式锁/协调ZooKeeperZAB或 etcd分布式事务Seata基于 2PC 优化或 Saga 模式高可用缓存Redis ClusterGossip或 Codis海量数据存储CassandraGossip或 TiDBRaft记住三个不要不要为了追求强一致而牺牲所有性能不要在需要事务的场景用最终一致性方案不要自己实现 Paxos除非你是 Google 或微信团队写在最后分布式一致性协议本质上是在一致性、可用性、分区容错性之间做权衡的艺术。没有最好的协议只有最适合场景的协议。从 CAP 定理的不可能三角到 2PC 的举手表决再到 Paxos 的议会投票、Raft 的选班长、Gossip 的八卦传播——每一种协议都是人类在分布式世界中的智慧结晶。希望这篇文章能帮你建立起分布式一致性的整体认知。下次面试官问你Raft 和 Paxos 有什么区别或者架构师问你这个场景选什么一致性方案你能胸有成竹地回答。 源码获取本文所有代码示例已整理到 GitHubGitHub 仓库https://github.com/example/distributed-consensus-demos包含内容Raft 算法的 Python 简化实现Gossip 协议的完整模拟2PC/3PC 的 Java 示例ZooKeeper 客户端操作示例 思考题假设你设计一个电商库存系统扣减库存时应该选强一致性还是最终一致性为什么Raft 的 Leader 选举中如果两个 Candidate 同时获得相同数量的选票会发生什么怎么解决为什么 Paxos 允许有多个 Proposer而 Raft 只允许一个 Leader各有什么优缺点在跨地域的分布式系统中CAP 定理如何指导你的架构设计ZooKeeper 的 ZAB 协议为什么要设计崩溃恢复阶段直接像 Raft 一样持续运行不行吗 系列文章预告《网络协议系列》持续更新中下一篇分布式事务的终极解决方案——从 2PC 到 Saga 再到 Seata番外篇etcd 源码解析Raft 算法的工业级实现实战篇用 Go 语言手写一个 Raft 分布式 KV 存储进阶篇Paxos 的数学证明为什么它能保证安全性关注专栏不错过每一篇干货如果这篇文章对你有帮助欢迎点赞 、收藏 ⭐、评论 你的支持是我持续创作的动力标签分布式系统一致性协议RaftPaxosZooKeeper