🤩TiKV

RocksDB 进阶版!

TiKV 是一个分布式事务型的键值数据库,提供了满足 ACID 约束的分布式事务接口,并且通过 Raft 协议保证了多副本数据一致性以及高可用。TiKV 作为 TiDB 的存储层,为用户写入 TiDB 的数据提供了持久化以及读写服务,同时还存储了 TiDB 的统计信息数据。

优势

  • 异地复制:使用Raft和Placement Driver进行异地复制来保证数据的安全性。

  • 水平扩展:直接增加节点即可实现系统扩容

  • 一致性分布式事务:使用基于Google Percolator、优化后的两阶段提交协议来支持分布式事务。可以使用“begin”来开启事务,再使用“commit”提交事务或者“rollback”来回滚事务。

  • 分布式计算的协处理器:跟HBase一样,支持协处理器框架来让用户直接在Tikv中做计算。

  • 与TiDB深度融合:使用TiKV作为TiDB的后端存储引擎,提供分布式关系型数据库

RocksDB in TiKV(摘自官方文档)

RocksDB 是用于快速存储环境的持久键值存储。以下是 RocksDB 的一些亮点功能:

  1. RocksDB 使用完全用 C++ 编写的日志结构数据库引擎,以获得最佳性能。键和值只是任意大小的字节流。

  2. RocksDB 针对快速、低延迟的存储进行了优化,例如闪存驱动器和高速磁盘驱动器。RocksDB 充分利用了闪存或 RAM 提供的高读/写速率的全部潜力。

  3. RocksDB 可以适应不同的工作负载

  4. 从 MyRocks 等数据库存储引擎到应用程序数据缓存再到嵌入式工作负载,RocksDB 可用于满足各种数据需求。

  5. RocksDB 提供了基本操作,例如打开和关闭数据库,读取和写入更高级的操作,例如合并和压缩过滤器。

TiKV 使用 RocksDB 是因为 RocksDB 成熟且高性能。在本节中,我们将探讨 TiKV 如何使用 RocksDB。

我们将在下面重点介绍 TiKV 中使用的一些特殊功能。

布隆过滤器是一种神奇的数据结构,它使用的资源很少,但有很大的帮助。我们不会在这里解释整个算法。如果您不熟悉 Bloom Filters,您可以将其视为数据集中的一个黑匣子,它可以在不实际搜索数据集的情况下告诉您某个键是否可能存在或 绝对不存在。有时布隆过滤器会给你一个误报的答案,尽管它很少发生。

TiKV 使用 Bloom Filter 以及称为 Prefix Bloom Filter (PBF) 的变体。PBF 不会告诉您数据集中是否存在整个键,而是会告诉您是否存在具有相同前缀的其他键。由于 PBF 只存储唯一的前缀而不是所有唯一的完整键,因此它也可以节省一些内存,但缺点是误报率较大。

TiKV 支持 MVCC,这意味着 RocksDB 中存储的同一行可以有多个版本。同一行的所有版本共享相同的前缀(行键),但具有不同的时间戳作为后缀。当我们想要读取一行时,我们通常不知道要读取的确切版本,而只想读取特定时间戳的最新版本。这就是 PBF 大放异彩的地方。PBF 可以过滤掉不可能包含与我们提供的行键具有相同前缀的键的数据。然后我们只需要在可能包含不同版本的行键的数据中搜索,找到我们想要的具体版本。

表属性

RocksDB 允许我们注册一些表属性收集器。RocksDB 在构建 SST 文件时,会将排序后的键值一一传递给每个收集器的回调,以便我们可以收集任何我们想要的东西。然后当 SST 文件完成后,收集的属性也将存储在 SST 文件中。

我们使用此功能来优化两个功能。

第一个用于拆分检查。拆分检查是检查区域是否足够大以进行拆分的工作人员。我们必须扫描一个区域内的所有数据来计算原始实现时区域的大小,这是资源消耗。使用该TableProperties 功能,我们记录每个 SST 文件中小子范围的大小,以便我们可以从表属性中计算出一个区域的大致大小,而无需扫描任何数据。

另一种是用于 MVCC 垃圾收集 (GC)。GC 是清理每一行的垃圾版本(比配置的生命周期更旧的版本)的过程。如果我们不知道一个区域是否包含一些垃圾版本,我们必须定期检查所有区域。为了跳过不必要的垃圾回收,我们在每个 SST 文件中记录一些 MVCC 统计信息(例如行数和版本数)。因此,在逐行检查每个区域之前,我们检查表属性以查看是否有必要对某个区域进行垃圾收集。

紧凑型

有时,某些区域可能会因为 GC 或其他删除操作而包含大量的 tombstone 条目。Tombstone 条目不利于扫描性能并浪费磁盘空间。

因此,使用该TableProperties功能,我们可以定期检查每个区域以查看其是否包含大量墓碑。如果是这样,我们将手动压缩区域范围以删除墓碑条目并释放磁盘空间。

我们还用于CompactRange从一些错误中恢复 RocksDB,例如跨不同 TiKV 版本的表属性不兼容。

EventListener允许我们监听一些特殊事件,如刷新、压缩或写入停顿条件更改。当特定事件被触发或完成时,RocksDB 将调用我们的回调,并提供有关该事件的一些信息。

TiKV 通过监听 compaction 事件来观察 region 大小的变化。如上所述,我们从表格属性中计算出每个区域的大致大小。大概的大小会被记录在内存中,这样如果没有任何变化,我们就不需要一次又一次地计算它。但是,在压缩期间,一些条目会被删除,因此应该更新一些区域的大致大小。这就是我们监听压缩事件并在必要时重新计算某些区域的近似大小的原因。

RocksDB 允许我们在外部生成一个 SST 文件,然后将文件直接摄取到 RocksDB 中。此功能可能会节省大量 IO,因为 RocksDB 足够智能,可以在可能的情况下将文件摄取到底层,这可以减少写入放大,因为摄取的文件不需要一次又一次地压缩。

我们使用这个特性来处理 Raft 快照。例如,当我们想将副本添加到新服务器时。我们可以先从另一台服务器生成快照文件,然后将文件发送到新服务器。然后新服务器可以直接将该文件摄取到它的 RocksDB 中,这样可以节省大量工作!

我们也利用这个特性将海量数据导入 TiKV。我们有一些工具可以从不同的数据源生成排序的 SST 文件,然后将这些文件摄取到不同的 TiKV 服务器中。与以通常的方式将键值写入 TiKV 集群相比,这非常快。

DeleteFilesInRange

此前,TiKV 使用直截了当的方式来删除一个范围的数据,即扫描范围内的所有键,然后逐个删除。但是,在压缩墓碑之前,不会释放磁盘空间。更糟糕的是,由于新写入的墓碑,磁盘空间使用量实际上会暂时增加。

随着时间的推移,用户在 TiKV 中存储的数据越来越多,直到磁盘空间不足。然后用户会尝试删除一些表或添加更多存储,并期望磁盘空间使用量在短时间内减少。但是 TiKV 并没有通过这种方法达到预期。我们首先尝试通过使用DeleteRangeRocksDB 中的特性来解决这个问题。但是, DeleteRange结果是不稳定的,不能足够快地释放磁盘空间。

释放磁盘空间的更快方法是直接删除一些文件,这将我们引向该DeleteFilesInRange功能。但是这个特性并不完美,它是相当危险的,因为它破坏了快照的一致性。如果您从 RocksDB 获取快照,使用DeleteFilesInRange删除一些文件,然后尝试读取该数据,您会发现其中一些丢失。所以我们应该谨慎使用这个特性。

TiKV 用于DeleteFilesInRange销毁 tombstone 区域和 GC 丢弃的表。这两种情况都有一个先决条件,即不能再访问已删除的范围。

Last updated