4. 安全升级方案设计:灰度发布策略、回滚方案设计、最小化停机时间方案
分区表升级,说白了就是给正在运行的数据库做“心脏手术”。
我见过太多团队,方案写得漂漂亮亮,一上线就崩。为什么?因为忽略了两个核心问题:怎么慢慢放量,以及出事了怎么跑路。
这一章,我就把压箱底的经验掏出来。灰度发布、回滚方案、最小化停机,咱们一个一个说清楚。
4.1 灰度发布策略:别一把梭哈
我个人习惯,任何分区表变更,都遵循“1-5-20-100”的灰度原则。
什么意思?就是流量从1%开始,逐步放大到5%、20%,最后全量。每一步都要观察,确认没问题再继续。
核心原则:灰度不是走形式,而是为了在最小影响范围内,暴露所有潜在问题。
4.1.1 灰度发布的三层模型
我把它分成三层:数据层灰度、服务层灰度、用户层灰度。
- 数据层灰度:先选一个非核心业务的分区表做试点。比如用户日志表,别一上来就动订单表。
- 服务层灰度:通过配置中心,只让部分应用实例连接到新分区表。其他实例继续走老路。
- 用户层灰度:按用户ID哈希,或者按地域,只让一小部分用户命中新分区逻辑。
我在项目中遇到过,数据层灰度跑通了,服务层灰度却出了问题。原因是新分区表的索引结构变了,老代码的SQL没适配。所以三层灰度,一层都不能省。
4.1.2 灰度发布流程图
下面这张图,是我每次做灰度发布前必画的。你照着这个流程走,基本不会翻车。
我的经验:每个灰度步骤之间,至少留出30分钟的观察窗口。别急着点下一步,让子弹飞一会儿。
4.2 回滚方案设计:留好退路
做升级方案,我最先想的不是怎么成功,而是怎么失败。
回滚方案不是事后补的,而是和升级方案一起设计的。说白了,你得保证“一键还原”。
4.2.1 回滚的三种模式
| 回滚模式 | 适用场景 | 回滚时间 | 风险等级 |
|---|---|---|---|
| 数据回滚 | 分区结构变更出错 | 分钟级 | 低 |
| 服务回滚 | 应用代码不兼容 | 秒级 | 极低 |
| 全量回滚 | 灾难性故障 | 小时级 | 高 |
我个人最推崇服务回滚。为什么?因为快。你想想看,数据回滚要重建分区表,全量回滚要切数据库,只有服务回滚,改个配置中心的值就搞定了。
4.2.2 回滚脚本示例
我曾经因为没写回滚脚本,吃过一次大亏。那次升级导致分区表数据错乱,花了整整4个小时才恢复。从那以后,我要求每个升级方案必须附带回滚脚本。
-- 回滚脚本:恢复旧分区结构
-- 场景:新分区策略导致查询性能下降
-- 1. 停止写入新分区
ALTER TABLE orders READ ONLY;
-- 2. 重命名新分区表
ALTER TABLE orders RENAME TO orders_new;
-- 3. 恢复旧分区表
ALTER TABLE orders_backup RENAME TO orders;
-- 4. 同步增量数据(假设有增量日志表)
INSERT INTO orders SELECT * FROM orders_new
WHERE create_time > (SELECT MAX(create_time) FROM orders);
-- 5. 恢复写入
ALTER TABLE orders READ WRITE;
-- 6. 清理临时表(确认无误后执行)
-- DROP TABLE orders_new;
注意:回滚脚本一定要在测试环境完整跑一遍。我见过太多回滚脚本,里面还有语法错误。真到用的时候,哭都来不及。
4.3 最小化停机时间方案:零感知升级
现在的业务,停机1分钟就是事故。所以我们要追求零感知升级。
说白了,就是用户完全不知道你在升级。怎么做?核心思路就四个字:在线切换。
4.3.1 双写方案
这是我最常用的方案。原理很简单:同时写入新旧两张分区表,等数据一致后,再切流量。
- 开启双写:应用层同时写入旧表和新表。新表先不对外服务。
- 数据校验:跑一个校验任务,确保新旧表数据完全一致。
- 流量切换:通过配置中心,将读流量逐步切换到新表。
- 关闭双写:确认新表稳定后,关闭旧表的写入。
我在项目中遇到过,双写阶段因为事务没处理好,导致新旧表数据不一致。后来加了个补偿任务,每小时跑一次,把差异数据补回来。
4.3.2 切换时间线
下面这个时间线,是我做最小化停机方案的标准模板。你照着这个节奏走,停机时间可以控制在30秒以内。
| 时间点 | 操作 | 状态 |
|---|---|---|
| T-7天 | 创建新分区表,开启双写 | 正常服务 |
| T-1天 | 全量数据校验,修复不一致 | 正常服务 |
| T-1小时 | 暂停非核心写入,降低负载 | 正常服务 |
| T+0 | 切换读流量到新表 | 停机30秒 |
| T+5分钟 | 验证新表查询性能 | 正常服务 |
| T+1小时 | 关闭旧表写入,清理资源 | 正常服务 |
避坑指南:我曾经在T+0切换时,忘了关旧表的写入触发器。结果新表数据被重复写入,导致主键冲突。记住:切换前,一定要检查所有触发器、定时任务、消息队列消费者,确保它们都指向新表。
4.3.3 零停机升级的终极方案:影子表
如果你连30秒的停机都接受不了,那就用影子表方案。
原理是:在同一个数据库里,创建一份“影子”分区表。通过数据库的在线DDL工具(比如gh-ost、pt-online-schema-change),在后台完成分区变更。变更期间,原表完全不受影响。
# 使用gh-ost进行在线分区变更
# 注意:gh-ost原生不支持分区表,需要变通处理
# 步骤1:创建影子表
CREATE TABLE orders_shadow LIKE orders;
# 步骤2:在影子表上应用新分区策略
ALTER TABLE orders_shadow
PARTITION BY RANGE (YEAR(create_time)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
# 步骤3:使用gh-ost同步数据(模拟)
# gh-ost --alter="..." --execute
# 步骤4:切换表名(原子操作,毫秒级)
RENAME TABLE orders TO orders_old, orders_shadow TO orders;
这个方案,我只有在核心交易场景才用。因为复杂度高,一旦出问题,排查起来很痛苦。但效果是真的好——用户完全无感知。
4.4 总结:安全升级的三板斧
好了,这一章的内容就这些。我最后帮你捋一下核心思路:
- 灰度发布:1% → 5% → 20% → 100%,每一步都要观察和验证。
- 回滚方案:优先服务回滚,脚本必须提前测试。
- 最小化停机:双写 + 在线切换,目标是30秒以内。
你想想看,只要把这三点做到位,分区表升级还有什么好怕的?
最后一句忠告:再完美的方案,也抵不过一次粗心大意的执行。升级前,一定要做一次完整的演练。我在团队里有个规矩:演练不过关,不准上线。