演讲实录|黄东旭:分布式数据库模式与反模式

我叫黄东旭,是 PingCAP 的联合创始人兼 CTO,也是本场论坛的主持人。我原来在 MSRA,后来到了网易、豌豆荚。跟在座的大部分数据分析师不太一样的是,我是一个数据库开发,虽然是 CTO,但是还在写代码。

同时,我也是一些用的比较广泛的分布式的开源软件的作者。比如说我们做的 TiDB、TiKV 这些大型的分布式关系型数据库的项目。

我们现在正在做一个 OLTP 的数据库,主要 focus 在大数据的关系型数据库的存储和可扩展性,还有关系的模型,以及在线交易型数据库上的应用。

所以,今天整个数据库的模式和反模式,我都会围绕着如何在一个海量的并发,海量的数据存储的容量上,去做在线实时的数据库业务的一些模式来讲。并从数据库的开发者角度,来为大家分享怎样写出更加适合数据库的一些程序。

基础软件的发展趋势

一开始我先简单介绍一下,现在我认为的一些基础软件上的发展趋势。

开源

第一点,开源是一个非常大的趋势。大家可以看到一些比较著名的基础软件,基本都是开源的,比如 Docker,比如 k8s。甚至在互联网公司里面用的非常多的软件,像 MySQL、Hadoop 等这种新一代的大数据处理的数据库等基础软件,也大多是开源的。其实这背后的逻辑非常简单:在未来其实你很难去将你所有的技术软件都用闭源, 因为开源会慢慢组成一个生态,而并不是被某一个公司绑定住。比如国家经常说去 IOE,为什么?很大的原因就是基本上你的业务是被基础软件绑死的,这个其实是不太好的一个事情。而且现在跟过去二十年前不一样,无论是开源软件的质量,还是社区的迭代速度,都已经是今非昔比,所以基本上开源再也不是低质低量的代名词,在互联网公司已经被验证很多次了。

分布式

第二,分布式会渐渐成为主流的趋势。这是为什么?这个其实也很好理解,因为随着数据量越来越大,大家可以看到,随着现在的硬件发展,我感觉摩尔定律有渐渐失效的趋势。所以单个节点的计算资源或者计算能力,它的增长速度是远比数据的增长速度要慢的。在这种情况下,你要完成业务,存储数据,要应对这么大的并发,只有一种办法就是横向的扩展。横向的扩展,分布式基本是唯一的出路。scale-up 和 scale-out 这两个选择其实我是坚定的站在 scale-out 这边。当然传统的关系数据库都会说我现在用的 Oracle,IBM DB2,他们现在还是在走 scale-up 的路线,但是未来我觉得 scale-out 的方向会渐渐成为主流。 碎片化

碎片化

第三,就是整个基础软件碎片化。现在看上去会越来越严重。但是回想在十年前、二十年前,大家在写程序的时候,我上面一层业务,下面一层数据库。但是现在你会发现,随着可以给你选择的东西越来越多,可以给你在开源社区里面能用到的组件越来越多,业务越来越复杂,你会发现,像缓存有一个单独的软件,比如 redis,队列又有很多可以选择的,比如说 zeromq, rabbitmq, celery 各种各样的队列;数据库有 NoSQL、HBase,关系型数据库有 MySQL 、PG 等各种各样的基础软件都可以选。但是就没有一个非常好东西能够完全解决自己的问题。所以这是一个碎片化的现状。

微服务

第四,是微服务的模式兴起。其实这个也是最近两年在软件架构领域非常火的一个概念。这个概念的背后思想,其实也是跟当年的 SOA 是一脉相承的。就是说一个大的软件项目,其实是非常难去 handle 复杂度的,当你业务变得越来越大以后,维护成本和开发成本会随着项目的代码量呈指数级别上升的。所以现在比较流行的就是,把各个业务之间拆的非常细,然后互相之间尽量做到无状态,整个系统的复杂度可以控制,是由很多比较简单的小的组件组合在一起,来对外提供服务的。

这个服务看上去非常美妙,一会儿会说有什么问题。最典型的问题就是,当你的上层业务都拆成无状态的小服务以后,你会发现原有的逻辑需要有状态的存储服务的时候你是没法拆的。我所有的业务都分成一小块,每一小块都是自己的数据库或者数据存储。比如说一个简单的 case,我每一个小部分都需要依赖同一个用户信息服务,这个信息服务会变成整个系统的一个状态集中的点,如果这个点没有办法做弹性扩展或者容量扩展的话,就会变成整个系统很致命的单点。

所以现在整个基础软件的现状,特别在互联网行业是非常典型的几个大的趋势。我觉得大概传统行业跟互联网行业整合,应该在三到五年,这么一个时间。所以互联网行业遇到的今天,可能就是传统行业,或者其他的行业会遇到的明天。所以,通过现在整个互联网里面,在数据存储、数据架构方面的一些比较新的思想,我们就能知道如何去做这个程序的设计,应对明天数据的量级。

现有存储系统的痛点

其实今天主要的内容是讲存储系统,存储系统现在有哪些痛点?其实我觉得在座的各位应该也都能切身的体会到。

弹性扩展

首先,大数据量级下你如何实现弹性扩展?因为我们今天主要讨论的是 OLTP ,是在线的存储服务,并不是离线分析的服务。所以在线的存储服务,它其实要做到的可用性、一致性,是要比离线的分析业务强得多的。但是在这种情况下,你们怎样做到业务无感知的弹性扩展,你的数据怎么很好的满足现有的高并发、大吞吐,还有数据容量的方案。

可用性

第二,在分布式的存储系统下,你的应用的可用性到底是如何去定义,如何去保证?其实这个也很好理解,因为在大规模的分布式系统里面,任何一个节点,任何一个数据中心或者支架都有可能出现硬件的故障,软件的故障,各种各样的故障,但这个时候你很多业务是并没有办法停止,或者并没有办法去容忍 Down time 的。所以在一个新的环境之下,你如何对你系统的可用性做定义和保证,这是一个新的课题。一会儿我会讲到最新的研究方向和成果。

可维护性

第三,对于大规模的分布式数据库来说它的可维护性,这个怎么办?可维护性跟单机的系统是明显不同的,因为单机的数据库,或者传统的单点的数据库,它其实做到主从,甚至做到一主多从,我去维护 master ,别让它挂掉,这个维护性主要就是维护单点。在一个大规模的分布式系统上,你去做这个事情是非常麻烦的。可以简单说一个案例,就是 Google 的 Spanner。Spanner 是 Google 内部的一个大规模分布式系统,整个谷歌内部只部署了一套,在生产环节中只部署了一套。这一套系统上有上万甚至上数十万的物理节点。但是整个数据库的维护团队,其实只有很小的一组人。想像一下,上十万台的物理节点,如果你要真正换一块盘、做一次数据恢复或者人工运维的话,这是根本不可能做到的事情。但是对于一个分布式系统来说,它的可维护性或者说它的维护应该是转嫁给数据库自己。

开发复杂度

还有,就是对于一个分布式数据库来说,它在开发业务的时候复杂度是怎么样的。大家其实可能接触的比较多的,像 Hbase、 Cassandra、Bigtable 等这种开源的实现,像 NoSQL 数据库它其实并没有一个很好的 cross-row transaction 的 support。

另外,对于很多的 NoSQL 数据库并没有一个很好的 SQL 的 interface,这会让你写程序变得非常麻烦。比如说对于一些很普通的业务,一个表,我需要去 select from table,然后有一个fliter 比如一个条件大于 10,小于 100,这么简单的逻辑,如果在 HBase 上去做的话,你要写十行、二十行、三十行;如果你在一个关系的数据库,或者支持 SQL 的数据库,其实一行就搞定了。

其实这个对于很多互联网公司来说,在过去的几年之内基本上已经完成了这种从 RDBMS 到 NoSQL 的改造,但是这个改造的成本和代价是非常非常高的。比如我原来的业务可能在很早以前是用 MySQL 已经写的稳定运行好久了,但是随着并发、容量、可扩展性的要求,我需要迁移 Bigtable、Hbase、Cassandra、MongoDB 这种 NoSQL 数据库上,这时基本上就要面临代码的完整重写。这个要放在互联网公司还可以,因为它们有这样的技术能力去保证迁移的过程。反正我花这么多钱,招这么牛的工程师,你要帮我搞定这个事情。但是对于传统的行业,或者传统的机构来说,这个基本上是不可能的事情。你不可能让他把原来用 Oracle 用SQL 的代码改成 NoSQL 的 code。

因为 NoSQL 很少有跨行事务,首先你要做一个转账,你如果不是一个很强的工程师,你这个程序基本写不对,这是一个很大的问题。这也是为什么一直以来像这种 NoSQL 的东西并没有很好的在传统行业中去使用的一个最核心的原因,就是代价实在太大。

存储系统的扩展模型

所以其实在去讲这些具体到底该怎么解决,或者未来数据库会是什么样的之前,我想简单讲一下扩展的模型。对于一个关系型数据库也好,对于存储的系统本身也好,它的扩展模型有哪些。

Sharding 模式

第一种模式是 Sharding 模式。如果在座的各位有运维过线上的 MySQL 的话,对这个模型会非常熟悉。最简单的就是分库、分表加中间件,就是说我不同的业务可能用不同的库,不同的表。当一个单表太大的时候,我通过一些 Cobar、Mycat 等这样的数据库中间件来去把它分发到具体的数据库的实例上。这种模型是目前用的最普遍的模型,它其实也解决了很大部分的问题。为什么这十年在关系型数据库上并没有很好的扩展方案,但是大家看上去这种业务还没有出现死掉的情况,就是因为后面有各种各样 Sharding 的中间件或者分库分表这种策略在硬扛着。像这种中间件 Sharding 第一个优势就是实现非常简单。你并不需要对数据库内做任何的改造,你也并不需要去比如说从你原来的 SQL 代码转到 NoSQL 的代码。

但是它也有自己的缺点。首先,对你的业务层有很强的侵入性。 这是没有办法的,比如你想用一个中间件,你就需要给它指定一个 Sharding key。另外,原来比如你的业务有一些 join ,有一些跨表跨行的事务,像这种事务你必须得改掉,因为很多中间件并没有办法支持这个跨 shard 的分布式 join。 第二个比较大的缺陷是它的分片基本是固定的,自动化程度、扩展性都非常, 你必须得有一个专职的 DBA 团队给你的 MySQL 或者 PG 的 Sharding 的集群去做运维。我之前在豌豆荚做过一段时间 MySQL cluster 的分片的维护工作。当时我记得是一个 16 个节点的 MySQL 的集群,我们需要扩展到 32 个节点的规模,整整提前演练了一个月,最后上线了一个礼拜。上线那个礼拜,晚上基本上没有办法睡觉,所以非常痛苦。

再说一个 Google 的事情,Google 在刚才我说的 Spanner 和 F1 这两个数据库没有上线之前,Google 的广告系统的业务是由 100 多个节点的 MySQL 的集群对外提供服务的。如 Google 这么牛的公司,在维护一百多个节点的 MySQL Sharding 的数据库的时候,都已经非常痛苦,宁可重新去写一个数据库,也不想去维护这个 database cluster。其实大家可以看到,像这种 Sharding 的方案,它比较大的问题就是它的维护代价或者维护集群的复杂度,并不是随着节点数呈线性增长,而是随着节点的增加非线性的增长上去。比如你维护 2 个节点的还好,维护 4 个节点的也还可以,但是你维护 16 个、64 个、128 个基本就是不可能的事情。

第三就是一些复杂的查询优化,并没有办法在中间件这一层,去帮你产生一个足够优化的执行计划,因此,对于一些复杂查询来说,Sharding 的方案是没法做的。所以对你的业务层有很高的要求。

这是一种思路,是目前来说互联网公司里边用的最多的一种 MySQL 或者 PG 这种关系型数据库的扩展方案。

Region Base 模型

第二种扩展模型是 Region Base。这张图是我项目里面扒出来的图。

Region Base

Region Base

它整个思路有点像 Bigtable,它相当于把底下的存储层分开,数据在最底层存储上已经没有表、行这样结构的划分,每一块数据都由一个固定的 size 比如 64 M、128 M 连续的 Key-value pairs 组成。其实这个模型背后最早的系统应该是谷歌在 06 年发表的 Bigtable 这篇论文里面去描述的。这个模型有什么好处呢?一是它能真正实现这种弹性的扩展。第二个,它是一个真正高度去中心化。去中心化这个事情,对于一个大的 Cluster 来说是一个非常重要的特性。

还有一个优势,在 KV 层实现真正具有一定的自动 Failover 的能力。 Failover指的是什么呢?比如说在一个集群比较大的情况下,或者你是一个 cluster ,你任何一个节点,任何一个数据损坏,如果能做到业务端的透明,你就真正实现了 Auto-Failover 的能力。其实在一些对一致性要求不那么高的业务里面,Auto-Failover 就是指, 比如在最简单的一个 MySQL 组从的模型里,当你的组挂掉了以后,我监控的程序自动把 slave 提上来,这也是一种 Failover 的方式。但是这个一致性或者说数据的正确性并不能做到很好的保证。你怎么做到一致性的 Auto-Failover,其实背后需要做非常非常多的工作。

这是 Region 模型的一些优势。但是它的劣势也同样明显,这种模型的实现非常复杂。 我一会儿会说到背后的关键技术和理论,但是它比起写中间件真的复杂太多了。你要写一个能用的 MySQL 或者 PG 的中间件,可能只需要一两个工程师,花一两周的时间就能写出一个能用的数据库中间件;但是你如果按照这个模型做一个弹性扩展的数据库的话,你的工作量就会是数量级的增加。

第二个劣势就是它业务层的兼容性。 像 Region Base 的模型,最典型的分布式存储系统就是 HBase。HBase 它对外的编程接口和 SQL 是千差万别,因为它是一个 Key Value 的数据库。你的业务层的代码兼容性都得改,这个对于一些没有这么强开发能力的用户来说,是很难去使用的,或者它说没有 SQL 对于用户端这么友好。

可用性级别

我一会儿会讲一下,刚才我们由 Region Base 这个模型往上去思考的一些东西,在此之前先说一些可用性。高可用。其实说到高可用这个词,大多数的架构师都对它非常熟悉。我的系统是高可用的,任何一个节点故障业务层都不受影响,但是真的不受影响吗?我经过很多的思考得到的一个经验就是主从的模型是不可能保证同时满足强一致性和高可用性的。可能这一点很多人觉得,我主从,我主挂了,从再提起来就好,为什么不能保护这个一致性呢?就是因为在一个集群的环境下,有一种故障叫脑裂。脑裂是什么情况?整个集群是全网络联通的,但是出现一种情况,就是我只是在集群内部分成了两个互不联通的一个子集。这两个子集又可以对外提供服务,其实这个并不是非常少见的状况,经常会发生。像这种情况,你贸然把 slave 提起来,相当于原来的 master 并没有完全的被 shutdown,这个时候两边可能都会有读写的情况,造成数据非常严重的不一致,当然这个比较极端了。所以你会发现阿里或者说淘宝,年年都在说我们有异地多活。但是去年甚至前几个月,杭州阿里的数据中心光纤被挖断,支付宝并没有直接切到重复层,而是宁可停止服务,完全不动,也不敢把 slave 数据中心提起来。所以其实任何基于主从模型的异地多活方案都是不行的。这个问题有没有办法解决呢?其实也是有的。

还是说到 Google,我认为它才是全世界最大的数据库公司,因为它有全世界最大的数据量。你从来没有听说过 Google 哪一个业务因为哪一个数据中心光纤挖断,哪一个磁盘坏了而对外终止服务的,几乎完全没有。因为 Google 的存储系统大多完全抛弃了基于主从的一致性模型。它的所有数据都不是通过主从做复制的,而是通过类似 Raft 或者 Paxos 这种分布式选举的算法做数据的同步。这个算法的细节不展开了,总体来说是一个解决在数据的一致性跟自动的数据恢复方面的一个算法。同时,它的 latency 会比多节点强同步的主从平均表现要好的一个分布式选举的算法。

在 Google 内部其实一直用的 Paxos,它最新的 Spanner 数据库是用 Paxos 做的 replication 。在社区里面,跟 Paxos 等价的一个算法就是 Raft。Raft 这个算法的性能以及可靠性都是跟 Paxos 等价的实现。这个算法就不展开了。我认为这才是新一代的强一致的数据库应该使用的数据库复制模型。

分布式事务

说到事务。对于一个数据库来说,我要做传统的关系型数据库业务,事务在一个分布式环境下,并不像单机的数据库有这么多的方法,这么多的优化。其实在分布式事务这个领域只有一种方法,并且这么多年了从分布式事务开始到现在,在这个方法上并没有什么突破,基本只有一条出路就是两阶段提交。其实可以看一下 Google 的系统。对于我们做分布式系统的公司来说,Google 就是给大家带路的角色。Google 最新的数据库系统上它使用的分布式事务的方法仍然是两阶段提交。其实还有没有什么优化的路呢?其实也是有的。两阶段提交最大的问题是什么呢?一个是延迟。因为第一阶段先要把数据发过去,第二阶段要收到所有参与的节点的 response 之后你才能去 commit 。这个过程,相当于你走了很多次网络的 roundtrip,latency 也会变得非常高。所以其实优化的方向也是有的,但是你的 latency 没法优化,只能通过吞吐做优化,就是 throughput 。比如说我在一万个并发的情况下,每个用户的 latency 是 100 毫秒,但是一百万并发,一千万并发的时候,我每个用户的 latency 还可以是 100 毫秒,这在传统的单点关系型数据库上,是没有办法实现的。第二就是去中心化的事务管理器。另外没有什么东西是银弹,是包治百病的,你要根据你的业务的特性去选择合适的一致性算法。

NewSQL

其实刚刚这些 pattern 会发展出一个新的类别,我们能不能把关系数据库上的一些 SQL、Transaction 跟 NoSQL 跟刚才我说到的 Region Base 的可扩展的模型融合起来。这个思想应该是在 2013 年左右的时候,学术界提出来比较多的东西,NewSQL。NewSQL 首先要解决 Scalability 的问题, 刚给我们说过 scalability 是一个未来的数据库必须要有的功能,第二个就是 SQL,SQL 对于业务开发者来说是很好的编程的接口。第三,ACID Transaction,我希望我的数据库实现转帐和存钱这种强一致性级别的业务。第四,就是整个 cluster 可以支持无穷大的数据规模,同时任何数据节点的宕机、损坏都需要集群自己去做监控,不需要 DBA 的介入。

案例:Google Spanner / F1

有没有这样的系统?其实有的。刚才一直提到 Google 的 Spanner 系统。Spanner 系统是在 2012 年底于 OSDI 的会议上发布了论文; F1 这篇论文在 2013 年的 VLDB 发布的,去描述了整个 Google 内部的分布式关系型数据库的实现。首先,根据 Spanner 的论文 Spanner 和 F1 在生产环境只有一个部署,上万物理节点遍布在全球各种数据中心内,通过 Paxos 进行日志复制。第二,整个架构是无状态的 SQL 层架在一个 NoSQL 的基础之上。第三,它对外提供的是一个跨行事务的语义,这个跨行事务是透明的跨行事务,我不需要对我的业务层做修改,它是通过 一个硬件支持的 Truetime API,GPS 时钟和原子钟去实现事务的隔离级别。这个系统是最早用来支撑 Google 的在线广告业务,在线广告业务大家知道,其实对于扣费、广告计费、点击记录,广告活动等一致性的级别要求非常高。首先这个钱不能多扣,也不能少扣,实时性要求非常高。而且业务逻辑相当复杂,这个时候需要一个 SQL 数据库,需要一个支持事务的数据库,需要一个支持 Auto-Failover 的数据库,所以就有了 Google Spanner/F1 这个系统。

典型业务场景

最典型的业务场景是什么呢?对于这种高吞吐、大容量的数据量级对于 NoSQL 系统来说,典型的应用的 Pattern 就是高吞吐大容量,还有就是 Workload 相对比较分散的情况。比较典型的反例是秒杀,秒杀这个场景其实非常不适合这种 NewSQL。另外就是一致性、可用性和 latency 之间怎么做取舍。在这种分布式数据库上面,第一个要丢弃的就是延迟,在这么大规模的量级上做强一致性,延迟是首先是不能保证的。你去看谷歌的 F1 和 Spanner 的论文里面,它提到它们的 latency 基本都是 10 毫秒、20 毫秒、甚至50 毫秒、100 毫秒的量级,但是它并不会太关心 latency 。因为它必须保证通过 Paxos 去做跨机房、多机房的复制,光速你肯定没法超越,所以这个时间是省不了的,它整个系统是面向吞吐设计的,而不是面向低延迟设计的,但是它要求非常强的一致性。

MySQL Sharding

一个典型的场景就是替换 MySQL Sharding 的业务。它的业务典型的特点,就是高吞吐,海量并发的小事务,并不是特别大的 transection,模型也相对简单,没有复杂的 join 的程序。但是痛点刚才提到了非常明显,首先就是对于 MySQL Sharding 方案 scale 能力很差,对于表结构变更的方案并没有太多;再者,cross shard transaction,目前 Sharding 的方案并没有办法很好支持,但 NewSQL 面向的场景和 MySQL Sharding 面向的场景是非常的像的。

Cross datacenter HA

第二种场景是 Cross datacenter HA(跨数据中心多活),这种场景简直是数据开发者追求的圣杯。因为像 Oracle、DB2,它在最关键,最核心的业务上,比如说像银行这种业务,它必须要求实现这种跨数据中心的高可用。但是在一个分布式的数据库上,或者说是 Open-source solution 里,有没有人有办法实现这个 Cross datacenter HA 呢?完全没有。目前来说并没有任何一个数据库能去解决这个问题。因为刚才提到了主从的模型根本是不可靠的。像这种业务的数据极端的重要,任何一点数据的丢失都不能容忍。另外即使整个数据中心宕机也不能影响线上的业务,很典型的像支付宝,这种涉及钱相关的,甚至有些比如你的银行卡,你肯定不能容忍说你吃完饭,告诉你不好意思数据中心挂了,你的钱我刷不了。但是并没有开源的数据库能解决这个问题,如果你真的自己做同步复制的方案的话,特别两地三中心的情况,你请求的延迟是取决于离你最远的数据中心,所以大部分业务来说延迟过大。 另外就是这些系统重度依赖人工运维。我认为一旦任何系统有人工的介入一定是会出错的。因为人是最难自动化的一个因素。

反模式

滥用传统关系模型

对于这种大规模的分布式 NewSQL 来说,比较大的反模式就是,大量的使用存储过程 foreign key,视图等操作。因为首先存储过程是没有办法 scale 的。另外,大表与大表的 JOIN ,在线上的 OLTP 数据库上最大的开销并不是优化器的开销,而是网络通信的开销。比如两个表去做一个笛卡儿积,算起来可能并不是这么慢,但是要把数据一条一条在网络中传输代价是非常大的,这种情况下用 OLAP 的数据库是比较适合。 没有利用好并发

没有利用好并发

刚才我一直强调对于新型的 OLTP 或者分布式关系型数据库来说,并发或者说吞吐才是应该追求的优化点。在架构设计时,不应该把对延迟非常敏感的业务去使用分布式数据库来实现。所以其实在延迟跟吞吐之间,数据库永远去选择吞吐。所以写程序会遇到的一个很典型的问题,就是我的查询是一行一行,上下之间有这种强依赖关系的模式。其实在这种模式下,对于分布式关系型数据库来说是非常不合适的。所以你应该用并发的思想去做,比如说我的请求,我可以把吞吐打到整个集群上,我的 Database,我的 cluster 会自动 balance 一些 workload,如果一行行查询之间是有依赖的话,那么每一条查询之间的 latency 是会叠加起来。所以这个其实并不是一个太好的 pattern。

不均匀的设计

数据库其实经常会被大家滥用。比如说我看到很多传统行业数据库使用的方法,其实真的是非常痛苦。无论什么业务我全都直接上 Oracle,不管这个业务是否真的需要 Database 来去支持;比如有人用 MySQL 做计数器,有些有 MySQL 做队列或者做秒杀,这个并不是太 scale 的东西。特别像秒杀的业务,特别高的并发打到同一个 key 上,workload 没办法去分拆到好多个节点来帮你去处理,所以整个分布式的集群就会退化成一个单点,这是非常不合理的。第二,当你的索引设计的不太好的时候,涉及到的过多全表扫描是非常不划算的。刚刚也说过,在一个分布式集群里面,最大的开销是网络的开销,你做全表扫描的时候并不是在一台机器上,而是在好多机器上做全表扫描,同时经过很大的网络传输,当你的索引设计不合理的时候会出现这样的问题。还有是过多的无效索引,会导致整个系统在写入的时候有延迟也好或者吞吐也好,会变得更慢。主要注意的就是不要存在任何可以把你的系统退化成单点的机会。

错误的一致性模型

还有一种最容易犯的问题,就是在没有好好思考你的业务适合的场景情况下,就去使用很强的一致性模型。因为当你要求一个非常高的强一致性的时候,你的分布式事务的 latency 一定比不这么强的一致性的业务要高得多的。其实,很多业务并不需要那么强的一致性,我的数据库虽然给你了这个能力,但如果你去滥用的话,你会说这个数据库好慢,或者为什么我这个请求100 毫秒才给我返回。但其实很多场景下,你并不是这么强的依赖强一致性 。这个跟数据库本身提不提供这个机制是没有关系的。作为一个 Database 开发者来说,我还是需要给你能实现这个功能的机制,但是不要滥用。

另外根据你业务的冲突的频繁程度选择不同的锁策略。你知道这个业务就是一个很高的冲突的场景,比如说可能像类似发工资,一个集团的账号发到成千上万的小账号。如果你是一个乐观事务的话,可能涉及到的冲突就会很大。因为所有的事务的发起者都要去修改这个公共账号,这种情况下一般可以使用悲观事务,因为你已经预知到你业务的 conflict 级别会很高。但当我知道我的业务的冲突很小,我要追求整个系统的吞吐,我可能会选用乐观的事务的模型。

我的演讲就到这儿,谢谢。

Q&A

提问:您好,我简单问一下对于秒杀这个场景,什么样的数据库设计是合适的?

黄东旭:其实秒杀这个业务是一个比较系统的工程,并不是一个简单的我用什么数据库就可以做的东西。我之前给一些朋友做秒杀的架构设计的时候,从最上层,甚至你可能从 JS 这边就要去做排队,一层一层的排队。比如在缓存这边我可以用 redis,我可以用一些缓存的数据库再去进行二级的排队,因为每一次排队你都会丢掉很多,最后到底下的队列,在数据库这一层,比如库存就十个,你只要在前面放十个进来就行了。就是说,你在上层把流量用排队的方式做更好。最典型的例子就是大家看 12306 卖火车票的时候经常会有排队。基本没有什么数据库能去应对,像淘宝,像京东这种秒杀的业务,它从上到下是整个系统的过程。谢谢。

提问:您好,我问一下,你说的是分布式的 OLTP 数据库,历史数据怎么清理,如果后续还需要分析数据的话。

黄东旭:其实在谷歌 Spanner 包括像 TiDB 、TiKV 这个系统之上,是通过一种 MVCC 的技术,每一条数据都会有一个版本号。比如说你不停的 update,它可能会产生好多版本。我背后会有一个 GC 的逻辑,去选定一个 safe point,在这个 safe point 之前的所有数据全都删掉也好,还是挪到冷的存储上也好,我们是通过这种方式实现的。另外随着数据量的膨胀,整个系统设计的时候,我们是通过 Raft 算法,比如说这个数据慢慢长大,我会把它切成两块,再慢慢 Auto-balance 到其它的节点上,然后新的那一块会再长大……这相当于一个细胞一样,一个细胞分裂成两个细胞,成千上万的细胞会均匀的分布在整个集群里面。如果你要删数据可以直接删,如果你不想删的话,你的数据可以全都留下,你自己随便往集群里增加新的节点。这个集群很大,它会自动帮你把数据均匀的分到这儿。所有的历史数据是通过 GC 的模块把历史的版本拿出来,丢到冷的存储上。

在 Google 几乎是完全不删数据的,因为存储的成本是很低的,但是数据未来说不定什么时候就用了。大概是这样,我需要有一个办法能把它存下来。

提问:当前做的交易要使用历史的参考数据,可能量不是很大,有这样的例子吗?

黄东旭:当然有。像 MVCC 在访问历史,它其实提供了一个 Lock-free 的 Snapshot Read, 它在读历史版本的时候是不会读线上的正在进行的读写事务,所以它是有一个无所读的机制,这也是为什么它要去采用每一个数据都要有版本号的机制去实现。它是有这个需求,而且在实现的时候也非常漂亮,不会阻塞其他的请求。

提问:你们的 TiDB 是怎么考虑用 MySQL 或者 PG 的?当时是怎么考虑的?

黄东旭:首先我们没有用 MySQL 或者 PG,刚才说的 TiDB 这个模型是谷歌的 Spanner 跟 F1 的模型。用传统的单机数据库做改造的话,有一个比较大的误区,整个 SQL 的查询优化器跟存储的数据结构,并不知道你底层是分布式的存储,它还是假设你生成 SQL 的方案都是单机的。比如最简单的例子,我需要去 count *,如果是一个单机的 MySQL 优化器,没有建索引的话会一行一行把数据拉过来计一个数,这样算下去;但是如果对一个分布式系统来说,我只要把 count * 这个逻辑推到所有存储表的数据节点上,算法再 reduce 回来就可以,更像一个 Mapreduce 这种分布式框架的 SQL 优化器。如果你没有从底到上完整的去实现 Database 的话,你很难对分布式的场景或者分布式数据存储的这些性质来去对数据库做优化。没有办法,这是一条很难的道路,但是我们也得去下走。

分享到微信

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