面试官:聊聊 etcd 中的 Raft 吧( 三 )
// #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}
- 用来保存当前 follower 节点的日志状态的属性:在正常情况下 , Next = Match + 1 , 也就是下一个要同步的日志应当是对方已有日志的下一条 。
- Match:保存目前为止 , 已复制给该 follower 的日志的最高索引值 。 如果 leader 对该 follower 上的日志情况一无所知的话 , 这个值被设为 0 。
- Next:保存下一次 leader 发送 append 消息给该 follower 的日志索引 , 即下一次复制日志时 , leader 会从Next开始发送日志 。
- 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 状态 。
- ProgressStateSnapshot
- ProgressStateReplicate
- ProgressStateProbe
- ins属性用来做流量控制 , 因为如果同步请求非常多 , 再碰上网络分区时 , leader 可能会累积很多待发送消息 , 一旦网络恢复 , 可能会有非常大流量发送给 follower , 所以这里要做 flow control 。 它的实现有点类似 TCP 的滑动窗口[16] , 这里不再赘述 。
文章插图
Progress State Machine
raft.go前面铺设了一大堆概念 , 现在终于轮到实现逻辑了 。 从名字也可以看出 , raft 协议的具体实现就在这个文件里 。 这其中 , 大部分的逻辑是由Step函数驱动的 。
// #L752func (r *raft) Step(m pb.Message) error {//...switch m.Type {case pb.MsgHup://...case pb.MsgVote, pb.MsgPreVote://...default:r.step(r, m)}}
Step的主要作用是处理不同的消息[17] , 所以以后当我们想知道 raft 对某种消息的处理逻辑时 , 到这里找就对了 。 在函数的最后 , 有个default语句 , 即所有上面不能处理的消息都落入这里 , 由一个小写的step函数处理 , 这个设计的原因是什么呢?其实是因为这里的 raft 也被实现为一个状态机 , 它的step属性是一个函数指针 , 根据当前节点的不同角色 , 指向不同的消息处理函数:stepLeader[18]/stepFollower[19]/stepCandidate[20] 。 与它类似的还有一个tick函数指针 , 根据角色的不同 , 也会在tickHeartbeat[21]和tickElection[22]之间来回切换 , 分别用来触发定时心跳和选举检测 。 这里的函数指针感觉像实现了OOP里的多态 。
文章插图
Raft State Machine
node.gonode的主要作用是应用层(etcdserver)和共识模块(raft)的衔接 。 将应用层的消息传递给底层共识模块 , 并将底层共识模块共识后的结果反馈给应用层 。 所以它的初始化函数[23]创建了很多用来通信的channel , 然后就在另一个goroutine里面开始了事件循环 , 不停的在各种channel中倒腾数据(貌似这种由for-select-channel组成的事件循环在 Go 里面很受欢迎) 。
// #L286for {select {case m := <-propc:r.Step(m)case m := <-n.recvc:r.Step(m)case cc := <-n.confc:// Add/remove/update node according to cc.Typecase <-n.tickc:r.tick()case readyc <- rd:// Cleaning after result is consumed by applicationcase <-advancec:// Stablize logscase c := <-n.status:// Update statuscase <-n.stop:close(n.done)return}}
- 蛋壳公寓|官媒发声:绝不能让“割韭菜者”一跑了之!
- 表达|重磅!2021世界安防博览会官方宣贯会正式召开,百余家企业表达参展意愿
- 公司|LVMH首席数字官跳槽至加密数字钱包公司Ledger
- 成为佛山移动服务体验官 表白留言赢取百元话费
- Store|在BlueMail的App Store反垄断案中 法官作出有利于苹果公司的判决
- 正式|首批体验官正式招募!仰望高端定制,OriginOS玩法大搜罗
- 全新|首批支持5款机型,vivo开启OriginOS首批体验官招募
- 内容|浅谈内容行业的一些规律和壁垒,聊聊电商平台孵化小红书难点(外部原因)
- 全新|OriginOS招募首批体验官,网友:先冲为敬
- 无人|无人维护?官方打脸:Element UI for Vue 3.0 来了!