第28章 系统迁移与升级:平滑迁移策略、版本兼容、回滚方案、数据迁移

做市商系统跑得好好的,为什么要动它?

说实话,没人喜欢做迁移。我见过太多团队,系统一跑就是三四年,代码越堆越厚,技术债越欠越多。直到某天,某个核心模块再也撑不住了——要么是性能瓶颈,要么是底层依赖不再维护,要么是业务需求变了。

这时候,你不得不面对一个问题:怎么把一辆高速行驶的赛车,在不停车的情况下换掉发动机?

28.1 平滑迁移的核心思路

平滑迁移,说白了就是让用户感觉不到你在换系统。我个人的经验是,迁移策略的选择,直接决定了项目成败。

核心原则:一次只变一件事。如果你同时改架构、换数据库、升级语言版本,出了问题你根本不知道是哪一步导致的。

常见的迁移模式有三种:

  • 蓝绿部署:两套环境同时运行,流量瞬间切换。适合无状态服务。
  • 灰度发布:按比例逐步放量,比如先切5%流量到新系统。适合有状态服务。
  • 金丝雀发布:先让一小部分真实用户用新系统,观察没问题再全量。我习惯用这个。

举个例子,我们之前迁移订单管理系统,用的是灰度发布。先让内部交易员用新系统,跑了三天没问题,再开放给10%的外部客户,然后逐步增加到50%、100%。每一步都留了观察窗口。

28.2 版本兼容:新老系统共存的艺术

迁移过程中,新老系统必然要共存一段时间。这时候版本兼容就是命门。

我踩过一个坑:新系统改了消息协议格式,老系统还在用旧格式。结果灰度期间,新系统发出去的消息老系统读不懂,直接丢单了。那叫一个惨。

避坑指南:我曾经因为没做双向兼容,导致灰度期间数据不一致,花了整整两天才修复。记住,新系统要能处理老数据,老系统也要能忽略新字段。

版本兼容的几个关键点:

  • 接口兼容:新增字段用optional,不要改已有字段的类型和含义
  • 协议兼容:使用protobuf或thrift时,字段编号不要复用
  • 数据兼容:数据库schema变更要向前兼容,只加列不改列
  • 配置兼容:新配置项要有默认值,老配置项不能删除
// 错误示范:改了已有字段的含义
message Order {
    int32 id = 1;      // 原来是订单ID,现在改成用户ID —— 大忌!
    string symbol = 2;
}

// 正确做法:新增字段
message Order {
    int32 order_id = 1;
    string symbol = 2;
    int32 user_id = 3;  // 新增字段,老系统会忽略
    optional string extra_info = 4; // optional更安全
}

28.3 回滚方案:给自己留条后路

做迁移,一定要想好怎么回来。这不是悲观,是职业素养。

我见过最离谱的案例:某团队做数据库迁移,直接把旧库删了。新库跑了三天发现性能有问题,想回滚?对不起,数据已经没了。

我的习惯:每次迁移前,先写回滚脚本,测试回滚流程。回滚脚本和迁移脚本一起评审,一起上线。这叫「上车先找逃生门」。

回滚方案要覆盖三个层面:

层面回滚策略注意事项
代码保留上一版本的部署包,通过CI/CD一键回退确保数据库schema也兼容旧代码
数据保留数据快照,支持全量或增量回滚增量回滚要保证幂等性
配置配置中心保留历史版本,支持秒级回退配置变更要记录变更日志

你想想看,如果回滚需要半小时,这半小时里系统是坏的。对于做市商系统,半小时可能意味着几百万的损失。所以回滚一定要快,最好在1分钟内完成。

28.4 数据迁移:最头疼的部分

数据迁移是迁移中最容易出问题的环节。为什么?因为数据有状态,有依赖,有完整性约束。

我个人的经验是,数据迁移要分三步走:

  1. 全量迁移:把历史数据一次性搬过去。这个阶段可以离线做,不影响线上。
  2. 增量同步:全量迁移完成后,开启实时同步,把新产生的数据同步到新系统。
  3. 数据校验:这是最关键的一步。两边数据要逐条比对,确保完全一致。

数据校验的要点:不要只比对总数,要抽样比对明细。我曾经遇到过总数对得上,但某几条数据字段值不一样的情况——因为同步脚本有bug,把某个字段写反了。

数据迁移的常见模式:

  • 双写模式:新老系统同时写入,保证两边数据一致。适合写少读多的场景。
  • 日志回放:通过解析binlog或WAL日志,把变更同步到新系统。适合写多的场景。
  • ETL批处理:定时批量同步,适合对实时性要求不高的场景。
# 数据校验脚本示例(伪代码)
def verify_data():
    old_count = old_db.count("orders")
    new_count = new_db.count("orders")
    if old_count != new_count:
        raise Exception(f"总数不一致: old={old_count}, new={new_count}")
    
    # 抽样校验
    sample_ids = random_sample(old_db, "orders", 1000)
    for id in sample_ids:
        old_row = old_db.get("orders", id)
        new_row = new_db.get("orders", id)
        if old_row != new_row:
            log_error(f"数据不一致: id={id}")
            # 触发修复流程

28.5 迁移的整体流程

说了这么多,我们来画一张图,把整个迁移流程串起来。

系统迁移与升级核心流程 1. 迁移准备 评估、规划、资源准备 2. 版本兼容 接口、协议、数据兼容 3. 数据迁移 全量+增量+校验 4. 上线 灰度放量 回滚路径(任何时候发现问题) 校验通过? 全量上线 触发回滚 每个阶段都要准备回滚方案,确保可逆 数据校验是迁移成功的最后一道防线

28.6 迁移后的验证与监控

迁移完成不代表万事大吉。我习惯在迁移后持续监控至少一周,重点关注几个指标:

  • 延迟对比:新系统响应时间是否和老系统一致?有没有异常抖动?
  • 错误率:新系统的错误率是否在可接受范围内?
  • 数据一致性:持续校验新老系统的数据,确保没有偏差。
  • 资源消耗:CPU、内存、磁盘IO是否正常?

一个小技巧:迁移后保留老系统只读模式运行一周。万一新系统有问题,还能从老系统查数据。一周后确认没问题,再彻底下线老系统。

嗯,说到最后,其实迁移这件事,技术方案只占一半,另一半是人的因素。沟通、计划、测试、回滚,每一步都要想清楚。我见过太多技术方案完美、执行一塌糊涂的案例了。

记住一句话:迁移不是技术问题,是风险管理问题。


交易系统化学习资料 微信Strategy888888