Raft

673 阅读7分钟

1.Raft

Raft是一个允许网络分区的一致性协议,它保证了在一个由N个节点构成的系统中有(N+1)/2(向上取整)个节点正常工作的情况下的系统的一致性,比如在一个5个节点的系统中允许2个节点出现非拜占庭错误,如节点宕机、网络分区、消息延时。

为什么raft最大容错节点数量是(n-1)/2 ?

raft 算法只支持容错故障节点,假设集群总节点数为n,故障节点为 f ,根据小数服从多数的原则,集群里正常节点只需要比 f 个节点再多一个节点,即 f+1 个节点,正确节点的数量就会比故障节点数量多,那么集群就能达成共识。因此 raft 算法支持的最大容错节点数量是(n-1)/2。

节点类型

Raft每个共识节点只能如下三种身份之一:LeaderFollower以及Candidate,节点由isLeaderisSelected两个bool型字段来区分共识节点的类型

leader:由Follower节点选举而来,在每一次共识过程中有且仅有一个Leader节点,由Leader全权负责从交易池中取出交易、打包交易组成区块并将区块上链;

follower:以Leader节点为准进行同步,并在Leader节点失效时举行选举以选出新的Leader节点

candidate:Follower节点在竞选Leader时拥有的临时身份。

任期

Raft算法将时间划分为不定长度的任期Terms,Terms为连续的数字。每个Term以选举开始,如果选举成功,则由当前leader负责出块,如果选举失败,并没有选举出新的单一Leader,则会开启新的Term,重新开始选举。

{UP1WLKI0{PAUBGO_KUYMAX.png

节点间状态转换

节点类型之间转换关系如下图所示: image.png

leader-->follower:(1)任期结束 (2)转移领导权给一个很活跃的节点

follower-->candidate: 没有接收到领导者“心跳”,投票给自己成为candidate,开始选举

candidate-->follower:在等待选举期间,Candidate收到了其他节点的“心跳”

candidate-->leader:超过一半验证人投票,赢得选举

candidate-->candidate: 选举时间结束后,没有Leader被选出。

心跳机制

Raft 的选主基于一种心跳机制,leader 会周期性的向所有节点发送心跳Heartbeat来维持自己的权威,follower若一段时间内收不到“心跳”就会触发选举(初次启动都默认自己为Follower)。

对于leader,tick被设置为HeartbeatTick,HeartbeatTick会递增心跳过期时间计数,只要1次tick时间过去,基本上会发送心跳消息。

雄安链的RAFT实现

雄安链的raft有两条通道,DataChannel和VoteChannel。

DataChannel用来同步节点间交易(TxsMessage)与区块(BlockMessage)

VoteChannel用来发起投票请求(VoteMessage)和投票响应(VoteResurtMessage)

核心流程

1.选举

Raft共识模块中使用心跳机制来触发Leader选举。当节点启动时,节点自动成为Follower且将Term置0。只要Follower从Leader或者Candidate收到有效的Heartbeat或投票请求消息,其就会保持在Follower状态,如果Follower在一段时间内( Election Timeout)没收到上述消息,则它会假设系统当前的Leader已经失活,然后增加自己的Term并转换为Candidiate,开启新一轮的Leader选举流程,流程如下:

  1. Follower增加当前的Term,转换为Candidate;
  2. Candidate将票投给自己,并广播投票请求到其他节点请求投票;
  3. Candidate节点保持在Candidate状态,直到下面情况中的一种发生:

(1)该节点赢得选举;

(2) 在等待选举期间,Candidate收到了其他节点的Heartbeat;

如果选举失败,节点间平票等原因导致没有选出leader时:

xchain为了避免分裂选举的问题,当5轮选举失败时,就会触发随机时间的休眠。休眠一段时间后再进行选举。

2.投票

1)如果msg类型为VoteMessage,发送给所有Validator(验证节点):

该节点在这些情况下会拒绝:

  • leader还存在并且超时计数不大于选举超时时间
  • 消息的Term小于当前状态机维护的Term
  • 竞选者的日志没有本地新

除了上述情况,节点都会赞成

2)统计VoteResurtMessage消息,如果赞成超过半数则竞争者变为leader状态,否则变成follower

3.日志复制

什么是日志?

无论是领导者还是跟随者,都各自保存一个日志副本。首先,每条记录都包括供状态机执行的一条命令,命令的格式可以是客户端与状态所达成一致的某种格式。其次,每条记录都包括一个任期号,这个任期号是该条记录创建时,领导者所处的任期,随着日志记录的增多,这个任期号也会单调上升。

流程:

  1. 集群某个节点收到client的命令,节点会发送给leader。
  2. leader收到消息以后,会处理消息中的日志条目,并将其添加到自己的日志中,广播此日志
  3. 各节点以并行的方式执行,返回相应给leader
  4. 一旦leader收到足够多的响应,可以它认为该条命令已经在多数节点上处于已提交状态时,那么该条命令就可以被执行。
  5. leader这时会将命令发送给状态机,当执行结束后,它会将结果返回给客户端。

一旦服务器知道某个记录已经处于提交状态,它就会告知其他的服务器。所以最终,每个跟随者都会知道该记录已提交,并且将该命令发送至自己本地的状态机执行。如果跟随者崩溃了或处于慢响应状态,领导者会反复重试这个调用,直到跟随者恢复后,领导者就能重试成功。但是领导者并不需要等待每个跟随者的响应,它只需要等到足够数量的响应,保证记录已被大多数服务器存储即可。

4.交易出块流程

Raft协议强依赖Leader节点的可用性来确保集群数据的一致性,因为数据只能从Leader节点向Follower节点转移。

1.当leader选出后,follower快速向leader同步块高度差的数据。

2.当MemPool.TxsAvailable()触发后,触发出块。

3.leader取出交易池中的交易打包成块

4.进行Precommit投票,当超过二分之一的验证节点赞成时,区块数据状态进入committed状态

5.follower节点根据blockMessage保存块

5. PreVote

PreVote是解决因为某个因为网络分区而失效的节点重新加入集群以后,会导致集群重新选举的问题。

问题出现的过程是这样的,假设当前集群的Term是1,其中一个节点,比如A,它因为网络分区,接收不到
leader的心跳,当超过选举超时时间以后,它会将自己变成Candidate,这时候它会把它的Term值变成2,然后
开始竞选。

当然这时候是不可能竞选成功的。可是当网络修复以后,无论是它的竞选消息,还是其他的回复消息,都会带
上它的Term,也就是2。而这时候整个集群里其他机器的Term还是1,这时候的leader发现已经有比自己Term高
的节点存在,它就自己乖乖降级为follower,这样就会导致一次重新选举。

这种现象本身不常见,而且出现了也只是出现一次重选举,对整个集群的影响并不大。但是如果希望避免这种情况发生,就需要PreVote。

PreVote的做法是:

当集群中的某个follower发现自己已经在选举超时时间内没收到leader的心跳,不是直接变成candidate,也就不会将Term自增1。而是引入一个新的环境叫PreVote。

1.它会先广播发送一个PreVote消息

2.其他节点如果正常运行,就回复一个反对预选举的消息
其他节点如果也失去了leader,才会有回复赞成的消息。

3.节点只有收到超过半数的预选举选票,才会将自己变成candidate,发起选举

这样,如果是这个单个节点的网络有问题,它不会贸然自增Term,因此当它重新加入集群时。也不会对现任leader地位有任何冲击。保证了系统更稳定的方式运行。