5.2 集群选举
5.2.1 Raft选举算法
MongoDB的副本集选举使用Raft算法来实现,这是一种使用广泛的分布式一致性算法,为了让读者更深入地理解MongoDB副本集中的一些概念,我们先来了解一下这个算法。
1.范例
Raft选举的设计思路基本来自现实中的场景,以民主选举中的总统大选为例,每个总统通常都有一个任期阶段,这是体制决定的一个周期性时间,比如三到五年。在任期结束后,又会重新进行选举来决定总统的任命。
由于是民主社会,总统的人选可以从普通的民众当中产生,这些人需要先成为总统候选者(Candidate),然后到处发表演讲以获得更多的支持。最终,由选民进行公平的投票,谁的票数多,谁就能当上总统(Leader)。当然,在选举时可能会发生平票这种小概率事件,那么就会进行新一轮的选举,直到总统被选举出来。
在总统上任之后,他还要到处去演讲,告诉所有人自己成为总统这个事实,而这些事情都是为了巩固总统的地位。
在上述案例中,基本上已经说明了Raft协议的一些关键要素。那么接下来,我们看看Raft协议具体是怎么定义的。
2.协议中的角色
● Leader:领导者,Leader会向其他节点发送心跳,同时负责处理客户端的读写操作,包括将数据同步到其他节点。
● Follower:追随者,响应来自Leader和Candidate的投票请求,如果在一定时间内没有收到Leader的心跳,则会转换为Candidate。
● Candidate:候选者,Follower主动选举转换成为Candidate,获得大多数投票后会成为Leader。
在选举中有一个重要的概念叫任期(Term),一个任期对应一次选举,在Raft协议中任期被设计为单调递增的数字。每个节点上都会记录一个对应的任期,代表它所处于的任期(阶段)。在选举的过程中,节点之间会通过任期的比较来解决冲突问题,此时任期比较新的节点会被接受。
在一定条件下,上述几种角色会发生相互转换,如图5-2所示。
图5-2 Raft协议的角色转换
3.选举流程
在开始时,所有节点都是Follower,此时大家都没有办法收到Leader的心跳。接下来,A节点出现等待超时,率先发起选举,并成为Candidate。A节点先是给自己投一票,然后接着向其他节点发送投票请求,一旦A节点获得了集群中大多数节点的投票,则会成为Leader,同时开始向其他节点广播心跳,以此来声明自己的Leader角色。这个过程如图5-3和图5-4所示。
图5-3 Raft选举(1)
图5-4 Raft选举(2)
上面的过程仅仅是最简单的情况,实际上的投票选举则可能比这要复杂一些,并且会伴随一些冲突或异常出现。为了保证能达到最终的一致性,Raft协议还加入了以下细节。
● 在同一个任期内,每个节点最多只能给一个Candidate投票(节点内部进行记录),任期内投票采用先到先得的原则。
● 节点在收到Candidate的投票请求时,只有当对方的任期、操作日志时间至少与自己的一样新时,才会给它投票。
● Candidate发起投票后,如果一直没有得到大多数票,则会一直保持这个状态直到超时,此后将继续发起新一轮任期的选举(Term自增);如果在投票期间检测到了Leader的心跳(其他Candidate率先完成选主),则会判断当前Leader的任期是否至少跟自己一样新,如果是则降级为Follower,并承认对方的Leader角色,否则不予理会。
● 无论是Candidate还是Leader节点,一旦发现了其他节点有更新的任期(Term值),都会自动降级为Follower。
4.冲突
读者可以发现,选举过程中解决冲突的关键在于对Term值的判断。而在分布式环境中,冲突的情况是必然会产生的,下面列举了一些可能出现的场景。
场景A.多个候选者竞争,大多数获胜,如图5-5所示。
图5-5 Raft——大多数原则
场景B.多个候选者竞争,平票,如图5-6所示。
图5-6 Raft——平票结果
如果集群节点个数是偶数,那么可能会产生平票,也就需要进行下一轮选举。通过为每个节点增加一个随机的选举延期时间,可以大大降低出现平票的概率。
场景C.网络分区,造成Term值不一致,如图5-7所示。
图5-7 Raft——网络分区问题
如图5-7所示,当网络出现分区时,B节点变得不可达,仅剩下A、C节点进行选举。
此时,由于B节点会一直无法选举成功,且会一直超时重试,最终则造成该节点的Term值激增。
一旦网络情况恢复,则B节点将在很长时间内不会给其他节点投票(由于自身Term值过高),而同时自身也无法获得其他节点的投票(由于自身的日志太旧),这对于集群选举来说都是非常不利的。因此Raft协议中提出了一种预投票的手段,即实现通过预先投票的方式试探自己能否选举成功,只有预投票通过了才进行真正的投票;而预投票不会造成Term值自增,这样就解决了Term值差距太大的问题。
5.2.2 MongoDB实现的扩展
如前面所述,MongoDB是基于Raft协议的,在副本集选举、复制的机制中都能看到与标准Raft协议的影子。但在其具体的实现中,MongoDB仍然添加了一些自己的扩展,这包括:
● 支持chainingAllowed链式复制,即备节点不只是从主节点上同步数据,还可以选择一个离自己最近(心跳延时最小)的节点来复制数据。
● 增加了预投票阶段,即preVote,这主要是用来避免网络分区时产生Term值激增的问题,可以参照前面内容中提到的“4.冲突—场景C”。
● 支持投票优先级,如果备节点发现自己的优先级比主节点高,则会主动发起投票并尝试成为新的主节点。
5.2.3 MongoDB选举介绍
有了前面的理论基础,我们就可以轻松地理解MongoDB副本集的一些设计了,比如“大多数原则”的由来,这是因为Raft协议的选举机制中Leader必须通过大多数节点投票才能产生。我们假设副本集内的投票成员数量为N,则大多数为N/2+1。这个计算见表5-1。
表5-1 投票中的“大多数”原则
当副本集内存活的成员数量不足大多数时,整个副本集将无法选举出主节点,此时无法提供写服务,这些节点都将处于只读状态。此外,如果希望避免平票结果的产生,最好使用奇数个节点成员,比如3个或5个。当然,在MongoDB副本集的实现中,对于平票问题已经提供了解决方案:
● 为选举定时器增加少量的随机时间偏差,这样避免各个节点在同一时刻发起选举,提高成功率。
● 使用仲裁者角色,该角色不做数据复制,也不承担读写业务,仅仅用来投票。
此外,在一个MongoDB副本集中,最多只能有50个成员,而参与投票的成员最多只能有7个。这是因为一旦过多的成员参与数据复制、投票过程,将会带来更多可靠性方面的问题。
成员角色
MongoDB为副本集成员提供了多种角色,具体如下。
● Primary:主节点,其接收所有的写请求,然后把修改同步到所有备节点。一个副本集只能有一个主节点,当主节点“挂掉”后,其他节点会重新选举出来一个主节点。
● Secondary:备节点,与主节点保持同样的数据集。当主节点“挂掉”时,参与竞选主节点。
● Arbiter:仲裁者节点,该节点只参与投票,不能被选为主节点,并且不从主节点中同步数据。当节点宕机导致复制集无法选出主节点时,可以给复制集添加一个仲裁者节点,这样即使有节点宕机,仍能选出主节点。仲裁者节点本身不存储数据,是非常轻量级的服务。当复制集成员为偶数时,最好加入一个仲裁者节点,以提升复制集的可用性。
● Priority0:优先级为0的节点,该节点永远不会被选举为主节点,也不会主动发起选举。通常,在跨机房方式下部署副本集可以使用该特性。假设使用了机房A和机房B,由于主要业务与机房A更近,则可以将机房B的复制集成员Priority设置为0,这样主节点就一定会是A机房的成员。
● Hidden:隐藏节点,具备Priority0的特性,即不能被选为主节点(Priority为0),同时该节点对客户端不可见。由于隐藏节点不会接受业务访问,因此可通过隐藏节点做一些数据备份、离线计算的任务,这并不会影响整个副本集。
● Delayed:延迟节点,必须同时具备隐藏节点和Priority0的特性,并且其数据落后于主节点一段时间,该时间是可配置的。由于延迟节点的数据比主节点落后一段时间,当错误或者无效的数据写入主节点时,可通过延迟节点的数据来恢复到之前的时间点。
● Vote0:无投票权的节点,必须同时设定为Priority0节点。由于一个副本集中最多只有7个投票成员,因此多出来的成员则必须将其vote属性值设置为0,即这些成员将无法参与投票。
一般来说,成员能否成为主节点,主要受某些因素的影响,这包括节点之间的心跳、节点优先级,以及OpLog时间戳。而触发一次选举,通常会来自下面的场景:
● 初始化一个副本集时。
● 备节点在一段时间内发现不了主节点(默认10s超时),由备节点发起选举。
● 主节点放弃自己的角色,比如执行rs.stepDown命令。
5.2.4 副本集模式
常见的副本集架构由3个成员节点组成,其中存在几种不同的模式。
1.PSS模式
PSS模式由一个主节点和两个备节点所组成,即Primary+Secondary+Secondary,如图5-8所示。
图5-8 PSS架构模式
2.PSA模式
PSA模式由一个主节点、一个备节点和一个仲裁者节点组成,即Primary+Secondary+Arbiter,如图5-9所示。
图5-9 PSA架构模式
其中,Arbiter节点不存储数据副本,也不提供业务的读写操作。Arbiter节点发生故障不影响业务,仅影响选举投票。
3.PSH模式
PSH模式由一个主节点、一个备节点和一个隐藏节点组成,即Primary+Secondary+Hidden,如图5-10所示。
图5-10 PSH架构模式
其中,Hidden节点对业务不可见,同时无法被选举为主节点。一般利用Hidden节点来执行数据备份任务,可以避免备份对业务性能产生影响。