**
导读
**
在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC、3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见?推荐阅读)。在这篇文章中我们将介绍RocketMQ的事务消息相关的内容,并通过一些实践和大家一起来探索下事务消息如何解决分布式系统中的分布式事务问题。
**
事务消息原理
**
事务消息特性可以看作是两阶段协议的消息实现方式,用以确保在以消息中间件解耦的分布式系统中本地事务的执行和消息的发送,可以以原子的方式进行。
举个例子,以某互联网公司的用户余额充值为例,因为有充返活动(充值100元赠送20元),优惠比较大,用户Joe禁不住诱惑用支付宝向自己的余额账户充值了100元,支付成功后Joe的余额账户有了120元钱。
而该公司的关于用户余额充值的系统设计是这样的:
在这个设计流程中,该公司通过自建支付系统完成用户Joe的支付宝扣款操作,成功后需要更新支付流水的状态,因为用户的余额账户系统与支付系统之间通过MQ解耦了,所以支付系统在完成支付流水状态更新后需要通过发送MQ消息到消息中间件服务,然后用户余额系统作为消费者通过消息消费的方式完成用户余额的增加操作。
这里有个问题:“支付系统如何确保这笔余额充值消息一定会成功发送到MQ,并且用户余额系统一定能处理成功呢”?如果支付系统在完成支付订单状态更新后,MQ消息发送失败或者用户余额系统消息处理失败的话,都会导致Joe支付扣款成功,而自己的余额账户却没到账的情况发生。
为了解决这个问题,按照目前的系统设计是需要“支付系统-MQ服务-用户余额系统”三者的处理满足数据的一致性要求。例如,如果支付系统感知到消息发送失败后还可以进行重新投递,从而确保支付系统与用户余额数据的最终一致性。
而上述问题就是事务消息要解决的问题,在具体了解RocketMQ提供的事务消息机制之前,我们先来看下在RocketMQ的早期版本不支持事务消息,或者因为历史原因选择的消息中间件本身就不支持事务消息的情况下,一些大公司是怎么解决这个问题的?
早期为了实现基于MQ异步调用的多个服务间,业务逻辑执行要么一起成功、要么一起失败,具备事务特点,通常会采用可靠消息最终一致性方案,来实现分布式事务。还是以Joe充值这件事来举例,可靠消息方案实现过程如下:
在可靠消息最终一致性方案中,为了实现分布式事务,需要确保上游服务本地事务的处理与MQ消息的投递具有原子性,也就是说上游服务本地事务处理成功后要确保消息一定要成功投递到MQ服务,否则消息就不应该被投递到MQ服务;同样,被成功投递到MQ服务的消息,也一定要被下游服务成功处理,否则就需要重新投递MQ消息。
为了实现双向的原子性,可靠消息服务需要对消息进行状态标记,与此同时还需要对消息进行状态检查,从而实现重新投递及消息状态的最终一致性。核心流程说明如下:
1、上游服务(支付系统)如何确保完成自身支付成功状态更新后消息100%的能够投递到下游服务(用户余额系统)指定的Topic中?
在这个流程中上游服务在进行本地数据库事务操作前,会先发送一个状态为“待确认”的消息至可靠消息服务,而不是直接将消息投递到MQ服务的指定Topic。可靠消息服务此时会将该消息记录到自身服务的消息数据库中(消息状态为->待确认),完成后可靠消息服务会回调上游服务表示收到了消息,你们可以进行本地事务的操作了。
之后上游服务就会开启本地数据库事务执行业务逻辑操作,这里支付系统就会将该笔支付订单状态更新为“已成功”。(注意,这里只是举个示例场景,在真正的实践中一般是不会把支付订单本身的状态与业务端回调放在一个事务流程中的,关于这部分的详细说明我们在下面的场景说明中再讨论)。
如果上游服务本地数据库事务执行成功,则继续向可靠消息服务发送消息确认消息,此时可靠消息服务就会正式将消息投递到MQ服务,并且同时更新消息数据库中的消息状态为“已发送”。(注意ÿ