简介
本文介绍分布式事务的一种解决方案:消息表结合MQ。
本地消息表+MQ(无事务)
简介
项 | 说明 |
项目来源 | 源于eBay经典的BASE方案。ebay的完整方案BASE: An Acid Alternative – ACM Queue |
基本设计思想 | 将远程分布式事务拆分成一系列的本地事务 |
使用的重要技术 | 消息队列和消息应用状态表 |
优缺点 | 优点:基本避免了分布式事务,实现了“最终一致性”;开发简单 缺点:需设计DB消息表,同时还需要一个后台任务,不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。 |
适用场景 | 适用于对一致性要求不高的非高并发场景。(本地消息队列是BASE理论,是最终一致模型)。 |
实例流程
说明:这种分布式事务适合前边服务弱依赖于后边的服务的场景,比如付款成功增积分:积分的多少不影响付款。但对于强依赖的情况不合适,如下单减库存操作:是否能下单由库存是否充足而定,而下单是否成功由减库存是否成功而定,此时,用此法最合适:订单模块直接调用减库存的微服务接口。
本处以购物时的付款成功增积分为例说明。
错误处理
- 步骤:1,2:任意一个出错,整个事务回滚,不会出错
- 步骤3:投递消息成功,结果接收MQ响应时网络出错。不会出错
- 步骤4:MQ宕机(解决方法:MQ一般支持持久化)。消费者消费失败(解决方法:重试3次)
- 步骤4-8:4-8任意一个失败次数超过重试次数。解决方法:发送报警,让人工处理。
从工程实践角度讲,这种整个流程自动回滚的代价是非常巨大的,不但实现复杂,还会引入新的问题。
比如自动回滚失败,又怎么处理?对应这种极低概率的case,采取人工处理,会比实现一个高复杂的自动化回滚系统,更加可靠,也更加简单。 - 步骤6:增积分完成之后,断网,导致增积分成功的消息没有发布出去。解决方法:定时任务会再次将消息投递,步骤5会判断到增积分已完成,直接跳到步骤8:发布消息
- 一个原子性问题:如果保证消息消费 + insert message到判重表这2个操作的原子性?消费成功,但insert判重表失败,怎么办?关于这个,在Kafka的源码分析系列,第1篇,exactly once问题的时候,有过讨论。
MQ(有事务)
简介
上边“本地消息表+MQ(无事务)”的一个缺点:需要设计DB消息表,同时还需要一个后台任务,不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。
为了能解决该问题,同时又不和业务耦合,RocketMQ提出了“事务消息”的概念。阿里巴巴的RocketMQ中实现了分布式事务,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部。
对比方案3和方案2,RocketMQ最大的改变,其实就是把“扫描消息表”这个事情,不让业务方做,而是消息中间件帮着做了。至于消息表,其实还是没有省掉。因为消息中间件要询问发送方,事物是否执行成功,还是需要一个“变相的本地消息表”,记录事物执行状态。
具体来说,就是把消息的发送分成了2个阶段:Prepare阶段和确认阶段。
具体来说,上面的2个步骤,被分解成3个步骤:
- 第一步:生产者:向RocketMQ发送Prepared消息,生产者会拿到消息的地址。
- 第二步:生产者:执行本地事务(修改数据库的数据)。
- 第三步:生产者:
若第二步执行成功,用第一步拿到的地址去访问消息,并修改状态(Confirm),消息接收者就能使用这个消息。
若第二步执行失败,用第一步拿到的地址去访问消息,并取消Prepared消息,消息接收者就得不到这个消息。
1. Producer向broker端发送消息
2. 服务端将消息持久化成功之后,向发送方ACK确认消息已经发送成功,此时消息为半消息
3. 发送方开始执行本地事务逻辑
4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit或者Rollback)。服务端收到Commit状态则将半消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半消息,订阅方将不会接受该消息。
5. 在断网或者是应用重启等特殊情况下,上述步骤4提交的二次确认最终未到达服务端,RocketMQ 会定期扫描消息集群中的事物消息,若发现未经确认的Prepared 消息,会对该消息发起消息回查
6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果
7. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照4对半消息进行操作
错误处理
消费方重试全部失败怎么办?
解决方法:发送报警,让人工处理。
从工程实践角度讲,这种整个流程自动回滚的代价是非常巨大的,不但实现复杂,还会引入新的问题。
比如自动回滚失败,又怎么处理?对应这种极低概率的case,采取人工处理,会比实现一个高复杂的自动化回滚系统,更加可靠,也更加简单。
实例
略
请先
!