写在前面
在分布式之Paxos共识算法分析 一文中我们分析了paxos算法,知道了其包括basic paxos和multi paxos,并了解了multi paxos只是一种分布式共识算法的思想,而非具体算法,但可根据其设计具体的算法,本文就一起来看下其一种具体的实现算法raft,这也是当前中心化架构分布式系统使用最多的一种分布式共识算法了,下面我们就一起看下吧!
1:raft提供了哪些内容
在multi paxos的描述中有一个霸道总裁的角色leader
,因此在raft中就定义了leader的选举策略。除此之外还定义了日志复制策略,成员变更。接下来我们分别看下这三部分内容。
2:leader选举
首先我们先来看下raft中节点的角色,包括领导者,跟随者(非leader大部分时间所处的状态)
,候选者(竞选leader时所处的状态)
,另外每个节点还有一个任期编号的属性,代表当前集群的leader是第几任leader(可用于处理多leader场景,以及leader选举投票确定是否投票)
,任期编号默认为0,如下图:
在系统初始状态,所有节点所处的状态都是跟随者,并且每个节点都有leader心跳消息的超时时间,该超时时间是随机的,避免多节点同时到期并竞选leader,导致无法快速选举leader的问题。如下节点A,节点B,节点C,超时时间分别是150ms,200ms,300ms:
很明显,节点A会先超时,转换角色为候选人并将自己的任期编号+1变为1(其认为主节点故障,需要产生新的leader,因此任期编号+1)
,先向自己投1
票,并向其他节点发送申请成为leader消息,如下图:
其他节点发现自己的任期都小于1,并且自己在term=1都还没有投过票(一个任期只能投一票,即某任期一旦投过票,在收到相同任期申请成为leader的请求则忽略或投拒绝票)
,因此会投出自己唯一的一票,如下:
之后节点A获取了3票赞成票,超过了3个节点的半数,因此成为leader,如下:
接着leader就会周期性的给其他节点发送心跳消息,防止其他节点的leader心跳超时时间到时,并篡权
,如下图:
以上过程源码参考这里 ,运行过程如下图:
补充下其他的规则:
1:领导者周期性地向所有跟随者发送心跳消息(即不包含日志项的日志复制 RPC 消息),通知大家我是领导者,阻止跟随者发起新的选举。
2:如果在指定时间内,跟随者没有接收到来自领导者的消息,那么它就认为当前没有领导者,推举自己为候选人,发起领导者选举。
3:在一次选举中,赢得大多数选票的候选人,将晋升为领导者。
4:在一个任期内,领导者一直都会是领导者,直到它自身出现问题(比如宕机),或者因为网络延迟,其他节点发起一轮新的选举。
5:在一次选举中,每一个服务器节点最多会对一个任期编号投出一张选票,并且按照“先来先服务”的原则进行投票。比如节点 C 的任期编号为 3,先收到了 1 个包含任期编号为 4 的投票请求(来自节点 A),然后又收到了 1 个包含任期编号为 4 的投票请求(来自节点 B)。那么节点 C 将会把唯一一张选票投给节点 A,当再收到节点 B 的投票请求 RPC 消息时,对于编号为 4 的任期,已没有选票可投了。
6:日志完整性高的跟随者(也就是最后一条日志项对应的任期编号值更大,索引号更大),拒绝投票给日志完整性低的候选人。比如节点 B 的任期编号为 3,节点 C 的任期编号是 4,节点 B 的最后一条日志项对应的任期编号为 3,而节点 C 为 2,那么当节点 C 请求节点 B 投票给自己时,节点 B 将拒绝投票。
2.1:常见问题
2.1.1:如何知道选票超过半数
总节点数是固定的,已知的,进行简单数学运算即可。
2.1.2:raft存在的问题
leader的单点问题,以及由此带来的写数据性能瓶颈问题。节点较多时,leader的心跳消息有较高的通信成本。节点较多时,选主获得半数投票时间长,集群不可用时间长。
2.1.3:所有节点都可能成为leader吗?
不是的,必须当前节点的日志完整性高于超过半数的节点(这样才可能获得半数以上选票)
,但这里并不带代表这些节点不能成为candidate并发起选举,只是说就算发起选举了,最终也会选举失败而已。
2.1.4:leader会一直是leader吗?
如果是机器和网络一直正常的话,是的,但是实际环境并不会,根据chubby团队观察,一个leader的任期大概是在几天左右。
2.1.5:如果集群重启,集群状态如任期编号会清0吗?
不会的,这些信息是持久化存储的,重启后,需要将集群恢复到重启前的状态。
2.1.6:如果某轮leader选举失败,下一轮选举任期编号会+1吗?
会的,因为一个任期一个节点只有一张选票,如果不+1的话,将无法在新一轮投出选票。
2.1.7:如果一个节点孤立,导致leader选举一直失败,任期编号不断增大,则当其回到集群会影响集群吗?
不会,因为当其回到集群后,别的节点发现自己的任期编号小于其任期编号,则会更新自己的任期编号为其任期编号,并且因为自己的日志完整性高于其,所以也不会给其投出选票,其也成不了leader,所以,不会有影响。
2.1.8:心跳随机超时时间怎么确定,如何查看?
这就是属于编码实现问题了,一般设置在某个时间范围内,至于如何查看也是和具体程序实现有关。
2.1.9:raft算法中只有leader才能接收读请求吗?
raft算法本身没有要求,如果是要求最终一致性,则可以允许非leader读取,比如zookeeper就是如此,直接响应本地数据。
2.1.10:如果集群正在选主此时集群还能正常工作吗?
写请求无法执行,需要客户端重试。读请求如果是允许非leader处理的话,则正常,否则也需要重试,等到选主完成。
2.1.11:因为leader心跳超时,导致选举出新leader,当老leader收到新leader心跳时会如何处理?
更新自己的状态为跟随者,并使用新leader的任期编号更新自己的任期编号。
3:日志复制
可以认为为日志即数据,日志由日志项组成,且日志必须是连续的,这不同于multi-paxos。一个日志项包含如下3部分内容:
指令:用户发送的指令,如set x 10这种
任期编号:创建该条日志项leader的任期编号,相当于是给数据标识版本
索引值:单调递增的索引编号,用来标识日志项
如下图:
日志同步过程如下:
1:leader生成日志条目,发送日志同步RPC消息复制日志条目到其他节点
2:当超过半数的节点返回复制成功消息后,将日志项应用到状态机(通过状态机记录日志项已经写入成功),然后返回成功消息给客户端
这里需要注意,leader在收到了大多数节点写入成功消息后,并不会再次发送提交日志条目消息到其他节点,也就是两阶段提交变为了一阶段提交,这里提交的消息并不是不发了,而只是会随着后续的同步日志消息,或者是leader的心跳消息,来同步给其他节点,这样就可以减少一半的网络交互开销,可通过下图对比查看:
假设节点A,节点B,节点C,节点D,其中节点A为leader,任期编号为9,索引编号是88,发送的指令是set x=90
,即要发送的日志段是(88,9,set x=90),则该过程如下图:
源码实现参考这里 ,运行效果如下图:
3.1:如何实现日志一致性
如果节点一直处于正常状态,节点能够正常的复制来自leader的日志段,但是如果是发生了宕机,重启等故障后follower出现了和leader的日志不一致,这种情况raft算法是怎么实现恢复一致的呢?我们一起来看下。首先看以下的点:
1:数据绝对以leader的为准
2:leader可以覆盖和删除follower不一致的数据
3:leader的数据不会被覆盖和删除
为了处理可能出现的日志一致性问题,leader在发送日志同步消息时,除了携带日志段外,还会携带当前日志段的上一个日志段的索引号以及对应的任期编号,如下要发送的索引号就是7,任期编号就是4:
当follower收到后会对比自己的最新的日志段是否匹配二者,如果匹配的话则接受日志段,否则返回失败,返回失败后leader继续向前找,继续发送,直到follower接受,则从接收的位置发送后续日志段,覆盖follower的日志段,从而使得和leader的日志保持一致,可参考下图:
3.2:常见问题
3.2.1:如果超过半数follower都收到日志段但是leader还未应用状态到状态机,此时leader挂掉,数据会丢失吗?
不会,因为最终当选为新leader的node是有该未提交日志段的,然后发现超过半数的node都有该日志段(老leader肯定是有的,所以这里的半数要包括老leader)
,则会提交日志段,并在后续发送消息给其它follower也提交该日志段。
3.2.2:如何保证客户端写操作请求到leader?
一般有两种方式(假定收到请求的节点是follower)
,1:follower充当代理角色,转发请求给leader 2:follower返回leader地址给客户端,客户端再次向leader发起写请求。
4:成员变更
成员变更是指通过新增节点来增加副本数,成员变更的一个问题就是集群短时间内处于不稳定状态,而可能会导致出现2个leader的情况,这就违背了leader唯一性
的原则,而导致出现这个问题的原因一般都是网络分区,如下是有3个节点的集群:
此时集群配置是[A,B,C]
,现在加入2个节点D,E,如下图:
假定集群稳定前的某一刻发生了网络分区,AB处于一个分区,CDE处于一个分区,如下图:
此时新配置是[A,B,C,D,E]
,则新配置的大多数就是3
,如上图就产生了新leader C,而老配置是[A,B,C]
,则老配置的大多数就是2
,产生了新leader A,此时就出现了2个leader A 和 C,这样就有问题了。raft解决这个问题的方式有联合共识和单节点变更,联合共识是最初的方案,但因为过于复杂,所以后续就有了单节点变更的方案,这种方案可以解决因为一次添加过多节点导致出现分区时同时满足新旧大多数
而破坏leader唯一性的问题。接下来我们看下单节点变更的方案,假设依然是3个节点ABC,如下:
首先添加一个节点D:
此时老配置是[A,B,C]
,大多数是2,新配置是[A,B,C,D]
,大多数是3,只有总节点数至少为5,才能同时满足新旧配置的大多数
,而此时节点数是4,所以在出现网络分区不会满足>1个leader
的情况,接着我们来增加节点E:
此时老配置是[A,B,C,D]
,大多数是3,新配置是[A,B,C,D,E]
,大多数是3,要想满足新旧配置的大多数,则至少需要有6个节点,而此时只有5个节点,所以不管出现何种情况的分区也不可能出现>1个leader的情况。
写在后面
小结
本文一起看了raft共识算法对分布式系统中leader选举,日支复制,成员变更3个主要问题提供的解决思路。希望本文能够帮助到你。
参考文章列表
分布式之Paxos共识算法分析 。
分布式理论之分布式选举 。
The Secret Lives of Data 。
什么是状态机?一篇文章就够了 。
状态机分析 。