5.2 安装快照
流程详解
流程概览
当 Follower 落后太多或新节点加入集群时,就会触发安装快照,其流程如下:
Leader 向 Follower 发送
InstallSnapshot请求,并停止向 Follower 同步日志Follower 根据请求中的
URI逐一向 Leader 下载快照对应的文件集:2.1 创建连接 Leader 的客户端
2.2 发送
GetFile请求获取 Leader 快照的元数据2.3 创建
temp目录用于保存下载的临时快照2.3 根据元数据中的文件列表对比本地快照与远程快照的差异,获得下载文件列表
2.4 根据文件列表,逐一发送
GetFile请求下载文件并保存至临时快照2.5 待快照下载完成后,删除本地快照,并将临时快照
rename()成正式快照
Follower 回调用户状态机的
on_snapshot_load加载快照等待快照加载完毕后:
4.1 更新
applyIndex为快照元数据中的lastIncludedIndex4.2 删除
logIndex<=lastIncludedIndex的日志(即全部日志)4.3 将快照元数据中的节点配置设为当前节点配置
Follower 向 Leader 发送成功的
InstallSnapshot响应Leader 收到成功响应后更新 Follower 的
nextIndex为快照的lastIncludedIndex+ 1Leader 从
nextIndex开始继续向 Follower 发送日志

大文件下载
Follower 通过发送 GetFileRequest 从 Leader 下载文件,而当快照的文件较大时(超过单个分片大小),这时候就会开启分片下载。每次通过设置 GetFileRequest 中的 count 和 offset 来实现分片下载,默认每个分片为 128KB,其受配置项 raft_max_byte_count_per_rpc 控制:
断点续传
Follwer 从 Leader 下载的快照文件会保存在临时快照 temp 目录中,如果 Follower 下载了一部分后 Crash,在重启后重新接收 InstallSnapshot 开始下载快照时,其不会删除 temp 目录,而是对比本地临时快照和远程快照的元数据,对于那些本地已经存在且 CRC 一样的文件,则无需重复下载:

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

总的来说,为了减少网络的传输,只要本地存在的文件,其文件名和 CRC 和 Leader 的一样就无需重复下载,详见以下过滤下载列表。
快照限流
当一个 Raft 进程挂掉一段时间后重启,其可能会从 Leader 下载快照。特别地,当一个进程上跑着大量的 Raft Group,而每一个 Node 都需要从 Leader 下载快照,这时候下载的数据量将是庞大的,可能会占满 Leader 和 Follower 的网卡和磁盘带宽,影响正常的 IO。为此,braft 提供了相应的快照限流特性。
限流作用于以下两个维度:
任务个数:节点每开启一个安装快照任务,任务计数将加一(该限制仅作用于 Follower)
带宽:Leader 读取本地快照、Follower 通过网络从 Leader 下载文件(文件会写入临时快照),带宽计数将增加对应的字节数。总的来说,其作用的是磁盘带宽和网络带宽
快照限流默认是关闭的,用户需要实现 SnapshotThrottle,并在构建 Node 时将其通过 NodeOptions 传递给 braft:
当然了,框架也提供了默认的 SnapshotThrottle 实现,即 ThroughputSnapshotThrottle,具体算法实现见限流算法,用户构造时可控制带宽大小:
此外,ThroughputSnapshotThrottle 还提供了一些动态配置项来控制并发任务个数以及带宽大小:
由于传递的限流对象是一个指针,用户可以通过指定哪些 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 将元数据填充到 proto(LocalSnapshotPbMeta)中,并将其序列化,最终持久化到文件:
删除上一个快照
调用 unref 删除上一个快照。需要注意的是,当前节点可能是 Leader,而该快照可能正用于下载给其他 Follower,所以需要判断其引用计数,若引用计数为 0,则删除其目录:
阶段四:Follower 加载快照
在将下载来的临时快照转为正式快照后,节点就开始加载快照。加载快照的流程我们在<5.3 加载快照>已有详细介绍,在这里就不重复介绍了。
当快照加载完毕后,运行 Closure 时会发送 InstallSnapshot 响应给 Leader。
阶段五:Leader 处理响应
Leader 在收到 InstallSnapshot 响应后,会调用 _on_install_snapshot_returned 进行处理。在该函数中会根据响应的结果,做出不同的决策:
Last updated