工作流程

首先需要明确的是一致性算法的目标是什么,主要面对的问题是在只使用单个服务器时由于发生错误导致数据丢失等事情发生。解决这个问题的思路也很简单,就是备份,将操作重复到多个机器上就不怕单个机器出错了。但随之而来的就是,数据不一致、乱序等问题,一致性算法想要做到的是即使有结点出错,对外仍是一个完整的可以正常工作的整体。

Raft 是一个非拜占庭的一致性算法,即所有通信是正确的而非伪造的。N 个结点的情况下(N为奇数)可以最多容忍 (N−1)/2 个结点故障。

Raft 正常工作时的流程如上图,也就是正常情况下日志复制的流程。Raft 中使用 日志 来记录所有操作,所有结点都有自己的日志列表来记录所有请求。算法将机器分成三种角色:Leader、Follower 和 Candidate。正常情况下只存在一个 Leader,其他均为 Follower,所有客户端都与 Leader 进行交互。

所有操作采用类似两阶段提交的方式,Leader 在收到来自客户端的请求后并不会执行,只是将其写入自己的日志列表中,然后将该操作发送给所有的 Follower。Follower 在收到请求后也只是写入自己的日志列表中然后回复 Leader,当有超过半数的结点写入后 Leader 才会提交该操作并返回给客户端,同时通知所有其他结点提交该操作。

通过这一流程保证了只要提交过后的操作一定在多数结点上留有记录(在日志列表中),从而保证了该数据不会丢失。

领导选举

在了解了算法的基本工作流程之后,就让我们开始解决其中会遇到的问题,首先就是 Leader 如何而来。

初次选举

在算法刚开始时,所有结点都是 Follower,每个结点都会有一个定时器,每次收到来自 Leader 的信息就会更新该定时器。

如果定时器超时,说明一段时间内没有收到 Leader 的消息,那么就可以认为 Leader 已死或者不存在,那么该结点就会转变成 Candidate,意思为准备竞争成为 Leader。

成为 Candidate 后结点会向所有其他结点发送请求投票的请求(RequestVote),其他结点在收到请求后会判断是否可以投给他并返回结果。Candidate 如果收到了半数以上的投票就可以成为 Leader,成为之后会立即并在任期内定期发送一个心跳信息通知其他所有结点新的 Leader 信息,并用来重置定时器,避免其他结点再次成为 Candidate。

如果 Candidate 在一定时间内没有获得足够的投票,那么就会进行一轮新的选举,直到其成为 Leader,或者其他结点成为了新的 Leader,自己变成 Follower。

再次选举

再次选举会在两种情况下发生。

第一种情况是 Leader 下线,此时所有其他结点的计时器不会被重置,直到一个结点成为了 Candidate,和上述一样开始一轮新的选举选出一个新的 Leader。

第二种情况是某一 Follower 结点与 Leader 间通信发生问题,导致发生了分区,这时没有 Leader 的那个分区就会进行一次选举。这种情况下,因为要求获得多数的投票才可以成为 Leader,因此只有拥有多数结点的分区可以正常工作。而对于少数结点的分区,即使仍存在 Leader,但由于写入日志的结点数量不可能超过半数因此不可能提交操作。这解释了为何 Raft 至多容忍 (N−1)/2 个结点故障。

这解释了每个结点会如何在三个状态间发生变化。

任期 Term

Leader 的选举引出了一个新的概念——任期(Term)。

每一个任期以一次选举作为起点,所以当一个结点成为 Candidate 并向其他结点请求投票时,会将自己的 Term 加 1,表明新一轮的开始以及旧 Leader 的任期结束。所有结点在收到比自己更新的 Term 之后就会更新自己的 Term 并转成 Follower,而收到过时的消息则拒绝该请求。

在一次成功选举完成后,Leader 会负责管理所有结点直至任期结束。如果没有产生新的 Leader 就会开始一轮新的 Term。任期在 Raft 起到了类似时钟的功能,用于检测信息是否过期。

投票限制

在投票时候,所有服务器采用先来先得的原则,在一个任期内只可以投票给一个结点,得到超过半数的投票才可成为 Leader,从而保证了一个任期内只会有一个 Leader 产生(Election Safety)。

在 Raft 中日志只有从 Leader 到 Follower 这一流向,所以需要保证 Leader 的日志必须正确,即必须拥有所有已在多数节点上存在的日志,这一步骤由投票来限制。

投票由一个称为 RequestVote 的 RPC 调用进行,请求中除了有 Candidate 自己的 term 和 id 之外,还要带有自己最后一个日志条目的 index 和 term。接收者收到后首先会判断请求的 term 是否更大,不是则说明是旧消息,拒绝该请求。如果任期更大则开始判断日志是否更加新。日志 Term 越大则越新,相同那么 index 较大的认为是更加新的日志。接收者只会投票给拥有相同或者更加新的日志的 Candidate。

由于只有日志在被多数结点复制之后才会被提交并返回,所以如果一个 Candidate 并不拥有最新的已被复制的日志,那么他不可能获得多数票,从而保证了 Leader 一定具有所有已被多数拥有的日志(Leader Completeness),在后续同步时会将其同步给所有结点。

定时器时间

定时器时间的设定实际上也会影响到算法性能甚至是正确性。试想一下这样一个场景,Leader 下线,有两个结点同时成为 Candidate,然后由于网络结构等原因,每个结点都获得了一半的投票,因此无人成为 Leader 进入了下一轮。然而在下一轮由于这两个结点同时结束,又同时成为了 Candidate,再次重复了之前的这一流程,那么算法就无法正常工作。

为了解决这一问题,Raft 采用了一个十分“艺术”的解决方法,随机定时器长短(例如 150-300ms)。通过这一方法避免了两个结点同时成为 Candidate,即使发生了也能快速恢复。这一长短必须长于 Leader 的心跳间隔,否则在正常情况下也会有 Candidate 出现导致算法无法正常工作。

日志复制

在之前的工作流程章节中已经描述了日志是如何被复制到其他结点上的,但实际中还会发生结点下线,从而产生不一致的情况的发生,也是这一章我们将要讨论的内容。

前提

Raft 保证了如下几点:

  • Leader 绝不会覆盖或删除自己的日志,只会追加 (Leader Append-Only)
  • 如果两个日志的 index 和 term 相同,那么这两个日志相同 (Log Matching)
  • 如果两个日志相同,那么他们之前的日志均相同

第一点主要是因为选举时的限制,根据 Leader Completeness,成为 Leader 的结点里的日志一定拥有所有已被多数节点拥有的日志条目,所以先前的日志条目很可能已经被提交,因此不可以删除之前的日志。

第二点主要是因为一个任期内只可能出现一个 Leader,而 Leader 只会为一个 index 创建一个日志条目,而且一旦写入就不会修改,因此保证了日志的唯一性。

第三点是因为在写入日志时会检查前一个日志是否一致。换言之就是,如果写入了一条日志,那么前一个日志条目也一定一致,从而递归的保证了前面的所有日志都一致。从而也保证了当一个日志被提交之后,所有结点在该 index 上提交的内容是一样的(State Machine Safety)。

日志同步

接下来我们就可以看到 Raft 实际中是如何做到日志同步的。这一过程由一个称为 AppendEntries 的 RPC 调用实现,Leader 会给每个 Follower 发送该 RPC 以追加日志,请求中除了当前任期 term、Leader 的 id 和已提交的日志 index,还有将要追加的日志列表(空则成为心跳包),前一个日志的 index 和 term。

当接收到该请求后,会先检查 term,如果请求中的 term 比自己的小说明已过期,拒绝请求。之后会对比先前日志的 index 和 term,如果一致,那么由前提可知前面的日志均相同,那么就可以从此处更新日志,将请求中的所有日志写入自己的日志列表中,否则返回 false。如果发生 index 相同但 term 不同则清空后续所有的日志,以 Leader 为准。最后检查已提交的日志 index,对可提交的日志进行提交操作。

对于 Leader 来说会维护 nextIndex[] 和 matchIndex[] 两个数组,分别记录了每个 Follower 下一个将要发送的日志 index 和已经匹配上的日志 index。每次成为 Leader 都会初始化这两个数组,前者初始化为 Leader 最后一条日志的 index 加 1,后者初始化为 0。每次发送 RPC 时会发送 nextIndex[i] 及之后的日志,成功则更新两个数组,否则减少 nextIndex[i] 的值重试,重复这一过程直至成功。

这里减少 nextIndex 的值有不同的策略,可以每次减一,也可以减一个较大的值,或者是跨任期减少,用于快速找到和该结点相匹配的日志条目。实际中还有可能会定期存储日志,所以当前日志列表中并不会太大,可以完整打包发给对方,这一做法比较适合新加入集群的结点。

日志提交

只要日志在多数结点上存在,那么 Leader 就可以提交该操作。但是 Raft 额外限制了 Leader 只对自己任期内的日志条目适用该规则,先前任期的条目只能由当前任期的提交而间接被提交。

例如论文中图 8 这一 corner case。一开始如 (a) 所示,之后 S1 下线,(b) 中 S5 从 S3 和 S4 处获得了投票成为了 Leader 并收到了一条来自客户端的消息,之后 S5 下线。© 中 S1 恢复并成为了 Leader,并且将日志复制给了多数结点,之后进行了一个致命操作,将 index 为 2 的日志提交了,然后 S1 下线。(d) 中 S5 恢复,并从 S2、S3、S4 处获得了足够投票,然后将已提交的 index 为 2 的日志覆盖了。

为了解决这个问题,Raft 只允许提交自己任期内的日志,从而日志 2 只能像 (e) 中由于日志 3 同步而被间接提交,避免了 Follower 中由于缺少新任期的日志,使得 S5 能够继续成为 Leader。

总结

论文中这张图实乃整个算法的精髓所在,值得细细品读 ☺️:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201001101502147.png#pic_center 500x)
Raft 简单来说就是用 Leader 负责复制日志,超过半数就可以提交。然后用了诸多限制(Election Safety、Leader Append-Only、Log Matching、Leader Completeness、State Machine Safety)保证了日志的正确性和一致性。这些限制和算法的流程可以说是强耦合在一起的,如果单拿一个出来会容易想不通为什么,但是这些限制结合到一起就成了一个极其精妙的算法~

参考

http://thesecretlivesofdata.com/raft/

https://zinglix.xyz/2020/06/25/raft/

转载:图解Raft 让一致性算法变得更简单相关推荐

  1. 快开宝PDA开单器出入库扫码:让批发零售变得更简单

    快开宝PDA开单器出现前 批发商户是这样开单和管理的 ★员工痛苦:需要记客户.价格.库存等等,应对报错价.错漏单.盘错货等各种状况. ★老板麻烦:每天要守店.对单.核账,经常因错漏单.库存乱.积压货. ...

  2. 转使用Moq让单元测试变得更简单

    [ASP.Net MVC3 ]使用Moq让单元测试变得更简单 前几天调查完了unity.现在给我的任务是让我调查Moq. 以下是自己找了资料,总结并实践的内容.如果有表述和理解错误的地方.恳请指正. ...

  3. 项目管理六大制约因素_知道了这7点,软件项目管理会变得更简单!

    软件项目管理,很多产品经理(项目经理)都在吐槽,自己怎么样难以管理,不好做.但是,软件项目管理虽然不易,但是也可以适当把软件项目管理变得更简单,更易于管理.但是需要多做一些管理工作.下面的七点,好好看 ...

  4. 深入解析 Kubebuilder:让编写 CRD 变得更简单

    原文连接:https://developer.aliyun.com/article/719215 作者 | 刘洋(炎寻) 阿里云高级开发工程师 导读:自定义资源 CRD(Custom Resource ...

  5. Kafka Streams简介: 让流处理变得更简单

    Introducing Kafka Streams: Stream Processing Made Simple 这是Jay Kreps在三月写的一篇文章,用来介绍Kafka Streams.当时Ka ...

  6. Git 配置别名 —— 让命令变得更简单

    Git 配置别名 -- 让命令变得更简单 卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注. 目前专注于 Android 开发,主要分享 Android ...

  7. 菏泽话务员机器人方案_智能打电话机器人让电话营销变得更简单

    原标题:智能打电话机器人让电话营销变得更简单 如今的生活因人工智能的发展而变的越来越好,特别是进入互联网时代,国内客服环境发生了巨大变化,传统的客服体系正在迫捷升级, 智能打电话机器人的到来对应了企业 ...

  8. LEARUN快速开发平台,让开发变得更简单

    互联网时代,程序员一直是一个令人羡慕的高薪职业.但在不久前有人发出了不一样的意见:程序员现在看似乎是一片光明,但AI和人工智能的发展很快,10年后会怎样呢?会不会程序员的工作都被AI替代了?软件编程也 ...

  9. java会变得更简单吗_spring 第一篇(1-1):让java开发变得更简单(下)

    切面(aspects)应用 DI能够让你的软件组件间保持松耦合,而面向切面编程(AOP)能够让你捕获到在整个应用中可重用的组件功能.在软件系统中,AOP通常被定义为提升关注点分离的一个技术.系统由很多 ...

  10. 云鲸扫拖一体机器人说明书_让做家务变的更简单:云鲸智能扫拖一体机器人测评...

    让做家务变的更简单:云鲸智能扫拖一体机器人测评 2020-02-07 10:57:33 14点赞 19收藏 30评论 一.前言 说到提升生活质量的家用电器,扫地机器人排名绝对能数得着,从早期简单清理到 ...

最新文章

  1. linux 中关于网络的配置方法
  2. Spring Cloud Alibaba基础教程:Nacos的数据持久化
  3. 命令行神器 Click 简明笔记
  4. CentOS快捷键总结
  5. 初学者学习Python,掌握这些实用小技巧能快速入门!
  6. Caffe+Kubuntu16.04_X64+CUDA 8.0配置
  7. 使用Gradle构建Monorepo项目
  8. kafka 重新分配节点_Kafka控制器-分区重分配
  9. JAVA可阻塞队列-ArrayBlockingQueue
  10. 树莓派要mysql的密码_树莓派raspberry Pi 3B+系统中安装mysql过程中不提示输入密码,安装完后如何设置密码...
  11. 【离散数学】二元运算的性质
  12. TryCatchFinallyProcessHelper
  13. 有关ArrayList增加Map引发的一个BUG
  14. HTTP相关知识 --转载
  15. Android异步批量下载图片并缓存
  16. 《互联网公司各职位面试360度全方位解读》
  17. 同步助手 android 微信 表情包,微信表情轻松导,同步助手带你装逼带你飞
  18. SPIR-V 研究:编译器基本原理(三) - Chomsky文法分类
  19. 计算机软件水平考试(软考)-程序员介绍
  20. stm32用什么软件编辑呢

热门文章

  1. r语言 rgl 强制过程中_R语言中的方差分析方法汇总
  2. 数据恢复系列(4)~开源恢复工具
  3. Inno Setup脚本
  4. CIO如何做好灾难恢复/数据外泄的业务连续性计划?
  5. 改变form里面input,textarea.select等的默认样式
  6. 苹果的 Safari 浏览器13岁了
  7. 1731: [Usaco2005 dec]Layout 排队布局*
  8. SQL代码自动生成器
  9. java程序调用xfire发布的webService服务(二)
  10. Cache 应用程序数据缓存