一次分表踩坑实践的探讨

原创
2019/04/16 09:03
阅读数 8.2K

前言

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

不过这次多少有些可以说道了。

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

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

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

临时方案

由于需求紧、人手缺的情况下,整个处理的过程分为几个阶段。

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

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

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

因为接近年底,尽可能的不想去动应用,考虑是否可以在运维层面缓解压力;主要的目的就是把单表的数据量降低。

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

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

如果我们强行按照时间进行筛选,可能查询出 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、创建时间),整个过程由于没有这个字段导致耽搁了很长时间。
  • 分表字段需要谨慎,要全盘的考虑业务情况,尽量避免出现查询扫表的情况。

最后欢迎留言讨论。

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

展开阅读全文
打赏
13
142 收藏
分享
加载中
确实设计要从一开始就做好,做了分库分表但是单表已经到千万了,最坑的是由于之前的设计原因做不了横向扩展。
ps:报表类型的需求没有用大数据去处理?
2019/05/06 11:15
回复
举报
其实分表方案很简单,难点在于怎么迁移数据,我刚好也做过历史数据分表的事情,我的解决方案是

1:不停服先迁移历史数据(很多数据其实不会变更或者变更很少的,确实有数据变更的,就改代码,新增的数据同时写到另外一个表)
2:停服迁移最近数据
3:数据迁移过程:将数据导出成csv(注意分隔符冲突),然后用程序分配成n个csv,在直接把csv导入到新的表,我们只处理分配,将数据IO直接交给mysql,我当时迁移的数据将近1T,10+个表,停服数据迁移过程只用了不到1个小时(最近数据)
2019/05/05 01:09
回复
举报

引用来自“简单生活”的评论

引用来自“crossoverJie”的评论

引用来自“简单生活”的评论

引用来自“crossoverJie”的评论

引用来自“简单生活”的评论

分表也叫大胆的想法?直接上中间件啊,这么小的数据量,怎么都会写博客
键盘侠真的牛逼

@crossoverJie 键盘侠从什么时候开始成为了意见相左者的代名词?只允许有一种声音存在,不同的声音就是键盘侠,可笑。
不同的声音你倒是提出论点啊,这么装逼的评论怎么都会写出来?

说你键盘侠都是客气了。

@crossoverJie 在你看来一个亿的数据似乎很大了,但在我看来一个亿的数据不是很常见嘛,网站上线,压测都不止这点数据,这就是本质的区别。你这篇文章没有提出任何创新方案,都是基础,只是把互联网上面很久之前,别人的解决方案稍加修改,说你是摘抄都不为过。这种数据量,分库,分表,读写分离,时序数据库都能轻松解决。

引用来自“月之舞步”的评论

这个博文说的是一个实践记录,关键点根本不是用了什么方案,而是用了方案之后,实践的过程,你自己也说了,网上分库分表方案一大堆,有个卵用。。你让一个新手去按照网上的方案去实践一次试试看??你确实有点键盘侠的味道。
这种人直接加灰处理吧,就是个骗浏览量的狗
2019/04/30 18:00
回复
举报
我觉得吧。要走就要一步到位,当然,你的选择在目前情况下,可能是最适合最快速的,但这种抉择,本身难道不是一种技术债务吗?之后,你还要花更多的时间和步骤,慢慢的把它迁移到标准实现上去,而这个过程中,随着业务增多,还有其他代码要开发之类的,可能会积重难返。最终结果就是,算了,就用我们自己的底层方案和组件吧。。等到了一定程度就会发现,啊啊,当初做这个决定的,真实坑啊,我们的底层组件怎么这么垃圾啊之类的抱怨了。
综上,我觉得,还不如时间拉长点,一次性上标准开源解决方案。
2019/04/30 15:04
回复
举报
你们公司研发平均工资不过2w的话, 不要自己研发任何组件, 随着业务的增长自研组件的坑越来越多.
2019/04/30 10:19
回复
举报
我就经历了楼主说的数据迁移,基本上是一样处理方式,看来大家的思路都差不多,并不是上来就能用高级工具的
2019/04/30 07:15
回复
举报

引用来自“伯一声”的评论

据说TIDB牛逼到不用分库分表
我也关注了一阵子,后来打听到一些用过的团队,评价并不高,暂时没有再继续尝试了,保持一定关注等更多使用者的声音再说了。
2019/04/29 23:47
回复
举报

引用来自“雷侠”的评论

引用来自“简单生活”的评论

引用来自“crossoverJie”的评论

引用来自“简单生活”的评论

分表也叫大胆的想法?直接上中间件啊,这么小的数据量,怎么都会写博客
键盘侠真的牛逼

@crossoverJie 键盘侠从什么时候开始成为了意见相左者的代名词?只允许有一种声音存在,不同的声音就是键盘侠,可笑。

引用来自“crossoverJie”的评论

不同的声音你倒是提出论点啊,这么装逼的评论怎么都会写出来?

说你键盘侠都是客气了。

引用来自“crossoverJie”的评论

就你这表达装逼能力,生活中没少挨揍吧。

引用来自“crossoverJie”的评论

气急败坏?是最近被人揍了照镜子看到的?

哈哈 最后回你一条 你说的那种改改别人的原创不是我 可以去网上搜搜(我所有信息网上都有,不像是你这类键盘侠只敢喷粪连个脚印都不敢留) 我所有文字全是一字一字打出来的 所有经验 全是平时积累出来的 如果是你自己写的东西 被一个傻逼一顿乱喷 估计已经顺着网线过去一顿XX。



只敢躲在屏幕后面乱喷就别再回我了,除非留下你的信息 我们可以当面聊聊技术。
我觉得OK,可以见面聊聊技术,探讨一下技术的伟大也是不错的😃。

不知道“简单生活”,你是否接受呢?

@雷侠 可以,可以,你也留一下信息,大家探讨一下技术的伟大
2019/04/29 19:34
回复
举报

引用来自“crossoverJie”的评论

引用来自“简单生活”的评论

引用来自“crossoverJie”的评论

引用来自“简单生活”的评论

分表也叫大胆的想法?直接上中间件啊,这么小的数据量,怎么都会写博客
键盘侠真的牛逼

@crossoverJie 键盘侠从什么时候开始成为了意见相左者的代名词?只允许有一种声音存在,不同的声音就是键盘侠,可笑。

引用来自“crossoverJie”的评论

不同的声音你倒是提出论点啊,这么装逼的评论怎么都会写出来?

说你键盘侠都是客气了。

引用来自“crossoverJie”的评论

就你这表达装逼能力,生活中没少挨揍吧。
气急败坏?是最近被人揍了照镜子看到的?

哈哈 最后回你一条 你说的那种改改别人的原创不是我 可以去网上搜搜(我所有信息网上都有,不像是你这类键盘侠只敢喷粪连个脚印都不敢留) 我所有文字全是一字一字打出来的 所有经验 全是平时积累出来的 如果是你自己写的东西 被一个傻逼一顿乱喷 估计已经顺着网线过去一顿XX。



只敢躲在屏幕后面乱喷就别再回我了,除非留下你的信息 我们可以当面聊聊技术。

@crossoverJie 可以啊,留下你的信息,我去找你😄
2019/04/29 19:33
回复
举报
一次分享而已, 有必要这么较真的说别人是抄袭嘛 ,纯粹技术分享 讨论嘛 搁谁都会有情绪呀。
2019/04/29 18:12
回复
举报
更多评论
打赏
32 评论
142 收藏
13
分享
返回顶部
顶部