
Raft算法简述java
Raft概要
Raft算法是一种用于管理Replicated Log的共识算法,其算法结果与效率与Multi-Paxos一致,可是在算法的设计结构上与Paxos算法是不一样的,Raft算法更加便于理解和实现,主要有如下两点:node
Raft算法将leader选举,日志复制以及安全共识要素分离出来,并强化一致性,即强leader模型,集群服务节点以leader节点为主来实现实现一系列值的共识和各节点日志的一致,强leader这样作的目的是为了减小集群其余服务节点必须考虑的状态,换言之,就是若是集群服务存在leader节点,那么对非leader节点服务而言,我只考虑leader节点是存活的状况下,保证我本身当前服务节点状态不会发生变动.python
其次,Raft算法提供成员变动机制,严格来讲是引入单节点变动机制来解决集群中存在“脑裂”状况(集群出现多个leader节点)git
Raft集群节点状态
Follower状态: 集群服务中普通的节点,主要职责有如下方面: 一是负责接收和处理leader节点的消息;二是负责维持与leader节点之间的心跳检测,以感知leader节点是处于可用状态;三是经过心跳检测获取leader节点不可用状态时,将会推荐本身做为候选节点而发起投票选举操做.github
Candidate状态: 当前集群节点为候选leader服务节点,将会向集群其余服务节点发起RPC消息的投票请求,通知其余集群服务节点来进行投票,若是超过半数投票那么当前服务节点将晋升成为leader节点.redis
Leader状态: 经过选举投票成为leader节点,此时主要的工做职责有三:一是负责接收客户端的全部写入请求,包括集群服务节点的写入请求也将会转发到leader节点来处理;二是同步client的数据操做日志(binlog/oplog)到各个follower服务节点,以保证数据的一致性;三是发送心跳检测以便于各个follower节点可以感知到leader节点是处于可用状态而不会发起投票选举操做.算法
Raft算法参考学习
分布式键值对存储系统Etcd: https://etcd.io/docs/v3.4.0/数据库
基于Go语言实现的分布式注册与配置中心Consul: https://www.consul.io/docs安全
Raft开源产品总览: https://raft.github.io/微信
Raft学习指南: http://thesecretlivesofdata.com/raft/
Raft算法核心原理
集群强Leader选举
leader选举要素
任期Term: leader选举存在任期Term,每次完成leader选举会更新一次任期Term.
超时Timeout: 具有两种含义,即包含leader心跳检测超时以及候选节点等待选举结果超时.
leader选举过程
-
初始化状态

-
投票请求

投票请求响应
当候选服务节点A发起RPC投票请求的时候,会在当前的服务节点设置一个等待选举结果的随机超时时间timeout,那么存在两种状况:
若是是在timeout时间内,集群的服务节点B以及C接收到候选节点A的RPC投票请求而且此时尚未接收到其余服务节点的投票请求,那么就会更新当前的任期编号为1,同时将投票给A服务节点并给予响应,即:

-
若是在超时时间内没有得到半数投票,那么原先的选举会失效并将会从新发起投票选举,若是未超时,A服务节点接将收到其余服务节点投票响应并为本身的选票进行相应的计算增长,即:

-
A节点得到半数投票成为leader节点

日志复制
日志项Log Entry
日志项属性

由上图可知,日志实体(log Entry)主要包含如下几个属性:
日志执行指令(cmd):客户端服务发起事务请求操做并经过leader服务节点的共识模块输出的一条持久化到leader服务节点所在的状态机上的指令.
日志索引值(logIndex): 日志项对应的整数索引值,用来标识日志项的,是一个连续的、单调递增的整数号码,而且当前的日志项(logEntry)不会改变其在日志(log)下的位置
日志任期编号(term): 建立当前日志项(logEntry)的leader节点当前所处的任期编号term,主要来保证appendEnrty到日志log的一致性检查.
日志项复制原理
-
客户端服务client service向Raft集群服务发起事务请求操做,将转发由Raft集群的leader节点进行事务请求的写入以此来保证Raft集群服务的共识问题,假设客户端服务发起写请求操做 set x=10
,即:

-
leader节点接收到事务请求操做,将请求提交给leader节点服务下的共识模块进行事务操做并输出cmd指令以RPC的方式进行AppendEntries(复制日志项)到其余服务节点中,即:

-
当follower节点将接收到leader节点RPC的appendEntries(日志复制)进行持久化后,将会返回给leader节点,而leader节点若是接收到大多数follower节点的RPC日志复制成功的响应,那么就会将当前的log应用到leader节点的状态机并给予客户端的响应.即:

这个时候leader节点若是有新的RPC日志项复制抑或是发起heartbeat心跳检测,
RPC-AppendEntries&Heartbeat
会携带当前最大的且即将提交的index
到follower节点,follower节点会进行一致性检查流程并将日志项提交到本地状态机以保证与leader节点的日志数据一致.即:

-
整个RPC日志复制流程以下:

最后,关于Raft算法的日志复制,能够类比数据库的主从复制架构来思考,上述的提交日志能够理解为binlog或者是oplog,那么每次发起的RPC日志复制抑或是心跳检测都会携带日志最大且即将提交的索引值index,经过binlog或者是oplog将数据更新到节点的状态机上.
日志的一致性检测
一致性检测原理

在上述图中,咱们看到leader节点与follower节点的日志数据存在不一致,咱们知道Raft算法是属于强leader模型,一切以“leader”为主,所以日志复制也不例外,一旦出现不一致,那么follower节点会进行一致性检查并以leader发送的RPC日志为主将不一致性的数据强制更新为与leader节点日志一致,而对于leader节点的日志是不会覆盖和删除本身的日志记录.也就是说对于raft算法的一致性检查原理主要包含如下步骤:
leader节点经过RPC日志复制的一致性检查,查找与follower节点上与本身相同的日志项最大的索引值,这个时候follower节点的索引值以前的日志记录与leader是保持一致的,而以后的日志将产生不一致.
leader节点找到与follower节点相同的索引值并将索引值以后的日志记录复制并经过RPC发送到follower节点,强制follower节点更新日志数据不一致的记录.
一致性检查流程
以上面的日志记录为例,leader节点当前的索引值为8,而follower节点A与follower节点B索引值分别为5和8,这个时候假设leader节点向集群follower节点经过RPC发送新的日志记录抑或是心跳消息.此时follower节点A与B将会进行一致性检查.
-
若为RPC复制新的日志项,其检查流程以下:

若为心跳检测,其检查流程以下:

index=9
或者
index=8
的日志项更新,因而对于leader节点将向后递减从新发送新的RPC复制日志到follower节点,即:

小结
-
leader节点经过日志复制RPC消息,将当前最新且uncommited的日志索引值发送到Raft集群的follower节点上,假设此时的消息索引值 index=rpcIndex
-
Raft集群的follower节点接收到日志复制的RPC消息,会检查上一个日志索引值preIndex(index=rpcIndex-1)对应的日志项entry中的index,term以及cmd的属性是否匹配,若是不匹配则会拒绝当前index(rpcIndex)的entry更新并返回失败消息给leader节点,若是成功则持久化当前日志项并返回成功消息给leader节点(一致性检查). -
leader节点接收到follower节点的失败消息,因而递减当前的index`(rpcIndex-1),再次发起新的日志RPC复制消息到follower节点上 -
这个时候follower再次进行一致性检查,若是匹配那么返回成功给leader节点,此时leader节点就会知道当前follower节点的数据索引值(rpcIndex-2)与leader节点的日志是一致的. -
最后leader节点再次发起日志复制的RPC请求,复制并更新该索引值(rpcIndex-2)以后的日志项,最终实现leader节点与follower节点的日志一致性.
成员变动
集群成员变动问题
集群中leader节点崩溃与恢复

从上面咱们能够看到在一个Raft集群服务中,若是leader节点发生不可用,那么剩下的follower节点将会从新进行选举,假设此时选举B做为leader节点,那么当原有的leader节点恢复正常的时候,此时集群存在两个“leader”节点,如何解决?
-
集群扩容

从上述能够看到,在实现集群服务节点的扩容时,若是新加入的服务节点恰好碰上原有的集群服务发生网络分区,致使C与B,A节点失去联系,而在C节点恢复的时候与刚加入的服务节点D&E组成一个新的Raft集群(D&E与原有的服务节点属于同一个区域内);其次若是是新加入的两个节点与原有的服务节点不属在同一个区域,那么当前raft集群扩容就存在跨区域的集群,跨区域必然会存在网络不可靠的因素,所以一旦两个区域发生网络分区错误,那此时新区域下的D&E以及原有的区域集群节点分别组成了一个Raft集群,此时就会面临双leader节点问题.
从上述的问题分析中,咱们都看到集群服务可能出现多个leader节点,这就违背了Raft算法的强leader且惟一性的特征,而对于Raft算法解决这类集群成员节点变动则是经过单节点变动来解决集群的“脑裂”问题.
单节点解决成员变动
在Raft算法的论文中关于集群成员节点的变动存在着一个集群的配置属性,即在了解单节点变动方式以前,咱们须要对集群cluster的配置有一个基本的认知(与ES集群类似)
集群配置
在一个稳定的Raft集群服务中,存在着如下的一个leader以及两个follower节点,follower节点的配置须要从leader节点同步进行更新,因而在这里咱们关注leader节点的配置便可,也就是整个集群的一份配置.对于ES集群而言,其配置信息以下:

引入集群配置的目的
-
其一,咱们能够想到的方案是停更集群服务的执行,更改集群的配置再重启生效,可是显然在当前互联网应用中是不容许也是不被采纳的,极大影响用户体验,容易形成用户流失; -
其二是重启集群服务的步骤存在着误操做步骤,容易致使上线服务时出现不可预知的错误.
单节点变动原理
-
假设现有的Raft集群服务配置为C[n1,n2,...],此时服务集群加入一个节点Xm,同时对应的配置nm,这个时候leader节点监听到有新的节点加入读取节点配置nm并更新到当前的leader配置C中,设新配置为C1,即[n1,n2,...nm],并向新节点同步数据log. -
其次leader节点更新配置以后C1并发起日志复制的RPC请求消息到集群服务的各个节点,集群服务节点接收到RPC消息请求并将新的配置应用到本地状态机中,至此完成了单节点变动.
解决成员变动分析
-
崩溃恢复过程
-
Raft集群的leader节点发生不可用,原先具有leader选举权的follower节点进行leader选举,最终节点B成为新一轮的leader,即以下:

-
这个时候若是A节点恢复并从新加入到现有的Raft集群中,那么利用单节点变动原理,leaderB更新配置并将log同步覆盖更新节点A,此时A节点天然做为follower节点,最后leader节点B经过日志复制的RPC向集群服务更新配置最终保证集群的强leader模型,即:
-
扩容解决“脑裂”问题

在现有的Raft集群中加入节点D,此时集群raft_cluster监听到节点D加入集群,此时leader节点更新集群的配置并向节点D同步日志数据log,最后更新集群配置以后再向Raft集群服务节点发起日志复制的RPC请求消息同步最新的集群配置信息从而保证Raft集群的强一致性.
集群跨区域“脑裂”问题(扩展)
集群脑裂
初始化状态,只有华南区域部署Raft集群服务,但因业务需求缘由,须要在华北地区增长机器节点,并加入到现有的Raft集群服务,即:
-
若是两区域网络没有发生分区错误,按照上述单节点变动原则,最终Raft集群配置以及节点状态以下:

若是两区域的网络发生分区,那么就会致使Raft集群服务在不一样的区域中产生两个leader节点,即:
-
华南区域leader节点发生不可用,而在从新选举的过程当中,区域产生分区错误,致使Raft集群服务出现两个leader节点,即:

对于3&4问题,属于跨区域的集群的“脑裂”问题(ES集群也存在一样的问题)
如何解决集群脑裂问题
脑裂产生的缘由
网络发生分区错误: 机器宕机抑或是网络拥堵超时
每一个节点具有成为leader节点资格.
脑裂解决
基于上述的现象,发生网络分区错误最有可能的缘由就是网络超时,因而能够针对分区域的不一样节点设定对应的超时时间,好比能够将华北区域的节点超时时间变长(等待心跳检查以及选举leader的超时时间)
其次能够将node节点下降权限,仅做为提供数据服务,不具有竞选leader资格(即没有投票资格),假设当前集群节点个数为n,根据反证法以及投票知足大多数原则(理想状态不考虑随机超时时间)可知最小能够配置具有成为leader节点个数为
(n/2)+1
而且是在处于同一个区域下,即:

Raft算法分析小结
节点状态变化
Raft算法集群节点服务的初始化状态均为Follower节点,而且每个Follower节点具有成为Leader节点的资格,也就是说能够同时具有存储数据以及集群Leader的特征,其次Follower节点都拥有一个随机等待leader节点发起ping心跳检测的超时时间timeout
成为候选节点以后就会更新任期Term并随机设置对应的投票请求超时时间,若是在指定的超时时间内超过半数投票,那么就会晋升成为leader节点,并周期性地向集群从服务节点发起心跳检测以免Follower节点成为候选节点发起leader投票选择.
主观下线,即Raft算法集群服务节点中有一个Follower节点超时没有获得leader节点的心跳检测请求,那么就会为本身进行投票,此时会晋升成为候选节点,又称为单节点的主观下线,即凭借单节点服务设置的超时时间timeout来判断leader服务是否可用.(redis的sentinel集群服务的sdown)
客观下线(不属于Raft算法),即集群半数以上的Follower节点没有在超时的时间内获得leader节点的心跳检测请求(或者说集群大多数节点认为leader节点不可用)而从新发起投票选举请求.(redis的sentinel集群服务的odown)
节点之间的通信
-
leader投票选举请求: 经过候选节点发起RPC的投票请求,集群其余节点在指定的timeout内接收到投票请求并按照先来先得的顺序进行投票. -
日志复制请求: 集群服务的leader节点接收到事务操做执行事务指令并发起RPC请求对事务指令复制到事务操做日志log中并同步到集群其余服务节点 -
leader与follower节点心跳维持: leader节点发起RPC并携带任期Term信息的心跳检测,以此来维持集群服务leader节点与follower节点之间的健康检测状态,保证集群服务的可用性.
共识与数据一致性
Raft算法架构

共识问题
什么是共识
简而言之,就是不一样进程p对分别输入一组u的数据,经过相同的程序处理逻辑来保证其输出值最终都是v.即:

-
对于Raft集群存在哪些共识问题
-
leader的选举
-
事务操做
数据一致性
什么是数据一致性
Raft集群服务对外提供数据的读取始终保证一致性,即对Raft集群以外的服务,简称为客户端服务client service,client service多个节点node向Raft集群服务发起读取请求,那么要保证client service的每一个节点node读取到请求数据都是一致的.
如何保证一致性
当Raft算法已经选举Leader节点以后,为了保证Raft集群中的数据一致性,Raft算法采起强制的Leader策略,将客户端的写入操做更新到leader节点的日志文件中,并以RPC通信的方式复制到Raft集群的从服务节点中,从服务节点从操做日志来增量更新数据从而保证leader与follower节点数据的一致性.
日志复制异常
当leader节点发起复制日志的RPC请求消息到Raft集群中各个follower节点,若是此时leader节点发生不可用,那么此时会有如下几种状况(前提是leader节点的日志已经写入成功,leader节点恢复以前):
其一follower节点大多数接收到复制日志的请求并执行成功;
其二follower节点只有少数接收到复制日志的请求并执行成功;
其三follower节点没有接收到复制日志的请求;
leader任期Term
任期特征
Raft算法中任期Term包含时间段以及编号,而且会影响leader选举和请求的处理
Raft算法中leader节点的任期编号具有单调性,且为正整数递增
任期的更新
当Raft集群服务中存在follower节点发现leader节点不可用的时候,就会成为candidate节点,并为当前节点的任期自增1,而后将本身的任期编号Term以参数的形式携带在投票请求的RPC中向其余服务节点发起新一轮任期leader节点的投票选举.
任期与投票选举
Follower节点接收到Candidate节点的任期投票,若当前的follower节点任期比投票选举的任期小且是在超时的时间范围内,那么当前Follower节点就会为发起投票的请求节点进行选举并给予响应
Follower节点接收到Candidate节点的任期投票,若当前的follower节点任期比投票选举的任期大,那么当前的Follower节点将会拒绝投票请求
若是Raft服务的leader节点因为发生不可用,而同时在不可用期间Raft集群已经经过选举产生新的leader节点服务,此时当原先的leader服务节点恢复健康状态时,因为会接收到leader节点的心跳检测以及任期编号等信息,发现当前的任期编号比接收到任期编号小,那么这时候原先的leader节点就会更新本身的任期并成为集群服务的follower节点(单节点变动).
选举规则
周期检测
leader节点会周期性向follower节点发起心跳检测(携带投票选举的Term),以免follower节点成为候选节点进行投票选举.
节点具有成为leader权利
Raft集群服务的follower节点在指定的时间内没有接收到leader节点的心跳检测消息,那么此时会认为leader节点不可用,会推荐本身成为候选节点并自增任期编号,同时发起leader选举.
大多数原则
follower节点成为candidate节点发起的leader选举,若是得到集群服务大多数服务节点的投票响应,那么当前follower节点就会成为leader节点
具有持续性
Raft集群经过每次选举产生新的leader,同时会对应新的Term,每一轮新Term都具有持续性,也就是说在一个任期Term内,只要是leader节点是可用的,那么就不会发生新的一轮选举,可用性是经过周期性检测来保证.
一次投票&先来服务

知足完整性日志
日志完整性高的follower节点拒绝投票给日志完整性低的候选节点.假设如今Raft集群服务提交的日志log以下(其中包含日志索引logIndex,实体log包含任期编号term以及变动的指令):

logIndex=5
的候选节点向一个日志索引下标为
logIndex=8
的follower节点发起投票请求,这个时候follower节点将会被拒绝,主要缘由是后者的
logIndex
更大,相对地,其日志完整性更高(可类比于kafka的ISR副本机制)
Raft算法中的timeout含义
-
follower节点等待leader节点发起的心跳检测的随机超时时间 -
每次发起新的一轮选举时,候选节点发起投票请求并等待投票请求响应的随机超时时间.
关于raft算法的论文,感兴趣的同窗能够关注下面公众号并回复“raft”获取,最后感谢花时间阅读,如对你有用,欢迎转发或好看,谢谢!

老铁们关注走一走,不迷路

本文分享自微信公众号 - 疾风先生(Gale2Writing)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。