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

  1. Leader 收到这个消息后(不管是 follower 转发过来的还是自己内部产生的)会有两步操作:
    1. 将这个消息添加到自己的 log 里
    2. 向其他 follower 广播这个消息
func stepLeader(r *raft, m pb.Message) error {switch m.Type {case pb.MsgProp://...if !r.appendEntry(m.Entries...) {return ErrProposalDropped}r.bcastAppend()return nil}}
  1. 在 follower 接受完这个 log 后 , 会返回一个MsgAppResp消息 。
  2. 当 leader 确认已经有足够多的 follower 接受了这个 log 后 , 它首先会 commit 这个 log , 然后再广播一次 , 告诉别人它的 commit 状态 。 这里的实现就有点像两阶段提交了 。
func stepLeader(r *raft, m pb.Message) error {switch m.Type {case pb.MsgAppResp://...if r.maybeCommit() {r.bcastAppend()}}}// maybeCommit attempts to advance the commit index. Returns true if// the commit index changed (in which case the caller should call// r.bcastAppend).func (r *raft) maybeCommit() bool {//...mis := r.matchBuf[:len(r.prs)]idx := 0for _, p := range r.prs {mis[idx] = p.Matchidx++}sort.Sort(mis)mci := mis[len(mis)-r.quorum()]return r.raftLog.maybeCommit(mci, r.Term)}ConclusionEtcd 里的 raft 模块只实现了 raft 共识算法 , 而像消息的网络传输 , 数据存储都由上层应用来完成 。 这篇文章先介绍了基本的数据结构 , 然后在这些数据结构的基础上引入了 raft 算法 。 同时 , 这里还以一个投票请求和写请求为例 , 介绍了一个请求从接受到应答的完整处理过程 。
但到目前为止 , 我们还有很多细节没有涉及 , 比如说 Linearizable Read , snapshot 机制 , WAL 的存储与回放 , 所以希望你能以这篇文章为基础 , 顺藤摸瓜 , 继续深入研究下去 。
  1. 到写这篇文章为止 , etcd 的最新版本为v3.3.10[24] , 所以这里的分析都是以 v3.3.10 为基础 。
作者:喵叔
原文链接:
参考资料[1]
1: #fn:1
[2]
动画演示:
[3]
它的论文:
[4]
这个 lecture:
[5]
etcd 中 raft 模块的源码解析:
[6]
raftexample:
[7]
Protocol Buffer:
[8]
EntryNormal: #L47
[9]
EntryConfChange: #L48
[10]
ConfChange: #L283
[11]
2: #fn:2
[12]
Storage: #L46
[13]
MemoryStorage: #L74
[14]
2: #fn:2
[15]
3: #fn:3
[16]
滑动窗口:
[17]
消息: #message
[18]
stepLeader: #L875
[19]
stepFollower: #L1121
[20]
stepCandidate: #L1079
[21]
tickHeartbeat: #L608
[22]
tickElection: #L598
[23]
初始化函数: #L243
[24]
v3.3.10:
面试官:聊聊 etcd 中的 Raft 吧文章插图