面试官:聊聊 etcd 中的 Raft 吧( 三 )


raftLog 由以下成员组成:

  • storage Storage:前面提到的存放已经持久化数据的 Storage 接口 。
  • unstable unstable:前面分析过的 unstable 结构体 , 用于保存应用层还没有持久化的数据 。
  • committed uint64:保存当前提交的日志数据索引 。
  • applied uint64:保存当前传入状态机的数据最高索引 。
需要说明的是 , 一条日志数据 , 首先需要被提交(committed)成功 , 然后才能被应用(applied)到状态机中 。 因此 , 以下不等式一直成立:applied <= committed 。
raftLog 结构体中 , 几部分数据的排列如下图所示2[14]:
面试官:聊聊 etcd 中的 Raft 吧文章插图
RaftLog Layout
这个数据排布的情况 , 可以从 raftLog 的初始化函数中看出来:
// #L45// newLog returns log using the given storage. It recovers the log to the state// that it just commits and applies the latest snapshot.func newLog(storage Storage, logger Logger) *raftLog {if storage == nil {log.Panic("storage must not be nil")}log :=--tt-darkmode-color: #EF7060;">Storage管理的已经持久化的数据 , 而在此之后都是unstable管理的还没有持久化的数据 。
以上分析中还有一个疑问 , 为什么并没有初始化 unstable.snapshot 成员 , 也就是 unstable 结构体的快照数据?原因在于 , 上面这个是初始化函数 , 也就是节点刚启动的时候调用来初始化存储状态的函数 , 而 unstable.snapshot 数据 , 是在启动之后同步数据的过程中 , 如果需要同步快照数据时才会去进行赋值修改的数据 , 因此在这里并没有对它进行操作的地方 。
progress.goLeader 通过Progress这个数据结构来追踪一个 follower 的状态 , 并根据Progress里的信息来决定每次同步的日志项 。 这里介绍三个比较重要的属性:
// #L37// Progress represents a follower’s progress in the view of the leader. Leader maintains// progresses of all followers, and sends entries to the follower based on its progress.type Progress struct {Match, Next uint64State ProgressStateTypeins *inflights}
  1. 用来保存当前 follower 节点的日志状态的属性:在正常情况下 , Next = Match + 1 , 也就是下一个要同步的日志应当是对方已有日志的下一条 。
  2. Match:保存目前为止 , 已复制给该 follower 的日志的最高索引值 。 如果 leader 对该 follower 上的日志情况一无所知的话 , 这个值被设为 0 。
  3. Next:保存下一次 leader 发送 append 消息给该 follower 的日志索引 , 即下一次复制日志时 , leader 会从Next开始发送日志 。
  4. State属性用来保存该节点当前的同步状态 , 它会有一下几种取值3[15]:探测状态 , 当 follower 拒绝了最近的 append 消息时 , 那么就会进入探测状态 , 此时 leader 会试图继续往前追溯该 follower 的日志从哪里开始丢失的 。 在 probe 状态时 , leader 每次最多 append 一条日志 , 如果收到的回应中带有RejectHint信息 , 则回退Next索引 , 以便下次重试 。 在初始时 , leader 会把所有 follower 的状态设为 probe , 因为它并不知道各个 follower 的同步状态 , 所以需要慢慢试探 。 当 leader 确认某个 follower 的同步状态后 , 它就会把这个 follower 的 state 切换到这个状态 , 并且用pipeline的方式快速复制日志 。 leader 在发送复制消息之后 , 就修改该节点的Next索引为发送消息的最大索引+1 。 接收快照状态 。 当 leader 向某个 follower 发送 append 消息 , 试图让该 follower 状态跟上 leader 时 , 发现此时 leader 上保存的索引数据已经对不上了 , 比如 leader 在 index 为 10 之前的数据都已经写入快照中了 , 但是该 follower 需要的是 10 之前的数据 , 此时就会切换到该状态下 , 发送快照给该 follower 。 当快照数据同步追上之后 , 并不是直接切换到 Replicate 状态 , 而是首先切换到 Probe 状态 。


    推荐阅读