6.8 选举定时器(Election Timer)

(选举定时器在上一篇有过一些介绍)

任何一条AppendEntries消息都会重置所有Raft节点的选举定时器。这样,只要Leader还在线,并且它还在以合理的速率(不能太慢)发出心跳或者其他的AppendEntries消息,Followers收到了AppendEntries消息,会重置自己的选举定时器,这样Leader就可以阻止任何其他节点成为一个候选人。所以只要所有环节都在正常工作,不断重复的心跳会阻止任何新的选举发生。当然,如果网络故障或者发生了丢包,不可避免的还是会有新的选举。但是如果一切都正常,我们不太可能会有一次新的选举。

如果一次选举选出了0个Leader,这次选举就失败了。有一些显而易见的场景会导致选举失败,例如太多的服务器关机或者不可用了,或者网络连接出现故障。这些场景会导致你不能凑齐过半的服务器,进而也不能赢得选举,这时什么事也不会发生。

一个导致选举失败的更有趣的场景是,所有环节都在正常工作,没有故障,没有丢包,但是候选人们几乎是同时参加竞选,它们分割了选票(Split Vote)。假设我们有一个3节点的多副本系统,3个节点的选举定时器几乎同超时,进而期触发选举。首先,每个节点都会为自己投票。之后,每个节点都会收到其他节点的RequestVote消息,因为该节点已经投票给自己了,所以它会返回反对投票。这意味着,3个节点中的每个节点都只能收到一张投票(来自于自己)。没有一个节点获得了过半投票,所以也就没有人能被选上。接下来它们的选举定时器会重新计时,因为选举定时器只会在收到了AppendEntries消息时重置,但是由于没有Leader,所有也就没有AppendEntries消息。所有的选举定时器重新开始计时,如果我们不够幸运的话,所有的定时器又会在同一时间到期,所有节点又会投票给自己,又没有人获得了过半投票,这个状态可能会一直持续下去。

Raft不能完全避免分割选票(Split Vote),但是可以使得这个场景出现的概率大大降低。Raft通过为选举定时器随机的选择超时时间来达到这一点。我们可以这样来看这种随机的方法。假设这里有个时间线,我会在上面画上事件。在某个时间,所有的节点收到了最后一条AppendEntries消息。之后,Leader就故障了。我们这里假设Leader在发出最后一次心跳之后就故障关机了。所有的Followers在同一时间重置了它们的选举定时器,因为它们大概率在同一时间收到了这条AppendEntries消息。

它们都重置了自己的选举定时器,这样在将来的某个时间会触发选举。但是这时,它们为选举定时器选择了不同的超时时间。

假设故障的旧的Leader是服务器1,那么服务器2(S2),服务器3(S3)会在这个点为它们的选举定时器设置随机的超时时间。

假设S2的选举定时器的超时时间在这,而S3的在这。

这个图里的关键点在于,因为不同的服务器都选取了随机的超时时间,总会有一个选举定时器先超时,而另一个后超时。假设S2和S3之间的差距足够大,先超时的那个节点(也就是S2)能够在另一个节点(也就是S3)超时之前,发起一轮选举,并获得过半的选票,那么那个节点(也就是S2)就可以成为新的Leader。大家都明白了随机化是如何去除节点之间的同步特性吗?

这里对于选举定时器的超时时间的设置,需要注意一些细节。一个明显的要求是,选举定时器的超时时间需要至少大于Leader的心跳间隔。这里非常明显,假设Leader每100毫秒发出一个心跳,你最好确认所有节点的选举定时器的超时时间不要小于100毫秒,否则该节点会在收到正常的心跳之前触发选举。所以,选举定时器的超时时间下限是一个心跳的间隔。实际上由于网络可能丢包,这里你或许希望将下限设置为多个心跳间隔。所以如果心跳间隔是100毫秒,你或许想要将选举定时器的最短超时时间设置为300毫秒,也就是3次心跳的间隔。所以,如果心跳间隔是这么多(两个AE之间),那么你会想要将选举定时器的超时时间下限设置成心跳间隔的几倍,在这里。

那超时时间的上限呢?因为随机的话都是在一个范围内随机,那我们应该在哪设置超时时间的上限呢?在一个实际系统中,有几点需要注意。

首先,这里的最大超时时间影响了系统能多快从故障中恢复。因为从旧的Leader故障开始,到新的选举开始这段时间,整个系统是瘫痪了。尽管还有一些其他服务器在运行,但是因为没有Leader,客户端请求会被丢弃。所以,这里的上限越大,系统的恢复时间也就越长。这里究竟有多重要,取决于我们需要达到多高的性能,以及故障出现的频率。如果一年才出一次故障,那就无所谓了。如果故障很频繁,那么我们或许就该关心恢复时间有多长。这是一个需要考虑的点。

另一个需要考虑的点是,不同节点的选举定时器的超时时间差(S2和S3之间)必须要足够长,使得第一个开始选举的节点能够完成一轮选举。这里至少需要大于发送一条RPC所需要的往返(Round-Trip)时间。

或许需要10毫秒来发送一条RPC,并从其他所有服务器获得响应。如果这样的话,我们需要设置超时时间的上限到足够大,从而使得两个随机数之间的时间差极有可能大于10毫秒。

在Lab2中,如果你的代码不能在几秒内从一个Leader故障的场景中恢复的话,测试代码会报错。所以这种场景下,你们需要调小选举定时器超时时间的上限。这样的话,你才可能在几秒内完成一次Leader选举。这并不是一个很严格的限制。

这里还有一个小点需要注意,每一次一个节点重置自己的选举定时器时,都需要重新选择一个随机的超时时间。也就是说,不要在服务器启动的时候选择一个随机的超时时间,然后反复使用同一个值。因为如果你不够幸运的话,两个服务器会以极小的概率选择相同的随机超时时间,那么你会永远处于分割选票的场景中。所以你需要每次都为选举定时器选择一个不同的随机超时时间。

最后更新于