摘取自: http://mp.weixin.qq.com/s?__biz=MzIyMTQ1NDE0MQ==&mid=2247483979&idx=1&sn=12864382e233fe9b900ab14349404032&chksm=e83dc819df4a410f5959b6922025d317d6c497b7110c4c5d8720fb2b0a70246ce651f9a19e91&mpshare=1&scene=1&srcid=0125HVXp5VF5Kuav7ko8iCrb#rd网络
0 - Raft协议和Paxos的因缘分布式
读过Raft论文《In Search of an Understandable Consensus Algorithm》的同窗都知道,Raft是由于Paxos而产生的。Paxos协议是出了名的难懂,并且不够详细,牢牢依据Paxos这篇论文开发出可用的系统是很是困难的。Raft的做者也说是被Paxos苦虐了无数个回合后,才设计出了Raft协议。做者的目标是设计一个足够详细而且简单易懂的“Paxos协议”,让开发人员能够在很短的时间内开发出一个可用的系统。
ui
Raft协议在功能上是彻底等同于(Multi)-Paxos协议的。Raft也是一个原子广播协议(原子广播协议参见《由浅入深理解Paxos协议(1)》),它在分布式系统中的功能以及使用方法和Paxos是彻底同样的。咱们能够用Raft来替代分布式系统中的Paxos协议以下图所示:设计
1 - Raft的设计理念3d
严格来讲Raft并不属于Paxos的一个变种。Raft协议并非对Paxos的改进,也没有使用Paxos的基础协议(The Basic Protocol)。Raft协议在设计理念上和Paxos协议是彻底相反的。正是因为这个彻底不一样的理念,使得Raft协议变得简单起来。日志
Paxos协议中有一个基本的假设前提:可能会同时有多个Leader存在。这里把Paxos协议执行的过程分为如下两个部分:队列
Leader选举图片
数据广播ci
在《由浅入深理解Paxos协议(2)》的“Leader的选取”一节中提到过,Paxos协议并无给出详细的Leader选举机制。Paxos对于Leader的选举没有限制,用户能够本身定义。这是由于Paxos协议设计了一个巧妙的数据广播过程,即Paxos的基本通信协议(The Basic Protocol)。它有很强的数据一致性保障,即便在多个Leader同时出现时也可以保证广播数据的一致性。开发
而Raft协议走了彻底相反的一个思路:保证不会同时有多个Leader存在。所以Raft协议对Leader的选举作了详细的设计,从而保证不会有多个Leader同时存在。相反,数据广播的过程则变的简单易于理解了。
2 - Raft的日志广播过程
为了保证数据被复制到多数的节点上,Raft的广播过程尽管简单仍然要使用多数派协议,只是这个过程要容易理解的多:
发送日志到全部Followers(Raft中将非Leader节点称为Follower)。
Followers收到日志后,应答收到日志。
当半数以上的Followers应答后,Leader通知Followers日志广播成功。
- 日志和日志队列
Raft将用户数据称做日志(Log),存储在一个日志队列里。每一个节点上都有一份。队列里的每一个日志都一个序号,这个序号是连续递增的不能有缺。
日志队列里有一个重要的位置叫作提交日志的位置(Commit Index)。将日志队列里的日志分为了两个部分:
已提交日志:已经复制到超过半数节点的数据。这些日志是能够发送给应用程序去执行的日志。
未提交日志:还未复制到超过半数节点的数据。
当Followers收到日志后,将日志按顺序存储到队列里。但这时Commit Index不会更新,所以这些日志是未提交的日志,不能发送给应用去执行。当Leader收到超过半数的Followers的应答后,会更新本身的Commit Index,并将Commit Index广播到Followers上。这时Followers更新Commit Index,未提交的日志就变成了已提交的日志,能够发送给应用程序去执行了。
从上面的解释咱们能够知道,日志队列中已经提交的日志是不可改变的,而未提交的日志则能够被更新成其余的日志(在Leader发生变化时会发生)。
Raft的日志队列和《由浅入深理解Paxos协议(1)》中的“预存储队列+存储队列”功能是同样的,可是巧妙的合并到了一块儿。这样作解决的问题和《由浅入深理解Paxos协议(1)》中“预存储队列+存储队列”解决的问题也是同样的,这里就再也不叙述。
3 - Raft的Leader选举
Raft称它的Leader为“Strong Leader”。Strong Leader 有如下特色:
同一时间只有一个Leader
只能从Leader向Followers发送数据,反之不行。
下面咱们看一下Raft经过哪些机制来实现Strong Leader。
- 多数派协议
为了保证只有一个Leader被选举出来,选举的过程使用了多数派协议。这样很好理解,当一个Candidate(申请成为Leader的节点)请求成为Leader时,只有半数以上的Followers赞成后,才能成为Leader。投票过程以下:
当发现Leader无响应后(一段时间内没有日志或心跳),Candidate发送投票请求。
Followers投票。
若是超过半数的Followers投了票,则Candidate自动变成Leader,开始广播日志。
- 随机超时机制
和《由浅入深理解Paxos协议(1)》中提到问题同样,这里也会发生多个Candidate同时发送投票请求,而致使谁都不可以获得多数同意票的状况,有可能永远也选不出Leader。为了保证Leader选举的效率,Raft在投票选举中使用了随机超时的机制:
在每一个Followers上设定的Leader超时时间是在一个范围内随机的。这样能够尽可能让Followers不在同一时间发起Leader选举。
每一个Candidate发起投票后,若是在一段时间内没有任何Candidate称为Leader则,须要从新发起Leader选举。这段等待的时间,在每一个Candidate上也是随机的。从而保证不会有多个Candidate同时从新发起Leader选举。
虽然说是随机的超时时间,可是也有个范围,过小或者太大都会影响系统的可用性。过小会致使过多的选举冲突,太大又会影响系统的平滑运行。在Raft的论文中,做者将这个超时时间称为electionTimeout,并给出了合理的范围,公式以下:
broadcastTime ≪ electionTimeout ≪ MTBF
“≪”表明数量级上的差别(10倍以上)。
- 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必定包含了全部已经提交的日志。
为何不是检查Commit Index?
由于Leader故障时,颇有可能只有Leader的Commit Index是最大的。
若是图中的Candidate A被选举为Leader,那么日志4就会被丢弃。可是日志4已经在原来的Leader上提交了,所以必须被保留才行。因此只能让日志长度更长的Candidate B选为Leader。这种作法有可能把原来Leader没广播完成的日志(图中的日志5)接着广播完成,这没有什么关系。
- Followers日志补齐
当Leader故障时,Followers上的日志状态是不同的,有长有短。所以新的Leader选出后,首先要将全部Followers的日志补齐才行。所以Leader要询问Followers的日志长度,从最小的日志位置开始补齐。
- Followers未提交日志的更新
新Leader的日志必定包含全部已经提交的日志。但新Leader的日志不必定是最长的,那些新Leader没有的日志,必定是未提交的日志,所以能够被更新,没有关系的。Leader只须要从本身的当前位置开始插入日志并广播出去就能够了。Followers会用新的日志去更新指定位置上的日志。
4 - 新旧Leader的交替
新的Leader选出后,开始广播日志。这时若是旧的Leader故障恢复了(好比网络临时中断),而且还认为本身是Leader,也会广播日志。这不就致使了同时有两个Leader出现吗?是的,Raft也没办法让旧的Leader不发日志,可是Raft有办法让Followers拒绝旧Leader的日志。
- Term
Raft将时间划分为连续的时间段,称为Term。 Term是指从一次Leader选举开始到下一次Leader选举的一段时间。这段时间内只能有一个Leader被选举成功,并负责管理系统或者没有Leader选出。
Raft论文上的Term图片
每一个Term都有一个惟一的数字编号。全部Term的数字编号是从小到大连续排列的。
- 做废旧Leader
Term编号在做废旧Leader的过程当中相当重要,但却十分简单。过程以下:
发送日志到全部Followers,Leader的Term编号随日志一块儿发送。
Followers收到日志后,检查Leader的Term编号。若是Leader的Term编号等于或者大于本身的当前Term(Current Term)编号,则存储日志到队列而且应答收到日志。不然发送失败消息给Leader,消息中包含本身的当前Term编号。
当Leader收到任何Term编号比本身的Term编号大的消息时,则将本身变成Follower。收到的消息包括:Follower给本身的回复消息、新Leader的日志广播消息、Leader的选举消息。
- Raft的实现
论文中做者仅用了两个RPC就实现了Raft的功能,它们分别是:
RequestVote() Candidate发起的投票请求
AppendEntries() 将日志广播到Followers上
AppendEntries()除了广播日志外,做者还巧妙的用它实现了如下的功能:
发送心跳(heartbeat): 没有客户日志时,经过AppendEntries()广播空日志,当作心跳。
发送Commit Index:当Commit Index更新后,能够随着当前的日志经过AppendEntries()广播到Followers上。若是没有客户端日志,则能够随着心跳广播出去。