https://mp.weixin.qq.com/s/uq2cw2Lgf-s4nPHJ4WH4aw
分库分表是存储层设计中一个普遍而重大的问题,什么时候分?怎么分?分完之后引发的新问题,比如不能Join、分布式事务? 本篇将从最基本的策略出发,逐步深入讲解这里面涉及的一序列策略。
分库的首先目的就是做“业务分拆“,关于业务分拆,在前面的序列文章“分布式系统--基本思想汇总“中已经有阐述。通过业务分拆,把一个大的复杂系统拆成多个业务子系统,之间通过RPC或消息中间件通信。这样做既便于团队成员的职责分工,也便于对未来某个系统做扩展。
另外一个考虑角度是“数据隔离"。如果你把核心业务的数据和非核心业务数据放在一个库里面,不分轻重,同等对待。这样如果因为非核心业务把DB搞挂了,核心业务也受到牵连。分开之后,区别对待,投入的开发和运维人力也不一样。
对于业务来讲,读多写少/读少写多,所对应的策略是不一样的。
如果是读多写少,那你可以通过加从库、加缓存来解决,不一定要分库分表。
如果是读少写多,或者说写入的QPS已经达到了DB的瓶颈,这个时候就得考虑分库分表了。
对于Mysql来讲,一般写入的QPS有3k/s左右,读写的QPS可以达到2万/s。可见写入和查询的吞吐量区别是很大的。
考虑了上面2个策略之后,最后万不得已,再做分库分表。因为分库分表的代价是很大的,意味着代码要大规模重构,以前能用的join,事务可能都不能用了,需要用新的办法解决。
垂直切分一般主要用于表的字段太多(比如上百个字段),这种情形通常就对应着上层业务没有很好的分拆,解耦。如果做好了上面的“业务分拆”,需要这种“垂直切分”的场景可能就会变得很少。下面主要讲“水平切分”。
当表的记录数太多,几千万,上亿,并且有很高的写入QPS,这个时候就得考虑水平切分了。
一般思路是:保证单表的记录条数不要超过千万,然后根据总数据量估算出要拆多少个表,再考虑把这些表分散到多少个库里面。
水平切分的最关键问题就是:拆分维度的选择。
比如电商的订单表,至少有3个查询维度:订单id,用户id,商户id。那你拆分的时候,根据哪个维度进行拆分呢?
假设你按用户id拆分,同一个用户的所有订单记录都落到同1个库的同1张表里面。那查询的时候,按user id查,就很容易;
但如果按订单id,或者商户id呢?拆分之后,同1个商户id的订单,可能被分到了不同的库、不同的表里面,根本没办法查。
对于这种分库分表之后,其他维度的查询,一般有以下几个办法:
(1)建立一个映射表,建立辅助维度和主维度之间的映射关系(也就是商户id和用户id之间的映射关系)。
查询的时候,根据商户id,查出用户id。再根据用户id,来查
(2)业务双写
同1份数据,2套分库分表。1套按user id切分,1套按商户id 切分。这个其实也就是我在”分布式系统--基本思想汇总“中所用的“重写轻读”
(3)异步双写
还是2套表,只是业务单写。然后通过binlog同步(比如阿里的canal,点评的puma),同步到另外一套表上。
(4)2个维度统一到1个维度
这个在特殊的场景下可以使用。比如order id和user id就可以统一成1个维度,把user id作为order id中的某几位,这样order id中就包含了user id信息。
然后按照user id 分库,当按照order id查询的时候,截取出user id,在按user id查询。但反过来就不行,按order id分库了,再按user id就不能查了。
分库分表之后,有些Join就不能用了,针对这种情况,一般有下面几种解决办法:
(1)把Join拆成多个单表查询,上层业务代码进行结果拼装
(2)提前做宽边,重写轻读
很多时候,你有这样的情况:需要把Join的结果分页,这个需要利用mysql本身的分页功能。对于这种不得不用Join的情况,可以另外做一个Join表,提前把结果Join好。这是“重写轻读”,其实也是“空间换时间”的思路。
(3)利用搜索引擎
对于(2)当中提到的场景,还可以利用Lucence,ES这种搜索引擎,把DB中数据导入到搜索引擎中来查询,从而解决Join问题。
做了分库之后,纯DB的事务就搞不了了。一般解决办法都是:通过精心选择维度,优化业务,避免跨库的事务,保证所有事务都落到单库上面。
如果实在无法避免,那就需要分布式事务的解决方案了。关于分布式事务,又是一个系统化的课题,没有一个标准答案。
后续文章会详细阐述这个问题,敬请期待!