两次不同的分表实践总结

有幸在两家不同的公司不同程度的参与了涉及分表的项目,其背景、分表方式、结果均有不同,根据自己的实际感受进行了简单的总结归纳,形成此文,如有不正之处,欢迎各位看客指正

先说几点自己的总结

1.为什么要分表? 及分表时机

我们都知道,当表数据量达到一定程度后,就会有慢查出现。根据自己对项目的感受和观察来看,当一个表的字段不算特别多,且索引合理,大部分sql合理(少回表、尽可能不联表或少联表等等)的情况下,一个表的数据量可能到2000W到3000W左右就可能需要考虑分表了,到了5000W上下,分表可能就已经到了迫不及待的地步了。不过具体这个区间,也是根据自己的感受估计的,实际上参与的两次分表均达到了5000W以上的数据量,甚至第一次进行分表时数据量已经达到三四亿(除了用id查,其他的条件查询似乎都查不动的地步)


2.分表的方式?

一般情况下,有根据业务参数(例如用户id或其他业务id)哈希或者取模的方式来分表,或者根据时间来分表例如年、月、周,恰好自己这两次分表一次是按用户id取模分表的,另一次是按月分表的。具体如何选择,感觉并无绝对的对错,按自己总结来看主要需要考虑以下几点:

  • 分表后数据能较为均匀的分散到分表中
  • 便于兼容查询
  • 从业务的角度来思考清晰合理,便于以后业务扩展
  • 尽量减少需要跨表merge
  • 如果能通过分表来把数据的冷热区分开就更好了
  • 数据维护成本

其实两次分表的操作在分表方式的选择上,都是经过一定考虑的,但后来也确实发现遇到了一些问题,后文详细赘述


3.关于分表后上线,及数据迁移的问题

感觉自己两次分表经历在这块都算是做的比较好的,几乎都做到了不停服或尽量少停服上线(第一次停了约1小时,第二次用户完全无感,主要是追数据的方式不同导致的,后文赘述具体情况),就是在数据字典中增加了业务开关(配置中心直接修改,不需要发版),主要是读开关和写开关,实话讲,第一次这个设计时是有人指导的,第二次感觉这个方式不错,就直接沿用保留了相关开关设计,整体的上线流程如下:
1.先写好查分表的程序,是一个单独的逻辑策略类,原来查大表的是另一个策略类,策略工厂中依据读开关和写开关来控制走哪套策略。
2.上线时默认双写,读老的大表,相当于查询完全没改,只是数据写入的时候多写了一份
3.等待分表的数据和老表完全一致后,切换读开关,改成读新的分表,这样一旦有问题还能随时切回老表,方便快速回滚
4.稳定一段时间后(看具体情况,可以一周,也可以一个月),若老的大表确实没有维护需求了,可将写开关改为只写新的分表,并在下次版本发布时,将读开关和写开关的逻辑下掉。


4.关于id发号器

最好是有单独的id发号器,或者简单点就用一个自增长的只有一个主键的数据库表来做id发号器(实际两次分表都是这么玩的),如果是雪花算法或者其他的方式,感觉要么id不是单调的,要么过于复杂了(个人感觉)


5.关于无法通过分表字段作为条件的查询

个人想法还是尽量在选择分表方式的时候能够覆盖C端的绝大多数查询,运营后台或者其他偏B端的服务,可以通过构建ES宽表等方式来查询,实在无法覆盖的,可以考虑根据不同的业务参数分多套表。
实在不行的,再去考虑异构索引表,不是不能解决问题,而是一旦异构索引表查出需要跨多张表去merge数据时,性能极差。这块感觉也回到第二点分表方案的设计时需要考虑尽量减少跨表merge的问题了

具体回顾一下自己的两次分表实践

一.根据业务id(uid)取模分表

1.1经历概述

事项 具体方式/情况 解释备注
业务背景 在线教育班课业务,用户课次表 用户报班后,具体用户每一节课的数据,冗余数据便于快速查询,避免查一次需要经过多张表,且用户报班后如果经过调/退/换课有该表更好操作一些
分表前数据量 约4亿
分表策略 根据用户id取模分表 C端用户查询,基本都是学生基于uid查询上课的课次
分表数 256张 shardingValue.getValue() % 256
分表框架 sharding-jdbc
潜在问题 部分查询不是通过uid来查询的 1.老师查的场景;

2.根据产品或班级查询该班的学员时;
解决方式 易购索引表 将不带uid的查询条件,冗余一份和uid关联异构索引表,易购索引表当时还是采用的mysql表,且由于数据量也很大,故也进行了分表,分32张
数据迁移 由于原表需要分256张,易购索引表也需要分32张,且数据量较大,经和DBA讨论后,是由DBA写的脚本程序来迁移同步 1.临近上线时设定一个备份时间,将该备份时间之前的数据,通过脚本初始化到新的两套分表中

2.备份时间后的数据,通过监听binlog时间追增量数据
上线方式 1.新的分表时独立的项目去读写,原项目操作老表的地方,增加读写开关来切换操作的是老表还是新表

2.对新表的操作方式统一按发消息,或者调用新项目接口的方式来操作

3. 刚上线时业务开关切换写老,读老

4.执行DBA的初始化脚本,初始化某备份时间前的数据到新的分表中

5.停服,执行DBA追增量数据的脚本,回溯从备份时间节点到停服的时间点中的binlog事件到新的分表中

6.一致后,开关切换到双写读老表,重启服务

7.程序写验证无问题后,开关切换双写读新

8.回归验证无问题,且确认新服务稳定后,开关切换写新读新

9.下次上线时下线开关逻辑代码
由于是通过回溯binlog事件的方式追数据,故在追增量数据时只能停服,否则一直会产生新的binlog事件
分表结果 1.分表前基本除id外已经完全查不动了;分表后对于通过单个uid的查询能快速查询

2. 非uid查询条件的,在经过易购索引表拿到uid后,也能正常查询,但一旦拿到的uid分散在N个分表中,存在较大性能问题

3.批量uid查询如果分散在多个分表中也存在较大性能问题

4.由于八成以上的场景确实是根据单个uid的查询,故一定程度上也解决或者缓解了最初完全查不动的问题,但总体来讲只能说勉强完成了最初期望的五六成吧

5.后续进一步分析业务后,从需求和业务逻辑上优化,取消了用户课次表的维护
遇到的关键问题在于,sharding-jdbc的多表路由和merge时存在较大的性能隐患,这点是一开始没意识和考虑到的,这块的相关资料例如这篇文章https://blog.csdn.net/D_19901719576/article/details/102925945
参与角色/程度 1.在他人的指导下,参与方案设计时的讨论
2.参与实际开发落地,加上自己共计三个开发,我和另一个开发新写的分表项目和策略,还有一个开发改造的业务代码调用处(新增业务开关切换)
自己是首次参与到分表项目中,方案设计时还算踊跃,应该说会有一些自己的想法,但由于不确定正确性,故仅讨论时偶尔提出,但最终还是按大家一致的意见落地,总体来讲还算是不错的经历

1.2后续自己的总结与思考

1.关于课次表的合理性的一些思考:
1.1 用户课次表的冗余比较方便的操作用户调课/换课/退课等,但确实会导致用户的课次数据一直增加,表会膨胀,且该表的数据可以由多个小表组合得出,故当时的分析后续是取消了该表的维护,直接从业务和逻辑上改成了多个小表拼接出大表结果;
1.2 但用户课次数据,尤其是上过的课,是否应该记录下来,自己始终存在疑惑。

2.如果保留用户上课的数据,是否有其他的分表方式?
2.1大学里面学校上课时,也存在每个人选的课不一样,一个老师上课同时面对多个不同的班级(子班)的情况,但一个比较大的区别在于,大学的课都是一个学期一个学期。如果按学季分表是否能更有效的支持后续的业务?未进行验证,且单个学季的数据量会不会也很大,此点存疑。
2.2当时的课程类型分为小、初、高,及特定的课程类型(讲座/免费课)等,如果按具体类型分表是否能更有效的支持后续的业务?未进行验证,此点存疑。
2.3针对不同的业务入参,按不同的方式分表,取消异构索引表,例如老师来查直接按teacher_id分表,学生来查直接按uid分表?未进行验证,此点存疑。

二.按月分表

2.1经历概述

事项 具体方式/情况 解释备注
业务背景 游戏互娱业务,用户背包道具表/用户道具消耗表 按用户和道具记录的流水,同一道具多次获得是多条数据,使用/兑换也只改用的那条数据的状态
分表前数据量 约5000万
分表策略 1.根据数据产生时间,按月分表

2.根据用户id,道具id,单独异步维护一份统计表,来支持查总价值,总数等
1.流水表主要是按时间段或者用户最近的获得记录等信息时需要用到

2.统计信息避免直接在流水表中做统计,消息异步维护统计表,直接查统计表就可以了

3.历史数据实际用户侧很少会查询,一定程度上按月分表有利于把存量不用的数据隔离开,查询较多的近几个月的数据相对独立
分表数 每月一张,表名为XXXX_YYYYMM
分表框架 自己逻辑写的查询方法和路由方法
潜在问题 用户道具记录持续往前翻的时候有可能从当前的表查到了上一页的表 数据拼接时容易出问题,但并非完全无法解决
数据迁移 提前建好历史到今年年底的所有分表

上线后定时任务按id从1到最新的逐步处理数据,每处理500条更新一下已处理的游标,每次都是从已处理游标后拿500条数据写到分表中
定时任务的入参将截止id作为参数,这样通过更改游标和定时任务截止的id,未来也可以随时通过该任务补偿数据
上线方式 1.新的分表是独立的策略去读写,原项目操作老表的地方,增加读写开关来切换操作的是老表还是新表

2. 刚上线时业务开关切换双写,读老

3.将新表开始写的id作为截止id的入参,开始执行同步数据的任务

4.等定时任务完全同步一致后,切换开关读新

5.持续观察稳定后,下次上线时下线开关逻辑代码
分表结果 1.分表前部分查询出现慢查;分表后同样的业务功能查询性能较好

2. 未停服,用户基本无感
参与角色/程度 由于该项目是个老项目,需要优化的大表多达十来个,讨论时多人一起讨论,最终几个人分别拿了不同的表去分析各自的落地方案,具体到该表的分表基本是自己完整设计和落地 有了此前的分表经历后,对方案的部分细节设计时更为笃定,偶尔遇到拿不准的也会提出来和同事讨论,最终基本由自己完整落地,也是一个很珍贵的精力

你可能感兴趣的:(两次不同的分表实践总结)