分布式事务(下)
前言
书接上文分布式事务(上)
5.分布式事务解决方案之可靠消息最终一致性
5.1 可靠消息最终一致性
- 可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。
- 此方案是利用消息中间件完成,事务发起方(消息生产方)将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间、事务参与方(消息消费方)和消息中间件之间都是通过网络通信,由于网络通信的不确定性会导致分布式事务问题。
- 因此可靠消息最终一致性方案要解决以下几个问题:
- 本地事务与消息发送的原子性问题:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。
- 事务参与方接收消息的可靠性:事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息。
- 消息重复消费的问题:由于在事务参与方与消息中间件有网络的存在,若某一个消费节点超时但是消费成功(比如RabbitMQ的消息消费是有确认机制的,正常情况下,消费者在消息消费成功后,会发送一个确认消息,消息队列接收到之后,就会将该消息从消息队列中删除,下次也就不会再投递了,所以可能出现消费成功但是回传确认由于网络原因超时),此时消息中间件会重复投递此消息,就导致了消息的重复消费。要解决消息重复消费的问题就要实现事务参与方的方法幂等性。
5.2 解决方案
5.2.1 本地消息表方案
- 通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。
下面以注册送积分为例来说明:
1.用户注册:用户服务在本地事务新增用户和增加 ”积分消息日志“。(用户表和消息表通过本地事务保证一致) 这种情况下,本地数据库操作与存储积分消息日志处于同一个事务中,本地数据库操作与记录消息日志操作具备原子性。
begin transaction;
//1.新增用户
//2.存储积分消息日志
commit transation;
2.定时任务扫描日志:经过第一步消息已经写到消息日志表中,可以启动独立的线程,定时对消息日志表中的消息进行扫描并发送至消息中间件,在消息中间件反馈发送成功后删除该消息日志,否则等待定时任务下一周期重试。
3.消费消息:可以使用MQ的ack(即消息确认)机制,消费者监听MQ,如果消费者接收到消息并且业务处理完成后向MQ 发送ack(即消息确认),此时说明消费者正常消费消息完成,MQ将不再向消费者推送消息,否则消费者会不断重试向消费者来发送消息。积分服务接收到”增加积分“消息,开始增加积分,积分增加成功后向消息中间件回应ack,否则消息中间件将重复投递此消息。由于消息会重复投递,积分服务的”增加积分“功能需要实现幂等性。
5.2.2 RocketMQ事务消息方案
rocketMQ我已经整理出了一个专题,请配合使用(RocketMQ相关面试题(上))
- RocketMQ 事务消息设计主要是为了解决 生产者的消息发送与本地事务执行的原子性问题,RocketMQ 的 设计中 broker 与生产者端的双向通信能力,使得 broker 天生可以作为一个事务协调者存在;而 RocketMQ 本身提供的存储机制为事务消息提供了持久化能力;RocketMQ 的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。
执行流程如下: 为方便理解我们还以注册送积分的例子来描述整个流程。
生产者:即MQ发送方,本例中是用户服务,负责新增用户。
消费者:MQ订阅方即消息,本例中是积分服务,负责 新增积分。
1、生产者发送事务消息
生产者发送事务消息到broker,broker将消息状态标记为Prepared,此时这条消息消费者是无法消费到的。用户服务发送 ”增加积分消息“ 到MQ Server。
2、broker回应消息发送成功
broker接收到生产者发送的消息则回应ACK确认消息表示MQ已接收到消息。
3、生产者执行本地事务
生产者执行业务代码逻辑,通过本地数据库事务控制。 用户服务执行添加用户操作。
4、消息投递
若生产者本地事务执行成功则自动向MQServer发送commit消息,MQ Server接收到commit消息后将”增加积分消息“ 状态标记为可消费,此时MQ订阅方(积分服务)即正常消费消息;
若Producer 本地事务执行失败则自动向MQServer发送rollback消息,MQ Server接收到rollback消息后 将删 除”增加积分消息“ 。
积分服务消费消息,消费成功则向MQ回应ack,否则将重复接收消息。
5、事务回查
如果执行生产者本地事务过程中,执行端挂掉,或者超时,MQ Server将会不停的询问同组的其他 Producer
来获取事务执行状态,这个过程叫事务回查。MQ Server会根据事务回查结果来决定是否投递消息。
以上主干流程已由RocketMQ实现,对用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此
只需关注本地事务的执行状态即可。
6.分布式事务解决方案之最大努力通知
6.1 最大努力通知
-
举例:
1、账户系统调用充值系统接口
2、充值系统完成支付处理向账户系统发起充值结果通知,若通知失败,则充值系统按策略进行重复通知。
3、账户系统接收到充值结果通知修改充值状态。
4、账户系统未接收到通知会主动调用充值系统的接口查询充值结果。 -
最大努力通知方案的目标:
目标:发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。
具体包括:
1、有一定的消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
2、消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。 -
最大努力通知与可靠消息一致性有什么不同?
1、解决方案思想不同:- 可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。
- 最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。
2、两者的业务应用场景不同:
- 可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。
- 最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。
3、技术解决方向不同:
- 可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。
- 最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。
6.2 解决方案
采用MQ的ack机制可以实现最大努力通知。
方案一:利用MQ的ack机制由MQ向接收通知方发送通知
- 发起通知方将通知发给MQ(消费者将消息发送给MQ)
使用普通消息机制将通知发给MQ。 注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。 - 接收通知方监听 MQ。
- 接收通知方接收消息,业务处理完成回应ack。
- 接收通知方若没有回应ack,则MQ会重复通知。
MQ会按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 (rocketMq在broker中可进行配置),直到达到通知要求的时间窗口上限。 - 接收通知方可通过消息校对接口来校对消息的一致性。
方案二:应用程序向接收通知方发送通知
- 流程如下:
- 发起通知方将通知发给MQ。使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ。
- 通知程序监听 MQ,接收MQ的消息。
方案1中接收通知方直接监听MQ,方案2中由通知程序监听MQ。
通知程序若没有回应ack则MQ会重复通知。 - 通知程序通过互联网接口协议(如http、webservice)调用接收通知方案接口,完成通知。通知程序调用接收通知方案接口成功就表示通知成功,即消费MQ消息成功,MQ将不再向通知程序投递通知消 息。
- 接收通知方可通过消息校对接口来校对消息的一致性。
- 方案1和方案2的不同点:
- 方案1中接收通知方与MQ接口,即接收通知方案监听 MQ,此方案主要应用与内部应用之间的通知。
- 方案2中由通知程序与MQ接口,通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收 通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。
总结
欢迎大家讨论,如有错误还请指正!