为什么基础 Raft 不够用
在分布式系统中,Raft 共识算法因其可理解性和工程可行性成为 etcd、Consul、TiKV、CockroachDB 等核心系统的基石。然而当集群规模从 10 个节点扩展到 1000 个节点,当数据分片从 GB 级扩展到 PB 级,当跨地域延迟从毫秒级跃升到百毫秒级时,原生 Raft 的 "一个 Group 管理全部数据" 模型便遭遇了严重的性能瓶颈。
本文聚焦于 Raft 在超大规模场景下的工程优化路径:从 Multi-Raft 分片架构、JRaft 生产级特性、到 PolarDB/Nova 等云数据库的自适应共识演进。
Multi-Raft —— 分而治之的共识架构
核心设计思想
Multi-Raft 的核心突破在于:将数据划分为大量分片(Shard/Region/Range),每个分片独立运行一个 Raft Group,不同 Group 的 Leader 可以分布在不同节点上,从而实现共识层的水平扩展。TiKV 的 Percolator 事务模型正是基于此架构。
具体实现要点包括:
- 静态分片 vs 动态分片:静态分片(如固定 Region 大小)实现简单但负载不均;动态分裂与合并(Merge/Split)则需要在 Raft Group 状态机中嵌入成员变更协议。
- 租约读写(Lease Read)优化:在 Multi-Raft 中,Leader 租约的时间窗口必须与时钟漂移(Clock Drift)解耦。TiKV 使用 ReadIndex 与 LeaseRead 混合策略:当租约有效时直接本地读;否则通过 ReadIndex 确认 Leader 身份。
- 心跳合并(Heartbeat Batching):当一个节点承载数百个 Raft Group 时,独立心跳产生大量小包。Gossip 风格的心跳广播和批处理 AppendEntries 是降低网络开销的关键。
Raft Group 分裂与成员变更
Region Split 不是简单的键范围重新划分,而是一个需要严格满足安全性的状态机转换过程。TiKV 的实现采用两阶段分裂:
- 分裂准备阶段:原 Region 通过 ConfChange 引入新 Region 的元数据,发起 SplitRequest 作为一条普通日志提交。
- 分裂执行阶段:当日志应用到状态机时,原 Region 截断自身范围,新 Region 初始化并从原 Region 复制 snapshot。
- 独立运行阶段:新 Region 作为独立 Raft Group 开始工作,Range 元数据(PD/Meta Region)同步更新。
JRaft —— 蚂蚁金服的工程级 Raft 实现
JRaft 是 SOFAStack 中基于 Java 的 Raft 实现,在蚂蚁的生产环境中承载了分布式协调、元数据管理和选主等关键能力。相比 etcd/raft(Go)和 braft(C++),JRaft 在 JVM 生态下做了大量工程优化。
Disruptor 环形缓冲区
JRaft 使用 LMAX Disruptor 实现批处理(Batching)和流水线(Pipeline),将日志复制吞吐量提升至百万级 QPS。Disruptor 的核心优势在于:
- 无锁的 Ring Buffer 设计,消除 CAS 争用
- 缓存行填充(Cache Line Padding)避免伪共享
- 批量事件消费减少系统调用
自适应流控与背压
在 Follower 处理能力不足时,JRaft 通过 Flow Control 机制实现背压,防止 Follower OOM。关键参数包括:
- max_byte_count_per_rpc:单次 RPC 最大字节数
- max_entries_size:单次 AppendEntries 最大条数
- max_body_size:Snapshot 传输时的单块最大尺寸
Snapshot 增量传输
JRaft 实现了类似 rsync 的增量快照:Leader 维护 Follower 的最新 Snapshot 元数据,当 Follower 落后过多时,仅传输差异部分(Differential Snapshot),大幅降低跨机房部署时的带宽消耗。
Raft 变体的工程权衡
PreVote + CheckQuorum
基础 Raft 在网络分区时可能产生 Term 号爆炸:被分区的节点不断发起选举、Term 递增。PreVote 协议在正式递增 Term 前先进行一次 "预投票",只有获得多数派预同意才进入正式选举。CheckQuorum 则让 Leader 定期验证与多数派的连通性,超时后主动下台,避免脑裂导致的客户端写入阻塞。
Learner / Witness 节点
TiKV 引入 Learner 节点:不参与投票、仅同步日志的只读副本。典型应用场景包括:
- 跨地域只读副本:将 Learner 部署在异地机房,提供本地读能力,不增加选举法定票数(Quorum)的延迟。
- 实时数据分析:将变更流推送给 Learner,由其驱动 OLAP 系统(如 TiFlash 列存引擎),实现 HTAP。
Witness 节点更进一步:参与投票但不存储日志,在保持 Quorum 大小的条件下降低存储成本,适用于元数据管理场景。
Flexible Raft(灵活的 Quorum 配置)
FlexRaft 允许自定义 Quorum 组成,而非简单多数派(N/2+1)。例如:在一个 5 节点集群中,可以配置写入 Quorum=3、日志提交 Quorum=3、快照恢复 Quorum=2,灵活平衡一致性级别与可用性。PolarDB-X 的 X-Paxos 协议即吸收了 FlexRaft 思想,支持多副本共识下的灵活降级。
云原生数据库中的自适应共识
PolarDB 的 Parallel Raft
阿里云 PolarDB 在共享存储架构下实现了 Parallel Raft:日志提交与状态机 Apply 解耦。当 Leader 将日志写入共享存储(PolarStore)后,允许乱序 Apply,提升状态机处理吞吐量。关键技术包括:
- Log 并行分发:Leader 通过 RDMA 将日志写入远端 Follower 内存,绕过传统 TCP RPC。
- 乱序提交(Out-of-Order Commit):只要日志完整,状态机可并行 Apply 不同 Slot。
- Follower Replay:Follower 可直接回放 Leader 的日志写入,减少 CPU 开销。
TiDB 的 Stale Read & ReadIndex
TiDB 基于 Raft 提供一致性级别可选的读取:
- Linearizable Read(强一致读):走完整 Raft ReadIndex 流程,延迟最高。
- Lease Read(租约读):在租约期内 Leader 本地读,延迟最低。
- Stale Read(历史读):指定时间戳从任意副本读取,牺牲一致性换取低延迟。
- Follower Read:将读请求路由到 Follower,通过 ReadIndex 确认Lease身份。
生产部署的硬核工程实践
磁盘 IO 隔离
Raft 的持久化日志写入与共享磁盘 IO 是生产环境中最常见的性能瓶颈方案。建议:
- 日志盘使用独立 NVMe SSD,不与数据共享带宽
- 使用 fsync 批次化(Group Commit):将多个日志条目的 fsync 合并为一次
- 启用 Write-Ahead Log(WAL)并使用 Direct IO + 512B/4KB 扇区对齐
网络抖动下的 Leader 切换
跨机房部署中,短暂网络抖动不应触发 Leader 切换。实践中使用以下策略:
- Election Timeout 自适应:基于最近 N 次 RTT 的 P99 计算选举超时(参考 braft 的 ElectionTimer 实现)。
- PreVote + Lease Read 结合:新 Leader 必须等到旧 Leader 租约过期后才能提交,避免 Split-Brain。
- Joint Consensus:成员变更时使用两阶段协议,避免新旧配置的多数派交集问题。
大规模集群的优雅关闭
在节点滚动升级时,需避免因 Leader 退出导致的服务中断:
- Leader Transfer:将领导权主动转移给指定 Follower
- Pre-Shutdown Leader Migration:在关闭前触发 Leader StepDown
- Connection Draining:排空当前请求后再退出 Raft Group
未来方向
共识算法的研究仍在快速演进:EPaxos 的无 Leader 架构降低冲突事务的提交延迟;NOPaxos 通过网络排序消除协调者开销;Barcelona(D原发性共识)则探索 FPGA 卸载加速共识层。在云原生时代,共识协议正从 "分布式协调利器" 演变为基础设施工厂组件,为 NewSQL 数据库、事件网格、数字身份等场景提供原生一致性保障。

发表评论 取消回复