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

MessageRaft 集群中节点之间的通讯都是通过传递不同的Message来完成的 , 这个Message结构就是一个非常 general 的大容器 , 它涵盖了各种消息所需的字段 。
// #L239type Message struct {TypeMessageTypeTouint64Fromuint64Termuint64LogTermuint64Indexuint64Entries[]EntryCommituint64SnapshotSnapshotRejectboolRejectHintuint64Context[]byte}

  • Type:当前传递的消息类型 , 它的取值有很多个 , 但大致可以分成两类:
  • Raft 协议相关的 , 包括心跳 MsgHeartbeat、日志 MsgApp、投票消息 MsgVote 等 。
  • 上层应用触发的(没错 , 上层应用并不是通过 api 与 raft 库交互的 , 而是通过发消息) , 比如应用对数据更改的消息 MsgProp(osal) 。
不同类型的消息会用到下面不同的字段:
  • To, From 分别代表了这个消息的接受者和发送者 。
  • Term:这个消息发出时整个集群所处的任期 。
  • LogTerm:消息发出者所保存的日志中最后一条的任期号 , 一般MsgVote会用到这个字段 。
  • Index:日志索引号 。 如果当前消息是MsgVote的话 , 代表这个 candidate 最后一条日志的索引号 , 它跟上面的LogTerm一起代表这个 candidate 所拥有的最新日志信息 , 这样别人就可以比较自己的日志是不是比 candidata 的日志要新 , 从而决定是否投票 。
  • Entries:需要存储的日志 。
  • Commit:已经提交的日志的索引值 , 用来向别人同步日志的提交信息 。
  • Snapshot:一般跟MsgSnap合用 , 用来放置具体的 Snapshot 值 。
  • Reject , RejectHint:代表对方节点拒绝了当前节点的请求(MsgVote/MsgApp/MsgSnap…)
log_unstable.go顾名思义 , unstable 数据结构用于还没有被用户层持久化的数据 , 它维护了两部分内容snapshot和entries:
// #L23type unstable struct {// the incoming unstable snapshot, if any.snapshot *pb.Snapshot// all entries that have not yet been written to storage.entries []pb.Entryoffsetuint64logger Logger}entries代表的是要进行操作的日志 , 但日志不可能无限增长 , 在特定的情况下 , 某些过期的日志会被清空 。 那这就引入一个新问题了 , 如果此后一个新的follower加入 , 而leader只有一部分操作日志 , 那这个新follower不是没法跟别人同步了吗?所以这个时候snapshot就登场了 - 我无法给你之前的日志 , 但我给你所有之前日志应用后的结果 , 之后的日志你再以这个snapshot为基础进行应用 , 那我们的状态就可以同步了 。 因此它们的结构关系可以用下图表示2[11]:
面试官:聊聊 etcd 中的 Raft 吧文章插图
img
这里的前半部分是快照数据 , 而后半部分是日志条目组成的数组 entries , 另外 unstable.offset 成员保存的是 entries 数组中的第一条数据在 raft 日志中的索引 , 即第 i 条 entries 在 raft 日志中的索引为i + unstable.offset 。
storage.go这个文件定义了一个Storage[12]接口 , 因为 etcd 中的 raft 实现并不负责数据的持久化 , 所以它希望上面的应用层能实现这个接口 , 以便提供给它查询 log 的能力 。
另外 , 这个文件也提供了Storage接口的一个内存版本的实现MemoryStorage[13] , 这个实现同样也维护了snapshot和entries这两部分 , 他们的排列跟unstable中的类似 , 也是snapshot在前 , entries在后 。 从代码中看来etcdserver和raftexample都是直接用的这个实现来提供 log 的查询功能的 。
log.go有了以上的介绍 unstable、Storage 的准备之后 , 下面可以来介绍 raftLog 的实现 , 这个结构体承担了 raft 日志相关的操作 。