5.1 创建快照

流程详解

流程概览

快照有压缩日志和加速启动的作用,其创建流程如下:

  1. 当快照定时器超时或用户手动调用接口,会触发节点执行创建快照的任务

  2. 节点会将任务放进 ApplyTaskQueue,等待其被执行

  3. 当任务被执行时,会创建一个 temp 目录用来保存临时快照,并返回一个 SnapshotWriter

  4. SnapshotWriterClosure 做为参数调用用户状态机的 on_snapshot_save

  5. 用户需通过 SnapshotWriter 将状态数据写入到临时快照中:

    • 5.1 调用 SnapshotWriter::get_path 获得快照目录绝对路径,并将快照文件写入该目录

    • 5.2 调用 SnapshotWriter::add_file 将快照文件相对路径添加至快照元数据中

  6. 待数据写入完成,用户需回调 Closure 将其转换为正式快照:

    • 6.1 将快照元数据持久化到文件

    • 6.2 通过 rename() 将临时快照转换为正式快照

    • 6.3 删除上一个快照

    • 6.4 删除上一个快照对应的日志

  7. 至此,快照创建完成

流程整体分为以下 3 个阶段:创建临时快照(1-3),用户写入数据(4-5),转为正式快照(6-7)

图 5.1 创建快照整体流程

快照结构

图 5.2 快照结构

正常情况下用户指定的快照存储目录下只会有一个快照目录。当创建快照时,会创建一个 temp 目录来保存临时快照,待创建完成后,通过 rename 将其转换为正式快照。

正式快照目录以当时创建快照时的 applyIndex 命名,如 snapshot_00000000000000002000

快照目录下除了用户写入的快照文件集合外,还有一个框架写入的元数据文件 __raft_snapshot_meta,元数据主要保存以下 2 部分信息:

  • 快照中每一个文件的相对路径

  • 快照中包含的最后日志的 indexterm,以及集群配置

参见以下元数据 proto

相关接口

用户手动触发快照:

用户需要实现的快照函数:

SnapshotWriter 相关接口:

阶段一:创建临时快照

触发快照任务

节点有以下 2 种方式触发快照任务,其最终都是调用 NodeImpl::do_snapshot 执行快照任务。

  • 快照定时器超时:

  • 用户手动触发:

执行快照任务

NodeImpl::do_snapshot 会调用 SnapshotExecutor::do_snapshot 执行快照任务。在正式创建快照前会做一些判断,决定是否要创建快照;若确定要创建快照,则:

  • 创建 temp 目录用于保存临时快照

  • 将创建快照任务放入 ApplyTaskQueue 中等待被执行

创建临时目录

LocalSnapshotStorage::create 会创建 temp 目录,若事先已存在 temp 目录,则先将其删除后再创建,并返回 SnapshotWriter

任务入队执行

创建好 temp 目录后,会调用 FSMCaller::on_snapshot_save 将快照任务放入 ApplyTaskQueue,等待其被执行:

队列消费函数会调用 FSMCaller::do_snapshot_save 执行快照任务:

FSMCaller::do_snapshot_save 函数中主要做以下 2 件事:

  • 准备好元数据,将其保存在 Closure

  • 将上述创建的 SnapshotWriterClosure 作为参数调用用户状态机的 on_snapshot_save

阶段二:用户写入数据

on_snapshot_save

用户需要实现状态机的 on_snapshot_save 函数,在该函数中用户需要做以下 3 件事:

  • 调用 writer->get_path 获取临时快照的目录路径,并将快照文件写入到该目录下

  • 调用 write->add 将快照中每一个文件的相对路径加入到快照元数据中

  • 待以上全部完成后,调用 done->Run 将临时快照转换为正式快照

get_path

get_path 接口会返回临时快照(temp)目录的绝对路径,用户需要在该目录中写入快照文件:

add_file

add_file 接口只是将文件路径添加到元数据中而已:

阶段三:转为正式快照

调用 Closure

用户完成快照的创建后,需调用 Closure,即 SaveSnapshotDone::Run(),而该函数会将临时快照 rename 成正式快照,并删除上一个快照,以及删除上一个快照对应的日志。

用户调用 SaveSnapshotDone::Run,该函数主要执行以下 2 件事:

on_snapshot_save_done 会完成以下几项工作:

save_meta 会将元数据保存到 LocalSnapshotMetaTable 中:

写入元数据

rename 成正式快照

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

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

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

  • (3) 删除上一个快照

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

删除上一个快照

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

删除上一个快照对应日志

Closure 中最后会调用 set_snapshot 删除上一个快照的日志,之所以只删除上一个快照的日志,而不立即删除当前快照的日志,主要考虑到有些 Follower 还没有同步完日志,如果删除了当前的日志,哪怕只差几条日志也只能发送快照进行同步,见以下注释:

Last updated