文档 - TiDB & 中间件
TiDB介绍
背景
当业务压力大而数据库达到瓶颈时,一般会采用“scale up”方式,即增加服务器的性能上限。但随着“摩尔定律”的逐渐失效,单台服务器上的性能极限已经慢慢显露,故大家开始寻找“scale out”方式,即将原有的集中式系统通过水平扩展的方式改造为分布式系统。而 TiDB 正是将“无限水平扩展”作为其核心功能。
当关系型数据库遇到性能瓶颈,而NoSQL虽易于扩展但却是以放宽ACID 原则为代价,这在很多业务场景下是无法被接受的,基于此类原因,Google 发表了 Spanner 的论文,论文种提出一种全新思路,Spanner 除了惊人的扩展性之外,还提供外部一致性以及高可用性。Spanner 及其之上的 F1 一起支撑了 Goolge 广告业务的后台,第一次在如此大的规模上运行的 NewSQL 系统。大家发现 NewSQL 同时融合了关系型数据库和 NoSQL 优势,从而将 NewSQL 推上分布式未来之星的位置。
特点
TiDB 正是参考 Google Spanner 框架,实现对 SQL 兼容、ACID 事务、自动故障恢复以及通过 Raft 协议实现多副本高可用。
随着大数据的业务探索、ETL 操作的复杂度变高、以及人类活动对数据的依赖度增高,AP+TP=HTAP 成为趋势,而基于 Key-Value 存储的 TiDB 从架构上就能和同样基于 Key-Value 存储的 Spark 很好的融合,故 TiDB 开始向 HTAP 数据库演变与发展。
TiDB产品架构
TiDB 主要由三块组件组成,加上其它各种组件或工具的辅助。TiKV 集群是存储引擎,相当于 中间件(分库分表)的MySQL节点;PD 集群是控制 TiKV 集群负载均衡及故障后自动恢复的调度者,同时存放 TiKV 的配置信息;TiDB 集群负责计算,相当于 中间件(分库分表)的计算节点(proxy)。
TiKV
- TiDB 使用 RocksDB 作为它的存储引擎。(以便不同 Region 的写入可以合并在一次 I/O中)
- 在 TiKV中,数据是以 key-value的形式行式存储的,其中 key-value pair 按照key的二进制顺序有序,即找到一个key 的位置后,可以找到它的next 来递增地获取比这个key 大的key-value。
其它组件
备份恢复
- 逻辑备份工具 Mydumper
- 数据导入工具 Loader
- 增量数据导入工具 Syncer
数据迁移
- 数据同步工具 Data Migration
- 全量数据高速导入工具 TiDB Lightning
其它工具
- 校验数据是否一致并提供修复的工具sync-diff-inspector
- 用于收集 binlog 的 TiDB Binlog
- 解决 HTAP 需求的 TiSpark
- 各种命令行工具。
对比与分析
TiKV优势与劣势
优势
运维成本低:TiKV 通过 raft 协议数据复制,用户不需要像搭建 MySQL 主从复制关系那样,搭建复制 关系,宕机后需要人工介入,将数据一致后,重新启动服务。但现在 MySQL5.7 支持基于 paxos 协议的 MGR 复制方式,也可以通过内部选举来达到数据一致。
不需要配置分片规则:在 TiKV 中,用户不能对分片字段分片规则作设定,若主键为数值类型则以主键作为分片字段且调度者PD来决定分片大小。减少用户使用分布式数据的学习成本,虽然灵活性低,但是 PD 可以只能调整region大小与位置,达到负载均衡。
劣势
- 无法配置分片规则:虽然 PD 可以通过调整 region 大小与位置,达到负载均衡,但当写入集中于某一张表时,特别是如果写入的值的索引值也是连续的(比如 update time 这种按时间递增的字段),因表数据和索引数据都具有相同的前缀,且它们都是连续存储的,就会造成在很少的几个 Region 上形成写入热点,成为整个系统的瓶颈。同样,读取也有类似问题。
但在 中间件(分库分表)上,类似这样大表可以通过水平分片并将访问热点打散的方法,将一张表分片到多个节点。
- 没有提供region的路由位置:虽然在大部分 TiDB 场景,用户不需要关注底层数据的存储,但是一旦发生问题,TiDB并不能提供路由位置供排查问题。
- 需要很多优化器:因为使用 RocksDB 作为存储引擎,所以 TiKV 需要自己写很多优化器来优化存储引擎,但 中间件(分库分表) 会将这些工作都下沉到 MySQL,由 MySQL 完成。
- 硬件要求高:中间件至少需要两台机器就能提供高可用服务,而 TiKV 则需要三台(官网上说至少三台,但是基于 raft 的选举,若三台机器中有一台宕机,为了防止脑裂,另外两台投票也选不出 leader,所以应该至少 4 台才能达到高可用)。
同时,虽然官网上要求内容最低 32GB,但无论是 RocksDB 还是 TiKV,从架构上应该都会对内存有很大消耗。
- RocksDB的缺点:同样因为使用RocksDB作为存储引擎,所以未来它需要克服RocksDB的缺点以及瓶颈,例如读写放大问题,牺牲读取性能来达到写入性能提升,对于需要大量查询的业务场景,性能会大大降低。
不过 TiKV 正在开发 TiTan 来取代 RocksDB,应该也是希望不让 RocksDB 的瓶颈也成为TiKV 的瓶颈。
- 大量跨库事务:这样的存储结构意味着每一个表都是分片表,因此 join 查询几乎都是跨节点的。
中间件 跨库Join
简单讲解,例子:select * from t1 join t2 on t1.id=t2.id
如果t1表按照id分片,t2表也按照id分片,且t1,t2两表的分片规则和节点都完全一致,表结构中分片字段的类型也一致,我们则可以推导出:对于t1和t2两张表来说,id一样的数据一定分布在同一个MySQL节点中。因此该JOIN语句的条件计算无需跨MySQL节点,这种情况下,该SQL不是跨库JOIN。
如果上述的条件不满足,例如t2按name分片,或者t2的分片规则和t1不一致,则对于t1和t2两张表来说,id一样的数据可能分布在不同的MySQL节点中,因此该JOIN语句的条件计算需要跨MySQL节点进行,该SQL在这种情况下则是跨库JOIN。
再比如,t1/t2都按id分片,且规则一致,但是JOIN的条件变为 t1.id < t2.id, 为了计算出所有满足条件的JOIN结果集,这种计算也需要跨MySQL节点进行,该SQL在这种情况下则是跨库JOIN。
跨库JOIN对 中间件 的计算、存储、网络、内存资源消耗都非常大,性能也比较差,在高并发大数据量的情况下,可能导致GC压力过高,导致 中间件 无法响应并触发高可用切换等问题,应当极力避免。
疑问
MySQL 的 InnoDB 将元数据存放在.frm 文件中,表数据根据索引以 B+树的形式顺序存 储在 .ibd 文件中。与 InnoDB 不同的是,TiKV 将一张表的元数据、表数据和索引数据分开存储,表数据和表索引分别用 tableid+rowid 和 tableid+rowid+indexid 作为 key,若主键为数值类型,则 rowid 默认为主键值。
从架构上来看,TiKV 使用 RocksDB 作为它的存储引擎,RocksDB 是用 LSM 树(提高写性能,降低读性能)结构存储, 而做到顺序存储 key,只能使用 B+树结构存储。官方没有明确说明这两者间的交互,猜测应为在底层磁盘上,数据以 LSM树存储,提取到内存并重新排序后,使用B+树结构存储。若是这样的话,若该表主键不是数值类型,用户做一次单表查询,需要到 B+ 树的索引数据找到主键的 rowid,再到 B+ 树的表数据中找到具体的值。如此一来,查询速度则每一次都会比 MySQL 的模式慢一步。
PD调度优势
- 负载均衡:PD 通过调度策略来满足上述目标,实现负载均衡,且是多维度(leader、访问热点、存储空间、副本分配等)的负载均衡。
- 故障自动恢复:若一个节点手动下线(或自动下线,但官网没有明确指出),PD 感知到后,可以将该节点上的 region 调度到其它节点,若其自动手动上线,也可以将其它节点上的 region 调度过来。
- 水平扩容:与故障自动恢复原理相同,通过 region 的调度,加入新增节点。但理论上,节点越多,性能越差。
MySQL兼容性
TiDB 100% 兼容 MySQL5.7 协议、MySQL5.7 常用的功能及语法。
不支持的功能特性
- 存储过程与函数
- 触发器
- 事件
- 自定义函数
- 外键约束
- 全文/空间函数与索引
- 非
ascii
/latin1
/binary
/utf8
/utf8mb4
的字符集 - SYS schema
- MySQL 追踪优化器
- XML 函数
- X Protocol
- Savepoints
- 列级权限
XA
语法(TiDB 内部使用两阶段提交,但并没有通过 SQL 接口公开)CREATE TABLE tblName AS SELECT stmt
语法CREATE TEMPORARY TABLE
语法CHECK TABLE
语法CHECKSUM TABLE
语法SELECT INTO FILE
语法
事务与隔离级别
TiDB 使用 Google Percolator 的事务模型,事务采用乐观锁,事务的执行过程中,不会检测写写冲突,只有在提交过程中,才会做冲突检测。
TiDB 的隔离级别是快照隔离 (Snapshot Isolation),基于 MVCC 的快照技术,实现了无锁的只读事务。
一致性强于 MySQL 的可重复读隔离级别的一致性,弱于串行化。
中间件 分布式事务
仅从理论的角度上来讲实现思路,中间件(基于MySQL)的分布式事务可以分为两种:弱一致模式、强一致模式。
弱一致模式
效果:
- 不保证隔离级别正确性。
- 一个分布式事务中各个MySQL节点的子事务可能出现部分提交。
处理逻辑:
客户端发出COMMIT或者自动提交的跨MySQL节点IUD语句
--1->
中间件 对多个MySQL节点执行完事务相关SQL后并发执行COMMIT
--2->
MySQL 回复 COMMIT OK
--3->
中间件 等待所有MySQL节点回复结束后,回复客户端OK
--4->
客户端收到OK
强一致模式:
- 应保证RR和SERIALIZABLE的隔离级别正确性。
- 保证一个分布式事务中各个MySQL节点的子事务全提交或者全回滚。
处理逻辑:
客户端发出COMMIT或者自动提交的跨MySQL节点IUD语句
--1->
中间件 自动开启XA事务并对多个MySQL节点执行完事务相关SQL后,进行XA END;XA PREPARE
--2->
中间件 收到所有MySQL节点的PREPAE OK后记录XA log
--3->
中间件 向所有MySQL节点发送XA COMMIT
--4->
MySQL 回复COMMIT OK
--5->
中间件 等待所有MySQL节点回复结束后,回复客户端OK
--6->
客户端收到OK
总结
- 客户端收到OK包的,都没问题。
- 强一致模式下,客户端COMMIT连接断开或者COMMIT报错的,保证全提交或者全回滚。
- 弱一致模式下,客户端COMMIT连接断开或者COMMIT报错的,可能部分提交部分回滚,客户端需要根据该事务执行的SQL和MySQL节点实际的数据情况判断事务提交情况。
TiDB 事务模型劣势
- 只支持一种隔离级别,快照隔离:快照隔离有它的优势,因为不用加锁,所以有更高的读性能。
但它也有它的劣势,比如对于两个事务 T1:b=a+1 和 T2:a=b+1,初始化 a=b=0。串行化的情况下,结果只可能是 (a=2,b=1)或者(a=1,b=2),而在快照隔离级别下,结果可能是(a=1,b=1)。这样的情况在有些业务场景下,尤其是金融 业务,可能是无法接受的。但 TiDB 只提供一种隔离级别,用户没有选择。
- 乐观锁导致高并发下的大量死锁,性能大幅下降:乐观锁是指只有在真正提交的时候,才会做冲突检测,如果有冲突,则需要重试。这种模型在冲突严重的场景下,会比较低效,因为重试之前的操作都是无效的,需要重复做。尤其是金融场景,悲观事务是不可或缺的一个特性。
不过 TiDB 在 3.0 的时候,推出了悲观事务,并且推出了死锁检测机制(乐观锁不存在死锁问题),实现情况有待考 证。
- 全局唯一的时间戳:无论是 spanner 框架还是 percolator 模型,都需要依靠全局时间戳。Google 通过原子钟实现该功能,但 TiDB 使用的是类似 Percolator 的 Timestamp Oracle(TSO)机制,也就意味着多一次网络开销。尤其是 TiDB 宣传的跨数据中心容灾方案,同一个集群中的不同 TiDB、PD、TiKV 分布在不同的异地 idc 上,在这样情况下,多一次网络开销将会不可避免地影响性能。
- 对事务的大小有限制,否则会导致raft 的复制卡住,即使是 spanner 也有类似限制:单个事务包含的 SQL 语句不超过 5000 条(默认);单条 KV entry 不超过 6MB;KV entry 的总条数不超过 30W;KV entry 的总大小不超过 100MB;单行数据不大于6MB;总的行数*(1 + 索引个数) < 30W;一次提交的全部数据小于 100MB;以及建议每个事务的行数不超过 200 行,且单行数据小于 100k,否则性能可能不好。
数据迁移
数据迁移指的是,在使用 TiDB 之前,如何将生产场景上的业务数据迁移到 TiDB 。TiDB 支持使用产品之前,将 TiDB 作为业务数据库的备库,既能测试是否满足业务需求,又能将业务数据迁移到 TiDB。因为 TiDB 提供诸多工具实现数据迁移,列举如下:
- Mydumper:用于从 MySQL 导出数据。(相当于 mysqldump 导出)
- Loader:用于将 Mydumper 导出格式的数据导入到 TiDB。(相当于 mysqldump 导入)
- Syncer:用于将数据从 MySQL 增量同步到 TiDB。(相当于 mysqlbinlog)
- DM(Data Migration):支持 MySQL 数据的全量导出和到 TiDB 的全量导入,还支持 MySQL binlog 数据到 TiDB 的增量同步。(相当于集成了 Mydumper、Loader、Syncer 的功能)
- TiDB Lightning:用于将全量数据高速导入到 TiDB 集群。例如,如果要导入超过1TiB 的数据,使用 Loader 往往需花费几十个小时,而使用 TiDB-Lightning 的导入速度至少是 Loader 的三倍。
备份恢复
TiDB 备份恢复功能,会比较麻烦一点,需要通过使用 Mydumper 从 TiDB 导出数据后,通过 Loader 导入数据到 TiDB,再通过 TiDB binlog 进行增量的备份与恢复。
HTAP思考
TiSpark 其实就是得力于 TiDB 的 key-value 方式存储,可以很方便的将数据读取为rdd 类型,供 spark 使用。但根据2019年 DTCC 大会中,TiDB 所宣传的来看,它们是在 TP 的基础上做的 AP,如果不能将 TP 和 AP 的资源隔离开,虽然让 TiDB 有一定的分析能力,但 AP 会吃掉机器大量的资源,让 TP 执行中的线上业务受到影响。而且 TiKV 底层使用的是 RocksDB,RocksDB 适用的场景是写入多于读取,也就意味着,在没有开发出替代 RocksDB 的存储引擎之前,从一些公开的测试结果来看,TiDB 的 OLAP 性能一般,不如一些专门的 OLAP 系统(Kylin,Druid,Palo,ClickHouse 等)。
从2019年北京的 DTCC 大会到2019年杭州的云栖大会,各大厂商(包括阿里的 DRDS、TiDB、OceanBase 等)都提出正在开发 HTAP 功能,可见这将成为未来的趋势。虽然现在大家都在做 HTAP,但是仍然出去摸索探路的过程,真正实现行列混存的没有几家,都是通过其它办法间接的完成 HTAP,因此性能问题还有待考证。
读写分离
TiDB 中不存在读写分离的说法,PD 会根据读写热点判断后,调整热点 region,使之 不同时存储于同一节点。中间件 则是沿用 MySQL 的读写分离策略,通过参数来选择读写分离策略。
从原理上暂时无法看出哪种读写分离的方法更好,但是根据 TiDB 论坛中的回答,目前分析热点问题,有时候复杂时要靠按 region、节点流量排序,才能分析出来。官方在 3.0 和 4.0 中,都将热点问题的排查和调度进行了优化,可见目前调度算法仍然有较大进步空间。
互联网案例
总结
优势
- SQL兼容能满足基本业务需求:从 SQL 兼容性上来看,因 TiDB 底层是以 key-value 形式存储的,在 SQL 兼容性上自然是不如 HotDB,但也能满足基本的业务需求。不支持外键约束、全文函数与索引、空间函数与索引、存储过程与函数、触发器、事件、自定义函数、增删主键等。
- 基于spanner框架的 ACID 的分布式事务:基于 spanner 框架,通过乐观锁、MVCC、二阶段提交、全局单点时间戳等多项技术, 实现了分布式事务的 ACID。
- 基于调度算法的自动故障恢复、水平扩容、负载均衡:PD 根据心跳检测收集来的状态信息,调整调度算法。即使发生了增加节点或节点故 障,PD 也可以通过增加、删除、转移 region,实现访问热点、存储空间、Leader 数 量等多维度的均匀分配。
- 基于raft 的多副本高可用:TiKV 底层以 region 为单位,创建多个 replica(副本),每个 replica 之间,通过 Raft 协议来达到数据的一致性。根据 raft 协议,其中一个拥有最新日志复制的副本 会被选举为 leader,其它的副本作为 follower。由 leader 进行读写操作,再复制给 follower,确认完成后提交。
- 运维成本低,用户几乎不需要关注底层是如何存储的:用户不需要配置分片规则,也不需要关心底层路由结果,故障后不需要特殊处理,PD 会自动将故障节点中的 region 调度到其它节点。
- 数据迁移成本低:TiDB 提供丰富的数据迁移工具,不仅有相当于 mysqldump 和 mysqlbinglog 的 Mydumper、Loader、Syncer,还有将这三者集成在一起的工具 Data Migration,以及 提供全量数据高速导入的 TiDB Lightning。
劣势
- 硬件要求高:中间件 至少需要两台机器就能提供高可用服务,而 TiKV 则需要三台(官网上说至少三 台,但是基于 raft 的选举,若三台机器中有一台宕机,另外两台投票也选不出 leader,所以应该至少 4 台才能达到高可用)。另外,无论是 RocksDB 还是 TiKV、 TiDB,从架构上应该都会对内存有很大消耗。
- 备份恢复功能不强:TiDB 备份恢复功能,会比较麻烦一点, 需要通过使用 Mydumper 从 TiDB 导出数据后,通过 Loader 导入数据到 TiDB,再通 过 TiDB binlog 进行增量的备份与恢复。
- key的连续存储导致性能瓶颈:当写入集中于某一张表时,特别是如果写入的值的索引值也是连续的,就会造成在很 少的几个 Region上形成写入热点,成为整个系统的瓶颈。同样,读取也有类似问 题。但在 中间件 上,类似这样大表可以通过水平分片并将访问热点打散的方法,将一 张表分片到多个节点。
- 性能不稳定,跨库事务多:无论是 spanner 框架还是 percolator 模型,都需要依靠全局时间戳。谷歌通过原子钟 实现该功能,但 TiDB 使用的是类似 Percolator 的 Timestamp Oracle(TSO)机制, 也就意味着多一次网络开销。尤其是 TiDB 宣传的跨数据中心容灾方案,同一个集群中 的不同 TiDB、PD、TiKV 分布在不同的异地 idc 上,在这样情况下,多一次网络开销将 会不可避免地影响性能。
- 全局唯一的时间戳意味着每个操作会多一次网络开销:无论是 spanner 框架还是 percolator 模型,都需要依靠全局时间戳。谷歌通过原子钟 实现该功能,但 TiDB 使用的是类似 Percolator 的 Timestamp Oracle(TSO)机制, 也就意味着多一次网络开销。尤其是 TiDB 宣传的跨数据中心容灾方案,同一个集群中 的不同 TiDB、PD、TiKV 分布在不同的异地 idc 上,在这样情况下,多一次网络开销将 会不可避免地影响性能。
- 只支持一种隔离级别,快照隔离:快照隔离有它的优势,因为不用加锁,所以有更高的读性能。但它也有它的劣势,会 有写入结果出错的情况,在有些业务场景下,尤其是金融业务,可能是无法接受的。 但 TiDB 只提供一种隔离级别,用户没有选择。
- 乐观锁导致高并发下的大量死锁,性能大幅下降:乐观锁是指只有在真正提交的时候,才会做冲突检测,如果有冲突,则需要重试。这种模型会导致高并发下的大量死锁,性能大幅下降。尤其是金融场景,悲观事务是不可或缺的一个特性。不过 TiDB 在 3.0 的时候,推出了悲观事务,并且推出了死锁检测机制(乐观锁不存在死锁问题),实际情况有待考证。
- 事务大小有限制:对事务的大小有限制,否则会导致 raft 的复制卡住,即使是 spanner 也有类似限制。