1. 选举过半原则
2.日志复制过半原则(包含自身),过半后即可将日志应用到状态机,通过后续心跳告知follower节点该日志已安全应用,follower也可以应用到状态机。
3.leader原则上只能根据过半原则确认当前term的日志是否需要应用到状态机,而不能当上leader后,立即把历史term的日志应用到状态机,应该间接应用历史日志(即必须存在当前term的新日志且达到应用状态机条件,此时由于需要按照日志的顺序应用,所以顺带把历史的日志应用到状态机,然后应用当前term这条日志)。这么做的目的是防止历史term某条日志可能只被少量节点复制,当切换leader后,这条日志槽位(顺序)被新leader的新日志存在,如果被leader贸然直接提交历史日志,有可能再次切换leader后,被其他节点覆盖这条日志。raft不允许这种现象发生,即一旦应用状态机的日志,是不允许被回退覆盖的。对应下面的场景:
4.raft集群节点发生变化,有2种处理方案:
(1)单节点变更:由于每次只变化一个节点,只要避免出现同一个term 多个leader出现就是安全。反证法:如要出现2个leader,则必然要将所有节点划分2个小集群组,每个集群组需要 1 + 50%节点数 ,那必然出现一个节点在2个小集群里面存在,由于一个节点不可能同时给多个节点投票,因此只会产生一个leader
(2)联合共识:下面通过例子来说明可能会同时出现两个leader的情况。在成员变更前是3节点的集群,集群中的节点为A、B和C,其中节点A为leader节点. 现在向集群中增加节点D和E,加入成功后变成5节点集群。集群旧配置用Cold表示,则Cold={A,B,C}。新配置用Cnew表示,则Cnew={A,B,C,D,E}.
上图来自raft论文,描述了联合一致过程中配置变更流程。图中虚线代表已创建但是未提交的配置,实线代表最新的已提交的配置。leader会首先创建Cold-Cnew日志项,并复制到新旧配置节点中的大多数。然后创建Cnew日志项,并复制到Cnew新配置的大多数。
这里说下上面配置的含义,上面配置就是集群中有哪些节点组成。通过例子加以说明。假设一个集群之前有A/B/C三个节点,现在发生配置变更,需要添加D和E两个节点。Cold,Cold-new,Cnew配置为:
Cold={A,B,C}
Cold-Cnew={A,B,C},{A,B,C,D,E}
Cnew={A,B,C,D,E}
复制
注意,Cold-Cnew有些文章写成Cold∪Cnew是有误导性的,我在看的时候有疑惑,如果理解成∪,那上面的Cold-Cnew进行并集处理后不是变为了{A,B,C,D,E},这不成了Cnew. 为了理清疑惑,查看了logcabin实现,logcabin(https://github.com/logcabin/logcabin
)是基于raft构建的分布式存储系统,可提供少量高度复制的一致存储, 它的集群变更采用这里的联合一致性方法,Cold-Cnew配置代码如下,它们是两个独立的集合。
下面结合上述变更图,从时间角度进行一个分析。整个变更过程划分为如下的区间。
- 当时间 t<Time1时,此时集群处于配置变更前的状态,全是Cold节点
- 当时间 Time1<t<Time2时,集群开始进行配置变更,leader开始把Cold-Cnew节点复制给其他节点。这个时间段里面,Cold-Cnew和Cold节点共存,选举出来的leader可能是拥有Cold配置的节点,也有可能是拥有Cold-Cnew配置的节点。拥有Cold-Cnew配置节点选举成功的条件是:Cold和Cnew中的大多数节点都同意。如果是Cold-Cnew配置节点选举成功,根据选举成功的条件,它一定是获得了Cold中大多数节点的同意,所以单纯的Cold配置节点不能选举成功。同理,如果Cold配置中的节点选举成功,那么Cold-Cnew配置中的节点是不能成功选举的,因为不满足条件中的Cold的大多数节点同意。
- 当时间 Time2<t<Time3时,leader已成功将Cold-Cnew配置复制到大多数节点,此时集群中可能有Cold配置节点。但来自Cold配置节点不能选出成为leader了。这个时间段选出的leader必然是拥有Cold-Cnew配置的节点。因为Cold-Cnew日志节点占了大多数,即使在Cold集群中,如果选举leader成功,必须有一个拥有Cold-Cnew日志的follower,但是Cold-Cnew中的follower更新,它会拒绝来自Cold中节点的选举请求。
- 当时间 Time3<t<Time4时,选举出来的leader可能是拥有Cold-Cnew配置的节点,也有可能是拥有Cnew配置的节点。但是只能是其中一个,假设Cold-Cnew中的节点当选成功,肯定是满足了条件有大多数Cnew节点同意,所以Cnew配置中的节点不可能当选为leader。同理,来自Cnew配置中的节点当选为leader,Cold-Cnew就不能当选为leader了。
- 当时间 t>Time4时,选举出来的leader一定是拥有Cnew配置的节点,因为如果存在一个拥有Cold-Cnew的节点请求当选leader,则需要Cnew中的大多数节点同意,但是Cnew中的大多数已经写入了Cnew日志,所以不会响应Cold-Cnew节点。
论文中还分析了配置变更其他需要解决的三个问题:
- 问题1:新加进来的节点开始时没有存储任何日志条目,当它们加入到集群中,需要一段时间来更新日志才能赶上其他节点,这段时间内它们无法提交新的日志条目。为了避免上述问题而造成的系统短时间的不可用,raft在配置变更前引入了一个额外的阶段,在该阶段,新的节点以没有投票权身份加入到集群中,leader也复制日志给它们,但考虑过半的时候不用考虑它们,一旦新节点追赶上了集群中的其他机器,配置变更可以按照上面描述的方式进行。
- 问题2:集群的leader可能不在新配置Cnew中,这种情况,leader一旦提交了Cnew日志条目就会退位到followr状态。这意味着有一个时间段(leader提交Cnew期间),leader管理的是一个不包括字节的集群,它复制日志但不把自己算在过半里面。leader转换发生在Cnew被提交的时候,这个时候是新配置可以独立做决定的最早时刻,在此之前只能从Cold中选出leader.
- 问题3:被移除的节点即不在Cnew配置中的节点可能会扰乱集群。这些节点将不会再接收到心跳,当选举超时时候,它们会进行新的选举过程。它们会发送带有新任期号的requestVote RPC,这会导致当前的leader回到follower状态。新的leader最终会被选出来,但被移除的节点会再次超时,导致系统可用性很差。处理方法是,当服务器认为当前leader存在时,会忽略requestVote PRC。特别当服务器在最小选举超时时间内收到一个requestVote RPC,它不会更新任期号或投票。这不会影响正常的选举,因为每个节点在开始一次选举之前,至少等待最小选举超时时间。