分布式共识算法之Raft算法

在这里插入图片描述

一. 问题背景

研究完关于Redis的架构之后,想要了解一下哨兵模式是如何选出哨兵leader的,因此来研究一下raft算法

参考:

  1. Raft官网
  2. 由浅入深理解Raft协议
  3. 白话讲解paxos&raft算法原理及实战_一点课堂(多岸学院&多岸教育)

二. 知识储备

2.1 共识算法

在分布式系统的环境下,有若干个服务器在不同地方同时运行,这些服务器需要共同使用一套规则来协商某些事情,这套规则就是共识算法,也可以叫一致性算法。Redis哨兵集群选举出leader就需要共识算法,可简单了解关于Redis的架构共识涉及多个服务器就价值达成一致。一旦他们对价值做出决定,该决定就是最终决定

2.2 CAP定理

在分布式系统中,CAP定理是:

  1. C,Consistency,一致性。在分布式系统中所有数据副本的值,在同一时刻是否相同。
  2. A,Availability,可用性。集群中的部分节点故障后,剩余节点是否还能响应客户端的读写请求。
  3. P,Partition tolerance。系统如果不能在时限内达成数据一致性,就意味着发生了分区(比如A地的服务端数据无法与B地的服务端数据同步,存在数据差异)。必须对当前操作在C和A之间做出选择

三. 前言

Redis对Raft的应用,是在哨兵集群选举出哨兵leader这方面,而不是直接选举出提升哪个Redis slave为master。

四. Raft和Paxos的因缘?

由于Paxos过于难懂,因此有了Raft算法(大牛们实在觉得Paxos太难懂了,因此模仿Paxos设计出了一套简单易懂的共识算法),并得到了广泛的应用。这里就不讲Paxos,只研究Raft

五. Raft原理

5.1 节点个数

一个Raft集群包含若干个服务器节点,通常是奇数个节点(一般是5个,这样的系统可以容忍2个节点失效,因为挂了2个还剩3个,3个中每个人都必须投票给其他人且只能投一次,所以肯定会有一个人有最多票)

5.2 节点的角色

Raft集群中,有3种角色:

  1. follower,跟随者 。每个节点最初都是跟随者,且都会有一个属于自己的随机超时时间(timeout),比如timeout为3s,3s内没有收到leader的信息,节点则变成candidate(候选者)。
  2. candidate,候选者。成为候选者,可以向其他节点发出请求,请他们投自己一票,即选举(每个节点在每次选举中只能投一次票),最快得到最多票数的候选者将成为leader(领导者)
  3. leader,领导者。成为领导者后,定时发送消息给其他节点,其他节点也要发送响应报文给leader。领导者可以代表整个raft集群内的所有节点,外部客户端是与领导者交互的。

5.3 多数派协议

为了保证选举的结果只产生一个leader,选举的过程采用多数派协议(即少数服从多数)。当一个candidate发出请求申请成为leader时,只有获得raft集群中半数以上的follower同意后,才能成为leader。投票过程如下:

  1. follower每响应完leader的信息后,都会开始一个随机超时时间,最快超过这个超时时间的节点成为candidate,candidate向其他节点发送请求,申请成为leader
  2. follower们投票
  3. 如果获得超过半数的follower投票,那么candidate自动变成leader,开始广播日志(即发送心跳包)

5.4 随机超时机制

candidate也会发生多个Candidate同时发送投票请求,而导致谁都不能够得到多数赞成票的情况,有可能永远也选不出Leader。为了保证Leader选举的效率,Raft在投票选举中使用了随机超时的机制:

  1. 在每个Followers上设定的Leader超时时间是在一个范围内随机的。这样可以尽量让Followers不在同一时间发起Leader选举

  2. 每个candidate发起投票后,如果在一段时间内没有任何candidate成为Leader则,需要重新发起Leader选举。这段等待的时间,在每个candidate上也是随机的。从而保证不会有多个candidate同时重新发起Leader选举

5.5 正常情况下,Raft集群是怎么样的?

正常情况下:

  1. Raft的集群只有一个Leader,其余节点都是Follower。
  2. Follower都是被动的,他们不会发送任何请求,只是简单地响应Leader和Candidate的请求。
  3. Leader处理所有的客户端请求。如果客户端与Follower通信,Follower会将请求重定向给Leader。

在这里插入图片描述

虽说是随机的超时时间,但是也有个范围,太小或者太大都会影响系统的可用性。太小会导致过多的选举冲突,太大又会影响系统的平滑运行。在Raft的论文中,作者将这个超时时间称为electionTimeout,并给出了合理的范围,公式如下:

broadcastTime ≪ electionTimeout ≪ MTBF

“≪”代表数量级上的差异(10倍以上)。

5.6 Candidate的日志长度要等于或者超过半数节点才能选为Leader

当Leader故障时,Followers上日志的状态很可能是不一致的。有的多有的少,而且Commit Index也不尽相同。
在这里插入图片描述

我们知道已经提交的日志是不能够丢弃的,必须要最终复制到所有的节点上才行。假如在选Leader时,图中Candidate A变成了Leader,就必须要首先从Candidate B上将日志4复制过来,然后才能开始处理新的日志。为了减少复杂性,raft就规定,只有包含了所有已提交日志的Candidate才能当选为Leader。

  • 当发现Leader无响应后(一段时间内没有数据或心跳),Candidate发送投票请求,请求中包含自己日志队列的长度(或者说最大日志的Index)

  • Followers检查Candidate的日志长度,只有Candidate的日志等于或者长于自己才投票

  • 如果超过半数的Followers投了票,则Candidate自动变成Leader,开始广播数据

因为已经提交的日志一定被复制到了多数节点上,所以日志长度等于或者长于多数节点的Candidate一定包含了所有已经提交的日志

5.7 为什么不是检查Commit Index?

因为Leader故障时,很有可能只有Leader的Commit Index是最大的。

在这里插入图片描述

如果图中的Candidate A被选举为Leader,那么日志4就会被丢弃。但是日志4已经在原来的Leader上提交了,因此必须被保留才行。所以只能让日志长度更长的Candidate B选为Leader。这种做法有可能把原来Leader没广播完成的日志(图中的日志5)接着广播完成,这没有什么关系。

5.8 Followers日志补齐

当Leader故障时,Followers上的日志状态是不一样的,有长有短。因此新的Leader选出后,首先要将所有Followers的日志补齐才行。因此Leader要询问Followers的日志长度,从最小的日志位置开始补齐。

5.9 Followers未提交日志的更新

新Leader的日志一定包含所有已经提交的日志。但新Leader的日志不一定是最长的,那些新Leader没有的日志,一定是未提交的日志,因此可以被更新,没有关系的。Leader只需要从自己的当前位置开始插入日志并广播出去就可以了。Followers会用新的日志去更新指定位置上的日志。

5.10 新旧Leader的交替

新的Leader选出后,开始广播日志。这时如果旧的Leader故障恢复了(比如网络临时中断),并且还认为自己是Leader,也会广播日志。这不就导致了同时有两个Leader出现吗?是的,Raft也没办法让旧的Leader不发日志,但是Raft有办法让Followers拒绝旧Leader的日志

5.10.1 Term

Raft将时间划分为连续的时间段,称为Term。 Term是指从一次Leader选举开始到下一次Leader选举的一段时间。这段时间内只能有一个Leader被选举成功,并负责管理系统或者没有Leader选出。

在这里插入图片描述

每个Term都有一个唯一的数字编号。所有Term的数字编号是从小到大连续排列的。

5.10.2 作废旧Leader

Term编号在作废旧Leader的过程中至关重要,但却十分简单。过程如下:

  1. 发送日志到所有Followers,Leader的Term编号随日志一起发送
  2. Followers收到日志后,检查Leader的Term编号。如果Leader的Term编号等于或者大于自己的当前Term(Current Term)编号,则存储日志到队列并且应答收到日志。否则发送失败消息给Leader,消息中包含自己的当前Term编号。
  3. 当Leader收到任何Term编号比自己的Term编号大的消息时,则将自己变成Follower。收到的消息包括:Follower给自己的回复消息、新Leader的日志广播消息、Leader的选举消息。

5.11 Raft的实现

论文中作者仅用了两个RPC就实现了Raft的功能,它们分别是:

  1. RequestVote(): Candidate发起的投票请求

  2. AppendEntries(): 将日志广播到Followers上

AppendEntries()除了广播日志外,作者还巧妙的用它实现了以下的功能:

  • 发送心跳(heartbeat): 没有客户日志时,通过AppendEntries()广播空日志,当做心跳。
  • 发送Commit Index:当Commit Index更新后,可以随着当前的日志通过AppendEntries()广播到Followers上。如果没有客户端日志,则可以随着心跳广播出去。

猜你喜欢

转载自blog.csdn.net/qq_40634846/article/details/113791886