在拆分成多个微服务的系统架构中,经常面临这样一个场景:服务在更新本地数据库之后,需要发布一条异步事件通知其他服务进行后续处理。比如提交了一份答卷,数据库记录状态变更为已提交,紧接着需要通过消息队列触发报告生成服务。这两个操作分属不同的资源管理器,如果数据库更新成功但消息发送失败,或者相反,就会导致微服务间的数据处于不一致状态。
常见的错误做法是直接在本地事务中调用消息发送逻辑。因为网络 I/O 是不可靠的,如果消息发送超时,本地事务面临回滚与否的困境;即使发送成功,若本地事务在最后的 Commit 阶段失败,已经发出的消息也无法撤回。
为了解决这一问题,事务发件箱模式(Transactional Outbox)被广泛应用。其核心思想是将消息作为一条记录与业务数据保存在同一个数据库实例中,并利用本地数据库的事务保证其原子性。具体而言,业务操作在向业务表插入数据的同时,也向一张单独的 Event 表插入一条待发送的消息记录。两者在同一个事务中提交。随后,一个独立的后台进程(如基于定时任务或 Binlog 监听如 Debezium)不断轮询 Event 表,将未发送的消息投递到消息中间件。一旦投递成功并收到 ACK,再将 Event 表中对应记录标记为已发送或删除。
橙星云技术团队在设计某些需要强一致性的跨服务数据同步时,也考量过这类方案。除了 Outbox 模式,RocketMQ 原生支持的事务消息提供了另一种解法。RocketMQ 采用两阶段提交的思想,先发送 Half 消息,若成功则执行本地事务,最后根据本地事务的执行结果向 Broker 提交 Commit 或 Rollback。Broker 端还设有回查机制,用于处理长时间未决的 Half 消息。
不论是自建发件箱还是依赖特定中间件的事务能力,其本质都是用“至少一次投递”(At-Least-Once)加上消费端的“幂等性校验”,来替代昂贵且难以扩展的分布式事务。这是构建大规模异步系统的基础工程哲学。
