In Search of an Understandable Consensus Algorithm<二>——MIT6.824

In Search of an Understandable Consensus Algorithm<二>

Cluster membership changes

上一篇博文中,我们都假设集群配置是固定的。但在实践中往往需要更改配置,可能需要更换服务器或者更改备份配置。为了使配置变更机制更加安全,在过渡期间不存在任意一个时间点会存在两个leader,但任何从旧配置切换到新配置的方法都是不够安全的,在切换期间可能会存在分裂成两个集群,如图:

img

为了确保安全,配置更改可以使用两阶段的方法。在raft中,集群首先切换到过渡配置,即为joint consensus。一旦commit了joint consensus,系统就会切换到新配置。

  • 日志会被复制到两种配置中的所有服务器;
  • 两种配置中的任何服务器就可以成为leader;
  • 选举等协议需要新旧两种配置的大多数票;

我们使用复制的日志中特殊条目来存储和传送集群配置,下图就是配置更改过程:

img

当leader收到请求,从Cold配置更改为Cnew时,它会将联合共识即\(C_{old, new}\)存储为日志。如果leader crash了,新的leader会在\(C_{old}\)\(C_{old, new}\)中选择。

更改配置后还有三个问题需要解决;

  • 新服务器可能一开始不会存储任何日志;raft的解决方法是在更改配置之前引入一个额外的阶段,在该阶段新的服务器以非投票成员的身份加入集群,leader会将日志复制到它们,但不参与投票;
  • 集群leader可能并不属于新配置;在这种情况下,leader在提交了日志\(C_{new}\)之后就会返回到follower阶段;
  • 删除的服务器可能会破坏集群;这些服务器不再接受心跳,因此会超时用新的term发送RequestVote RPC请求选举,并且重复这个过程。为了避免这个问题,服务器会在当前leader存在时,忽略掉RequestVote RPC,不会更新term或者授予票数;

Log compaction

日志会在服务器运行期间无限增长,如果我们不及时丢弃过期的日志,那么对着日志的增长,它会占据更多的内存空间并需要更多时间来重新执行日志。

snapshot是最简单的压缩方法,下图就是raft的快照方式:

img

每个服务器独立获取快照,快照内容仅仅覆盖了已提交的条目。快照除了包括状态集信息之外,还包含了少量的元数据信息,如上图就包含了最近索引和term。包含了这些信息,可以帮助支持快照后第一个日志条目的AppendEntries一致性检查。

虽然服务器独立生成快照,但一般情况下,如果有一个落后非常多的follower或者新的服务器加入集群,leader会通过InstallSnapshot RPC往其它服务器发送快照。

img

如果snapshot中包含了follower未包含的日志新内容,该follower会丢弃整个日志,并用snapshot替代。如果接收者收到的snapshot是当前日志的前缀部分,则该快照后面的条目保留,其余删除。

如果是由leader生成snapshot再转发到各个follower,这种做法会浪费网络带宽并降低生成快照的速度。另外还有两个问题会影响性能:

  • 服务器必须决定何时进行快照。一个简单的策略是在日志达到固定大小(以字节为单位)时拍摄快照,此大小设置为远大于快照的预期大小,则用于快照的磁盘带宽开销将很小
  • 写快照可能需要很长时间,我们不希望这会延迟正常操作。解决方案是写时拷贝

Client interaction

本节主要描述raft客户端与raft的交互。

raft将所有的客户端请求发送到leader,如果客户端联系的不是leader,那么服务器会拒绝这一请求,并提供最新的leader地址。

我们对Raft的目标是实现可线性化的语义(即每个操作似乎在其调用和响应之间的某个时刻只执行一次)。但如果leader在提交日志条目之后但在响应客户端之前发生了冲突,则客户端将使用新的leader重试该命令,从而变成了二次执行。解决方案是客户端为每个命令分配唯一的序列号,如果它收到一个序列号已经执行的命令,它会立即响应而不重新执行请求。

只读操作可能会因为leader的重新选举而返回过时的数据,raft需要在不使用日志的情况下确保自己不返回过期的数据,这里采取两个措施:

  • 首先,leader必须拥有关于提交的日志的最新信息。虽然leader拥有所有提交了的日志,但leader不知道这是什么,Raft通过让每个leader在其任期开始时将空白的无操作日志条目输入到日志中来处理此问题。
  • 其次,leader必须在处理只读请求之前检查它是否已被废除;这个可以通过与大多数集群交换心跳来解决;