Raft 协议在 Nacos 中的实现
在 Nacos 中,服务注册表和配置中心的数据必须在多节点集群中保持一致。如果数据不一致,就可能导致某些消费者拿到过期实例,部分服务调用失败,甚至配置错乱。
为了解决这一问题,Nacos 在 2.x 版本中引入了 Raft 协议,用于集群内的数据一致性维护。相比 ZooKeeper 的 ZAB 协议,Raft 更易理解、实现更简单。
一、Raft的优势
在 Nacos 1.x 版本中,数据一致性依赖 MySQL + 数据库锁的方式。但随着服务规模增长,这种方案的问题逐渐暴露:
-
写放大严重:所有节点必须落库,写入性能瓶颈明显
-
脑裂风险高:数据库锁并不能完全避免分区导致的数据不一致
-
可用性不足:数据库成为单点,宕机即导致注册中心瘫痪
。 2.x,Nacos 引入了 Raft 协议 来替代数据库锁。Raft 具有以下特征: -
强一致性:依赖 Leader 节点顺序提交日志
-
高可用性:只要超过半数节点存活,集群即可正常工作
-
实现简单:相比 Paxos,Raft 的状态转换更直观
二、总体架构
在 Nacos 中,Raft 主要用于 Naming Service 和 Config Service 的数据同步。Nacos 集群节点通过 Raft 协议选举出一个 Leader,所有的写操作(注册、下线、配置更新)必须经由 Leader 提交,再同步到 Follower 节点。
- Leader 负责写:所有写请求都路由到 Leader
- Follower 负责同步:从 Leader 接收日志并保持状态一致
- 查询可从任意节点:Nacos 客户端默认支持就近查询
三、Raft 协议的核心流程
Nacos 中 Raft 协议主要包含三个核心过程:Leader 选举、日志复制、故障恢复。
3.1 Leader 选举机制
当 Nacos 集群启动或 Leader 宕机时,集群会自动触发选举。
触发条件
- 集群启动初期,无 Leader 节点
- Leader 心跳超时(默认 5 秒)
- 网络分区导致 Leader 不可达
选举流程
源码解析
在 Nacos 2.x 中,Raft 逻辑位于com.alibaba.nacos.core.distributed.raft
包内。
触发选举的关键逻辑在RaftCore
:
if (leader == null || isLeaderDown()) {currentTerm.incrementAndGet();becomeCandidate();sendVoteRequests();
}
- 采用随机超时时间避免选票冲突
- 至少过半节点同意才能成为 Leader
- 新 Leader 立即向其他节点发送 AppendEntries 心跳,避免二次选举
3.2 日志复制与提交
当客户端发起写请求(例如注册服务或更新配置)时,请求首先路由到 Leader。Leader 会将变更封装为一条 Raft 日志,并通过 AppendEntries RPC 推送到所有 Follower。
- Leader 等待超过半数 Follower 返回 ACK 才能提交日志
- 提交日志后才更新状态机并返回客户端结果
- Follower 会顺序存储日志,保证状态机在所有节点上一致
源码解读
日志追加核心类是com.alibaba.nacos.core.distributed.raft.RaftLogProcessor
:
public void appendLog(RaftLog log) {// 本地写入日志localStorage.append(log);// 异步同步到其他Followerfor (Peer peer : peers) {if (!peer.equals(self)) {sendAppendEntries(peer, log);}}
}
3.3 故障恢复与脑裂处理
Raft 的一个优势是天然避免脑裂。在 Nacos 中,如果 Leader 宕机或网络分区,Raft 会自动选出新 Leader,并保证日志不会回退。
场景一:Leader 宕机
- Follower 检测到心跳超时
- 重新发起选举
- 新 Leader 从已提交日志开始同步,不影响一致性
场景二:网络分区
- 被隔离的旧 Leader 会因为拿不到多数派投票而自动降级为 Follower
- 其他节点选出新 Leader,继续处理写请求
- 网络恢复后,旧 Leader 会通过日志追赶机制同步最新状态
四、Nacos 中 Raft 的实现细节
相比原生 Raft,Nacos 做了很多优化。
4.1 多 Raft Group 设计
在 Nacos 2.x 中,不同的服务命名空间和配置分片对应不同的 Raft Group。这样做有两个好处:
- 写入隔离:不同服务间的注册操作互不干扰
- 扩展性更强:单个 Raft Group 压力不会成为瓶颈
RaftGroupMember group = raftCore.getRaftGroup("naming_service");
group.appendLog(new RaftLog("REGISTER", instanceMeta));
4.2 长连接与增量推送
传统 Raft 协议基于短连接 RPC,而 Nacos 在此基础上引入了 gRPC 长连接:
- Follower 启动时与 Leader 建立长连接
- 日志变更通过流式推送,降低握手成本
- 支持增量日志同步,而非全量拉取
4.3 本地快照与故障快速恢复
当 Raft 日志过多时,Nacos 会定期做本地快照(Snapshot):
- 快照文件存储最新状态机数据
- 新节点加入时,可以直接加载快照,而不必从零回放日志
- Leader 崩溃重启时,可以从快照恢复,大幅缩短恢复时间
snapshotManager.saveSnapshot(currentState);