分布式事务在微服务中是比较常见且又比较棘手的难题,当然,它也并不是无解的,如果熟悉分布式事务的同学,应该知道 XA、2PC/3PC、TCC 、事务消息等解决方案。
事务消息是分布式事务解决方案的一种,也是我们今天要讨论的主题。
什么是事务消息?
事务消息的目标,是为了实现可靠性消息最终一致性。
这个又是怎么理解呢?举个例子可能大家就比较容易理解。
还是我们比较常见的支付场景:用户支付下单,修改订单状态为完成,然后会通过 MQ 发送订单消息。
下游服务比如积分系统更新用户的积分、物流系统需要更新订单的物流信息、推送系统需要给用户推送消息或者广告等。
图片
可以说这算是一个比较标准的微服务架构,根据业务领域划分不同业务系统,各业务系统通过 MQ 消息来实现订单业务的解耦,流程上看似没什么问题。
但我们仔细看一下,在订单系统订单更新和发送消息的这个步骤中,实际上会发生不一致的情况:
- 订单更新成功,消息发送失败。
- 订单更新成功,消息发送超时。
- 订单更新失败,消息发送不管成功还是失败,整个事务都会回滚。
- 订单更新成功,消息发送成功,业务端消费失败。
我们逐一来分析这几种情况:
- 第1种订单更新成功,消息发送失败。这时下游的业务系统和支付系统的订单数据是不一致的。不过我们既然明确知道是消息发送失败,是可以对消息进行重试或者补偿。
- 第2种订单更新成功,消息发送超时。这种情况我们是无法确定消息是否发送成功,如果要进行重试或补偿,就需要对投递的消息进行反查询,不然可能会重复发送。
- 第3种情况比较简单,订单执行失败,这时是抛出异常或者直接返回,都不会走到消息发送的流程。
- 第4种情况我们可以不用考虑,因为消息发送成功了之后,订单服务就完成了自己的任务,至于下游是否消费成功,是各自业务系统各自负责的。
在这里其实可能有人会有疑问,包括我自己。如果把发送消息和订单更新逻辑放在一个事务里面,执行事务成功就发送普通消息,失败就回滚是不是也可以?
这里就是对应的第2种情况,如果事务执行失败是因为消息发送超时,但是我们无法确定消息是否就真的成功发送到消息服务中。
总结一下,消息发送失败或者超时,其实已经造成了数据不一致的情况,也就是订单系统和下游的积分系统、物流系统、推送系统的订单数据不一致。
而对消息进行重试或者补偿,是为了保证订单数据最终能同步到下方各业务系统,这就是所说的消息最终一致性。
如果我们要解决这个问题,实现消息最终一致性,有什么方案吗❓
增加一个消息发送记录表,在订单执行成功的同时,插入一条当前订单的消息发送记录,消息发送成功后,再更新数据的发送状态。如果是消息发送失败或者超时,由程序后台任务来补偿重发。
图片
这个方法比较简单好理解,就是通过记录消息发送的状态,来确保订单更新成功后,消息最终一定能触达到 MQ,后台的定时任务则是对发送超时的消息补偿。
如果对于要求消息最终一致性的业务场景,是不是都要这么实现一套繁琐的流程呢?没错,这个确实很麻烦。
不过,事务消息可以帮我们解决这个问题。
事务消息原理
RocketMQ 事务消息流程👇
图片
我们结合上面用户支付的例子,看一下使用 RocketMQ 如何实现事务消息:
- 首先,在订单执行操作之前,先发送一条半事务消息到 RocketMQ,表示即将开始执行本地事务,RocketMQ 收到这个半事务消息之后,不会马上投递,而是把消息暂存在临时队列中。
- 半消息发送成功之后,订单系统就开始执行本地事务,即更新订单的操作。执行完成后会发送一条确认消息到 RocketMQ 中,告知订单执行的结果。如果订单执行成功,RocketMQ 会将之前收到的半事务消息投递到目标队列中,消费端可马上订阅消费;如果订单执行失败,RocketMQ 则会删除之前收到的半事务消息,保证消息不会被消费。
当然,我们也要考虑到极端的情况:假如订单更新后就宕机或者重启,那么发给 RocketMQ 确认消息就会丢掉,订单数据还是会发送不一致的情况。
所以,这里就需要消息回查的机制。RocketMQ 收到半事务消息后,在特定的时间内如果没有收到确认消息,那么 RocketMQ 会反过来请求本地事务执行的最终状态(即订单更新的执行结果),根据返回的执行结果再对半事务消息做处理。
不过换个角度来看,其实 RocketMQ 的事务消息与我们上面使用消息记录表来实现消息一致性的方案有异曲同工之妙。或者可以说其实我们已经逆向实现了这一套事务消息。
RocketMQ 自身会存储消息,相当于帮我们实现了消息发送的记录;发送半消息相当于插入一条消息发送记录;发送确认消息相当于更新消息发送记录;消息回查相当于后台任务的补偿和重试。
再从另外一个角度来看,无论是我们自己实现的事务消息还是 RocketMQ 的事务消息,其实都是二阶段提交(2PC)的实现。第一阶段的提交与第二阶段的确认,目的都是保证本地事务与消息的一致性。
适用场景
事务消息是保证消息的最终一致性,在中间阶段可能会出现数据不一致的情况。
所以适用于一些需要异步更新数据,并且对数据实时性要求不太高的场景。
比如在用户支付订单后,可能会出现在短暂的几秒里,用户积分没有变动、购物车的商品没有被处理等,这些情况是可以接受的,只要保证最终用户积分、购物车的数据和订单数据保持一致就可以。