在分布式系统中,当多个节点通过网络协同工作,且随时可能发生节点宕机或网络分区时,如何让整个集群对某一份数据达成一致,是一致性算法要解决的根本问题。Paxos 和 Raft 是这个领域最具代表性的两个算法,前者是理论基石,后者是工程主流。本文将从原理、结构、流程和落地实践几个方面对二者进行详细对比。

一、Paxos:理论严谨但晦涩的经典算法
Paxos 由图灵奖得主 Leslie Lamport 于 1990 年提出,其原始论文用希腊城邦的故事描述算法流程,导致长期无人能读懂,直到 2001 年 Lamport 不得不重写了一篇更通俗的《Paxos Made Simple》。即便如此,Paxos 依然以难懂、难实现、难验证著称。
Basic Paxos 的核心角色包括三类:Proposer(提案者)、Acceptor(接受者)和 Learner(学习者)。算法通过两阶段协议达成一致:第一阶段 Prepare,Proposer 选择一个全局递增的提案编号 n,向多数派 Acceptor 发送 Prepare 请求;Acceptor 承诺不再接受编号小于 n 的提案。第二阶段 Accept,Proposer 向多数派发送 Accept 请求,携带提案值,收到多数派确认后,值即被选定。
// Basic Paxos 伪代码示意
class Proposer {
int proposalId; // 全局递增的提案编号
Value value;
void prepare(List<Acceptor> acceptors) {
// 阶段一:发送 Prepare(n)
for (Acceptor a : acceptors) {
Promise p = a.onPrepare(proposalId);
if (p.accepted) {
// 已接受过更高编号的值,需要回退采用该值
value = p.acceptedValue;
}
}
}
void accept(List<Acceptor> acceptors) {
// 阶段二:发送 Accept(n, value)
for (Acceptor a : acceptors) {
a.onAccept(proposalId, value);
}
}
}两阶段的本质是通过“多数派”这个概念规避单点故障:只要超过半数的节点达成一致,即使少数节点宕机,决策仍然有效。但 Basic Paxos 只能对一个值达成一致,实际系统需要对一系列值形成有序日志,于是又衍生出 Multi-Paxos。Multi-Paxos 通过选出一个稳定的 Leader,省去大部分 Prepare 阶段,将两阶段简化为一阶段,大幅提升性能。然而 Multi-Paxos 的论文只给出了松散的描述,日志空洞、快照、成员变更等细节都留给实现者自行发挥,这也是不同 Paxos 实现差异巨大、互不兼容的根本原因。
二、Raft:以可理解性为设计目标的工程方案
Raft 于 2013 年由斯坦福大学的 Diego Ongaro 和 John Ousterhout 提出,论文开篇就明确说明:Raft 的设计目标与 Paxos 在功能上等价,但更容易理解和工程实现。为了达成这个目标,Raft 做了两件关键的事:问题分解和状态简化。
问题分解是指 Raft 把一致性拆成三个相对独立的子问题:领导者选举、日志复制和安全性保证。任何时刻,集群中的每个节点处于 Leader、Follower、Candidate 三种角色之一。Leader 负责处理所有客户端请求并把日志复制给 Follower;Follower 被动接收 Leader 的日志;当 Follower 在选举超时时间内没有收到 Leader 的心跳,就会变成 Candidate 发起选举。
// Raft 选举超时与状态流转示意
type State int
const (
Follower State = iota
Candidate
Leader
)
func (r *RaftNode) tickElection() {
r.electionElapsed++
if r.electionElapsed >= r.electionTimeout {
// 超时未收到 Leader 心跳,转为候选者发起选举
r.becomeCandidate()
r.requestVotes() // 向其他节点拉票,获得多数票则成为 Leader
}
}
func (r *RaftNode) becomeLeader() {
r.state = Leader
r.sendHeartbeat() // 立即广播心跳,压制其他选举
}日志复制流程则清晰得多:客户端请求统一交给 Leader,Leader 把命令追加到本地日志,并行发送 AppendEntries RPC 给所有 Follower。当 Leader 确认某条日志已被多数派写入,就提交该日志并应用到状态机,随后在后续的 AppendEntries 中通知 Follower 提交。Raft 还有一条关键的选举约束:候选人必须拥有最新的日志才能当选,配合“日志只追加、提交只能通过 Leader 当前任期日志”这两条规则,保证了已提交的日志永远不会丢失或被覆盖,这正是论文中反复强调的 Leader Completeness 和 State Machine Safety 特性。
三、核心维度对比:从角色模型到性能表现
从协议结构上看,Paxos 是对等模型,任何节点都可以提出提案,节点之间不存在固定的主从关系,这带来了更好的对称性,但也使得冲突处理(活锁)变得复杂——两个 Proposer 互相打断对方的提案,可能导致协议一直无法收敛。Raft 则是强 Leader 模型,所有数据流都经过 Leader,逻辑线性清晰,代价是 Leader 成为写入的瓶颈和单点依赖,需要靠高质量的选举机制快速恢复。
| 对比维度 | Paxos / Multi-Paxos | Raft |
|---|---|---|
| 设计目标 | 正确性证明的严谨性 | 可理解性与工程可实现性 |
| 角色模型 | Proposer、Acceptor、Learner 对等 | Leader、Follower、Candidate 强主从 |
| 日志结构 | 允许空洞,各条目独立共识 | 连续日志,不允许空洞 |
| 变更顺序 | 允许乱序提交 | 严格顺序提交 |
| 成员变更 | 论文未详述,实现各异 | 单节点变更与联合共识两种方案 |
| 工程实现 | 门槛高,实现差异大 | 有标准参考实现和一致性测试套件 |
在性能层面,两者在稳定状态下都需要一次多数派往返即可提交一条日志,理论吞吐相近。差异主要出现在变更场景:Paxos 允许乱序提交,在网络抖动时可以更快提交不冲突的提案;Raft 的顺序提交牺牲了一定的并发度,换来实现简单和调试容易。在 Leader 切换时,Raft 需要完整走一遍选举流程,恢复期间集群不可写入,而一些 Multi-Paxos 实现可以做到更平滑的权柄转移。不过对绝大多数业务系统来说,这种毫秒级的差距并不构成选型的决定因素。
四、工程实践与选型建议
从落地情况看,Raft 明显占据了主流:etcd、Consul、TiKV、CockroachDB、RocketMQ DLedger 等知名项目都直接采用或深度借鉴了 Raft。这些实现大多遵循论文的标准流程,并且社区有成熟的一致性测试工具(如 Jepsen 和 raft 测试套件),新实现的正确性验证成本大幅降低。
Paxos 系的代表则是 Google 的 Chubby 分布式锁服务,以及 spanner 中使用的 Paxos 变体;国内的有 Amazon DynamoDB 的类 Paxos 协议、微信 Phxpaxos 等开源实现。这些实现证明 Multi-Paxos 在大规模生产环境完全可行,但它们往往是深度定制的内部系统,公开文档有限,直接复用的难度高于 Raft。
具体到选型,可以参考以下原则:第一,如果你的团队是第一次构建带强一致性的组件,选 Raft,不要犹豫,丰富的开源实现和社区资料能帮你避开绝大多数正确性陷阱。第二,如果你对写入性能有极致要求,比如需要乱序提交、流水线优化、跨地域部署时的平权写入,可以研究 Multi-Paxos 或其现代变体(如 EPaxos、Flexible Paxos)。第三,如果只是需要一个高可用的元数据存储或服务发现能力,直接使用 etcd 或 Consul,它们已经把 Raft 封装成了开箱即用的产品,没必要自己从零实现任何一致性算法。
总结来看,Paxos 与 Raft 并非替代关系,而是理论与实践的传承关系。Raft 用结构化的方式重新表达了 Paxos 的核心思想,让一致性算法从论文走进了工业界。理解两者差异,不仅能帮助你在架构选型时做出正确判断,更能深入理解分布式一致性的本质:在不可靠的网络和硬件之上,构建可靠的共识机制。