参考:
MySQL 数据库平滑扩容方案剖析
分库分表后如何实现不停机扩容
一 双写迁移方案
可通过 canal 或 mq 做实现
第一阶段:在线双写,查询走老库
- 建立好新的库表结构,数据写入老库的同时,也让写入拆分的新库。
- 数据迁移:使用数据迁移程序,将旧库的历史数据迁移到新库。避免增量影响,先断开主从,再导入(耗时较长), 同步完成并做校验
- 使用定时任务:新旧库的数据对比,补齐差异
第二阶段:在线双写,查询走新库
- 完成了历史数据的同步和校验
- 把对数据库的读操作切换到新库,通过 Nginx,切换访问流量至新的服务。
- 依然向旧库写数据,观察业务一段时间,查看是否正常
第三阶段:旧库下线
- 旧库不再写入新数据
- 经过一段时间,确定旧库没有请求之后,就可以下线旧库。
二 平滑2N扩容方案
问题:使用取模分片后,如需进行扩容,新增节点时会有大量的旧数据需要迁移,迁移过程中可能需停服,等全部数据迁移完成后才能哈希到正确的数据库。但这样会面临大量的数据压力,并且对服务造成极大的不稳定性。
关键点:每个数据库都配置了一个从库,读写分离架构,从库和主库数据基本保持一致。扩容时,把所有的从库变成主库,这样主库就从N个变成了2N个。取模分片算法从id%N 变成 id%2N,使得扩容后原有数据不需要迁移
上图左侧时扩容前,有两个主库A和B,分别对应从库A0和B0。扩容后如右侧,有四个主库。
第一步:从库升级为主库
将A0和B0变成主库,此时整个库有一半的冗余数据,后续随时可以清理。
比如 uid=3
扩容前 两个库, 3%2=1, 存入B库,B0是从库,也存在该条数据
扩容后 四个库, 3%4=3,存入B0,而B中也有该数据,那么B库有冗余,后续随时可以清理。
第二步:逻辑层把取模分片算法从id%N改为id%2N。
调整数据库配置,通过配置中心更新,不需要重启。
扩容后取模分片算法 | 目标分片 | 不迁移原因 |
---|---|---|
uid%4=0 | A | 原库,不需要迁移 |
uid%4=1 | B | 原库,不需要迁移 |
uid%4=2 | A0 | 扩容前在A库,A0存在该数据 ,不需要迁移 |
uid%4=3 | B0 | 扩容前在B库,B0存在该数据,不需要迁移 |
第三步:为2N个主库分布配置一个从库
保证数据库的高可用,也为下一次2N扩容做准备。
注意事项: 由于旧的主库一直在进行DML操作,从库不可能完全和主库的数据保持一致。所有需要从库和主库完全同步结束时才能升级为主库。 解决方案:主库停写一段时间,如停服一小时,或主库开启只读一段时间。
参考落地实现:平滑 2N 扩容方案实践
三 停机方案
1. 发布停服公告
为了进行数据的重新拆分,在停止服务之前,我们需要提前通知用户,比如:我们的服务会在 yyyy-MM-dd 进行升级,给您带来的不便敬请谅解
2. 停止服务
关闭 Service,不再对数据库进行增删改查等操作
3. 增加新的数据库节点
购买或建立新的数据库,建好数据库和表
4. 旧数据迁移
将旧库中的全部数据按照新的哈希算法,将数据重新分配,更新到正确的数据库
5. 数据校验
开发一个程序对旧库和新库中的数据进行校验,比对,确保重分配是正确的
6. 更改数据库配置和哈希算法
Service配置中添加新库连接地址,账号密码等;修改 Service 层的算法,也就是将原来的 uid%3 变为 uid%4
7. 恢复服务
重启 Service 服务
总结 停止服务之后, 能够保证迁移工作的正常进行。 但是服务停止,伤害用户体验, 而且造成了时间压力, 必须在指定的时间内完成迁移,且不能出错,每一步都要有回滚预案
四 停写方案
1. 发布停写公告
为了进行数据的重新拆分,我们需要提前通知用户,比如:我们的服务会在 yyyy-MM-dd 进行升级,期间不可进行某些操作,给您带来的不便敬请谅解
2. 停写,中断写操作(或拦截返回统一提示)
每个数据库设置为只读状态,关闭写的功能。在 Service 层对所有的写请求进行拦截,统一返回提示信息,如:服务正在升级中,只对外提供读服务
3. 增加新的数据库节点
购买或建立新的数据库,建好数据库和表
4. 旧数据增量式迁移
将旧库中的全部数据按照新的哈希算法,将数据重新分配,更新到正确的数据库。注意迁移时不可删除旧库的冗余数据,因为正在提供读服务
5. 数据校验
开发一个程序对旧库和新库中的数据进行校验,比对,确保重分配是正确的
6. 更改数据库配置和哈希算法
Service配置中添加新库连接地址,账号密码等;修改 Service 层的算法,也就是将原来的 uid%3 变为 uid%4
7. 恢复写操作
打开数据库的写功能,去除 Service 层的写拦截提示
8. 清除冗余数据
按照旧的哈希算法,也就是uid%3,使用 delete 语句对冗余数据进行删除
总结
优点:避免了长时间的停服,停写期间用户可以登陆,可以查看自己的数据,可以做其他非DB写入性活动缺点:类似方案一,造成了时间压力, 必须在指定的时间内完成迁移,且不能出错,每一步都要有回滚预案