微服务中事务性消息的核心是保证业务与消息的原子性,避免数据不一致。主流方案包括本地消息表和可靠事件模式。本地消息表通过在同库中创建消息表,将消息发送作为本地事务的一部分,确保业务与消息同时提交;事务提交后由后台任务异步投递消息,实现最终一致性。可靠事件模式如rocketmq的事务消息,则利用“半消息”机制,先发送不可见消息,待本地事务执行后再决定提交或回滚,由MQ协调状态,简化开发。对于跨服务长事务,常采用Saga模式,通过事件驱动链式调用,各服务完成本地事务后发布事件,失败时触发补偿操作,需保障幂等性。spring Cloud stream等框架可支持事件处理。总体思路是牺牲强一致性,以异步和补偿换取系统可用性与弹性。技术选型取决于中间件支持与业务复杂度,有事务消息功能优先使用,否则采用本地消息表为可靠兜底方案。
微服务中的事务性消息,核心目标是确保业务操作和消息发送这两个动作的原子性。简单说,就是不能出现“业务数据改了,但消息没发出去”或者“消息发了,但业务失败了”的情况。解决这个问题,主流方法是采用本地消息表或可靠事件模式,利用最终一致性来保证整体正确。
基于本地消息表的方案
这个方法的关键在于把“发送消息”这个动作,也当成一个数据库的本地操作来处理,从而能和业务操作放在同一个数据库事务里。
- 同库同事务:在业务数据库中创建一张专门的消息表。当执行业务逻辑时,比如用户下单,除了插入订单记录,同时在这个消息表里插入一条待发送的消息(例如“订单已创建”)。
- 提交即确认:业务代码用@Transactional注解包裹这两个数据库操作。只要事务成功提交,就代表业务和消息记录都持久化到了数据库,不会丢失。
- 异步投递:事务提交后,启动一个独立的后台任务(可以是定时任务或监听机制),扫描这张消息表,把状态为“待发送”的消息通过MQ(如kafka、rabbitmq)可靠地发布出去,并更新消息状态为“已发送”。
即使应用在发送消息前宕机,重启后扫描任务依然能发现未发送的消息并继续处理,保证了消息最终会被发出。
使用可靠事件模式(事务消息)
一些高级的消息中间件(如RocketMQ)原生支持“事务消息”,简化了上述流程。
- 半消息机制:生产者先向MQ发送一个“半消息”,这个消息对消费者不可见。如果这一步失败,整个流程结束。
- 执行本地事务:MQ收到半消息后,会询问生产者:“你的本地业务执行成功了吗?” 生产者此时去执行数据库操作等业务逻辑。
- 提交或回滚:生产者根据本地事务的执行结果,通知MQ是“提交”还是“回滚”这条消息。如果是提交,MQ才将消息变为对消费者可见;如果是回滚,则删除该消息。
这种方式将协调工作交给了MQ,开发者只需要实现一个回调接口来检查本地事务状态,比手动维护消息表更简洁。
结合Saga模式处理复杂流程
对于跨多个服务的长事务,常采用Saga模式,它本身就是一种基于事件驱动的补偿机制。
- 事件驱动:每个服务完成自己的本地事务后,发布一个领域事件(Domain Event)。
- 链式触发:下一个服务订阅该事件,并开始自己的事务。如果某个环节失败,就发布一个“补偿事件”,触发前面已经成功的服务进行逆向操作(如取消订单、释放库存)。
- 幂等性保障:由于网络可能重试,所有服务的事件处理必须是幂等的,即同一条消息处理一次和多次效果相同。
像spring cloud Stream这样的框架,可以很好地支持事件的发布、订阅和带重试的处理,让这种模式更容易落地。
基本上就这些,核心思路都是放弃强一致性,通过异步和补偿换取系统的可用性和弹性。选哪种方案取决于技术栈和业务复杂度。有现成的事务消息功能就用它,没有的话,本地消息表是最经典可靠的兜底方案。