Raft分布式一致性算法原理(选举和同步)

Stella981
• 阅读 348

Raft分布式一致性算法原理(选举和同步)

一. 背景

在集群环境下,很容易出现单节点故障的问题,那么我们就需要进行集群部署,但是当集群部署的环境下,我们如何保证工作有序的调度与通信并且保证一致性呢,当客户端发送一连串指令,我们需要在集群环境下,所有服务机器最终要保证一致性,而且在出现一系列异常并且恢复之后的情况下,仍然要保证 最终状态的一致性(状态机) ,于是就引出了分布式一致性协议。

在说到 分布式一致性协议 ,多数人第一个想到的就是大名鼎鼎的 Paxos 协议,因为我们最常用的 Zookeeper 采用的就是Paxos协议,只不过对Paxos进行了改造,而且Google的很多大型分布式系统都采用了Paxos算法来解决分布式一致性问题,但是由于Paxos对于初学者来说很不容易理解,Paxos的最大特点就是难,不仅难以理解,更难以实现,因此诞生了 Raft 分布式一致性协议,有意思的是,raft协议的两为创作者起初也是因为Paxos协议不容易理解和实现,于是Raft被迫营业了。

Raft协议 (共识算法) 是目前使用比较广泛的 具有一致性,高可用且去中心化的分布式一致性协议 ,比如现在比较流行的 etcd分布式存储仓库 组件就是使用raft协议实现分布式一致性的,在本文中主要介绍几个方面:

  • 复制状态机

  • raft的基本概念

  • raft主从选举原理

  • raft数据同步原理

二. 复制状态机

何为复制状态机,首先状态机就是每个节点都是具有状态的,且状态机是确定性的,不同的状态对应不同的角色和需要在该状态下的一系列操作,这个状态可能是临时的,可能是长时间保持的,但是状态机都是为了实现容错和一致性所设计的,那么复制状态机就是在状态机的基础上增加同步复制的功能,在同步复制的时候,每个节点可以在缓存或者磁盘保存同步复制的位置和顺序,该模型一句话描述就是: 多个节点上,从相同的初始状态开始,执行相同的一串命令,产生相同的最终状态。 所以状态机需要实现以下方法:

  1. 在多个独立的服务器上放置状态机的副本。
  2. 接收客户端请求,解释为输入到状态机。
  3. 选择输入的顺序。
  4. 在每台服务器上按所选顺序执行输入。
  5. 通过状态机的输出响应客户端。
  6. 监视状态或输出差异的副本

Raft分布式一致性算法原理(选举和同步)

这就是复制状态机的逻辑图,利用 state machinelog 实现复制和一致性。

三. raft的基本概念

1. 三个状态

  • leader(领导者)

    表示该节点是主节点,该节点主要处理与客户端的通讯和交互,还有与follower从节点的的数据同步与心跳机制的维护。

  • follower(追随者)

    表示该节点是从节点,该节点主要负责转发客户端的请求到leader节点,然后保持与leader节点的数据同步。

  • candidate(候选者)

    表示该节点是选举主节点的候选人,该节点状态下表示现在是选举主从节点的时间点,作为候选人,既有可能成为leader,也有可能成为follower,具体的选举原理,下文会详细介绍。

这三种状态的转换图:

Raft分布式一致性算法原理(选举和同步)

2. Term和vote概念

term属性表示投票的轮数,也可以理解是任期数,term用连续的数字进行表示。当然了,该属性一般会持久化到磁盘中,在所有节点刚开始的时候或者在重新选举的时候会进入candidate状态,进行选举投票,每一轮投票之后term都会加1,从下图可以看出来,不管这一轮选举是否成功选出leader,只要一轮结束,term都会加1。

Raft分布式一致性算法原理(选举和同步)

而vote属性就是代表各个节点投票的时候,该节点选择投的那个节点,每个节点在收到投票请求的时候,就是根据request vote进行统计和判断是否自己成为leader的依据和条件。

3. log与index概念

log日志每个节点都持有一份,并且是用来保证一致性和容错性的,首先来看下图。从这张图可以看出来,每个日志文件包含: index索引号,term任期号,以及term任期号下的数据信息,这么做的好处是为了节点启动后容错检查和主从同步的一致性 ,下文会详细介绍。

Raft分布式一致性算法原理(选举和同步)

4. 小结

在这一节主要介绍了raft协议的三种状态,term任期概念,以及log日志和index索引的概念,在介绍完这些基础概念之后,可能大家还是一头雾水,别着急,介绍这些基本概念是为了在介绍下面的主从选举和数据同步做铺垫,下面开始进入核心内容。

四. 主从选举原理

首先,我们需要知道的是,raft和zookeeper一样都采用 过半选举 的机制,所以我们要求raft的实现也是需要大于等于3个节点部署。现在,我们假设开始部署三个服务节点:

  1. 在这三个节点进行启动的时候, 首先进入candidate(候选者)的状态 ,然后进行投票选举,这个时候,所有的节点都会在本地创建一个 term属性,并且初始化值为0 ,表示还没有开始选举投票,然后每个节点都会设置一个 随机数的倒计时(150~300ms) ,当这个倒计时结束的时候,第一个倒计时结束的节点会率先发起投票,并且投票给自己,即携带(vote = 自己)的投票请求给其他两个节点。

Raft分布式一致性算法原理(选举和同步)

  1. 假设节点C的倒计时先结束,它率先发起了投票,则节点C将自己的term从0置为1,并且vote票投给自己,然后将这些参数包装发送给节点A和节点B,这个时候节点A和B还没有结束倒计时:

Raft分布式一致性算法原理(选举和同步)

  1. 节点A和B在收到C的投票请求之后,首先会比较收到的term和自己的term作比较,如果发现接收到的term数值比自己的大,则会放弃自己的投票,把自己的票(vote)投给第一个接收到的投票请求,即节点A和B在收到C的请求之后,比较发现自己的term为0,而接收到的term为1,则会把自己的vote置为节点C,然后响应请求:

Raft分布式一致性算法原理(选举和同步)

  1. 当节点C收到了节点A和节点B的响应(ack)和投票信息(vote),则会计算票数,由于过半选举的机制,一个节点需要成为leader节点,必须得到 ( n/2 +1 ) 的票数,这里n=3,即得到2票就可以当选leader节点,而C节点收到了节点A和B的两票,再加上自己的一票,总共 3票 ,则满足条件,当选leader,当选leader之后,向节点A和B发送心跳包,并且告诉它们自己 成为了leader节点 ,那么节点A和B就会 从candidate(候选者)状态转为follower(追随者) 状态,并开始正常工作流程了:

Raft分布式一致性算法原理(选举和同步)

  1. 正常情况下,选举就已经结束了,那么如果遇到异常情况呢,现在假设leader节点发生了故障宕机了,这里就要强调一个点,就是每个follower节点在和leader节点维持心跳的时候,都会设置一个 超时时间(timeout) ,当follower节点在等待leader节点的心跳请求的时候如果超过了这个超时时间,就会认为leader出现了异常,进而会 重新进入candidate状态 ,然后重新选举leader,可能会有人有疑问了,不是说必须奇数个节点吗,节点C宕机了,现在只有两个节点了,无法进行过半选举了啊,其实不是这样的,在这里follower节点已经知道了原来的leader节点已经无法通讯和投票,那么这一次的投票每个followr节点就会默认宕机的leader节点 总是会把票投给自己

Raft分布式一致性算法原理(选举和同步)

  1. 从上图可以看出来,现在假设节点B经过上面的流程然后成为了新的leader节点,这个时候节点A和节点B的term(任期数)从1增加到了2,但是由于节点C的宕机,节点C的term还是原来的1,这么做的好处就是当节点C重新启动的时候,发现自己的term已经落后了,那么就不能成为leader,就等待新的leader将新的数据同步给自己,不会出现数据丢失的情况。

  2. 可能有人就有疑问了,会不会出现多个leader的情况呢,现在假设增加一个节点,即现在有 4个node 节点,然后由于新节点的加入,所有节点进入candidate状态并开始 重新选举leader 流程,从下图可以看到,节点A和节点B的倒计时同时结束,然后term加1,并发起投票:

Raft分布式一致性算法原理(选举和同步)

  1. 由于A和B同时发起投票,而节点C首先收到的是节点A的请求,则C把票投给了A,而节点D首先收到的是节点B的请求,则D把票投给了B,这样就会出现节点A和节点B收到的投票数都不过半,那么就要 开启下一轮请求

Raft分布式一致性算法原理(选举和同步)

  1. 这里所有节点的term都增加到了5,说明又走了一遍投票流程,而且新的一轮选举出了节点C为leader:

Raft分布式一致性算法原理(选举和同步)

五. 数据同步原理

前面说到了复制状态机的其中一个作用就是保证容错和一致性,当一个主节点正常工作的同时,还要保证与从节点的数据同步,这里的同步不仅仅是简单的同步,还要保证某个节点宕机重启后还能拿到所有的数据达到一致性,那么下面开始介绍Log Replication同步的过程和步骤:

Raft分布式一致性算法原理(选举和同步)

  1. 看上图,现在有三个节点A,B,C,其中节点B是leader节点,A和C是follower节点,客户端向leader节点B发送数据(set 5),节点B在收到指令的时候开始开始做数据存储和数据同步,这个时候并不是直接存储,而是 采用2PC两阶段提交 ,先去请求所有的folloer节点,具体流程是:
    • leader节点B收到客户端数据请求,对所有follower发起2PC请求,
    • follower节点收到leader的请求,确认自己可以执行数据存储,响应ack给leader,
    • leader收到ack响应,会进行判断,如果有 过半的节点 返回了可以执行的ack,则 leader进行数据存储,并记录日志
    • leader 返回给客户端成功的响应
    • 然后leader再次向所有folower发送确认执行的请求,
    • follower 进行数据存储,并记录日志
    • 需要注意的是: 从节点也是接受客户端请求的,只不过不处理请求,会转发给leader节点处理。

如果大家对2PC两阶段提交协议还不是很清楚的,可以看这一片文章,传送门:分布式事务

  1. Raft甚至可以在面对网络分区时保持一致,大家在看到这种数据同步的问题的时候,都会考虑脑裂或者网络分区的问题,比如redis,但是raft是可以解决这个问题的:

    Raft分布式一致性算法原理(选举和同步)

    这里可以看到,5个节点A,B,C,D,E,一开始的时候节点B是leader节点,由于网络原因,产生了分区,其中A和B在一个区,而C,D,E在一个区,由于C,D,E所在的区没有leader节点,所以他们重新进行了选举,所以上面三个节点的term任期都是2,而A和B一直是正常工作的,所以他们的term还是原来的1,现在的情况就是,分区1:A和B保持心跳连接,B是leader;分区2:C和D和E保持心跳连接,C是leader。

  2. 然后客户端与服务节点正常通信,首先客户端向B发送set 5的指令,由于节点B和节点A 无法满足过半成功 的机制,所以无法进行数据存储和同步,也无法正确的响应客户端,而节点C,D,E在收到客户端set 8的请求后,是可以正常进行数据存储和同步的,并给客户端一个正确的响应:

Raft分布式一致性算法原理(选举和同步)

  1. 当网络正常了之后,这五个节点收到了其余节点的心跳,而由于节点A和B的term已经落后了其他三个节点,则他俩无法成为leader,所以两个 节点A和B都将回滚其未提交的条目,并与新的leader的日志相匹配,并且更新term为最新的2

Raft分布式一致性算法原理(选举和同步)

  1. 上面简单介绍一下数据同步的思想和步骤,下面再详细介绍数据同步是如何利用log进行容错和一致性的,我们知道,我们在考虑服务集群的情况下,总是认为服务是不稳定的,因此可能其他节点当宕机然后重启之后,会出现数据版本落后的情况,那在这种情况下,leader是如何保证让这个follower拿到所有落后的数据,然后达到一致性的呢,具体步骤如下:

    • leader在向follower节点发送心跳的时候,会带上 (term,log index - 1) 信息,

    • 当存在落后的follower节点收到心跳之后,会那自己的(term,log index)与收到的(term,log index)比较,

    • 当follower发现自己没有这一条日志,会响应给leader拒绝的消息,

    • 然后leader收到了拒绝的消息,会重新发一条消息,这次携带的是 (term,log index - 1 - 1) 的消息,

    • 然后重复上面的步骤,总的来说就是,当follower拒绝消息,leader则发送上一次的log给follower,依次递减,直到follower 补满所有的log日志 ,达到数据一致性的结果。

      Raft分布式一致性算法原理(选举和同步)

      根据上图的例子来看,现在的leader最新的日志文件保存的信息是:log index索引 = 8;term = 3;set x = 4,然后第一个follower节点现在是落后了三个log index,当这个follower启动后,leader与它同步数据的步骤如下:

      1. leader发送(3,7)的心跳给第一个follower,
      2. 第一个follower收到(3,7)的请求之后发现自己最大的log信息为(3,5),并不相等,则返回拒绝的响应,
      3. leader收到第一个follower的响应之后,然后向再次发送(3,6)的请求,
      4. 第一个follower收到(3,6)的请求之后发现自己最大的log信息为(3,5),并不相等,则返回拒绝的响应,
      5. leader收到第一个follower的响应之后,然后向再次发送(3,5)的请求,
      6. 第一个follower收到(3,5)的请求之后发现自己最大的log信息为(3,5),相等,则返回正确的响应,并接受创建(3,6)的请求,
      7. 以此类推,follower将日志补满。
  2. 到这里大家可能就清楚很多了,但是还是存在一个疑问,就是当客户端不仅仅是落后数据,而是其他的情况怎么解决,就比如说下图中,follower即有可能出现(a),(b)这种数据落后的情况,也可能出现例如©数据多出来的情况,还有可能出现(d),(e),(f)这三种数据异常的情况,raft是如何解决的呢,很简单,Raft 始终会认为领导者的日志总是正确的,即 所有的follower节点必须强制保持和leader节点一致数据 ,也就是说,不管出现下面的(a),(b),©,(d),(e),(f)中的哪一种情况,在正常工作的情况下,所有的日志数据都必须强制最终与leader节点一样,这也是可以理解的,因为作为leader并不知道你的异常数据是什么原因导致的,但是我既然是leader,那所有的follower必须与我的log保持一致,不然肯定会出现日志记录会无限堆积并出现混乱的状态等各种各样的问题。

Raft分布式一致性算法原理(选举和同步)

  1. 可能又有人提出疑问了,假如一个日志文件落后了很多版本的节点当选了leader,然后又强制所有的follower与自己保证一直,那这数据不全都丢失了吗,对于这个问题,raft肯定也想到了,所以当进行选举投票的时候,所有的节点都要携带自己的term和log index信息, 对于那些日志数据落后的节点,直接拒绝投票,也就是说,只有拥有最新的日志信息的节点,才有可能当选leader,比较规则是:比较term,term大的拥有选举权,如果term相等,则比较log index,大的拥有选举权,如果都相等,则公平竞争。

六. 总结

这一篇主要介绍了raft分布式一致性协议的思想和实现原理:

  • 首先是raft的目标是为了解决在集群环境下,需要所有服务节点保持容错和一致性的问题;
  • 其次是raft利用状态复制机的原理和思想实现了该目标;
  • 再然后就是利用复制状态机的思想进行主从选举和数据同步,以及容错的实现。

对于哪些开源项目是利用raft协议实现的,大家可以去看一下etcd开源项目,只不过etcd是使用go语言实现的,推荐大家去看一下阿里开源的nacos项目,是用java实现的,可读性还是很好的。

最后福利:raft流程演示

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这