文章摘要:在设计系统时,需要根据实际的业务情况来选用合适的组件构建系统。
在互联网时代,随着业务数量的暴增和应用规模的不断扩大,无论是oracle还是mysql这样子的关系型数据库,都会面临服务器CPU、磁盘IO和内存的各种瓶颈问题。基于此情况,各个业务团队迫切需要一种数据分片的方案将业务数据量存储成本分摊到成本可控的各个普通数据库服务器上,数据库切分的方案便应运而生。
由于之前发布的一篇文章《记一次使用ShardingJdbc切分数据库表的SpringBoot工程实践》评论中,有部分同学觉得还没有结合实际情况来进行场景深度分析与介绍,让读者还无法完全理解为何需要选用开源ShardingJdbc组件和选用其构建业务系统带来的一些优势。因此,写本文意在结合实际的业务场景进行分析,详细阐述选型开源组件—ShardingJdbc时候的一些思考,并最终给读者呈现业务系统集成ShardingJdbc的最终设计方案。另外,本文仅为使用开源组件ShardingJdbc第一篇幅,后续篇幅还将继续介绍开源组件ShardingJdbc的一些其他进阶用法。
一、数据的切分方式
一般,线上系统的业务量不是很大,比如说单库的数据量在百万级别以下,那么MySQL的单库即可完成任何增/删/改/查的业务操作。随着业务的发展,单个DB中保存的数据量(用户、订单、计费明细和权限规则等数据)呈现指数级增长,那么各种业务处理操作都会面临单DB的IO读写瓶颈带来的性能问题。
(1)垂直切分方案
这时候,我们会考虑使用对之前的整个单DB采用垂直数据切分的方案,根据不同的业务类型划分库表,比如订单相关若干表放在订单库,用户相关的表放在用户库,账务明细相关的表放在账务库等。这样就将数据分担到了不同的库上,达到专库专用的效果。
使用垂直切分方案的主要优点如下:
a.拆分后业务清晰,符合微服务的总体设计理念;
b.子系统之间的整合与扩展相对容易;
c.按照不同的业务类型,将不同的库表放在不同的数据库服务器上,便于管理;
d.根据业务数据的“冷”、“热”状态,采用不同的缓存和数据库模式设计方案;
垂直切分的缺点如下:
a.跨库的Join查询,需要使用不同子系统的API接口读取后在内存中完成关联查询,提高系统复杂度;
b.如果某一种类型的业务呈现爆发式地增长,该业务对应的库表还是会存在单DB的IO读写的瓶颈问题,从这一点来说垂直切分并未从根本解决单库单表数据量过大的问题;
c.存在跨库事务的一致性问题,解决起来比较麻烦,当然也可以采用分布式事务来解决;
(2)水平切分方案
由于本文主题讲的是利用开源组件ShardingJdbc进行数据水平切分的实践,因此对于垂直切分方案的一些细节问题就不做过多的详细介绍了。与垂直切分对比,这里讲的水平切分不是将库表根据业务类型进行分类存储,而是将其按照数据表中某个字段或某几个字段的某种规则切分存储至多个DB中,在每个库每个表中所包含的只是其中的一部分数据,所有库表加起来的才是全量的业务数据。
这种对数据的切分方式,基本可以保证经过水平切分后的单库单表存储的容量不会太大,从而保证了对单表的增/删/改/查的SQL执行效率和处理能力。其中,数据的分片规则也是需要重点考虑的,因为它会使得数据分片在多个库表中是否均匀分布存储。然而,数据水平切分方案为业务系统带来福音的同时也给系统构建带来了一些值得思考的问题。
使用水平切分方案的主要优点如下:
a.单库单表的数据容量可以维持在一个量级,有助于提高业务SQL的执行效率和系统性能;
b.不管是利用ShardingJdbc组件还是MyCat框架,业务系统的应用层涉及改造得都较少,只需要根据实际的业务情况来设计数据分片的路由规则即可;
c.可以提高业务系统的稳定性和负载能力;
使用水平切分方案的主要缺点如下:
a.数据水平切分后,分布在多库多表中,跨库Join查询比较复杂;
b.分片数据的一致性较为难保证;
c.对于历史数据的迁移和数据库的扩容需要较大的维护工作量;
二、选型ShardingJdbc组件的分析与思考
对于,“流水”/“明细”一类的业务数据,通常的业务需求是准实时或者说相对滞后,这些数据是按小时、按日和按月汇总加工处理后生成最终业务需求的数据(比如用户账单、报表和话单)。此外,由于这一类型的业务数据量普遍较大,比如清算系统的清分明细、云管平台的资源计量明细、订单系统的订单流水和云计算主机资源上报的性能数据等,如果只是使用单库单表存储的普通方案,那么在单表数据量达到千万级别以上的时候,单库的IO读写能力将持续降低,会成为业务系统整体吞吐量和性能的瓶颈。
对于上述的问题,有一些对DB较为熟悉的同学第一时间想到的解决方案,可能会是MySQL的分区表。MySQL的分区表比较适合用于解决业务数据具有较强时间序列特点,且数据量偏大的场景。但是,如果SQL的查询条件并非基于时间维度的,那么执行起来的效率并不会有所改观,同时对于处理单表千万级别以上数据量时,MySQL的分区表方案还是会显得有些“力不从心”。
对于以上这种“流水”/“明细”类的业务数据,作者还是推荐使用水平切分的方案从根本上来解决业务上遇到的单表数据过大的问题。由于该类型的业务数据基本不会涉及跨库的Join连表SQL查询、只需保证分库的本地事务,且并不会遇到上面水平切分方案中的几个需要考虑的问题,对于这样子的业务场景可以考虑使用水平切分的方案。那么,我们应该选择哪一款开源的分库分表组件,又或者是根据业务情况来自己定制化研发呢?
(1)选型分库分表中间件的分析
如果业务系统设计之初打算采用水平切分方案的话,那么有必要好好思考下如何来选型分库分表的中间件。在当前的开源组件中比较流行的两款分别为ShardingJdbc和MyCat。
ShardingJdbc这款分库分表组件代表了客户端类的分库分表技术框架,其定位为轻量级数据库驱动,可以由Spring Boot这样的业务工程直接快速集成,以jar包形式提供服务,无需额外部署和其他依赖。对于业务系统的开发人员与数据库运维人员无需改变原有的开发与运维方式。在2.X版本中,采用"半理解"理念的SQL解析引擎,以达到性能与兼容性的最大平衡。因此,ShardingJdbc即为增强版的JDBC驱动,其优势在于无需对原有的业务工程进行任何改造的基础上,即可使其拥有分库分表的能力,成本较低,易于上手。但是,也有缺点,中间件与业务应用工程绑定在一起,对应用本身有侵入。并且目前只支持Java语言,问题难以追踪。
而MyCat是一个开源的分布式数据库系统(属于代理类框架),相当于一个实现了MySQL协议的数据库代理服务器。用户可以使用MySQL客户端工具和命令行访问,而其后端使用MySQL原生协议与多个MySQL数据库服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信。其分库分表的方案优点在于,能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强且对于应用服务透明且没有增加任何额外负载。其缺点是上线部署需要额外的中间件,增加运维成本;应用服务需经过代理来连接数据库,网络上多了一层链接的开销,性能有损失且有额外风险。
(2)使用ShardingJdbc解决的基本业务场景
选择ShardingJdbc组件后,就需要使用该组件来解决实际的问题。这一节主要根据之前提到的“流水”/“明细”一类的业务数据,同时结合ShardingJdbc组件的特点来进行一定的分析,使得读者对正确使用ShardingJdbc组件进行业务系统水平切分有一定的了解。
前面已经提到了“流水”/“明细”类的业务数据,一般是准实时或者说相对滞后,需要按小时、按日和按月汇总处理后生成最终的业务数据(如账单、报表和话单等)。我们对“流水”/“明细型”业务数据处理过程中,一般都会涉及数据落库(Insert SQL)、数据分组汇总和分组查询(Select+sum(xxx)+Group By SQL)以及删除数据表(Delete SQL)等业务处理操作。这里有必要对这几种类型的SQL进行一定的说明:
a. 数据落库(Insert SQL):流水/明细类的数据一般都需要先DB持久化处理;以云计算平台中10W台虚拟机同时产生性能明细数据上报,如果是单库单表,则这个数据库会进行每小时会有10W次的写请求;如果使用100个分表(分10个库,每库10个表)则每个表平均承受1000次写请求,每个库平均分担1W次的请求压力,这样子就可以将原来单库单表的写请求压力成倍的减少;一般业务研发人员使用ShardingJdbc组件,设置合理的数据分片路由规则,即可使得流水/明细类的数据基本均匀落在我们预先设置的分库分表中。
b. 数据分组汇总查询(Select+sum(xxx)+Group By SQL):由于(a)中持久化至分库分表的业务数据为若干段时间的业务数据,根据业务需求还需要按日,按周或者按月进行累加汇总,因此有必要对各个分表中的数据执行Select+sum(xxx)+Group By的分组汇总SQL;ShardingJdbc组件可以完成SQL的解析、改写、路由和结果归并,对于“Select+sum(xxx)+Group By SQL”的语句,可以遍历设置的多个分库分表,对每个分库分表执行SQL后进行一个结果归并再返回给业务调用方。
c. 删除数据表(Delete SQL):一般业务系统对会通过定时任务来生成明细数据加工处理后的业务数据(比如用户账单、清偿明细、云资源按日按月的话单)。一旦生成这些有效业务数据后,原来落库的明细也就没有什么业务价值,可以通过任务定期删除或者迁移至历史库的方式来使得分库分表的数据水位量级维持在一定量,因此就需要涉及对原来存储在分库分表的明细数据进行删除;ShardingJdbc组件可以根据“Delete SQL”语句中的筛选条件进行规则路由来定位某个分库和分表,否则会删除所有分库中的分表数据。
三、业务系统集成ShardingJdbc的架构设计
根据上面对“流水”/“明细”型业务数据的场景分析,基本可以画出SpringBoot业务工程集成分库分表组件ShardingJdbc的架构设计图:
从上面业务系统架构设计图中可以看出,明细型业务数据(比如之前提的,清算系统的清分明细、云管平台的资源计量明细、订单系统的订单流水和云计算主机资源上报的性能数据明细)根据设置的数据分片路由规则先落库至sharding_db00~sharding_db04五个分库的对应分表中。然后,利用ShardingJdbc组件对分组汇总查询SQL的解析、改写、路由和归并结果的能力,分别对五个库中对应业务分表中的数据汇总累加求出每天/每月同一个用户下的资源计费累加值。最后,将这些“加工”后的业务数据批量插入至共享库share_db中,其他定时任务再从共享库中读取并生成最终形式的业务数据(比如,按月的账单、话单或者性能计量值)。其中,对于异常情况(明细流水异常、汇总异常和系统异常等),需要将其保存至共享库中的异常信息表中。另外,在明细落库之前还需要考虑幂等前置校验的问题。对于ShardingJdbc组件的分库分表路由规则可以参照下图:
从上面的分库分表路由规则图上可以看出,预先设置了通过客户id来路由定位至分库,通过用户id来路由定位至分表。这里,由原来的单库单表分成五个库的多分表(每个库中均有test_msg_queue_bill_record0~test_msg_queue_bill_record4五个分表,这里只是示例,在真实的业务场景下需要根据业务数据的总体容量来设定分库分表的规模,究竟是分5个库每个库5表,还是分10个库每个库10表来满足业务要求)。在一般情况下,如果执行的SQL为“select * from test_msg_queue_bill_record”就能借助ShardingJdbc组件来遍历查完5个分库中的test_msg_queue_bill_record0~4五个分表,无需我们自己来做分库分表的遍历查询了。
四、总结
本文先介绍了目前两种数据切分的主要方案(垂直切分和水平切分)及其优缺点。根据“流水”/“明细”类别的数据切分业务场景,阐述了业务系统设计之初选型分库分表组件的分析,并介绍了如何利用ShardingJdbc来解决“数据落库(Insert SQL)”、“数据分组汇总查询(Select+sum(xxx)+Group By SQL)”和“删除数据表(Delete SQL)”的几种基本业务场景。最后,给出业务系统集成ShardingJdbc组件后的架构设计方案。本文仅仅使用了Sharding-Jdbc组件的核心功能来解决了一部分基本的业务场景问题。ShardingJdbc组件还有其他很多高级的功能(比如读写分离、最大努力送达型的柔性事务、分片路由规则动态配置和数据库治理和ShardingProxy等,ps:听@亮哥说,ShardingJdbc 3.0起可以完美支持Batch Insert SQL,很是期待),限于篇幅将在后续的文章中结合对应的场景进行介绍。限于笔者的才疏学浅,对本文内容可能还有理解不到位的地方,如有阐述不合理之处还望留言一起探讨。