TiDB:基于Raf的类似CRDB分布式数据库


TiDB是一个开源混合事务和分析处理(HTAP)数据库,由 PingCap 开发。

  • TiDB 服务器是用 Go 编写的,是查询/事务处理组件;它是无状态的,因为它不存储数据并且仅用于计算。
  • 底层键值存储TiKV是用Rust编写的,它使用RocksDB作为存储引擎。
  • 他们添加了一个名为 TiFlash 的列式存储,本文对它进行了主要介绍。

本文来自 VLDB 2020。

如果您了解 CockroachDB/CRDB:

CockroachDB 是开源的,可通过 GitHub 获取。该数据库的核心功能采用商业源代码许可证 (BSL),三年后将转换为完全开源的 Apache 2.0 许可证。

CRDB 使用多版本并发控制。混合逻辑时钟时间戳用于版本控制。这些值永远不会就地更新。墓碑Tombstones 用于删除。通过这种方式,多版本存储为每个事务提供快照。

CRDB 使用 RocksDB(现在的Pebble)在存储层存储这些键值范围。范围是复制的单位。CRDB使用Raft共识协议进行复制,每个范围都是一个Raft组。

同样,每个范围都是一个 Raft 组:复制不是在节点级别,而是在范围级别。这提供了对数据放置的细粒度控制。

Raft 提供了一个有用的构建块,那就是原子复制。命令由租用者提出,您可以假设它与 Raft 领导者相同。当法定​​数量的副本确认时,该命令被接受。

分布式事务
CockroachDB 中的事务使用可序列化的隔离级别

为了保证跨越多个范围的事务的原子性,CockroachDB 利用了 Raft 的范围级原子性。每个事务都与事务记录相关联,就像其他数据更新一样,事务记录也要经过 Raft。事务的原子提交是通过该事务记录将所有写入值视为提交前的临时值来实现的。

CRDB 将这些临时值称为写入意图。意向是一个普通的 MVCC KV 对,只不过它前面有元数据,表明后面的内容是一个意向。该元数据指向事务记录,事务记录存储了事务的当前处置:待处理、暂存、已提交或已中止。

事务记录用于一次性原子式更改所有意图的可见性,并与事务的首次写入持久地存储在同一范围内。

并发控制
为了实现可串行化的事务,需要并发控制来检测冲突并根据需要对事务进行重新排序。

CRDB 是一个 MVCC 系统,每个事务在其提交时间戳处执行读取和写入。这会导致系统中所有事务的总排序,代表可序列化的执行。然而,事务之间的冲突可能需要调整提交时间戳。

  • 读写冲突。遇到具有较低时间戳的未提交意图的读取将等待较早的事务完成。等待是使用内存队列结构实现的。遇到具有较高时间戳的未提交意图的读取会忽略该意图,并且不需要等待。
  • 读写冲突。如果已经在较高时间戳 tb >= ta 处对同一密钥进行了读取,则无法在时间戳 ta 处执行对密钥的写入操作。CRDB 强制写入事务将其提交时间戳提前到 tb 之后。
  • 写-写冲突。遇到具有较低时间戳的未提交意图的写入将等待较早的事务完成(类似于写入-读取冲突)。如果它在更高的时间戳处遇到提交的值,则会将其时间戳提前(类似于读写冲突)。当不同事务以不同顺序写入意图的情况下,写-写冲突也可能导致死锁。CRDB 采用分布式死锁检测算法来中止等待者周期中的一个事务。

CRDB 提交类似于Spanner 提交协议(2012),因为 Spanner 也在 Paxos 组上使用 2PL+2PC。

但是,在 Spanner 中,时间戳不会向上移动。一旦获得锁,Spanner 就会设置它,然后事务提交根据需要等待提交时间戳的时钟不确定性。他们可以通过使用不确定性区间非常小的原子钟来负担得起。

CRDB不依赖专门的硬件进行时钟同步,因此可以在公共云和私有云中的现成服务器上运行,并提供NTP等软件级时钟同步服务。

CRDB 集群中的每个节点都维护一个混合逻辑时钟 (HLC),它提供物理时间和逻辑时间组合的时间戳。

  • HLC 通过其逻辑组件在每个节点间交换上提供因果关系跟踪。
  • HLC 在单个节点上的重新启动内和跨重新启动之间提供严格的单调性。
  • HLC 在存在隔离瞬态时钟偏差波动的情况下提供自稳定性。

然而,当物理时钟同步不确定性较高时,CRDB仍然需要上述读刷新技术来保证事务的可串行化。

另一个区别在于锁定。Spanner 在所有读写事务中获取读锁。CRDB 使用悲观写锁,但除此之外,它是一种乐观协议,具有上述读取刷新机制,如果在时钟不确定窗口内观察到冲突的写入,则会增加事务的提交时间戳。

  • 对于低争用的工作负载,此方法提供可序列化的隔离,并且比 Spanner 协议的延迟更低。
  • 然而,对于高度竞争的工作负载,它可能需要更多的事务重试,因此 CRDB 的未来版本将包括对悲观读锁的支持。

CockroachDB 不支持任何低于可串行性的隔离级别。
最初选择 Raft 作为 CRDB 的共识算法,是因为它的易用性和对其实现的精确描述。在实践中,我们发现在复杂的系统中使用 Raft 存在一些挑战就像 CRDB 一样。

在 CRDB 中采用 PostgreSQL 的 SQL 方言和网络协议,以利用客户端驱动程序的生态系统。
然而,CRDB 与 PostgreSQL 的行为方式不同,需要干预客户端代码。例如,客户端必须在 MVCC 冲突后执行事务重试并配置结果分页。
按原样重用 PostgreSQL 驱动程序需要我们教开发人员如何“在每个应用程序中重新部署更高级别的 CRDB 特定代码。这是我们没有预料到的反复出现的摩擦源。因此,我们现在正在考虑逐步引入 CRDB 特定的客户端驱动程序。


TiDB与CRDB
TiDB = CRDB - PostGres + MySQL

TiDB 兼容 MySQL,具有水平扩展、强一致性、高可用性的特点。
TiDB 架构看起来与 MySQL 完全不同:
TiDB 驱动与 MySQL 兼容。它缺少 MySQL 的许多功能,包括对存储过程的支持。
但它确实提供了无麻烦的自动分区/缩放,这是 MySQL 所不具备的。  

TiDB = CRDB - GeoDistribution
与 CRDB 一样,TiDB 使用 Raft 副本集在 96Mb(默认)范围分片中维护数据库的 SMR 复制。该论文将范围称为区域,这是一个非常令人困惑的词,因为这个概念与地理区域无关。据我所知,TiDB 是单区域的。TimeStampOracle (TSO) 是集中式的,需要对其进行修改以允许多区域。布局驱动器 (PD) 的处理方式相同。

TiDB = CRDB - 可串行化 + 快照隔离
TiDB 为 ACID 事务提供快照隔离 (SI),即可重复读 (RR) 语义。它还允许事务的读提交(RC)配置。

该实现基于多版本并发控制(MVCC),避免读锁定并防止写写冲突。它们的事务协议与CRDB非常相似:选择一个键作为主键并用它来代表事务的状态,并基于两阶段提交(2PC)来进行事务。

我能看到的唯一区别是,除了 CRDB 使用的悲观锁定之外,TiDB 还具有 OCC 版本的键锁定。
OCC 锁定版本可在低冲突工作负载下提供更好的吞吐量。

TiDB = CRDB + 列式
TiDB 的创新主张除了 OLTP 基于行的存储之外,还通过 Raft 学习器添加列式存储,以在不影响 OLTP 事务性能的情况下提供 OLAP。

在 OLTP 方面,他们表示 TiDB 的性能比 CRDB 稍好,这是“由于事务处理和 Raft 算法的优化”。这再次被实验证明是不合理的。我们如何知道差异不是来自使用 SI 而不是 SER 隔离?

我发现论文的写作和组织有些欠缺,但这并不影响理解。我还发现演示风格上有一些奇怪的地方。TiDB 新引入的内容和之前已经存在的内容没有很好的区分。第 2 节中关于 Raft 学习器的讨论就是一个例子。