githubEdit

5.2 安装快照

流程详解

流程概览

当 Follower 落后太多或新节点加入集群时,就会触发安装快照,其流程如下:

  1. Leader 向 Follower 发送 InstallSnapshot 请求,并停止向 Follower 同步日志

  2. Follower 根据请求中的 URI 逐一向 Leader 下载快照对应的文件集:

    • 2.1 创建连接 Leader 的客户端

    • 2.2 发送 GetFile 请求获取 Leader 快照的元数据

    • 2.3 创建 temp 目录用于保存下载的临时快照

    • 2.3 根据元数据中的文件列表对比本地快照与远程快照的差异,获得下载文件列表

    • 2.4 根据文件列表,逐一发送 GetFile 请求下载文件并保存至临时快照

    • 2.5 待快照下载完成后,删除本地快照,并将临时快照 rename() 成正式快照

  3. Follower 回调用户状态机的 on_snapshot_load 加载快照

  4. 等待快照加载完毕后:

    • 4.1 更新 applyIndex 为快照元数据中的 lastIncludedIndex

    • 4.2 删除 logIndex<=lastIncludedIndex 的日志(即全部日志)

    • 4.3 将快照元数据中的节点配置设为当前节点配置

  5. Follower 向 Leader 发送成功的 InstallSnapshot 响应

  6. Leader 收到成功响应后更新 Follower 的 nextIndex 为快照的 lastIncludedIndex + 1

  7. Leader 从 nextIndex 开始继续向 Follower 发送日志

图 5.3 安装快照 RPC 交互

大文件下载

Follower 通过发送 GetFileRequest 从 Leader 下载文件,而当快照的文件较大时(超过单个分片大小),这时候就会开启分片下载。每次通过设置 GetFileRequest 中的 countoffset 来实现分片下载,默认每个分片为 128KB,其受配置项 raft_max_byte_count_per_rpc 控制:

断点续传

Follwer 从 Leader 下载的快照文件会保存在临时快照 temp 目录中,如果 Follower 下载了一部分后 Crash,在重启后重新接收 InstallSnapshot 开始下载快照时,其不会删除 temp 目录,而是对比本地临时快照和远程快照的元数据,对于那些本地已经存在且 CRC 一样的文件,则无需重复下载:

图 5.3 断点续传

进一步地,对于本地正式快照已经存在的文件也无需重复下载。之所以要对比本地正式快照,是因为该快照可能也来自于 Leader 之前的快照:

图 5.4 断点续传

总的来说,为了减少网络的传输,只要本地存在的文件,其文件名和 CRC 和 Leader 的一样就无需重复下载,详见以下过滤下载列表

快照限流

当一个 Raft 进程挂掉一段时间后重启,其可能会从 Leader 下载快照。特别地,当一个进程上跑着大量的 Raft Group,而每一个 Node 都需要从 Leader 下载快照,这时候下载的数据量将是庞大的,可能会占满 Leader 和 Follower 的网卡和磁盘带宽,影响正常的 IO。为此,braft 提供了相应的快照限流特性。

限流作用于以下两个维度:

  • 任务个数:节点每开启一个安装快照任务,任务计数将加一(该限制仅作用于 Follower)

  • 带宽:Leader 读取本地快照、Follower 通过网络从 Leader 下载文件(文件会写入临时快照),带宽计数将增加对应的字节数。总的来说,其作用的是磁盘带宽和网络带宽

快照限流默认是关闭的,用户需要实现 SnapshotThrottlearrow-up-right,并在构建 Node 时将其通过 NodeOptions 传递给 braft:

当然了,框架也提供了默认的 SnapshotThrottle 实现,即 ThroughputSnapshotThrottlearrow-up-right,具体算法实现见限流算法arrow-up-right,用户构造时可控制带宽大小:

此外,ThroughputSnapshotThrottlearrow-up-right 还提供了一些动态配置项来控制并发任务个数以及带宽大小:

由于传递的限流对象是一个指针,用户可以通过指定哪些 Node 共用同一个限流对象,来实现各个级别的限流策略,如针对每块盘或每个 Raft Group

相关 RPC

安装快照 RPC:

下载文件 RPC:

阶段一:Leader 下发指令

触发安装快照

Leader 在给 Follower 同步日志时,发现 Follower 需要的日志已经被压缩掉了,就会调用 _install_snapshot 向 Follower 下发安装快照的指令:

发送请求

_install_snapshot 会向 Follower 发送 InstallSnapshot 请求,详见以下注释:

generate_uri_for_copy 会返回下载的 URI,其格式为 remote://ip:port/reader_id

处理请求

Follower 在接收到 InstallSnapshot 请求后,会调用 handle_install_snapshot_request 处理,而该处理函数最终会调用 SnapshotExecutor::install_snapshot 来安装快照:

install_snapshot 主要执行以下几件事:

阶段二:Follower 下载快照

准备下载快照

register_downloading_snapshot 会创建客户端,并启动客户端来下载快照:

初始化客户端

LocalSnapshotCopier::init 会初始化一个 RPC Client 用于下载快照:

启动客户端

LocalSnapshotCopier::start 会创建单独的 bthread 来运行 LocalSnapshotCopier::copy,而该函数就正式开始下载快照:

下载快照流程

copy 函数中描述的是整个下载流程,待全部下载完毕后,会调用 LocalSnapshotStorage::close 将临时快照转为正式快照,其中的每一个步骤,我们都将在下面一一介绍:

下载快照元数据

调用 RemoteFileCopier::start_to_copy_to_iobuf 下载快照元数据,等待其下载完成后,保存至 _remote_snapshot

安装快照流程中的所有下载操作都会调用 start_to_copy_to_iobuf,该函数接受一个 source,向 Leader 发送 GetFile 请求,将下载的内容保存在 dest_buf 中。特别地,针对大文件,会采用分片的方式进行传输:

过滤下载列表

在正式下载文件前,我们会过滤掉本地拥有的文件,具体规则详见以上断点续传

创建临时目录

首先会调用 LocalSnapshotStorage::create 创建一个 temp 目录用来保存下载的临时快照,并返回 SnapshotWriter。注意,我们在创建本地快照时,也有一样的创建流程。唯一的区别在于用于保存下载快照的 temp 目录即使事先存在也不会删除,主要是为了实现我们上面提到的断点续传功能:

遍历用户指定的快照目录,保存最近的一个快照,删除其余全部快照:

逐一下载文件

在上面的下载快照流程 copy 函数中,我们已经介绍过,会根据快照元数据中的文件列表,逐一从 Leader 下载快照文件:

调用 list_files 获取快照元数据中的文件列表:

调用 copy_file 下载指定文件,参见以下详情注释:

阶段三:转为正式快照

在上面的下载快照流程 copy 函数中,我们已经介绍过,当快照中的所有文件都下载完毕后,就会调用 LocalSnapshotStorage::close 将下载的临时快照转为正式快照:

写入元数据

rename 成正式快照

close 函数中主要以下做四件事:

  • (1) 将元数据持久化到文件

  • (2) 通过 rename() 将临时快照变为正式快照

  • (3) 删除上一个快照

sync 会调用 save_to_file 将元数据填充到 protoLocalSnapshotPbMeta)中,并将其序列化,最终持久化到文件:

删除上一个快照

调用 unref 删除上一个快照。需要注意的是,当前节点可能是 Leader,而该快照可能正用于下载给其他 Follower,所以需要判断其引用计数,若引用计数为 0,则删除其目录:

阶段四:Follower 加载快照

在将下载来的临时快照转为正式快照后,节点就开始加载快照。加载快照的流程我们在<5.3 加载快照>arrow-up-right已有详细介绍,在这里就不重复介绍了。

当快照加载完毕后,运行 Closure 时会发送 InstallSnapshot 响应给 Leader。

阶段五:Leader 处理响应

Leader 在收到 InstallSnapshot 响应后,会调用 _on_install_snapshot_returned 进行处理。在该函数中会根据响应的结果,做出不同的决策:

Last updated