本文来自 CSDN《程序员》2017 年 2 月的封面报道。
对于一个从零开始的数据库来讲:选择什么语言,总体架构怎么作,要不要开源,如何去测试…太多的问题须要去考量。前端
在本篇文章中,PingCAP 联合创始人兼 CTO 黄东旭对 TiDB 的开发历程进行了详细简介,为你们还原 TiDB 的架构演进全过程。程序员
在大约两年前,我有一次作 MySQL 分库分表和中间件的经历,那时在中间件里作 sharding,把 16 个节点的 MySQL 扩到 32 节点,差很少要提早一个月作演练,再用一个礼拜来上线。我就在想,能不能有一个数据库可让咱们再也不想分库分表这些东西?当时咱们也刚刚作完 Codis,以为分布式是个比较合适的解决方案。另外我一直在关注学术圈关于分布式数据库的最新进展,有看到谷歌在 2013 年发的 Spanner 和 F1 的论文,因此决定干脆就从新开始写一个数据库,从根本上解决 MySQL 扩展性的问题。算法
而决定以后发现面对的问题很是复杂:选择什么语言,整个架构怎么作,到底要不要开源……作基础软件有一个很重要的事情:写出来并不难,难的是你怎么保证这个东西写对了。尤为是对于业务方,他们全部的业务正确性是构建在基础软件的正确性上。因此,对于分布式系统来讲,什么是写对了,怎么去测试,这都是很重要的问题。关于这些我想了好久。数据库
一开始老是要起步的。当时就决定冷静一下,先肯定一个目标:解决 MySQL 的问题。MySQL 是单机型数据库,它没有办法作全扩展,咱们选择 MySQL 兼容,首先选择在协议和语法层面的兼容,由于已有的社区里边不少的海量的测试。第二点是用户的迁移成本,能让用户迁移得很顺畅。第三是由于万事开头难,必须得有一个明确的目标,选定一个目标去作,对开发人员来讲心理的压力最小。肯定目标之后,咱们 3 我的的创始团队从原来的公司出来,拿了一笔比较大的风险投资,开始正式作这件事情。编程
兼容 MySQL 最简单的方案,就是直接用 MySQL。为了让这个东西尽快地作起来,咱们一开始作了一个最简单的版本,复用 MySQL前端 代码,作一个分布式的存储引擎就能够了,这个事情想一想仍是蛮简单的,因此很是乐观,以为这个战略很完美。
上图是我在 2015 年 4 月份用六个礼拜完成的第一个版本的框架,可是后来没好意思开源出来,虽然能跑,可是在性能上彻底没法接受。我就想这个东西为何这么慢?一步一步去看每一层,就想动手改,可是发现工程量巨大,好比 MySQL 的 SQL 优化器, 事务模型等等,彻底没有办法下手。就像这个架构图里看到的,由于在 MySQL Engine 这一层,咱们能作的事情太少了,因此就没有办法。安全
初版实验到此宣告失败,如今看起来写 SQL parser 和优化器等这些已是绕不开了,咱们索性决定从头开始写,惟一给我安慰的就是终于可使用咱们最爱的编程语言了,就是 Go。网络
咱们跟其余作这种软件的工程师的思路相反,选择了从上往下写,先写最顶层的 SQL 的接口 SQL Layer,我要保证这个东西长得跟 MySQL 如出一辙,包括网络协议和语法层。从 TiDB 网络协议、SQL 的语法解析器、到 SQL 的优化器、执行器等基本从上到下写了一遍。这个阶段持续了大概三个月左右。从这个阶段开始,咱们慢慢摸索出了几个实践中深有体会的开发哲学。数据结构
第一,全部计算机科学里面的问题均可以把它不停地抽象,抽象到另一个层次上去解决。架构
咱们完成了架构 0.2,此时 TiDB 只有一个 SQL 解析器,彻底不能存数据,由于底下的存储引擎根本没有实现。我想要保证这个数据库是对的,要先保证 SQL Layer 是对的,让它能够完整的跑 MySQL 的 test。至于底下的存储我能够实现个假的或者内存里先存着,先保证个人 SQL 正常运转起来就能够了。负载均衡
其实在苦哈哈的写 TiDB 的 SQL Parser 的时候咱们还作了不少事情,无论是 MySQL 的 unittests,SQL logic tests,ORM tests 等,把它的测试全都收集下来,到如今大概有一千万个集成测试用例。咱们还作了一个事情,就是把存储引擎这个概念抽象成很薄的几个接口,使得它去接入一个 KV engine。绝大多数的 KV engine 很是多,好比 LevelDB,RocksDB,接口的语义都是很是明确的。
几个月过去了,团队也大约有了十几我的,由于在每一层我都很是严格地要求咱们团队用接口来划分,使得每个层次上的工做都是能够并行,这对于整个项目的推动是很是有利的事情。
大概去年九月份,历史上第一个不能用来存数据的数据库——没有存储引擎的初版 TiDB 开源了,放在 HackerNews 很是受欢迎,还被推荐到了首页。
第二,Talk is cheap,show me the tests。
作基础软件 test 是比 code 更重要的事情。好比你提了一个 Feature,我究竟是合并仍是不合,不能直接判断,须要看到你的 test。咱们如今在 GitHub 上运营 TiDB,一个新的提交若是让整个项目的代码测试的覆盖率降低了,我是不容许你的代码合并到主干分支的,很是严格。构建一个数据库最难的并非把它写出来,而是证实它是对的,尤为是分布式系统的测试要比单机的测试要更加困难。由于在分布式系统里面每个节点均可能 crash,每个网络的延迟多是飘忽不定的,各类各样的异常状况都会发生。咱们在作整个数据库的时候,第一步是完成 SQL Layer,第二步是把每一个 IO,每一个集群的节点交互行为全都抽象成为一个接口,使得咱们能够回放整个包括 TCP/IP 包的接收顺序。一旦发现 bug,就把它重放到单元测试里面重现。无论是新的开发者或者新的模块加入,是没法相信“人”的,只相信机器。我只相信 strong test 才能不断的保证项目在能够控制的范围以内。
后来作了一个架构 0.5,由于已经有了 SQL 层,SQL 层跟存储层基本上作了彻底分离,终于能够像最初的 0.1 那样,我能够接一个分布式引擎上去,当时咱们接了 HBase。 HBase 是阶段性的战略选型,由于咱们想既然个人 SQL Layer 写的足够稳定,那么咱们先接一个分布式的引擎上去,可是我又不能在架构中引入太多不肯定的变量,因而就挑选了一个在市面上能找到的,我认为最稳定的分布式引擎,先接上去看整个系统到底能不能跑起来。结果还能够,可以跑起来,可是咱们的要求会更高,因此以后咱们就把 HBase 扔掉了。接 HBase 这个事情标志着咱们上层 SQL Layer 跟咱们接口的抽象已经足够稳定,咱们的 test 已经足够健壮,能让咱们往下一步步去作分布式的东西。
这个架构大概是这样:
上层是 MySQL 业务层 Client,你能够用任意的 MySQL 的客户端去链接它,若是数据量大的话,你不须要再去分库分表,就把它当成无穷大的 MySQL 用就好,这个用户体验很好。由于 TiDB 是一个无状态的设计,它并不存储数据,因此你能够部署无数多个 TiDB 负载均衡。底层一开始是个 HBase,那时候是 11 月,至此距离创业半年过去了。
由于有海量的 Test 保证,让整个设计的过程没有太过困难。不过这里涉及一个问题:咱们在作技术选型的时候,若是在有很大自由度的前提下,怎么去控制发挥欲望和膨胀的野心?你的敌人并非预算,而是复杂度。你怎么控制每一层的复杂度是很是重要的,特别是对于一个架构师来讲,全部的工做都是在去规避复杂度,提高开发效率和稳定性。
当时咱们选择了一个很是小众的编程语言就是 Rust。首先它是一个 high performance 的编程语言,它没有 GC ,也没有 runtime,不少的创新是作在了编译器这一层,最大的特色就是安全、安全和安全,我认为它是更现代的 C++,不过 C++ 最大的问题是若是用不熟容易把本身的手脚砍断。当时我选 Rust 之后,不少朋友问我你为何去选择它。说实话最开始我是很怕的,由于这个语言毕竟是一个新的比较小众的语言,community 也没那么大,但这是当时对于咱们团队的情况来讲是最优选择。咱们就用 Rust 写起来,结果一不当心 TiKV 成为了 Rust 社区最大的开源项目之一。由于咱们在 Rust 很早期的时候就开始用了,Rust 官方也一直在找咱们来分享 Rust 的使用经验,咱们也很热心的去拥抱 Rust community。 Rust 社区每周的 weekly 里面有一个固定的专栏叫作 “This week in TiKV” ,就是为咱们打造的 :)
2015 年的冬天咱们是在纠结中度过的。一是用最新的编程语言 Rust, 你们以前都没有接触过;第二就是,咱们想要的“弹性扩展、真正的高可用、高性能、强一致”这四点要求,每个都很是困难。
怎么办?只能去拥抱社区,不要本身去作全部的事情,一是人数有限,第二是复用是个很好的习惯,既然别人都已经干过这些事情,就不要再去重复性的工做。咱们要作一个真正高可用的数据库,把高可用的分布式存储找了一圈发现 Etcd,Etcd 背后算法叫 Raft这是个一致性算法等价于 Paxos。这个算法目前来讲最稳定地实现就是 Etcd 里的 Raft。并且 Etcd 是真正在生产环境中被大量认证过的 Raft 的实现。我仔细看过 Etcd 的源码,每一个状态的切换都抽象成接口,咱们测试是能够脱离整个网络、脱离整个 IO、脱离整个硬件的环境去构建的。我以为这个思路很是赞,这也是为何 CoreOS 的 Etcd 包括像 k8s 背后的元信息存储也用的是它,质量很是高,性能很是好。可是 Etcd 有一个问题是它是 GO 写的,咱们已经决定去用 Rust 开发底层存储的数据库。若是用相似 paxos 这种算法,我不相信除了 Google Chubby 之外的公司有能力把它写对。可是 Raft 不同,虽然它也很难,可是毕竟它是能够实现的东西,因此咱们为了它的质量,加速咱们开发的进度,咱们作了一件比较疯狂的事情,就是咱们把 Etcd 的 Raft 状态机的每一行代码,line by line 的翻译成了 Rust。而咱们第一个转的就是全部 Etcd 自己的测试用例。咱们写如出一辙的 test ,保证这个东西咱们 port 的过程是没有问题的。
TiDB 底层的存储引擎一开始是不能存数据的,那如今是时候要选一个真正的 Storage engine,咱们以为这个事情是一个巨坑。本地存储引擎让一个小团队去写的话基本不现实,咱们就从最底层选择了 RocksDB。RocksDB 你们能够认为是一个单机的 key-value engine,前身实际上是 LevelDB,是 Google 在 2011 年左右开源的 key-value 的存储引擎。 RocksDB 的背后结构是 LSM Tree,是一个对写很是友好、同时在你的机器内存比较大的时候它的读性能会很是好的数据结构 。存储引擎还有一个很重要的工做就是,须要根据你机器的性能去作针对性的调优,你们会看到像 MySQL 调优都快变成黑魔法同样的东西,RocksDB 也是一个调优能写本书的存在。你们能够看到,新一代的分布式数据库存储引擎你们都会选择 RocksDB,我以为这是大势所趋。
从 15 年的冬天开始,咱们苦逼哈哈的写了 5 个月的代码,用 Rust 去写,到 2016 年 4 月 1 日 TiKV 终于开源了。
从上图能看到,最底层是 RocksDB ,上面的分布式这一层是用 Raft,这两层虽然是咱们写的,可是质量上是咱们社区的盟友帮咱们保证的。在 Raft 之上是 MVCC ,从这里往上,就都是咱们本身来写的了。因此 TiKV 终因而一个能够实现弹性扩展、支持 ACID 事务、全局一致性,跨数据中心高可用的存储引擎了,并且性能还很是的棒。由于我没有在底下去接一个像 HDFS 的文件系统。
其实从开源到如今,咱们一直在作 TiKV 的性能调优、稳定性等不少的工做,可是从架构上来看,这个架构我以为至少在将来的五年以内,不会再有很大的变化。我一直在强调的点就是:复杂性才是你最大的敌人,我宁肯是以不变应万变的姿式去应对将来突变的需求。还好,数据库这个东西的需求变化也没太多。
第三,Where there’s a metric there’s a way。
再说说 Metrices。对架构师而言一个很重要的工做就是查看系统中有哪些 block 的点,挨个解决掉这些问题。咱们发如今数据库领域,有不少不少点若是能予以解决的话,性能会上去十倍。我有一个观点,全部的东西,只要有 Metrices,能被监控,这个东西就能被解决。 也就是:“Where there’s a metric there’s a way”。 一旦能重复观察性能的平衡点,性能问题是最好解决的问题,可是写对是最难的问题。
通常来讲你们都在公司内部本身去审 Metrics,还有监控工具。对于咱们小团队来讲,或者说是一个拥抱社区的团队来讲,这基本上是一个得不偿失的事情。由于你费好多劲去写一个,还不如社区里面写得好,这很麻烦。因此,咱们在数据库里面内嵌了 Prometheus 和 Grafana。Prometheus 如今在硅谷太火了,它实际上是一个分布式的时序数据库,可是它很适用于日志搜集和性能调优,它作得更完美的地方是它提供 DSL 去用于查询提供监控的报警,包括你能够写报警的规则。它没有一个好看的 Dashboard,这时社区里面另一个哥们就出来,说我要去给你作一个很好看的界面,这个项目就是 Grafana。Grafana 是一个可视化的 dashboard ,它能让每一次 dashboard 排布的位置、类型、样式、大小、宽度都是能够自定义的。并且整个 Metrics 收集 Prometheus 提供了两种模式,一种是 push 的模式,一种是 pull 的模式。 对于收集监控的代码性能影响很小。
最后想补充的问题,第一就是工具。对于一个互联网出身的团队来讲,其实工具是咱们很是重视的一个点,能够最小化业务的迁移成本。我以为不少在大公司作重构,或者作基础软件的工程师,最好的方式就是润物细无声。你彻底不知道我在作底层的重构就莫名地重构完了,这是最完美的状态。好比我以前作 Codis,个人要求是若是用户如今在用 Twemproxy ,他迁到新的方案上必需要一行代码都没有改,你甚至彻底不知道我在作迁移,这才是最好的,我认为这应该是全部作基础设施团队的自我修养。
第二就是不要意外。好比说,你在作一个数据库,你号称跟 MySQL如出一辙,那么你展示出来任何跟 MySQL 不同的东西都会让用户吓一跳,并且这是很重要的一个开发原则。
第三就是悲观预设。永远都会有各类各样的恶心事情和异常的情况发生,其实这是做为分布式系统开发工程师天天都要对本身说的话。业务的数据是重于泰山的,可是任何的基础设施都是会挂的,你的网线可能会断,整个数据中心可能会 shut down……你要预设你的数据库是必定会丢数据的,这个时候你的数据库设计才会更好。如何保护本身如何保护业务,咱们作了一些神奇的工具,好比像 syncer,这个东西就是把 TiDB cluster 做为一个 假的 slave 接到 MySQL 上,业务在上面跑 MySQL ,后面存起来实际上是个集群。另外还作了一个比较变态的事情,就是反向。咱们顶在业务上面,下面能够接到 MySQL。MySQL 能够作 TiDB 的 slave,TiDB 能够作 MySQL 的 slave。这个功能对于业务来讲是很是惊喜的。有的客户一开始想用 TiDB可是有点惧怕,我说不要紧,你在后面接个从,而后你在从上面去查询。好比你原来一个 SQL 跑了 20 分钟,如今我能让你跑到 10 秒之内;或者你跑了大概半年都一点数据没丢,系统很是稳定,再切成主的业务代码中更改。
一个架构师老是要去想一些将来十年会发生什么。有一个名词 Cloud-Native,我是认为一切的东西在将来都会跑在云端,如何针对云上这个环境去设计基础软件?数据库设计一个很重要的原则是,数据一旦发生了宕机它可以自动修复和均衡数据,人在里边给这些集群加机器就好了,整个集群必定要可以有本身的思考。将来怎么针对 Cloud 作基础架构,这是一个须要去思考的问题。