分布式一致性协议Raft-案例剖析

前言

这是Raft系列文章的第二篇。在上一篇《认识Raft》 中我们成功论证了Raft保证集群数据一致性的问题。然而现实世界是非常复杂的,任何情况都可能发生。那么对于各种异常场景Raft集群究竟如何应对的呢?

本篇会列举几个经典的Case来解释Raft的应对措施。如果没看过上一篇《认识Raft》 文章的同学可以先看一下,这篇相当于上篇的加强补充篇。

案例剖析

场景一-选票瓜分

上一篇我们知道,Leader为了巩固自己的领导地位,会定期向Follower发送心跳。假设集群中有4个节点,当Leader挂掉的时候,如果Follower有多个都出现心跳超时,这时集群会出现多个Candidate进行选举。分布式一致性协议Raft-案例剖析_第1张图片
上图展示节点B和节点C同时成为Candidate,并且分别向其他三个节点发送投票请求(绿色小球表示投票请求)。因为B和C首先都会投给自己一票,要想成为Leader,还需要至少两个节点投票给自己形成多数派。Raft规定:每个节点对同一个Term最多投一票。 此时B和C都处于Term 4,而A和D处于Term 3,因此B和C都有机会获得来自A和D的投票,关键就看投票请求到达的先后。假如C的投票请求先到达A和D,获得了A和D的投票,那么C就提升为Leader。同理B也一样。但是还有一种情况,D投票给了C,A投票给了B,这样B和C各得到两票,均未形成多数派,B和C都不能成为Leader。那这个时候怎么办呢?Raft规定:Candidate必须有一个选举超时时间,当超时时间到来的时候,如果还无法提升为Leader,就自增Term,然后开始新一轮的投票选举。所以B和C这个时候都会自增Term,开始新一轮选举。如果之后B和C又各得两票,又要等待超时重新选举。。。这样周而复始,集群就一直无法选出Leader。为了解决这个问题,Raft提出了一个简单有效的办法,就是指定选举超时时间(Election Timeout)在一个小范围内随机,比如:100ms-150ms,这样就减低了节点同时超时的概率。更近一步,如果能避免多个Follower同时转换成Candidate,这样选票瓜分的可能性更小了。做法就是让心跳超时时间(Heartbeat Timeout)也在一个小范围内随机选择。通过这两个超时时间的随机选择,可以让Raft更快的选举出Leader,提高集群可用性。

场景二-网络分区

在一个集群中,如果发生了网络分区,会不会出现两个Leader导致数据不一致呢?
分布式一致性协议Raft-案例剖析_第2张图片
上图中展示了由5个蓝色节点组成的Raft集群,绿色代表Client,用黑色圈起来的B是当前的Leader。这个时候发生了网络分区(黑色虚线把集群分成了上下两部分)。上方C、D、E三个Follower处于一个分区,由于当前没有Leader,会出现心跳超时,部分节点成为Candidate,而三个节点是多数派,最终能选举出一个Leader。而下方的分区只有两个节点,B作为Old leader虽然仍能接受Client写入请求,但是无法在多数节点写入(多数至少需要3个节点),故无法提交。

如下图详细分析一下,E被推选为上方分区Leader后,这时客户端发送“SET 8”的请求,能够得到C、D的成功响应,加上E自身共三个,形成多数,所以请求被提交,服务正常运行。下方分区Old leader B接受一个客户端“SET 3”的写入请求,由于无法形成多数,请求会一直无法提交,客户端得不到成功的响应,最终超时。
分布式一致性协议Raft-案例剖析_第3张图片

当网络分区结束后,因为Leader E的Term是2,大于Old leader B的Term 1。Raft规定:当集群Leader接受到大于自己的Term后,会立即更新自己Term到较大值,并转为Follower(可以从上一篇的图2-角色转换状态机看到)。所以B收到E的正常Append写入请求后,会立即转为Follower,这样整个集群恢复到正常状态。如下图所示。
分布式一致性协议Raft-案例剖析_第4张图片

场景三-提交日志被覆盖

集群在运行的过程中,有可能出现一种比较复杂的情况,如下图。
集群有S1-S5五个节点,方块中数字表示任期号Term,用粗线框起来代表当前任期的Leader。最上方一行数字表示Log index。

图(a)中S1为Term 2的Leader,并且在index=2的地方写入黄色标记的Entry,但只有S2接受成功。然后S1突然挂掉,集群推选了S5为Leader,此时它的Term为3,并在index=2的位置写入了另外一条蓝色标记的Entry,如图(b)。但在S5准备发送Append请求给其他Follower时,它挂掉了。此时集群又推选S1为Leader,它的Term为4,并在index=3位置写入一条红色标记的Entry,同时它尝试提交原来index=2上黄色标记的Entry,因为该日志被S1、S2、S3所接受,所以提交成功,如图[c]。这个时候,不幸再次发生,S1挂了,集群刚好又把S5提升为Leader,那么S5会尝试去提交之前在index=2上写入的蓝色标记的Entry,最终它成功了,结果就覆盖掉了index=2上之前已提交的黄色Entry,如图(d)。这里就违背了Raft的规定:已提交的日志不能被修改或者覆盖。那怎么解决这个问题呢?其实问题就出在图[c]的时候,S1作为Leader尝试去提交了旧的Entry,并且成功了。如果S1不去提交旧的Entry,而是只去提交新来的Entry,如图(e)中只尝试提交新写入到index=3的红色标记的Entry,一旦这个红色Entry提交成功,就算S1挂了,S5也永远不可能被选为Leader,因为集群S1、S2、S3的最新index和Term号都大于S5,这样就不会出现刚才的问题。
总结一下就是: Leader永远只主动提交最新接受到的Log entry。一旦最新的Entry被提交成功,之前的Entry就自动默认都已提交。
分布式一致性协议Raft-案例剖析_第5张图片

后续优化Raft正在加紧制作,敬请期待

参考引用
http://thesecretlivesofdata.com/raft/
Diego Ongaro,John Ousterhout.《In Search of an Understandable Consensus Algorithm(Extended Version)》

你可能感兴趣的:(Raft协议,共识算法,分布式一致性,共识算法,分布式,算法)