「一切都会运行在云端」。
云时代早已来临,本文着眼于顶级云服务商云服务商的云数据库方案背后的架构,以及笔者最近观察到的一些对于云数据库有意义的工业界的相关技术的进展,希望读者能有所收获。
现在越来越多的业务从自己维护基础设施转移到公有(或者私有)云上, 带来的好处也是无需赘述的,极大降低了 IaaS 层的运维成本,对于数据库层面来说的,以往需要很强的 DBA 背景才能搞定弹性扩容高可用什么的高级动作,现在大多数云服务基本都或多或少提供了类似的服务。
Amazon RDS
其实说到公有云上的云数据库,应该最早 Amazon 的 RDS,最早应该是在 2009 年发布的,Amazon RDS 的架构类似在底层的数据库上构建了一个中间层(从架构上来看,阿里云 RDS,UCloud RDS 等其他云的 RDS 服务基本是大同小异,比拼的是功能多样性和实现的细节)。
这个中间层负责路由客户端的 SQL 请求发往实际的数据库存储节点,因为将业务端的请求通过中间层代理,所以可以对底层的数据库实例进行很多运维工作,比如备份,迁移到磁盘更大或者 IO 更空闲的物理机等。这些工作因为隐藏在中间层后边,业务层可以做到基本没有感知,另外这个中间路由层基本只是简单的转发请求,所以底层可以连接各种类型的数据库。
所以一般来说,RDS 基本都会支持 MySQL / SQLServer / MariaDB / PostgreSQL 等流行的数据库,对兼容性基本没有损失,而且在这个 Proxy 层设计良好的情况下,对性能的损失是比较小的。另外有一层中间层隔离底层的资源池,对于资源的利用和调度上可以做不少事情。
简单举个例子,比如有一些不那么活跃的 RDS 实例可以调度在一起共用物理机,比如需要在线扩容只需要将副本建立在更大磁盘的机器上,在 Proxy 层将请求重新定向即可,比如定期的数据备份可以放到 S3 上,这些一切都对用户可以做到透明。
但是这样的架构缺点也同样明显:本质上还是一个单机主从的架构,对于超过最大配置物理机的容量,CPU 负载,IO 的场景就束手无策了。随着很多业务的数据量并发量的增长,尤其是移动互联网的发展,无限的可扩展性成为了一个很重要需求。当然对于绝大多数数据量要求没那么大,单实例没有高并发访问的库来说,RDS 仍然是很适合的。
Amazon DynamoDB
对于刚才提到的水平扩展问题,一些用户实在痛的不行,甚至能接受放弃掉关系模型和 SQL。比如一些互联网应用业务模型比较简单,但是并发量和数据量巨大,应对这种情况,Amazon 开发了 DynamoDB,并于 2012 年初发布 DynamoDB 的云服务。其实 Dynamo 的论文早在 2007 年就在 SOSP 发表,这篇有历史意义的论文直接引爆了 NoSQL 运动,让大家觉得原来数据库还能这么搞。
Dynamo 对外主打的特点是水平扩展能力和通过多副本实现(3 副本)的高可用,另外在 API 的设计上可以支持最终一致性读取和强一致性读取,最终一致性读取能提升读的吞吐量。
但是请注意,DynamoDB 虽然有强一致读,但是这里的强一致性并不是传统我们在数据库里说的 ACID 的 C,而且由于没有时序的概念(只有 vector clock),对于冲突的处理只能交给客户端,Dynamo 并不支持事务。不过对于一些特定的业务场景来说,扩展能力和可用性是最重要的,不仅仅是容量,还有集群的吞吐。
阿里云 DRDS
但是那些 RDS 用户的数据量也是在持续增长的,对于云服务提供商来说不能眼睁睁的看着这些 RDS 用户数据量一大就走掉或者自己维护数据库集群。因为也不是谁都能彻底重构代码到 NoSQL 之上,并且分库分表其实对于业务开发者来说是一个很痛苦的事情,在痛苦中往往是蕴含着商业机会的。
比如对于 RDS 的扩展方案,我介绍两个比较典型的:第一个是阿里云的 DRDS (不过现在好像从阿里云的产品列表里拿掉了?),DRDS 其实思路很简单,就是比 RDS 多一小步,在刚才提到的 RDS 的中间层中加入用户配置的路由策略,比如用户可以指定某个表的某些列作为 sharding key 根据一定规则路由到特定的实例,也可以垂直的配置分库的策略。
其实 DRDS 的前身就是淘宝的 TDDL,只不过原来 TDDL 是做在 JDBC 层,现在将 TDDL 做进了 Proxy 层(有点像把 TDDL 塞到 Cobar 的感觉)。这样的好处是,将应用层分库分表的工作封装起来了,但是本质上仍然是一个中间件的方案,尽管能对简单的业务做到一定程度的 SQL 兼容。
对于一些复杂查询,多维度查询,跨 Shard 事务支持都是有限了。毕竟中间路由层对 SQL 的理解有限,至于更换 Sharding key 、DDL、备份也是很麻烦的事情。从 Youtube 开源的中间件 Vitess 的实现和复杂程度来看甚至并不比实现一个数据库简单,但是兼容性却并没有重新写一个数据库来得好。
Amazon Aurora
后来时间来到了 2015 年,Amazon 走了另外一条路。在 2015 年,Amazon Aurora 发布。Aurora 的资料在公网上并不多,Aurora 提供了 5x 于单机 MySQL 5.6 的读吞吐能力,不过最大也就扩展到 15 个副本,副本越多对写吞吐影响越大,因为只有一个 Primary Instance 能提供写入服务,单个副本最大支持容量 64T,而且支持高可用以及弹性的扩展。
值得一提的是 Aurora 的兼容性。其实做数据库的都知道,兼容性是一个很难解决的问题,可能实现上很小的差异就会让用户的迁移成本变得很大,这也是为什么中间件和分库分表的方案如此反人类的原因,我们大多都在追求用户平滑的迁移体验。
Aurora 另辟蹊径,由于公开的资料不多,我猜想 Aurora 在 MySQL 前端之下实现了一个基于 InnoDB 的分布式共享存储层( https://www.percona.com/blog/2015/11/16/amazon-aurora-looking-deeper/ )。对于读实例来说是很好水平扩展的,这样就将 workload 均摊在前端的各个 MySQL 实例上,有点类似 Oracle RAC 那样的 Share everything 的架构。
这个架构的好处相对中间件的方案很明显,兼容性更强,因为还是复用了 MySQL 的 SQL 解析器。优化器,业务层即使有复杂查询也没关系,因为连接的就是 MySQL。
但是也正是由于这个原因,在节点更多,数据量更大的情况下,查询并不能利用集群的计算能力(对于很多复杂查询来说,瓶颈出现在 CPU 上),而且 MySQL 的 SQL 优化器能力一直是 MySQL 的弱项,而且对于大数据量的查询的 SQL 引擎的设计是和单机有天壤之别的。一个简单的例子,分布式 Query Engine 比如 SparkSQL / Presto / Impala 的设计肯定和单机的 SQL 优化器完全不同,更像是一个分布式计算框架。
所以我认为 Aurora 是一个在数据量不太大的情况下(有容量上限),对简单查询的读性能优化的方案,另外兼容性比中间件的方案好得多。但是缺点是对于大数据量,复杂查询的支持还是比较弱,另外对于写入性能 Aurora 其实没有做太多优化(单点写入),如果写入上出现瓶颈,仍然需要在业务层做水平或者竖直拆分。
(点击放大图像)
Google Cloud BigTable
Google 作为大数据的祖宗一样的存在,对于云真是错过了一波又一波:虚拟化错过一波让 VMWare 和 Docker 抢先了(Google 早在十年前就开始容器的方案,要知道容器赖以生存的 cgroups 的 patch 就是 Google 提交的);云服务错过一波让 Amazon 抢先了(Google App Engine 真是可惜):大数据存储错过一波让开源的 Hadoop 拿下了事实标准,以至于我觉得 Google Cloud BigTable 服务中兼容 Hadoop HBase API 的决定,当时实现这些 Hadoop API for BigTable 的工程师心中应该是滴血的 :)
不过在被 Amazon / Docker / Hadoop 刺激到以后,Google 终于意识到社区和云化的力量,开始对 Google Cloud 输出 Google 内部各种牛逼的基础设施,2015 年终于在 Google Cloud Platform 上正式亮相。对于 BigTable 的架构相信大多数分布式存储系统工程师都比较了解,毕竟 BigTable 的论文也是和 Amazon Dynamo 一样是必读的经典,我就不赘述了。
BigTable 云服务的 API 和 HBase 兼容,所以也是 {Key : 二维表格结构},由于在 Tablet Server 这个层次还是一个主从的结构,对一个 Tablet 的读写默认都只能通过 Tablet Master 进行,这样使得 BigTable 是一个强一致的系统。这里的强一致指的是对于单 Key 的写入,如果服务端返回成功,接下来发生的读取,都能是最新的值。
由于 BigTable 仍然不支持 ACID 事务,所以这里的强一致只是对于单 Key 的操作而言的。对于水平扩展能力来说, BigTable 其实并没有什么限制,文档里很嚣张的号称 Incredible scalability,但是 BigTable 并没有提供跨数据中心(Zone)高可用和跨 Zone 访问的能力。
也就是说,一个 BigTable 集群只能部署在一个数据中心内部。这其实看得出 BigTable 在 Google 内部的定位,就是一个高性能低延迟的分布式存储服务,如果需要做跨 Zone 高可用需要业务层自己做复制在两个 Zone 之间同步,构建一个镜像的 BigTable 集群。
其实 Google 很多业务在 MegaStore 和 Spanner 出来之前,就是这么搞的。对于 BigTable 来说,如果需要搞跨数据中心高可用,强一致,还要保证低延迟那是不太可能的,也不符合 BigTable 的定位。另外值得吐槽的是 BigTable 团队发过一个 Blog :
( https://cloudplatform.googleblog.com/2015/05/introducing-Google-Cloud-Bigtable.html )。
里面把 HBase 的延迟黑得够呛,一个 .99 的响应延迟 6 ms, HBase 280ms。其实看平均响应延迟的差距不会那么大…BigTable 由于是 C++ 写的,优势就是延迟是相当平稳的。但是据我所知 HBase 社区也在做很多工作将 GC 带来的影响降到最小,比如 off-heap 等优化做完以后,HBase 的延迟表现会好一些。
Google Cloud Datastore
在 2011 年,Google 发表了 Megastore 的论文,第一次描述了一个支持跨数据中心高可用 + 可以水平扩展 + 支持 ACID 事务语义的分布式存储系统。 Google Megastore 构建在 BigTable 之上,不同数据中心之间通过 Paxos 同步,数据按照 Entity Group 来进行分片。Entity Group 本身跨数据中心使用 Paxos 复制,跨 Entity Group 的 ACID 事务需要走两阶段的提交,实现了 Timestamp-based 的 MVCC。
不过也正是因为 Timstamp 的分配需要走一遍 Paxos,另外不同 Entity Groups 之间的 2PC 通信需要通过一个队列来进行异步的通信,所以实际的 Megastore 的 2PC 的延迟是比较大的,论文也提到大多数的写请求的平均响应延迟是 100~400ms 左右。据 Google 内部的朋友提到过,Megastore 用起来是挺慢的,秒级别的延迟也是常有的事情。
作为应该是 Google 内部第一个支持 ACID 事务和 SQL 的分布式数据库,还是有大量的应用跑在 Megastore 上,主要是用 SQL 和事务写程序确实能轻松得多。为什么说那么多 Megastore 的事情呢?因为 Google Cloud Datastore 的后端就是 Megastore。
其实 Cloud Datastore 在 2011 年就已经在 Google App Engine 中上线,也就是当年的 Data Engine 的 High Replication Datastore,现在改了个名字叫 Cloud Datastore,当时不知道背后原来就是大名鼎鼎的 Megastore 实在是失敬。
虽然功能看上去很牛,又是支持高可用,又支持 ACID,还支持 SQL(只不过是 Google 精简版的 GQL)但是从 Megastore 的原理上来看延迟是非常大的,另外 Cloud Datastore 提供的接口是一套类似的 ORM 的 SDK,对业务仍然是有一定的侵入性。
Google Spanner
虽然 Megastore 慢,但是架不住好用。在 Spanner 论文中提到,2012 年大概已经有 300+ 的业务跑在 Megastore 上,在越来越多的业务在 BigTable 上造 ACID Transaction 实现的轮子后,Google 实在受不了了,开始造一个大轮子 Spanner,项目的野心巨大,和 Megastore 一样,ACID 事务 + 水平扩展 + SQL 支持。
但是和 Megastore 不一样的是,Spanner 没有选择在 BigTable 之上构建事务层,而是直接在 Google 的第二代分布式文件系统 Colossus 之上开始构建 Paxos-replicated tablet。
另外不像 Megastore 实现事务那样通过各个协调者通过 Paxos 来决定事务的 timestamp,而是引入了硬件,也就是 GPS 时钟和原子钟组成的 TrueTime API 来实现事务。这样一来,不同数据中心发起的事务就不需要跨数据中心协调时间戳,而是直接通过本地数据中心的 TrueTime API 来分配,这样延迟就降低了很多。
Spanner 近乎完美的一个分布式存储,在 Google 内部也是的 BigTable 的互补,想做跨数据中心高可用和强一致和事务的话,用 Spanner,代价是可能牺牲一点延迟,但是并没有 Megastore 牺牲那么多;想高性能(低延迟)的话,用 BigTable。
Google Spanner 目前没有在 Google Cloud Platform 中提供服务,但是看趋势简直是一定的事情,至少作为 Cloud Datastore 的下一代是一定的。另外一方面来看 Google 仍然没有办法将 Spanner 开源,原因和 BigTable 一样,底层依赖了 Colossus 和一堆 Google 内部的组件,另外比 BigTable 更困难的是,TrueTime 是一套硬件。
所以在 12 年底发布 Spanner 的论文后,社区也有开源的实现,比如目前比较成熟的 TiDB 和 CockroachDB,一会提到社区的云数据库实现的时候会介绍。Spanner 的接口比 BigTable 稍微丰富一些,支持了它称之为 Semi-relational 的表结构,可以像关系型数据库那样进行 DDL,虽然仍然要指定每行的 primary key,但是比简单的 kv 还是好太多。
Google F1
在 Spanner 项目开始的同时,Google 启动了另外一个和 Spanner 配套使用的分布式 SQL 引擎的项目 F1,底层有那么一个强一致高性能的 Spanner,那么就可以在上层尝试将 OLTP 和部分的 OLAP 打通。F1 其实论文题目说是一个数据库,但是它并不存储数据,数据都在 Spanner 上,它只是一个分布式查询引擎,底层依赖 Spanner 提供的事务接口,将用户的 SQL 请求翻译成分布式执行计划。
Google F1 提供了一种可能性,这是在其他的数据库中一直没有实现过的:OLTP 与 OLAP 融合的可能性。因为 Google F1 设计目标是给 Google 的广告系统使用,广告投放系统这类系统一是对于一致性要求很高,压力也很大,是典型的 OLTP 场景;第二是可能会有很多复杂的广告投放效果评估的查询,而且这类的查询越是实时越好,这又有点实时 OLAP 的意思。
传统的做法是 OLTP 的数据库将数据每隔一段时间同步一份到数据仓库中,在数据仓库中离线的进行计算,稍微好点的使用一些流式计算框架进行实时计算。第一种使用数据仓库的方案,实时性是比较差的,倒腾数据是很麻烦的事情;至于使用流式计算框架的方案,一是灵活性不好,很多查询逻辑需要提前写好,没法做很多 Ad-hoc 的事情,另外因为两边是异构的存储,导致 ETL 也是很麻烦的工作。
F1 其实依靠 Spanner 的 ACID 事务和 MVCC 的特性实现了 100% 的 OLTP,并且自身作为一个分布式 SQL 引擎,可以利用集群的计算资源实现分布式的 OLAP 查询。这带来的好处就是并不需要额外在设置一个数据仓库进行数据分析,而是直接在同一个数据库里实时分析,另外由于 Spanner 的 MVCC 和多副本带来的 Lock-free snapshot read 的特性,这类 OLAP 查询并不会影响正常 OLTP 的操作。
对于 OLTP 来说,瓶颈经常出现在 IO 上。对于 OLAP 来说,瓶颈反而经常出现在 CPU 也就是计算上。其实看上去是能融合起来,提升整个集群的资源利用率,这也是我看好 Google F1 + Spanner 这个组合的原因,未来的数据库可能会融合数据仓库,提供更完整且更实时的体验。
(其实这个下面的 GFS 不太准确,现在应该是 Colossus)
Open source cloud-native database
2016 年在硅谷突然有个新词火了起来 GIFEE,Google Infrastructure For Everyone Else,大家意识到好像随着新一代的开源基础软件的繁荣发展,原来在 Google 内部的基础设施已经有很多高质量的开源实现。
比如容器方面有 Docker,调度器方面 Google 主动开源的 Borg 的第二代 Kubernetes,传统的 BigTable 和 GFS 社区还有虽然屎但是还是能凑合用的 Hadoop,而且很多大厂觉得 Hadoop 屎的都基本自己都造了类似的轮子……
更别说最近 Google 开源上瘾,Kubernetes 就不提了,从大热的 Tensorflow 到相对冷门但是我个人认为意义重大的 Apache Beam(Google Cloud Dataflow 的基础),基本能独立开源的都在积极的拥抱社区。
这就造成了社区与 Google 内部差距正在缩小,但是目前来说,Spanner 和 F1 并不是那么容易造的。就算抛开 TrueTime 的硬件不提,实现一个稳定的 Multi-Paxos 都不是容易的事情,另外分布式 SQL 优化器这种事情也是有很高技术门槛的,另外就算造出来了,测试的复杂度也一点不比实现的复杂度低(可以参考 PingCAP 的分布式测试哲学的几篇分享)。
目前从全球范围内来看,我认为开源世界只有两个团队:一个 PingCAP 的 TiDB,一个 CockroachLabs 的 CockroachDB 是有足够的技术能力和视野能将 Spanner 的开源实现造出来的。目前 TiDB 已经 RC1 并有不少使用者在生产环境使用,比 CockroachDB 的成熟度稍好,架构上更接近正统的 F1 above Spanner 的架构。CockroachDB 的成熟度稍微落后一些,并且协议选择 PostgreSQL,TiDB 选择的是 MySQL 的协议兼容。
而且从 TiDB 的子项目 TiKV 中,我们看到了新一代分布式 KV 的雏形,RocksDB + Multi-Raft 不依赖第三方分布式文件系统(DFS)提供水平扩展能力,正在成为新一代分布式 KV 存储标准架构。另外也很欣喜的看到竟然是由一个国内团队发起并维护的这样级别的开源项目,即使放到硅谷也是顶级的设计和实现,从 Github 的活跃度和使用的工具及运营社区的流程上来看,很难看出是一个国内团队。
Kubernetes + Operator
刚才提到了一个词 Cloud-Native,其实这个词还没有准确的定义,不过我的理解是应用开发者和物理设施隔离,也就是业务层不需要再去关心存储的容量性能等等一切都可以透明水平扩展,集群高度自动化乃至支持自我修复。
对于一个大规模的分布式存储系统来说人工是很难介入其中的,比如一个上千个节点的分布式系统,几乎每天都可能有各种各样的节点故障,瞬时网络抖动甚至整个数据中心直接挂掉,人工去做数据迁移,数据恢复几乎是不可能的事情。
很多人非常看好 Docker,认为它改变了运维和软件部署方式,但是我认为更有意义的是 Kubernetes。调度器才是 Cloud-native 架构的核心,容器只是一个载体而已并不重要,Kubernetes 相当于是一个分布式的操作系统,物理层是整个数据中心,也就是 DCOS,这也是我们在 Kubernetes 上下重注的原因,我认为大规模分布式数据库未来不可能脱离 DCOS。
不过 Kubernetes 上对于有状态的服务编排是一件比较头疼的事情。而一般的分布式系统的特点,他不仅每个节点都有存储的数据,而且他还要根据用户需要做扩容,缩容。
当程序更新时要可以做到不停服务的滚动升级,当遇到数据负载不均衡情况下系统要做 Rebalance,同时为了保证高可用性,每个节点的数据会有多个副本,当单个节点遇到故障,还需要自动恢复总的副本数。而这些对于 Kubernetes 上的编排一个分布式系统来说都是非常有挑战的。
Kubernetes 在 1.3 版本推出了 Petset ,现在已经改名叫 StatefulSet, 核心思想是给 Pod 赋予身份,并且建立和维护 Pod 和 存储之间的联系。当 Pod 可能被调度的时候,对应的 Persistent Volume 能够跟随他绑定。但是它并没有完全解决我们的问题,PS 仍然需要依赖于 Persistent Volume。
目前 Kubernetes 的 Persistent Volume 只提供了基于共享存储,分布式文件系统或者 NFS 的实现,还没有提供 Local Storage 的支持,而且 Petset 本身还处于 Alpha 版本阶段,我们还在观望。
不过除了 Kubernetes 官方社区还是有其他人在尝试,我们欣喜的看到,就在不久之前,CoreOS 提出了一个新的扩展 Kubernetes 的新方法和思路。CoreOS 为 Kubernetes 增加了一个新成员,叫作 Operator。
Operator 其实是一种对 Controller 的扩展,具体的实现由于篇幅的原因我就不罗嗦了,简单来说是一个让 Kubernetes 调度带状态的存储服务的方案,CoreOS 官方给出了一个 Etcd-cluster 的备份和滚动升级的 operator 实现,我们也在开发 TiDB 的 operator,感兴趣的可以关注我们的 Github 了解最新的进展。
老司机简介
黄东旭,PingCAP 联合创始人 /CTO,资深 infrastructure 工程师,擅长分布式存储系统的设计与实现,开源狂热分子,著名的开源分布式缓存服务 Codis 的作者,对于开源文化和技术社区建设有独到的理解。
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论