经常延迟的库存同步一般只是把轮询周期拆短,轮询方案做多少个月都有天花板,而事件驱动设计能让同步延迟降到秒级。核心思路是源端数据变化后立即把订单变化事件放进消息队列,消费端根据变更来自驱动库存更新,再用事件ID做幂等来控制重复消息。这个方案已经在多个电商项目中验证,资源开销比高频轮询低得多。
轮询同步像坐等闹钟:定时任务每隔固定秒数去查数据库,找到新增或修改的订单记录后再给库存模块签核。这个固定周期就是延迟的下限,比如5分钟跑一次的任务,最差要等4分59秒才知道了新订单产生,用户早已下完单,页面库存超过真实库存的时间自然很难看。
轮询带来的第二个缺陷是查库无规律。周期短了,数据库中每次的都是连续扫描,慢查询直推源库负载;周期长了,延迟又变大。它还要考虑多条记录在分批处理中失败,要么依赖人工补跑,要么写一堆增加重试逻辑,这里同步复杂度吸取成本明显过高。
事件驱动方案的核心是状态变化本身成为一条消息,数据库不需要被反复扫描,订单创建的瞬间让CDC工具截取变更日志,再发送到完成事件。
最正统做法:MySQL数据库开启binlog,用Debezium或Flink CDC读取binlog变化,生成包含sku_id、扣减数量这些字段的InventoryChangeEvent,发送到Kafka。库存模块订阅这个Topic,消费到事件后Jedominant地对Redis和数据库执行扣减。下面这只消费模型代码已移位业务的面向组合,不需要逐个查询订单表。
@Service
public class StockEventConsumer {
@KafkaListener(topics = "inventory-change", groupId = "stock-apiserver")
public void onMessage(InventoryChangeEvent event) {
// 消息已带全部数据,不反查订单库
stockService.changeStock(
event.getSku(),
event.getQuantity(),
event.getEventId()
);
}
}从轮询到事件驱动,表面看只是消费方式变了,但它绕过了拉取链路和扫描开销,同步不等周期,消息一落盘就可被消费。Martin Kleppmann在《Designing Data-Intensive Applications》里也提到,把数据变化当作事件流处理,比定时批量轮询更容易保持不同系统之间的状态最终一致。
Kafka和RabbitMQ这类消息系统只要说不保证消息只返回一次,重复消息就是消费端必须处理的常态,所以幂等设计实现是关键。做到每次收到同一事件后库存结果一致,不做重复扣除。
最简单实现:在库存分录表里用event_id做唯一键,消费时先进行INSERT,冲突后代码返回,这样让重复消息自然闲置,在同一个数据库事务中完成记录明天的库存。相似的错配代码可以用一条SQL。
INSERT INTO stock_journal (event_id, sku_id, delta) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id = id; -- 受影响行数为0时说明之前执行过
如果项目使用Redis预先扣减,就常用SET NX命令指定event_id为key,中重建的幂等表也可以。只要重复消费不会让数据变化两次,库存同步的最终一致性才有办法。
要衡量轮询和事件驱动到底各自适合怎样的场景,可以把每项重要的维度列出对比。
| 对比维度 | 定时轮询 | 事件驱动同步 |
|---|---|---|
| 端到端延迟 | = 轮询周期1~5分钟 | 秒级(网络传输为主) |
| 源库存压力 | 每轮全量接口扫描 | 需要开启binlog方式监听 |
| 失败补偿 | 靠下一轮重新恢复 | 消息重试或死信队列 |
| 数据重复 | 反正常周期全量,不冲突 | 重复来自消息提高性能,必须幂等 |
| 实现复杂度 | 代码编写简单,调优困难 | 组件较多,起点成本高 |
全场景看来,库存准确度敏感的电商项目,秒级同步必须是事件驱动。如果业务允许分钟级延迟,并且对同步链路没有重复、金额敏感,那么轮询也能继续用,但必须接受最坏周期内的差异。
事件驱动的消息链路仍然需要处理崩溃,用消息队列的手动确认模式可以避免消息消失。比如使用RabbitMQ接口,消费者处理完事件并且落库成功后再发送ack给broker,另外消费者执行库失败时不要提交,Broker会重新投递。
继续配合一个补偿表:服务从Kafka拿到事件时先写一条记录标记pending,扣库存完成改success,扣减失败记failed。线上监控每隔5分钟扫一次,如果始终过不去就当失败,通过备用库存补单。这条链路相当于给事件加上一个“有状态”的持久层,保证库存更新不会落在半路上。
上线切量时,还需要一个库存校验定时任务来当安全网,每分钟比较源库存和库存的差异记录,相差超过阈值就告警。注意这里并不是专数据同步,只是监控覆盖,不要让它成为新的轮询点,减少扫描频率和扫描范围,比如只取近期变更的SKU。
事实上,延迟从分钟秒到提升的关键变化,不只是把DSL从“跑一遍任务”换成“监听一个事件”。消息命中后的消费性能也一定要保证,Flow势时消费镜像会拉长延迟。只要设置合理的消费并发和批量策略,再加上binlog变更下游的几毫秒路由,库存数据就能在绝大多数情况下做到秒级对齐。
把库存同步改成事件驱动,最直接的原因不是用MySQL binlog的数量,而是你能对传统系统数据变化建立一条足够的、可监控的时序线。这种思路对你Oracle/Redis里的缓存库存同样可行,值得你在下一次业务重构时用上。
评论
加载中...