TiDB 工具 | 备份的 “算子下推”:BR 简介
BR 选择了在 Transaction KV 层面进行扫描来实现备份,这样,备份的核心便是分布在多个 TiKV 节点上的 MVCC Scan:简单,粗暴,但是有效,它生来就继承了 TiKV 的诸多优势:分布式、利于横向拓展、灵活(可以备份任意范围、未 GC 的任意版本的数据)等等优点。
相较于从前只能使用 mydumper 进行 SQL 层的备份,BR 能够更加高效地备份和恢复:它取消了 SQL 层的开销,同时支持备份索引,而且所有备份都是已经排序的 SST 文件,以此大大加速了恢复。
BR 的实力在之前的文章(https://pingcap.com/zh/blog/cluster-data-security-backup)中已经展示过了,本文将会详细描述 BR 备份侧的具体实现:简单来讲,BR 就是备份的 “算子下推”:通过 gRPC 接口,将任务下发给 TiKV,然后让 TiKV 自己将数据转储到外部存储中。
BR 的基本流程
接口
Backup
的接口,这个接口与一般的读请求不同——它不会返回数据给客户端,而是直接将读到的数据存储到指定的存储器(External Stroage)中:service Backup {
// 收到 backup 的 TiKV,将会将 Request 指定范围中,所有自身为 leader
// 的 region 备份,并流式地返回给客户端(每个 region 对应流中的一个 item)。
rpc backup(BackupRequest) returns (stream BackupResponse) {}
}
// NOTE:隐藏了一些不重要的 field 。
message BackupRequest {
// 备份的范围,[start_key, end_key)。
bytes start_key = 2;
bytes end_key = 3;
// 备份的 MVCC 版本。
uint64 start_version = 4;
uint64 end_version = 5;
// 限流接口,为了确保和恢复时候的一致性,限流限制保存备份文件的阶段的速度。
uint64 rate_limit = 7;
// 备份的目标存储。
StorageBackend storage_backend = 9;
// 备份的压缩 -- 这是一个用 CPU 资源换取 I/O 带宽的优化。
CompressionType compression_type = 12;
int32 compression_level = 13;
// 备份支持加密。
CipherInfo cipher_info = 14;
}
message BackupResponse {
Error error = 1;
// 备份的请求将会返回多次,每次都会返回一个已经完成的子范围。
// 利用这些信息可以统计备份进度。
bytes start_key = 2;
bytes end_key = 3;
// 返回该范围备份文件的列表,用于恢复的时候追踪。
repeated File files = 4;
}
客户端
依据每个 table 的所有 data key 生成 range。(所有带有 t{table_id}_r 前缀的 Key)
依据每个 index 的所有 index key 生成 range。(所有带有 t{table_id}_i{index_id} 前缀的 Key)
如果 table 存在 partition(这意味着,它可能有多个 table ID),对于每个 partition,按照上述规则生成 range。
在之前的“粗粒度备份”中,BR 客户端每收到一个 BackupResponse 就会将其中的 [start_key, end_key) 作为一个 range 存入一颗区间树中(你可以把它想象成一个简单的 BTreeSet<(Vec
, Vec )。)> “粗粒度备份” 遇到任何可重试错误都会忽略掉,只是相应的 range 不会存入这颗区间树中,因此树中会留下一个 “空洞”,这两步的伪代码如下。
func Backup(tree RangeTree) {
// ...
for _, resp := range responses {
if resp.Success {
tree.Insert(resp.StartKey, resp.EndKey)
}
}
}
// An example:
// When backing up the ange [1, 5).
// [1, 2), [3, 4) and [4, 5) successed, and [2, 3) failed:
// The Tree would be like: { [1, 2), [3, 4), [4, 5) },
// and the range [2, 3) became a "hole" in it.
//
// Given the range tree is sorted, it would be easy to
// find all holes in O(n) time, where n is the number of ranges.在 “粗粒度备份” 结束之后,我们遍历这颗区间树,找到其中所有 “空洞”,并行地进行 “细粒度备份”:
找到包含该空洞的所有 region。
对他们的 leader 发起 region 相应范围的 Backup RPC。
成功之后,将对应的 range 放入区间树中。
TiKV
BackupRequest
会被转化为一个Task
。Task
中的start_key
和end_key
生成一个叫做 “Progress
” 的结构:它将会把Task
中庞大的范围划分为多个子范围,通过:扫描范围内的 Region。 对于其中当前 TiKV 角色为 Leader 的 Region,将该 Region 的范围作为 Backup 的子任务下发。
Progress
提供的接口是一个使用 “拉模型” 的接口:forward
。随后,TiKV 创建的各个 Backup Worker 将会去并行地调用这个接口,获得一组待备份的 Region,然后执行以下三个步骤:对于这些 Region,Backup Worker 将会通过 RaftKV 接口,进行一次 Raft 的读流程,最终获得对应 Region 在 Backup TS 的一个 Snapshot。(Get Snapshot)
对于这个 Snapshot,Backup Worker 会通过 MVCC Read 的流程去扫描 backup_ts 的一致版本。这里我们会扫描出 Percolator 的事务,为了恢复方便,我们会准备 “default” 和 “write” 两个临时缓冲区,分别对应 TiKV Percolator 实现中的 Default CF 和 Write CF。(Scan)
然后,我们会先将扫描出来的事务中两个 CF 的 Raw Key 刷入对应缓冲区中,在整个 Region 备份完成(或者有些 Region 实在过大,那么会在途中切分备份文件)之后,再将这两个文件存储到外部存储中,记录它们对应的范围和大小等等,最后返回一个 BackupResponse 给 BR。(Save)
s3://some-bucket/some-folder
,可以指定备份到 S3 云盘上的some-bucket
之下的some-folder
目录中。BR 的挑战和优化
BackupMeta 和 OOM
message BackupMeta {
// Some fields omitted...
// An index to files contains data files.
MetaFile file_index = 13;
// An index to files contains Schemas.
MetaFile schema_index = 14;
// An index to files contains RawRanges.
MetaFile raw_range_index = 15;
// An index to files contains DDLs.
MetaFile ddl_indexes = 16;
}
// MetaFile describes a multi-level index of data used in backup.
message MetaFile {
// A set of files that contains a MetaFile.
// It is used as a multi-level index.
repeated File meta_files = 1;
// A set of files that contains user data.
repeated File data_files = 2;
// A set of files that contains Schemas.
repeated Schema schemas = 3;
// A set of files that contains RawRanges.
repeated RawRange raw_ranges = 4;
// A set of files that contains DDLs.
repeated bytes ddls = 5;
}
meta_files
将自身指向下一个节点:File
是一个到外部存储中其他文件的引用,包含文件名等等基础信息。GC, GC never changes
备份压缩
限流与隔离
--ratelimit
参数启动 BR 的时候,TiKV 侧的第三步 “Save”,将会被限流,与此同时也会限制之前步骤的流量。ratelimit
限流施加于 Save 阶段,因此是限制写备份数据的速度。总结