字节困境
首发于字节困境
etcd-raft 源码分析

etcd-raft 源码分析

本篇文章主要分析etcd-raft的代码实现,关于raft协议的细节,参考:

Raft Consensus Algorithmraft.github.io图标

Etcd-raft的实现包含以下功能:

  • Leader election
  • Log replication/Log compaction
  • Membership changes/Leader transfer
  • Linearizable / Lease read

值得注意的是,etcd-raft并没有实现消息的网络发送和接收,而是把消息保存在内存中,需要用户自己定义网络传输层,实现消息的发送和接收。同时,etcd-raft定义了Storage接口,用户使用者自己定义存储层。

1. etcd-server的启动

raftNode启动了一个包含for-select结构的goroutine,三个case分支的作用分别如下:

1.1 时钟驱动

通过r.ticker.CTime类型的Channel)监听时钟事件, 然后调用自身的tick方法,raft的时钟主要用来处理heartbeatelection timeout

1.2 消息监听

监听消息是否到达: Ready是raft的核心结构体,下面我们看一下Ready 各个字段的含义:

  • SoftState包含了两个字段:Lead,RaftState。Lead表示当前Leader的ID,RaftState表示节点当前的状态(Follower,Leader,Candidate ...)
  • HardState包含了:Term,Vote,Commit等字段。代表了消息发送前的状态细节,比如commit索引值等,待消息发送后要做一些相应处理
  • ReadStateslinearizable read有关,后续再做解释
  • Entries是要在log replication前存储到stable storage的日志实例
  • Snapshot是要存储到stable storage的快照例
  • CommittedEntriesreplication给集群中其他成员的日志实例
  • Messages是要发送给其他成员的消息实例
  • MustSync标识HardStateEntries是否必须写入到stable storage

2. node / raft的初始化

在etcd-raft中,有两个重要的概念:node和raft。

node是一个StateMachine,而raft代表StateMachine当前的State 。

node代表了etcd集群中的一个节点,负责时钟驱动,事件触发,IO调用;raft负责维护当前自身节点的状态:任期,Leader,日志Commit等。在 etcd集群中,node 实例和raft实例是一对一的关系。

2.1 node是如何启动的

通过newRaft初始化raft实例,并将自身状态设置为:Follower。通过newNode初始化node,并通过node.run(raft)实现node实例与raft实例的启动。通过下面的run方法可以发现,noderaft之间通过Channel进行交互。

我们看一下node.run的核心代码:

看到这里我们应该明白了node中的Channel是通过tick来驱动运作的,并且上面代码中出现了我们前面提到的 Ready对象的构造:

3、 消息发送

heartbeat message 是由tick触发的,heartbeat的触发频率即为tick的频率:

既然是tick驱动,那我们看一下raft在接收到tick之后的行为。

根据raft 实例当前不同的状态,tick会触发不同的行为:Follower,Candidate,PreCandidate会触发 tickElection,Leader 会触发tickHeartbeat

tickElection会触发MessageHup类型的消息,tickHeartbeat会触发pb.MsgCheckQuorum, pb.MsgBeat 类型的消息。

而上述过程中出现的 Step方法是raft实例消息处理的核心方法:

同样,根据当前raft实例角色(Leader, Follower, Candidate)的不同,处理消息的类型和方式也不同,具体实现上对应不同的stepFunc:

stepLeader为例,Leader处理的消息主要包括:

pb.MsgBeat, pb.MsgCheckQuorum , pb.MsgProp, pb.MsgReadIndex, pb.MsgAppResp, pb.MsgHeartbeatResp,pb.MsgSnapStatus,pb.MsgUnreachable,pb.MsgTransferLeader

4、存储

etcd-raft的存储内容主要包含三部分内容:

4.1、raftLog

raft本身就像一个StateMachine,通过log-replication来实现成员之间状态的一致。

  • Storage是一个接口,即使用者需要自己实现的存储方案,在raft/example中有一个MemoryStorage的实现,感兴趣可以自己看一下。
  • unstable:已经生成或发出,尚未获得集群认可的日志
  • commited:已获得集群至少二分之一成员认可的日志
  • applied:已经应用到 StateMachine的日志

4.2、binLog

  • binlog 和 snapshot主要数据的迁移和恢复;

4.3、 kvStore

  • 用来存储client端提交的数据,在raft/example中用hashmap代替


待续 。。。

参考链接:

etcd中的raft实现 | jasper的blogwww.opscoder.info图标etcd源码解读之raft协议实现_深度技术栈-Lambda在线vlambda.com图标吴镝:etcd raft library设计原理和使用zhuanlan.zhihu.com图标TiKV 源码解析系列 - Raft 的优化pingcap.com图标吴镝:etcd raft如何实现Linearizable Readzhuanlan.zhihu.com图标吴镝:etcd raft如何实现leadership transferzhuanlan.zhihu.com图标https://www.cockroachlabs.com/blog/scaling-raft/www.cockroachlabs.com

编辑于 2018-11-15

文章被以下专栏收录