实战篇:单库单表变更成多库多表

  • 2022 年 4 月 10 日
  • 筆記

大家好,我是七淅(xī)。

如标题所说,本文会结合我自己的亲身经历,介绍 3 部分内容:

  1. 线上单库单表变更到多库多表的各个实现方案
  2. 方案优劣对比
  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;

2.2 方式二

新增 user_0、user_1、...、user_7 共 8 个库,user 库数据不动,继续使用。

至于选哪种方式,大多情况下,我个人认为都可以,但如果历史表本身请求就很高,那可以考虑用方式二,避免 0 号库压力太大。

我这边是选择的方式一。当用户要访问历史表时,指定路由到 0 号库就好了,顺便省下一台数据库的钱,真香

3. 变更方案

方案这块内容,我会基于方式一的历史数据处理方式来讲。

首先,先不考虑任何方案,我把最简单的,变更到多库多表的操作按顺序列举一下:

  1. 修改服务连接数据库的配置,业务代码编写
  2. 增加 user 0-7 号数据库
  3. 将 user 库旧表数据迁移到新增 user_0 库
  4. 部署服务

但是如果按照上述做法,在第 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 后端干货。

如果对你有帮助的话,可以给个赞再走吗