一致性协议raft详解(二):安全性

  • 前言
  • 安全性
    • log recovery
      • 为什么no-op能解决不一致的问题?
  • 成员变更
    • Single mempership change
  • raft用到的随机时间
  • raft 脑裂
    • 数据到达 Leader 节点前
    • 数据到达 Leader 节点,但未复制到 Follower 节点
    • 数据到达 Leader 节点,成功复制到 Follower 所有节点,但还未向 Leader 响应接收
    • 数据到达 Leader 节点,成功复制到 Follower 部分节点,但还未向 Leader 响应接收

前言

有关一致性协议的资料网上有很多,当然错误也有很多。笔者在学习的过程中走了不少弯路。现在回过头来看,最好的学习资料就是Leslie LamportDiego Ongaro的数篇论文、Ongaro在youtube上发的三个视频讲解,以及何登成的ppt。

本系列文章是只是笔者在学习一致性协议过程中的摘抄和总结,有疏漏之处敬请谅解,欢迎讨论。

安全性

raft的log replication机制并不能充分的保证每一个状态机会按照相同的顺序执行相同的指令。例如,一个follower可能会进入不可用状态同时leader已经提交了若干的日志条目,然后这个follower可能会被选举为leader并且覆盖这些日志条目;因此,不同的状态机可能会执行不同的指令序列

我们通过在leader election增加限制来完善raft算法。在任何基于leader的一致性算法中,leader都必须存储所有已经提交的日志条目(或者通过额外的机制保证leader丢失的日志条目可以同步给leader)。Raft 使用了一种更加简单的方法,它可以保证所有之前的任期号中已经提交的日志条目在选举的时候都会出现在新的leader中,不需要传送这些日志条目给leader。这意味着日志条目的传送是单向的,只从leader传给跟随者,并且leader从不会覆盖自身本地日志中已经存在的条目。

**由于raft节点中的日志是顺序添加的,那么raft在leader election的时候通过比较两份日志中最后一条日志目录的索引号index和任期号term,就可以判断出谁的日志更新。**只有最新的人才有权利作为leader。候选人为了赢得选举必须联系集群中的大部分节点,这意味着每一个已经提交额度日志条目至少会存在于这些节点中的至少一个上。如果候选人的日志至少和大多数的服务器节点一样新,那么他一定持有了所有已经提交的日志条目

log recovery

以下摘自baidu braft文章

Log Recovery这里分为current Term修复和prev Term修复,Log Recovery就是要保证一定已经Committed的数据不会丢失,未Committed的数据转变为Committed,但不会因为修复过程中断又重启而影响节点之间一致性。

  • current Term修复主要是解决某些Follower节点重启加入集群,或者是新增Follower节点加入集群,Leader需要向Follower节点传输漏掉的Log Entry,如果Follower需要的Log Entry已经在Leader上Log Compaction清除掉了,Leader需要将上一个Snapshot和其后的Log Entry传输给Follower节点。
  • prev Term修复主要是在保证Leader切换前后数据的一致性。通过上面RAFT的选主可以看出,每次选举出来的Leader一定包含已经committed的数据(抽屉原理,选举出来的Leader是多数中数据最新的,一定包含已经在多数节点上commit的数据),新的Leader将会覆盖其他节点上不一致的数据。虽然新选举出来的Leader一定包括上一个Term的Leader已经Committed的Log Entry,但是可能也包含上一个Term的Leader未Committed的Log Entry。这部分Log Entry需要转变为Committed,相对比较麻烦,需要考虑Leader多次切换且未完成Log Recovery,需要保证最终提案是一致的,确定的。 **RAFT中增加了一个约束:对于之前Term的未Committed数据,修复到多数节点,且在新的Term下至少有一条新的Log Entry被复制或修复到多数节点之后,才能认为之前未Committed的Log Entry转为Committed。**下图就是一个prev Term Recovery的过程:

  1. S1是Term2的Leader,将LogEntry部分复制到S1和S2的2号位置,然后Crash。
  2. S5被S3、S4和S5选为Term3的Leader,并只写入一条LogEntry到本地,然后Crash。
  3. S1被S1、S2和S3选为Term4的Leader,并将2号位置的数据修复到S3,达到多数;并在本地写入一条Log Entry,然后Crash。
  4. 这个时候2号位置的Log Entry虽然已经被复制到多数节点上,但是并不是Committed
    1. S5被S3、S4和S5选为Term5的Leader,将本地2号位置Term3写入的数据复制到其他节点,覆盖S1、S2、S3上Term2写入的数据
    2. S1被S1、S2、S3选为Term5的Leader,将3号位置Term4写入的数据复制到S2、S3,使得2号位置Term2写入的数据变为Committed

为了避免4-1中的现象,协议又强化了一个限制:

  • 只有当前 Term 的 LogEntry 提交条件为:满足多数派响应之后(一半以上节点 Append LogEntry 到日志)设置为 commit;
  • 前一轮 Term 未 Commit 的 LogEntry 的 Commit 依赖于高轮 Term LogEntry 的 Commit。

也就是说,我们要通过提交 NO-OP LogEntry 提交系统可用性

在 Leader 通过竞选刚刚成为 Leader 的时候,有一些等待提交的 LogEntry (即 SN > CommitPt 的 LogEntry),有可能是 Commit 的,也有可能是未 Commit 的(PS: 因为在 Raft 协议中 CommitPt 不用实时刷盘)。

所以为了防止出现非线性一致性(Non Linearizable Consistency);即之前已经响应客户端的已经 Commit 的请求回退,并且为了避免出现上面4-1中的 Corner Case(已经commit的日志被覆盖),往往我们需要通过下一个 Term 的 LogEntry 的 Commit 来实现之前的 Term 的 LogEntry 的 Commit (隐式commit),才能保障提供线性一致性。

但是有可能接下来的客户端的写请求不能及时到达,那么为了保障 Leader 快速提供读服务,系统可首先发送一个 no-op LogEntry 来保障快速进入正常可读状态。

为什么no-op能解决不一致的问题?

刚当选的leader发送no-op,如果这个no-op可以commit成功那么可以认为这个节点之前所有的日志都已经commit成功了。换句话说,大部分节点已经将当前这个leader写入了log中。

也就是说,通过这个no-op commit,我们commit了leader节点上所有的log。就算后面再有figure8中的覆盖问题,覆盖的也是这个no-op

详情可以看我写的另外一篇文章:raft引入no-op解决了什么问题

成员变更

一文看尽 Raft 一致性协议的关键点

  1. 可从数学上严格证明,只要每次只允许增加或删除一个成员,Cold与Cnew不可能形成两个不相交的多数派。这种方式比较简单,也是etcd的做法。

  2. 如果每次增加超过两个。会导致变更过程中出现多个多数派,所以要引入用两阶段成员变更

  3. 系统状态变化同paxos一样通过日志同步

  4. add node

  5. catch up

  6. transition Cold和Cnew需要同时达成多数派,log replication才算是成功。

Single mempership change

论文中提以下几个关键点:

  1. 由于 Single 方式无论如何 Cold 和 CNew 都会相交,所以 Raft 采用了直接提交一个特殊的 replicated LogEntry 的方式来进行 single 集群关系变更
  2. 跟普通的 LogEntry 提交的不同点,configuration LogEntry 不需要 commit 就生效,只需要 append 到 Log 中即可。( PS: 原文 “The New configuration takes effect on each server as soon as it is added to the server’s log”)。
  3. 后一轮 MemberShip Change 的开始必须在前一轮 MemberShip Change Commit 之后进行,以避免出现多个 Leader 的问题。

注意:

  1. 后一轮 MemberShip Change 的开始必须在前一轮 MemberShip Change Commit 之后进行
  2. 提交特殊的 replicated LogEntry不需要commit(不需要形成多数派),只要有一条日志被commit了,整个系统后续也认为这条日志已经apply成功了。

raft用到的随机时间

  1. raft leader的任期是随机时间(一般情况下不需要指定leader的任期,因为term变动会导致系统不可服务)
  2. Raft 算法使用随机选举超时时间的方法来确保很少会发生选票瓜分的情况,每一任期已经投票的follower节点在过了选举超时时间之后(或者节点刚起来的时候,会等待随机的时间才变为candidate),会变为candidate,并向向其他节点发送requestVote。
  3. 每一个candidate在开始一次选举的时候会重置一个随机的选举超时时间,然后在超时时间内等待投票的结果。

raft 脑裂

Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。当 Client 向集群 Leader 节点提交数据后,Leader 节点接收到的数据处于未提交状态(Uncommitted),接着 Leader 节点会并发向所有 Follower 节点复制数据并等待接收响应,确保至少集群中超过半数节点已接收到数据后再向 Client 确认数据已接收。一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed),Leader 节点再向 Follower 节点发通知告知该数据状态已提交。

在这个过程中,主节点可能在任意阶段挂掉,看下 Raft 协议如何针对不同阶段保障数据一致性的。

数据到达 Leader 节点前

这个阶段 Leader 挂掉不影响一致性,不多说。

数据到达 Leader 节点,但未复制到 Follower 节点

这个阶段 Leader 挂掉,数据属于未提交状态,Client 不会收到 Ack 会认为超时失败可安全发起重试。Follower 节点上没有该数据,重新选主后 Client 重试重新提交可成功。原来的 Leader 节点恢复后作为 Follower 加入集群重新从当前任期的新 Leader 处同步数据,强制保持和 Leader 数据一致。

数据到达 Leader 节点,成功复制到 Follower 所有节点,但还未向 Leader 响应接收

这个阶段 Leader 挂掉,虽然数据在 Follower 节点处于未提交状态(Uncommitted)但保持一致,重新选出 Leader 后可完成数据提交,此时 Client 由于不知到底提交成功没有,可重试提交。针对这种情况 Raft 要求 RPC 请求实现幂等性idempotent),也就是要实现内部去重机制(client如果发了个请求,就要一直无限重试知道成功,并且raft集群也会在自己恢复之前尝试commit但是可能因为节点crash没有commit成功的log(Figure 8)。也就是说raft节点会忽略自己已经commit的log的rpc请求,直接返回成功?)。

数据到达 Leader 节点,成功复制到 Follower 部分节点,但还未向 Leader 响应接收

这个阶段 Leader 挂掉,数据在 Follower 节点处于未提交状态(Uncommitted)且不一致,Raft 协议要求投票只能投给拥有最新数据的节点。所以拥有最新数据的节点会被选为 Leader 再强制同步数据到 Follower,数据不会丢失并最终一致。

疑问

  1. Q: uncommitted日志会作为当前的log entry被保存吗?A: 会的,应该没有commit rpc整个系统就可以运转正常了。
  2. Q: 什么时候回复client?A: leader append log entry 之后应该就可以回了,如果除了leader已经有超过半数写入了,那么不到半数也能回

一致性协议raft详解(二):安全性相关推荐

  1. 一致性协议raft详解(一):raft整体介绍

    一致性协议raft详解(一):raft介绍 前言 概述 raft独特的特性 raft集群的特点 raft中commit何意? raft leader election log replication ...

  2. 一致性协议raft详解(四):raft在工程实践中的优化

    一致性协议raft详解(四):raft在工程实践中的优化 前言 性能优化 client对raft集群的读写 参考链接 前言 有关一致性协议的资料网上有很多,当然错误也有很多.笔者在学习的过程中走了不少 ...

  3. 一致性协议raft详解(三):raft中的消息类型

    一致性协议raft详解(三):raft中的消息类型 前言 raft 节点 Raft中RPC的种类 RequestVote leader选举成功后 AppendEntries 请求参数 返回值 存储日志 ...

  4. 一致性协议-ChainPaxos详解

    一致性协议-ChainPaxos详解 一.背景 二.算法实现 流程概况 主流程实现细节 故障处理以及reconfiguration 选举 新增副本 线性读 三.总结 一.背景 该paxos变体的主要特 ...

  5. CPU处理器一致性协议MESI详解

    CPU处理器缓存一致性协议MESI详解 缓存一致性的由来 MESI协议简介 Exclusive状态 Shared状态 Modified和Invalid状态 MESI状态切换 Modified状态跳转 ...

  6. 一致性协议Paxos详解(一):Basic Paxos协议详解

    一致性协议Paxos详解(一):Basic Paxos协议详解 前言 Paxos是什么 Paxos算法原理与推导 Basic Paxos Proposal Numbers prepare阶段 prep ...

  7. Nacos如何实现Raft算法与Raft协议原理详解

    前言 大名鼎鼎的Paxos算法可能不少人都听说过,几乎垄断了一致性算法领域,在Raft协议诞生之前,Paxos几乎成了一致性协议的代名词.但是对于大多数人来说,Paxos算法太难以理解了,而且难以实现 ...

  8. MQTT协议详解 二、MQTT控制包格式

    文章目录 系列文章目录 前言 一.MQTT控制包格式 二.固定包头 控制包类型 控制包类型标识 剩余长度 三.可变包头 数据包标识 四.载荷 系列文章目录 MQTT协议详解 一.MQTT简介 MQTT ...

  9. Android面试Hash原理详解二

    Hash系列目录 Android面试Hash原理详解一 Android面试Hash原理详解二 Android面试Hash常见算法 Android面试Hash算法案例 Android面试Hash原理详解 ...

最新文章

  1. 基于Numpy构建全连接前馈神经网络进行手写数字识别
  2. C++_STL——queue(and priority_queue)
  3. Maven无法上传到到私服
  4. 常见浏览器兼容性问题与解决方案
  5. JAVA web 会话技术CookieSession
  6. 英特尔推出全新至强处理器E5 v4产品家族
  7. php 自动验证类,Thinkphp实现自动验证和自动完成
  8. Qt文档阅读笔记-QLatin1String解析及文本段跨行赋值
  9. 《Reids 设计与实现》第十三章 Sentinel
  10. 液压与气压传动(机电)_简要问答_复习笔记
  11. JMeter详细使用手册
  12. getlasterror 126
  13. mysql索引一般什么数据结构_mysql索引一般使用什么数据结构
  14. 兔子问题 php,PHP基于递归算法解决兔子生兔子问题php技巧
  15. adb push命令
  16. 自学整理之HTML5常用标签和知识——小白篇
  17. VSS2005安装指南
  18. 用Python做市场调查:餐饮商铺的用户满意度分析
  19. 个人实践,B450锐龙3600安装原版win7方法
  20. 前端基本功---JS数据类型知多少

热门文章

  1. html设置返回的样式,jQuery设置或返回元素样式属性。
  2. NJUST4316(立体几何投影的面积交)
  3. PostgreSQL学习笔记5之函数和操作符一
  4. 排序算法——插入排序
  5. socket 通信关于bind那点事
  6. 你对JVM三色标记的理解嘛?
  7. 圆桌对话:数字化升级,视频通信云赋能全行业
  8. 毕业五年的音视频开发工程师过得怎么样了?
  9. 参会者说丨LiveVideoStackCon 2019上海 音视频技术大会
  10. 腾讯万亿级 Elasticsearch 内存效率提升技术解密