## 引言:为什么分布式共识如此重要 分布式共识(Distributed Consensus)是分布式系统的基石问题:多个节点就某个值达成一致,即使部分日志复制过程中发生故障。这个问题看似简单,但其理论边界(FLP 不可能定理、CAP 定理)、算法复杂度和工程陷阱使其成为计算机科学中最具挑战性的领域之一。 本文将深入对比三种主流共识算法: - **Paxos**(Leslie Lamport,1989/2001):理论基础最深厚,但难以理解与实现 - **Raft**(Ongaro & Ousterhout,2014):为可理解性设计,现代系统首选 - **Viewstamped Replication(VR)**(Barbara Liskov,1988):常被忽视,但在某些场景比 Raft 更灵活 ## 共识问题的形式化定义 ### 基本模型 共识协议运行在异步、消息可能丢失的网络环境中,节点可能崩溃但不会作恶(非 Byzantine 场景)。协议需要满足以下安全属性: **统一性(Uniform Agreement)**。任何两个正确的节点不能做出不同的决定。 **完整性(Integrity)**。每个节点最多只决定一次值。 **终止性(Termination)**。所有正确的节点最终都会做出决定(这受限于 FLP 不可能定理:在异步网络中,即使只有一个节点可能崩溃,也没有确定性共识算法能保证终止)。 **有效性(Validity)**。被决定的值必须是被某个节点提议过的。 ### FLP 不可能定理的含义 Fischer、Lynch 和 Paterson 在 1985 年证明:即使在只有一个节点做崩溃故障的异步网络中,也没有算法能保证在有限时间内达成共识。这意味着所有实际共识算法都是**概率终止**的——它们依赖超时机制(而非确定性保证)来最终做出决策。 这一定理的实践意义是:共识算法的"活跃性"(liveness)本质上是一个超时和概率问题。工程上我们必须明确:经历网络分区后,共识能多快恢复?是否有活锁风险? ## Paxos:理论的高峰 ### Basic Paxos 的两阶段 Paxos 的核心是一个两阶段提交:**Prepare/Promise** 阶段学习当前已经承诺的最大提案号,**Accept/Accepted** 阶段实际提交值。 ``` Client Proposer Acceptor Learner | | | | |---request(value)-->| | | | |---prepare(N)----->| | | |<--promise(N,V')---| | | |---accept(N,V)---->| | | |<--accepted(N,V)---|---learn(V)--->| |<--response---------| | | ``` **Phase 1 - Prepare**: 1. Proposer 选择一个全局唯一的提案编号 N(通常(轮次,节点 ID)组成) 2. 向多数 Acceptors 发送 Prepare(N) 3. Acceptor 如果 N > 当前承诺的最大提案号,则承诺不再接受 < N>= r.randomizedTimeout { // 150-300ms 随机 r.electionElapsed = 0 r.Step(m{type: MsgHup}) // 自荐为 Candidate } } // Candidate 发起选举 func (r *raft) campaign() { r.becomeCandidate() r.Term++ for id := range r.prs { if id != r.id { r.send(m{ type: MsgVote, to: id, term: r.Term, logIndex: r.raftLog.lastIndex(), logTerm: r.raftLog.lastTerm(), }) } } } ``` **为什么随机化能避免活锁**:当多个 Follower 同时超时成为 Candidate 时,Term 相同、选票分裂。由于各节点超时时间不同(150-300ms 随机),第一个超时的节点总是能抢走全部选票。 ### 安全性:LogEntry 的提交规则 Raft 的安全性保证中有一条容易被忽略:**Leader 不能仅凭当前 Term 确认前一 Terms 的日志条目已提交**。 ``` Log entries: Term 1: [1] [2] Term 2: [3] Term 3: [4] Term 4: [5] ▲ Leader (Term 4) 如果直接提交 Term 4 的第 5 条日志(提交了 5 条), 但此时 Term 2 的第 3 条日志: - 如果被复制到多数则已提交 - 但如果发生 Leader 切换... 新 Leader 可能覆盖第 3 条 Raft 规则:只能通过提交当前 Term 的条目来"间接"提交前一 Terms 的条目。不能直接通过计数前一 Terms 的拷贝数来提交。 ``` ## VR(Viewstamped Replication):被忽视的第三名 ### 核心差异 Barbara Liskov 在 1988 年提出的 VR 协议与 Raft 有两点本质差异: **异步提交**。VR 不需要多数节点确认即可返回客户端请求,而是将"提交"决策推迟到后续操作。这在低延迟场景有优势。 **灵活的视图变更**。VR 的视图变更协议比 Raft Leader 切换更灵活——允许更细粒度的故障恢复。 ### VR 的工程身影 虽然 VR 在学术论文中的曝光率远低于 Raft 和 Paxos,但它在实际系统中广泛使用: - **Nokia/Arizona** 团队的 VR 实现用于电话交换机(Liskov 本人的工程背景) - **CouchDB 3.x** 内部数据库使用类 VR 共识 - **VMware vSAN** 的组件放置决策使用 VR 协议 在某些 Raft 支持不完善的场景(如嵌入式系统、异步网络),VR 仍然是一个被低估的备选方案。 ## 三种算法的工程对比 | 维度 | Paxos | Raft | VR | |------|-------|------|-----| | 可理解性 | 难(即使 Lamport 后来写了 Paxos Made Simple) | 中-高(论文配有动画演示) | 中(论文描述略抽象) | | 正确性证明 | 完整(Lamport 用 TLA+) | 完整(论文附带 Coq 证明的部分子集) | 有但较少引用 | | 标准实现 | 无标准(每家实现都不同) | etcd/raft, HashiCorp Raft 等 | 少数学术/工程实现 | | 生态成熟度 | 高(经过数十年验证) | 高(过去十年爆发) | 低 | | Leader 切换 | 复杂(Multi-Paxos 的 Leader 切换需小心) | 简洁(Term + 超时) | 中等 | | 日志空洞处理 | 复杂(需要 No-Op entries + 特异规则) | 简单(Leader 追加 No-Op 即可) | 较灵活 | | 适用场景 | 通用(金融、电信核心系统) | 通用(云服务、分布式 KV) | 异步通信、低延迟场景 | ## 工程陷阱与最佳实践 ### 1. Split Brain 防护 多数系统使用 **Fencing Tokens** 来防止双 Leader: ```python # Leader 获取 fencing token(通常是最高的已提交 log index) fencing_token = raft_state.commit_index # 向存储层提交时携带 token storage.submit(data, token=fencing_token) # 存储层拒绝 token < 已见过的最大 token 的请求 ``` ### 2. Read-Only 查询的一致性 Raft 中 Leader 处理只读请求需要**确认 Leader 身份**(避免 Stale Read): ```go // etcd 的 ReadIndex 协议 func (n *node) ReadIndex(rctx []byte) { // Leader 记录当前 commit index ri := n.raftLog.committed // 向所有 Follower 发送心跳 // 等心跳确认后(确认自己仍是 Leader) // 等待状态机应用到 ri 之后,返回读结果 } ``` ### 3. 集群成员变更:单节点变更 vs Joint Consensus Ongaro 论文支持的 **单节点逐一变更(Single-Server Changes)** 是最安全的方式:一次只添加或删除一个节点,保证新旧配置的任意多数都有重叠。这种方式避免了 Joint Consensus 的复杂性。 ### 4. 快照传输与 Straggler 节点 新加入节点或宕机恢复的节点(Straggler)日志落后太多时,逐条追日志太慢。Leader 会将当前状态机的快照压缩传输给 Follower: ```go // etcd 的快照传输 msg := raftpb.Message{ Type: raftpb.MessageSnap, To: followerID, Snapshot: *snapshot, // 包含 last index, term, membership } r.send(msg) ``` ## 结语:选择算法的工程原则 **选择 Raft** 当你需要: - 现有成熟的 Go/Rust/Java 实现 - 清晰的 Leader 选举语义 - etcd、Consul、TiKV 等经过大规模生产验证的组件 **选择 Paxos** 当你需要: - 最高的理论正确性保证 - 自定义的复制策略(Flexible Paxos 可以减少消息轮次) - 金融/电信核心系统中已经被证明的协议 **选择 VR 当你**: - 已经在 Liskov 传统系统中 - 需要异步提交语义以降低延迟 - 学术研究或对经典算法有兴趣 无论选择哪种算法,记住:**共识算法只是冰山一角**。完整的一致性系统是共识 + 状态机 + Snapshot + Member Change + Snapshot Transfer 的组合工程。每个环节出错都可能导致数据丢失。 ## 参考资料 1. D. Ongaro & J. Ousterhout, "In Search of an Understandable Consensus Algorithm", USENIX ATC 2014 2. L. Lamport, "The Part-Time Parliament", ACM Transactions on Computer Systems 1998 3. B. Liskov & J. Cowling, "Viewstamped Replication Revisited", MIT CSAIL 2012 4. etcd Contributors, "etcd Documentation: Raft Consensus Algorithm", etcd.io 2024 5. M. van Renesse et al., "Paxos Made Moderately Complex", ACM Computing Surveys 2015 6. J. Howard et al., "Flexible Paxos: Quorum Intersection Reconsidered", DISC 2016 7. N. Bronson, "Consensus on Transaction Commit", IEEE Data Engineering Bulletin 2019

发表评论 取消回复