SOFAJRaft 选举机制剖析 | SOFAJRaft 实现原理

SOFAStack
Scalable  Open  Financial  Architecture Stack
是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

默认标题_2019-07-09-0.png

本文为《剖析 | SOFAJRaft 实现原理》第四篇,本篇做者力鲲,来自蚂蚁金服《剖析 | SOFAJRaft 实现原理》系列由 SOFA 团队和源码爱好者们出品,项目代号:<SOFA:JRaftLab/>,目前领取已经完成,感谢你们的参与。java

SOFAJRaft 是一个基于 Raft 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。git

SOFAJRaft :https://github.com/sofastack/sofa-jraftgithub

前言

在 Raft 算法中,选举是很重要的一部分,所谓选举也就是在多个节点中选出一个 Leader 节点,由他来对外提供写服务 (以及默认状况下的读服务)。算法

在剖析源码时,对选举机制的理解常常会遇到两极分化的状况,对于了解 Raft 算法基本原理的同窗,阅读源码就是品味实现之巧妙的过程,而对初体验的同窗,却会陷入丈二和尚的窘境,仿佛坠入云里雾里。架构

为了提高文章的可读性,我仍是但愿花一部分篇幅讲清楚选举机制的基本原理,以便后面集中注意力于代码实现。下面是一段图文比喻,帮助理解的同时也让整篇文章不至于过早陷入细节的讨论。dom

问题1:选举要解决什么

一个分布式集群能够当作是由多条战船组成的一支舰队,各船之间经过旗语来保持信息交流。这样的一支舰队中,各船既不会互相彻底隔离,但也无法像陆地上那样保持很是密切的联系,天气、海况、船距、船只战损状况致使船舰之间的联系存在但不可靠。分布式

舰队做为一个统一的做战集群,须要有统一的共识、步调一致的命令,这些都要依赖于旗舰指挥。各舰船要服从于旗舰发出的指令,当旗舰不能继续工做后,须要有别的战舰接替旗舰的角色。性能

图1 - 全部船有责任随时准备接替旗舰

图1 - 全部船有责任随时准备接替旗舰spa

如何在舰队中,选出一艘获得你们承认的旗舰,这就是 SOFAJRaft 中选举要解决的问题。3d

问题2:什么时候能够发起选举

什么时候能够发起选举?换句话说,触发选举的标准是什么?这个标准必须对全部战舰一致,这样就可以在标准获得知足时,全部舰船公平的参与竞选。在 SOFAJRaft 中,触发标准就是通讯超时,当旗舰在规定的一段时间内没有与 Follower 舰船进行通讯时,Follower 就能够认为旗舰已经不能正常担任旗舰的职责,则 Follower 能够去尝试接替旗舰的角色。这段通讯超时被称为 Election Timeout (简称 ET), Follower 接替旗舰的尝试也就是发起选举请求。

图2 - ET 触发其余船竞选旗舰

图2 - ET 触发其余船竞选旗舰

问题3:什么时候真正发起选举

在选举中,只有当舰队中超过一半的船都赞成,发起选举的船才可以成为旗舰,不然就只能开始一轮新的选举。因此若是 Follower 采起尽快发起选举的策略,试图尽早为舰队选出可用的旗舰,就可能引起一个潜在的风险:可能多艘船几乎同时发起选举,结果其中任何一支船都没能得到超过半数选票,致使这一轮选举无果,而后失败的 Follower 们再一次几乎同时发起选举,又一次失败,再选举 again,再失败 again ···

图3 - 同时发起选举,选票被瓜分

图3 - 同时发起选举,选票被瓜分

为避免这种状况,咱们采用随机的选举触发时间,当 Follower 发现旗舰失联以后,会选择等待一段随机的时间 Random(0, ET) ,若是等待期间没有选出旗舰,则 Follower 再发起选举。

图4 - 随机等待时间

图4 - 随机等待时间

问题4:哪些候选者值得选票

SOFAJRaft 的选举中包含了对两个属性的判断:LogIndex 和 Term,这是整个选举算法的核心部分,也是容易让人产生困惑的地方,所以咱们作一下解释:

一、Term

咱们会对舰队中旗舰的历史进行编号,好比舰队的第1任旗舰、第2任旗舰,这个数字咱们就用 Term 来表示。因为舰队中同时最多只能有一艘舰船担任旗舰,因此每个 Term 只归属于一艘舰船,显然 Term 是单调递增的。

二、LogIndex

每任旗舰在职期间都会发布一些指令(称其为“旗舰令”,类比“总统令”),这些旗舰令固然也是要编号归档的,这个编号咱们用 Term 和 LogIndex 两个维度来标识,表示“第 Term 任旗舰发布的第 LogIndex 号旗舰令”。不一样于现实中的总统令,咱们的旗舰令中的 LogIndex 是一直递增的,不会由于旗舰的更迭而从头开始计算。

图5 - 总统令 Vs 旗舰令,LogIndex 稍有区别

图5 - 总统令 Vs 旗舰令,LogIndex 稍有区别

全部的舰船都尽量保存了过去从旗舰接收到的旗舰令,因此咱们选举的标准就是哪艘船保存了最完整的旗舰令,那他就最有资格接任旗舰。具体来讲,参与投票的船 V 不会对下面两种候选者 C 投票:一种是 lastTermC < lastTermV;另外一种是 (lastTermV == lastTermC) && (lastLogIndexV > lastLogIndexC)。

稍做解释,第一种状况说明候选者 C 最后一次通讯过的旗舰已经不是最新的旗舰了,至少比 V 更滞后,因此它所知道的旗舰令也不可能比 V 更完整。第二种状况说明,虽然 C 和 V 都与同一个旗舰有过通讯,可是候选者 C 从旗舰处得到的旗舰令不如 V 完整 (lastLogIndexV > lastLogIndexC),因此 V 不会投票给它。

图6 - Follower 船 b 拒绝了船 c 而投票给船 a,船 a 旗舰令有一个空白框表示“第 Term 任旗舰”没有发布过任何旗舰令

图6 - Follower 船 b 拒绝了船 c 而投票给船 a,船 a 旗舰令有一个空白框表示“第 Term 任旗舰”没有发布过任何旗舰令

问题5:如何避免未入流的候选者“捣乱”

如上一小节所说,SOFAJRaft 将 LogIndex 和 Term 做为选举的评选标准,因此当一艘船发起选举以前,会自增 Term 而后填到选举请求里发给其余船只 (多是一段很复杂的旗语),表示本身竞选“第 Term + 1 任”旗舰。

这里要先说明一个机制,它被用来保证各船只的 Term 同步递增:当参与投票的 Follower 船收到这个投票请求后,若是发现本身的 Term 比投票请求里的小,就会自觉更新本身的 Term 向候选者看齐,这样可以很方便的将 Term 递增的信息同步到整个舰队中。

图7 - Follower 船根据投票请求更新本身的 Term

图7 - Follower 船根据投票请求更新本身的 Term

可是这种机制也带来一个麻烦,若是一艘船由于本身的缘由没有看到旗舰发出的旗语,他就会自觉得是的试图竞选成为新的旗舰,虽然不断发起选举且一直未能当选(由于旗舰和其余船都正常通讯),可是它却经过本身的投票请求实际抬升了全局的 Term,这在 SOFAJRaft 算法中会迫使旗舰 stepdown (从旗舰的位置上退下来)。

图8 - 自觉得是的捣乱者,迫使旗舰 stepdown

图8 - 自觉得是的捣乱者,迫使旗舰 stepdown

因此咱们须要一种机制阻止这种“捣乱”,这就是预投票 (pre-vote) 环节。候选者在发起投票以前,先发起预投票,若是没有获得半数以上节点的反馈,则候选者就会识趣的放弃参选,也就不会抬升全局的 Term。

图9 - Pre-vote 预投票

图9 - Pre-vote 预投票

选举剖析

在上面的比喻中,咱们能够看到整个选举操做的主线任务就是:

  1. Candidate 被 ET 触发
  2. Candidate 开始尝试发起 pre-vote 预投票
  3. Follower 判断是否定可该 pre-vote request
  4. Candidate 根据 pre-vote response 来决定是否发起 RequestVoteRequest
  5. Follower 判断是否定可该 RequestVoteRequest
  6. Candidate 根据 response 来判断本身是否当选

这个过程可用下图表示:

图10 - 一次成功的选举

图10 - 一次成功的选举

在代码层面,主要是由四个方法来处理这个流程:

com.alipay.sofa.jraft.core.NodeImpl#preVote //预投票
com.alipay.sofa.jraft.core.NodeImpl#electSelf //投票
com.alipay.sofa.jraft.core.NodeImpl#handlePreVoteRequest //处理预投票请求
com.alipay.sofa.jraft.core.NodeImpl#handleRequestVoteRequest //处理投票请求

代码逻辑比较直观,因此咱们用流程图来简述各个方法中的处理。

预投票和投票

图11 - 预投票 Vs 投票

图11 - 预投票 Vs 投票

图中可见,预投票请求 preVote 和投票请求 electSelf 的流程基本相似,只是有几个细节不太同样:

  1. preVote 是由超时触发;
  2. preVote 在组装 Request 的时候将 term 赋值为 currTerm + 1,而 electSelf 是先将 term ++;
  3. preVote 成功后,进入 electSelf,electSelf 成功后 become Leader。

处理请求

处理预投票和投票请求的逻辑也比较相似,一样用图来表示。

图12 - 处理预投票请求

图12 - 处理预投票请求

图13 - 处理投票请求

图13 - 处理投票请求

图中可见,处理两种请求的流程也基本相似,只是处理投票请求的时候,会有 stepdown 机制,强制使 Leader 从其 Leader 的身份退到 Follower。在具体的实现中,Leader 会经过租约的机制来避免一些没有必要的 stepdown,关于租约机制,能够参见以前的系列文章《SOFAJRaft 线性一致读实现剖析 | SOFAJRaft 实现原理》。

总结

咱们在本文中采用了类比的方式来剖析源码,主要是为了让读者能更容易的理解如何在分布式环境中达成共识,其实这也是整个 SOFAJRaft 要实现的目标。

行文至此,做者感受已经把选举说清楚了,若是还有没提到的地方,或者一些流程中的分支任务,欢迎从源码中进一步寻找答案。最后贴出上面提到的四个方法的源码。

图14 - preVote 预投票

图14 - preVote 预投票

图15 - electSelf 投票

图15 - electSelf 投票

图16 - handlePreVoteRequest 处理预投票

图16 - handlePreVoteRequest 处理预投票

图17 - handleRequestVoteRequest 处理投票

图17 - handleRequestVoteRequest 处理投票

《剖析 | SOFAJRaft 实现原理》系列文章回顾

公众号:金融级分布式架构(Antfin_SOFA)

相关文章
相关标签/搜索