一次分表踩坑实践的探讨

前言

以前很多人问我“可否分享一些分库分表相关的实践”,其实不是我不分享,而是真的经验很少🤣;和大部分人同样都是停留在理论阶段。java

不过此次多少有些能够说道了。算法

先谈谈背景,咱们生产数据库随着业务发展量也逐渐起来;好几张单表已经突破亿级数据,而且保持天天 200+W 的数据量增长。数据库

而咱们有些业务须要进行关联查询、或者是报表统计;在这样的背景下大表的问题更加突出(好比一个查询功能须要跑好几分钟)。多线程

可能不少人会说:为啥单表都过亿了才想方案解决?其实不是不想,而是因为历史缘由加上错误预估了数据增加才致使这个局面。总之缘由比较复杂,也不是本次讨论的重点。运维

临时方案

因为需求紧、人手缺的状况下,整个处理的过程分为几个阶段。测试

第一阶段应该是去年末,当时运维反应 MySQL 所在的主机内存占用很高,总体负载也居高不下,致使整个 MySQL 的吞吐量明显下降(写入、查询数据都明显减慢)。大数据

为此咱们找出了数据量最大的几张表,发现大部分数据量在7/8000W 左右,少数的已经突破一亿。优化

经过业务层面进行分析发现,这些数据多数都是用户产生的一些日志型数据,并且这些数据在业务上并非强相关的,甚至两三个月前的数据其实已经不须要实时查询了。spa

由于接近年末,尽量的不想去动应用,考虑是否能够在运维层面缓解压力;主要的目的就是把单表的数据量下降。线程

本来是想把两个月以前的数据直接迁移出来放到备份表中,但在准备实施的过程当中发现一个大坑。

表中没有一个能够排序的索引,致使咱们没法快速的筛选出一部分数据!这真是一个深坑,为后面的一些优化埋了个地雷;即使是加索引也须要花几个小时(具体多久没敢在生产测试)。

若是咱们强行按照时间进行筛选,可能查询出 4000W 的数据就得花上好几个小时;这显然是行不通的。

因而咱们便想到了一个大胆的想法:这部分数据是否能够直接不要了?

这多是最有效及最快的方式了,和产品沟通后得知这部分数据真的只是日志型的数据,即使是报表出不来从此补上也是能够的。

因而咱们就简单粗暴的作了如下事情:

  • 修改原有表的表名,好比加上(_190416bak)。
  • 再新建一张和原有表名称相同的表。

这样新的数据就写到了新表,同时业务上也是使用的这个数据量较小的新表。

虽然说过程不太优雅,但至少是解决了问题同时也给咱们作技术改造预留了时间。

分表方案

以前的方案虽然说能够缓解压力,但不能根本解决问题。

有些业务必须得查询以前的数据,致使以前那招行不通了,因此正好咱们就借助这个机会把表分了。

我相信大部分人虽然说没有作过实际作过度表,但也见过猪跑;网上一搜各类方案层出不穷。

我认为最重要的一点是要结合实际业务找出须要 sharding 的字段,同时还有上线阶段的数据迁移也很是重要。

时间

可能你们都会说用 hash 的方式分配得最均匀,但我认为这仍是须要使用历史数据的场景才用哈希分表。

而对于不须要历史数据的场景,好比业务上只查询近三个月的数据。

这类需求完成能够采起时间分表,按照月份进行划分,这样改动简单,同时对历史数据也比较好迁移。

因而咱们首先将这类需求的表筛选出来,按照月份进行拆分,只是在查询的时候拼接好表名便可;也比较好理解。

哈希

刚才也提到了:须要根据业务需求进行分表策略。

而一旦全部的数据都有可能查询时,按照时间分表也就行不通了。(也能作,只是若是不是按照时间进行查询时须要遍历全部的表)

所以咱们计划采用 hash 的方式分表,这算是业界比较主流的方式就再也不赘述。

采用哈希时须要将 sharding 字段选好,因为咱们的业务比较单纯;是一个物联网应用,全部的数据都包含有物联网设备的惟一标识(IMEI),而且这个字段自然的就保持了惟一性;大多数的业务也都是根据这个字段来的,因此它很是适合来作这个 sharding 字段。

在作分表以前也调研过 MyCATsharding-jdbc(现已升级为 shardingsphere),最终考虑到对开发的友好性及不增长运维复杂度仍是决定在 jdbc 层 sharding 的方式。

但因为历史缘由咱们并不太好集成 sharding-jdbc,但基于 sharding 的特色本身实现了一个分表策略。

这个简单也好理解:

int index = hash(sharding字段) % 分表数量 ;

select xx from 'busy_'+index where sharding字段 = xxx;
复制代码

其实就是算出了表名,而后路由过去查询便可。

只是咱们实现的很是简单:修改了全部的底层查询方法,每一个方法都里都作了这样的一个判断。

并无像 sharding-jdbc 同样,代理了数据库的查询方法;其中还要作 SQL解析-->SQL路由-->执行SQL-->合并结果 这一系列的流程。

若是本身再作一遍无异于从新造了一个轮子,而且并不专业,只是在现有的技术条件下选择了一个快速实现达成效果的方法。

不过这个过程当中咱们节省了将 sharding 字段哈希的过程,由于每个 IMEI 号其实都是一个惟一的整型,直接用它作 mod 运算便可。

还有一个是须要一个统一的组件生成规则,分表后不能再依赖于单表的字段自增了;方法仍是挺多的:

  • 好比时间戳+随机数可知足大部分业务。
  • UUID,生成简单,但无法作排序。
  • 雪花算法统一辈子成主键ID。

你们能够根据本身的实际状况作选择。

业务调整

由于咱们并无使用第三方的 sharding-jdbc 组件,全部没有办法作到对代码的低侵入性;每一个涉及到分表的业务代码都须要作底层方法的改造(也就是路由到正确的表)。

考虑到后续业务的发展,咱们决定将拆分的表分为 64 张;加上后续引入大数据平台足以应对几年的数据增加。

这里还有个小细节须要注意:分表的数量须要为 2∧N 次方,由于在取模的这种分表方式下,即使是从此再须要分表影响的数据也会尽可能的小。

再修改时只能将表名称进行全局搜索,而后加以修改,同时根据修改的方法倒推到表现的业务并记录下来,方便后续回归测试。


固然没法避免查询时利用非 sharding 字段致使的全表扫描,这是全部分片后都会遇到的问题。

所以咱们在修改分表方法的底层查询时同时也会查看是否有走分片字段,若是不是,那是否能够调整业务。

好比对于一个上亿的数据是否还有必要存在按照分页查询、日期查询?这样的业务是否真的具备意义?

咱们尽量的引导产品按照这样的方式来设计产品或者作出调整。

但对于报表这类的需求确实也没办法,好比统计表中某种类型的数据;这种咱们也能够利用多线程的方式去并行查询而后汇总统计来提升查询效率。

有时也有一些另类场景:

好比一个千万表中有某一特殊类型的数据只占了很小一部分,好比说几千上万条。

这时页面上须要对它进行分页查询是比较正常的(好比某种投诉消息,客户须要一条一条的单独处理),但若是咱们按照 IMEI 号或者是主键进行分片后再分页查询那就比较蛋疼了。

因此这类型的数据建议单独新建一张表来维护,不要和其余数据混合在一块儿,这样无论是作分页仍是 like 都比较简单和独立。

验证

代码改完,开发也单测完成后怎么来验证分表的业务是否正常也比较麻烦。

一个是测试麻烦,再一个是万一哪里改漏了仍是查询的原表,但这样在测试环境并不会有异常,一旦上线产生了生产数据到新的 64 张表后想要再修复就比较麻烦了。

因此咱们取了个巧,直接将原表的表名修改,好比加一个后缀;这样在测试过程当中观察先后台有无报错就比较容易提早发现这个问题。

上线流程

测试验收经过后只是分表这个需求的80%,剩下如何上线也是比较头疼。

一旦应用上线后全部的查询、写入、删除都会先走路由而后到达新表;而老数据在原表里是不会发生改变的。

数据迁移

因此咱们上线前的第一步天然是须要将原有的数据进行迁移,迁移的目的是要分片到新的 64 张表中,这样才会对原有的业务无影响。

所以咱们须要额外准备一个程序,它须要将老表里的数据按照分片规则复制到新表中;

在咱们这个场景下,生产数据有些已经上亿了,这个迁移过程咱们在测试环境模拟发现耗时是很是久的。并且咱们老表中对于 create_time 这样用于筛选数据的字段没有索引(之前的技术债),因此查询起来就更加慢了。

最后没办法,咱们只能和产品协商告知用户对于以前产生的数据短时间可能会查询不到,这个时间最坏可能会持续几天(咱们只能在凌晨迁移,白天会影响到数据库负载)。

总结

这即是咱们此次的分表实践,虽然说很多过程都不优雅,但受限于条件也只能折中处理。

但咱们后续的计划是,修改咱们底层的数据链接(目前是本身封装的一个 jar 包,致使集成 sharding-jdbc 比较麻烦)最终逐渐迁移到 sharding-jdbc .

最后得出了几个结论:

  • 一个好的产品规划很是有必要,能够在合理的时间对数据处理(无论是分表仍是切入归档)。
  • 每张表都须要一个能够用于排序查询的字段(自增ID、建立时间),整个过程因为没有这个字段致使耽搁了很长时间。
  • 分表字段须要谨慎,要全盘的考虑业务状况,尽可能避免出现查询扫表的状况。

最后欢迎留言讨论。

你的点赞与分享是对我最大的支持

相关文章
相关标签/搜索