大家好,我是七淅(xī)。
如标题所说,本文会结合我自己的亲身经历,介绍 3 部分内容:
- 线上单库单表变更到多库多表的各个实现方案
- 方案优劣对比
- 对于历史存在的单表,并且它们不需要变成多表,需要怎么处理
先下个结论,没有百分百完美的方案,技术方案永远要结合产品业务来设计。
以下举例的方案也只是较为通用的做法,具体细节是可以根据业务场景进行变化调整的。
只要能够满足业务需求,就是好方案,不要为了秀技术而忽略业务。
看完这篇文章,如果后面有人问你,关于变更到多库多表的方案问题,那你可以和他谈笑风生了。
好了,下面我说下我这边的业务背景,和大家解释清楚为什么需要多库多表。后面会引申出方案的,莫急。
1. 业务背景有一个在线上运行着的数据库,假设是 user 库,库中只有 1 张单表。
现在有个新需求,该需求的功能有一定的请求量和数据量。
其中数据量初期是百万级,考虑到业务增加,增长到千万、上亿都是有可能的。所以从数据量上看,单库单表不合适。
Q1:如果只是数据量问题,那用单库多表行不行?
A1:行。
Q2:那为什么还用多库多表呢?
A2:因为一个数据库的连接数量是有限的,怕翻车。
上面有介绍业务有一定的请求量,担心一个库来处理的话,万一哪天网络不好/慢查/该表业务有突发性活动等情况出现。
一不小心就把连接数占满了,那就直接翻车了。
加上我司对多库多表的基建比较成熟,所以我这边就直接上多库多表了。
Q3:既然如此,前期先上单库多表,等量上来后再多库多表行不行?
A3:可以。但是到时再来一次太累了。
比如再来一次会经历以下事情:
- 每天需要看看数据监控
- 有没有到瓶颈
- 到时再次变更时,开发运维测试业务的排期和执行
- 业务变动:说好的下个季度大推,结果提前到下一个月进行,此时数据库能不能扛住,扛不住改造时间是否充足?
所以,我们要不还是一步到位吧。
2. 历史数据处理滴,七淅提醒你:看到这,如果有人问你单库多表和多库多表的使用场景,你应该知道怎么发挥了吧
我先说下对历史数据处理,篇幅较少。
这里的内容对应文章开头的第三点:对于历史存在的单表,并且它们不需要变成多表,需要怎么处理
这里可以有两种处理方式。
我们知道,历史数据在 user 库,假设业务需要增加到 8 个库,并且新表需要在这 8 个库中
2.1 方式一新增 user_0、user_1、...、user_7
共 8 个库,使用 rename
命令,将 user 库的表迁移到 user_0
库中,最后将 user 库删掉即可。
rename 命令其实就是重新命名,实现剪切数据的效果,而不是复制。当然要用复制的方式迁移数据也是可以的,但我们这边没用。
reanme 命令使用如下:
rename table user.table_name to user_0.table_name;
新增 user_0、user_1、...、user_7
共 8 个库,user 库数据不动,继续使用。
至于选哪种方式,大多情况下,我个人认为都可以,但如果历史表本身请求就很高,那可以考虑用方式二,避免 0 号库压力太大。
我这边是选择的方式一。当用户要访问历史表时,指定路由到 0 号库就好了,顺便省下一台数据库的钱,真香
3. 变更方案方案这块内容,我会基于方式一的历史数据处理方式来讲。
首先,先不考虑任何方案,我把最简单的,变更到多库多表的操作按顺序列举一下:
- 修改服务连接数据库的配置,业务代码编写
- 增加 user 0-7 号数据库
- 将 user 库旧表数据迁移到新增 user_0 库
- 部署服务
但是如果按照上述做法,在第 3、4 步执行期间,如果用户访问原 user 库的数据会有问题。
具体来说:user 库的旧数据此时已经通过 rename,迁移到了 user_0 库,但因为部署还没部署完成,连接数据库的配置没有更新。
所以请求依旧会跑去 user 库查询,导致查不到数据,后续业务逻辑没法顺序继续执行。
用户也会纳闷:「这个地方之前进来都有数据的呀,怎么现在全空了?」
所以,需要确定合理的升级方案,最大程度减少对业务和用户的影响,
3.1 方案一这是最简单的方式。
看监控,挑选没有流量的时候,进行 db 变更和服务部署。
当然,监控也只是过去的情况,保不准功能上线那天就一直有流量没停歇过呢。
所以再求稳一点的话,可以发个公告,告知用户 xx 功能会在 xxx 时间段进行维护,期间不可访问。
如果有玩农药(王者荣耀)的朋友应该很熟悉吧,每次版本更新都需要停服,就是这样的效果哈。
最后在完成之后,进行回归测试和新功能测试,看看功能是否正常。
如果正常那就可以去睡觉了,有问题就继续改 bug 解决;
如果评估没法在公告所说的截止时间解决,那就只能进行回滚,改日再(jia)战(ban)。
PS:如果需要对历史数据进行分库分表的话,最好进行数据量的对比检验。因为我这边不涉及对历史数据进行分库分表,所以这步就省了。
3.2 方案二这个方案会复杂很多,开发量也会很大。
我这边就只说关键步骤,具体细节就没法一一写了。因为要写的话又多出几千字的内容,篇幅太长,我估计也没多少人有耐心看完。
那话说回来,这个方案最大的好处就是业务功能不用停用,所以也就不用熬大夜了。
那要怎么做呢?
3.2.1 历史单表数据处理1、先把 user 库现有的数据复制一份到 user_0 库。
2、因为 user 库的数据是会被修改和新增的。所以当复制完成后,数据依旧存在变化,所以需要新增双写逻辑,保证 user_0 库的数据也能同步到变更。
3、对于数据的读写,都支持由开关控制,分别可以控制数据读写是请求到哪个数据库。
4、服务更新完成后,进行两个库的数据一致性对比。都没问题后,开关控制读写数据都请求到 user_0 库
3.2.2 新功能的多表数据处理因为是新功能,其实不用怎么特殊处理。
为什么这么说呢?
因为我们部署服务的顺序肯定是操作数据库的底层服务先发布,发布完成后,才对用到底层服务的应用服务进行发布。
所以作为业务功能入口的应用服务都还没发布,此时是不会有新功能数据到达底层服务的。
要是不能保证这个顺序,你想下功能入口开放了,用户请求进来后,底层服务发现找不到这个表,是不是就直接报错了?
所以才会有上面说的发布顺序,只要保证发布顺序没错,那这块新功能的数据是不需要特殊处理。
3.3 方案优劣对比其实 2 个方案就是互补的,一个方案的优点就是解决了另一个方案的缺点。
七淅用表格总结一下:
最后,你问我当初是选哪个方案?
那肯定是方案一啊,大不了熬一夜嘛。
不然那么麻烦的方案,排期又那么紧张,开发是不可能开发的,这辈子都不可能的。真有什么问题,大不了就人工介入处理,yyds
文章首发公众号:七淅在学Java ,持续原创输出 Java 后端干货。
如果对你有帮助的话,可以给个赞再走吗
【本文由:高防服务器ip http://www.558idc.com/gfip.html 复制请保留原URL】