tidb-in-action

2.2.1 Lightning 工作原理

TiDB Lightning 工具支持高速导入 Mydumper 和 CSV 文件格式的数据文件到 TiDB 集群,导入速度可达每小时 300 GB,是传统 SQL 导入方式的 3 倍多。它有两个主要的目标使用场景:大量新数据的快速导入,以及全量数据恢复。

本节将介绍 TiDB Lightning 工具的工作原理。

1. 整体架构

组件概览

架构图

如上图所示,TiDB Lightning 工具包含两个组件:

那么,为什么要把一个流程拆分成两个组件呢?

总体而言,TiDB Lightning 工具的设计思路是,绕过 SQL 层,在线下将数据文件转化为键值对,并生成排好序的 SST 文件,直接推送到 TiKV 层的 RocksDB 里。这种批处理方式可以绕过 TiDB 层复杂的 SQL 和 事务处理,省却 TiKV 层线上排序等耗时步骤,提升数据导入过程的整体效率。

数据导入过程

导入流程图

  1. 导入数据之前,tidb-lightning 会自动将 TiKV 集群切换为“导入模式”(import mode)以优化写入效率。
  2. tidb-lightning 会在目标 TiDB 集群上创建好空数据库和表。
  3. 每张表的数据文件都会被分割为多个连续的批次,这样就能实现大表(200 GB 以上)的并行数据导入了。
  4. tidb-lightning 会并发读取数据文件,转换成与目标 TiDB 集群相同编码的键值对,然后发送到 tikv-importer。tidb-lightning 通过 gRPC 传递键值对给 tikv-importer。tikv-importer 会为每一个批次的键值对准备一个“引擎文件”(engine file)。
  5. 当一个引擎文件数据写入完毕,tikv-importer 便开始对目标 TiKV 集群进行 Region 分裂和调度,然后执行数据导入。有两种引擎文件:数据引擎与索引引擎,它们分别对应两种键值对:行数据和次级索引。通常,行数据在数据文件里是完全有序的,而次级索引则是无序的。因此,数据引擎文件在对应 Region 写入完成后会被立即上传,而索引引擎文件只有在整张表所有 Region 编码完成后才会执行导入。
  6. 一张表的两种引擎文件都完成了导入之后,tidb-lightning 会对比本地数据文件及目标 TiDB 集群数据的 Checksum,确保数据完整性;然后,让 TiDB 运行 ANALYZE TABLE 命令更新表和索引的统计信息,为后续 TiDB 生成正确的 SQL 执行计划做好准备。同时,tidb-lightning 会调整表的 AUTO_INCREMENT 值防止后续新增数据时发生冲突。表的自增 ID 是通过行数的上界估计值得到的,与表的数据文件总大小成正比。因此,最后的自增 ID 通常比实际行数大得多。考虑到 TiDB 中自增 ID 不一定是连续分配的,这种状况是可接受的。
  7. 在所有步骤完毕后,tidb-lightning 会自动将 TiKV 切换回“普通模式”(normal mode),此后 TiDB 集群才可以正常对外提供服务。

导入模式

一旦目标 TiKV 集群切换到导入模式,整个数据导入阶段该集群将被 tidb-lightning 独占,无法对外提供正常服务。tidb-lightning 会修改下列集群配置以提高数据导入速度:

数据导入完成后,tidb-lightning 会自动把 TiKV 集群切换回“普通模式”。

2. tidb-lightning 架构

Lightning 架构图

工作原理

tidb-lightning 会扫描数据文件,区分出结构文件(包含 CREATE TABLE 语句)和数据文件(包含 INSERT 语句)。结构文件的内容会直接发送到 TiDB,用于建立数据库和表。然后,tidb-lightning 会并发处理数据文件。这里,我们来具体看一下一张表的导入处理过程。

每张表的数据文件内容都是规律的 INSERT 语句,如下所示:

INSERT INTO `tbl` VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9);
INSERT INTO `tbl` VALUES (10, 11, 12), (13, 14, 15), (16, 17, 18);
INSERT INTO `tbl` VALUES (19, 20, 21), (22, 23, 24), (25, 26, 27);

tidb-lightning 会找出每一行的位置,并分配一个行号,这样即使没有定义主键的表也能够区分每一行。tidb-lightning 会直接借助 TiDB 实例把 SQL 转换为键值对,称为“键值编码器”(KV encoder)。与外部的 TiDB 集群不同,键值编码器是寄存在 tidb-lightning 进程内的,并使用内存存储;每执行完一个 INSERT 之后,tidb-lightning 可以直接读取内存获取转换后的键值对(这些键值对包含数据及索引),并发送到 tikv-importer。

并发设置

tidb-lightning 把数据文件拆分成多个能并发执行的小任务。下面的配置选项可以帮助调节这些任务的并发度:

4.png

3. tikv-importer 架构

工作原理

Importer架构图

因异步操作的缘故,tikv-importer 得到的原始键值对注定是无序的。所以,tikv-importer 要做的第一件事就是要排序。这需要给每个表划定准备排序的储存空间,我们称之为引擎文件。

对大数据排序是个解决了很多遍的问题,我们在此使用现有的答案:直接使用 RocksDB。一个引擎文件就相等于一个本地 RocksDB,并大量写入操作做了配置上的优化。排序就相当于将键值对全部写入到引擎文件里,然后 RocksDB 就会自动帮我们合并、排序,最终得到 SST 格式的文件。

SST 文件包含整个表的数据和索引,和 TiKV 的储存单位 Region 比起来实在太大了。所以接下来要切分成合适的大小(默认为 96 MB)。tikv-importer 会根据要导入的数据范围预先把 Region 分裂好,然后借助 PD 把这些分裂出来的 Region 分散调度到不同的 TiKV 实例上。

最后,tikv-importer 将 SST 上传到对应 Region 的每个副本上,通过 Leader 发起 Ingest 命令,把 SST 文件导入到 Raft group 里。这样就完成了一个 Region 的导入过程。

并发设置

6.png

4. 数据校验

7.png

完成数据导入后会自动执行数据校验以确保数据完整性。tidb-lightning 会在每个表完成导入后,对比导入前后的 Checksum 确认二者是否一致。

一个表的 Checksum 是透过计算键值对的哈希值产生的。因为键值对分布在不同的 TiKV 实例上,这个 Checksum 函数应该具备结合性;另外,tidb-lightning 传送键值对之前它们是无序的,所以 Checksum 也不应该考虑顺序,即服从交换律。也就是说, Checksum 计算并不是简单地针对整个 SST 文件计算 SHA-256。

我们的解决办法是这样的:先计算每个键值对的 CRC64,然后用 XOR 结合在一起,得出一个 64 位元的校验数字。为降低 Checksum 值冲突的概率,我们同时会计算键值对的数量和大小。在下面两个地方分别计算来比对表中 3 个指标的和:

5. 分析与更新自增值

数据校验结束后,tidb-lightning 会重新计算表的统计信息,并更新表的自增值:

ANALYZE TABLE `xxxx`;
ALTER TABLE `xxxx` AUTO_INCREMENT=123456;

6. 使用限制

数据导入开始前须确保以下两点:

一个表只能接受一个 tidb-lightning 实例导入,多个 tidb-lightning 实例不能同时导入数据到同一张表。同一个数据文件若需要使用多个 tidb-lightning 实例并行导入,应该修改白名单配置以确保一个表只接受一个 tidb-lightning 实例导入。

7. 机器配置要求

8. 量化指标

下面给出的一些公式和计算方法可以帮助我们计算数据导入过程中的资源使用量。 这里,我们假定 tidb-lightning 和 tikv-importer 之间的交互过程不是性能瓶颈所在。