写在前面的话
技术选型是由技术方向和业务场景 trade-off 决定的,脱离业务场景来说技术选型是没有任何意义的,所以本文只是阐述了伴鱼技术团队数据库选型的过程,这并不是 MySQL、MongoDB 和 TiDB 之间直接的比较,只能说明 TiDB 更适合伴鱼的业务场景和技术规划,另外由于 TiDB 是非常新的数据库技术,所以这也能体现出伴鱼技术团队对新技术的态度、技术后发优势的理解、成本与效率的衡权和技术生态与红利的思考。
为什么放弃 MongoDB
伴鱼是 2015 年成立的,那个时候 NoSQL 还如日中天,关系型数据库为了应付海量的数据只能业务侵入式的分库分表,虽然 Google 在 2012 年发布了 NewSQL 数据库 Spanner 的论文,但是工业界还没有一款可以使用的 NewSQL 数据库,综合当时的各种情况,伴鱼选择的是 MongoDB。
不过,在 2015 年到 2017 年之间,对于伴鱼来说 MongoDB 确实是一个上佳之选,主要有以下几个方面的原因:
- 开发更高效:公司初期处于探索期,产品迭代非常快,MongoDB 是 NoSQL 数据库,不需要做建库建表等DDL操作,特别在产品快速迭代,需要频繁增减字段的时候就更高效,当然这个也是有代价的,从本质上来说,MongoDB 是读模式,它几乎不检查写入的内容是否合法,对数据 Schema 的解释是在应用程序的代码中,导致写入数据的约束性是没有保证的。
- 运维更高效:当时公司研发非常少,这段时间整个后端只有两个工程师,没有专职的运维和 DBA ,但是 MongoDB 的单机性能比 MySQL 要高不少,不但对数据库的运维成本要低不少,并且当时除了几个热点库外,其他的库 MongoDB 可以直接扛住流量压力,省去了中间的 Cache 层,让开发和运维都更高效。
- 有事务需求的场景不多:当时使用的是 MongoDB 2.x 和 3.x,只提供了数据一致性的选择(强一致性、单调一致性和最终一致性)和原子操作,在少数的几个场景,比如交易相关的场景,通过选择强一致性和原子操作,再在应用层实现 MVCC 的机制,可以满足简单的事务需求。
总体来说,在伴鱼产品的探索期,为了效率牺牲一点数据约束性和事务能力是值得的,但是 2017 年底伴鱼产品方向比较明确后,业务场景从探索期转变到快速发展期,对数据库的需求从效率优先转变为效率、事务能力与生态并重:
- 有事务需求的场景急增:事务场景从最初与钱相关的交易扩展到一些虚拟货币,并且由于并发量的增加,之前没有事务保障的场景出现竞争的情况越来越多,还在应用层通过 MVCC 机制实现简单的事务是非常低效的,并且在应用层实现事务的正确性也是很难保证的。(一个有趣的故事:Jeff Dean曾经说过自己对 Bigtable 最后悔的事情是没有提供跨行事务支持,导致业务就会在上层企图自己搞事务,并且业务实现的分布式事务大部分都是错的,所以在后来的 Spanner 数据库中 Jeff Dean 直接提供了官方分布式事务支持。)
- 对大数据生态的需求急增:在产品探索期的时候,也有很强的数据分析需求,不过当时数据总量小,在 MongoDB 的隐藏从库中直接分析就足够了,但是产品快速发展期,数据量急剧增加,在 OLTP 数据库中进行 OLAP 操作已经力不从心了。但是通过大数据生态来进行数据分析,对于 MongoDB 来说有一个非常残酷的现实,基本所有的大数据生态都是围绕 MySQL 生态打造的,如果想接入 MongoDB 的数据,意味着需要重新大量造轮子。
- 对数据约束性的要求更高:由于业务快速的发展,服务可能会出现多人维护和移交的情况,如果存储的数据没有约束,意味着存储的数据 Schema 是不可控的,这很容易让后面参与的工程师崩溃和掉进坑里,这个时候数据的约束性变成是一个更高优秀级的需求,关系数据库的写模式变成更好的选择。
到产品快速发展期,由于业务场景对数据库的需求已经发生了很大的改变,所以在这个时候,伴鱼技术团队开始谨慎思考数据库重新选型的问题,我们理想型的数据库是这样的:
- 高可用;
- 高吞吐;
- 支持ACID事务;
- 大数据生态友好;
- 有水平扩张能力,并且尽量做到不侵入业务;
基于上面这些需求,我们开始了数据库的重新选型之路。
初识 TiDB
早在 2015 年的时候我就非常关注分布式数据库,当时已经经历过高并发高 QPS 的场景,利用分布式架构解决无状态高并发高QPS场景是不复杂的,但是分布式存储由于涉及到一致性问题是非常有挑战的,如果还想支持ACID事务那就更难了,所以当时就对分布式数据库技术特别感兴趣,开始关注OceanBase并且收集和研究相关的理论和架构文档。
后来同事推荐说有个叫TiDB的数据库,目前有些公司也在用了,反馈也不错,所以我们决定调研一下。TiDB 官网的文档做的非常友好,不论是理论还是架构的文章都非常齐全,几乎一口气就把所有的文章都看了一遍(当时文章比现在要少),完备的理论支持、优雅的架构设计、与 Google Spanner 一脉相承的设计思路让我们对 TiDB 的前景非常看好,并且功能上完全满足我们的要求,所以当时就决定长期关注 TiDB,并且准备进行初步验证。
初步验证
通过调研我们发现,TiDB 是通过 raft 协议来保证多副本数据的一致性( ACID 中的 C ),通过 2PC 协议来保证事务的原子性( ACID 的 A ),通过乐观锁加 MVCC 来实现可重复读的事务隔离级别( ACID 中 I ),这意味着 TiDB 每一次事务的成本是比 MySQL 要高很多的,特别是有事务冲突的时候(乐观锁的原因),所以性能是需要验证的关键点。
当时伴鱼所有的业务都部署在阿里云上(现在有自建机房),就直接在阿里云上面按 TiDB 的配置要求购买了机器,当时安装的是 TiDB 1.x 的版本。因为 TiDB 官网已经有 Sysbench 的压力测试数据,这个性能数据是符合我们需求的,所以决定对我们的业务场景进行一次完全模拟的长期测试: 伴鱼 IM 的并发比较高,并且采用写扩散的设计,对数据库的要求会比较高,所以适合进行验证。通过对 IM 业务的 inbox 表进行双写,业务同步写 MongoDB 和异步写 TiDB,业务读只读 MongoDB,这样如果 TiDB 有问题也不会影响线上业务。
在低峰期对 IM 业务开启双写后,TiDB 监控的 999 线和 99 线还满足要求,但是所有 TiKV 节点的 io 使用率一直在 90% 附近波动,这个如果到高峰期是绝对会有问题的,通过重读 TiKV 的配置,在修改配置项 sync-log = false 后,TiKV 的 io 使用率维持在 5% 以下,当天的高峰期也一切正常,没有出现问题。
在这之后,我们对 IM 的双写观察 2-3 个月的时间,在确认一切正常后,再将同步读写都修改为 TiDB 并且异步写 MongoDB,一切正常并且持续观察。
sync-log 配置是控制 TiKV 数据多副本进行 raft 协议同步的时候,如果 sync-log=false,则内存中处理完成就返回 ack,对于 3 副本来说,单节点故障是不会丢失数据的,同一个 raft 集的 2 个副本同时故障可能会出现丢数据的情况,这个问题除了金融等对数据安全性要求非常高的场景外,其他的业务场景是可以接受的,并且 MySQL 等其他数据库的集群方案在 master 节点故障的时候问题更大。
深度交流
在前面初步验证 TiDB 的过程中,一个看似很严重的问题但是调整一个配置就可以解决,这让我们发现了我们对 TiDB 的理解和控制力还不够,在对每一个配置都进行理解研究外,还有一些我们非常关心的问题但没有官方答案。如果对这些问题没有官方答案,那么我们直接使用 TiDB 就是有很大风险的,所以我们决定和 TiDB 团队进行一次深度的交流。
我们当时非常关心的问题列表为:
- TiKV的线性扩展能力怎么样?
- 两地三中心架构,TiDB 可以容忍数据中心之间的延迟是多少?
- 目前业界 TiDB 最大的一个集群的 TiKV 和 TiDB 的节点数、数据量、QPS 最高是多少?
- TiDB 哪一些配置是需要特别关注和调整的?
…
收集了大概 20 多个问题,得益于伴鱼和 TiDB 都在北京,离得还非常近,在线上联系上并且约好时间后,和 TiDB 进行了第一次深度的交流。
大概是2018年上半年的一天,我和 TiDB 的 3-4 个同事聊了一整个上午,基本都是我将收集到的问题一个个抛出来,大家一起讨论。整个交流过程解答了很多我们关心的问题,也了解到当前业界对TiDB的使用情况,大大增强了我们对 TiDB 的信心,对于数据库的选型来说,这是非常关键的事情。
特别感谢当时一起交流的 TiDB 同事:房晓乐以及另外 2-3 位我不知道名字的同学(非常抱歉)。
为什么不选择 MySQL
经过对 TiDB 的调研、试用和深入交流后,在传统的关系型数据库 MySQL 和 NewSQL 数据库 TiDB 之间,我们需要做出自己的选择了,这不仅仅是两个数据库之间的选择,这其实也体现了伴鱼对新技术的态度、技术后发优势的理解、成本与效率的衡权和技术生态与红利的思考。
对新技术的态度
伴鱼对新技术的态度是非常积极的,如果业务场景需要的新技术我们都会去了解它、研究它和掌握它,我们相信我们对新技术趋势的判断能力和掌控能力,所以在 TiDB 和 MySQL 的选型的过程中,MySQL 确实是非常稳的选择,并且对我们的需求目前都有现成的解决方案,比如高可用,比如水平扩展能力,只不过不是非常优雅的解决方案,但是 TiDB 无论是理论层面和架构层面都比 MySQL 高出一个时代(MySQL 是面向单机数据库设计的,是这个领域非常优秀的数据库,只是现在伴鱼想要解决的是单机无法存储的海量数据场景,在这个维度上比较确实 TiDB 更好一些,但是这并不是 MySQL 的问题,是因为它们的设计目标不同而已),但是稳定性和成熟度会比 MySQL 要差一些,这个时候,我们选择相信我们对 NewSQL 技术方向的判断力和掌控力,相信 TiDB 的进化能力,相信时间站在我们这边,让子弹再飞一会。
技术后发优势的理解
伴鱼在之前用的数据库是 MongoDB,MySQL 和 TiDB 都没有用过,如果我们判断 TiDB 更面向未来的数据库,那么我们是先从 MySQL 开始,走一遍 MySQL 的道路,在后面可预见的未来再迁移到 TiDB 上来;还是直接深入研究和掌握 TiDB,直接 All in TiDB?
初创公司在技术沉淀和积累上是远远不及一些成熟公司的,这些沉淀和积累就是成熟公司在技术上的先发优势,当技术没有出现变革的时候我们没有选择,但是当技术正出现重大变革的时候,如果我们还做同样的技术选型,那么也需要花同样的时间和成本才能达到成熟公司的水平,然后等大家都开始迁移到新的技术上的时候,这些技术沉淀和积累就可能会变成技术债务。
所以初创公司应该去预判技术趋势,选择面向未来的技术,在技术上弯道超车,避免自己的技术债务,这个是伴鱼技术团队对技术后发优势的理解。
成本与效率的衡权
成本和效率是技术选型绕不过的关键点,对于数据库来说更是如此,因为数据库需要的机器等资源成本会占总资源成本的很大一部分,所以伴鱼技术团队在 TiDB 和 MySQL 做选择的时候,对成本与效率进行了深度的评估。
Unix 哲学是经过时间和实践的锤炼设计原则,很多时候也是伴鱼技术团队的实践原则,比如 Rule of Economy :「宁花机器一分,不花程序员一秒」。在技术选型上,我们是总是期望基础软件做更多的事情,业务研发做更少的事情,如果业务研发需要在业务层去做原本基础软件应该做好的事情,那其实就是基础软件的抽象泄漏了。如果基础软件抽象泄漏,必然会导致业务层重复去解决这个问题,这个其实是非常大的隐性成本。
MySQL 相比较 TiDB 而言,集群的高可用和大表需要分库分表其实就是 MySQL 在对面当前需求的抽象泄漏,MySQL 的集群高可用需要 DBA 和基础架构团队花成本去解决,MySQL 的大表分库分表方案需要 DBA、基础架构团队和业务研发团队花成本去解决,只不过这些都是隐性成本,不像在搭建数据库集群的时候,TiDB 比 MySQL 可能需要更多的机器来的简单直接,所以很容易被忽略了。
所以,对于成本与效率的衡权,伴鱼技术团队更关注工程师的效率,更关注工程师的心情(在业务上层重复解决一些底层软件抽象泄漏的问题是很影响心情的),更关注隐性成本,而不仅仅是账面明显可以比较的资源数字,特别是在机器越来越便宜,人才越来越值钱的趋势下。
技术生态与红利的思考
选择一个技术,其实也是选择了这个技术的生态,如果技术生态完善,做事情往往会事半功倍,极大地提高研发效率。TiDB 在这个方面做的非常好,全面兼容 MySQL 协议,让 TiDB 的用户享受到 NewSQL 的能力的同时也享受到 MySQL 的生态,这个是非常正确的决定,MySQL 生态是几十年的积累,不是一朝一夕可以做到的。
另一方面,在选择面向未来、优雅高效的解决方案,还是选择成熟的但不够优雅和高效的解决方案,如果选择成熟的解决方案,对技术的掌控会比较高,但是会在效率方面持续的进行付出;如果选择面向未来的解决方案,需要花时间和精力来掌握新技术,但是新技术会优雅和高效的解决问题,我们认为这个就是技术的红利。比如对于大表的解决方案,MySQL 提供的解决方案是分库分表,业务研发和 DBA 一起配合非常低效地解决这个问题,但是对于 NewSQL 的 TiDB,单表几乎可以理解为无限大的(业界已经存在 100 亿以上的表),从根本上解决了这个问题。现在伴鱼的大表都从 MongoDB 迁移到 TiDB 上面,业务研发和 DBA 不再为数据的增加而不停地进行分库分表,这个就是巨大的技术红利。
所以,基于上面的一些讨论与思考,伴鱼决定 All in TiDB,MongoDB 不再增加新的库和表,正在使用 MongoDB 的业务继续使用,并且对 MongoDB 上的大表进行有计划的迁移,避免进行分库分表操作。
踩过的坑
在完全掌握一项新技术前,享受新技术的红利是有代价的,特别是伴鱼在 TiDB 比较早期的时候就决定All in,这很考验技术团队的学习和进化能力、新技术的社区和官方提供的技术支持的能力。在 TiDB 这件事情上,伴鱼技术团队和 TiDB 的技术支持团队都做的非常优秀了,但是我们从 TiDB 1.x 到目前的 3.x 的过程中依然还是踩了一些的坑:
优化器选择索引问题
- 单表数据 30W+,查询请求并发约 10+,某次业务上线,新增一个索引后,导致原有的查询索引选择错误,TiKV 实例所在机器 cpu 迅速被打满,引发故障。
- 线上某张大表,请求量比较大,偶尔出现个别条件走不到索引,导致全表扫描,从而引发接口响应时间的抖动,影响业务。
- 线上某张 14 亿的大表,查询条件区分度很高,某天出现特定条件突然走不到索引,导致全表扫描,引发故障。后面经过 TiDB 同学排查,系 bug 导致。
优化器选择索引问题,TiDB 从 1.x 到 3.x 的过程中,优化器表现越来越好,同时伴鱼 DBA 团队通过性能监控和慢日志监控提前快速地发现问题,并且对大表采用强制索引的方式避免隐患,目前这个问题已经比较彻底的解决了。
大数据同步问题
- 为了进行数据分析,我们把上游各 TiDB 集群的数据通过 Pump / Drainer 汇聚到一个 TiDB 集群供大数据分析使用,在使用过程中,遇到数据不一致、数据同步慢和编码不一致导致同步失败等问题。
随着伴鱼的 DBA 团队深度研究 TiDB 并且和 TiDB 的同学进行持续的深入沟通,目前对 TiDB 的掌控力越来越强,大数据同步问题目前已经得到解决。
现在的情况
现在伴鱼有 10 套 TiDB 数据库,110+ 数据库实例,6 个 TPS 过万核心集群,999 线基本维持在 16ms 左右,响应时间和稳定性都达到预期。从目前的情况来看,伴鱼选择 TiDB 是一次非常正确的选择,我们在数据库技术方面弯道超车,避免了对 MySQL 技术的重复建设与积累,享受了 NewSQL 数据库 TiDB 在高可用和水平扩展等方面的技术红利,大大提高了业务研发和 DBA 的工作效率。当然,这是伴鱼技术团队(特别是DBA)和 TiDB 技术团队共同努力的结果。
这里还要特别提一点,TiDB 每一次版本升级都会带来惊喜,这是一个可以持续享受的技术红利。
写在后面的话
目前,在摩尔定律失效、业务的高可用要求和成本优化等综合的大环境下,分布式架构是技术潮流的大势所趋,流量路由策略加多副本部署(微服务是其中的一种架构形式)解决了无状态服务的分布式架构问题,Redis Cluster 和 Codis 等方案解决了缓存的分布式架构问题,Kubernetes 完成了操作系统的分布式进化,数据库领域自然也不会例外,它的分布式架构趋势一定是不可阻挡的。要特别说明一下,这里所说的解决问题是指系统性的解决问题,MySQL 业务侵入式的分库分表确实是一个可以解决问题的分布式架构方案,但是需要业务研发配合一个业务场景一个业务场景的去解决,这就不能称之为系统性的解决方案,因为在解决这个问题方式上,业务侵入式的分库分表方案将本应由数据库处理好的大表抽象泄漏给业务层了,在这个问题上,我们认为 NewSQL 是一个系统性的解决方案,而 TiDB 就是当下非常不错的一个选择。
另外还需要说明一点的是,这是一篇数据库选型的文章,所以只记录了与之相关的内容,比如详细描述了伴鱼技术团队在将数据库迁移到 TiDB 后踩的坑,因为这是我们数据库选型 TiDB 付出的代价,所以一定要详细记录;没有记录在使用其他数据库踩的坑,这并不代表我们没有踩到,比如在使用 MongoDB 的过程中也踩过一些坑,但是因为这并不是我们决定重新做数据库选型的原因(决定重新选型的原因见文章「为什么放弃 MongoDB」部分),所以就没有在文章中记录。