ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

数据迁移不踩坑:双写方案+Binlog增量同步的平滑迁移实战解析

数据迁移不踩坑:双写方案+Binlog增量同步的平滑迁移实战解析 数据迁移这事儿越是在生产环境摸爬滚打得久越会明白“平滑”两个字的分量。很多系统跑着跑着就得换库、换机、换云但业务不能停数据不能丢用户无感知领导只看结果。我之前接手过一个单节点 K8s 上若依微服务整套环境的迁移要求是准不停服、不丢数据迁到云上 ECS迁移完还要用 JMeter 压测验证承载能力。当时选来选去最后落地的一套核心方案就是双写方案。这篇文章就把这套思路、设计、坑点和实操过程完整拆出来希望能给准备做数据迁移的朋友一些实在的参考。1. 为什么迁移一定会遇到“双写”这道坎1.1 停机迁数看起来简单为什么没人敢用很多人第一次接触迁移脑子里冒出来的方案很直接找个维护窗口停服导出数据导入新库校验切换。这套流程在小数据量、可接受停服的场景下确实是标准做法但放到真实的业务系统里问题立刻冒出来。停服时间怎么算假设你有 200GB 数据导出要 20 分钟传输到云上要 40 分钟导入要 1 小时校验加切换要 20 分钟整体就是近 2.5 小时的业务中断。对于内部系统可能咬咬牙能接受但对外提供服务、或者有 SLA 承诺的系统这种窗口基本批不下来。而且数据量越大停机时间越长风险越高——一旦导入过程中报错、主键冲突、字符集乱掉时间还会继续往后拖。更麻烦的是回退。停机迁移如果切过去之后发现新环境有问题想回退就得把数据再倒回来又是一轮全量拷贝。这个过程中业务仍然处于中断状态问题被越放越大。所以凡是要求“准不停服、不丢数据”的迁移单纯靠停机搬运是走不通的。必须换思路让数据在迁移期间持续流动而不是一次性地“搬完”。双写方案就是在这种背景下成为主流选择的。1.2 双写方案的本质把“一次性搬迁”变成“持续同步”先给第一次接触这个概念的朋友说清楚双写就是在过渡期内让写操作同时落到旧存储和新存储上。旧库继续承担线上读写新库在接收这些同步过来的数据的同时不断追平增量最终两边数据达到一致状态再找一个低峰期把流量正式切过去。用搬家来打比方传统停机迁移等于先把老房子封了把所有家具一股脑搬过去再开门迎客双写方案则像是在新房子先开一个“分店”两边同时营业老店稳定运行新店持续补货等货架全部补齐、店员也熟练了才正式把客人引导到新店老店再慢慢退租。这个思路解决了三个核心诉求。第一是准不停服。整个迁移过程中业务流量始终打在旧环境上新环境只是在后台默默接收和追平数据客户完全无感知唯一需要留意的是切换瞬间的连接转移。第二是数据不丢。双写机制配合增量同步只要链路没断、对账机制在跑理论上任意时刻的数据都能在新库找到对应版本。即使中途出现异常旧库还在服务线上数据源头始终没丢。第三是可回退。双写状态下系统随时可以放弃新环境、继续用旧环境回退成本极低不需要反向导数据。这比停机迁移的“豪赌”要安全得多。所以双写方案并不是什么花哨的技术它的核心价值是用一段时间的“重复写”换来了迁移过程中的可控、可验、可回退。2. 双写方案的整体设计与架构拆解2.1 双写链路怎么搭业务代码双写、Binlog同步、日志回放双写方案听起来简单但“怎么写”才是关键。我在实际项目里梳理过三种主流做法各有适用场景这里详细说一下。第一种是业务代码双写。在应用层把写操作同时分发到旧库和新库比如 Service 层先写旧库再异步或同步写一份到新库。优点是链路短、意图明确、不依赖中间件哪个表写了什么逻辑自己心里有数。缺点是侵入性强所有涉及写入的代码都要动一遍漏一个接口就可能导致数据缺漏。而且双写代码是临时的迁移完成之后还要再清一遍等于写两遍代码。第二种是基于 Binlog 的增量同步。放开手让业务代码只写旧库通过 Canal、DTS、DRS 这类工具监听数据库的 Binlog 变更把增量的 Insert/Update/Delete 解析出来再回放到新库。这个方案的优点是对业务代码零侵入不需要为了实现迁移而大改应用缺点是需要额外维护一套同步链路对 Binlog 格式、位点管理、回放顺序有比较高的要求而且如果业务里大量使用存储过程、触发器、跨库事务解析回放很容易出幺蛾子。第三种是日志回放。把数据库的操作日志导出后在目标库重新执行本质上跟 Binlog 同步类似只是时效性更差通常用于离线补数不适合做实时双写。我在若依这套环境上最终用的是“业务单写旧库 Binlog 增量回放新库”的组合。原因是若依这套微服务虽然拆了模块但底层数据库还是 MySQL 主库单点用 Binlog 同步可以把侵入降到最低同时业务代码不用因为一次迁移而大动干戈同步链路本身也比较好监控。2.2 三个关键设计决策顺序、幂等与对账双写方案能不能成真正的考验不在链路搭起来而在细节设计。我总结了三个必须在一开始就想清楚的点。第一个是顺序问题。数据库 Binlog 里同一个事务内的变更是有序的但多线程回放时如果并发执行就可能出现顺序错乱。比如一个订单记录先 Insert 后 Update如果 Update 先被回放新库中这条记录还不存在就会报错或者丢更新。稳妥的做法是让同步任务支持按主键或按事务分组保证同一个实体的操作串行化。Canal 里面支持按表 hash 到不同线程就是干这个用的如果是 DTS/DRS 这类云上工具它们内部一般已经处理了事务顺序但自建链路一定要自己注意。第二个是幂等。同步链路一旦抖动大概率会有重试机制而重试就意味着同一条 Binlog 事件可能被回放两次。回放必须是幂等的否则就会出现主键冲突或者重复插入。怎么做到幂等最简单的方式是同步回放用 Insert On Duplicate Key Update或者先查后插但性能会打折扣。更常用的做法是让同步工具基于主键做“存在则更新、不存在则插入”并在设计表结构时确保主键稳定、业务上有唯一键。这一点在迁移前最好把每张表过一遍别等到同步跑起来才发现一堆表没有主键。第三个是对账。双写不是写完就完了必须有一套机制定期校验新旧库的数据一致性。可以每天跑抽样比对也可以在关键业务表上做全量 checksum。我们当时是写了一套对账脚本用时间字段和 mod 方式抽样对比行数和关键字段的哈希值发现差异立刻告警。对账是双写方案安全感的来源——数据是否真的没丢不是靠“感觉”而是靠“比对结果”。2.3 工具选型自建同步还是用云上产品同步链路这块自建和用云产品各有取舍。自建 Canal 的方式灵活、可控、不花钱但需要自己部署 Canal、Kafka可选、消费回放程序还要处理位点管理、监控告警、版本升级。听起来不算复杂但真正跑起来Binlog 积压、连接数打满、回放线程卡死之类的问题层出不穷对团队运维能力是个不小的考验。用云上数据传输产品则省心很多阿里云 DTS、华为云 DRS 都是成熟方案支持全量迁移加增量同步界面化配置自带监控和告警还能做数据校验。我当时的目标环境是阿里云 ECS所以优先考虑 DTS它会自动处理全量增量的衔接增量同步还能显示延迟秒数非常直观。我的建议是如果团队没有专门的 DBA或者迁移时间窗口紧张优先用云产品如果是长期有多环境同步需求且有精力维护再考虑自建 Canal。不要为了炫技去自建迁移这件事稳定压倒一切。3. 从零落地一次双写迁移实操全流程3.1 第一步盘点数据边界画清数据地图开工之前别急着配同步工具先把家底盘清楚。我做的第一件事是把数据库里所有表列出来逐张确认主键类型、是否有自增、是否有外键、是否软删除、改动频率高不高。之所以要做这个动作是因为 Binlog 同步对表结构非常敏感主键缺失、外键依赖、无时间字段等情况都会影响后面的同步和校验策略。若依系统本身表不少业务表、工作流表、定时任务表都有。当时我们的处理原则是核心业务表必须严格同步和校验日志类、临时数据类表可以放宽要求有些纯缓存性的表甚至不需要迁。分清主次能节省大量对账和排查时间。画数据地图还有一个好处它能帮助评估同步链路的压力。哪些表是热表每秒写入多少次Binlog 增长量多大都决定了同步任务的并发和规格配置。这一步做得越细后面的坑越少。3.2 第二步全量数据导入保证基准一致双写启动前新库必须有一份和旧库在同一时间点的基准数据。怎么拿到这份基准可以用 DTS/DRS 的全量迁移功能也可以用 mysqldump 导出再导入但要注意一致性。最稳妥的方式是利用数据库的一致性快照。以 MySQL 为例在低峰期做一次 FLUSH TABLES WITH READ LOCK 配合 START TRANSACTION WITH CONSISTENT SNAPSHOT或者用 mysqldump --single-transaction --master-data2 导出一致性快照同时记录当前 Binlog 位点。导出完成之后把这个位点作为增量同步的起点。这样全量和增量之间就不会出现缝隙或重叠。导入完成后要做三件事校验基准数据行数比对、关键字段抽样比对、自增序列核对。行数比对比如SELECT COUNT(*) FROM old_db.orders; SELECT COUNT(*) FROM new_db.orders;关键字段抽样比对可以算字段哈希SELECT SUM(CRC32(CONCAT(id, order_no, amount, status))) FROM old_db.orders; SELECT SUM(CRC32(CONCAT(id, order_no, amount, status))) FROM new_db.orders;两边哈希一致基本可判断这批数据是一致的。自增序列核对也别忘了否则后续插入新数据可能因为自增主键冲突或越过间隙导致问题。3.3 第三步开启增量同步等待追平基准数据校验通过后就可以配置增量同步任务了。用 DTS 的话界面里填好源库和目标库连接信息指定同步类型为增量同步它会自动从全量迁移完成的位点开始消费 Binlog。如果自建 Canal则需要手动记录起始位点然后启动 Canal 和回放程序。增量同步开启后新库开始持续接收旧库的变更。这时要盯两个指标同步延迟和同步状态。正常情况延迟在秒级甚至毫秒级如果延迟持续上涨说明回放能力跟不上写入速度需要扩容同步任务的并发或拆分同步链路。什么算“追平”不是延迟变成 0 就立刻切换稳妥做法是让同步任务在零延迟状态下稳定运行一段时间比如持续 15 到 30 分钟同时业务高峰期也扛过去确认增量链路稳定后再考虑切流。我们当时特意等了一个业务高峰周期确保写入压力最大的时段同步也没掉链子。3.4 第四步校验、灰度切换与快速回退追平之后是不是可以大刀阔斧切换了别急还要做两轮校验。第一轮是全量对账把每一张核心表都拉出来比对看行数、关键字段哈希第二轮是业务完整性验证在新环境上跑一遍核心功能的只读接口确认数据能查得到、页面能打得开、依赖的中间件都正常。校验通过后切换策略上我强烈建议分阶段走先切读流量再切写流量最后再下线旧环境。切读流量是把业务的查询请求通过网关逐步导向新库此时写操作仍然打旧库靠同步链路把增量继续传到新库。观察一段时间确认新库在真实读压力下性能正常、数据无差异再进入下一步。切写流量是把应用的写库连接切换到新库同时保持旧库回写或者直接停掉对旧库的写入。这一步是整个迁移的关键节点切换完成后要立刻检查新库的写入是否正常、延迟是否归零、应用有没有报错。最后下线旧环境。为了安全旧库别急着销毁至少保留一段时间确认新环境稳定运行后再清理。整个过程中一旦发现切换后出现严重问题回退方式很简单把应用连接改回旧库同时停掉往新库的同步任务即可。因为旧库在迁移期间一直是完整的数据源回退不会丢数据。这就是双写方案最大的底气。4. 不止是数据整套环境迁移与压测验证4.1 双写跑通后K8s 环境怎么一起迁数据只是迁移的一部分特别是原始需求里提到的“单节点 K8s 上的若依微服务整套环境迁到阿里云 ECS”应用的搬迁同样需要安排。我当时把应用迁移拆成两步走。第一步是先在新环境的 ECS 上把整套微服务部署起来包括 Nacos 注册中心、Redis、MySQL、若依的各个微服务模块还有配套的网关和前端。这个阶段新环境是不接真实流量的只是提前把部署工作做完确保所有组件能正常启动、互相能连通。第二步是在双写同步期间让新环境的服务连上同一个注册中心实际上不行——两个环境不能同时注册到同一个 Nacos否则流量会乱。更稳妥的做法是让新旧环境各有一套独立的 Nacos迁移期间新环境的服务完全不注册到生产注册中心只是配置好数据源指向新库通过直连方式做功能验证。真正接流量时再用网关层做灰度切流比如按 IP、按 Header、按比例逐步把流量导到新环境。所谓“整套环境迁移”本质上要把配置、密钥、定时任务、文件存储路径都逐一迁移否则数据过去了应用却因为缺少某个配置项而启动失败就非常尴尬。4.2 迁移完成后的压测JMeter 脚本怎么用才有说服力迁移完成不等于结束还要证明新环境能扛住真实业务压力。原始的迁移需求里提到由压测人员用配套的 JMeter 脚本做高并发测试验证云上环境的承载能力。这一步千万别走过场压测要想有说服力至少要做到下面几点。第一压测数据要独立。不要直接对着生产新库狂写压测数据否则会产生大量脏数据。最好准备独立的压测账号、影子表或者压测完成之后统一清理。我们当时是单独建了一套压测业务数据用独立的用户体系压完直接清理避免影响真实业务。第二压测场景要贴近真实。JMeter 脚本不能只测一个登录接口至少要把核心链路覆盖进去比如用户登录、查询列表、提交工单、审核流程这类真实业务操作。并发数从低到高逐步加压观察系统在 100、500、1000 并发下的表现记录 TP99、错误率、CPU、内存、数据库连接数等指标。第三重点关注“新环境”的瓶颈点。压测不是简单看扛不扛得住而是要找出薄弱环节。比如我们当时压测发现单节点 K8s 迁移到 ECS 后连接池配置还是沿用原来的一套导致数据库连接数在高峰期被占满应用频繁报获取连接超时。压测把这个问题逼了出来调整连接池参数和数据库最大连接数后系统才稳定下来。压测结果要形成一个明确的结论支撑多少 QPS、平均响应时间多少、错误率多少哪个环节先到瓶颈。这个数据既是迁移验收的依据也是后续容量规划的参考。5. 常见问题与排查技巧实录5.1 同步报主键冲突别忘了检查表结构增量同步跑起来之后我遇到的第一个典型问题就是主键冲突。现象是同步任务报 duplicate key某张表在插入数据时主键已存在。查了半天发现原因是表里一些旧数据本身没有遵循主键唯一约束历史脏数据在主键上存在重复或者自增起始值在新库被重置过导致新插入的 ID 和旧数据撞车。解决办法分两层迁移前清洗脏数据确保主键唯一迁移后把新库的自增起始值调整到旧库当前最大值加 1避免“插入一条新数据正好撞上同步过来的历史数据”的尴尬。5.2 时间字段差 8 小时时区问题永远不要想当然若依这套环境里有大量时间字段用来做创建时间、更新时间、审批时间等。同步完成后一查新库里的时间比旧库晚了 8 个小时。原因很常见源库连接串没有指定 serverTimezone或者新旧数据库实例的时区配置不一致导致 Binlog 里记录的 timestamp 在回放时被按不同时区解析。排查方式是在源库和目标库分别执行SELECT global.time_zone, session.time_zone;确保两边一致同时 JDBC 连接串里明确指定 serverTimezoneAsia/Shanghai。这类问题最隐蔽因为它不影响同步任务的状态只会在对账时暴露。5.3 增量延迟越拉越大多半是回放能力不足有段时间我观察到 DTS 的增量同步延迟从 1 秒慢慢涨到 300 多秒排查后发现源库高峰期有大批量更新操作Binlog 单日增长量比平时翻了好几倍同步任务的规格跑不动了。解决思路是扩容同步链路、拆分表同步把压力大的几张热表单独拆成独立同步任务。如果自建 Canal还可以调大 Canal 的消费线程数、增大批量提交大小但要注意回放目标库的写入压力不能过高必要时分批提交降低锁竞争。持续延迟最危险因为它意味着新库数据追不上旧库一旦在这个状态下切流就会有大量数据没同步过去。所以监控一定要做好延迟超过阈值就告警。5.4 切写后业务报错可能是序列、权限、配置三座大山切写流量是个容易出幺蛾子的环节但问题通常不是数据链路而是三个容易被忽略的点。一个是自增序列。旧库的 orders 表 id 已经到 50000新库由于导入方式问题自增起始值还是 1一切写流量就立刻主键冲突。修复方法是在切写前主动把新库的 AUTO_INCREMENT 改到旧库当前值以上。另一个是权限。应用账号在新库上要有完整的增删改查、事务、索引等权限迁移时如果只授予了 SELECT业务一写就报权限错误。这个在切写前就要用业务账号实际跑一遍写入操作验证。还有一个是配置项。很多系统里有配置中心或者表配置比如若依里的参数配置切换环境后如果配置没有同步过来业务逻辑可能表现异常。迁移时要把这些业务配置也纳入数据迁移范围而不只是关注业务表。5.5 对账不平怎么办先定位范围再查原因对账脚本发现差异时第一反应不是去改数据而是先定位差异范围。用主键区间分段比对把差异缩小到具体某张表、某个主键区间再逐条查看是新库多了、少了还是字段值变了。常见原因无非三类增量同步延迟导致的暂时性差异过一会儿再对可能就一致了同步链路本身丢事件需要检查 Binlog 位点有没有跳变业务系统在新库上被其他任务直接改过数据导致两边分叉。我遇到过最离谱的一次是运维同事为了“提前验证功能”手工改了新库的几条数据结果对账怎么都不平。所以迁移期间一定要管住手新库除了同步任务和必要校验不允许任何手工写入。最后说一个我自己的体会双写方案本质上不是技术难题而是流程和耐心问题。环节虽多但只要把每个环节的校验做到位、延迟盯紧、回退方案备好迁移并不会像想象中那么惊心动魄。希望这篇拆解能帮你在做数据迁移时少走几步弯路。
返回列表