分布式系统学习小记5 —— Raft详解

“这是我自学算力大集群相关知识的第5篇笔记。在本篇中,我们将继续深入了解Raft的完整知识。”

摘要
  • Raft的基本结构

Raft通过网络使Leader与Follower之间相互沟通,可以简单将Raft协议的内容分成两个部分:第一部分为应用层内容,如一个包含了“键k/值v”的表;第二层即Raft层,则是一系列操作的日志。

如在一个拥有三个副本的系统中,即三台服务器,它们具有完全相同的结构,并且期望在上述两个层上都拥有完全相同的数据。

当客户端对服务器进行请求时,它们无需意识到正在与一个复制服务进行通信,即在客户端看来,它请求的对象可被视作一台完整的服务器。它们实际上是将请求发送给当前Leader副本在Raft中的应用层。

Leader在接收到请求后,将应用层的信息提交到Raft日志中,随即,Raft协议中的节点彼此进行通信交流,直至所有副本或多数副本将此新操作记录至其日志中,从而实现复制。此时每个Follower的Raft层都会将操作传递至本地应用层。

一旦大多数节点的回复收到并完成追加。即在包括Leader本身,只有三个副本的系统中,领导者只需等待另一个副本对操作日志追加条目请求做出肯定响应(不必等待剩余节点全部完成反馈)。Leader便会执行该命令,如对于GET请求,然后将回复发送回客户端。

*这里需要注意的是Raft机制本身并不具备流量控制功能,如果Follower节点的处理速度远小于Leader节点处理的速度,将导致日志无限增加,即Follower节点未操作的步骤将无限增加。

**服务器处理重复请求的一种合理方式是,服务器通过某种独特的请求编号或来自客户端的类似信息来维护索引表,以便服务器能够记住其已经见过这个请求并执行了它。所以在遇到重复请求时会将其忽略。


  • Leader的选举机制

实际上存在不区分Leader、Follower的技术方案,但在实际应用中,这样的方案往往效率较低。

对于Leader角色而言,在每一个任期内,可能存在该任期内没有Leader,也可能有一个Leader,但不能存在两个Leader。

系统中的服务器都维护着一个选举定时器,如果在整个选举周期内,服务器未收到来自当前Leader的任何消息,那么Follower便会推理当前Leader服务器可能已失效,并发起新一轮的选举,即决定成为候选者并率先发起新选举的服务器, 首先会递增其自身的任期。由于在一个任期内,不能存在多个Leader,因此需要开始一个新的任期。随后,候选者会发送请求投票的RPCS至系统中的各个服务器。

为了在一个任期内当选,候选者必须获得多数服务器的赞成票,且每个服务器在每个任期内仅会投出一次赞成票。一旦赢得选举,新Leader将立即向所有其他服务器发送一个Append Entries消息。此时,不允许任何人发送Append Entries,除非他们是当前任期的领导者。

此外,Append Entries操作同时也会重置所有节点的选举计时器,从而抑制任何新候选者的出现。通过随机化选举计时器,避免同时触发选举机制,导致每个服务器都投票给自己当Leader,从而导致无法选举出Leader的情况。

在一些特殊场景,如Leader并未失效,但由于网络速度较慢或丢失了一些心跳信号,可能会遇到选举计时器触发的情况。即如果举行新的选举,旧的Leader可能仍然存在,并且仍然认为自己是Leader。处于服务器数量较少分区的Leader会发送Append Entries,但它无法从包括自己在内的大多数服务器那里收到响应,因此它将永远不会提交该操作,不会执行它,也不会向客户端回复说已经执行了该操作。因此位于不同分区的旧服务器,客户端可能会发送请求,但它们将永远收不到响应。

*为什么不能选择拥有最长日志的服务器作为Leader?

虽然日志最长,但其包含的数据/内容很可能与大多数服务器上的内容不一致(如离线或其他情况所导致),若选此服务器未领导者,则可能会强制修改大部分服务器上的数据,这将导致整个系统的历史数据变化并可能会与客户端接收到的历史数据发生冲突。因为在大部分的服务器上,某些操作已确认提交,很可能已执行,且极有可能已向客户端发送了响应,即破坏了数据一致性。


  • 如何保证日志一致性

Leader仅在大部分节点提交已完成追加操作日志才会执行操作,随后才向客户端发送回复。在异常情况下,Leader可以直接将其完整日志发送给Follower以确保整体的一致性。

同时,对于选举,候选人需满足条件——在其日志的最新条目中具有更高的任期,或者具有相同的最新任期,且日志长度大于或等于接收投票请求的服务器的日志长度时才可以被选为新Leader。


  • 关于持久性(Persistent)

如果更改了一些内容,并将其标记为持久性,服务器会将其写入磁盘或某种其他非易失性存储介质,如SSD或带有电池备份的设备等,以确保在服务器重启时能够找到这些信息,并将其重新加载到内存中。其目的是为了让服务器在发生崩溃并重启后,能够从断点处继续工作。

为了追求Raft协议的简洁与安全,有些信息将会在崩溃时被丢弃,其性质相对于持久性,被称为易失性(volatile)。可被丢弃的信息必须满Leader能够通过检查自己的日志以及发送给Follower的追加条目结果来重建已提交的内容。

通常需要标记为持久性的内容有:

1.日志,因为其是应用状态唯一的记录;

2.当前任期与投票结果,这是为了确保每个任期最多只有一个Leader。

若每次变更都进行持久性记录,则可能因需要等待磁盘写入而影响系统性能,此场景下需要考虑写入策略(如批处理)或使用高速存储硬件如SSD等。


  • 关于快照

由于大多情况下,应用状态的大小很可能远小于对应的日志,日志在各服务器之间的传递可能会带来较大的网络开销或额外的性能问题。又或在某些情况下Follower的日志在Leader日志开始的位置之前就结束了。所以这里便引入快照的方式来优化日志机制。

如当Raft感知到当前日志变得过大时,Leader在特定日志条目处保存或请求应用程序保存其状态的一份副本,并将舍弃该时间点之前的所有内容,以此来形成一份快照。

当某些Follower刚刚启动,发现其日志较短时,Leader会向其发送追加条目,同时,Leader将进行回溯,若它已到达实际存储日志的起始点,无法匹配Follower当前的日志内容时,Leader将发送其当前的快照以及当前日志。


  • 线性一致性(Linearizability)

本次内容的最后,我们再了解一下“线性一致性”。

对于一个复制服务而言,什么才算是正确的行为?“线性一致性”可作为一种评判标准:若存在一个全序关系,则执行历史即为一系列客户端请求的序列,可能包含来自多个客户端的众多请求。

如果在历史记录中存在某种全序操作,且该全序与请求的实时顺序相匹配。如每进行一次读取,可将其视为从最近一次对该同一块数据进行写入后的值与最近写入顺序。则可以称之为具备线性一致性。


以上内容来自对 MIT 《分布式系统 6.824》的学习笔记,在此特别声明。