可靠事件通知的核心是本地事务+事件表+消息队列协同实现最终一致性:第一步在发起服务本地事务中写事件表确保操作与记录原子性;第二步异步扫描事件表发消息并更新状态为“已发送”;第三步下游消费消息并幂等处理,通过查重避免重复执行。

Java 中实现分布式系统下的可靠事件通知,核心不是“立刻做完所有事”,而是“确保每件事最终都被正确处理”。这靠的是本地事务 + 事件表 + 消息队列三者协同,不依赖强一致协议,却能达成业务可接受的最终一致性。
为什么不能直接用数据库事务?
单库事务靠锁和日志保障 ACID,但跨服务时:数据库彼此隔离、网络可能中断、服务可能宕机。强行用两阶段提交(2PC)会卡住整个流程,性能差、易阻塞,还存在协调者单点故障风险——它适合银行核心账务,不适合大多数微服务场景。
可靠事件通知的关键三步
以“用户下单后扣库存、发通知”为例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
第一步:在发起服务本地事务中写事件表
订单服务执行下单逻辑的同时,在自己数据库里插入一条记录:
订单ID=123,事件类型=“扣库存”,状态=“待发送”,时间戳=now()。
这条记录和订单数据一起提交——要么都成功,要么都失败。它是后续一切操作的唯一事实来源。 - 第二步:异步读取事件表,发消息到 MQ 启一个独立线程或定时任务(比如每秒扫一次),查出所有 status = '待发送' 的事件; 调用 Kafka/RocketMQ 发送消息,并在发送成功后,把该事件状态更新为 “已发送”(这个更新也必须在本地事务内完成)。
- 第三步:下游服务消费消息并幂等处理 库存服务监听消息,收到后执行扣减逻辑; 扣成功就更新自己库存,再记录一条“已处理订单123”的日志(或写入处理表); 下次再收到同一条消息(因重试或重复投递),先查是否已处理过——是则直接忽略,保证不重复扣减。
怎么应对常见异常?
这套机制天然抗错:
- 消息发送失败?→ 事件表里还是“待发送”,下次扫描继续发;
- MQ 宕机或消息丢失?→ 事件表有记录,重试即可;
- 消息重复投递?→ 靠下游服务的幂等设计(如唯一业务键 + 状态校验)兜底。
代码层面要盯住的细节
不是加个 @Transactional 就完事,关键在边界控制:
- 事件表必须和主业务表在同一个数据库、同一个本地事务中操作;
- 消息发送不能放在业务方法里同步调用(否则事务一回滚,消息却发出去了);
- 状态更新(如“待发送”→“已发送”)必须紧跟在消息发送成功之后,且在同一事务;
- 消费端必须实现幂等:推荐用“业务主键+操作类型”作为唯一索引,INSERT IGNORE 或 ON DUPLICATE KEY UPDATE。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










