一篇读懂 Raft:选主、日志复制与安全性

共识问题的本质

Raft 共识算法的三大机制

分布式系统的核心难题:多台机器如何对「一个值」达成一致(谁来当主、配置改成什么、这条数据该不该提交)——即使有机器宕机、网络分区、消息乱序。共识算法(Consensus Algorithm)就是解决这个问题的数学工具——Paxos 是祖师爷(但以晦涩著称——论文难懂、实现更难),Raft 是 2014 年诞生的「可理解版共识」——作者 Diego Ongaro 的博士论文目标就是「可理解性」——用「选主 + 日志复制」的直觉模型实现与 Paxos 同等的正确性。

今天的工程实践里,Raft 是绝对主流:etcd(Kubernetes 的元数据存储)、TiKV(TiDB 的存储层)、Consul、RethinkDB、CockroachDB——全部基于 Raft。这篇拆解 Raft 的三大机制。

机制一:选主(Leader Election)

三种角色:Leader(领导者——处理所有写请求)、Follower(跟随者——被动接受)、Candidate(候选者——选主过程中的角色)。选主的流程:Leader 定期发心跳(AppendEntries 的空日志)——Follower 超时没收到心跳 → 转为 Candidate → 任期号(term)+1 → 向所有节点拉票(RequestVote)→ 获得多数票 → 成为 Leader。「多数派(majority)」:5 节点集群需要 3 票——「多数派」是共识的基石(任何决策都要多数派同意——「脑裂」的防御(网络分区时,只有多数派所在分区能工作——少数派分区无法选出 Leader——「CAP 的一致性选择」——Raft 选择一致性牺牲可用性(少数派分区不可用——「分区恢复」:网络恢复后,少数派的 Leader 发现更高任期号 → 自动降级为 Follower → 同步新 Leader 的数据。

选主的细节与陷阱:随机超时(每个 Follower 的选举超时是随机的(150-300ms——「随机化」的目的(避免多个节点同时发起选举(选主冲突——「split vote」(两边各得一半票——任期+1 重来——随机超时让「同时」的概率极低。「日志完整性」的投票约束:投票时会比较候选者的日志(「我的日志至少跟你一样新」才投票(比较最后一条日志的 term 与 index——「日志新者优先」保证选出的 Leader 有最新的已提交数据——「这个约束」是 Raft 安全性的关键之一——避免了「旧数据 Leader 覆盖新数据」的灾难。

机制二:日志复制(Log Replication)

写的流程:客户端请求 → Leader 追加到本地日志(未提交状态)→ 并行发送 AppendEntries 给所有 Follower → 多数派确认后提交 → 应用到状态机 → 回复客户端 → 下一条心跳通知 Follower「提交了」。「日志的结构」:每条日志项 =(任期号,索引,命令)——「日志的顺序」:所有节点的日志顺序必须一致——「日志匹配性质」:如果两个节点的日志在某个索引处的 term 相同,则该索引之前的所有日志都相同——「日志匹配」是复制的正确性基础。

一致性检查与日志修复:AppendEntries 携带「前一条日志的 term+index」——Follower 校验:我这里有这条日志吗?——「不一致 → 拒绝 → Leader 回退」:Leader 递减 nextIndex 重发——「回退到一致点」——Follower 的冲突日志被覆盖——「Leader 的日志永远是对的」(Raft 的强 Leader 设计——「Follower 的冲突日志被覆盖」是安全的(因为 Leader 的日志至少与投票时的多数派一样新——「覆盖的安全性」由选举约束保证——这就是选主约束与复制机制环环相扣的设计。

提交的规则与「旧任期日志」的陷阱:「只提交当前任期的日志」——Leader 不能直接提交旧任期的日志(只能通过提交当前任期的日志间接提交旧日志——「为什么」:旧任期的日志可能只是「复制到多数派但未提交」——直接提交可能与已提交的冲突——「图 8 问题」(Figure 8)——Raft 论文中最烧脑的安全性问题——「no-op 的解决」:新 Leader 上任后立即提交一条 no-op(空日志)——通过提交 no-op 间接提交所有之前的日志——「no-op」是工程实现的必做细节。

graph TD
    A[Raft 三大机制] --> B[选主: term+随机超时+多数票<br/>日志新者优先的投票约束]
    A --> C[日志复制: AppendEntries+一致性检查<br/>多数派确认提交 · Leader 日志覆盖修复]
    A --> D[安全性: 日志匹配性质<br/>只提交当前任期 · no-op 解 Figure 8]

机制三:安全性与工程实现

安全性的保证清单:选举安全(同一任期最多一个 Leader——多数票的唯一性)、日志只追加(Leader 不删不改自己的日志——「日志不可变」是复制的信任基础)、状态机安全(已应用的状态机日志不会消失——「提交规则」保证)、「成员变更」(集群节点数的变更——「单节点变更」(每次只增减一个节点——「避免两多数派」(一次变更多个节点可能出现两个不相交的多数派——「joint consensus」与「单节点变更」两种方案——「单节点变更」更简单成为主流(etcd 的实现)。快照与日志压缩:日志无限增长的问题——「快照」(状态机的当前状态快照——丢弃之前的日志——「快照后」的日志从快照点开始——「InstallSnapshot」(落后太多的 Follower 直接收快照——「快照」是长期运行集群的必需机制。

工程实现与调参:etcd 的实践(Kubernetes 的心脏——「etcd 的调参」:心跳与选举超时(heartbeat 100ms、election 1s——网络延迟的适配——「跨机房部署」的延迟问题(跨机房的心跳延迟导致误选举——「部署的拓扑」(Raft 节点的机房分布(同机房优先、「TiKV 的 Region Raft」:TiKV 把数据切 Region、每个 Region 一个 Raft 组——「Multi-Raft」(一万+ Raft 组的管理——「Multi-Raft 的调度」(Region 的分裂与迁移——「Multi-Raft」是分布式数据库的主流架构(TiKV、CockroachDB——Raft 从「一个集群的共识」进化为「数据分片的共识」——「Raft 的工业化」的广度。

Raft 高频问答

收尾前回答三个高频问题。「Raft 集群的节点数怎么选?」——奇数与规模的权衡:「奇数节点」的原因(偶数节点不增加容错(4 节点容忍 1 个、5 节点容忍 2 个——「偶数」的浪费——「3 vs 5」的选择(3 节点容忍 1 个、5 节点容忍 2 个——「跨机房」的部署(5 节点跨 3 机房(「2+2+1」的分布——「多数派」在任何单机房故障下仍存——「节点数与写性能」的权衡(节点越多、复制越慢——「写延迟 ≈ 1 RTT × log(N)」——「3 或 5」是甜区——「7+」的少见。「读请求要过 Raft 吗?」——三种读的模式:「读的三种模式」:读 Leader 的本地状态(快但可能旧(网络分区的旧 Leader——「旧 Leader」的风险(「CheckQuorum」的防护——线性一致读(「ReadIndex」(确认自己是 Leader 再读——「ReadIndex」的成本(一次心跳往返——「Follower Read」(Follower 读——「Follower Read 的条件」(ReadIndex 转发——「读的一致性级别」按业务选——「强一致读」贵、「单调读」便宜。「Raft 的「脑裂」会丢数据吗?」——不会:「脑裂的不丢数据」:少数派的写不提交(「未提交的写」被覆盖——「覆盖的安全性」(日志约束——「旧 Leader 的写」被丢弃——「丢的是未提交的」——「已提交的永不丢」(提交过的日志被多数派持有——「Raft 的核心承诺」:已提交的数据不丢——「承诺的前提」(正确的配置与实现——「实现的 bug」是真正的风险——「用经过验证的实现」(etcd/etcd raft 库——「自己实现 Raft」的劝退——「Raft 易理解 ≠ 易实现」。Raft 的问答收束:Raft 的三个心智模型——「任期是朝代、日志是正史、多数派是民意」——选主是改朝换代、复制是修史、提交是盖棺——「盖了棺的(已提交)不会被翻案」——这是 Raft 给用户的承诺,也是它所有机制的设计目标。

Raft 的工程细节:etcd 的实现与「Multi-Raft 的调度」

Raft 的工程细节深挖:etcd 的实现要点与 Multi-Raft 的调度机制。「etcd 的实现要点」:「etcd 的存储结构」:mvcc 的多版本(etcd 的 MVCC(key 的多版本存储——「etcd 的 revision」(全局递增的版本号——「watch 的机制」(基于 revision 的事件推送——「K8s 的 watch」(Kubernetes 依赖 etcd 的 watch——「watch 的性能」(大量 watcher 的压力——「etcd 的性能瓶颈」(QPS 的上限(「etcd 的规模限制」(K8s 大规模的 etcd 瓶颈——「etcd 的分片」(未来方向的讨论。「etcd 的运维」:压缩与碎片整理(「compaction」(历史版本的压缩——「defrag」(碎片整理——「defrag 的必要性」(压缩后空间的回收——「defrag 的执行」(逐节点执行(「defrag 的风险」——「etcd 的备份」(snapshot 的定期备份——「etcd 备份」的恢复演练。「Multi-Raft 的调度机制」:「Region 的切分」:Region 的大小(默认 96MB——「Region 的分裂」(超限自动分裂——「Region 的合并」(过小自动合并——「Region 的分裂」对业务的影响(「热点的分裂」(热点 Region 的分裂——「热点调度」(热点 Region 的迁移——「负载均衡的调度器」(PD(Placement Driver)的调度——「PD 的角色」(TiKV 的调度中心——「Region 迁移的成本」(数据搬迁的 I/O——「迁移的限速」。「Multi-Raft 的事务」:「跨 Region 的事务」(分布式事务的复杂度(「2PC 的 percolator」——「事务的规模」(大事务的锁——「大事务的拆分」——「事务的挑战」是 Multi-Raft 架构的深水区。Raft 的进阶总结:etcd 的实现细节(mvcc、watch、compaction)+Multi-Raft 的调度(Region 分裂、热点迁移、PD)+跨 Region 事务的挑战——「Raft 的工业化」从「一个算法」变成「一整套分布式数据库的地基」——**「读懂 Raft」是读懂 TiKV/CockroachDB 这些新数据库的门票。

小结

Raft 的三大机制:选主(term+随机超时+多数票——「日志新者优先」的投票约束保证选出最完整的 Leader)、日志复制(AppendEntries+一致性检查——Leader 日志覆盖 Follower 冲突——「Leader 永远是对的」的强主设计)、安全性(日志匹配性质、只提交当前任期、no-op 的细节——Figure 8 的烧脑但必懂)。工程实践:etcd(Kubernetes 的心脏)的调参、Multi-Raft(TiKV 的 Region 共识——Raft 的工业化)、成员变更与快照。给工程师的学习建议:Raft 值得每个后端工程师读一次论文(不是背机制,是理解「为什么这样设计就安全」——每一处细节(随机超时、日志约束、no-op)都有它防御的具体故障——「每一个细节都是一个已知的坑」——这就是算法论文与工程实践之间最好的桥梁。

Paxos 证明了共识是可能的,Raft 证明了共识是可以被理解与实现的——后者才是工程世界真正需要的。

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?