Task
完成 Raft 的快照功能(涉及到较多的与 Service 层的交互。
- 为什么要有 snapshot?
- snapshot 可以将 log 压缩,比如将 10 个 log 压缩成 9 个(当两个 log 修改的 key 值一样时)。
- snapshot 可以减少 Raft 层 log 的长度,帮助进度较慢的 Raft 节点快速恢复状态机(减短 raft 中 log 的长度)。snapshot 区别于持久化 log,后者主要是不让宕机的 raft 丢失太多日志。
- 在 2D 中,snapshot 会涉及到 raft 层与 service 层的多次交互,看这个 diagram of Raft interactions 或许可以帮助理解 Raft 协议不同层次的功能与特性。
- snapshot 作用于每一个 Raft 节点,我们需要记录 snapshot 最后一个 index 和 term,用于一致性检查。
Step
- Snapshot() 被 service 层调用,约莫 10 次 commit 调用一次,用于保存快照。需要注意的是快照是 service 传给 raft 层的,而不是我们在 raft 层写入日志创建的,我们不需要创建快照,仅需要处理 Service 层传进来的 snapshot,进行日志截断和快照持久化。
|
|
2. InstallSnapshot:
-
Leader 方面,当$nextIndex[Follower] \leq Leader.lastIncludeIndex$的时候,由于 Leader 已经没有这部分的日志,Leader 会发送 InstallSnapshot RPC 给 Follower,请求 Follower 安装快照(快速追赶),并且在成功收到返回值后,Leader 会更新 nextIndex 和 matchIndex
-
Follower 方面,如果 Follower 收到 Leader 的任期没有过期的话,只能执行。与论文不同的是:不需要考虑分块发送 snapshot,所以
offset
和done
都不需要考虑,相应的,在实现的时候也不需要考虑第 2,3,4 点 implementation。最后另起一个 go routine 传 snapshot 以及其他必要参数给 applyCh 让 service 更新状态机(发送给 service 后 service 不会立刻拿 snapshot 更新状态机,会先调用 CondInstallsnapshot 来询问 Follower 在这期间有没有 commit,若没有,才会用 snapshot 更新状态机。
- CondInstallSnapshot:若 $lastIncludedIndex\leq commitIndex$ ,则返回
false
,此时 service 不会应用 snapshot;否则剪短自己的 log,并更新 snapshot、lastIncludedTerm 和 lastIncludedIndex,并进行持久化。
tips:
-
在持久化的时候考虑 lastIncludedIndex 和 lastIncludedTerm,并应用到 commitIndex 和 lastApplied。
-
因为我们要删掉已经被 snapshot 的 log,所以需要改变 log 的索引方式。
-
对 condInstallsnapshot 的解释:Follower 收到 InstallSnapshot,向服务器请求应用 snapshot 的时候,如果服务器发送 condInstallSnapshot 的那一刻 lastCommit 还没被修改的话,就保证了原子操作。为什么需要保证原子操作?因为节点通过 applychan 向 service 通信的情况有两种,一种是 commit 到状态机,另一种是 snapshot 到状态机,两者是并行的,互不干扰。只要 raft 节点在发送 snapshot channel 到 service 接收到这个 channel 之间有 commit channel,也就是当 $lastIncludedIndex\leq commitIndex$ 的时候,snapshot 发送的 log 就很有可能被应用到状态机了。