分布式数据库的拆分设计实践
前言
并不是换了分布式数据库后,应用性能就一定会提升。也并不是用了分布式数据库后,应用所有场景性能都会提升。
本文总结一下开发转型使用分布式关系数据库时做设计需要考虑的几个问题。好的性能是设计出来,应用是数据库的上游。应用性能不好,表面上原因可能是数据库慢,实际上可能是前期做数据库设计时方法不当。
了解分布式关系数据库有关的特点
分布式数据库的能力很多,开发做设计可以关注一下以下几个问题。
问题1: 数据如何分片存储?
水平拆分就是将数据分片存储,每个分片的结构是一致的,只是数据不同。分布式数据库的水平拆分实现方式目前见到三种。一是分区,一是分库分表,还有就是按定长块拆分。
分区是数据库的分区表的概念。如Oracle的分区表里的分区。蚂蚁的OceanBase也支持分区表的分区分布到不同机器实例上。业务SQL读取表,数据库内核会自动找到具体的分片。
分库分表是分布式数据库中间件的概念。中间件将业务定义的表(又叫逻辑表)的数据存储在多个MySQL实例的多个库的多个同构的表(又叫物理表)下,并在业务读取的时候做逻辑表和物理表的映射转换。
每个分片表示的范围就是拆分的粒度。分区的粒度最小是到分区,分库分表的粒度是到分表。这两个粒度对运维都是可见的,可以在设计的时候通过定义规则来干预数据分到哪个分片。
第三种拆分方式按定长块拆分,就是拆分粒度更细,是一个定长块。如Google的Spaaner产品的拆分粒度是 Directory,Directory是一段连续的空间,大小在几十MB左右。还有开源的TiDB产品也是这个思路,拆分粒度是Region。这种粒度对运维不可见,目前看没有办法定义规则来控制数据分布的特点。
问题 1.1: 数据分片算法是什么?
这是另外一个角度的描述数据拆分特点的。
水平拆分时,数据分片算法,不同数据库做法不完全一致。
Oracle支持多种分区策略:RANGE、HASH、LIST、复合分区、间隔分区等。还可以自动创建分区,以及分区分裂。支持局部索引和全局索引。
DRDS是分库分表,策略更丰富。可以只分表不分库、只分库不分表、既分库又分表。一般第一种使用意义不大,多是后面两种。并且分库和分表的拆分算法可以不一样。拆分算法支持RANGE、HASH、LIST、UNI_HASH、RANGE_HASH。分库和分表同时拆分就是一种复合分区的用法。全局索引是通过同步维护一个索引表实现。
OceanBase也支持多种分区策略:RANGE、HASH、LIST、复合分区等,也支持分区分裂、局部索引和全局索引。目标是对标Oracle,目前功能上还相对有些不完善。
TiDB不支持拆分算法指定,所以业务无法干预底层分片规则。TiDB支持全局索引。
问题2: SQL如何执行?
无论是哪种拆分实现方式,业务写的SQL都是一样的,区别在于执行SQL的细节和能力不一样。
分库分表方式下,SQL都要经过中间件层,然后才会被转发或者略加转换然后转发到多个后端MySQL里去执行,结果再集中返回到中间层节点,然后再返回给客户端。后端MySQL的性能是MySQL的能力决定,中间件层对SQL的支持程度和性能就很关键。如DRDS产品的DRDS Server节点。
通过数据库内核实现的拆分,内核会有一个模块接收SQL请求,解析SQL生成分布式执行计划,去多个节点获取数据等。如TiDB的TiDB Server节点、OceanBase的 OBProxy+OBServer(其中OBProxy只做单纯的路由)。
更深入的SQL执行细节就是SQL的执行计划方面的。如SQL改写:谓词下推、子查询消除、聚合下推等技术。
问题3: 分布式事务怎么支持?
现在的分布式数据库都声称支持分布式事务,在实现细节和效果上会有细微的区别。
分布式事务如果是在中间件层实现,有多种方法。
方法一是基于XA协议的两阶段提交。这个架构分两层,事务管理器和本地资源管理。后者依赖数据库是否支持XA协议。XA很常见不多说。MySQL 5.7才实现对XA的完美支持。这个方法好处是保证分布式事务的ACID特性,数据是严格一致,缺点就是数据库锁粒度太大,支持的并发很低,吞吐量很低。DRDS支持XA协议的分布式事务(MySQL是5.7)。
方法二是用消息中间件实现最终一致。这个架构原理是将分布式事务拆分为一个消息事务(A系统的本地操作和消息)和B系统的本地操作。B系统本地操作是靠A系统的消息触发的,所以是异步的。数据是最终一致。B有重试逻辑,如果B系统操作一直失败,需要人介入处理。
方法三是TCC编程模式。这个架构也是两阶段提交,实现了一个编程框架,将业务逻辑分为Try、Confirm和Cancel三块。Try方法做业务资源的锁定,Confirm方法做业务事务逻辑的执行,Cancel方法做对Try的回滚操作。所有成员的Try方法成功后,自动调用对应的Confirm操作;任何一个成员的Try方法失败,都会触发对应的Cancel方法。这个架构优点是锁粒度小,性能好,吞吐量大,缺点是业务需要实现三个方法,并且数据最终一致。蚂蚁的SOFA-DTX支持TCC分布式事务。
方法四是FMT编程模式。这个是TCC的改进版。FMT依然有Try、Conform和Cancel三块,只是业务只要写Try方法做业务逻辑,框架会自动的读取业务修改数据的快照并保存起来。在Confirm方法里删除快照,在Cancel里用快照数据回滚Try的修改。SOFA-DTX、阿里云的GTS、DRDS都支持这种分布式事务。
分布式事务实现最好的途径是在数据库内核里实现,依然是基于XA协议的两阶段提交,不过在锁的策略上有些优化。比如TiDB和OceanBase都针对两阶段提交做了不同的优化。数据都是强一致,性能也比传统2PC有很大提升。
问题4: 如何做弹性伸缩?
分布式数据库的弹性伸缩对业务是透明的,无感知的,这是最基本的要求。水平拆分方法的不同,弹性伸缩的原理和效果也有所不同。
使用分布式数据库中间件做水平拆分的,弹性伸缩时有两个途径。一是数据分片规则不变,动态调整后端MySQL的资源规格。如果MySQL主机资源不足,会涉及到MySQL实例的搬迁,即投入更多的机器。如DRDS的扩容可以不改变分库和分表数量,只改变实例里分库数目和实例总数、以及实例占用的机器数等方式扩容。在这个方法没有效果的时候,还有个途径就是调整分片规则。如调大分库分表数目,这涉及到数据的重分布,需要借助数据同步组件或者产品将老实例数据迁入到新实例里。
使用分区或者定长块做水平拆分的,弹性伸缩同理。一是不改变数据分片规则,只是将分片的最小粒度尽可能的分布到更多的机器上。如TiDB的PD组件将数据分散多更多的TiKV节点上(需要扩容TiKV机器),OceanBase将分区分散到更多的OBServer节点上(资源用尽时也需要扩容机器)。二是改变数据分片规则。如调整分区数。如OceanBase的分区分裂功能。无论那种,涉及到数据迁移时都是内核内部实现,所以数据绝不会丢失。
问题5: 分布式数据库性能好在哪里?
分布式数据库用好的情况下,总体性能是高于集中式数据库,会体现在下面几点。
优点一:SQL的响应时间更短。这个有两个原因。
一是分片(分区)裁剪。数据分片存储,单个SQL访问时只需要扫描更小粒度的数据。当SQL里带上拆分键(或者分区键)时,会先有一个分片(分区)裁剪的动作,剩下的就只需要到一个很小的范围内去搜索数据。此时还有索引就更好。SQL的响应时间在分布式数据库下会更低。
二是并行读取。数据分片存储,如果SQL没有带上拆分键(或者分区键)时,就需要扫描多个分片(分区)甚至全部分片(分区)。在集中式数据库里,大表的读取性能都是不好,开启并行会有提升。只是并行有上限,跟主机CPU个数有关。在分布式数据库下,分片并行读取到上限可以更高,跟实际主机的CPU无关,跟调度组件的能力有关。当分片存储在不同节点(机器)上,这个并行可以更高。
优点二:SQL的吞吐量更高。单个节点(机器)的能力是一定的,单个节点处理的吞吐量也有上限。分片存储在不同节点上,就可以同时发挥多个机器的能力,所以总体的吞吐量会更高。
注意分布式数据库性能好是有前提的,就是拆分设计合理和SQL写的好。
数据库表拆分设计考虑点
好的性能首先是设计出来的。
虽然不同分布式数据库实现细节有些不同,但对业务而言,表进行拆分设计时要考虑的问题基本相同。
问题1:按什么维度拆分?
首先,梳理业务特点。目前经验是OLTP型业务比较适合分布式拆分。业务场景还可以细分出哪些是重要的?哪些对性能要求很高?哪些是次要的?哪些对影响要求不是很高。哪些是前端业务?哪些是后端业务?
通常来说面向用户的前端业务比较重要,性能要求高。前端业务访问量大,性能差一点,用户体验就不好,可能流失。后端任务访问量低,处理数据量大,性能可以慢一点,用户多是内部人,容忍度可以高一点。
其次,最重要的场景优先考虑。一个拆分设计如果能满足所有场景需求,自然是最好。但实际上往往不现实。最终拆分设计要优先考虑最重要的场景。
同一份数据,使用者不同,可能就有不同的查询维度。水平拆分的优点要发挥出来,前提就是访问数据时能带上拆分键。拆分键是有对应业务含义,一个拆分键通常难以包含两个或者多个维度的业务含义。对于一个交易表,为照顾买家性能,按买家ID做水平拆分,则卖家根据卖家ID查询时就不可避免的要查询所有分片,性能不是最好。同样的,根据交易ID去查也面临相同问题。
这里有个变通的方式,在字段数据设计上,可以让交易ID跟买家ID有内在联系,应用在SQL上变化一下,就解决了交易ID查询性能问题。如DRDS的拆分函数RANGE_HASH就很好的解决了这种场景。
根据卖家ID查询这个场景就不好变通了。只能靠全局索引去解决。Oracle支持全局索引,OceanBase 2.x版本也会支持全局索引。DRDS通过内部同步组件实现了一个类似全局索引的表。
问题2: 数据拆分算法选什么?
不同分布式数据库的共性拆分算法都支持LIST、RANGE、HASH、复合分区。大部分业务场景都能很好的决策选哪个。比如说按照时间字段,肯定选RANGE或者LIST算法最好。
这里主要是提一下有时候会在RANGE和HASH之间纠结该怎么选。一个大表的数据分写入场景和读取场景。按照拆分维度写入和读取性能是没问题的,但是如果是后端业务批量读取和修改场景,这里可能就有性能问题。
批量场景往往就是一个范围扫描,比如说基于拆分键的范围扫描。如果这个批量作业的性能要求高于数据写入端的性能,那就选用RANGE拆分算法。但是RANGE算法的缺点就是事先业务得明确定义好确定的范围。如果写入数据超出了分区字段的RANGE定义,就可能报错或者放入一个默认分区,引起数据分布规则并不统一的问题。并且可能存在写入时各个分片压力严重不均衡。
如果前端写入性能要求高于后端,就使用HASH拆分算法。此时后端范围查询肯定会扫描全部分片(分区),性能会很慢。这个只能通过改写SQL,发挥并行读取的优势来化解了。后面会详述。
问题3: 主键是否选择自增ID?
Oracle支持用sequence获取自增值,保证单调递增,但不保证连续。
TiDB支持全局自增ID。不保证连续,不是严格递增,全局范围内趋势是自增的。
OceanBase分区表支持全局自增ID,不过不能作为分区键。
DRDS自身设计了一个类似oracle sequence的机制,不保证连续,不是严格递增,全局范围内趋势是自增的。
通常来说在分布式场景下,并发很高时,自增ID机制会降低SQL性能。要综合考虑。通常来说的主键数字里会有部分字段是取自自增值,其他字段是带有某种业务含义。
业务SQL优化建议
程序是数据结构加算法。SQL就是取数据的算法,如果想SQL的性能更好,就要结合数据分布特点去写。
建议1: 尽可能的带上拆分键
无论是分库分表还是分区,都有个拆分键。SQL里带上拆分键可以发挥分布式数据库的优点。
当然,如果没有带上拆分键,就要依赖分布式数据库的全局索引功能。
建议2: 选择正确的批量读写算法
在分布式数据库下,如果批量读写性能很差,往往就是SQL没有循着分布式数据分布特点写。
批量任务的特点有两个,一是批量提交;二是有一定的并行度(不高)。并行读取之间可能会有某种并行策略将数据区分开,以避免处理重复的数据。这个并行策略在集中数据库上是没有问题,在分布式数据库下,功能也是满足的。只是性能很可能不好。
建议的并行策略是获取数据的实际分布特征,针对各个分片做并行。每个会话只批量读写同一个分片,不同会话并行读写不同的分片。
这个策略说起来简单,原理也很容易理解。在实际实现时,业务开发可能很容易排斥。因为代码逻辑要稍加复杂一些。认为这应该是数据库自身应该解决的问题。
为了纠正业务研发固执的思维,再构想一个下面这个场景来说明这样优化的必要性。
假设一个搬砖场景。要求把砖从A地搬到B地。砖头都有序号,连续的砖头都是叠加摆放在一起。要求工人每次搬10块砖,且必须是连续的10块砖(除非序号不存在,否则不能遗漏)。
在集中式数据库场景下,A地的砖头是集中式摆放,堆积如山。派5个工人来搬。每个人取到10块砖就走,工人之间可能有排队等待。工人动作快的话,这个排队时间不会很长。总体搬砖效率更高。
在分布式数据库场景下,A地的状态是分为N堆摆放,每堆也是堆积如山。堆与堆之间有些距离。砖头依然是有序号的,连续序号的砖头通常分布在不同的堆里面。还是5个工人。每个工人遵循以前的规则,取连续的10块砖。这个时候他就是先从第一堆里找到几块砖,然后又跑到第二堆里找几块砖,再到第三个堆里。直到凑齐了10块砖然后就回到B地。每个工人都是这样。
明眼人一眼就看出后面这批工人的搬砖方式是错误的。稍微正常的人都知道直接在一堆里取连续10块砖搬回去处理,不同的工人可以走向不同的堆,这样还少了一些排队等待时间。
后面这种就是优化后的搬砖算法。当然优化后的效果跟原来的需求会有些细节区别,就是在处理数据的顺序上不是严格一致了,而是每个会话内部是一致的,不同会话之间是有交错的。这点只要分析一下批量读写业务需求,看看业务是否要求一定要严格一致顺序处理。实际情况业务往往是要求在一定的时间内处理完这个批量任务即可。如果某些数据前后确实有依赖关系,这里就要在并行策略上多想办法规避数据冲突问题。
后记
实际情形和困难可能会比描述的复杂,关键在于业务开发是否能认识到分布式数据库的不同之处,是否肯转变自己的观念去适应这种变化。
参考
DRDS产品文档 https://help.aliyun.com/document_detail/29659.html
TiDB产品文档 https://pingcap.com/docs-cn/
OceanBase 产品文档 https://oceanbase.alipay.com/docs