记得上次面试官问了我一个问题:
面试官:你说你们项目用到了MQ,那么你往MQ发消息是在你业务事务提交之前还是之后呢?
我:……
那接下来分析一下这个问题。
场景复现
比如有个抢购,用户服务点击抢购,订单服务先返回排队中,订单服务处理完了之后肯定是通过MQ异步通知去支付的。现在的问题是,发MQ告诉用户抢去付款这个操作是在订单相关操作(比如扣库存,订单入库等)的事务提交之前还是之后呢?如果是之前,那如果事务回滚了就会出现用户付了钱但是订单没入库的情况;如果是之后,那就可能会出现订单入库了但是没通知用户去付款的情况。
简言之,就是要让数据库操作和发送MQ是在同一个事务内!
事务消息
可能有人想到了,这不就是事务消息嘛!没错,不过不同的MQ事务消息也有所不同。
kafka事务消息
kafka事务类似数据库事务,就是一条消息要发往多个分区的时候,它可以保证发往的这多个分区同时成功或者失败,这种事务显然不能解决上面的问题。
RocketMQ事务消息
- 流程:它是两阶段提交事务,可以很好地解决上面的问题。一阶段先发送一条half消息到MQ Server,此时这条消息对消费者是不可见的;接着执行业务逻辑;二阶段根据业务逻辑的执行结果,判断MQ的事务是提交还是回滚,如果提交,那么这条消息就可以被消费者消费了。
- 补偿措施:如果根据业务逻辑对MQ事务执行提交或者回滚时因为超时等原因失败了,MQ Server会回调业务端的接口,通过这个接口去查询刚才的业务到底成功了没有,根据查询结果再决定MQ的事务要提交还是回滚。这个回调接口是需要我们自己去实现的。
其他方案
- 新建一个表用来保存生产者生产的消息;
- 在执行业务逻辑的方法里,不直接把消息发往MQ,而是先入库;
- 这样可以保证这两个入库操作是同一个数据库事务;
- 最后通过定时任务去查询库中的消息,发往MQ,发失败了还可以通过该任务重发
总结
RocketMQ的两阶段提交事务可以解决这个问题,但是每个场景我们都需要写对应的回调接口;先入库再通过定时任务去发消息这种方案可能就会有一点点的延时,即定时任务执行的频率就是消息消费的延时时长,比如你5秒执行一次,那么消息入库后最多就需要5秒钟后才会被查出来去消费。