一次性吃透分布式一致性Raft协议

Raft是一种简单易懂的一致性算法,它通过领导人选举、日志复制和安全性保证分布式系统的强一致性。在选举过程中,随机选举超时避免选票瓜分,确保领导人权威。日志复制则通过领导者处理所有客户端请求,确保集群中所有节点日志的一致。此外,选举限制策略防止数据丢失,保证新领导人拥有所有已提交的日志。Raft还支持配置变更和处理网络分区问题,确保系统的稳定性和可用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

Raft是一种管理复制日志的一致性算法,它提供了和 Paxos 算法相同的功能和性能,但是它的算法结构和 Paxos 不同,使得 Raft 算法更加容易理解并且更容易构建实际的系统。为了提升可理解性,Raft 将一致性算法分解成了几个关键模块:领导人选举日志复制安全性。同时它通过实施一个更强的一致性来减少需要考虑的状态的数量(相对于 Paxos,Raft减少了非确定性和服务器互相处于非一致性的方式)。

Raft的一些概念

Raft协议是leader-follower模式,即一个leader节点和多个follower节点的模式。每个节点都会维护一个状态机,该状态机一共有3中状态,分别为leader状态、follower状态和candidate状态,任意一个节点都处于这三个状态之一。
在这里插入图片描述
状态列表:

  • Leader(领导人):负责处理所有客户端请求,当接收到写入请求时,本地处理后再同步至其他节点。
  • Follower(跟随者):是响应来自leader和candidate的请求。也不会处理client的请求,而是将请求重定向到leader节点进行处理。
  • Candidate(候选人): 当follower节点长时间没有收到leader节点发送的心跳时,则该节点的选举计时器会过期,同时自身状态会转变为candidate,并发起新一轮的选举。

其他概念:

  • 选举超时时间(election timeout): 每个follower节点接收不到leader节点心跳消息,并不会立即发起新一轮选举,而是选举超时时间过期后切换为candidate状态,再发起新一轮选举。这主要是因为leader节点发送的心跳消息可能因为网络延迟或者程序卡顿而迟到或者丢失。这个值一般设置为150ms-300ms之间的随机数。
  • 心跳时间(heartbeat timeout): 这个时间是指leader节点向集群其他follower节点发送心跳消息的时间间隔。
  • 任期(Term): 任期实际上是一个全局的、连续递增的整数,在Raft协议中每进行一次选举,任期数就会加1,在每个节点中都会记录当前的任期值。每个任期都是从一次选举开始的,在选举时会出现一个或者多个candidate节点尝试成为leader节点,如果其中一个candidate节点赢得选举,该节点就会切换为leader状态并成为该任期的leader节点,直到该任期结束。
复制状态机

一致性算法是从复制状态机的背景下提出的,在分布式系统中,我们为了保证服务的容错性,通常的做法是让一个服务的多个副本同时运行在多个节点上,为了保证多个副本运行的状态都是一致的,即保证客户端请求到任何一台机器上的返回结果都是一样的,所以采用复制状态机的机制。
在这里插入图片描述
复制状态机是通过复制日志来实现的,每个服务节点都存储一个相同且顺序一致的日志。因为每个日志中的命令都相同并且顺序一致,状态机会按照日志的顺序执行,这样子就可以得到相同的状态和输出结果,也因此服务器集群看起来形成一个高可用的状态机。

Raft的工作就是保证复制日志的一致性,服务器上的consensus模块接收来自客户端的命令,并将它们添加到日志中。随后该服务器与其他服务器上的consensus模块通信,以确保每个服务器上具有相同的日志序列,即使有小部分的服务器通信失败。每个服务器上的状态机按顺序执行命令,并将输出返回给客户端,这样就形成了高可用的复制状态机。

领导人选举

在这里插入图片描述
通常leader选举会发生在两种情况下,第一种是Raft集群启动时,这时所有的节点都是follower节点;第二种是leader节点宕机,follower没有收到leader的心跳。接下来我们来看一下Raft是如何做leader选举的。

  • 第一阶段:所有节点都是follower节点

在这里插入图片描述
初始状态集群的所有节点都是follower并且term都是0,同时启动选举定时器,每个节点的选举定时器超时时间都是150ms-300ms之间的随机数。这种随机选举超时时间主要是为了避免选票瓜分的情况,所谓选票瓜分可以理解为:当多个follower同时成为candidate,那么选票可能会被瓜分以至于没有candidate可以赢得大多数节点的支持。这时candidate会发生选举超时,然后增加当前任期号来开始一轮新的选举,如果没有额外的机制,每个candidate发生选举超时都会进行下一轮选举,这时就会出现无限选票瓜分的情况。为了解决这个问题所以引入随机选举超时时间的机制,这样可以把选举超时都分散开,在大多数情况下只有一个candidate会选举超时。

  • 第二阶段:follower 转为candidate并发起投票

在这里插入图片描述
follower节点在一个选举定时器周期内没有收到来自leader的心跳或者candidate的投票请求,则term+1并从follower状态变为candidate状态,并向集群中所有节点发送投票请求并且重置选举定时器。第一个阶段中也提到选举定时器的时间是150ms-300ms的随时数,最先转为candidate并发起投票请求的节点将具有成为leader的先发优势。

  • 第三阶段:投票策略

在这里插入图片描述
首先每个follower刚成为candidate的时候会将票投给自己,节点收到投票会按照一定规则来选择是否接受投票:请求节点的term大于自己的term,且自己尚未投票给其它节点,则接受请求,把票投给它;请求节点的term小于自己的term,且自己尚未投票,则拒绝请求,将票投给自己。

  • 第四阶段:candidate转为leader

在这里插入图片描述
一轮选举过后,正常情况下,会有一个candidate收到超过半数节点(N/2+1)的投票,它将胜出并升级为leader。然后定时发送心跳给其它的节点,其它节点会转为follower并与leader 保持同步,到此,本轮选举结束。

日志复制

当leader被选举出来,它就开始为客户端提供服务。客户端的每一个请求都包含一条被状态机执行的指令,leader把这条指令作为一条新的日志条目附加到日志中,然后并行地向其它节点发起 AppendEntries RPC 。当这条日志条目被安全地复制,leader会应用这条日志条目到它的状态机中(状态机执行该指令),然后把执行的结果返回给客户端。如果follower崩溃或者运行缓慢、网络丢包,领导人会不断地重试发送 AppendEntries RPC(即使 Leader 已经回复了客户端)直到所有的follower最终都存储了所有的日志条目。
在这里插入图片描述

  • 第一阶段:客户端请求提交到leader

在这里插入图片描述
首先leader会接收到客户端的请求,比如存储数据10。leader在收到请求后,会将它作为日志条目(Entry)写入本地日志中。此时该Entry的状态是未提交(Uncommitted),leader并不会更新本地数据,因此它是不可读的。

  • 第二阶段:leader将Entry发送到其它follower

在这里插入图片描述
leader与follower之间保持着心跳联系,随心跳leader将追加的Entry(AppendEntries)并行地发送给其它的follower,并让它们复制这条日志条目。

  • 第三阶段:leader等待follower回应

在这里插入图片描述
follower接收到leader的请求之后,将日志写入本地,此时该Entry的状态也是未提交(Uncommitted),当leader收到大多数 follower的回应后,会将第一阶段写入的Entry标记为提交状态(Committed),并把这条日志条目应用到它的状态机中。

  • 第四阶段:leader回应客户端

在这里插入图片描述
完成前三个阶段后,leader会向客户端回应 OK,表示写操作成功。

  • 第五阶段:leader通知 follower Entry已提交

在这里插入图片描述
leader回应客户端后,将随着下一个心跳通知follower,follower收到通知后也会将Entry标记为提交状态。至此,Raft 集群超过半数节点已经达到一致状态,可以确保强一致性。

选举限制的安全性

由于Raft算法通过强制覆盖follower日志来保证数据一致性,从来不会覆盖或者删除自己的日志。如果一个具有少数日志条目的节点当选为leader ,那么就会造成大量的数据丢失,为了避免发生这种情况,Raft 在领导选举时会增加一些限制条件,保证任何的leader对于给定的任期号,都拥有之前任期所有被提交的日志条目。

Raft 中日志条目的传送是单向的,只能由leader传递给follower,并且leader从不会覆盖自身本地日志中已经存在的条目(只会追加)。因此Raft使用投票的方式来阻止一个candidate赢得选举,除非这个候选人包含了所有已经提交的日志条目。Candidate为了赢得选举必须联系集群中的大部分节点,这意味着每一个已经提交的日志条目在集群中肯定存在于至少一个节点上。如果candidate的日志和大多数的节点一样新,那么他一定持有了所有已经提交的日志条目。Raft通过比较两份日志中最后一条日志条目的lastLogIndexterm来判断谁的日志比较新。

  • 如果两份日志最后的条目的term不同,那么term较大的日志是最新的;
  • 如果两份日志最后条目term的相同,那么拥有较大lastLogIndex的日志是最新的。
集群成员变化

在项目运行中,我们可能会改变集群的配置,例如增加节点或机器的配置。尽管可以通过使整个集群下线,更新所有配置,然后重启整个集群的方式来实现,但是在更改期间集群会不可用。另外,如果存在手工操作步骤,那么就会有操作失误的风险。为了避免这些的问题,Raft将配置变更自动化归并到算法中。
在这里插入图片描述
如上图,Server 1和Server 2可能以C-old配置选出一个主,而Server 3,Server 4和Server 5可能以C-new选出另外一个主,导致出现双主,这显然是违背了Raft的思想。
在这里插入图片描述
为了保证安全性,Raft 配置更改采用两阶段方法。在配置变更过程中新老配置互相无法感知,而配置更替也无法一蹴而就。所以在配置更替前,将集群引导入一个过渡阶段,使得使用新配置和旧配置的机器都不会独立地处理日志。具体的切换过程如下:

  • leader收到配置变更请求时,创建包含包含新旧配置的日志C-old-new并复制给其他节点
  • follower以日志中存在的最新的配置做决定,即使该配置未被提交,leader只有在C-old-new复制到大多数节点后才以这个配置做决定,这时处于共同决定的过程
  • 之后提交新配置到所有节点,一旦新配置被提交,旧配置就会被抛弃

一旦一个服务器将新的配置日志条目增加到它的日志中,他就会用这个配置来做出未来所有的决定(服务器总是使用最新的配置,无论它是否已经被提交)。共同一致允许独立的服务器在不影响安全性的前提下,在不同的时间进行配置转换过程。此外,共同一致可以让集群在配置转换的过程中依然响应客户端的请求。

网络分区

在这里插入图片描述
在一个Raft集群中,如果有部分节点的网络发生故障,与集群中另一部分节点的连接中断,形成相对独立的子网,就会出现网络分区现象,Raft算法对不同情况下的网络分区具有不同的应对方案。

  • leader节点在少数节点分区中

假设现在Raft集群的leader是N1,leader节点被分在了少数节点分区中,那么随着时间的流逝,N3、N4、N5节点没有收到N1的心跳,随机选举定时器就会超时从而发起新一轮的选举,假设这个时候N5变成了leader,那么这个Raft集群中包含了两个leader节点明显是违背协议的。Raft需要让当前leader并且是有效的leader来处理请求,leader节点会发起一次广播来联系到大部分的节点,来继续维护自己的权威,在上图中因为发生了网络分区,N1所在的区无法联系到集群中大部分的节点,所以请求会被转到N5来处理,最终N1存放的是发生网络分区之前的数据,最新的数据存在了N5分区中保存。当网络故障修复之后,N1的心跳对于N3、N4、N5节点还是可以收到,但是N1的term是小于N3、N4、N5节点的term的,因此会被N3、N4、N5节点忽略掉;同时N5的心跳对于N1、N2是可以收到的,并且term是大于N1、N2节点的term,这个时候N1、N2节点会被切换成follower,至此N5成为整个集群的leader。

  • leader节点在多数节点分区中

如果网络分区时,leader节点被划分到节点较多的分区中,此时节点较少的分区中,会有节点的选举计时器超时,切换成 candidate并发起新一轮的选举。但是由于该分区中节点数不足半数,所以无法选举出新的leader,从而导致该分区内的节点不断发起选举,term号不断增长。Raft 协议对这种情况进行了处理,当某个节点要发起选举之前,需要先进入PreVote的状态,节点会先尝试连接集群中的其他节点,如果能够成功连接到半数以上的节点,才能切换成candidate身份真正发起新一轮的选举。

日志压缩

当系统中的日志越来越多后,会占用大量的空间。Raft 算法采用了快照机制来压缩庞大的日志,在某个时间点,将整个系统的所有状态稳定地写入到可持久化存储中,然后这个时间点后的所有日志全部清除。在这里插入图片描述
通常服务器都是独立地创建快照,但是leader也会偶尔发送快照给一些落后的节点,例如一个运行缓慢的follower或者新加入集群的服务器,通过网络发送快照让该follower更新到最新的状态。leader使用Snapshot RPC分块发送快照给太落后的follower,如果快照中包含重复的日志条目,那么follower会删除日志中存在的条目,采用快照中的数据。

总结

Raft算法的实现原理清晰,在逻辑上比较遵循人的直觉,描述也很细致,考虑到了一些边界问题,这些不仅提升了Raft的可理解性,也令人相信其正确性。

Raft算法将共识问题分解成数个相对独立的字问题,总体流程是节点选举出leader,由leader负责日志的复制与提交。为了在任何异常情况下系统不出错,Raft对领导选举与日志复制实施诸多约束:

  • 利用随机选举超时时间避免选票瓜分;
  • 使用一致性检查处理日志不一致问题;
  • 通过选举限制策略保证新leader数据的最新性;
  • 利用最小选举超时时间保证leader的权威性。

这种将复杂问题分解化的设计思想很好地描述了Raft是如何解决分布式系统中的一致性问题,并提出了一定的解决方案,帮助开发者更好地将其应用到工程中。

参考文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值