接手过老系统改造的人应该都有同感系统上线本身并不难真正让人睡不着觉的是那一堆历史数据怎么搬。前阵子我正好给某支付系统的对账单模块做过一次完整的数据迁移老库里有2.3亿条流水记录新系统上线之后老库几乎没有任何读取入口所有历史数据都锁死在旧表里。后来我们基于一套自研的迁移工具把整个迁移过程拆成抽取—清洗—校验—回滚四个阶段全程业务无感最终跑完耗时约17小时数据差为0。这篇就完全围绕这个场景来写老系统改造中的亿级数据迁移到底应该怎么设计、怎么执行、怎么兜底。内容会覆盖迁移方案的选型思路、分片抽取原理、双写策略、校验机制和回滚方案适合正在做新老系统切换的研发同学参考。1. 老系统改造里数据迁移为什么总是最容易被低估的环节很多团队在规划新老系统改造时第一反应是先写新功能、搭新架构数据迁移放到上线前一周再考虑。这个顺序其实完全反了。对于亿级数据量的系统迁移方案的设计应该从改造的第一天就介入因为它直接影响新系统的表结构设计、双写时机和上线节奏。就拿我负责的这个项目来说老系统是个单体应用核心表是账务流水表数据量2.3亿条单表占空间接近200GB。新系统改成了分库分表的微服务架构同样的数据要按用户维度散到32个分片里。如果不在改造前期就确定哪些数据要迁、怎么迁、怎么校验等新系统接口都开发完了再回头补迁移方案大概率会被历史数据的历史包袱拖死。1.1 先分清三种数据静态数据、动态数据、归档数据做迁移方案前第一步是给数据分门别类这个环节决定了后续所有技术选型。静态数据比如用户信息、商户配置、产品参数这类数据基本不变或极少变化迁移时只需要做一次全量同步再补一个短期的增量窗口即可。动态数据比如交易流水、订单记录、账务变动这类数据在迁移期间仍然持续写入必须设计双写或增量子同步机制是最难处理的部分。归档数据比如三年前的日志、历史对账单这类数据访问频率极低但合规要求必须保留。可以考虑冷存储方式迁移或者在新系统中只保留索引、原始文件转存对象存储。我这次的对账单流水属于动态数据中的典型场景迁移期间业务不停每天还有几十万条新流水产生。所以最终方案必须包含存量数据全量迁移 迁移期间增量数据实时同步两条链路缺一条都会造成数据断层。1.2 迁移对业务的影响面远比想象中更大除了数据本身还要考虑迁移对上下游的连带影响。老系统的数据不只是给老系统自己用的周边还有报表系统、风控系统、财务对账平台在读取这些数据。新老系统切换后这些下游系统的数据源切换也要排进迁移计划——子系统的兼容性改造、取数接口的适配、数据字典的映射这些工作量摊开来往往是迁移本身的三倍以上。我们当时梳理下来光是对账单流水这个数据的下游依赖就有7个系统。最麻烦的是一个老报表平台它直接连老库写SQL切换之后要么给它搭一个只读的新库视图要么改造它的取数逻辑。最后我们选了折中方案新库上线后保留老库只读状态30天老报表平台继续读老库用定时任务把增量数据反向同步回去给下游系统留足改造时间。这个新库为主、老库只读的过渡期设计是这次项目能顺利切换的关键之一。2. 亿级数据迁移的三种技术路线怎么选出最优解数据迁移没有银弹方案取决于业务容忍度、数据规模、系统架构这几个约束条件。我习惯把常见方案分成三类同构停机迁移、同构双写滚动迁移、异构在线迁移。对账单流水这个场景最终用的是第三种方案的变体但前两种的适用场景也值得展开说一下方便大家对照自己的项目判断。2.1 同构停机迁移最简单但窗口期要求高如果新旧系统数据库类型一致、表结构基本不变、业务允许一个较长的维护窗口那停机迁移是最省事的方案。流程就是停服—导出—导入—校验—开放新系统。它的优势是逻辑简单不需要考虑双写和增量同步一致性天然有保障。缺点也很明显亿级数据量下导出导入本身就要好几个小时加上校验时间整个停机窗口可能超过一个工作日。现在的互联网业务别说一天就是停一个小时都会有大量客诉。所以我们最开始就否掉了这个方案——这笔账算下来完全不能接受。2.2 同构双写滚动迁移适合数据量中等、表结构变化不大的系统同构双写滚动迁移指的是新老系统共用同一套数据库结构上线阶段对新老两套应用同时写入相同数据然后在某个时间点把读流量切到新系统。这个方案在服务升级场景很常见但不太适合表结构大幅重构的情况。对账单流水的老表和新表虽然字段接近但分片键完全变了老表按账号余额分片、新表按用户ID分片还加了好几个新字段。如果做同构双写意味着每写一条数据要在两套表里各写一次结构不同还得做字段映射业务逻辑里的写入点全部要改。成本比预想高得多所以这个方案也排除了。2.3 异构在线迁移最复杂但最通用异构在线迁移就是这次最终采用的路线。核心思路是老系统继续作为生产主系统运行迁移工具从老库抽取数据按新系统的表结构加工转换写入新库同时通过双写或MQ同步捕获增量数据最后通过影子校验、数据对账确保两边数据一致再切换流量。这里有一个关键认知迁移工具本质上是一个数据加工管道它要处理的不只是搬运还有结构映射、清洗、去重、幂等等一系列逻辑。对于亿级数据这个管道要设计成可水平扩展的分片任务模型才能做到在可接受的时间内完成任务迁移。3. 工程落地分片抽取、双写同步与失败重试的具体设计方案定了之后真正的硬骨头在于落地细节。亿级数据迁移的每一个环节单拿出来都值得写一篇文档。这一节我把几个核心组件的设计思路展开讲都是这次项目实际踩过坑之后调优出来的方案。3.1 数据抽取的分片策略主键区间分片 动态水位线对账单流水表的主键是自增ID最自然的分片方式是按主键区间切分。我们当时把2.3亿条数据按每1万条一个分片切成23000个任务分片扔到一个基于内存队列的任务调度器里由多个worker并发消费。真正需要花心思的是吞吐控制。直接全量并发拉数据老库的CPU和IO会瞬间被打满影响线上交易。我们设计了水位线保护机制迁移工具实时监控老库的活跃连接数和慢查询数一旦超过阈值就自动降低并发度等系统空闲再恢复。最终实测下来老库的CPU稳定在30%以下平均抽取速度能达到每秒6500条左右。提示分片大小不是固定的主要看单条记录的大小。如果单条数据在1KB以内1万条一个分片很合适如果单条有几十KB建议把分片调小到2000-3000条否则导入新库时内存压力很大。分片抽取一定要支持断点续传。我们用了一张迁移任务表每个分片的状态字段记录待执行/执行中/成功/失败worker执行完一个分片就更新状态。这样任何一个节点崩溃重启之后只需要继续拉取执行中之外的分片即可不需要从头扫一遍全量数据。这一点在长耗时迁移中非常重要——你永远无法保证数小时的迁移过程中不出现任何机器故障。3.2 结构映射与清洗规则迁移工具里的加工车间从老库抽出来的数据不能直接灌进新库因为两套系统的字段定义完全不一样。我举个具体的例子老系统的金额字段是int类型单位是分新系统统一改成bigint单位改成厘。用户ID老系统是自增ID新系统换成了雪花算法生成的分布式ID中间还需要通过映射表查出对应的新ID。如果不做清洗就直接导入那新系统上线第一天账就对不平。所以我们在迁移工具里设计了转换链的概念一条数据经过字段映射、类型转换、字典翻译、格式校验四道工序后才会被写入新库。每一道工序都可以单独配置和启停方便分批调试。清洗规则存成配置而非代码这样遇到特殊字段时可以直接调整配置重启任务不需要重新打包发布。3.3 增量同步的双写机制接口层拦截 MQ削峰存量数据在迁移期间新产生的数据怎么处理这是动态数据迁移的核心矛盾。我们使用的方式是双写但不是简单地在业务代码里加一行同时写新库。具体设计是这样的在交易核心链路的写入接口上做了代理层正常写老库的逻辑不变同时将变更数据以统一格式发送到MQ。迁移工具里单独跑一个增量消费组消费消息后按新库结构写入。这里有个很重要的设计原则——增量同步必须做到幂等。因为MQ存在重试机制同一条消息可能被消费两次所以我们把幂等键设为主键ID 数据版本号写入新库时先查一次是否已存在存在就比对版本号新版本覆盖旧版本。实际运行中增量同步的延迟控制在3秒以内基本可以对业务无感知。偶尔出现MQ堆积延迟会拉到30秒左右这个在我们的容忍范围内。如果你业务上无法接受延迟就需要改成同步双写但同步双写会拖慢主链路响应时间需要压测评估。3.4 历史存量与增量数据的衔接边界水位线对齐存量数据和增量数据有一条天然的边界线迁移任务启动时间点之前的数据算存量之后产生的算增量。但实际执行中全量迁移要跑十几个小时这期间增量消息一直在产生。如果不做衔接两边数据会产生重叠或空洞。我们的做法是记录两个水位线全量抽取启动那一刻的对账单最大主键ID记为A增量消费者记录已消费的最新消息时间戳记为B。全量迁移执行过程中增量任务照常消费消息。全量结束后再从主键ID A的位置往后扫一次查漏补缺把全量迁移期间边抽边增的数据重新同步一次。这一步我们称之为增量追平是全量增量方案的必然组成不能省略。4. 影子校验与灰度切换如何证明数据真的一致数据迁移最怕的不是迁移失败而是迁移看似成功但数据对不上。所以我们把校验环节提到了和迁移本身同等重要的位置设计了四层校验机制。现在复盘来看这套校验体系是数据最终一致性的最大保障。4.1 第一层校验分片级的记录数比对每个分片迁移完成后迁移worker会顺手执行一次count比对——在老库执行SELECT COUNT(*) WHERE id BETWEEN x AND y在新库执行同样的查询对比记录数。不一致就直接标记该分片失败触发重跑。这个校验成本很低但能挡掉大部分明显问题比如丢片、重复导入、分片边界错位。只校验数量不校验内容所以它只是第一层防线。4.2 第二层校验抽样字段级的摘要比对数量一致不代表内容一致。我们设计了抽样校验任务对每个分片随机抽取5%的记录对关键字段做拼接后计算MD5摘要老库和新库分别计算、逐一比对。这里的关键字段覆盖了金额、用户ID、交易时间、状态、对手方账号也就是说抽取到的任何一条记录只要有任何字段不一致摘要就会不同任务立即告警。抽样比例可以根据数据量动态调数据量越多抽样比例可以适当降低但不能低于1%。我们最终是按1万条抽500条的规则执行单分片校验耗时基本在几百毫秒到一秒之间全量校验跑一轮大约需要40分钟。4.3 第三层校验业务维度的汇总对账技术层面的比对只能证明每个字段都一样但业务上还关心汇总对不对——比如某一天的交易总额、某个月的对账总笔数。我们在校验模块里加了一个自定义规则引擎支持按时间维度、金额维度、商户维度配置汇总SQL。老库跑一条汇总新库跑同样汇总比对结果是否一致。其中最有价值的是按日汇总校验把每天的交易笔数和交易金额作为一组对照指标一旦某天数据不一致可以精准定位到是迁移导致的历史数据问题还是双写引入的增量数据问题。这比直接把全量数据重新比对一遍的效率高得多。4.4 灰度切换让真实流量来验证最后一公里校验做得再充分也抵不过一次真实环境的配置错误。所以我们没有做一次性全量切换而是设计了灰度切换流程第一步切1%的只读流量到新系统观察日志、监控、业务指标重点看查询耗时和异常率。 第二步切10%的只读流量同时开启新库到老库的数据反向同步防止用户操作新系统后数据分流。 第三步切50%流量观察一段时间稳定后直接切100%。 最后新系统全量运行72小时后无异常才真正关闭迁移工具的增量消费老库降级为只读归档。灰度切换的节奏可以根据业务风险调整但原则是每次切换都必须有明确的可观测指标和回滚触发条件。我们当时定的回滚阈值是新系统查询P99超过200ms或系统异常率超过0.1%立即把流量切回老系统。因为有自动回滚预案整个灰度过程执行的还比较顺利没有触发回滚。5. 数据迁移过程中的常见事故与排查思路再完美的方案实际执行时也会遇到意外。这一节写几个我们真实验证过的坑每一个都是靠日志和排查链路一点点定位出来的。5.1 字符集不一致导致的乱码居然最后才被发现老库的字符集是latin1业务写入时用UTF-8编码字符串导致老库里存的数据本身就有编码问题。新库统一用UTF8MB4迁移工具按字节流搬过去后部分历史数据出现乱码。抽样校验时MD5对不上排查了很久才定位到这个原因。处理方式是在清洗链里加了一个编码修复规则对疑似乱码的字符串尝试按latin1解码后重新按UTF-8编码。这里有个风险部分latin1数据本身是正确的如果强制转换反而会破坏内容。所以我们只对校验出异常痕迹的字段做修复并且修复前先记录原始值方便回溯。5.2 分片键选择不当导致新库数据倾斜新系统的分片键是用户ID理论上比较均匀但实际数据中头部用户贡献了大部分交易按用户ID分片会出现明显的局部倾斜。最极端的两个用户单用户流水超过100万条所在的物理分片数据量是平均分片的5倍以上。解决思路是二次分片用户ID分片为主分片键再叠加一个日期字段作为二级分片条件。这样单用户的大批量流水也能均匀分布到多个物理表上。这个调整对迁移工具的影响不大但需要重新设计查询路由规则属于迁移过程中顺带发现、顺带解决的架构优化。5.3 迁移工具本身成为新的瓶颈迁移工具跑起来之后我们发现工具所在的应用服务器的内存占用持续攀升GC频繁。定位后发现是因为批量插入新库时把每条数据先做了ORM对象化2.3亿条数据全部转化成了Java对象堆积在内存里有不小的内存压力。优化方案是改成流式处理逐条读取老库的ResultSet处理完后直接写入新库不再缓存整批数据。分片内数据在内存中只保留当前正在处理的几条记录。这一改整个迁移工具的内存占用从峰值7GB降到了1.5GB左右稳定性明显提升。6. 亿级数据迁移的必备实操清单从评估到上线的完整流程前面讲了很多设计细节这一节我把整个迁移项目的执行流程整理成一份可以直接参考的清单。哪怕你的项目规模不到亿级这套流程的骨架也是通用的不同之处只在于分片大小和并发度参数。6.1 迁移前的评估与准备阶段盘点数据资产列出所有需要迁移的表、记录数、存储空间、增长速率明确哪些是静态、动态、归档数据。梳理依赖关系找出所有读取老数据的上下游系统逐个确认数据源切换方案。制定字段映射字典把老库表字段与新库表字段一一对应标注类型转换规则和清洗规则这个字典后续要进配置中心不能只存在于文档里。确定迁移窗口和容忍度和业务方确认停机时间窗口范围、数据延迟容忍度、数据不一致容忍度这些参数直接决定了方案选型。6.2 迁移工具开发与自测阶段开发分片抽取模块内置断点续传和水位线保护能力。开发清洗转换链支持配置化规则预留编码修复、类型转换、默认值填充等常用组件。开发增量同步消费组保证幂等消费和延迟监控。自测策略先在测试环境造一个200万条的镜像库完整跑一遍迁移流程重点验证断点续传、幂等写入、校验规则三个环节。6.3 全量迁移与校验阶段分批执行全量迁移任务观察老库负载、迁移速度、失败率三项核心指标。存量迁移完成后立即执行增量追平任务处理迁移期间的新增数据。依次跑四层校验记录数比对、抽样摘要比对、业务汇总对账、真实流量灰度校验。校验发现问题时通过失败分片定位到具体数据修复后单独重跑该分片不需要全量重来。6.4 切换与保障阶段按灰度策略逐步切换流量每步都观察业务指标和日志异常时自动回滚。切换完成后保留老库只读状态至少一个月并确保新库到老库的反向同步链路在切换初期持续运行。安排专人值守重点盯增量同步延迟、新库写入性能、下游系统数据源切换后的取数正确性。7. 迁移工具该自研还是买现成的很多团队第一次做迁移时会纠结这个问题。我的观点很直接亿级数据规模下没有完全贴合的现成产品与其去适配别人的设计不如自研一个最小可用版本然后按需迭代。这次项目里的迁移工具核心逻辑不到2000行代码但每个细节都是针对目标系统定制的这是商业工具给不了的价值。自研迁移工具的最低配置是分片任务调度器 转换链组件 幂等写入模块 校验模块。这四个组件加起来一个熟练的后端开发大概需要三周时间完成初版。如果你团队连这个成本都压不出来那说明你的数据迁移需求本身还不够复杂用现成的ETL工具做全量同步加脚本做增量同步也能兜住大部分场景。反向补充一句自研迁移工具千万不能一开始就追求大而全逐字段的翻译器、动态限流、自动回滚这些能力完全可以等第一轮迁移跑出结果后按需增加。把核心链路先跑通远比一开始就设计一个完美系统重要。