事务的概念就不用多说了,我相信阅读文章的童鞋都是有着非常深刻的认识。我们都知道MQ可以实现微服务之间的异步以及解耦,那么引入MQ之后,如何实现微服务之间的数据一致性是一个值得思考的问题。RocketMQ事务消息正是解决这个问题的解决方案。另外事务消息也是为了解决消息丢失问题。
哪些场景会出现消息丢失
在分析RocketMQ事务消息之前,我们先来分析下引入消息中间件之后,整个消息链路在哪些场景会出现消息丢失的异常情况。
当我们支付订单之后,我们账户的购物积分也会进行相应的积分调整。我们结合下面的订单服务、RocketMQ、积分服务的简化交互图来看,我们来分析下整个链路中可能会出现的消息丢失问题。
场景1:在订单服务向RocketMQ发送订单成功生成的消息的时候,可能由于网络抖动的问题导致订单消息没能正常投递到RocketMQ,导致消息丢失。
场景2:那么假如订单服务以及RocketMQ之间的网络没问题,消息正常被RocketMQ接收到了,那么会存在消息丢失的情况吗?答案是肯定的,这和RocketMQ的持久化机制有关系,当消息到达RocketMQ之后,并不是立马落盘存储,而是存储在page cache中的。如果此时出现服务器断电或者宕机情况,那么还没来得及落盘的消息数据就有可能丢失。另外即使是落到磁盘当中,如果出现磁盘坏道的话,依然会出现消息数据的可能。
场景3:如果前面两种场景都没问题,积分服务拿到订单消息了。还会出现消息丢失的问题吗?答案依然是肯定的。即便是积分服务拿到了订单消息,当积分服务自动提交消息offset到RocketMQ中,但是此时如果出现宕机或者积分服务挂了,没有将本该增加的积分进行处理,此时也就出现了消息丢失的情况。
事务消息机制原理
half消息
所谓的RocketMQ事务机制,其实是RocketMQ提供了一种half消息的机制。当订单服务接受到订单支付信息后,订单服务会发送half消息到RocketMQ中,这个half消息是不被消费者所见的。怎么理解这个half信息呢,按照我自己的理解,就是它实现了一半的消息功能,只在生产端可见,在消费端不可见。另外这个half信息相当于一种RocketMQ的可用性探测,如果half消息都发送失败的话,就不必再进行下游业务的一系列操作了。
如果此时用于探测RocketMQ的可用性的half消息发送失败了,那么说明此时订单服务与RocketMQ存在异常,则会对之前订单进行一系列的回滚操作。如果half消息被成功投递,则需要进行本地事务操作,更新订单状态。
如果本地事务执行失败了怎么办,订单服务可以发送rollback请求,将之前的half消息从RocketMQ中进行删除,不再进行后续的消息投递。
half消息原理分析
上文提到half消息不被消费端可见,那么这个half消息是怎么实现在RocketMQ中不被积分服务所见的呢?
订单服务发送half消息,实际并不是将消息投递到积分服务订阅的topic,而是将消息投递到RocketMQ中的RMQ_SYS_TRANS_HALF_TOPIC对应的messeageQueue。由于积分服务并没有订阅这个Topic,所以这个消息对于积分服务是不可见的。
另外有个OP_TOPIC用于记录对应half消息的commit/rollback状态。大致的交互如下如所示:
如果订单服务half消息发送失败了,由于网络原因或者RocketMQ挂了,那么此时需要执行一些回滚操作,让订单进行关闭。因为订单信息无法通知到下游服务了。
那么如果half消息已经写入RocketMQ中,但是本地事务执行失败又该怎么办呢?也就是说当订单服务接收到half消息写入成功的响应后,更新订单信息时发生了异常,无法完成状态更新。那么此时订单服务需要发送rollback的请求给RocketMQ,通知其将原来的half信息进行删除。如果本地事务执行成功,则需要发送commit请求给RocketMQ,RocketMQ会将原先存在RMQ_SYS_TRANS_HALF_TOPIC中的消息重新投递到积分服务订阅的TOPIC中去,这样积分服务就可以正常消费信息进行下一步的积分操作了。
再考虑一种情况,如果订单服务发送commit或者rollback请求未正常投递到RocketMQ中,RocketMQ不知道half消息到底是对应的本地事务到底是执行成功了还是执行失败了。针对这种情况,订单服务需要提供状态回查接口,RocketMQ定时检测是否还有没有处理的half消息,当存在这样的消息时,RocketMQ调用回查接口确认本地事务执行情况。执行失败的则删除half消息,执行成功则重新投递消息。
重新投递消息到对应的ConsumerQueue中,而此时积分服务订阅了对应的订单topic,可以正常消费这个消息了,继续后续的业务流程。
总结
通过上文的分析,订单服务和RocketMQ之间的交互,通过事务消息机制可以保证消息可以被可靠投递。至少在订单服务和RocketMQ之间不会出现消息丢失的问题。