十问 TiDB :关于架构设计的一些思考

做者:黄东旭web

“我但愿可以把 TiDB 的设计的一些理念可以更好的传达给你们,相信你们理解了背后缘由后,就可以把 TiDB 用的更好。”

作 TiDB 的缘起是从思考一个问题开始的:为何在数据库领域有这么多永远也躲不开的坑?从 2015 年咱们写下第一行代码,3 年以来咱们迎面遇到无数个问题,一边思考一边作,尽可能用最小的代价来快速奔跑。算法

做为一个开源项目,TiDB 是咱们基础架构工程师和社区一块儿努力的结果,TiDB 已经发版到 2.0,有了一个比较稳定的形态,大量在生产环境使用的伙伴们。能够负责任的说,咱们作的任何决定都通过了很是慎重的思考和实践,是通过内部和社区一块儿论证产生的结果。它未必是最好的,可是在这个阶段应该是最适合咱们的,并且你们也能够看到 TiDB 在快速迭代进化。数据库

这篇文章是关于 TiDB 表明性“为何”的 TOP 10,但愿你们在了解了咱们这些背后的选择以后,能更加纯熟的使用 TiDB,让它在适合的环境里更好的发挥价值。编程

这个世界有不少人,感受大于思想,疑问多于答案。感恩你们保持疑问,咱们承诺回馈咱们的思考过程,毕竟有时候不少思考也颇有意思。缓存

1、为何分布式系统并非银弹

其实并无什么技术是完美和包治百病的,在存储领域更是如此,若是你的数据可以在一个 MySQL 装下而且服务器的压力不大,或者对复杂查询性能要求不高,其实分布式数据库并非一个特别好的选择。 选用分布式的架构就意味着引入额外的维护成本,并且这个成本对于特别小的业务来讲是不太划算的,即便你说须要高可用的能力,那 MySQL 的主从复制 + GTID 的方案可能也基本够用,这不够的话,还有最近引入的 Group Replication。并且 MySQL 的社区足够庞大,你能 Google 找到几乎一切常见问题的答案。 安全

咱们作 TiDB 的初衷并非想要在小数据量下取代 MySQL,而是尝试去解决基于单机数据库解决不了的一些本质的问题。服务器

有不少朋友问我选择分布式数据库的一个比较合适的时机是什么?我以为对于每一个公司或者每一个业务都不太同样,我并不但愿一刀切的给个普适的标准(也可能这个标准并不存在),可是有一些事件开始出现的时候:好比是当你发现你的数据库已经到了你天天开始绞尽脑汁思考数据备份迁移扩容,开始隔三差五的想着优化存储空间和复杂的慢查询,或者你开始不自觉的调研数据库中间件方案时,或者人肉在代码里面作 sharding 的时候,这时给本身提个醒,看看 TiDB 是否可以帮助你,我相信大多数时候应该是能够的。 网络

并且另外一方面,选择 TiDB 和选择 MySQL 并非一刀切的有你没他的过程,咱们为了能让 MySQL 的用户尽量减少迁移和改形成本,作了大量的工具能让整个数据迁移和灰度上线变得平滑,甚至从 TiDB 无缝的迁移回来,并且有些小数据量的业务你仍然能够继续使用 MySQL。因此一开始若是你的业务和数据量还小,大胆放心的用 MySQL 吧,MySQL still rocks,TiDB 在将来等你。数据结构

2、为何是 MySQL

和上面提到的同样,并非 MySQL 很差咱们要取代他,而是选择兼容 MySQL 的生态对咱们来讲是最贴近用户实际场景的选择:架构

  1. MySQL 的社区足够大,有着特别良好的群众基础,做为一个新的数据库来讲,若是须要用户去学习一套新的语法,同时伴随很重的业务迁移的话,是很不利于新项目冷启动的。
  2. MySQL 那么长时间积累下来大量的测试用例和各类依赖 MySQL 的第三方框架和工具的测试用例是咱们一个很重要的测试资源,特别是在早期,你如何证实你的数据库是对的,MySQL 的测试就是咱们的一把尺子。
  3. 已经有大量的已有业务正在使用 MySQL,同时也遇到了扩展性的问题,若是放弃这部分有直接痛点的场景和用户,也是不明智的。

另外一方面来看,MySQL 自从被 Oracle 收购后,无论是性能仍是稳定性这几年都在稳步的提高,甚至在某些场景下,已经开始有替换 Oracle 的能力,从大的发展趋势上来讲,是很是健康的,因此跟随着这个健康的社区一块儿成长,对咱们来讲也是一个商业上的策略。

3、为何 TiDB 的设计中 SQL 层和存储层是分开的

一个显而易见的缘由是对运维的友好性。不少人以为这个地方稍微有点反直觉,多一个组件不就会增长部署的复杂度吗?

其实在实际生产环境中,运维并不只仅包含部署。举个例子,若是在 SQL 层发现了一个 BUG 须要紧急的更新,若是全部部件都是耦合在一块儿的话,你面临的就是一次整个集群的滚动更新,若是分层得当的话,你可能须要的只是更新无状态的 SQL 层,反之亦然。

另一个更深层次的缘由是成本。存储和 SQL 所依赖的计算资源是不同的,存储会依赖 IO,而计算对 CPU 以及内存的要求会更高,无需配置 PCIe/NVMe/Optane 等磁盘,并且这二者是不必定对等的,若是所有耦合在一块儿的话,对于资源调度是不友好的。 对于 TiDB 来讲,目标定位是支持 HTAP,即 OLTP 和 OLAP 须要在同一个系统内部完成。显然,不一样的 workload 即便对于 SQL 层的物理资源需求也是不同的,OLAP 请求更多的是吞吐偏好型以及长 query,部分请求会占用大量内存,而 OLTP 面向的是短平快的请求,优化的是延迟和 OPS (operation per second),在 TiDB 中 SQL 层是无状态的,因此你能够将不一样的 workload 定向到不一样的物理资源上作到隔离。仍是那句话,对调度器友好,同时调度期的升级也不须要把整个集群所有升级一遍。

另外一方面,底层存储使用 KV 对数据进行抽象,是一个更加灵活的选择。

一个好处是简单。对于 Scale-out 的需求,对 KV 键值对进行分片的难度远小于对带有复杂的表结构的结构化数据,另外,存储层抽象出来后也能够给计算带来新的选择,好比能够对接其余的计算引擎,和 TiDB SQL 层同时平行使用,TiSpark 就是一个很好的例子。

从开发角度来讲,这个拆分带来的灵活度还体如今能够选择不一样的编程语言来开发。对于无状态的计算层来讲,咱们选择了 Go 这样开发效率极高的语言,而对于存储层项目 TiKV 来讲,是更贴近系统底层,对于性能更加敏感,因此咱们选择了 Rust,若是全部组件都耦合在一块儿很难进行这样的按需多语言的开发,对于开发团队而言,也能够实现专业的人干专业的事情,存储引擎的开发者和 SQL 优化器的开发者可以并行的开发。 另外对于分布式系统来讲,全部的通讯几乎都是 RPC,因此更明确的分层是一个很天然的并且代价不大的选择。

4、为何不复用 MySQL 的 SQL 层,而是选择本身重写

这点是咱们和不少友商很是不同的地方。 目前已有的不少方案,例如 Aurora 之类的,都是直接基于 MySQL 的源码,保留 SQL 层,下面替换存储引擎的方式实现扩展,这个方案有几个好处:一是 SQL 层代码直接复用,确实减轻了一开始的开发负担,二是面向用户这端确实能作到 100% 兼容 MySQL 应用。

可是缺点也很明显,MySQL 已是一个 20 多年的老项目,设计之初也没考虑分布式的场景,整个 SQL 层并不能很好的利用数据分布的特性生成更优的查询计划,虽然替换底层存储的方案使得存储层看上去能 Scale,可是计算层并无,在一些比较复杂的 Query 上就能看出来。另外,若是须要真正可以大范围水平扩展的分布式事务,依靠 MySQL 原生的事务机制仍是不够的。

本身重写整个 SQL 层一开始看上去很困难,但其实只要想清楚,有不少在现代的应用里使用频度很小的语法,例如存储过程什么的,不去支持就行了,至少从 Parser 这层,工做量并不会很大。 同时优化器这边本身写的好处就是可以更好的与底层的存储配合,另外重写能够选择一些更现代的编程语言和工具,使得开发效率也更高,从长远来看,是个更加省事的选择。

5、为何用 RocksDB 和 Etcd Raft

不少工程师都有着一颗造轮子(玩具)的心,咱们也是,可是作一个工业级的产品就彻底不同了,目前的环境下,作一个新的数据库,底层的存储数据结构能选的大概就两种:1. B+Tree, 2. LSM-Tree。

可是对于 B+Tree 来讲每一个写入,都至少要写两次磁盘: 1. 在日志里; 2. 刷新脏页的时候,即便你的写可能就只改动了一个 Byte,这个 Byte 也会放大成一个页的写 (在 MySQL 里默认 InnoDB 的 Page size 是 16K),虽说 LSM-Tree 也有写放大的问题,可是好处是 LSM-tree 将全部的随机写变成了顺序写(对应的 B+tree 在回刷脏页的时候可能页和页之间并非连续的)。 另外一方面,LSMTree 对压缩更友好,数据存储的格式相比 B+Tree 紧凑得多,Facebook 发表了一些关于 MyRocks 的文章对比在他们的 MySQL 从 InnoDB 切换成 MyRocks (Facebook 基于 RocksDB 的 MySQL 存储引擎)节省了不少的空间。因此 LSM-Tree 是咱们的选择。

选择 RocksDB 的出发点是 RocksDB 身后有个庞大且活跃的社区,同时 RocksDB 在 Facebook 已经有了大规模的应用,并且 RocksDB 的接口足够通用,而且相比原始的 LevelDB 暴露了不少参数能够进行针对性的调优。随着对于 RocksDB 理解和使用的不断深刻,咱们也已经成为 RocksDB 社区最大的使用者和贡献者之一,另外随着 RocksDB 的用户愈来愈多,这个项目也会变得愈来愈好,愈来愈稳定,能够看到在学术界不少基于 LSM-Tree 的改进都是基于 RocksDB 开发的,另一些硬件厂商,特别是存储设备厂商不少会针对特定存储引擎进行优化,RocksDB 也是他们的首选之一。

反过来,本身开发存储引擎的好处和问题一样明显,一是从开发到产品的周期会很长,并且要保证工业级的稳定性和质量不是一个简单的事情,须要投入大量的人力物力。好处是能够针对本身的 workload 进行定制的设计和优化,因为分布式系统自然的横向扩展性,单机有限的性能提高对比整个集群吞吐其实意义不大,把有限的精力投入到高可用和扩展性上是一个更加经济的选择。 另外一方面,RocksDB 做为 LSM-Tree 其实现比工业级的 B+Tree 简单不少(参考对比 InnoDB),从易于掌握和维护方面来讲,也是一个更好的选择。 固然,随着咱们对存储的理解愈来愈深入,发现不少专门针对数据库的优化在 RocksDB 上实现比较困难,这个时候就须要从新设计新的专门的引擎,就像 CPU 也能作图像处理,但远不如 GPU,而 GPU 作机器学习又不如专用的 TPU。

选择 Etcd Raft 的理由也相似。先说说为何是 Raft,在 TiDB 项目启动的时候,咱们其实有过在 MultiPaxos 和 Raft 之间的纠结,后来结论是选择了 Raft。Raft 的算法总体实现起来更加工程化,从论文就能看出来,论文中甚至连 RPC 的结构都描述了出来,是一个对工业实现很友好的算法,并且当时工业界已经有一个通过大量用户考验的开源实现,就是 Etcd。并且 Etcd 更加吸引咱们的地方是它对测试的态度,Etcd 将状态机的各个接口都抽象得很好,基本上能够作到与操做系统的 API 分离,极大下降了写单元测试的难度,同时设计了不少 hook 点可以作诸如错误注入等操做,看得出来设计者对于测试的重视程度。

与其本身从新实现一个 Raft,不如借力社区,互相成长。如今咱们也是 Etcd 社区的一个活跃的贡献者,一些重大的 Features 例如 Learner 等新特性,都是由咱们设计和贡献给 Etcd 的,同时咱们还在不断的为 Etcd 修复 Bug。

没有彻底复用 Etcd 的主要的缘由是咱们存储引擎的开发语言使用了 Rust,Etcd 是用 Go 写的,咱们须要作的一个工做是将他们的 Raft 用 Rust 语言重写,为了完成这个事情,咱们第一步是将 Etcd 的单元测试和集成测试先移植过来了(没错,这个也是选择 Etcd 的一个很重要的缘由,有一个测试集做为参照),以避免移植过程破坏了正确性。另一方面,就如同前面所说,和 Etcd 不同,TiKV 的 Raft 使用的是 Multi-Raft 的模型,同一个集群内会存在海量的互相独立 Raft 组,真正复杂的地方在如何安全和动态的分裂,移动及合并多个 Raft 组,我在个人 这篇文章 里面描述了这个过程。

6、为何有这样的硬件配置要求

咱们其实对生产环境硬件的要求仍是蛮高的,对于存储节点来讲,SSD 或者 NVMe 或者 Optane 是刚需,另外对 CPU 及内存的使用要求也很高,同时对大规模的集群,网络也会有一些要求 (详见咱们的官方文档推荐配置的 相关章节),其中一个很重要的缘由是咱们底层的选择了 RocksDB 的实现,对于 LSM Tree 来讲由于存在写放大的自然特性,对磁盘吞吐需求会相应的更高,尤为是 RocksDB 还有相似并行 Compaction 等特性。 并且大多数机械磁盘的机器配置倾向于一台机器放更大容量的磁盘(相比 SSD),可是相应的内存却通常来讲不会更大,例如 24T 的机械磁盘 + 64G 内存,磁盘存储的数据量看起来更大,可是大量的随机读会转化为磁盘的读,这时候,机械磁盘很容易出现 IO 瓶颈,另外一方面,对于灾难恢复和数据迁移来讲,也是不太友好的。

另外,TiDB 的各个组件目前使用 gRPC 做为 RPC 框架,gPRC 是依赖 HTTP2 做为底层协议,相比不少朴素的 RPC 实现,会有一些额外的 CPU 开销。TiKV 内部使用 RocksDB 的方式会伴随大量的 Prefix Scan,这意味着大量的二分查找和字符串比较,这也是和不少传统的离线数据仓库很不同的 Pattern,这个会是一个 CPU 密集型的操做。在 TiDB 的 SQL 层这端,SQL 是计算密集型的应用这个天然不用说,另外对内存也有必定的需求。因为 TiDB 的 SQL 是一个完整的 SQL 实现,表达力和众多中间件根本不是一个量级,有些算子,好比 Hashjoin,就是会在内存里开辟一块大内存来执行 Join,因此若是你的查询逻辑比较复杂,或者 Join 的一张子表比较大的状况下(偏 OLAP 实时分析业务),对内存的需求也是比较高的,咱们并无像单机数据库的优化器同样,好比 Order by 内存放不下,就退化到磁盘上,咱们的哲学是尽量的使用内存。 若是硬件资源不足,及时的经过拒绝执行和失败通知用户,由于有时候半死不活的系统反而更加可怕。

另一方面,还有不少用户使用 TiDB 的目的是用于替换线上 OLTP 业务,这类业务对于性能要求是比较高的。 一开始咱们并无在安装阶段严格检查用户的机器配置,结果不少用户在硬件明显没有匹配业务压力的状况下上线,可能一开始没什么问题,可是峰值压力一上来,可能就会形成故障,尽管 TiDB 和 TiKV 对这种状况作了层层的内部限流,可是不少状况也无济于事。 因此咱们决定将配置检查做为部署脚本的强制检查,一是减小了不少沟通成本,二是可让用户在上线时尽量的减小后顾之忧。

7、为何用 Range-based 的分片策略,而不是 Hash-based

Hash-based 的问题是实现有序的 Scan API 会比较困难,咱们的目标是实现一个标准的关系型数据库,因此会有大量的顺序扫描的操做,好比 Table Scan,Index Scan 等。用 Hash 分片策略的一个问题就是,可能同一个表的数据是不连续的,一个顺序扫描即便几行均可能会跨越不一样的机器,因此这个问题上没得选,只能是 Range 分片。 可是 Range 分片可能会形成一些问题,好比频繁读写小表问题以及单点顺序写入的问题。 在这里首先澄清一下,静态分片在咱们这样的系统里面是不存在的,例如传统中间件方案那样简单的将数据分片和物理机一一对应的分片策略会形成:

  • 动态添加节点后,须要考虑数据从新分布,这里必然须要作动态的数据迁移;
  • 静态分片对于根据 workload 实时调度是不友好的,例如若是数据存在访问热点,系统须要可以快速进行数据迁移以便于将热点分散在不一样的物理服务商上。

回到刚才提到的基于 Range 分片的问题,刚才我说过,对于顺序写入热点的问题确实存在,但也不是不可解。对于大压力的顺序写入的场景大多数是日志或者相似的场景,这类场景的典型特色是读写比悬殊(读 << 写),几乎没有 Update 和随机删除,针对这种场景,写入压力其实能够经过 Partition Table 解决,这个已经在 TiDB 的开发路线图里面,今年以内会和你们见面。

另外还有一个频繁读写小表形成的热点问题。这个缘由是,在底层,TiDB 的数据调度的最小单位是 Region,也就是一段段按字节序排序的键值 Key-Value Pairs (默认大小 96M),假设若是一个小表,总大小连 96M 都不到,访问还特别频繁,按照目前的机制,若是不强制的手动 Split,调度系统不管将这块数据调度到什么位置,新的位置都会出现热点,因此这个问题本质上是无解的。所以建议若是有相似的访问 pattern,尽量的将通用的小表放到 Redis 之类的内存缓存中,或者甚至直接放在业务服务的内存里面(反正小)。

8、为何性能(延迟)不是惟一的评价标准

不少朋友问过我,TiDB 能替换 Redis 吗?你们对 Redis 和 TiDB 的喜好之情我也很能理解,可是很遗憾,TiDB 并非一个缓存服务,它支持跨行强一致事务,在非易失设备上实现持久化存储,而这些都是有代价的。

简单来讲,写磁盘的 IO 开销 (WAL,持久化),多副本高可用和保证分布式事务必然会牺牲延迟,更不用说作跨数据中心的同步了,在这点上,我认为若是须要很低延迟的响应速度(亚毫秒级)就须要在业务端作缓存了。TiDB 的定位是给业务提供一个可扩展的 The Source of Truth (真相之源),即便业务层的缓存失效,也有一个地方可以提供强一致的数据,并且业务不用关心容量问题。另外一方面,衡量一个分布式系统更有意义的指标是吞吐,这个观点我在不少文章里已经提到过,提升并发度,若是系统的吞吐可以随着集群机器数量线性提高,并且延迟是稳定的才有意义,并且这样才能有无限的提高空间。在实际的环境中,单个 TiDB 集群已经有一些用户使用到了百万级别的 QPS,这个在单机架构上是几乎不可能实现的。另外,这几年硬件的进步速度很是快,特别是 IO 相关的创新,好比 NVMe SSD 的普及,还有刚刚商用的持久化内存等新的存储介质。不少时候咱们在软件层面上绞尽脑汁甚至牺牲代码的优雅换来一点点性能提高,极可能换块磁盘就能带来成倍的提高。

咱们公司内部有一句话:Make it right before making it fast。正确性和可靠性的位置是在性能以前的,毕竟在一个不稳定的系统上谈性能是没有意义的。

9、为何弹性伸缩能力如此重要

在业务初期,数据量不大,业务流量和压力不大的时候,基本随便什么数据库都可以搞定,但不少时候业务的爆发性增加多是没有办法预期的,特别是一些 ToC 端的应用。早期的 Twitter 用户必定对时不时的大鲸鱼(服务不可用)深恶痛绝,近一点还有前两年有一段时间爆红的足记 App,很短的时间以内业务和数据量爆发性增加,数据库几乎是全部这些案例中的核心瓶颈。 不少互联网的 DBA 和年轻的架构师会低估重构业务代码带来的隐造成本,在业务早期快速搞定功能和需求是最重要的。想象一下,业务快速增加,服务器天天都由于数据库过载中止服务的时候,DBA 告诉你没办法,先让你从新去把你的业务全改写成分库分表的形式,在代码里处处加 Sharding key,牺牲一切非 Sharding key 的多维关联查询和相关的跨 Shard 的强一致事务,而后数据复制好多份……这种时候是真正的时间等于金钱,决定这个公司生死存亡的时候不是去写业务和功能代码,而是由于基础设施的限制被迫重构,实际上是很是很差的。 若是这个时候,有一个方案,可以让你几乎无成本的,不修改业务代码的时候对数据库吞吐进行线性扩展(无脑加机器实际上是最便宜的),最关键的是为了业务的进化争取了时间,我相信这个选择其实一点都不难作。

其实作 TiDB 的初心正是如此,咱们过去见到了太多相似的血和泪,实在不能忍了,分库分表用各类中间件什么的炫技是很简单,可是咱们想的是真正解决不少开发者和 DBA 眼前的燃眉之急。

最近这段时间,有两个用户的例子让我印象很深,也很自豪,一个是 Mobike,一个是转转,前者是 TiDB 的早期用户,他们本身也在数据增加很快的时候就开始使用 TiDB,在快速的发展过程当中没有由于数据库的问题掉链子;后者是典型的快速发展的互联网公司,一个 All-in TiDB 的公司,这个早期的技术选型极大的解放了业务开发的生产力,让业务可以更放开手脚去写业务代码,而不是陷入无休止的选择 Sharding key,作读写分离等等和数据库较劲的事情。

为业务开发提供更加灵活便捷和低成本的智能基础存储服务,是咱们作 TiDB 的出发点和落脚点,分布式/高可用/方便灵活的编程接口/智能省心,这些大的方向上也符合将来主流的技术发展趋势。对于CEO 、 CTO 和架构师这类的管理者而言,在解决眼前问题的同时,跟随大的技术方向,不给将来多变的业务埋坑,公司尽量快速发展,这个才是核心要去思考的问题。

10、如何根据本身的实际状况参考业内的使用案例

TiDB 是一个通用的数据库,甚至但愿比通常的数据库更加通用,TiDB 是很早就尝试融合 OLTP 和 OLAP 的边界的数据库产品,咱们是最先将 HTAP 这个概念从实验室和论文里带到现实的产品之一。这类通用基础软件面临的一个问题就是咱们在早期其实很难去指导垂直行业的用户把 TiDB 用好,毕竟各自领域都有各自的使用场景和特色,TiDB 的开发团队的背景大部分是互联网行业,因此自然的会对互联网领域的架构和场景更熟悉,可是好比在金融,游戏,电商,传统制造业这些行业里其实咱们不是专家,不过如今都已经有不少的行业专家和开发者已经能将 TiDB 在各自领域用得很好。

咱们的 Blog,公众号,官网等平台会做为一个案例分享的中心,欢迎各位正在使用 TiDB 的用户,将大家的思考和使用经验分享给咱们,就像如今已有案例背后的许多公司同样,咱们会将大家的经验变成一篇篇的用户案例,经过咱们的平台分享给其余的正在选型的公司。

相关文章
相关标签/搜索