4

实战篇:单库单表变更成多库多表 - 七淅在学Java

 2 years ago
source link: https://www.cnblogs.com/czd-xi/p/16125241.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

大家好,我是七淅(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 库

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK