本文行文:为什么主从复制不够、需要共识 → Raft 怎么把共识拆成三个子问题 → etcd 如何把它做成生产级实现 → Redis Sentinel 为什么只是”Raft-like” → MySQL 为什么很少谈共识。
1. 共识机制
简介
为什么需要:主从复制的天花板
前面的文章里,MySQL 高可用靠 binlog 复制 + 外部选主,Redis 高可用靠主从 + 哨兵。它们的共同点是:复制解决”数据有几份”,但”谁说了算”始终悬而未决。
| 问题 | 只有主从复制时 | 需要共识时 |
|---|---|---|
| 谁是主? | 配置里写死的,或由外部组件(脚本、MHA Manager、哨兵)裁决 | 由节点之间投票裁决,不依赖任何外部单点 |
| 主库挂了谁知道? | 监控系统(可能误判、可能自己挂了) | 多数派失联即判定,判定本身也经过共识 |
| 新主数据全不全? | 人工或脚本比较位点,容易选错 | 选举规则内建”日志至少不落后于投票者”的约束 |
| 网络分区怎么办? | 两侧可能各自认为自己是主 → 脑裂 | 只有拿到多数派的一侧能继续服务,另一侧自动只读 |
| 已确认的写入会丢吗? | 异步复制下会丢(RPO > 0) | 多数派确认后才提交,已确认的写入不会丢 |
复制提供副本,共识提供”唯一的真相”。只要系统里存在”多个副本 + 需要自动决策”,就迟早要面对共识问题:
- etcd / Consul / ZooKeeper 存的是”集群的元数据”,本身就必须强一致,否则 K8s 的调度、租约、分布式锁全部失效;
- Redis Sentinel 要在多个哨兵之间决定”谁是新主”,本质是一次配置共识;
- MySQL MGR 要通过组通信决定”这个事务是否被接受且顺序固定”,本质是一次日志共识。
Paxos:共识的理论起点
Paxos(Lamport, 1998)是第一个被严格证明正确的共识算法,围绕三个角色:
| 角色 | 职责 |
|---|---|
| Proposer | 提出提案(编号 + 值) |
| Acceptor | 对提案投票(承诺 + 接受),多个 Acceptor 组成多数派 |
| Learner | 学习已被选定的值 |
核心是两阶段:
- Prepare 阶段:Proposer 用编号
n询问多数派 Acceptor:“是否已接受过编号小于n的提案?“Acceptor 承诺不再接受编号小于n的提案,并返回它已接受的最高编号提案的值。 - Accept 阶段:Proposer 若发现有人已接受过某值,就必须沿用那个值(这是安全性的关键);否则可用自己的值。向多数派发送 Accept 请求,多数派接受即提案被选定。
于是得到共识的两个基本事实:
- 多数派交集:任意两个多数派必然有交集,因此”已被选定的值”一定会被后续 Proposer 看到并沿用,不会出现两个不同的值同时被选定;
- 半数才可推进:少于多数派的任何集合都无法独立做出决定,这是”防脑裂”的数学基础。
Paxos 的问题不在正确性,而在可理解性与工程化:
- 原始 Paxos 只描述了”选定一个值”,而工程需要的是连续决策的日志(Multi-Paxos),其中的 Leader 选举、日志空洞填补、成员变更都要额外设计;
- 论文晦涩,实现差异极大,几乎没有两个团队的 Paxos 实现长得一样;
- 缺少”领导者”这一显式角色时性能很差(每写一条日志都要两轮往返)。
Raft:为可理解性而生的共识
Raft(Ongaro & Ousterhout, 2014)的目标很直白:在功能与性能上与 Multi-Paxos 等价,但把问题拆成几个可独立理解、可独立实现的部分。
Raft is a consensus algorithm that is designed to be easy to understand. It’s equivalent to Paxos in fault-tolerance and performance.
相比 Paxos,Raft 做的关键工程化选择:
| 设计选择 | 带来的好处 |
|---|---|
| 强领导(Strong Leader):日志只能由 Leader 流向 Follower | 把”多 Proposer 竞争”简化为”选出一个 Leader 后顺序追加”,问题分解变得自然 |
| 随机化选举超时 | 用极简的机制打破投票平局,避免复杂的优先级协议 |
| 任期(Term)作为逻辑时钟 | 所有过期信息都能用”任期号更小”一概拒绝,无需额外时序假设 |
| 日志连续性约束 + 提交规则 | 状态机复制(Replicated State Machine)的正确性可以被逐步论证 |
Raft 共识
对于一个复杂的共识问题,斯坦福大学的 Ongaro 将其拆分成三个子问题:
- Leader Election(领导者选举):集群中必须有一个 Leader,且任意时刻至多一个;
- Log Replication(日志复制):Leader 接收客户端请求,作为日志条目复制到多数节点后提交;
- Safety(安全性):任何已提交的日志条目都不会被覆盖或丢失,状态机的执行顺序在所有节点上一致。
这三个子问题加上”成员变更(Membership Change)“,就构成了完整可用的 Raft。下面逐个展开。
角色与任期
每个节点在任意时刻处于三种角色之一:
| 角色 | 行为 |
|---|---|
| Leader | 处理所有客户端写请求,向 Follower 发送心跳与日志 |
| Follower | 被动响应:收到投票请求就投票,收到日志就追加 |
| Candidate | 选举期间的临时角色:向其他节点拉票 |
任期(Term) 是单调递增的整数,充当逻辑时钟:
- 每次选举开始,任期 +1;
- 任期号承载在每一次 RPC 里;任何节点收到比自己任期更大的请求,立即更新自己的任期并转为 Follower;
- 收到比自己任期更小的请求,直接拒绝(并回传自己的任期,让对方知难而退)。
由于”同一任期最多一个 Leader”,所有一致性判断都可以归结为”任期号大小比较”,不需要假设时钟同步。
子问题一:Leader Election
- 集群启动或 Leader 失联时,Follower 在 election timeout(通常 150~300ms) 内没收到心跳,就转为 Candidate;
- Candidate 任期 +1,投自己一票,向所有节点并发发送
RequestVote; - 节点在一个任期内只投一票(先到先得),并在”候选人的日志至少和自己一样新”时才投票;
- 拿到多数派票的 Candidate 成为 Leader,立即发送心跳宣告权威;
- 如果出现平票(常见于多个节点同时超时),本轮无人当选,各自等待随机化的选举超时后重试——随机化是打破僵局的关键,把活锁概率压到可忽略。
RequestVote(term, candidateId, lastLogIndex, lastLogTerm) → (term, voteGranted)AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit) → (term, success, conflictIndex, conflictTerm)Raft 只有这两个 RPC。AppendEntries 同时承担日志复制和心跳两个职责(entries 为空时就是心跳)。
子问题二:Log Replication
一次完整的写入:
- 客户端把命令发给 Leader;
- Leader 把命令追加为本地日志条目,记录
(index, term, command); - Leader 向所有 Follower 发
AppendEntries; - 当该条目被复制到多数节点(含 Leader 自己)时,Leader 把它提交(commit)并应用到状态机,然后把结果返回客户端;
- 后续的心跳会携带
leaderCommit,Follower 据此知道哪些条目已提交,并按顺序应用到自己的状态机。
两条关键不变式:
- Log Matching Property:如果两个日志在相同 index 上的条目 term 相同,则它们及之前的所有条目完全相同;
- 状态机安全:Leader 只能提交”自己任期内”的条目;跨任期的旧条目要等本任期的新条目提交后,才被间接提交(这是 Raft 论文中容易被忽略、但实现时极易出错的一条规则)。
日志不一致的修复:Follower 拒绝不一致的 AppendEntries 后,Leader 用 nextIndex 逐步回退(论文原版是每次减一,实现上通常用 conflictIndex 一次跳过大段冲突日志),直到找到匹配点,再从这里开始覆盖 Follower 上所有冲突条目——Follower 上未提交的条目以 Leader 为准。
子问题三:Safety
Raft 用五条规则保证”已提交的永不丢失、状态机执行顺序一致”:
| 规则 | 内容 | 防住的问题 |
|---|---|---|
| 选举安全 | 同一任期至多一个 Leader(每节点每任期一票 + 多数派交集) | 双主/脑裂 |
| 选举限制 | 只有”日志足够新”的节点才能当选 | 落后节点当主导致数据被覆盖 |
| 提交规则 | 只有多数派复制成功才提交;且只提交本任期条目 | 已提交日志被回滚 |
| 日志匹配 | 同 index 同 term 的日志前缀完全一致 | 日志分叉 |
| 状态机安全 | 所有节点按相同顺序应用相同日志 | 副本间结果不一致 |
持久化要求:currentTerm、votedFor、日志条目在响应 RPC 前必须落盘(fsync)。只把 commitIndex、lastApplied 放内存是可以的(重启后从 Leader 重建)。忘了持久化 votedFor,就会在重启后投出两票,直接破坏选举安全性——这是自研 Raft 最常见的翻车点。
子问题四:成员变更
集群运维中增删节点不能靠”停集群改配置”,否则配置不一致的瞬间可能出现两个多数派。Raft 的解法是联合共识(Joint Consensus):先进入 C_old,new 联合配置(任何决定需同时在两套配置中取得多数),再切到 C_new。工程实现里更常用的是单节点变更(One-at-a-time):一次只加或减一个节点,此时新旧配置的多数派必然有交集,因而不需要联合配置的复杂度。
etcd 的成员变更(etcdctl member add/remove)就是走这条路径,因此它要求一次只变一个成员,且变更期间集群不应处于故障状态。
共识的代价
| 代价 | 说明 |
|---|---|
| 延迟 | 每次写要等多数派落盘,延迟 ≥ 多数派中最慢节点的往返 + fsync 时间 |
| 可用性上限 | N 节点的集群只能容忍 (N-1)/2 个故障;跨地域部署时 RTT 会显著放大写延迟 |
| 运维复杂度 | 成员变更、快照压缩、磁盘 IOPS 都直接影响服务 |
| 规模上限 | 节点越多容错越强但写越慢,因此生产上 3 节点、5 节点为主,很少超过 7 节点 |
2. K8s etcd
标准 Raft 的教科书实现
结论先行: etcd 是 Raft 协议的生产级实现,Leader 选举与日志复制深度耦合,写操作必须获得多数派(quorum)确认,强一致性优先。
Raft 核心机制:
- Leader 选举:集群启动时无 Leader,各节点随机超时后发起选举,请求投票;获得多数票的节点成为 Leader,开始发送心跳维持权威。
- 日志复制:所有写请求由 Leader 处理,Leader 将日志条目复制到 Follower,当日志被复制到多数节点(quorum)后,该写操作才被提交并应用到状态机。
- Quorum 计算:
quorum = (N / 2) + 1。三节点集群 quorum 为 2,可容忍 1 个节点故障;五节点 quorum 为 3,可容忍 2 个节点故障。奇数节点是生产最佳实践,因为偶数节点在分区时可能均分导致双方都无法达成多数。 - 故障转移:Leader 故障或网络隔离后,Follower 心跳超时触发新一轮选举,自动选出新 Leader,无需人工干预。在 quorum 恢复之前,etcd 会暂停写入,恢复后自动安全地继续。
etcd 的 Raft 是数据共识——写操作必须经过多数派确认才提交,保障数据的线性一致性。
etcd 在这套理论上的工程化选择
etcd 用的 raft 库(go.etcd.io/raft)是一个纯粹的 Raft 状态机,不负责网络、磁盘和线程,因此它的边界非常清晰:
| 层 | 职责 | 说明 |
|---|---|---|
raft 库 | 状态机推进、投票、日志序列 | 无 IO、无网络、无锁竞争,便于测试 |
| WAL(Write-Ahead Log) | 持久化 HardState(term/vote/commit)与日志条目 | 每次变更都 fsync,这是 etcd 写延迟的主要来源 |
| Snapshot + 后端存储 | 定期快照并压缩日志;bbolt 存储状态机 | 快照让日志可以被截断,否则磁盘无限增长 |
gRPC / etcdserver | RPC、租约、watch、事务、权限 | 把 Raft 包装成对外的 KV 服务 |
参数与行为特征(对应上面的理论):
| 概念 | etcd 参数 | 说明 |
|---|---|---|
| 心跳间隔 | --heartbeat-interval(默认 100ms) | Leader 发送心跳的周期 |
| 选举超时 | --election-timeout(默认 1000ms) | 应为心跳的 5~10 倍,且远大于网络抖动 |
| 快照阈值 | --snapshot-count(默认 100000) | 每 10 万个已提交事务打一次快照并截断 WAL |
| 成员变更 | etcdctl member add/remove | 内部走单节点变更,一次只变一个且不应在故障中执行 |
| 请求超时 | --request-timeout | 避免长时间阻塞在失联节点上 |
读写语义:为什么写入只能问 Leader
- 写:必须发给 Leader,由它走完整的 Raft 流程。客户端发错节点时会收到重定向提示,官方客户端会自动重试到 Leader。
- 线性一致读(默认
--consistency=s):走 Raft 日志(ReadIndex 优化后无需真正写日志),保证读到的是最新已提交数据。 - 串行读(
--consistency=l):绕过 Raft 直接从本地状态机读,延迟低但可能读到旧数据(Follower 落后时尤其明显),只在能容忍陈旧的场景使用。 - Watch:基于 MVCC 的历史版本,可以指定
--rev回溯,这也是 K8s 各类控制器实现”事件可靠投递”的基础。
用 K8s 的视角理解共识
K8s 的整个控制面都建在 etcd 的强一致之上:
- API Server 无状态:多个实例谁都能处理请求,真正的”唯一真相”在 etcd;
resourceVersion:来自 etcd 的 MVCC 版本号,是 K8s 乐观并发控制(conflict→ 重试)的基础;- Leader Election(
coordination.k8s.io/Lease):kube-controller-manager、kube-scheduler 通过租约选主,多个副本只有 leader 真正工作——这本身就是把共识当”分布式锁”用; - Controller 的期望状态收敛:靠 watch 拿到有序的变更事件流,从而能实现”至少一次”的处理语义。
因此”etcd 挂了 K8s 控制面就瘫痪”不是 bug 而是设计:共识层不允许被绕过。
3. Redis Sentinel
轻量级“Raft-like”变种
结论先行: Sentinel 的故障转移本质上是一个简化版、以可用性优先的 Raft-like 协议,它只做“选主 + 配置切换”,不参与数据日志的共识复制。
故障转移流程(五步走):
- 主观下线(sdown):单个 Sentinel 在
down-after-milliseconds内没收到 master 的 PING 响应,就单方面标记其“主观下线”。 - 客观下线(odown):该 Sentinel 向其他 Sentinel 发起询问,当超过
quorum(通常为多数)的 Sentinel 都认为 master 不可达时,才判定“客观下线”。这一步是分布式共识的入口。 - Leader Sentinel 选举:确认 odown 后,Sentinel 之间基于 Raft-like 投票选出一个 Leader,由它主导后续操作。投票携带 epoch(配置纪元) 递增,获得多数票的 Sentinel 当选。与标准 Raft 的关键差异在于:Sentinel 的选举不依赖日志索引和任期完整性,而是侧重于监控状态和配置更新,省略了日志复制与持久化步骤。
- Slave 晋升筛选:Leader 从 slave 中按优先级筛选新 master,标准包括:复制偏移量最大(数据最新)、网络连接正常、运行状态健康。
- 配置切换与通知:Leader 向选中的 slave 发送
SLAVEOF NO ONE使其晋升,其余 slave 重定向到新 master,并通过 Sentinel 的 Pub/Sub 通知客户端新的 master 地址。
Sentinel 的共识是 “故障检测共识 + Leader 选举”,而非数据复制共识。它允许在网络分区期间牺牲强一致性换取可用性,这也是 Jepsen 测试中 Sentinel 在分区时可能丢数据的原因。
把 Sentinel 和 Raft 逐条对照
| Raft 的概念 | Sentinel 里的对应物 | 差异与后果 |
|---|---|---|
任期 term | 配置纪元 epoch(current_epoch / configEpoch) | 同样是单调递增的逻辑时钟,用来拒绝过期信息、收敛配置 |
RequestVote | SENTINEL is-master-down-by-addr(带 runid 表示要投票) | 投票语义相同:每个 Sentinel 在每个纪元只投一票 |
| 心跳 | 每秒 PING | 心跳失败计数达到阈值 → sdown → 询问 → odown |
| election timeout | down-after-milliseconds + 随机延迟 | 随机化是为了避免同时发起投票 |
| 日志复制 | 不存在 | 不复制任何数据,只传播”谁是主”这一条配置 |
| 提交多数派 | 判定 odown 的 quorum + 选举 leader 的多数派 | quorum 决定”是否认为下线”,多数派决定”谁有权切换” |
| 持久化 | sentinel.conf 里的 epoch 与配置 | 比 etcd 弱:配置落盘但不是 WAL 式的严格持久化 |
| 读一致性 | 无 | 客户端可能读到旧主(切主瞬间),Sentinel 不提供任何线性一致保证 |
为什么说它”牺牲一致性换可用性”
标准 Raft 的核心是”多数派确认才提交”,因此已提交的数据永远不会丢。Sentinel 没有这一层:
- 数据复制仍是异步的:Redis 主从复制的 offset 差距意味着新主可能缺一段写入,切换后会永久丢失;
- 共识只覆盖”谁是主”,不覆盖”数据是否到位”:Sentinel 可以正确选出新主,但无法保证新主拥有全部数据;
- 分区期间宁可切换也不阻塞:多数派一侧照常切换并对外服务,这是可用性优先的明确取舍;
- 没有日志安全性的等价规则:它不能用”日志至少不落后”来约束候选人——只能比较 offset,而 offset 并非强一致量。
所以 Jepsen 之类的测试中,Sentinel 在分区场景下出现丢数据(已确认写入丢失)是设计使然,而非实现 bug。想要”零丢失”的 Redis 高可用,需要的是共识化的复制(例如 Redis 自身的 Raft 类方案、或把强一致元数据放到 etcd/ZK 中)。
4. MySQL?
同样是数据库,为什么 MySQL 高可用很少谈论“共识机制”?
数据层
核心原因:MySQL 的传统高可用范式与共识协议的设计目标存在结构性错位。
- 传统主从复制的本质是“异步/半同步”。MySQL 默认复制是异步的:主库写完 binlog 就返回成功,不等从库确认。共识协议的起点是“多数派确认才算提交”,这与 MySQL 异步复制的设计哲学根本不同。
- 主流 MySQL HA 方案(MHA)是“外部管理器单点决策”。MHA 的故障切换由单个 Manager 进程决策:检测主库失败后,从从库中选数据最新的提升为新主,再补差异日志。Manager 本身不是共识集群,它挂掉后集群仍可运行,但无法自动切换。这种架构的共识“发生在外部管理器”,而非节点之间的投票。
- MGR是唯一引入 Paxos 的官方方案,但工程成熟度劝退了大规模采用。MGR 底层基于 Paxos 协议做认证和冲突检测,理论上可提供零数据丢失的自动选主。但实际生产中暴露了硬伤:
- Paxos 消息未持久化到磁盘,MySQL 重启后认证数据库缺失,容易导致持续处理持久化 Paxos 消息时出现不一致状态。
- 此外,MGR 在网络不佳或大事务场景下崩溃过且难以修复,导致不少团队研究后仍退回主从模式。
| 维度 | Redis Sentinel / etcd Raft | MySQL 传统 HA |
|---|---|---|
| 共识对象 | 节点状态 / 配置变更(Sentinel);数据日志(etcd) | 传统方案不涉及共识;MGR 共识数据但工程不成熟 |
| 决策主体 | Sentinel 集群 / Raft 集群 | 外部 Manager(单点) / MGR 集群(Paxos,但落盘缺陷) |
| 一致性目标 | 可用性优先(Sentinel)/ 强一致(etcd) | 异步/半同步,RPO>0 可接受 |
| 工程成熟度 | 生产验证充分 | MHA 成熟但无共识;MGR 有共识但稳定性不足 |
MySQL 生态的历史惯性选择了 “外部管理器 + 异步复制” 的轻量路径;当真正尝试把 Paxos 塞进复制层时(MGR),又因持久化缺陷和稳定性问题未能成为主流讨论的默认选项。
主节点决策
虽然 MySQL 本身不采用共识机制,但如果把视野放宽到”谁来决定谁是主”,MySQL 生态其实有三种截然不同的答案:
| 路线 | 代表 | 共识在哪 | 说明 |
|---|---|---|---|
| 控制面外包 | Patroni(PG)、Vitess + etcd/ZK、Orchestrator + Consul | 外部共识存储 | 数据库自己不实现共识,把”选主 + 配置”交给 etcd/ZK,这正是 Raft 最擅长的小数据强一致场景。这是当前最主流的务实方案 |
| 写进复制层 | MySQL MGR(XCom) | 组通信的 Paxos 变体 | 8.0 后 XCom 把消息持久化到 mysql 库,早期”重启后不一致”的缺陷已被修复,但运维复杂度、跨地域延迟、大事务下的表现仍限制了普及 |
| 认证式同步 | MariaDB Galera / Percona XtraDB Cluster | 写集认证(非共识) | 广播写集 + 冲突认证,没有 Leader 选举也没有日志共识,靠 quorum 拒写防脑裂;因此它是”同步复制”而非”共识复制”,流控与热点冲突是它的固有痛点 |
顺带回答一个常见困惑:为什么 PostgreSQL 的高可用文档里到处都是 etcd/Consul,而 MySQL 的文档里很少提? 因为 PG 社区更早地把选主问题识别为”共识问题”并外包了(Patroni 事实上成为标准),而 MySQL 社区的历史路径是 MHA/Orchestrator 这类”管理器”方案,共识是被隐式吞掉的。这既是历史惯性,也是运维习惯的差异。
5. 区别
四种”选主/共识”机制对比
| 维度 | etcd(标准 Raft) | Redis Sentinel(Raft-like) | MySQL MGR(XCom/Paxos) | 主从 + 外部管理器(MHA/脚本) |
|---|---|---|---|---|
| 共识对象 | 数据日志(每一条写) | 仅”谁是主”的配置 | 事务的组内顺序与认证 | 无共识 |
| 决策主体 | 节点投票(多数派) | Sentinel 投票(多数派) | 组内多数派原子广播 | 单个外部进程/人 |
| 能否自动选主 | 能 | 能 | 能 | 取决于脚本,管理器自身是单点 |
| 数据是否会丢 | 不会(已提交即多数派落盘) | 会(异步复制 + 无数据共识) | 单主模式基本不会(多数派提交) | 会(RPO > 0) |
| 脑裂防护 | 少数派无法提交 | 少数派无法完成切换 | 少数派无法提交 | 依赖 STONITH / VIP 隔离,弱 |
| 持久化要求 | term/vote/log 必须 fsync | 配置文件级 | 依赖组通信层(历史上有缺陷) | 无 |
| 客户端语义 | 线性一致读写、Watch | 只知道”主在哪” | 普通 MySQL 协议 | 普通 MySQL 协议 |
| 运维复杂度 | 中(磁盘、快照、成员变更) | 低(≥3 个哨兵进程) | 高(版本、网络、冲突回滚) | 低但需人工介入 |
| 典型 RTO | 秒级(选举 1~2 个超时周期) | 秒~分钟级 | 秒级 | 分钟~小时级 |
共识谱系
把”数据要不要经过共识”作为横轴,就能看清各系统的定位:
| 系统/协议 | 共识层位置 | 说明 |
|---|---|---|
| Raft / Paxos / ZAB | 通用共识协议 | ZAB 是 ZooKeeper 的原子广播协议,与 Paxos 同族,强调”主备顺序” |
| Multi-Raft(TiDB) | 数据分片级 | 每个 Region 一个 Raft 组,成千上万个组并行,共识与分片结合 |
| etcd / Consul | 元数据级 | 小数据量、强一致,是”共识的样板间” |
| Redis Sentinel | 控制面级(无数据共识) | 只共识配置,不共识数据 |
| Redis Cluster | 控制面级(无数据共识) | 与哨兵同属”Raft-like”:epoch + 每主每纪元一票 + 多数派,但没有日志与严格的持久化 |
| MySQL MGR | 复制层级 | 用 Paxos 变体(XCom)做组通信 |
| MySQL 传统 HA | 无共识 | 管理器/云平台决策 |
| Patroni + etcd/ZK | 控制面级 | PostgreSQL 的常见做法:把选主这一”共识问题”外包给专门的共识存储 |
观察结论:成熟生态的普遍做法是把共识”外包”给专门的共识存储,而不是在数据层重新造一个。 Patroni(PostgreSQL)、Vitess(MySQL)、Kubernetes 都是这个思路。
6. 小结
- 共识解决的是”多个副本如何在不依赖外部单点的前提下达成同一个决定”:多数派交集是安全性的数学基础,因此”少于多数派就不能做决定”是防脑裂的根本,也是共识与异步复制最重要的分野。
- Paxos 提供了理论,Raft 提供了工程。Raft 把共识拆成选举、日志复制、安全性(+ 成员变更)四个可独立验证的部分,用”强 Leader + 随机化选举超时 + 任期逻辑时钟”换来了可实现性与可教学性。
- 安全性全靠几条规则撑着:每任期一票、候选人日志不能落后、只提交多数派复制成功的条目(且只提交本任期条目)、
term/vote/log必须落盘。少任何一条,“共识”就退化成一个会丢数据的乐观复制协议。 - etcd 是标准 Raft 的教科书实现,也是”共识被当作基础设施”的最佳范例:K8s 控制面的租约、
resourceVersion、watch 语义都建立在它的线性一致性上。 - Redis Sentinel 只是 Raft-like:它共识”谁是主”,不共识数据,因此设计上就允许分区期间丢数据。这个取舍适合缓存与会话,不适合锁与幂等键。
- MySQL 传统高可用不依赖共识,因为”外部管理器 + 异步复制”的路径更轻;但当 RPO 要求趋近于 0 时,共识终将回到视野——要么写进复制层(MGR),要么外包给 etcd/ZK(Patroni 模式)。
- 一句话选型:能不能接受”丢一点数据”,决定了你要的是”复制 + 监控”,还是”共识”。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 36 天
部分内容可能已过时