Raft 协议实战系列(二)—— 选主

注:本文原创,转载请标明出处。欢迎转发、关注微信公众号:Q的博客。 不定期发送干货,实践经验、系统总结、源码解读、技术原理。

笔者期望通过该系列文章帮助读者深入理解Raft协议并能付诸于工程实践中,同时解读不易理解或容易误解的关键点。

本文为《Raft实战》系列第二篇,该系列会从原理、源码、实践三个部分为大家讲解Raft算法。

原理部分我们会结合Raft论文讲解Raft算法思路,整体分篇会遵循Raft的模块化思想,分别讲解Leader election、Log replication、Safety、Cluster membership change、Log compaction等。

源码部分我们会通过分析hashicorp/raft来学习一个工业界的Raft实现,hashicorp/raft是Consul的底层依赖。

实践部分我们会基于hashicorp/raft来实现一个简单的分布式kv存储,以此作为系列的收尾。

系列历史链接:Raft实战系列(一)—— 基本概念

什么是选主?

选主(Leader election)就是在分布式系统内抉择出一个主节点来负责一些特定的工作。在执行了选主过程后,集群中每个节点都会识别出一个特定的、唯一的节点作为leader。

我们开发的系统如果遇到选主的需求,通常会直接基于zookeeper或etcd来做,把这部分的复杂性收敛到第三方系统。然而作为etcd基础的Raft自身也存在“选主”的概念,这是两个层面的事情:基于etcd的选主指的是利用第三方etcd让集群对谁做主节点的决策达成一致,技术上来说利用的是etcd的一致性状态机、lease以及watch机制,这个事情也可以改用单节点的MySQL/Redis来做,只是无法获得高可用性;而Raft本身的选主则指的是在Raft集群自身内部通过票选、心跳等机制来协调出一个大多数节点认可的主节点作为集群的leader去协调所有决策。

当你的系统利用etcd来写入谁是主节点的时候,这个决策也在etcd内部被它自己集群选出的主节点处理并同步给其它节点

Raft为什么要进行选主?

按照论文所述,原生的Paxos算法使用了一种点对点(peer-to-peer)的方式,所有节点地位是平等的。在理想情况下,算法的目的是制定一个决策,这对于简化的模型比较有意义。但在工业界很少会有系统会使用这种方式,当有一系列的决策需要被制定的时候,先选出一个leader节点然后让它去协调所有的决策,这样算法会更加简单快速。

此外,和其它一致性算法相比,Raft赋予了leader节点更强的领导力,称之为Strong Leader。比如说日志条目只能从leader节点发送给其它节点而不能反着来,这种方式简化了日志复制的逻辑,使Raft变得更加简单易懂。

Raft选主过程

下图我们在前一篇文章已经看到了,但只是做了简单的描述,接下来我们会结合具体的Leader election细节来深刻理解节点的状态转换。

节点状态图

Raft的选主基于一种心跳机制,集群中每个节点刚启动时都是follower身份(Step: starts up),leader会周期性的向所有节点发送心跳包来维持自己的权威,那么首个leader是如何被选举出来的呢?方法是如果一个follower在一段时间内没有收到任何心跳,也就是选举超时,那么它就会主观认为系统中没有可用的leader,并发起新的选举(Step: times out, starts election)。

这里有一个问题,即这个“选举超时时间”该如何制定?如果所有节点在同一时刻启动,经过同样的超时时间后同时发起选举,整个集群会变得低效不堪,极端情况下甚至会一直选不出一个主节点。Raft巧妙的使用了一个随机化的定时器,让每个节点的“超时时间”在一定范围内随机生成,这样就大大的降低了多个节点同时发起选举的可能性。

一个五节点Raft集群的初始状态,所有节点都是follower身份,term为1,且每个节点的选举超时定时器不同

若follower想发起一次选举,follower需要先增加自己的当前term,并将身份切换为candidate。然后它会向集群其它节点发送“请给自己投票”的消息(RequestVote RPC)。

S1率先超时,变为candidate,term+1,并向其它节点发出拉票请求

接下来会有三种可能的结果,也即节点状态图中candidate状态向外伸出的三条线。

1. 选举成功(Step: receives votes from majority of servers)

当candicate从整个集群的大多数(N/2+1)节点获得了针对同一term的选票时,它就赢得了这次选举,立刻将自己的身份转变为leader并开始向其它节点发送心跳来维持自己的权威。

“大部分”节点都给了S1选票

S1变为leader,开始发送心跳维持权威

每个节点针对每个term只能投出一张票,并且按照先到先得的原则。这个规则确保只有一个candidate会成为leader。

2. 选举失败(Step: discovers current leader or new term)

Candidate在等待投票回复的时候,可能会突然收到其它自称是leader的节点发送的心跳包,如果这个心跳包里携带的term不小于candidate当前的term,那么candidate会承认这个leader,并将身份切回follower。这说明其它节点已经成功赢得了选举,我们只需立刻跟随即可。但如果心跳包中的term比自己小,candidate会拒绝这次请求并保持选举状态。

S4、S2依次开始选举

S4成为leader,S2在收到S4的心跳包后,由于term不小于自己当前的term,因此会立刻切为follower跟随S4

3. 选举超时(Step: times out, new election)

第三种可能的结果是candidate既没有赢也没有输。如果有多个follower同时成为candidate,选票是可能被瓜分的,如果没有任何一个candidate能得到大多数节点的支持,那么每一个candidate都会超时。此时candidate需要增加自己的term,然后发起新一轮选举。如果这里不做一些特殊处理,选票可能会一直被瓜分,导致选不出leader来。这里的“特殊处理”指的就是前文所述的随机化选举超时时间

S1~S5都在参与选举

没有任何节点愿意给他人投票

如果没有随机化超时时间,所有节点将会继续同时发起选举……

以上便是candidate三种可能的选举结果。

节点状态图中的最后一条线是:discovers server with higher term。想象一个场景:当leader节点发生了宕机或网络断连,此时其它follower会收不到leader心跳,首个触发超时的节点会变为candidate并开始拉票(由于随机化各个follower超时时间不同),由于该candidate的term大于原leader的term,因此所有follower都会投票给它,这名candidate会变为新的leader。一段时间后原leader恢复了,收到了来自新leader的心跳包,发现心跳中的term大于自己的term,此时该节点会立刻切换为follower并跟随的新leader。

上述流程的动画模拟如下:

S4作为term2的leader

S4宕机,S5即将率先超时

S5当选term3的leader

S4宕机恢复后收到了来自S5的term3心跳

S4立刻变为S5的follower

以上就是Raft的选主逻辑,但还有一些细节(譬如是否给该candidate投票还有一些其它条件)依赖算法的其它部分基础,我们会在“安全性”一篇描述。

当票选出leader后,leader也该承担起相应的责任了,这个责任是什么?就是下一篇将介绍的日志复制~

欢迎转发、关注微信公众号:Q的博客,不定期发送干货,实践经验、系统总结、源码解读、技术原理。

你可能感兴趣的:(Raft 协议实战系列(二)—— 选主)