分布式数据库TiDB调研

版权申明:本文为原创文章,转载请注明原文出处

原文链接:https://www.wangheng.online/archives/336df028.html

简介

TiDB是 PingCAP公司自主设计、研发的开源分布式关系型数据库,是一款同时支持在线事务处理与在线分析处理的融合型分布式数据库产品,具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 协议和 MySQL 生态等重要特性。目标是为用户提供一站式 OLTP 、OLAP、HTAP 解决方案。

image-20210816203013696

整体架构

architecture.png

  1. TiDB: SQL 层,负责接受客户端的连接,执行 SQL 解析和优化,最终生成分布式执行计划。tidb-server 本身并不存储数据,只是解析 SQL,将实际的数据读取请求转发给底层的存储层 TiKV。
  2. TiKV : 分布式 KV 存储,类似 NoSQL 数据库,作为 TiDB 的默认分布式存储引擎,支持完全弹性的扩容和缩容。对分布式事务的原生支持,默认提供了 SI (Snapshot Isolation)的隔离级别,这也是 TiDB 在 SQL 层面支持分布式事务的核心。
  3. TiFlash:TiFlash 是一类特殊的存储节点,和普通 TiKV 节点不一样的是,在 TiFlash 内部,数据是以列式的形式进行存储,主要的功能是为分析型的场景加速。
  4. Placement Driver : 简称PD,整个 TiDB 集群的元信息管理模块,负责存储每个 TiKV 节点实时的数据分布情况和集群的整体拓扑结构,提供 Dashboard 管控界面,并为分布式事务分配事务 ID。PD 不仅仅是单纯的元信息存储,同时 PD 会根据 TiKV 节点实时上报的数据分布状态,下发数据调度命令给具体的 TiKV 节点,是整个集群的大脑。
  5. TiSpark:TiSpark 是 PingCAP 为解决用户复杂 OLAP 需求而推出的产品。它借助 Spark 平台,同时融合 TiKV 分布式集群的优势,和 TiDB 一起为用户一站式解决 HTAP的需求。TiSpark 依赖于 TiKV 集群和 Placement Driver (PD),也需要你搭建一个 Spark 集群。

数据存储流程

TiKV 的选择是 Key-Value 模型,并且提供有序遍历方法。简单来讲,可以将 TiKV 看做一个巨大的 Map,其中 Key 和 Value 都是原始的 Byte 数组,在这个 Map 中,Key 按照 Byte 数组总的原始二进制比特位比较顺序排列。

通过一个简单的例子,来理解 TiDB 的 Key-Value 映射关系。假设 TiDB 中有如下这个表:

1
2
3
4
5
6
7
8
CREATE TABLE User {
ID int,
Name varchar(20),
Role varchar(20),
Age int,
PRIMARY KEY (ID),
KEY idxAge (Age)
};

假设该表中有 3 行数据:

1
2
3
1, "TiDB", "SQL Layer", 10
2, "TiKV", "KV Engine", 20
3, "PD", "Manager", 30

首先每行数据都会映射为一个 (Key, Value) 键值对,同时该表有一个 int 类型的主键,所以 RowID 的值即为该主键的值。假设该表的 TableID 为 10,则其存储在 TiKV 上的表数据为:

1
2
3
t10_r1 --> ["TiDB", "SQL Layer", 10]
t10_r2 --> ["TiKV", "KV Engine", 20]
t10_r3 --> ["PD", "Manager", 30]

除了主键外,该表还有一个非唯一的普通二级索引 idxAge,假设这个索引的 IndexID 为 1,则其存储在 TiKV 上的索引数据为:

1
2
3
t10_i1_10_1 --> null
t10_i1_20_2 --> null
t10_i1_30_3 --> null

TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。

Region

TiDB 以 Region 为单位对数据进行切分,每个 Region 有大小限制(默认为 96MB)。

一个Region只属于一个表,一个表对应多个Region。

每个 Region 会有多个副本,每一组副本,称为一个 Raft Group。每个 Raft Group 中由 Leader 负责执行这块数据的读和写。

tidb-data-overview.png

TiDB 中每个 DatabaseTable 都有元信息,也就是其定义以及各项属性。这些信息也需要持久化,TiDB 将这些信息也存储在了 TiKV 中。而元信息的版本号存放在 pd-server 内置的 etcd 中。

数据查询流程

image-20210816112938875

分布式SQL运算:

image-20210816113049377

事务

TiKV 的事务采用的是 Google 在 BigTable 中使用的事务模型:Percolator,TiKV 根据这篇论文实现,并做了大量的优化。

乐观事务

1.png
  1. 客户端开始一个事务。

  2. TiDB 向 PD 获取 TSO作为当前事务的 start timestamp。

  3. 客户端发起读或写请求。

  4. 客户端发起 Commit。

  5. TiDB 开始两阶段提交,保证分布式事务的原子性,让数据真正落盘。

    i. TiDB 从当前要写入的数据中选择一个 Key 作为当前事务的 Primary Key。

    ii. TiDB 并发地向所有涉及的 TiKV 发起 Prewrite 请求。TiKV 收到 Prewrite 请求后,检查数据版本信息是否存在冲突,符合条件的数据会被加锁。

    iii. TiDB 收到所有 Prewrite 响应且所有 Prewrite 都成功。

    iv. TiDB 向 PD 获取第二个全局唯一递增版本号,定义为本次事务的 commit timestamp。

    v. TiDB 向 Primary Key 所在 TiKV 发起第二阶段提交。TiKV 收到 Commit 操作后,检查锁是否存在并清理 Prewrite 阶段留下的锁。

  6. TiDB 向客户端返回事务提交成功的信息。

  7. TiDB 异步清理本次事务遗留的锁信息。

悲观事务

在 v3.0.8 之前,TiDB 默认使用的乐观事务模式会导致事务提交时因为冲突而失败。为了保证事务的成功率,需要修改应用程序,加上重试的逻辑。特别是在冲突严重的场景和重试代价大的场景无法满足用户需求,而悲观事务可以弥补这方面的缺陷。

1.png

悲观事务在 Percolator 乐观事务基础上实现,在 Prewrite 之前增加了 Acquire Pessimistic Lock 阶段用于避免 Prewrite 时发生冲突:

  • 每个 DML 都会加悲观锁,锁写到 TiKV 里,同样会通过 raft 同步。
  • 悲观事务在加悲观锁时检查各种约束,如 Write Conflict、key 唯一性约束等。
  • 悲观锁不包含数据,只有锁,只用于防止其他事务修改相同的 Key,不会阻塞读,但 Prewrite 后会阻塞读。
  • 提交时同 Percolator,悲观锁的存在保证了 Prewrite 不会发生 Write Conflict,保证了提交一定成功。

死锁检测

TiKV 中实现了 Waiter Manager 用于管理等锁的事务,当悲观事务加锁遇到其他事务的锁时,将会进入 Waiter Manager 中等待锁被释放,TiKV 会尽可能按照事务 start timestamp 的顺序来依次获取锁,从而避免事务间无用的竞争。

Waiter Manager 中等待锁的事务间可能发生死锁,而且可能发生在不同的机器上,TiDB 采用分布式死锁检测来解决死锁问题:

  • 在整个 TiKV 集群中,有一个死锁检测器 leader。
  • 当要等锁时,其他节点会发送检测死锁的请求给 leader。

2.png

死锁检测器基于 Raft 实现了高可用,等锁事务也会定期发送死锁检测请求给死锁检测器的 leader,从而保证了即使之前 leader 宕机的情况下也能检测到死锁。

隔离级别

可重复读(RR)

但因架构和实现细节的不同,TiDB 和 MySQL InnoDB 的行为在细节上有一些不同:

  1. TiDB不支持Gap锁

    TiDB 使用 range 作为 WHERE 条件,执行 DML 和 SELECT FOR UPDATE 语句时不会阻塞范围内并发的 INSERT 语句的执行。

  2. TiDB 不支持共享锁

    使用这个语句执行的时候,效果和没有加锁是一样的,不会阻塞其他事务的读写。

  3. DDL 可能会导致悲观事务提交失败

    MySQL 在执行 DDL 时会被正在执行的事务阻塞住,而在 TiDB 中 DDL 操作会成功,造成悲观事务提交失败:ERROR 1105 (HY000): Information schema is changed. [try again later]

  4. START TRANSACTION WITH CONSISTENT SNAPSHOT 之后,MySQL 仍然可以读取到之后在其他事务创建的表,而 TiDB 不能。

  5. autocommit 事务不支持悲观锁

    所有自动提交的语句都不会加悲观锁,该类语句在用户侧感知不到区别,因为悲观事务的本质是把整个事务的重试变成了单个 DML 的重试,autocommit 事务即使在 TiDB 关闭重试时也会自动重试,效果和悲观事务相同。

读已提交(RC)

TiDB的读已提交和Oracle的行为一致。

大事务

  • 单个 kv 大小限制 6MB ,这是存储引擎层的限制,也就是依然不建议类似 Blob 等超长字段存放在 TiDB 中。
  • 目前单个事务大小限制在 10GB,超过 10GB 的事务依然会报错。
  • 事务对内存的占用可能会有 34 倍的放大,10GB 大的事务可能会占用 3040GB 的内存。如果需要执行特别大的事务,需要提前做好内存的规划,避免对业务产生影响。

其他

Sequence

Sequence 是数据库系统按照一定规则自增的数字序列,具有唯一和单调递增的特性。

AutoRandom

AutoRandom 是 TiDB 4.0 提供的一种扩展语法,用于解决整数类型主键通过 AutoIncrement 属性隐式分配 ID 时的写热点问题。

AutoRandom 提供以下的功能:

  • 唯一性:TiDB 始终保持填充数据在表范围内的唯一性。
  • 高性能:TiDB 能够以较高的吞吐分配数据,并保证数据的随机分布以配合 PRE_SPLIT_REGION 语法共同使用,避免大量写入时的写热点问题。
  • 支持隐式分配和显式写入:类似列的 AutoIncrement 属性,列的值既可以由 TiDB Server 自动分配,也可以由客户端直接指定写入。该需求来自于使用 Binlog 进行集群间同步时,保证上下游数据始终一致。

与MySQL 比较

MySQL TiDB
隔离级别 支持读未提交、读已提交、可重复读、串行化,默认为可重复读 乐观事务支持快照隔离,悲观事务支持快照隔离和读已提交
锁机制 悲观锁 乐观锁、悲观锁
存储过程 支持 不支持
触发器 支持 不支持
事件 支持 不支持
自定义函数 支持 不支持
窗口函数 支持 部分支持
JSON 支持 不支持部分 MySQL 8.0 新增的函数
外键约束 支持 忽略外键约束
字符集 只支持 ascii、latin1、binary、utf8、utf8mb4
增加/删除主键 支持 通过 alter-primary-key 配置开关提供
CREATE TEMPORARY TABLE 支持 TiDB 忽略 TEMPORARY 关键字,按照普通表创建
DML affected rows 支持 不支持
AutoRandom 列属性 不支持 支持
Sequence 序列生成器 不支持 支持
事务 支持乐观事务,悲观事务(默认)
不支持 select in share mode共享锁。不支持gap lock。
隔离级别 RC、RR
索引 支持主键,唯一(单列或联合),普通(单列或者联合),全文索引。5.0版本以上才支持聚集索引,并且需要显示声明出来,只支持显示声明到主键列。
存储结构 B+ tree LSM tree

基准测试

稳定性和高可用搭建

同城多中心

同城三数据中心方案,即同城存有三个机房部署 TiDB 集群,同城三数据中心间的数据同步通过集群自身内部(Raft 协议)完成。同城三数据中心可同时对外进行读写服务,任意中心发生故障不影响数据一致性。集群 TiDB、TiKV 和 PD 组件分别分布在 3 个不同的数据中心,这是最常规且高可用性最高的方案。

image-20210823143607012

优点:

  • 所有数据的副本分布在三个数据中心,具备高可用和容灾能力
  • 任何一个数据中心失效后,不会产生任何数据丢失
  • 任何一个数据中心失效后,其他两个数据中心会自动发起 leader election,并在合理长的时间内(通常情况 20s 以内)自动恢复服务

缺点:

性能受网络延迟影响。具体影响如下:

  • 对于写入的场景,所有写入的数据需要同步复制到至少 2 个数据中心,由于 TiDB 写入过程使用两阶段提交,故写入延迟至少需要 2 倍数据中心间的延迟。
  • 对于读请求来说,如果数据 leader 与发起读取的 TiDB 节点不在同一个数据中心,也会受网络延迟影响。
  • TiDB 中的每个事务都需要向 PD leader 获取 TSO,当 TiDB 与 PD leader 不在同一个数据中心时,它上面运行的事务也会因此受网络延迟影响,每个有写入的事务会获取两次 TSO。

两地三中心

两地三中心架构,即生产数据中心、同城灾备中心、异地灾备中心的高可用容灾方案。在这种模式下,两个城市的三个数据中心互联互通,如果一个数据中心发生故障或灾难,其他数据中心可以正常运行并对关键业务或全部业务实现接管。相比同城多中心方案,两地三中心具有跨城级高可用能力,可以应对城市级自然灾害。

image-20210823143538295

优点

  • Region Leader 都在同城低延迟机房,数据写入速度更优。
  • 两中心可同时对外提供服务,资源利用率更高。
  • 可保证任一数据中心失效后,服务可用并且不发生数据丢失。

缺点

  • 因为数据一致性是基于 Raft 算法实现,当同城两个数据中心同时失效时,因为异地灾备中心只剩下一份副本,不满足 Raft 算法大多数副本存活的要求。最终将导致集群暂时不可用,需要从一副本恢复集群,只会丢失少部分还没同步的热数据。这种情况出现的概率是比较小的。
  • 由于使用到了网络专线,导致该架构下网络设施成本较高。
  • 两地三中心需设置 5 副本,数据冗余度增加,增加空间成本。

多城多集群

多城多集群,即不同城市的机房部署不同的集群,其中有一个主控服务集群,其余的为从集群。从集群通过TiDB Binlog或者TiCDC同步数据。

image-20210823145239048

优点

  • 两个机房两套独立的集群,部署简单,能达到集群级别的容灾。
  • 单个集群所有节点都在同一个机房,相互交互网络开销小,数据写入速度和读取速度快。

缺点

  • 由于数据同步的滞后性,切换集群可能会造成部分数据丢失。
  • 两个集群直接的切换需要人工手动切换。

专有名词解释

名词 备注
TiDB SQL层 类似于MySQL的server层
TiKV 分布式KV存储引擎 类似于MySQL的InnoDB引擎
PD TiDB 集群的元信息管理模块,提供 Dashboard 管控界面
TiFlash 是 TiKV 的列存扩展
OLTP 在线事务处理(Online Transaction Processing) MySQL、Oracle、PostgreSQL
OLAP 在线分析处理(Online Analytical Processing) ES、Hive
HTAP OLTP+OLAP
作者

CoderW

发布于

2021-07-11

更新于

2021-12-08

许可协议

评论