目录
事务消息的引出
以购物场景为例,张三购买物品,账户扣款 100 元的同时,需要保证在下游的会员服务中给该账户增加 100 积分。而扣款的业务和增加积分的业务是在两个不同的应用,正常处理逻辑一般是先扣除100元,然后网络通知积分服务增加100积分。
以上过程会存在3个问题:
-
账号服务在扣款的时候宕机了,这时候可能扣款成功,也可能扣款失败;
-
由于网络稳定性无法保证,通知扣积分服务可能失败,但是扣款成功了;
-
扣款成功,并且通知成功,但是增加积分的时候失败了。
实际上,rocketmq的事务消息解决的是问题1和问题2这种场景,也就是解决本地事务执行与消息发送的原子性问题。即解决Producer执行业务逻辑成功之后投递消息可能失败的场景。
而对于问题3这种场景,rocketmq提供了消费失败重试的机制。但是如果消费重试依然失败怎么办?rocketmq本身并没有提供解决这种问题的办法,例如如果加积分失败了,则需要回滚事务,实际上增加了业务复杂度,而官方给予的建议是:人工解决。RocketMQ目前暂时没有解决这个问题的原因是:在设计实现消息系统时,我们需要衡量是否值得花这么大的代价来解决这样一个出现概率非常小的问题。
事务消息的实现思路和过程
RocketMQ 事务消息的设计流程同样借鉴了两阶段提交理论,通过在执行本地事务前后发送两条消息来保证本地事务与发送消息的原子性,过程如下图:
事务消息详细过程说明
- Producer发送Half(prepare)消息到broker;
- half消息发送成功之后执行本地事务;
- (由用户实现)本地事务执行如果成功则返回commit,如果执行失败则返回roll_back。
- Producer发送确认消息到broker(也就是将步骤3执行的结果发送给broker),这里可能broker未收到确认消息,下面分两种情况分析:
- 如果broker收到了确认消息:
如果收到的结果是commit,则broker视为整个事务过程执行成功,将消息下发给Conusmer端消费;
如果收到的结果是rollback,则broker视为本地事务执行失败,broker删除Half消息,不下发给consumer。
- 如果broker未收到了确认消息:
broker定时回查本地事务的执行结果;
(由用户实现)如果本地事务已经执行则返回commit;如果未执行,则返回rollback;
Producer端回查的结果发送给broker;
broker接收到的如果是commit,则broker视为整个事务过程执行成功,将消息下发给Conusmer端消费;如果是rollback,则broker视为本地事务执行失败,broker删除Half消息,不下发给consumer。如果broker未接收到回查的结果(或者查到的是unknow),则broker会定时进行重复回查,以确保查到最终的事务结果。
补充:对于过程3,如果执行本地事务突然宕机了(相当本地事务执行结果返回unknow),则和broker未收到确认消息的情况一样处理。
事务消息的使用
关于rocketmq事务消息如何使用,最好的学习思路是从github上下载下源码,参考demo示例。这里也以官方的demo讲解如何使用(在demo基础上做了一点修改)。
代码示例
为了模拟事务执行的异常场景,这里会模拟发送5条事务消息,前三条(msg-1、msg-2、msg-3)对应的本地事务执行结果为unknow(模拟本地事务执行未知的情况);
第4条消息(msg-4)返回COMMIT_MESSAGE(模拟本地事务执行成功的情况),第5条消息(msg-5)返回ROLLBACK_MESSAGE(模拟本地事务执行失败的情况);
对于前三条消息,模拟回查到的本地事务处理结果分别为UNKNOW,COMMIT_MESSAGE,ROLLBACK_MESSAGE。
- 发送事务的逻辑:
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
//事务执行的listener,由用户实现及接口