问题
我们看到在转账业务中,有两步,一个是操作用户A扣钱,一个是操作用户B加钱
如果在同一个数据库中进行,可以保证这两步操作,要么同时成功,要么同时不成功。这样就保证了转账的数据一致性。
但是如果用户A的数据在集群A中,用户B在集群B中呢?因为他们不在同一个事务中;如用户A扣款成功,但用户B加钱失败了;那就坑了,数据不完整了。
类似这种问题在微服务架构会更多,因为各个服务都是独立的模块,都是远程调用,都没法在同一个事务中,都会遇到事务问题。
消息中间件方案
RocketMQ事务方案
RocketMq消息中间件把消息分为两个阶段
Prepared阶段
该阶段主要发一个消息到rocketmq,但该消息只储存在commitlog中,但consumeQueue中不可见,也就是消费端(订阅端)无法看到此消息。
commit/rollback阶段(确认阶段)
该阶段主要是把prepared消息保存到consumeQueue中,即让消费端可以看到此消息,也就是可以消费此消息。
整个流程:
1、在扣款之前,先发送预备消息
2、发送预备消息成功后,执行本地扣款事务
3、扣款成功后,再发送确认消息
4、消息端(加钱业务)可以看到确认消息,消费此消息,进行加钱
异常场景:
异常1:如果发送预备消息失败,下面的流程不会走下去;这个是正常的
异常2:如果发送预备消息成功,但执行本地事务失败;这个也没有问题,因为此预备消息不会被消费端订阅到,消费端不会执行业务。
异常3:如果发送预备消息成功,执行本地事务成功,但发送确认消息失败;RocketMq会定时遍历commitlog中的预备消息,因为预备消息最终肯定会变为commit消息或Rollback消息,所以遍历预备消息去回查本地业务的执行状态,如果发现本地业务没有执行成功就rollBack,如果执行成功就发送commit消息。
异常4:如果发送预备消息成功,执行本地事务成功,发送确认消息成功,消费端失败;失败会一直重试,重试到一定次数不重试了那就发短信通知管理人员手动处理
消费端(加钱业务)需要考虑幂等设计
常用的方案是唯一键判重
参考文章:https://www.jianshu.com/p/286cac4625b6