Raft总结

news/2024/9/24 23:48:02/文章来源:https://www.cnblogs.com/INnoVationv2/p/18430337

Raft算法

image-20240829012226487

State

所有server都有的持久化状态 先存储,然后响应RPC
currentTerm 当前任期,初始为0,单调递增
votedFor 当前任期投票给谁了,没有就是null
log[] 日志条目,每个条目都包含命令Leader收到条目时的任期,第一个条目的index为1
所有server都有的Volatile state
commitIndex 已提交的最大日志条目index(初始为0,递增)
lastApplied 被应用到状态机的最高日志条目index(初始为0,递增)
Leader独有的Volatile state:
nextIndex[] 对于每个Follower,表示下一次将发送到该服务器的日志条目的起始索引(初始化为领导者的最后一个日志索引 + 1)
matchIndex[] 对于每个Follower,表示已复制到该服务器上最高日志条目的索引(初始化为 0,单调递增)。

AppendEntries RPC

  1. 只从Leader-->Follower,复制日志条目(§5.3)
  2. 也用于心跳连接(§5.2)
参数
term Leader任期
leaderId Follower可通过这个重定向client的请求到Leader
prevLogIndex 紧接在新日志之前的日志条目的索引
prevLogTerm 紧接在新日志之前的日志条目的任期
entries[] 日志条目,心跳时为空(为提高效率,可一次携带多个条目)
leaderCommit Leader的commitIndex
返回值
term Follower的Term,以便Leader更新自己
success 是否接收日志

Follower处理逻辑

  1. term<CurrentTerm

    返回false(§5.1)

  2. 若Follower日志中没有与prevLogIndexprevLogTerm匹配的项

    返回false(§5.3)

  3. 若Follower的日志和发来的日志冲突(比如相同index条目的term不同)

    删除冲突条目及后面所有的条目(§5.3)

  4. 将Leader发来的日志中,自己没有的追加到日志中

  5. IF leaderCommit > commitIndex:

    SET commitIndex=min(leaderCommit, 最后一个新表项的索引)

RequestVote RPC

由Candidate发起,收集投票(§5.2).

Arguments -
term Candidate的当前任期
candidateId 用以说明谁在请求选票
lastLogIndex Candidate最后一个日志项目的index(§5.4)
lastLogTerm Candidate最后一个日志项目的任期(§5.4)
Result
term 当前任期
voteGranted 是否投票

Receiver implementation:

  1. if term < currentTerm: return false(§5.1)
  2. 如果votedFor是 null 或 candidateId,并且候选人与接收人的日志一样是最新的,则投票(§5.2, §5.4)。

各个Server要遵守的规则

所有Server:

  1. 如果RPC请求或回复中包含的term大于自身term

    更新自己的term,并转为follower(§5.1)

  2. IF commitIndex > lastApplied:

    增加lastApplied,将log应用到状态机(§5.3)

Followers:

  1. 对来自候选人和Leader的RPC进行回复

  2. 如果超时未收到现任Leader的AppendEntries RPC,就转换为Candidate,开启选举

    收到其他Candidate的RequestVote时也算作收到RPC,重置超时器,推迟选举,防止多个Follower同时开始选举

Candidates:

  1. 转换为Follower后,启动选举:

    • 增加当前任期
    • 投票给自己
    • 重置选举超时时间
    • 发送RequestVote RPC给其他所有服务器
  2. 如果收到超过半数的投票

    成为新Leader

  3. 如果选举期间收到来自新Leader的AppendEntries RPC

    停止选举,转换回Follower

  4. 如果选举时再次超时

    重新启动一场新选举

Leaders:

  1. 当选Leader后:

    向所有Server发送空的AppendEntries RPC(心跳)

    在空闲期间重复发送以防止Server们选举超时。(§5.2)

  2. 如果收到客户端的指令:

    将指令包装为日志项,然后添加到日志中,待条目应用到状态机后再响应客户端(§5.3)

  3. 如果Leader日志的最大索引 ≥ 某个Follower的nextIndex:

    发送AppendEntries RPC,附上从nextIndex开始的日志项

    • 如果成功

      更新nextIndex和matchIndex(§5.3)

    • 如果因为日志不一致而失败

      减小nextIndex并重试(§5.3)

  4. 如果存在某个N,使得N>commitIndex,并且大多数的matchIndex[i]≥N,且log[N].term == currentTerm(这点尤其重要,不要使用计数规则提交以前任期的日志):

    设置commitIndex = N (§5.3, §5.4).

Leader选举

RequestVote RPC

Arguments -
term Candidate的当前任期
candidateId 用以说明谁在请求选票
lastLogIndex Candidate最后一个日志项目的index(§5.4)
lastLogTerm Candidate最后一个日志项目的任期(§5.4)
Result
term 当前任期
voteGranted 是否投票

0.新服务器加入集群

初始状态为Follower

只要持续收到Leader或Candidate的心跳信息,就保持Follower。

1.开始选举:

Follower通过选举超时(election timeout)时间间隔来决定是否启动选举。

如果在选举超时时间内未收到有效的心跳信息(一般是不包含日志条目的AppendEntries RPC),则认为当前Leader失效,开启选举。

选举超时时间是在一个固定的时间区间内随机选择(例如,150-300毫秒),这样大多数情况下只有一个服务器超时并发起选举,赢得选举并在其他服务器超时前发送心跳信号。

2.Follower的工作(投票规则)

Server收到RequestVote RPC时,根据以下规则进行投票:

  • 如果RPC中的Term < 服务器当前的任期

    拒绝投票

  • 如果已经在当前任期投过票

    拒绝投票

  • 确保Candidate包含所有已提交的日志

    RequestVote RPC中包含日志信息,收到投票请求时,投票人会对比日志

    • 如果Candidate的日志和投票方一样新或者更新

      投票

    • 如果Candidate的日志不如投票人新

      不投票

    日志新的定义:如果Term不同,则Term较大者更新,如果Term相同,则Index较大者更新。

  • 如果以上都没有

    投票,并重置自己的选举超时器。

3.Candidate的工作

  • 如果获得超过半数的选票,则成为新Leader。向所有服务器发送心跳消息,以防止新的选举。

  • 如果在选举过程中,收到心跳消息,且该消息的任期>=候选人的任期,则转回Follower,并将心跳消息发送方当做新Leader

  • 如果选举超时也没有选出Leader,则触发新一轮选举。

4.无法获胜

还有一种情况,同时有多个Follower成为了Candidate,因此没人能获得多数票。

这种情况,Candidate在选举超时后,会触发新一轮选举。

Log replication

AppendEntries RPC

只从Leader-->Follower

参数
term Leader任期
leaderId Follower可通过这个重定向client的请求到Leader
prevLogIndex 紧接在新日志之前的日志条目的索引
prevLogTerm 紧接在新日志之前的日志条目的任期
entries[] 日志条目,心跳时为空(为提高效率,可一次携带多个条目)
leaderCommit Leader的commitIndex
返回值
term Follower的Term,以便Leader更新自己
success 是否接收日志

1.Leader的工作

  1. 接收客户端请求,每个请求包含一个要执行的命令

  2. 将命令封装成日志条目追加到日志中。日志条目由命令和任期编号组成

  3. 向所有Follower并行发送AppendEntries RPC

  4. 一旦收到大多数Follower(包括Leader在内的半数以上)的确认,就认为日志条目被safely replicated

  5. Leader执行条目到自己的状态机

    将已执行的最大日志目录编号记录到lastApplied,下次与Follower通信时,写入leaderCommit,这样Follower就知道lastApplied之前的所有条目都已执行,Follower同样执行这些条目到状态机。

  6. 返回结果:Leader执行命令到状态机后,将操作结果返回给客户端

    此时,分布式系统中的大多数服务器就达成一致并成功执行了客户端的请求。

    image-20230425171710770

    图6:日志由按顺序编号的条目组成。每个条目包含创建它的任期(方框中的数字)和要执行的命令。如果一个条目可以安全地应用于状态机,那么它就被认为是Commited的。

2.Follower的工作

  1. 如果RPC term<CurrentTerm

    返回false

  2. 如果Follower日志中没有与prevLogIndexprevLogTerm匹配的项

    返回false

  3. 如果Follower的日志和发来的日志冲突(比如相同index条目的term不同)

    删除最早的冲突条目及后面所有的条目

  4. 将Leader发来的日志中、自己没有的追加到日志中

  5. 如果leaderCommit > commitIndex:

    SET commitIndex=min(leaderCommit, 最后一个新表项的索引)

日志修复

日志冲突处理步骤:

Leader为每个Follower维护一个nextIndex,这是Leader将发送给Follower的下一个日志条目的索引

Leader首次当选时,将所有nextIndex值初始化为日志中最后一个值之后的索引(图7中的11)。如果Follower与Leader的日志不一致,下一次AppendEntries RPC肯定会失败。失败后,Leader减少nextIndex并重试AppendEntries RPC。最终,nextIndex将达到Leader和Follower日志匹配的点。

成功匹配后,AppendEntries将成功,Follower将删除日志中的所有冲突条目,并从Leader日志(如果有的话)中追加条目。一旦AppendEntries成功,Follower与Leader的日志将一致,并且在该Term内始终保持一致。

如果需要,可以对协议进行优化,以减少被拒绝的AppendEntries rpc的数量。For example, when rejecting an AppendEntries request, the follower can include the term of the conflicting entry and the first index it stores for that term. With this information, the leader can decrement nextIndex to bypass all of the conflicting entries in that term; one AppendEntries RPC will be required for each term with conflicting entries, rather than one RPC per entry。实际上,我们怀疑这种优化是否必要,因为故障很少发生,并且出现大量不一致条目的可能性很小。

CH-7 日志压缩

Raft日志在正常操作过程中会不断增长,但随着日志变长,它占用的空间越来越多,重放所需的时间也越来越长。这最终会导致可用性问题,因此需要某种机制丢弃日志中的过时信息。

快照(Snapshot)是最简单的压缩方法。在快照中,整个当前系统状态被写入稳定存储的快照中,然后就可以丢弃掉快照包含的所有日志。

image-20240902124640912

图12:服务器用一个新的快照替换其日志中已提交的条目(索引1到5),快照存储最终状态(上图中的x<-0y<-9)。快照的last included indexlast included term用于快照在日志中定位,即快照包含了条目6之前的数据。

图12展示了Raft快照的基本思想。每个服务器独立地进行快照,只涵盖其日志中已提交的条目。大部分工作由状态机将其当前状态写入快照来完成。快照中还包含一些元数据:last included index:快照中包含的最后一个条目的索引(状态机已经应用的最后一个条目)last included term是该条目的任期。这些数据被保留下来以支持快照之后第一个日志条目的AppendEntries一致性检查。为了支持集群成员变更(见第6节),快照还包括最新的配置,截至最后包含的索引。一旦服务器完成了快照写入,它可以删除所有已包含的日志条目,以及所有先前的快照。

虽然服务器通常独立地进行快照,但Leader有时必须将快照发送给落后的Follower。当Leader已经删除了它需要发送给某个Follower的下一个日志条目时,就会出现这种情况。幸运的是,这种情况在正常操作中很少发生:跟随Leader保持同步的Follower已经拥有了该条目。然而,一个异常缓慢的Follower或新加入集群的服务器(见第6节)可能没有该条目。使这样的Follower赶上最新进度的方法是Leader向其发送快照。

Leader使用一个新的RPC:InstallSnapshot,将快照发送给落后的Follower;见图13。当Follower通过这个RPC接收到快照时,它必须决定如何处理现有的日志条目。通常情况下,快照会包含接收者日志中尚未存在的新信息。在这种情况下,Follower会丢弃其整个日志;因为它的日志全部被快照取代,且可能包含与快照冲突的未提交条目。如果Follower接收到一个描述其日志前缀的快照(由于重传或错误),则由快照覆盖的日志条目会被删除,但快照后续的条目仍然有效,必须保留。

image-20240902135015013

接收方需实现的逻辑

  1. 如果Arguments/term<currentTerm,立即回复。

  2. 如果是第一个数据块(偏移量为 0),则创建一个新的快照文件。

  3. 将数据写入快照文件中的给定偏移量位置。

  4. 如果done为false,回复Leader并等待更多的数据块到来。

  5. 保存快照文件,丢弃所有索引小于该快照的现有或部分快照。

  6. 如果现有日志条目与快照的最后包含条目具有相同的索引和任期,保留其后的日志条目并回复。

  7. 丢弃整个日志。

  8. 使用快照内容重置状态机(并加载快照的集群配置)。

使用快照偏离了Raft的强领导原则,因为Follower可以在不通知Leader的情况下进行快照。然而,我们认为这是合理的。虽然有Leader有助于避免在达成共识时出现冲突的决策,但在进行快照时,已经达成共识,因此不会有冲突的决策。数据仍然只从Leader流向Follower,只是Follower重新组织了自身的数据。

我们也考虑过基于Leader的快照,其中只有Leader创建快照,然后将此快照发送给其每个Follower。然而,这种方法有两个缺点。首先,将快照发送给每个Follower会浪费网络带宽并减慢快照过程。每个Follower已经拥有创建其自身快照所需的信息,而且服务器从其本地状态生成快照通常比通过网络发送和接收快照便宜得多。其次,Leader的实现将更加复杂。例如,Leader需要在向Follower复制新的日志条目时并行发送快照,以免阻塞新的客户端请求。

还有两个问题影响快照性能。

  1. 何时进行快照?如果服务器过于频繁地进行快照,会浪费磁盘带宽和能量;如果快照频率过低,则可能耗尽存储空间,并增加在重启时重放日志所需的时间。

    一种简单的策略是当日志达到固定字节大小时进行快照。如果将这个大小设置为显著大于快照预期大小,那么快照产生的磁盘带宽开销将很小。

  2. 性能问题,写入快照可能需要大量时间,而我们不希望这延迟正常操作。

    解决方案是写时复制,以便可以在不影响正在写入的快照的情况下接受新更新。例如,使用功能性数据结构构建的状态机自然支持这一点。或者,可以使用操作系统的写时复制支持(例如,Linux上的fork)来创建整个状态机的内存快照(我们的实现使用了这种方法)。

CH-8 客户端交互

本节描述客户端如何与Raft交互,包括客户端如何找到集群Leader以及Raft如何支持线性化语义。这些问题适用于所有基于共识的系统,Raft的解决方案与其他系统类似。

找到集群Leader

Raft的客户端将所有请求发送给Leader。当客户端首次启动时,它会连接到一个随机选择的服务器。如果这个服务器不是Leader,那服务器会拒绝客户端的请求,并提供它所知道的Leader信息(AppendEntries 请求包含Leader的网络地址)。如果Leader崩溃,客户端请求将超时;此时,客户端会再次尝试连接随机选择的服务器。

支持线性化语义

Raft 的目标是实现线性化语义(每个操作看起来在其调用与响应之间的某个时刻瞬间执行且仅执行一次)。然而,如前所述,Raft可能会多次执行一个命令:例如,如果Leader在提交日志条目后、响应客户端之前崩溃,客户端将重新提交该命令到新Leader,导致命令再次执行。解决方案是让客户端为每个命令分配唯一的序列号。然后,状态机会跟踪每个客户端处理的最新序列号及其关联的响应。如果它收到的命令的序列号已经执行过,它会立即响应,而不重新执行请求。

对于只读操作,可在不写入日志的情况下进行处理。然而,如果没有额外限制,可能会返回过时数据,因为响应请求的Leader可能已被更新的Leader取代,而旧Leader并不知情。线性化读取必须确保不返回过时数据,Raft采取两项预防措施来保证这一点而不使用日志。

  1. Leader必须掌握最新的已提交条目信息。Leader完整性属性保证了Leader拥有所有已提交的条目,但在任期开始时,它可能不知道哪些条目是已提交的。为了解决这个问题,Leader需要提交一个来自其任期的条目。Raft通过在每个Leader任期开始时提交一个空的无操作条目到其日志中来解决这一问题。
  2. Leader在处理只读请求之前,必须检查自己是否已被罢免(如果更近期的Leader已经当选,其信息可能已经过时)。Raft通过让Leader在响应只读请求之前与集群的多数节点交换心跳消息来进行确认。或者,Leader可以依赖心跳机制来提供某种租约[9],但这依赖于时间安全性(假设时钟偏移受限)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/803118.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lab3 Raft

Lab3 Raft 1.Getting Started 代码位置:基础框架代码位置:src/raft/raft.go测试代码:src/raft/test_test.go建议测试时使用-race 2.The code向raft/raft.go添加代码来实现Raft。实现必须支持以下接口 // 创建一个Raft Server rf := Make(peers, me, persister, applyCh) fun…

Lab3 记录

Part 3A: leader election 1.选举主要流程新服务器加入集群服务器在启动时状态是Follower。只要持续接收到Leader或Candidate的心跳信息,就继续保持Follower状态。开始选举每个Server都有一个随机的选举超时时间,选举超时在一个固定区间内随机选择(例如,150-300毫秒) 如果…

软件工程课程第三次作业

软件工程 https://edu.cnblogs.com/campus/fzu/SE2024作业要求 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13261作业目标 分析学生们的需求,设计一个app原型解决他们的问题学号 072208130合作伙伴 052205144张诗悦使用figma设计原型,原型链接:https://www.figma.…

大文件去重

若文件存的字符如下图,要求进行去重可将数据存入HashSet,如下,但如果文件很大,大于虚拟机内存的话,会报异常java.lang.OutOfMemoryError: Java heap spaceHashSet set = new HashSet();File file = new File("E:\\aa.txt");BufferedReader reader = new Buffere…

9月13日关于数组存储数据

在题目中要求建立数组来存储项目信息,储存的内容包括String、int、boolean、double等各种不同类型,刚开始我还处于建立普通数组要不是int【】要不是string【】,越琢磨越不对劲这样并不能存储不同类型的数据,但是数据又需要统一存取,网上又没有这么简单的讲解,也是被这个简…

9.24日总结

今日上学配置了Node.JS的环境变量,并应用VScode进行JavaScript的相关学习应用 其中

9月11日toString重载方法的使用

在编辑过程中我经常会写一部分调试一部分,至少知道哪里有错能够及时改正,在编写时发现studentManger中的打印出来的是地址,而不是自己想要的内容,经过查询是需要写toString来重载输出利用这样的方法,一是可以正常打印出自己想要的内容,而是可以根据一个参数打印出所有的信…

软件工程作业——结对项目

这个作业属于哪个课程 22级计科12班这个作业要求在哪 作业要求这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序成员姓名 学号 GitHub地址吕宏鸿 3122004446 结对项目宋观瑞 3122004402 结对项目1.PSP表格PSP2.1 预估耗时(分钟) 实际耗时(分钟)计划 10 5* 估计…

9月10日循环条件的结束

在测试编程中涉及到输入错误要重新返回UI界面,但是我写的总是输入不管是对还是错都会直接结束程序,完全不符合要求,经过整理思路,查询代码结构,此处应该设计为双层循环外部为while,内部为witch case语句,当输入为1时执行case==1;经应该是执行生产计划类然后跳出witch条…

IDEA更改远程git仓库地址

前言 我们在使用IDEA开发时,一般会配置好对应的git仓库,这样就比较容易对代码进行控制以及协同开发。但有时候,我们远程的仓库地址由于这样那样的原因,需要迁移(这在爱折腾的企业是常有的事情)。那么,我们该如何在IDEA中更新远程仓库地址呢? 如何设置 首先,我们点击上…

vue3开发中易遗漏的常见知识点

组件样式的特性 Scoped CSS之局部样式的泄露 示例(vue3): 父组件: <template><h4>App Title</h4><hello-world></hello-world> </template> <script> import HelloWorld from ./HelloWorld.vue;export default {name: App,compo…

PasteForm最佳CRUD实践,实际案例PasteTemplate详解(一)

本文将介绍soft.pastecode.cn出品的PasteForm,PasteForm是贴代码使用Dto思想实现的CRUD的一个组件,或者说输出一个思想! 为啥我觉得是最佳的CRUD呢?先结合你的实际项目解答下以下问题: 1.如果有一个系统,有100个表,你的管理端需要多少页面?别和我说100个表很多,需求复…