Building a Reliable Large-Scale Distributed Database - Principles and Practice

大家好,我叫申砾,是 PingCAP Tech Leader,负责 TiDB 技术相关的工作。我曾就职网易有道、360 搜索,主要在做垂直搜索相关的事情,现在主要关注分布式计算/存储领域。过去的一年半时间我在 PingCAP 做分布式关系数据库 TiDB。目前我们的整个项目已经开源了大概一年时间,获得了不少关注。在 Github 上 Star 数量超过 5k,并且 Contributor 数量为 50+,拥有一个活跃的社区,在国内和国际上都有一定的知名度。 今天主要想和大家分享一下我们在做一款开源的分布式数据库产品过程中得到的一些经验和体会,包括技术上、产品上以及开源社区方面的内容,不会涉及太多技术上的细节。

数据库现状

近年来,随着移动互联网、物联网、人工智能等技术的兴起,我们已经进入了一个信息爆炸的大数据时代,需要处理和分析的数据越来越多,这些数据如何保存、如何应用是一个重要的问题。

传统的 SQL 数据库一般通过中间件、分库分表等方案获得 Scale 的能力。但是这些方案仍然很难做到对应用透明且保证数据均匀分布,同时也无法支持一致性的跨节点事务、JOIN 等操作。在进行扩容的时候往往需要人工介入,随着集群规模的增大,维护和扩展的复杂度呈指数级上升。

以 Google 的 BigTable 论文为开端,涌现出了一大批 NoSQL 方案。这些方案致力于解决扩展性,而牺牲一致性。如果采用 NoSQL 方案替换原有关系型数据库,往往要涉及大规模的业务重构,这相当于将数据库层的计算逻辑复杂度转嫁给业务层,同时还要损失掉事务等特性。

以上两种方案都没有完美地解决高可用的问题,跨机房多活、故障恢复、扩容经常都需要繁重的人工介入。

最近几年,人们希望有一种既有 SQL/NoSQL 的优点,又能避免他们的不足的新型数据库,于是提出了 NewSQL 的概念。Google 发布的 Spanner/F1,算是第一个真正在大规模业务上验证过的分布式数据库,向业界证明了 NewSQL 这条道路的正确性。TiDB 作为 Google Spanner/F1 的开源实现,正是业界盼望已久的 NewSQL 开源数据库。

什么是 NewSQL

并不是所有号称 NewSQL 的数据库都是 NewSQL。我们认为作为 NewSQL 数据库需要有下面几点特性:

**首先是 Scale。**这点上我想大家都深有体会,不管什么数据解决方案,最基本的要求就是能有足够的能力,保存用户所有的数据。

**第二是事务。**ACID Transaction,这个东西如果业务不需要,就感觉不到;一旦你的业务有这种需求,就能体会到它的重要性了。事实证明这个需求是广泛存在的,Google 的 BigTable 没有提供事务,结果内部很多业务都有需求,于是各个组造了一堆轮子,Jeff Dean 看不下去,出来说他最大的错误就是没有给 BigTable 提供事务。

**第三是 SQL。**SQL 作为一门古老的语言,在现在的技术领域内依然有强大的生命力,基本是流行的各种 NoSQL 上面都会有人来做一套 SQL-on-X。最近刚看到一个 2016 最佳编程语言榜单,SQL 依然能上榜。我想 SQL 在业界的应用还会延续很多很多年。

第四是 Auto-failover / Self recovery / Survivability。

Spanner 能够做到任何一个数据中心宕机,底层可以完全的 Auto-Failover,上层的业务甚至是完全无感知的。这个 Failover 的过程是完全不需要人工介入的。国内很多互联网公司也都在做这个,但是还没有那一家能做的特别好,比如光纤被挖断之后,大家发现支付工具无法支付了。除了业务不被中断这一好处之外,Auto-failover 还会极大地降低运维的成本,如 Google 这么牛的公司,在维护一百多个节点的 MySQL Sharding 的数据库的时候,都已经非常痛苦,宁可重新去写一个数据库,也不想去维护这个 database cluster。

为了给业界提供 NewSQL 数据库,我们开发了 TiDB,提供如下特性:

  • 无限水平扩展
  • 分布式 ACID 事务
  • 强一致
  • 高可靠
  • 提供 SQL 并且支持 MySQL 协议

TiDB 目前是世界上最受欢迎的开源 NewSQL 数据库之一,可能是国人发起和主要维护的最大也是 stars 数最多的开源的数据库项目。

我们在开发 TiDB 过程中学到的东西

下面就和大家分享一下我们在开发这个项目过程中学到的一些东西。

Always believe shit is about to happen

大家写程序的时候,总会做一些错误检查,处理异常情况,但是很多情况可能大家普遍不会想到,比如光纤被挖断、IDC 整个 down 掉,还有前几天的阿里云 IO 问题。这说明即使以这些顶级大厂的技术能力,依然不能保证基础设施不出问题。所以我们会特别强调 Auto-failover 的作用。我们的整个设计始终会考虑容灾。其中最重要的就是如何保存多副本,一份数据写足够多的副本并且使用健壮的副本分布策略,才能保证安全。Spanner 默认使用 5 副本,重要的数据使用 7 副本。

传统的保存副本的方案是 Master-slave 模式。但是这并不是一个完美的方案。如果要在 master 和 slave 之间保持强一致,那么不但要担心效率问题 (速度取决于最慢的那个 slave),还需要考虑各种容错。比如如果写 Slave 失败了,如何处理,是否要回滚 Master?所以依靠 master-slave 实现可靠复制对性能和复杂度都有比较大的挑战。而如果不要做到强一致,那么就面临 master 挂掉之后,slave 和 master 之间数据还没有同步完成,造成丢数据的问题。

Multi-Paxos / Raft 是一个更好的选择,采用这种方案,多数节点写成功即可成功,在保证可靠性的同时,尽可能的提升了复制效率。

Don't rely on humans

面对容灾,我们需要尽可能的将工作自动化,因为人会累,会犯错,但是机器不会。我们希望能提供一套自动的方案,来支撑业务的需求,抵御各种异常情况。比如做 Scale 的时候,只需要点击几下即可增加新节点,然后系统自动将部分数据迁移到新节点。发生灾难时,系统能够自动下线 down 掉的节点,并将数据迁移到其他的节点。

Talk is cheap, show me the tests

其实做数据库这么长时间,我认为最难的事情是测试。首先我们做的是一个支持 SQL 的数据库,想一下 SQL 的各种语法、操作符、函数,如何进行测试?同时我们是一个分布式的数据库,做测试就更难了。一直困扰我们的问题就是如何对我们的产品进行测试,比如一个 PR 上来后,如何检查逻辑是否正确,是否影响性能,是否支持容错?我们不断的探索和实践,有了一些自己的方案:

**首先是单元测试。**我们的 Code Review 规范明确的写了,如果改了逻辑、bug,没有单测不予 Review。

**第二是集成测试。**我们为了验证逻辑正确性,引入了大量的集成测试,其中一大块是 MySQL 源代码中的 test,因为我们支持 MySQL 语法和协议,所以可以直接拿过来用,省掉了我们大量的时间,很难想象没有这些测试,代码会变成什么样子。

**除此之外还有大量的 ORM 框架自带的 Test,我们也会运行。**我们精选出一批集成测试 case,每次提交 PR 之前必需要跑过。当 PR merge 后,我们内部的 CI 会自动运行,跑更多更耗时的测试。

**为了测试分布式场景下的系统容错能力,我们也引入了一些工具,**比如 Jepson/Namazu,可以进行错误注入。很多知名的分布式系统都被这些工具找出来过 bug,比如 etcd/zookeeper。

“All problems in computer science can be solved by another level of indirection”

在一年半的时间里,TiDB 从零开始,成长为一个庞大的项目。首先我们是实现了一个内存的数据库,在一个简单的 memkv 基础上,做了一个小的 SQL 层,然后慢慢的替换 memkv 为持久化存储。我们将 SQL 和存储引擎之间的接口进行了抽象。后续的分布式存储引擎,也依赖于这个接口。这样就屏蔽了下层存储引擎的差别。无论是 TiDB 还是 TiKV 我们在开发过程中始终遵循良好的分层+抽象的原则,有效的降低了开发的复杂度和耦合度,另外对测试也有很大的帮助,我们可以更容易的 mock 某一层,做更精细的控制。抽象是对抗大系统复杂性的有效武器。

Don't try to teach your user, just follow them

想要做一款成功的数据库产品,就需要让用户觉得方便好用。在这一点上,中间件或者 Sharding 方案往往需要侵入用户代码、或者是修改用户逻辑,在做扩容的时候,也要消耗用户大量的精力。我们要求 TiDB 能够尽可能的减少用户工作,可以一行代码都不用修改就能从单机 MySQL 迁移到 TiDB 上。并且要尽可能的和行业标准贴近,减少用户的工作量。

Make it right, and then make it fast

我们大概用了一个月的时间,做了一个可以用的数据库,在接下来的工作中,我们准备了整套测试框架,然后不断的完善功能,保证每次都是对的,与此同时,我们还会调整架构,最终做出一个还算不错的数据库,但是初期性能并不好,比如我们和第一家客户去聊,然后测试一下,发现一个简单的 SQL 跑了 600s,但是我们并不气馁,经过半个月的优化,我们将这个 SQL 优化到了 60ms。之所以能这么快优化到这么好,是因为我们有良好的架构,清晰的分层,完善的测试。所以这里并不是说我们不需要做优化,而是不要过早优化。这种大型系统,应该在早期关注架构的正确性以及弹性,为后面的优化留下足够的操作空间,保证每个模块可以单独的去优化。

Embrace the community

TiDB 是一个开源的数据库,我们从第一天起就坚定的走拥抱社区的路线,希望能够成为整个大数据生态的一部分。我们通过开源社区获得了大量高质量的第三方库,提高开发进度。与此同时,我们也向开源社区贡献了很多代码,比如 Etcd、Rocksdb、go-hbase、go-mysql 等。客户在进行数据库技术选型时,如果使用一些闭源的产品,那么就很容易被绑死在某一个厂商,或者是某一个云上。而使用 TiDB 不会有这方面的困扰,无论是迁移过来还是从 TiDB 迁移到其他的产品,都很容易,也不会被某个特定的云厂商绑死。

如果一个公司符合如下任何一种情况,TiDB 或许是一个很好的解决方案:

  1. 项目选型阶段。为了快速开发,简化生产力和运维,使用 TiDB,再也不用对数据库进行分库分表或者选用数据库中间件,TiDB 帮你搞定所有底层的跨节点的分布式事务、聚合查询难点,开发人员专注于业务设计,维护人员运维非常容易。

  2. 目前使用 MySQL 且数据量很大,但是查询速度特别慢。TiDB 是 MySQL 兼容的,且在大数据量性能大大优于 MySQL。

  3. 有跨数据中心数据强一致性需求或者自动运维需求。

Q&A

提问:TiDB 是否和其他的数据库(比如 MySQL、Oracle) 进行过性能对比?

申砾:我们内部用 Sysbench 做过对比,单套 TiDB 在写入能力上超过 MySQL,读取方面比 MySQL 略低。从整体上看,延迟会大于 MySQL,但是吞吐可以远高于 MySQL。我们正在不断提高性能,不久之后会对外发布性能测试结果。

提问:TiDB 是如何支持跨机房容灾?

申砾:TiDB 使用 Raft 做复制,PD 是整个集群的管理节点。PD 会自动根据存储节点的 IDC 位置指定副本存放策略,保证同一个 Raft Group 中的副本分布在多个机房。

提问:TiDB 是否会出商业版本?

申砾:是的,PingCAP 会提供 TiDB 的商业版。(含监控、部署、数据处理、调度及支持服务等)

分享到微信

打开微信,使用 “扫一扫” 即可将网页分享至朋友圈。