上文讲到,查询分离的方案存在三大不足,其中一个就是:当主数据量越来越大时,写操作会越来越缓慢。这个问题该如何解决呢?可以考虑分表分库。
这里先介绍一下真实的业务场景,而后依次介绍拆分存储时如何进行技术选型、分表分库的实现思路是什么,以及分表分库存在哪些不足。
接下来进入业务场景介绍。
这次项目的对象是电商系统。该系统中大数据量的实体有两个:用户和订单。每个实体涵盖的数据量见表3-1。
表3-1 数据量
某天,领导召集IT部门人员开会,说:“根据市场推广的趋势,我们的订单很快就会上亿,每天会有100万的新订单。不要问我这个数据怎么出来的,总之,领导交代,让IT部门提前做好技术准备,以防到时候系统撑不住”。
那时候同事们内心是这样想的:“又听市场吹牛吧”。领导看了同事们的表情,也知道大家在想什么,他说:“我知道你们不相信,我也不相信。但是现在领导给大家任务了,要求系统可以支持上亿订单和每日百万新订单,服务器可以采购。”
做这个规划之前,存储订单的数据库表是一个单库单表。可以预见,在不久的将来数据库的I/O和CPU就可能支撑不住,因为订单系统原来就不是很快。
然后项目组做了简单的功能,插入一些测试数据,订单量到2000万的时候,响应时长就不可接受了。为了使系统能承受这种日百万级新订单的压力,项目组探讨过很多解决方案,最终决定使用分表分库:先将订单表拆分,再进行分布存储。
原来的订单表就是一个sale数据库里面的一张order表,之后就会创建多个order数据库order1,order2,order3,order4,……,每个数据库里面又有多张订单表t_order_1,t_order_2,t_order_3,……。
当 然 , 订 单 子 表 也 是 多 张 :t_order_item_1 , t_order_item_2 ,t_order_item_3,……。
订单数据根据一定的规律分布存储在不同order库里的不同order表中。
其实项目组并不是一开始就打算用分表分库,当初也评估了一下拆分存储的其他技术方案。接下来介绍当时是怎么选型的。
拆分存储常用的技术解决方案目前主要分为4种:MySQL的分区技术、NoSQL、NewSQL、基于MySQL的分表分库。
MySQL的分区技术
图3-1所示为MySQL官方文档中的架构图。MySQL的分区技术主要体现在图3-1中的文件存储层File System,它可以将一张表的不同行存放在不同的存储文件中,这对使用者来说比较透明。
在以往的项目中,项目组不使用它的原因主要有3点。
1)MySQL的实例只有一个,它仅仅分摊了存储,无法分摊请求负载。
2)正是因为MySQL的分区对用户透明,所以用户在实际操作时往往不太注意,如果SQL跨了分区,那么操作就会严重影响系统性能。
3)MySQL还有一些其他限制,比如不支持query cache、位操作表达式等 。
感 兴 趣 的 读 者 可 以 查 看 官 方 文 档 中 的 相 关 内 容
https://dev.mysql.com/doc/refman/5.7/en/partitioninglimitations.html。
NoSQL
比较典型的NoSQL数据库就是MongoDB。MongoDB的分片功能从并发性和数据量这两个角度已经能满足一般大数据量的需求,但是还需要注意下面3点。
1)约束考量:MongoDB不是关系型数据库而是文档型数据库,它的每一行记录都是一个结构灵活可变的JSON,比如存储非常重要的订单数据时,就不能使用MongoDB,因为订单数据必须使用强约束的关系型数据库进行存储。举个例子,订单里面有金额相关的字段,这是系统里面的核心数据,所以必须保证每个订单数据都有这些金额相关的字段,并且不管是怎样的业务逻辑修改,这些字段都要保存好,这时可以通过数据库的能力加一层校验,这样即使业务代码出了问题,导致这些字段存储不正确,也可以在数据库这一层面阻隔问题。
当然,MongoDB 3.2版以后也支持Schema Validation(模式验证),可以制订一些约束规则。不过项目组使用MongoDB的原因之一就是看重它灵活的Schema(模式)。
2)业务功能考量:订单这种跟交易相关的数据肯定要支持事务和并发控制,而这些并不是MongoDB的强项。而且除了这些功能以外,多年来,事务、锁、SQL、表达式等各种各样的操作都在MySQL身上一一实践过,MySQL可以说是久经考验,因此在功能上MySQL能满足项目所有的业务需求,MongoDB却不一定能,且大部分的NoSQL也存在类似复杂功能支持的问题。
3)稳定性考量:人们对MySQL的运维已经很熟悉了,它的稳定性没有问题,然而MongoDB的稳定性无法保证,毕竟很多人不熟悉。
基于以上的原因,当时项目组排除了MongoDB。
NewSQL
NewSQL技术还比较新,笔者曾经想在一些不重要的数据中使用NewSQL(比如TiDB),但从稳定性和功能扩展性两方面考量后,最终没有使用,具体原因与MongoDB类似。
基于MySQL的分表分库
最后说一下基于MySQL的分表分库:分表是将一份大的表数据进行拆分后存放至多个结构一样的拆分表中;分库就是将一个大的数据库拆分成类似于多个结构的小数据库。场景介绍里就举了个简单的例子,这里不再赘述。
项目组没有选用前面介绍的3种拆分存储技术,而是选择了基于MySQL的分表分库,其中有一个重要考量:分表分库对于第三方依赖较少,业务逻辑灵活可控,它本身并不需要非常复杂的底层处理,也不需要重新做数据库,只是根据不同逻辑使用不同SQL语句和数据源而已,因此,之后出问题的时候也能够较快地找出根源。
如果使用分表分库,有3个通用技术需求需要实现。
1)SQL组合:因为关联的表名是动态的,所以需要根据逻辑组装动态的SQL。比如,要根据一个订单的ID获取订单的相关数据,Select语句应该针对(From)哪一张表?
2)数据库路由:因为数据库名也是动态的,所以需要通过不同的逻辑使用不同的数据库。比如,如果要根据订单ID获取数据,怎么知道要连接哪一个数据库?
3)执行结果合并:有些需求需要通过多个分库执行后再合并归集起来。
假设需要查询的数据分布在多个数据库的多个表中(比如在order1里面的t_order_1,order2里面的t_order_9中),那么需要将针对这些表的查询结果合并成一个数据集。
而目前能解决以上问题的中间件分为两类:Proxy模式、Client模式。
1)Proxy模式:图3-2所示为ShardingSphere官方文档中的Proxy模式图,重点看中间的Sharding-Proxy层。
这种设计模式将SQL组合、数据库路由、执行结果合并等功能全部放在了一个代理服务中,而与分表分库相关的处理逻辑全部放在了其他服务中,其优点是对业务代码无侵入,业务只需要关注自身业务逻辑即可。
2)Client模式:ShardingSphere官方文档中的Client模式如图3-3所示。
这种设计模式将分表分库相关逻辑放在客户端,一般客户端的应用会引用一个jar,然后在jar中处理SQL组合、数据库路由、执行结果合并等相关功能。
• 图3-2 Proxy模式图
• 图3-3 Client模式图
这两种模式的中间件见表3-2。
表3-2 常见分表分库中间件
这两种开源中间件的设计模式该如何选择呢?先简单对比一下它们的优缺点,见表3-3。
表3-3 Client模式与Proxy模式的优缺点
因为看重“代码灵活可控”这个优势,项目组最终选择了Client模式里的Sharding-JDBC来实现分表分库,如图3-3所示。
当然,关于拆分存储选择哪种技术合适,在实际工作中需要根据具体情况来定。