raft协议笔记-一致性协议

497 阅读13分钟

Raft协议是一种一致性算法,用于在分布式系统中保持整个系统的一致性。

  1. 是分布式系统开发首选的共识算法
  2. Raft算法是经过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。
  3. 用于管理日志一致性的协议。
  4. 将分布式一致性分解为多个子问题:
    1. Leader选举(Leader election)
    2. 日志复制(Log replication)
    3. 安全性(Safety)
    4. 日志压缩(Log compaction)

在了解 Raft 之前需要了解一下什么状态机:

论文指出,Raft 是一种用来管理日志复制的一致性算法。所以我们就要先了解一下。什么是日志复制状态机。我们思考一个问题。如果你要与你的小伙伴分享一个很复杂的操作及计算。一般来说你有两种做法: 第一种:你自己负责计算,经过一段时间的计算,算出结果后,直接把计算结果告诉你的小伙伴。 第二种:你把每一个操作的步骤都告诉你的伙伴,告诉他怎么做,由你的伙伴自己计算出结果。

第二种方式,就是复制状态机的工作原理。复制状态机是通过复制日志来实现的。每一台服务器保存着一份日志,日志中包含一系列的命令,状态机会按顺序执行这些命令。因为每一台计算机的状态机都是确定的,所以每个状态机的状态都是相同的,执行的命令是相同的,最后的执行结果也就是一样的了。

在实际中这种有很多类似的应用比如 mysql 的主从同步就是通过 binlog 进行同步。

Raft协议使整个集群组成了一个状态复制机,一个主节点来对客户端提供服务,服务只能通过命令来对状态进行改变。每执行一个命令,主节点便将执行命令的日志复制给从节点,通过复制日志,如果从节点和主节点的日志是一样的,那么它们的状态就是一样的了。

动画演示Raft:

thesecretlivesofdata.com/raft/

一 概念

  1. 大多数:(服务器节点总数/2)+1
  2. term:任期
  3. 选举超时
  4. 心跳超时

基本安全保证:

为了保证过程正确性,Raft需要保证以下的性质时刻为真:

●选举安全原则: 同一届任期内至多只能有一个领导人。

●领导人只加原则: 领导人的日志只能增加,不能重写或者删除。

●日志匹配原则: 如果两个日志具有相同的任期和索引,则这两段日志在[0,索引]之间的日志完全相同。

●领导人完全原则: 如果一条日志被提交,那么后续的任意任期的领导人都会有这条日志。

●状态机安全原则: 如果一个服务器已经将给定索引位置的日志条目应用到状态机中,则所有其他服务器不会在该索引位置应用不同的条目。

二 领导者选举----随机选举超时

Raft协议在leader选举中,节点会处于三种状态之一:

●Follower 从节点,给其他节点提供选票

●Candidate 候选人,如果一个Follower在一定时间后没有收到Leader的心跳包,则判断Leader故障,将自己的状态转换为Candidate,开始新一期选举流程。

●Leader 主节点,如果Candidate选举成功,则转换为Leader,开始对外提供服务。

选举过程:

随机选举超时 从Follower状态成为Candidate状态需要等待的时间,这个时间是随机的

选举超时的作用 1. 防止多个节点同时发起投票 2.在大多数情况下只有一个服务器节点先发起选举,而不是同时发起选举,减少了因选票瓜分导致选举失败的情况。

  1. Follower在一段时间后没有收到Leader的心跳包,则转变为Candidate,该节点的term值加 1 ,然后给自己投一票,向其它所有节点发送选举请求。该选举请求是一个 RPC 调用:RequestVoteRPC
  2. 从节点收到选举请求,返回投票或者拒绝投票。

投票遵从先到先得原则。Follower判断请求的term值是否比自己的大,如果大,则投选举成功,否则拒绝投票。 假设当前有两个Candidate节点 A 和 B 同时向 C 发出选举请求,C 只会对最先到的选举请求投选举票。

  1. 当一个节点获得最多的票数后,该节点成为Leader,开始对外提供服务。其他Candidate自动转为Follower。

如果多个节点同时拥有最多的票数,比如 A 和 B 两个节点的票数都是 2 ,这种时候会等待一个超时时间 ( 150ms ~ 300ms ) 后,重新开始选举流程。 这种情况下会让服务更长时间不能提供服务,所以Raft协议的超时值是一个随机的区间时间,来避免这种情况出现。

●Raft 由多个节点组成。 ●强领导者, 整个 Raft 在同一时间,只有一个领导者,日志有领导者负责分发和同步。 ●领导选举, 领导是由民主选举产生的,集群中多数节点投票通过就能成为主。

每个领导人都有一个任期限制。每一届任期的开始阶段,都是选举。如果选举出了领导者就由该领导人负责领导集群。如果没有选举出领导,就会进入下一次选举。直到选举出领导者为止。

领导者会周期性的向每台机器发送心跳,确保自己的领导地位。

跟随者在长时间没有收到领导人的心跳,就会发起投票成为候选人,同时任期 + 1,如果获得超过半数的支持,就升任为领导。

如果候选人,在发起投票的时候,发现集群里面有领导人的时候,就会重新成为追随者。

如果候选人,发起投票后,一定时间里面没有收到超过半数的反馈,就会再次发起投票。

如果领导者发现在集群中发现存在下一任期的领导者,就会变为追随者。

Raft 算法的几个关键机制 通过以下几个关键机制,保证了一个任期只有一位领导,极大减少了选举失败的情况。

1.任期 2.领导者心跳信息 3.随机选举超时时间 4.先来先服务的投票原则 5.大多数选票原则

三 日志复制

日志执行流程

在这里插入图片描述①客户端请求集群中的领导者,并要求执行操作日志。 ②领导者向所有的追随者发起写日志请求。追随者接收到写日志请求后,将日志放入日志队列中并回应领导者。 ③当大多数的追随者响应成功,则领导者发起应用日志请求,追随者收到请求后则将日志写入各自的状态机内 ④领导者回应客户端写入成功

日志组成

1.日志由有序编号(log index)的日志条目组成。 2. 每个日志条目包含它被创建时的任期号(term)和用于状态机执行的命令。 3. 如果一个日志条目被复制到大多数服务器上,就被认为可以提交(commit)了。 大多数:(服务器节点总数/2)+1 在这里插入图片描述

日志的一致性

如果不同日志中的两个条目有着相同的索引和任期号,则它们所存储的命令是相同的

原因:leader 最多在一个任期里的一个日志索引位置创建一条日志条目,日志条目在日志的位置从来不会改变

如果不同日志中的两个条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的

原因:leader每次 RPC 发送附加日志时,会附加这条日志之前日志的索引和任期号一起发送给 follower

  1. 如果 follower 发现和自己的日志不匹配,那么就拒绝接受这条日志,这个称之为一致性检查
  2. 如果 follower 发现和自己的日志匹配,才会附加上去。

日志不一致

1.raft是通过跟随者强制复制领导者的日志来保证的。

2.一般情况下,Leader和Followers的日志保持一致,因此 AppendEntries(附加条目) 一致性检查通常不会失败。然而,Leader崩溃可能会导致日志不一致

旧的Leader可能没有完全复制完日志中的所有条目。

日志不一致的处理策略

当附加日志RPC的一致性检查失败时,追随者会拒绝这个请求。当领导者检测到附加日志的请求失败后,会减小当前附加日志的索引值,再次尝试附加日志,直至成功。为了减少领导者的附加日志RPC被拒绝的次数,可以做一个小优化,当追随者拒绝领导者的附加日志请求时,追随者可以返回包含冲突的条目的任期号和自己存储的那个任期的最早的索引地址,从而使得领导人和追随者尽快找到最后两者达成一致的地方。当追随者和领导者之间的日志相差过大时,领导者会直接发送快照来快速达到一致。

安全性

1.Leader通过强制Followers复制它的日志来处理日志的不一致,Followers上的不一致的日志会被Leader的日志覆盖。Leader为了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆盖Followers在该位置之后的条目。

2.具体的操作

Leader会从后往前试,每次AppendEntries(附加条目)失败后尝试前一个日志条目,直到成功找到每个Follower的日志一致位置点(基于上述的两条保证),然后向后逐条覆盖Followers在该位置之后的条目。

3.总结 当 leader 和 follower 日志冲突的时候,leader 将校验 follower 最后一条日志是否和 leader 匹配,如果不匹配,将递减查询,直到匹配,匹配后,删除冲突的日志。这样就实现了主从日志的一致性。

Raft增加了如下两条限制以保证安全性:

1.拥有最新的已提交的log entry的Follower才有资格成为leader。

Follower就是对应节点拥有当前领导者已经提交的所有日志

2.Leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志,旧term日志的提交要等到提交当前term的日志来间接提交(log index 小于commit index的日志被间接提交)。

1.Leader只能推进commit index。 2. 当Leader的日志负责到一半的flowers上,Leader才回提交当前term的日志。 3.Leader当前日志已经提交了,当前日志之前的日志一定也要提交。

Raft中节点在投票的时候,会判断被投票的候选者对应的日志是否至少和自己一样新。如果不是,则不会给该候选者投票。

日志的压缩:

日志的压缩比较容易理解,随着集群的使用,日志的数量越来越大,就会降低集群的性能,同时占用大量的存储空间。所以需要定期对日志进行压缩。快照是最简单的压缩方法。在快照系统中,整个系统的状态都以快照的形式写入到稳定的持久化存储中,然后到那个时间点之前的日志全部丢弃。

1.在实际的系统中,不能让日志无限增长。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都丢弃。

2.每个副本独立的对自己的系统状态进行snapshot,并且只能对已经提交的日志记录进行snapshot。 在这里插入图片描述 如图:对于日志索引5之前的日志项可以删除,只保留一个快照(保存有当前状态以及一些任期索引号等元信息)即可 Snapshot中包含以下内容

1.日志元数据

2.最后一条已提交的log entry的log index和term。

这两个值在snapshot之后的第一条log entry的AppendEntries RPC的完整性检查的时候会被用上。

3.系统当前状态。

发送快照的时机

1.当Leader要发给某个日志落后太多的Follower的log entry被丢弃,Leader会将snapshot发给Follower。 2.当新加进一台机器时,也会发送snapshot给它。 3.发送snapshot使用InstalledSnapshot RPC。

快照注意点

1.做snapshot不要做的太频繁,会消耗磁盘带宽 2. 做snapshot不要太不频繁,否则一旦节点重启需要回放大量日志,影响可用性。 3. 推荐当日志达到某个固定的大小做一次snapshot。 4.做一次snapshot可能耗时过长,会影响正常日志同步。

可以通过使用copy-on-write技术避免snapshot过程影响正常日志同步。

Raft日志快照主要有两种用途:

●压缩日志 当系统中的日志越来越多后,会占用大量的空间,Raft算法采用了快照机制来压缩庞大的日志,在某个时间点,将整个系统的所有状态稳定地写入到可持久化存储中,然后这个时间点后的所有日志全部清除。

●快照RPC 当我们拥有了快照之后,就能通过快照直接将领导者的状态复制到那些过于落后追随者上,从而使得追随者和领导者的状态能够快速到达一致。

我们可以看到Raft的快照机制和Redis的持久化存储是很相像的。所以一些Redis的优化机制可以有选择地应用到Raft之中,如快照自动触发、使用fork机制来降低创建快照的资源占用、使用特殊的数据结构来保证快照的可检测性等。

安全性(Safety)

成员变更双主脑裂 脑裂指在网络出现分割的情况下,集群会分为两个小集群,这种情况下会产生两个Leader。 脑裂的情况下,两个小集群中,一定只有一个可以提供服务。

假设当前集群有N个节点,N为奇数,发生脑裂后,最多只有一个集群的Follower数量大于等于N/2,只有该集群可提供服务。

发生脑裂的情况下,客户端请求到了少数集群,不会收到 Ack,再次尝试请求,此时请求了多数集群,会收到 Ack。当网络恢复后,少数集群会自动成为Follower。

成员变更存在的问题是增加或者减少的成员太多了,导致旧成员组和新成员组没有交集,因此出现了双主。

解决方案(只需要保证不会让新或者旧配置单独作出决定就行) 每次成员变更只允许增加或删除一个成员(如果要变更多个成员,连续变更多次)