面试官:聊聊 etcd 中的 Raft 吧

Raft:(computer_science "Raft") 是近年来比较流行的一个一致性算法 。 它的原理比较容易理解 , 网上也有很多相关的介绍 , 因此这里我就不再啰嗦原理了 , 而是打算以 raft 在 etcd 中的实现1[1]为例 , 从工程的角度来讲讲这个算法的一个具体实现 , 毕竟了解原理只算是“纸上谈兵” , 离真正能把它应用起来还有很长一段距离 。
如果你还不熟悉 raft , 这个经典的动画演示[2]、它的论文[3]以及这个 lecture[4]可能会对你有帮助 。 或者你也可以直接观看下面的视频 , 这是我作的一次技术分享 , 讲的是etcd 中 raft 模块的源码解析[5] 。 说句题外话 , 很多 Conference 和 Meetup 都会把视频录像上传到 YouTube 上 , YouTube 简直就是程序员的衣柜 , 每逛一次都有新收获 。 (方便播放 , 放一个 B 站链接)
Overview【面试官:聊聊 etcd 中的 Raft 吧】Etcd 将 raft 协议实现为一个 library , 然后本身作为一个应用使用它 。 当然 , 可能是为了推广它所实现的这个 library , etcd 还额外提供了一个叫raftexample[6] 的示例程序 , 向用户展示怎样在它所提供的 raft library 的基础上构建出一个分布式的 KV 存储引擎 。
在 etcd 中 , raft 作为底层的共识模块 , 运行在一个goroutine里 , 通过channel接受上层(etcdserver)传来的消息 , 并将处理后的结果通过另一个channel返回给上层应用 , 他们的交互过程大概是这样的:
面试官:聊聊 etcd 中的 Raft 吧文章插图
Raft Stack
这种全异步的交互方式好处就是它提高了性能 , 但坏处就是难以调试 , 代码看起来会很绕 。 拿 etcd 举例 , 很多时候你只看到它把一个消息 push 到一个 slice/channel 里面 , 然后这部分函数调用链就结束了 , 你无法直观的追踪到 , 到底是谁最后处理了这个消息 。
Code Breakdown我们来看一下这个 raft library 里面都有哪些文件:
$ tree --dirsfirst -L 1 -I '*test*' -P '*.go'.├── raftpb├── doc.go├── log.go├── log_unstable.go├── logger.go├── node.go├── progress.go├── raft.go├── rawnode.go├── read_only.go├── status.go├── storage.go└── util.go下面按功能模块依次介绍:
raftpbRaft 中的序列化是借助于Protocol Buffer[7]来实现的 , 这个文件夹就定义了需要序列化的几个数据结构 , 我们先从Entry和Message开始看起:
Entry从整体上来说 , 一个集群中的每个节点都是一个状态机 , 而 raft 管理的就是对这个状态机进行更改的一些操作 , 这些操作在代码中被封装为一个个Entry 。
// #L203type Entry struct {Termuint64Indexuint64TypeEntryTypeData[]byte}