runsisi's

technical notes

hashicorp raft

2019-03-18 runsisi#ha

raft 是 paxos 分布式一致性协议的简化版本,常见的 raft C++ 实现有 baidu 的 braft,golang 实现有 hashicorp 独立的 raft 代码库,也有 etcd 中的 raft package,以及最近开源的 dragonboat

hashicorp 的 raft 库在其自家的产品如 consul 中用的比较广泛,当前 rqlite 使用的也是该实现。

hashicorp raft 更新操作(Raft.Apply)的基本流程如下:

  1. 应用调用 Raft.Apply(buf, timeout) 将二进制数据 buf 通过 raft 进行日志分发及应用;
  2. Raft.Apply 返回 ApplyFuture 接口(内部实际返回 logFuture 接口),其提供 Error(), Index(), Response() 三个方法;
  3. Raft.Apply 将二进制数据封装成 raft 日志并通过 chan 发送给内部的 runLeader() / runFollower() / runCandidate() 循环;
  4. runLeader() 调用 LeaderLoop();
  5. LeaderLoop() 调用 dispatchLogs([]*logFuture) 分发日志,当所有日志提交之后,通过 commitCh 通知 LeaderLoop() 进行应用状态更新;
  6. LeaderLoop() 调用 processLogs(uint64, *logFuture) 将提交后的日志通过 fsmMutateCh 发往 Raft.runFSM() 处理;
  7. Raft.runFSM() 调用应用的 Apply(*Log) 方法处理提交后的日志,设置 logFuture.response 并调用 logFuture 的 respond 方法将结果发送至 errCh;
  8. 应用调用 ApplyFuture.Error() 将阻塞当前线程并等待 errCh 返回 raft 层错误码,然后调用 ApplyFuture.Response() 可以获取 logFuture.response 的结果;

raft 内部有几个非常关键的操作,Snapshot、Restore 以及 runFSM。raft 的更新操作首先都需要以日志的形式在各节点提交,然后通过上面提到的 runFSM 在各节点应用以更新应用状态,随着更新操作的累积,日志记录也必然越来越多,因此需要定时删除已应用的日志,Snapshot 所做的事情就是将应用当前的状态固化到本地,然后清理日志。而 Restore 主要用于上电过程中加载快照、Follower 从 Leader 全量同步之后的加载,以及用户触发的备份还原操作。

参考资料

分布式一致性算法—Raft

https://www.cnblogs.com/cchust/p/5634782.html

A brief overview of the Raft algorithm

http://blog.carlosgaldino.com/a-brief-overview-of-the-raft-algorithm.html

etcd-raft snapshot实现分析

https://zhuanlan.zhihu.com/p/29865583