Hyperledger fabric 自1.4.1版本开始支持了 Raft 共识,比原先的生产环境推荐共识”kafka”,搭建的复杂度和可维护性上要好不少,并且 1.4.0 是 Hyperledger的第一个长期维护版本(long term support),推荐在生产环境中使用,完全可以升级并使用Raft共识。

本文介绍总结Raft 算法,主要是根据著名的 Raft讲解动画的理解,并进行文字化的总结。强烈推荐这个讲解动画,可配合动画阅读下文,更容易理解。

概念

Raft 算法中的三种角色:

  • follower 追随者,普通节点默认都是follower

  • candidate 候选人,当准备发起新一轮选举的时候,由follower升级而成,选举完成会变回follower或成为leader

  • leader 领导者,即候选人被选中上,成为主节点

Raft 算法中的两种超时

election timeout - 选举超时

选举超时,即 follower 等待成为 candidate 的超时时间,一般在 130ms~300ms 之间。

在follower 开始倒计时 election tomeout 时间后,开始成为candidate,并且发起一次选举过程。

heartbeat timeout - 心跳超时

leader 会在heartbeat timeout 后发送心跳包,这也就是leader发送心跳包的时间间隔。

流程

同步数据的过程

图示 这个过程叫做日志复制(Log replication)

  1. client 向系统发送(会导向leader节点)更改消息
  2. leader 预修改数据,但不commit(未确认修改)
  3. leader 向follower,发送修改消息
  4. follower 回复 ack,并且预修改这个消息,但不commit
  5. leader收到这个ack之后,首先进行commit(确认修改),并且发给client
  6. leader 向 follower发送心跳包,携带leader已commit这个消息
  7. follower收到leader的已commit消息,知道leader已经commit了,那么自己也进行commit

开始选举的过程

election

  1. candidate首先给自己投一票,然后发送消息给其他节点,请求其他节点的选票

    ​如果这时候,这些 follower 节点还没在这轮选举中投过票,那么会投这个 candidate,然后重置自己的 election timeout。上文提到follower 在倒计时election timeout后发起新一轮选举,那么这时因为重置了 election timeout,所以是不会发起选举的,维持了节点的follower特性。

  2. candidate 收到足够的选票,成为 leader

  3. leader 发送心跳包

leader 会在每间隔 header timeout 给每个folloer发送一个心跳包,称为Append Entries

只要follower能收到心跳包,则维持这次的选举结果(重置election timeout?),继续当一个follower

异常情况的处理

同时成为candidate,导致分裂选举的情况

因为 election timeout 是各自在 150~300 随机的,出现同时达到的情况概率较低,但仍有可能。

  1. 两个 follower 同时达到 election timeout,同时成为 candidate
  2. 两个 candidate 各自给自己投一票,并且同时发送选举请求给其他的follower
  3. 因为其他 follower 只对这轮选举投一个,所以,如果有两个follower(或者说偶数),则可能出现,每个candidate收到一个follower(或者说收到的选票相等),那么选举无法完成。
  4. 选举无法完成,两个 candidate 再间隔一段时间后(election timeout重新随机了一次,再次同时的概率更低了,这也是为什么election timeout是个范围值而不是固定值),继续发起新一轮的选举。
  5. 如果这次没有再出现两个人收到的选票一样的情况,那么,选票多的candidate当选leader
  6. 完成选举后,会对同步leader的数据给follower

如图所示:doubleElection

出现网络分区时怎么做?

异常情况出现了,假设存在五个节点,node A~E,B是leader,这时候因为网络分区,node A和B可互通信,但其他节点已经联系不上AB节点,那么就会出现两个分区,一边三个节点,一边两个节点。如图所示:

这时 CDE 节点没有leader,会发起新的选举,直到从中选中新的leader。他们的选举轮数肯定是递增的(比AB的大)。

  • 如果客户端访问到的是 AB的分区,那么按如下流程:
  1. leader(B) 把数据发给 folloer (A)
  2. A 接受到数据,数据处于未提交状态,并发个ack给leader(node B)
  3. B 只收到了一个回复,远远小于选举时的节点数(4个follower),通知A不要commit

AB的数据一直属于未提交状态(uncommited)。

  • 如果客户端访问到的是CDE的分区,那么按如下流程:

1.leader 把 数据发给 follower们

2.follower 收到并回复 ack

3.leader 收到大多数follower的ack,决定更新数据,并告知follower们也更新数据

这时候,如果网络故障修复,消除了网络分区,那么因为 CDE节点所在的分区,选举轮数(term)比较大,所以AB所在分区会回滚掉中断之后的修改,而是以CDE分区的数据为准。并且以 CDE 的leader 为统一的leader

如何保证leader的数据是最新的呢?

因为 candidate 当选leader后,会让底下的follower同步他的数据,那么就必须保证他的数据是最新的。Raft 通过如下机制保证 candidate 的数据是最新的:

candidate 在发送 RequestVote 请求follower 给自己投票的时候,把自己当前最新的log 的index 和 term(选举的轮数)发给 follower,follower收到后如果发现自己本地的数据比candidate还新,那么拒绝投票。

比较新旧的依据是, term 大的比term小的数据更新,如果 term一样,则 log index大的数据更新。