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

  • ProgressStateSnapshot
  • ProgressStateReplicate
  • ProgressStateProbe
  • ins属性用来做流量控制 , 因为如果同步请求非常多 , 再碰上网络分区时 , leader 可能会累积很多待发送消息 , 一旦网络恢复 , 可能会有非常大流量发送给 follower , 所以这里要做 flow control 。 它的实现有点类似 TCP 的滑动窗口[16] , 这里不再赘述 。
  • 综上 , Progress其实也是个状态机 , 下面是它的状态转移图:
    面试官:聊聊 etcd 中的 Raft 吧文章插图
    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里的多态 。
    面试官:聊聊 etcd 中的 Raft 吧文章插图
    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}}propc和recvc中拿到的是从上层应用传进来的消息 , 这个消息会被交给 raft 层的Step函数处理 , 具体处理逻辑我上面有过介绍 。
    下面来解释下readyc的作用 。 在 etcd 的这个实现中 , node并不负责数据的持久化、网络消息的通信、以及将已经提交的 log 应用到状态机中 , 所以node使用readyc这个channel对外通知有数据要处理了 , 并将这些需要外部处理的数据打包到一个Ready结构体中:
    // #L52// Ready encapsulates the entries and messages that are ready to read,// be saved to stable storage, committed or sent to other peers.// All fields in Ready are read-only.type Ready struct {// The current volatile state of a Node.// SoftState will be nil if there is no update.// It is not required to consume or store SoftState.*SoftState// The current state of a Node to be saved to stable storage BEFORE// Messages are sent.// HardState will be equal to empty state if there is no update.pb.HardState// ReadStates can be used for node to serve linearizable read requests locally// when its applied index is greater than the index in ReadState.// Note that the readState will be returned when raft receives msgReadIndex.// The returned is only valid for the request that requested to read.ReadStates []ReadState// Entries specifies entries to be saved to stable storage BEFORE// Messages are sent.Entries []pb.Entry// Snapshot specifies the snapshot to be saved to stable storage.Snapshot pb.Snapshot// CommittedEntries specifies entries to be committed to a// store/state-machine. These have previously been committed to stable// store.CommittedEntries []pb.Entry// Messages specifies outbound messages to be sent AFTER Entries are// committed to stable storage.// If it contains a MsgSnap message, the application MUST report back to raft// when the snapshot has been received or has failed by calling ReportSnapshot.Messages []pb.Message// MustSync indicates whether the HardState and Entries must be synchronously// written to disk or if an asynchronous write is permissible.MustSync bool}