系统订单模块最近做了一次比较大的改造,核心是把履约数据同步从全量定时任务切换成了增量消息推送。整个过程从梳理现状、方案选型、双写验证到灰度上线,差不多花了一个半月。回头来看,这次改造的技术难度不算高,真正的难点在于想清楚“为什么要改”和“怎么保证改了不出事”。这篇文章把完整过程做个复盘,涉及的方案对比、关键设计、踩坑记录都写清楚,给后面要做类似改造的同学一个参考。
1. 改造背景:为什么全量同步撑不住了
先说项目背景。我们系统的订单履约链路大概是这样的:交易订单创建后,履约系统需要根据订单信息生成履约单,然后拆分出货品、安排仓库发货,中间还牵扯到库存占用、物流回传、售后拦截等一堆环节。履约数据不是简单从订单表读一条记录就完事,它涉及到多个子系统之间的状态协同。
这次改造的对象,是核心服务的订单数据同步模块。
改造之前,订单履约的数据同步采用的是全量同步方案,说白了就是一个定时任务,每隔一段时间把订单表里的数据全量捞一遍,同步给下游系统。这个方案非常直观,代码也简单:写一个Job,查订单表,按更新时间筛选出增量变化的记录,然后调用下游接口推送过去。最开始数据量小的时候,这套方案非常稳定,基本上是零维护成本。
但是随着业务增长,问题逐渐暴露出来了。
订单表的数据量到了千万级别之后,全量扫描的代价变高了。虽然我们用了更新时间索引,但每次扫描涉及的数据量依然很大,数据库压力直线上升。最难受的是大促期间,订单量暴增,订单状态频繁流转,同一张订单可能在几分钟内被多次修改,全量同步根本追不上更新的节奏。下游系统拿到的数据经常是滞后的,有时候用户都收到货了,商详页还显示“待发货”,客服那边被问询投诉搞得很被动。
具体的痛点我列一下,应该是很多做订单系统的同学都有共鸣的:
- 核心数据库连接池被定时任务长期占满,高峰时期订单写入出现阻塞,出现过因为同步任务拖垮订单主库的线上事故。
- 同步延迟无法精准控制。定时任务频率降低可以减少数据库压力,但延迟会上升;频率升高延迟下来了,数据库又扛不住,怎么调都不对。
- 全量扫描出来的数据不包含操作类型,下游系统每次拿到数据只能全量覆盖本地缓存,不管订单是不是真的发生了变化,白白浪费了大量计算和IO。
- 监控靠“上次执行时间”来推断,数据是否同步成功、是否有遗漏,心里没底。
- 下游系统的库存预占、物流下单这些操作依赖实时性,订单状态变了不能马上通知到,用户体验和内部运营效率都受影响。
这次改造的目标很明确:把订单数据同步从“定时全量扫描”变成“实时增量推送”,核心是引入基于Binlog的消息机制,实现订单变更事件的准实时分发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案选型:三个演进方向,为什么最终选了增量消息
既然要改造,第一步就是做方案选型。当时团队内部讨论过三个方向,各有优劣,这里详细展开说说。
2.1 方向一:优化全量同步方案
最小成本的思路,是在原有全量同步的基础上做优化,比如分库分表、分页优化、只同步变化的字段、引入增量标记位等。
这个方案的好处是风险最小,代码改动量少,几乎不需要改变上下游的对接方式。但问题也很明显:这属于治标不治本。全量扫描这个模式本身决定了它的下限——数据量上来之后,数据库的扫描成本就摆在那里,你优化得再好,也躲不过每次都要“捞一遍看看哪些变了”的宿命。而且订单系统的写入高峰期和同步任务执行时间如果重叠,对数据库的性能影响无法根除。
这个方向最终被否掉了,因为我们不想一年之后再被同样的问题困扰。
2.2 方向二:业务双写,改造业务代码主动发消息
第二个方案是业务侧主动发送消息:在订单创建、状态变更等业务操作时,除了写数据库,同时往消息队列里发送一条变更消息,下游系统消费消息来做数据同步。
这个方案的好处是实时性非常好,消息语义清晰,业务上能精确知道“这张订单发生了什么变化”。而且实现起来不算复杂,就是在一个事务里写库+发消息。
但为什么没有最终选择它?有两点顾虑:
第一,侵入性太强。订单系统的核心业务代码非常复杂,下单、支付回调、发货、签收、退款、售后,每个环节都要插入消息发送逻辑,改动面覆盖了几乎所有核心链路。任何一个分支漏发了消息,数据同步就断了,而且很难排查。
第二,消息发送和数据库写入不是一个原子操作。如果先写库再发消息,消息发送失败怎么办?如果先发消息再写库,消费者到了消息但数据库还没提交,读到脏数据怎么办?虽然可以用事务消息来解决一部分问题,但事务消息对消息队列的要求也比较高,当时的消息中间件版本对事务消息的支持并不完善。
2.3 方向三:基于Binlog的增量消息方案
第三个方向,就是这次最终选择的方案——基于数据库Binlog解析的增量消息。核心思路是:在数据库层面开启Binlog,然后通过一个中间组件监听Binlog事件,解析出数据变更记录,转发到消息队列,下游消费消息完成数据同步。
这个方案的优势很突出:
- 对业务代码零侵入。不修改任何业务逻辑,直接从数据库层面拿数据变更,不用担心漏发、错发。
- 数据时效性极好。Binlog是数据库的真实操作日志,数据一提交就能通过Binlog感知到,理论上秒级延迟。
- 语义丰富。Binlog里天然包含了INSERT、UPDATE、DELETE事件,下游可以精确知道每条订单发生了什么操作,实现真正的增量更新。
- 解耦彻底。业务系统完全不知道有同步这回事,下游系统的订阅变更不影响订单主流程。
缺点也有:需要额外维护一套Binlog采集组件,对运维和监控提出了更高要求。而且Binlog是物理日志,里面包含的是字段级别的变更,需要结合业务语义做解析和过滤。
方向三还有个隐藏的好处:除了订单数据,未来如果有其他核心表需要做实时同步,这套基础能力可以直接复用。作为基础设施投入,性价比是三个方案里最高的。
最终我们定了方案三。现
