分布式数据库已经流行好多年,产品非常众多,其中分布式数据库中间件使用场景最广。本文主要是总结如何基于分布式数据库中间件做数据库架构设计,以充分发挥它的分布式能力。各个中间件产品功能核心原理相同,细节上有些区别。这里仅以阿里云的DRDS
为例分析,在产品架构、功能、成熟度和市场占有率上,它都比同行产品有优势。
首先为了避免被误解为:「手里有把锤子,看什么都是钉子!」,说明一下不是什么业务都适合分布式数据库,更不是用了分布式数据库性能就一定能得到扩展。这个我在另外一篇文章《分布式数据库的拆分设计实践》已经有过分析。这里以DRDS
的使用为例更深阐述这个观点。
本文有关「线性扩展」的分析是来自于DRDS
产品团队梦实
的分享。有关如何发挥分布式资源能力是个人观点,仅供参考。DRDS
的功能相对很完备,如全局Sequence
、异构索引表、分库分表策略、分布式Join
、小表广播、分布式事务(柔性事务和强一致事务)、读写分离和只读实例、HTAP查询
等。这些在官网上都有详尽的介绍。这里总结的是官网上没有明示的或者重点突出的业务数据库架构设计经验。
好的数据库性能首先是应用设计出来的(还会有其他方面因素),如果你不认同这个观点,那本文并不适合你。
文章《一些关系数据库的架构总结》列举过很多分布式数据库的架构图,它们都有一个共同的特点就是主体功能都是在MySQL
数据库前面部署了一个中间件。这个中间件接管并响应应用的SQL
请求。所以它的基础必备能力就是解析SQL
,做分库分表路由,到底层MySQL
数据库里取数据并可能做一些计算(排序聚合等)然后返回给应用。这个中间件对应用屏蔽了表被拆分的细节。
DRDS
如上是运维视角下的DRDS
的架构图。(注:这里划分和后面各个概念都是个人理解。运维人员是能看到DRDS
的各个组成模块。)
DRDS
数据库整体上是分为两层。一个是中间件层,或者也叫服务层(也叫DRDS Server
),负责响应SQL请求,承担部分计算(SQL
)功能。Server
由多个特定相同资源规格(CPU
和内存)的进程组成,运算数据会在内存里,但不持久化,所以Server
层简单理解没有存储功能。也可以理解为Server层
是无状态的(内存数据可以丢失)。
数据是存储在下面的数据库层(或者叫存储层)。这个数据库通常就是一组MySQL
实例(在云上是RDS MySQL
实例)。数据库层除了存储数据也承担了部分SQL
计算功能,不过这里的SQL
通常不会太复杂。所以也有一个不完全正确的观点:在分布式MySQL
数据库集群里,都是把MySQL
当存储用的。
有几个简单概念突出一下:
Server
层:由一组DRDS Server
(后简称Server
)组成,Server
节点是部署在ECS
上的一个Java
进程,ECS
的资源规格(CPU
和内存)就是Server
节点的主要能力。用户购买的DRDS
实例实际上就是购买一组Server
节点。每个DRDS
实例至少会包含2个Server
节点(因为要高可用,其次是负载均衡),规格很大的实例,会有4或8个Server
节点组成(负载均衡是主要目的)。DRDS
实例的规格决定了它的主要计算能力。MySQL
实例组成,不同的实例其包含的数据都是全部数据的子集(广播表的数据会在多个实例内部冗余这个例外)。每个实例有自己的Slave
实例,不是重点后面都忽略它。每个实例代表了一定的资源能力(CPU
、内存和空间)。MySQL
里就是数据库,分库说的是这个数据库是总体数据的子集,一个MySQL
实例会包含多个分库,在RDS里默认是8个(外部实例默认不让改,这个设定导致了DRDS
的拆分设计思路在内部业务和外部业务上呈现不同的特点,其中有一个比较难理解)。MySQL
每个数据库下的普通表(非分区表),分表说的是它的数据是总数据的子集,并且在所有实例里有很多结构相同的表(只是可能表名后面的编号不同)。每个物理分库下可以有1个或多个分表,不同产品特点不一。QPS
:所有MySQL
实例的QPS
总和,衡量数据库层压力的一个指标。TPS
同理。QPS
:所有Server
节点的QPS
总和,衡量DRDS
实例压力的一个指标。TPS
同理。`DRDS`在内部叫`TDDL`,没有`Server层`,而是跟应用代码部署在一起(应用引用`TDDL`的一个`Jar包`)。拆分规则、数据库拓扑和连接信息等配置会由其他模块(集中式部署)推送(`PUSH`)到各个应用客户端(可能客户端也有自己`PULL`逻辑)。
DRDS
如上是业务视角下的DRDS
架构图。对业务而言看到的就是一个数据库实例,实例下有库有表。这几个概念很简单:
DRDS
实例,会有个链接地址,通常是域名或者某个负载均衡产品上的VIP
。DRDS
实例下的数据库,后端是由一组物理分库组成。理论上业务只要申请到DRDS
实例然后建库建表即可。稍有不同的时候需要设计物理分库的数量和物理分表的数量。后面重点首先是介绍这个分库分表的设计,然后是业务SQL
如何写最佳。
分库分表设计首先要根据业务选择合适的拆分维度以及拆分策略。这个在前文《分布式数据库的拆分设计实践》已经有过分析。这里重点说的分多少个库和分多少个表的选择考虑。
技术上DRDS
也支持不做分库分表拆分这种用法,不过这个中间层就显得多余了。当业务要做水平拆分决定的时候,通常是因为业务碰到下面几个难题:
CPU
瓶颈或者IO
瓶颈等)。具体是哪个瓶颈会影响分库分表数量的选择。
首先常见的一类问题是“表数据量到多少就要拆分?或者表大小到多少就要拆分?”。 业务方总希望能用一个万能的公式就直接给出答案。如果产品给出了一个公式,那多半是被逼的;如果业务方简单去套用公式,那多半就是不清楚数据库细节懒于分析。
换个问题“拆分为多少个分表比较合适?”。总分表数也是数据的分片数。 总数据分表数目一旦确定后,后期调整数量就非常不方便(那意味着全量数据重分布)。这个就是选择这个分表数的第一个考虑点。
分表是存在于分库中,分库在分实例里,多个实例组成了全部的业务数据。关于分表数这里倒是有个简单万能的公式:
总分表数(N
) = 总物理实例数(X
)* 每个实例下的分库数(Y
)* 每个分库下的分表数(Z
)
所以,当你定一个总的分表数N
时,这个N
要能够拆分为三个数(X
、Y
和Z
)的乘积。这个是选择分表数的第二个考虑点。而这个X
和Y
的选择都有讲究。
最后分表数目也不能太大,否则元数据管理成本会比较高,对稳定性和性能都有影响。这点不同产品能力不一样。DRDS
的内部案例里分表数最多到4096
。
所以分多少个表此时还没有结论。先往后看。
每个物理实例占用了一定的主机资源(CPU
、内存和空间),提供一定的计算和存储能力,重点是计算能力,具体指标就是QPS
(也包括TPS
)。
我们先假设每个物理实例都独占一台主机资源,那么一个物理实例的能力上限就是那个主机的能力上限。如果一个DRDS
实例后端包括一个物理实例(默认Slave
实例不提供服务,这里不考虑读写分离场景),那理论上它数据层最大的能力就是一台主机的能力;如果后端包括两个物理实例,那理论上它数据层最大的能力就是两台主机的能力。
所以,一个DRDS
实例的计算能力有个简单公式就是:
DRDS
实例数据层计算能力(物理QPS
) = ∑ 物理实例i
的QPS
, i
∊ (1, X
)
所以,如果当初主要是解决集中式数据库的计算能力瓶颈的时候,拆分为多少个实例就决定了DRDS
实例的理论上的上限。大规模业务分布式数据库集群能力评估的时候,这个公式是主要考量点。评估的结果如果少了一个数量级,则很可能导致业务有性能风险。
还有个别业务一个是基于存储能力考虑的,这个同理。
注意:
SQL
写法。RDS MySQL
实例都是有具体的规格的(如多少CPU
多少内存多少空间等),资源之间有一定的资源隔离策略。所以存在拆分为2个物理实例,但是这2个实例依然在一台主机上。由于MySQL
的功能特点,2个小实例的计算能力很可能也高于合并为1个大实例后的计算能力。具体以业务实际运行结果为准。DRDS
内部最多128
个实例。1个实例是否能方便扩容为2个实例,取决于这个实例里有多少个分库。
如果1个实例的分库数是2或2的倍数,那拆分为两个实例还是比较方便的,简单说把分库对半分。具体就是搭建一个Slave
实例,数据同步追上后断开同步,分别去掉一半分库。
如果1个实例只有1个分库,那这个方法就行不通。还要继续看有多少个分表,然后对分表集合进行对半分。但总体操作上没有对分库集合对半分要方便。
这种对半拆的方案是最简单的,但并不是唯一的选择。DRDS
有能力通过精卫对数据全量进行重分布,从而突破不可继续对半分
的限制,只是要消耗更多资源和更多时间。通常运维会选择对半拆。所以分库数量决定了实例可以分拆(扩容)的次数。
注意:
由上面分析知,分多少个实例(X
)主要决定了整个DRDS
实例后端计算能力的上限(部分业务还要考虑存储能力因素,但越是规模大的业务,对计算能力要求弹性远高于存储能力)。每个实例下多少个分库(Y
)决定了未来的弹性扩容的能力(倍数)。
所以也可以粗略的说X
与Y
的乘积决定了这个DRDS
实例后端未来最大的计算能力。 在业务初步上线的时候,可以X
很小,Y
稍微大一些;在业务规模上去后,再调大X
调小Y
。这就是分布式数据库容量评估的艺术。可以说考虑未来若干年内的需求时,这个X
和Y
的乘积基本确定了,然后总的分表数(N
)就看每个分库下的分表数(Z
)了。
关于这个Z
的指定,DRDS
在内外业务上的处理方法稍有不同。
DRDS
建表语句支持指定分表数,指定的就是这个Z
的值(并不是指定总的分表数)。DRDS
对这个Z
值并没有建议要求。可以是奇数、质数、合数都没关系。每个分表名只是在分库内部不重名,不同分库的分表名是一样的。
总分表数会通过公式 N
=X*Y*Z
来计算。这个计算结果值不宜超过目前实践最大值(4096
)。
在内部业务设计里,分表总数是在建表的时候指定,一般是2的幂,16起步,最大4096.然后你才会看到每个分库下有多少个分表。每个分表的命名在全局不重名(尾部编号不同)。
这是两种设计习惯,当先熟悉了一种后再用另外一种会有一点不适应,至少我是这么感觉的。
分表数的选择有可能要考虑业务数据分布特点。当有很多热点数据的时候,选择分区策略后要尽可能让每个分表数据尽可能分布均衡,并且访问量也尽可能的均衡。但是绝对均衡也很难,只是说如果业务上访问有热点数据,总分表数尽量大一些,以便热点数据能够分散的开一些。这只是个建议,对于特别热点的访问(比如说秒杀IPhoneX
)这个效果也不明显(还需要其他手段并用,热点行应对策略以后有时间再分享)。
前面分库分表的设计是把分布式数据库集群的最大能力尽可能的提升,但并不意味着业务SQL
就一定能发挥出数据库的分布式能力。所以DRDS
提出一个线性扩展的概念。
这里线性扩展
是DRDS
用来描述SQL
的一种能力的。我不确认在分布式领域或者其他什么领域是否也有这个概念。如果有先忽略它在别的地方的含义。
线性扩展
指的是随着业务规模成倍的增长,对分布式数据库后端实例进行弹性扩容(增加实例数)后,业务SQL
的响应时间(RT
)能维持不变或者小范围的变慢,以及吞吐量能相应倍数的增长。
线性扩展能力是衡量单个SQL
的扩展性能力,跟SQL
写法有关。不同的SQL
表现可能不一致,我们只考虑核心业务SQL
或者对性能影响很大的SQL
。
当业务表拆分为N
个分表后,分布在X
个实例里。DRDS
建议业务SQL
尽可能的带上具体的拆分条件。这样Server
节点可以直接将SQL
路由到后端具体的MySQL
实例中。
每个请求落在具体的实例节点上,不同请求可能落在不同的实例节点上,所有实例同时提供服务,DRDS
实例整体吞吐量最大。如下图,当SQL
都带拆分键(等值条件)后,1个逻辑QPS
就对应1个物理QPS
。
此时如果后端数据层扩容增加了1个MySQL
实例,则物理QPS
提升50%
,逻辑QPS
也提升50%
。当然这里前提是Server
节点层不是瓶颈(Server
节点的QPS
能力高于MySQL
的QPS
能力。这类SQL
的线性扩展能力就很好。
如果SQL
没有带上拆分键,则Server
节点会将SQL
请求路由到所有MySQL
实例。1个逻辑QPS
就对应3个物理QPS
。
此时如果后端数据层扩容了,虽然物理QPS
提升了50%
时,但是Server
节点的能力并没有增加。这类SQL
的线性扩展能力就不好。
这个对比很好理解。
IN
查询业务的线性扩展能力分析上面说的SQL
带了拆分键并且是等值条件。也有一类SQL
带了拆分键,但是是拆分键IN
查询。
Server
节点在处理这个SQL
时,会将IN
后的LIST
值一个个判断在后端哪个MySQL
实例里。如果都是属于同一个MySQL
实例,那效果跟上面那种带了拆分键等值条件一样。这是最好的情形,取决于业务数据。如果LIST
里的值都是分属于不同的MySQL
实例,则近似于上面不带拆分键查询情形。
如下面是买家订单查询业务举例。拆分键是订单ID,16个分实例。每个订单IN
查询里的列表长度平均为2.假设分布均匀,每个逻辑QPS
就对应2个物理QPS
。如果单个实例的QPS
能力是1000,则DRDS
的逻辑QPS
能力就是8000。
但是如果后期业务规模增长,平均一个买家订单数量扩了100倍(IN
后面列表长度200),实例扩容了4倍。此时整个DRDS
的逻辑QPS
能力是1000。相比之前的业务能力,这个是下降了。
大家很容易看到导致业务能力下降的关键是新业务下SQL
的IN
列表长度比实例数要多很多。这是一类业务特点。
还有一类业务即使业务规模增长,其IN
后的列表长度不会大变。比如说人口业务。根据子女ID
查询相关信息。虽然总人口增长了很多,单每个家庭子女数量平均在1-2个左右。这个场景下如果实例扩容了,业务是能收获相应倍数的QPS
提升。
Join
上面描述的拆分键IN
查询还是很直观的,容易理解。稍微复杂一点的业务SQL
会使用表连接。这个又分多种情形。
一是做根据参加表连接的表类型。分为非拆分表和拆分表的连接、拆分表和拆分表的连接。后者还分拆分维度是否一致。
二是根据连接条件是否是拆分键。有些连接条件是一方的拆分键是另外一方的非拆分键。
分析分布式Join
的线性扩展能力关键首先了解连接的算法。通常是嵌套循环(Nested-Loop Join
)。然后判断在应用目标表连接条件时该条件是否是拆分键。再参照前面拆分键IN
查询的表现综合分析。
本节只是个引子,具体分析可以参考梦实的分享。
分布式数据库(中间件)的特点就是分库分表,这个比较灵活和容易理解,使用场景最广。具体分多少个实例多少个库会影响分布式数据库性能理论上限。此外,SQL
能否将该分布式数据库的性能能力都发挥出来,取决于SQL
的写法,一般跟拆分键的行为有关。不同分布式数据库产品的功能有细节上的差别,但是分库分表的逻辑基本相同,所以上面的分析同样适用于其他分布式数据库(中间件)产品。
OceanBase里的租户的能力取决于资源单元(Unit
)的数量,OceanBase里的分区表设计思路等同于分库分表思路。所以上面的思路也可以在OceanBase的业务数据库设计里参考。
个人总结,如有表达不当之处欢迎指正。公众号文章还没有留言功能,可以给公众号留言。
本文作者 庆涛,蚂蚁OceanBase数据库解决方案架构师,对分布式数据库,业务架构有比较深入理解。公众号二维码,欢迎围观。
-The End-