本文选自我在知乎上的回答。《设计数据密集型应用》(DDIA)提到了Raft的一个问题,就是Raft算法存在一个liveness的极端情况:如果出现不可靠的网络连接,Raft的当前leader会不断被强制下台,导致系统几乎没有任何进展。让我们首先详细描述问题。如图所示的4节点Raft集群,一个节点和其他三个网络不稳定,假设它可以向其他节点发送消息,但收不到其他节点的消息,那么它永远不会收到心跳消息,然后转进入候选人自增任期并发起新的选举。来自更大任期的RequestVote请求将导致当前Leader下台并重新选举。重复此操作将导致集群无法正常工作。大Raft论文提到,一种解决方案是添加一个新的PreVote阶段,这就是etcd所做的,添加一个称为PreCandidate状态的新节点状态。PreVote阶段的作用是当一个节点要发起选举时,首先要确认自己确实有资格赢得投票而不是浪费时间,然后才会真正发起新的自增条款的选举。PreVote阶段的具体过程是在发起真正的选举之前向所有节点发送PreVote消息。PreVote消息与RequestVote消息相同,但节点不会增加自己的term,只会增加消息中的term参数。接收到PreVote消息的节点同意重选的条件是:参数中的任期较大,或任期相同但日志索引较大;至少有一个选举超时时间没有收到leader的心跳;只有超过半数的节点同意PreVote消息,该节点才能真正增加自己的任期并发起新的选举。回到上面的情况,网络链路有问题的节点会发现自己在PreVote阶段无法赢得超过半数的节点同意发起选举(其他节点可以收到心跳),所以term不会自高自大干扰Leader的工作。问题解决了吗?问题没有解决,只是PreVote阶段可能出现cornercase导致Raft失去activity。如图:图中是一个由5个节点组成的Raft集群,故障前有4个是Leader。现在出现故障,5down了,而4只和2相连,1、2、3互相相连。在这种情况下,1和3无法收到leader的心跳,会发起PreVote请求,但是由于2可以收到Leader节点4的心跳,所以2不会同意PreVote请求,所以节点1和3无法获得多数PreVote同意。这个集群的问题是不能选出新的leader,但是旧的leader只能AppendEntries到两个节点(2和它自己),无法达到多数。整个集群无法取得任何进展,活动不满足。这里Raft协议明明可以容忍两个节点故障,但是加入PreVote阶段后,就不能只容忍一个节点故障了。事实上,如果没有PreVote阶段,1和3就有机会被选为leader来推动整个系统的正常运行。所以Raft还需要加一个机制让Leader主动下台。机制很简单:Leader在没有收到来自多数节点的AppendEntries响应时下台。Inthisway,1,2,and3inthefigurehavetheopportunitytobeelectedasthenewleader,andtheentireclustercanstillworknormally.etcd调用这个CheckQuorum,在etcd的issue中有关于这个问题的讨论:https://github.com/etcd-io/etcd/issues/3866CheckQuorum保证如果当前Leader不能连接到多数节点,它会stepdown并选出新的Leader。PreVote保证一旦Leader被选举出来,整个系统就会稳定,Leader不会被迫下台。那么PreVote+CheckQuorum能否解决liveness问题呢?是的!
