Raft算法

背景
分布式是一组独立的计算机,利用网络协同来工作的系统。客户看来就如同一台机器在进行工作。在信息爆照时代,传统的单机以无法应对需求了,分布式具有可扩展性强,可用性高,廉价高效的特点称为了服务发展的趋势。
问题
解决了高需求的问题,但同时也引入了新的问题,需求与服务之间的矛盾并没有彻底解决只是转换成可以分解的问题。CAP理论是分布式理论的基础,它提出三个要素:

Consistency(强一致性):任何客户端都可以读取到其他客户端最近的更新;
Availability(可用性): 系统一直处于可服务状态。
Partition-tolenrance(分区可容忍性):单机故障或网络分区,系统仍然可以保证强一致性和可用性。

很明显这三个是存在需求的矛盾区,一个分布式系统最多只能满足两个,P是必不可少的,AP保证服务的高可用性,但是对于数据的一致性问题,如金融系统中显现出数据丢失的风险。CP保证了数据的一致性,但是损失了性能。传统的主备强同步模式虽然可以保证保证数据一致性,但是对于网络分区或者网络故障就无法使用。paxos和raft等一致性算法的提出,弥补了这部分缺陷,它们在保证CP的前提下,只要大部分机器可以使用,就认为整个系统是可以使用的。
在消息中间件领域,金融支付场景是非常需要强一致性和高可靠性,目前主流的消息中间件存在或多或少的问题:

 RabbitMQ:一个请求需要在所有节点上处理2次才能保证一致性,性能不高。Kafka:kafka主要应用在日志、大数据等方向,少量丢失数据业务可以忍受,但不适合要求数据高可靠性的系统。RocketMQ:未采用一致性算法,如果配置成异步模式可能丢失数据,同步模式下节点故障或网络分区都会影响可用性。SQS:只提供最终一致性,不保证强一致性。
针对这些问题我们研究一下基于Raft的一致性算法。

Raft算法基本原理

概述
Raft算法主要包括选举和日志同步。

第一阶段:选举

 从集群中选择一台机器作为Leader。

第二阶段:日志同步:

 选举出的Leader接收客户端的请求,将其转换为Raft日志;Leader将日志同步到其他的节点,当大多数节点写入成功后,标志为committed,已经committed的日志不再发生更改;Leader发生故障时跳到第一阶段进行选举。

Raft算法的术语解释:

  Term : 当前的节点所处的周期,可以当作某一个帝王统治时期;votedFor:当前Term的投票信息,每个节点在一个Term上只能投一次票;Entry:raft日志的基本单元,包括index,user_dara,term,其中index在日志文件中顺序分配,term是创建该entry所处的term,user_data是业务数据;State:节点状态(角色)(Leader、Candidate、Follower之一)CommittedIndex:已提交日志的索引;State Machine:状态机,业务模块;ApplyIndex: 已应用的日志索引;ElectionTime:选举超时时间。

节点之间通过rpc来进行通信,发送方在发送时会携带自身的term,接收方在处理时按照两种流程:

   1、RPC中的Term大于自身的Term,更新自身的Term,votedFor更新为null,State转换为Follower。2、当RPC中的Term小于自身的Term,拒绝请求,相应包中携带自身的Term。

选举
Raft算法属于强Leader模式,只有Leader可以处理请求,Leader通过心跳维持自身地位,除非Leader故障或者网络异常,否则保持Leader不变。选举是为了从集群中选出合适的Leader.

选举流程

1、节点初始状态均为Follower,Follower只能被动接收来自Leader的请求,如果ElectionTime到期仍没有收到来自Leader的AppendEntry RPC,就认为没有Leader,State转为Candidate;
2、Candidate在集群中广播RequestVote RPC,尝试竞选Leader,其他节点收到后首先判断是否同意本次选举,并将决定返回给Candidate,当收到大多数节点的赞同票时,Candidate称为Leader;
3、Leader接收客户端请求,将其转为Entry 追加到日志,同时AppendEntry RPC同步日志Entry到Follower。

示例:
下面通过一个案例详细说明选举流程。

1、初始状态:3个节点组成的集群,初始均为Follower状态,图中方格部分代表节点的raft日志。
2、发起选举:节点1选举定时器先到期,转为Candidate,Term自增,更新voteFor=1(投票给自己),接着广播RequestVote RPC给集群中其他节点,RequestVote RPC会携带节点的日志信息。
3、响应选举:节点2和3收到RequestVote RPC后,根据RPC规则,更新term和voteFor(term:6 , voteFor:null );然后判断是否同意本次选举,如果已投票给别人,则拒绝本次选举(这里voteFor:null 未投票),如果RequestVote RPC中的日志没有自身全,也拒绝,否则同意(这里节点1的日志比2、3都更全);最后通过voteReply RPC响应投票结果。
4、选举完成:由于节点2和3都同意本次选举,所以节点1在收到任何一个的voteReply RPC便转为Leader(大多数同意即可)。


注意:选举超时:
在选举时可能会发生两个节点的选举定时器同时到期,各自的到一半的选票,导致选举失败。如果选举定时器是固定值发生若干个节点选举定时器同时到期概率很大,将选举到期定时器设置为随机值可以很好的降低冲突的概率,随机值设置过大会导致长时间没有Leader,设置过段容易造成选举次数过于频繁,一般建议设置300-600毫秒。

日志同步
在选举结束后,Ledaer开始接收来自客户端的请求,将请求封装为Entry,追加到Raft的日志文件末尾,之后同步Entry到其他子节点,当大多数节点被写入成功,该Entry被标记为committed,Raft算法保证被committed的日志不再会发生更改。

**日志同步具体流程:**1、Leader为每个节点维护NextIndex、MatchIndex,NextIndex表示待发往该节点Entry index,MatchIndex表示该节点已匹配的Entry Index。同时为每个节点维护CommitIndex表示该节点已提交的Entry index。转为Leader会将所有节点的NextIndex设置为自己的最后一条日志的index,MatchIndex为0,自己的CommitIndex设置为0。2、Leader不断将user_data装维Entry追加到日志文件尾,Entry包含index,user_data,term,其中index在日志文件中从1开始排,Term为当前Leader的Term。   3、Leader通过AppendEntry RPC将Entry同步到节点,节点收到后开始校验该Entry之前的日志是否匹配,匹配成功直接写入Entry,失败删除不匹配的日志,返回失败。校验是通过AppendEntry RPC携带待写入的上一条Entry的信息。4、当节点写入成功,Leader更新该节点的MatchIndex,NextIndex,继续发送后续的Entry。如果大多数节点写入成功即MatchIndex大于CommitIndex,则更新CommitIndex。Follower返回失败时回退NextIndex继续发送,直到Follower返回成功。5、Leader每次更新时AppendEntry RPC时,会携带Leader当前最新的CommitIndex,节点判断自身的CommitIndex与Leader的CommitIndex更新为Min(LastLogIndex,LeaderCommitIndex)。注意:每次日志写入均需要刷盘以保证宕机时数据不丢失。

示例

 1、初始状态所有节点的Term=1,CommitIndex=2,接着Leader收到一条y←9的请求,转为Entry写入日志的末尾,Entry的index =3 term =1。2、Leader通过AppendEntry RPC同步该Entry给2个Follower,RPC中包含前一条Entry信息(index=2 term =1),Follower收到后首先校验前一条Entry是否与自身匹配(这里匹配成功),之后写入该Entry,返回Leader成功。3、Leader在收到Follower的回包后,更新相应节点的NextIndex和MatchIndex,这时大多数节点MatchIndex已经大于CommitIndex,所以Leader更新CommitIndex=3。4、Leader继续发送AppendEntry到Follower,此时由于没有新Entry,所以RPC中entry信息为空,LeaderCommitIndex为3。Follower收到后更新CommitIndex=3 (Min(3,3))。

注意:日志冲突
在日志同步的过程中,可能会出现节点之间的日志不一致问题。如:节点写日志过慢,Leader切换导致就Leader未提交的脏数据等情况。在Raft算法中,日志冲突以Leader中日志为准,删除节点中不匹配的日志。

如上图所示:
节点与Leader的日志都存在不一致问题,其中(a)、(b)节点日志不全,©、(d)、(e)、(f)有冲突日志。Leader首先从index=11(最后一条Entry index +1)开始发送AppendEntry RPC,节点均返回不匹配,Leader收到后不断回退。(a)、(b)在找到第一条匹配的日志后正常同步,©、(d)、(e)、(f)在这个过程中会逐步删除不一致的日志,最终所有节点的日志都与Leader一致。成为Leader节点后不会修改和删除已存在的日志,只会追加新的日志。

算法证明
Raft算法的两个核心:

      1、已提交的日志不会在更改(可靠性)2、所有节点上的数据一致(一致性)

证明:
Leader Completeness:给定Term上提交的日志一定存在于后续更高Term的Leader上。(日志不丢失)

   1)选举出的Leader一定包含当前已提交所有日志:已提交的日志存在于大多数节点上,而同意选举的前提是候选者的日志必须够全或够新。一个不包含已提交日志的节点必然不会得到大多数节点的选票(这些节点上都有已提交的日志,不满足日志足够全的前提),也就无法成为Leader2) Leader节点不修改和删除已存在日志(算法的约束)。

Leader Completeness可知已提交的日志不会再修改,业务的状态机依次取出Entry中的user_data应用,最终所有节点的数据一致。

Raft应用

 我们用State Matchine统一表示业务模块,其通过ApplyIndex维护以应用到的日志index:1)客户端请求发送Leader节点。2)Leader节点的Raft模块将请求转为Entry并同步到Follers。3) 大多数节点写入成功后Raft模块更新CommittedIndex。4)各节点上的State Matchine顺序读取ApplyIndex + 1 到CommittedIndex之间的日志,取出user_data,使用完更新ApplyIndex。5)Leader 上的State Machine通知客户端操作成功。6)循环以上步骤。


快照管理
在节点重启时,由于无法得知State Matchine当前ApplyIndex(除非每次应用完日志都持久化ApplyIndex,还要保证是原子操作,代价较大),所以必须清空State Matchine的数据,将ApplyIndex置为0,,从头开始应用日志,代价太大,可以通过定期创建快照的方式解决该问题。如下图所示

 1) 在应用完Entry 5 后,将当前State Matchine的数据连同Entry信息写入快照文件。2) 如果节点重启,首先从快照文件中恢复State Matchine,等价于应用了截止到Entry 5为止的所有Entry,但效率明显提高。3) 将ApplyIndex置为5,之后从Entry 6继续应用日志,数据和重启前一致。


快照的优点

  降低重启耗时:通过snapshot + raft log恢复,无需从第一条Entry开始。节省空间:快照做完后即可删除快照点之前的Raft日志

异常场景及处理

 Raft具有很强的容错性,大多数节点正常互联,即可保证系统的一致性和可用性,下面是一些常见的异常情况,以及他们的影响及处理:
异常场景 影响及处理
Leader故障 短暂不可服务,自动选举,客户端须重试
Follower或Candidate故障 无影响
网络分区 大多数互联时自动选举,伪Leade
节点重启 快照+raft日志自动恢复
大多数节点故障 不可用(保证强一致性)
选举冲突 暂时不可服务(随机选举时间保证极少出现该场景)

由此表格可以看出异常情况对系统的影响很小,即使Leader故障也能很短的时间回复,任何情况下都保证一致性。但是Leader故障时新Leader可能包含旧Leader未提交或已提交但未通知用户的的日志,由于算法对Leader日志不可修改的限制,所以这部分日志会被新Leader同步并提交,但由于连接信息丢失,客户端无法得知该情况,当发起重试后会出现重复数据,需要有幂等性保证。
另外Raft是围绕Leader展开,当出现网络分区时,可能出现伪Leader问题:

上述情况下,Leader与2个Follower网络异常,而2个Follower之间通信正常,由于收不到Leader的心跳,其中一个Follower发起选举并成为Leader,原Leader成为伪Leader,接下来我们分析该场景下是否会影响系统的一致性:
写一致性:发往新Leader的写请求可以被提交,而发往伪Leader的请求无法得到提交,只有一个Leader在正常处理写请求,所以不影响写一致性。
读一致性:如果读请求不经过Raft同步,那么当客户端的写请求被发往新Leader并执行成功后,读请求发往了伪Leader并得到结果,就会造成数据不一致。有两种方案可以解决该问题:

1. 读请求也经过Raft同步,这样就不会有不一致的问题,但会增加系统负载。
2. 读请求收到后,Leader节点等待大多数节点再次响应心跳RPC,接着返回结果。
因为大多数节点响应心跳,说明当前一定没有另一个Leader存在(大多数节点还与当前Leader维持租约,新Leader需要得到大多数投票)。

小结

Raft具有高一致性、高可靠性、高可用性、高性能等优点具体体现在:

 强一致性:虽然所有的节点日志非实时的,但是Raft算法保证Leader节点的数据最全,同时Leader节点处理所有请求,从客户端来看是一致的;高可靠性:Raft算法保证了已提交的日志不会被修改,State Matchine只应用了State Matchine的日志,所以当客户端收到请求接收成功,即代表数据不会在被修改。Committed日志在大多数节点上冗余存储,少于一半的磁盘故障数据不会丢失。高可用性:Raft算法原理中选举和同步日志只要大多数节点正常工作即认为可以处理,少量节点的问题不会影响系统的可用性,即使Leader节点故障,通过选举选出新的Leader也不会造成服务长时间不能使用。但是故障时可能造成数据重复,需要业务保证幂等性或去重。高性能:与必须将日志写入所有的节点相比,Raft只需要大多数即可,少量节点的写入缓慢不会造成系统延时。

强一致性算法Raft解析相关推荐

  1. 谈分布式事务一致性(2PC、3PC、TCC)、强一致性算法Paxos等关系

    一致性这个词重载的很厉害,在不同的语境和上下文中,它其实代表着不同的东西: 在事务的上下文中,比如ACID里的C,指的就是通常的一致性(Consistency) 在集群环境中,主从复制,如ZK(Pax ...

  2. 分布式事务探讨系列(九):一致性算法Raft

    一致性问题 在分布式系统中,一致性问题(consensus problem)是指对于一组服务器,给定一组操作,我们需要一个协议使得最后它们的结果达成一致. 由于CAP理论告诉我们对于分布式系统,如果不 ...

  3. 分布式一致性算法Raft

    导语 | 对于很多工程人员来说,Paxos算法不容易理解和落地实现.因此斯坦福学者提出了一个更易理解和实现的共识算法Raft.本文主要介绍Raft的基本原理.算法流程以及和Paxos的区别. 一.Ra ...

  4. 转盘抽奖php,使用PHP实现转盘抽奖算法案例解析

    这次给大家带来使用PHP实现转盘抽奖算法案例解析,使用PHP实现转盘抽奖算法的注意事项有哪些,下面就是实战案例,一起来看一下. 流程: 1.拼装奖项数组 2.计算概率 3.返回中奖情况 代码如下: 中 ...

  5. 一致性算法 - Raft

    一致性算法 - Raft Raft 状态 一个 Raft 集群包含若干个服务器节点:通常是 5 个,这允许整个系统容忍 2 个节点的失效,每个节点处于以下三种状态之一: follower(跟随者) : ...

  6. adaboost算法java_Adaboost 算法实例解析

    Adaboost 算法实例解析 1 Adaboost的原理 1.1 Adaboost基本介绍 AdaBoost,是英文"Adaptive Boosting"(自适应增强)的缩写,由 ...

  7. 分布式一致性算法Raft简介(上)

    最近看了Ongaro在2014年的博士论文<CONSENSUS: BRIDGING THEORY AND PRACTICE>的部分章节,对raft有了初步的理解.其中论文中提到用于教学的u ...

  8. 机器学习算法实现解析:libFM之libFM的训练过程之SGD的方法

    本节主要介绍的是libFM源码分析的第五部分之一--libFM的训练过程之SGD的方法. 5.1.基于梯度的模型训练方法 在libFM中,提供了两大类的模型训练方法,一类是基于梯度的训练方法,另一类是 ...

  9. 再说共识性算法Raft

    本文再聊下分布式共识算法Raft 文章目录 什么是共识 Raft简介 Raft简介 分布一致性 复制状态机 RAFT应用 Raft基础 选主 什么是选主 Raft 为什么要进行选主 日志复制 安全性及 ...

最新文章

  1. Android App的启动过程
  2. ps随机排列_漂亮!自然材料:人工可控微米级胶体粒子“堆积木”——粒子随心所欲的组装排列!...
  3. 20180827-Java网络编程
  4. Linux利用子命令m,Linux mdir命令
  5. Java Gradle入门指南之依赖管理(添加依赖、仓库、版本冲突)
  6. springboot细节挖掘(日志系统)
  7. CmD空格转义的三种方法,总有一种会解决问题
  8. 总结一下目标检测与跟踪
  9. python爬虫-- 爬取51job网招聘信息
  10. java毕业设计基于javaweb+mysql数据库实现的在线学习网站|在线课堂含论文+开题报告
  11. handlersocket mysql_[原创]MongoDB、HandlerSocket和MySQL性能测试及其结果分析
  12. 记一次axsi2调用 web services 提示 java.net.SocketException: Connection reset 的解决
  13. JDK8: Invalid byte tag in constant pool: 18
  14. 第一章 学习Java的建议
  15. 电脑计算机u盘启动不了桌面,开机进不了桌面怎么办?教你5招这样解决
  16. 跨行学Linux运维 通配符及Find
  17. 虚拟现实技术在物理光学的应用
  18. html中js隐藏div的高度,jQuery实现获取隐藏div高度的方法示例
  19. 互联网摸鱼日报(2023-03-08)
  20. OpenWRT编译失败问题解决(一)

热门文章

  1. ZYNQ_DMA控制BRAM读写的地址问题
  2. 淘淘商城第54讲——商品搜索之service层开发
  3. Linux如何添加路由
  4. imu_utils IMU内参标定工具 imu_utils安装教程 imu_utils使用教程
  5. windows10 cmd命令行添加php环境变量无效问题解决
  6. vue项目兼容ie浏览器(出现白屏)
  7. nodemcu的led显示驱动
  8. 洛谷---数学---数论
  9. 【闪电侠学netty】第6章 客户端与服务端双向通信
  10. 记一次异常Caused by: java.lang.ClassNotFoundException: cn.exrick.xboot.core.entity.XXX