在单机系统里,“一致性”通常意味着一次写入要么成功、要么失败,读到的数据应该符合预期。但在分布式系统中,数据被拆分到多台机器上,节点可能宕机,网络可能延迟、丢包、分区,消息可能乱序到达。此时,“让多个节点对同一件事达成一致”就变成了一个核心难题。
分布式一致性算法的目标,就是在不可靠的网络和节点环境中,让多个副本尽可能可靠地就某个值、某个日志顺序、某个事务结果达成一致。
一、为什么需要分布式一致性
典型场景包括:
- 分布式数据库主从复制
- 分布式事务提交
- 配置中心的配置变更
- 注册中心的服务上下线
- 分布式锁
- 元数据管理
- 多副本日志复制
- 分布式文件系统 Master 高可用
假设有三个节点 A、B、C,它们都保存用户余额:
A: balance = 100 B: balance = 100 C: balance = 100现在用户转出 30 元,A 已经更新为 70,但 B、C 还没来得及更新。如果此时系统对外提供读服务,就可能读到不同结果。
这就是分布式系统里的核心问题:多个副本如何保持一致。
二、一致性的不同层次
1. 强一致性
强一致性要求一次写入成功后,后续所有读都能读到最新值。
例如:
写入 x = 1 成功 之后任意节点读取 x,都必须返回 1强一致性的用户体验最好,但实现成本最高,通常会牺牲可用性或性能。
2. 最终一致性
最终一致性允许短时间内不同节点看到不同数据,但只要没有新的写入,系统最终会收敛到相同状态。
例如:
A 节点已经更新 B、C 节点稍后同步 最终 A、B、C 数据一致DNS、缓存系统、很多互联网业务系统都大量使用最终一致性。
3. 线性一致性
线性一致性是强一致性的一种严格形式。它要求所有操作看起来像是按照某个全局顺序依次执行,并且这个顺序符合真实时间。
如果操作 A 在操作 B 开始前已经完成,那么所有节点都必须认为 A 发生在 B 之前。
Raft、Paxos 这类共识算法通常追求的就是线性一致的日志复制。
三、CAP 理论
CAP 理论指出,在分布式系统中,以下三者不能同时完全满足:
- Consistency:一致性
- Availability:可用性
- Partition Tolerance:分区容错性
网络分区在分布式系统中无法彻底避免,所以实际系统通常必须在 CP 和 AP 之间取舍。
CP 系统
CP 系统优先保证一致性和分区容错性。
当网络分区发生时,为了避免数据冲突,一部分节点可能拒绝服务。
代表系统:
- ZooKeeper
- etcd
- Consul 的一致性存储部分
- HBase 的强一致元数据管理
AP 系统
AP 系统优先保证可用性和分区容错性。
当网络分区发生时,各分区仍然可以处理请求,但后续需要通过冲突合并、版本比较等方式实现最终一致。
代表系统:
- Cassandra
- DynamoDB 的部分设计思想
- Riak
- CouchDB
四、2PC:两阶段提交
2PC,全称 Two-Phase Commit,是经典的分布式事务提交协议。
它主要解决的问题是:多个参与者要么全部提交,要么全部回滚。
阶段一:Prepare
协调者询问所有参与者是否可以提交事务。
Coordinator -> Participant A: Can commit? Coordinator -> Participant B: Can commit? Coordinator -> Participant C: Can commit?参与者执行本地预提交操作,锁定资源,然后回复:
YES / NO阶段二:Commit / Rollback
如果所有参与者都返回 YES,协调者发送 COMMIT。
只要有一个参与者返回 NO,协调者发送 ROLLBACK。
所有人 YES -> COMMIT 任意人 NO -> ROLLBACK2PC 的优点
- 模型简单
- 容易理解
- 能保证事务原子性
- 适合传统数据库 XA 事务场景
2PC 的缺点
2PC 最大的问题是阻塞。
如果协调者在第二阶段宕机,参与者可能已经进入 prepared 状态,并且不知道最终应该提交还是回滚。
此时参与者为了保证一致性,只能继续阻塞等待协调者恢复。
Participant A: 已 prepared,但不知道最终结果 Participant B: 已 prepared,但不知道最终结果 Coordinator: 宕机这会导致资源长时间被锁住,影响系统可用性。
五、3PC:三阶段提交
3PC,全称 Three-Phase Commit,是对 2PC 的改进。
它将 2PC 的提交过程拆成三个阶段:
- CanCommit
- PreCommit
- DoCommit
3PC 的核心思路
3PC 在提交前增加了一个 PreCommit 阶段,试图减少 2PC 中参与者长时间阻塞的问题。
流程大致如下:
CanCommit: 询问是否可以提交 PreCommit: 通知即将提交 DoCommit: 正式提交3PC 的优点
- 相比 2PC,阻塞风险降低
- 引入超时机制后,参与者可以在部分场景下自行决策
3PC 的缺点
3PC 依然不能完美解决网络分区问题。
如果发生网络分区,不同节点可能基于超时做出不同决策,导致一致性被破坏。
因此,3PC 在真实工程系统中使用并不多。
六、Paxos:经典共识算法
Paxos 是分布式一致性领域最著名、也最难理解的算法之一。
它解决的问题是:在多个可能失败的节点之间,对某个值达成一致。
Paxos 中有三个角色:
- Proposer:提议者,提出某个值
- Acceptor:接受者,对提案投票
- Learner:学习者,学习最终被选定的值
一个节点可以同时扮演多个角色。
Paxos 的基本约束
Paxos 希望满足:
- 只能有一个值最终被选定
- 被选定的值必须是某个 Proposer 提出的值
- 一旦某个值被选定,所有 Learner 最终都能学习到这个值
- 少数节点故障不影响整体决策,只要多数派可用
多数派机制
Paxos 依赖多数派 Quorum。
如果有 5 个节点,那么多数派至少是 3 个。
任何两个多数派一定有交集。
多数派 1: A B C 多数派 2: C D E 交集: C这个交集非常关键。它保证后续提案可以知道之前可能已经被接受的值,从而避免多个值同时被选定。
Paxos 两个阶段
第一阶段:Prepare / Promise
Proposer 生成一个全局递增的提案编号 n,向多数 Acceptor 发送 Prepare 请求。
Prepare(n)Acceptor 收到后,如果 n 大于它见过的所有提案编号,就承诺:
- 不再接受编号小于 n 的提案
- 返回自己曾经接受过的最大编号提案和值
这叫 Promise。
第二阶段:Accept / Accepted
Proposer 收到多数派 Promise 后,根据规则选择值:
- 如果没有 Acceptor 返回已接受的值,可以使用自己的值
- 如果有 Acceptor 返回已接受的值,必须选择编号最大的那个已接受值
然后 Proposer 向多数 Acceptor 发送 Accept 请求。
Accept(n, value)Acceptor 如果没有承诺过更大的编号,就接受该提案。
当某个值被多数 Acceptor 接受时,这个值就被选定。
Paxos 的优点
- 理论严谨
- 能容忍少数节点故障
- 基于多数派保证一致性
- 是很多共识算法的理论基础
Paxos 的缺点
- 难理解
- 难实现
- 工程落地复杂
- 原始 Paxos 只决定一个值,实际系统需要 Multi-Paxos 来复制日志
七、Multi-Paxos
单次 Paxos 只能对一个值达成一致。
但真实系统通常需要对一系列操作达成一致,例如:
log[1] = set x = 1 log[2] = set y = 2 log[3] = delete zMulti-Paxos 就是把 Paxos 扩展到多条日志。
它通常会选出一个稳定 Leader,由 Leader 负责连续发起提案。
这样可以减少 Prepare 阶段的开销,提高性能。
Multi-Paxos 的核心优化
普通 Paxos 每次提交都需要两轮通信:
Prepare -> Promise Accept -> AcceptedMulti-Paxos 在 Leader 稳定的情况下,可以复用 Leader 身份,后续日志只需要 Accept 阶段。
Accept -> Accepted这让它更适合工程系统。
八、Raft:更容易理解的共识算法
Raft 的目标是提供一个比 Paxos 更容易理解、也更容易实现的共识算法。
很多现代分布式系统使用 Raft,例如:
- etcd
- Consul
- TiKV
- Nacos 部分一致性实现
- CockroachDB 的部分复制机制
Raft 把共识问题拆成三个子问题:
- Leader 选举
- 日志复制
- 安全性保证
九、Raft 的角色
Raft 中每个节点有三种状态:
- Leader:领导者,处理客户端请求并复制日志
- Follower:跟随者,被动接收 Leader 消息
- Candidate:候选者,发起选举
正常情况下,一个 Raft 集群只有一个 Leader。
Client -> Leader -> Followers十、Raft Leader 选举
Raft 使用任期 term 来区分不同选举周期。
每个节点启动时都是 Follower。
如果 Follower 在 election timeout 时间内没有收到 Leader 的心跳,就会变成 Candidate,并发起选举。
选举流程:
- 当前节点 term + 1
- 将自己变成 Candidate
- 给自己投票
- 向其他节点发送 RequestVote
- 获得多数票后成为 Leader
为什么需要随机超时
如果所有节点同时超时,就会同时发起选举,导致票数分散。
Raft 使用随机 election timeout,降低选票冲突概率。
Node A timeout: 150ms Node B timeout: 230ms Node C timeout: 310ms通常最早超时的节点会先发起选举,并更容易成为 Leader。
十一、Raft 日志复制
客户端写请求会先发送给 Leader。
Leader 将操作追加到自己的日志中,然后并行发送给 Followers。
Client -> Leader: set x = 1 Leader append log Leader -> Followers: AppendEntries Followers append log Followers -> Leader: success Leader receives majority success Leader commits log Leader applies to state machine只要日志被多数节点复制成功,Leader 就可以提交该日志。
已提交日志
一条日志被多数节点保存后,就可以认为是 committed。
提交后,Leader 会把这条日志应用到状态机,并通过后续心跳通知 Followers 也提交。
log[8] copied to A, B, C 5 节点集群中已有 3 个节点保存 log[8] committed十二、Raft 的安全性
Raft 通过几个规则保证安全。
1. Leader 只追加日志
Leader 不会覆盖或删除自己的日志,只会追加新日志。
2. 日志匹配原则
如果两个日志条目拥有相同的 index 和 term,那么它们之前的所有日志也完全相同。
log[5].term = 3 如果两个节点的 log[5] 都是 term 3 那么 log[1..5] 都应该一致3. Leader 完整性
如果某条日志已经在某个任期被提交,那么之后所有 Leader 都必须包含这条日志。
这是 Raft 能够保证已提交数据不丢失的关键。
十三、ZAB:ZooKeeper 的一致性协议
ZAB,全称 ZooKeeper Atomic Broadcast,是 ZooKeeper 使用的一致性协议。
ZAB 和 Raft 很相似,也依赖 Leader 和多数派机制。
ZooKeeper 的数据更新必须经过 Leader,然后广播给 Followers。
ZAB 的核心目标
ZAB 主要保证:
- 原子广播
- 全局有序
- 崩溃恢复
- Leader 切换后数据不丢失
ZAB 的基本流程
- Client 向 ZooKeeper 发起写请求
- 请求转发给 Leader
- Leader 生成事务 Proposal
- Leader 广播 Proposal 给 Followers
- 多数 Followers 返回 ACK
- Leader 发送 COMMIT
- 各节点应用事务
Client -> Leader Leader -> Followers: Proposal Followers -> Leader: ACK Leader -> Followers: COMMIT十四、Raft 与 ZAB 的区别
Raft 和 ZAB 都使用 Leader 和多数派,但关注点略有不同。
| 对比项 | Raft | ZAB |
|---|---|---|
| 典型系统 | etcd、Consul | ZooKeeper |
| 核心模型 | 复制日志 | 原子广播 |
| Leader 选举 | Raft 自身定义 | ZooKeeper 选举机制 |
| 写入路径 | Leader 复制日志 | Leader 广播事务 |
| 工程目标 | 易理解、易实现 | 服务 ZooKeeper 数据模型 |
| 一致性基础 | 多数派 | 多数派 |
十五、Gossip:最终一致性的传播协议
Gossip 不是强一致性共识算法,而是一种最终一致性传播协议。
它的思想类似流言传播。
一个节点知道某个消息后,会随机告诉其他节点。被通知的节点再继续传播。
A -> B, C B -> D C -> E D -> F经过多轮传播后,整个集群最终都会知道这个消息。
Gossip 的优点
- 去中心化
- 可扩展性强
- 容错性好
- 适合大规模集群状态传播
Gossip 的缺点
- 不保证强一致
- 收敛有延迟
- 短时间内不同节点状态可能不同
典型应用
- Cassandra 节点状态传播
- Consul 成员发现
- Dynamo 风格系统
- 分布式缓存节点状态同步
十六、常见算法对比
| 算法 | 类型 | 一致性 | 是否依赖 Leader | 是否阻塞 | 典型场景 |
|---|---|---|---|---|---|
| 2PC | 分布式事务协议 | 强一致事务结果 | 协调者 | 会阻塞 | XA 事务 |
| 3PC | 分布式事务协议 | 尽量强一致 | 协调者 | 降低阻塞 | 理论方案较多 |
| Paxos | 共识算法 | 强一致 | 不强制固定 Leader | 不依赖单点 | 理论共识 |
| Multi-Paxos | 日志共识 | 强一致 | 通常有 Leader | 多数派可用即可 | 分布式存储 |
| Raft | 日志共识 | 强一致 | 是 | 多数派可用即可 | etcd、Consul |
| ZAB | 原子广播 | 强一致 | 是 | 多数派可用即可 | ZooKeeper |
| Gossip | 传播协议 | 最终一致 | 否 | 不阻塞 | 状态传播 |
十七、如何选择一致性算法
1. 如果你要做分布式事务
可以考虑:
- 本地消息表
- Saga
- TCC
- 2PC / XA
但在高并发互联网系统中,强 XA 事务通常不是首选,因为它会带来资源锁定、性能下降和可用性问题。
更常见的做法是业务补偿加最终一致性。
2. 如果你要做配置中心或注册中心
可以考虑:
- Raft
- ZAB
- Multi-Paxos
这类系统通常要求元数据强一致,因为配置错误或服务发现错误会影响整个系统。
3. 如果你要做大规模状态传播
可以考虑 Gossip。
例如节点上下线、心跳状态、缓存拓扑变化等场景,不一定需要所有节点立即强一致。
4. 如果你要做分布式锁
应该优先选择已经成熟的 CP 系统,例如:
- ZooKeeper
- etcd
分布式锁对一致性要求很高,不建议基于普通缓存系统随意实现。
十八、工程实践中的取舍
真实系统很少只追求单一指标。
一致性越强,通常意味着:
- 写入延迟更高
- 可用性更容易受网络影响
- 系统实现更复杂
- 运维成本更高
一致性越弱,通常意味着:
- 性能更好
- 可用性更高
- 业务需要处理冲突
- 需要补偿、重试、幂等和对账机制
所以工程上真正重要的问题不是“哪个算法最好”,而是:
这个业务到底需要多强的一致性? 数据短暂不一致会造成什么后果? 是否可以通过补偿机制修复? 用户是否能感知? 资金、库存、权限、配置等关键数据是否允许最终一致?十九、一个简单的选择建议
| 业务场景 | 推荐一致性策略 |
|---|---|
| 金融转账 | 强一致或可靠最终一致,必须有对账 |
| 库存扣减 | 强约束库存可用 CP,普通库存可最终一致 |
| 用户资料更新 | 最终一致通常可接受 |
| 配置中心 | 强一致 |
| 注册中心 | CP 或 AP 取决于业务容忍度 |
| 分布式锁 | 强一致 |
| 日志采集 | 最终一致 |
| 缓存同步 | 最终一致 |
| 订单状态流转 | 本地事务 + 消息最终一致 + 幂等 |
二十、总结
分布式一致性算法本质上是在不可靠环境中协调多个节点的决策。
2PC 和 3PC 主要面向分布式事务提交,强调多个参与者对事务结果达成一致。
Paxos、Multi-Paxos、Raft 和 ZAB 属于共识或日志复制协议,强调多个节点对操作顺序和状态变更达成一致。
Gossip 则不追求强一致,而是通过传播机制实现最终一致,适合大规模状态同步。
在工程实践中,一致性不是越强越好。强一致能减少业务复杂度,但会牺牲性能和可用性;最终一致性能提升系统吞吐和容错能力,但要求业务具备幂等、重试、补偿和对账能力。
设计分布式系统时,最重要的是先判断业务的一致性边界,再选择合适的算法和架构,而不是盲目追求某个“最强”的一致性方案。