文章目录
分布式事务系列文章 |
---|
初探分布式事务:扫盲分布式事务的基础概念和理论知识点 |
图解分布式事务中的2PC与Seata方案 |
案例驱动学习:轻松理解TCC分布式事务 |
消息队列与分布式事务:探讨不同MQ如何实现分布式事务的可靠消息传递 |
MQ最大努力通知VS可靠性消息一致性:分布式事务中的区别与应用比较 |
TODO:TCC分布式事务之综合案例分析 |
文章导图
一、什么是最大努力通知
最大努力通知是一种灵活的分布式事务处理方式,适合对一致性要求不高的业务场景。它通过尽力而为的策略,结合消息重试和主动查询机制,来提升系统的可靠性和用户体验。
其核心思想是尽可能地将业务处理结果通知给下游的业务接收方,即使在某些情况下通知可能失败,但是它可以主动向发起方查询获取所需信息进行补偿。
下边是一个是充值的例子:
交互流程
- 账户系统调用充值系统接口
- 充值系统完成支付处理向账户系统发起充值结果通知,若通知失败,则充值系统按策略进行重复通知
- 账户系统接收到充值结果通知修改充值状态。
- 账户系统未接收到通知会主动调用充值系统的接口查询充值结果。
通过上边的例子我们总结最大努力通知方案的目标:发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。
具体包括:
-
有一定的消息重复通知机制。
因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
-
消息校对机制。
如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。
最大努力通知与可靠消息一致性有什么不同?
-
解决方案思想不同
- 可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。
- 最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。
-
两者的业务应用场景不同
-
可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。
-
最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。
-
-
技术解决方向不同
- 可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。
- 最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。
二、解决方案
通过对最大努力通知的理解,采用MQ的ack机制就可以实现最大努力通知。
方案1
本方案是利用MQ的ack机制由MQ向接收通知方发送通知,流程如下:
-
发起通知方将通知发给MQ。
使用普通消息机制将通知发给MQ。
注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。
-
接收通知方监听 MQ。
-
接收通知方接收消息,业务处理完成回应ack。
-
接收通知方若没有回应ack则MQ会重复通知。
MQ会按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 (如果MQ采用rocketMq,在broker中可进行配置),直到达到通知要求的时间窗口上限。
-
接收通知方可通过消息校对接口来校对消息的一致性。
方案2
本方案也是利用MQ的ack机制,与方案1不同的是应用程序向接收通知方发送通知,如下图:
交互流程如下:
-
发起通知方将通知发给MQ。
使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ。
-
通知程序监听 MQ,接收MQ的消息。
方案1中接收通知方直接监听MQ,方案2中由通知程序监听MQ。通知程序若没有回应ack则MQ会重复通知。
-
通知程序通过互联网接口协议(如http、webservice)调用接收通知方案接口,完成通知。
通知程序调用接收通知方案接口成功就表示通知成功,即消费MQ消息成功,MQ将不再向通知程序投递通知消息。
-
接收通知方可通过消息校对接口来校对消息的一致性。
方案1和方案2区别
- 方案1中接收通知方与MQ接口,即接收通知方案监听 MQ,此方案主要应用与内部应用之间的通知。
- 方案2中由通知程序与MQ接口,通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。
三、RocketMQ实现最大努力通知型事务
本实例通过RocketMq中间件实现最大努力通知型分布式事务,模拟充值过程。
业务流程如下图:
交互流程如下:
1、用户请求充值系统进行充值。
2、充值系统完成充值将充值结果发给MQ。
3、账户系统监听MQ,接收充值结果通知,如果接收不到消息,MQ会重复发送通知。接收到充值结果通知账户系
统增加充值金额。
4、账户系统也可以主动查询充值系统的充值结果查询接口,增加金额。
核心代码讲解
这里主要针对的是方案1的代码解析
发起方充值成功,发送MQ消息
@Override
public AccountPay insertAccountPay(AccountPay accountPay) {
int success = accountPayDao.insertAccountPay(accountPay.getId(), accountPay.getAccountNo(), accountPay.getPayAmount(), "success");
if(success>0){
//发送通知,使用普通消息发送通知
accountPay.setResult("success");
rocketMQTemplate.convertAndSend("topic_notifymsg",accountPay);
return accountPay;
}
return null;
}
接收方消费MQ消息
这里接收方监听MQ,接受到消息以后更新金额即可,如果消费失败会进行衰减式重试
//接收消息
@Override
public void onMessage(AccountPay accountPay) {
log.info("接收到消息:{}", JSON.toJSONString(accountPay));
if("success".equals(accountPay.getResult())){
//更新账户金额
AccountChangeEvent accountChangeEvent = new AccountChangeEvent();
accountChangeEvent.setAccountNo(accountPay.getAccountNo());
accountChangeEvent.setAmount(accountPay.getPayAmount());
accountChangeEvent.setTxNo(accountPay.getId());
accountInfoService.updateAccountBalance(accountChangeEvent);
}
log.info("处理消息完成:{}", JSON.toJSONString(accountPay));
}
发起方通知失败,接收方主动回查
发起方会尽最大努力将结果通知给接收方,极端情况下有失败的可能,此时发起方需提供查询接口。
接收方对于长时间不知道结果的业务,可以主动去向发起方查询,然后进行处理。
// 主动查询充值结果
@GetMapping(value = "/payresult/{txNo}")
public AccountPay result(@PathVariable("txNo") String txNo){
AccountPay accountPay = accountInfoService.queryPayResult(txNo);
return accountPay;
}
//远程调用查询充值结果
@Override
public AccountPay queryPayResult(String tx_no) {
//springcloud的方式进行远程调用
AccountPay payresult = payClient.payresult(tx_no);
if("success".equals(payresult.getResult())){
//更新账户金额
AccountChangeEvent accountChangeEvent = new AccountChangeEvent();
accountChangeEvent.setAccountNo(payresult.getAccountNo());//账号
accountChangeEvent.setAmount(payresult.getPayAmount());//金额
accountChangeEvent.setTxNo(payresult.getId());//充值事务号
updateAccountBalance(accountChangeEvent);
}
return payresult;
}
发起方提供的回查方法供接收方回查
//springcloud的方式提供接口: 查询充值结果
@GetMapping(value = "/payresult/{txNo}")
public AccountPay payresult(@PathVariable("txNo") String txNo){
return accountPayService.getAccountPay(txNo);
}
//查询充值记录,接收通知方调用此方法来查询充值结果
@Override
public AccountPay getAccountPay(String txNo) {
AccountPay accountPay = accountPayDao.findByIdTxNo(txNo);
return accountPay;
}
三、总结
最大努力通知方案是分布式事务中对一致性要求最低的一种,适用于一些最终一致性时间敏感度低的业务;
最大努力通知方案需要实现如下功能:
- 消息重复通知机制。
- 消息校对机制。
综上所述,只要业务主动方提供了回调查询接口,业务被动方保证接口的幂等性,消息数据是允许丢失的
在实现最大努力通知型分布式事务时,最关键的两点是业务主动方提供回调查询接口,业务被动方接收消息通知的接口保证幂等性
参考文章:
主要整理于黑马分布式事务教程