最大努力通知分布式事务

224 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

一、前言

最大努力通知型解决方案适用于:

  1. 最终一致性时间敏感度低的场景
  2. 事务被动方的处理结果不会影响主动方的处理结果。

典型应用场景:支付成功后,支付平台异步通知商户支付结果。

此方案需要实现的服务模式:

  1. 可查询操作:提供查询自身事务状态的接口。
  2. 幂等操作:只要参数相同,无论调用多少次接口,都应该和第一次调用产生的结果相同。

实现最大努力通知型方案,需要实现如下功能:

  1. 业务主动方在完成业务处理后,会向业务被动方发送消息通知。

    发送消息通知时,允许消息丢失。

  2. 在实现上,业务主动方可以设置时间阶梯型通知规则,在消息通知失败后,可以按照规则再次通知,直到到达最大通知次数为止。

  3. 业务主动方需要提供查询接口供业务被动方按照需要查询,用于恢复丢失的消息。

方案的优点:

  1. 能够实现跨企业的数据一致性。
  2. 业务被动方的处理结果不会影响业务主动方的处理结果。
  3. 能够快速接入其他业务系统,达到业务数据一致性。

方案的缺点:

  1. 只适用于时间敏感度低的场景。
  2. 业务主动方发送的消息可能丢失,造成业务被动方收不到消息。
  3. 需要业务主动方提供查询消息的接口,业务被动方需要按照主动方的接口要求查询数据,增加了开发成本。

需要注意的问题

  1. 消息重复通知产生的问题
  • 原因:由于业务主动方发送消息通知后,业务被动方不一定能够接收到消息,因此需要按照一定的梯度型通知规则重复想业务被动方发送消息桶。
  • 解决方案:业务被动方接收消息通知需要具备幂等性
  1. 消息通知丢失的问题
  • 原因:如果业务主动方最大努力都没有将消息通知给业务被动方,或者业务被动方接收到消息并执行完毕后,需要再次获取消息。

    此时,业务主动方已经删除对应的通知消息,不再向业务被动方发送消息通知,即消息通知已经丢失。

  • 解决方案:业务主动方需要提供查询消息的接口来满足业务被动方主动查询消息的需求,以恢复丢失的业务。

    业务主动方在设计消息回查接口时,一定要注意接口的安全性和并发性。

二、实战实验

涉及服务有:

  • 账户服务:
  • 充值服务:

rocketmq-最大努力.png

实验准备:

  • MySQL:8.0.20
  • RocketMQ 消息中间件:rocketmq-all-4.5.0-bin-release
  • RocketMQ 客户端:rocketmq-spring-boot-starter 2.0.2
  • Spring Boot 版本:2.2.6.RELEASE

服务:

数据准备:

INSERT INTO `account_info` VALUES (1001, '1001', 'yyf', 1000.00);
​
SELECT * FROM account_info;
+------|------------|--------------|-----------------+
| id   | account_no | account_name | account_balance |
+------|------------|--------------|-----------------+
| 1001 | 1001       | yyf          |         1000.00 |
+------|------------|--------------|-----------------+

(1)正常流程

请求:

$ curl "http://localhost:8083/pay/pay_account?accountNo=1001&payAmount=1000"
{"txNo":"dbb01f7e-2894-4ab1-9e88-2e9a98da3cb8","accountNo":"1001","payAmount":1000,"payTime":[2022,5,9,22,17,50,456000000],"payResult":"success"}

账号日志:

2022-05-09 22:39:49.138  INFO 8835 --- [MessageThread_1] c.d.t.message.NotifyMsgAccountListener   : 账户微服务收到RocketMQ的消息:{"accountNo":"1001","payAmount":1000,"payResult":"success","payTime":"2022-05-09T22:17:50.456","txNo":"dbb01f7e-2894-4ab1-9e88-2e9a98da3cb8"}
2022-05-09 22:39:49.304  INFO 8835 --- [MessageThread_1] c.d.t.message.NotifyMsgAccountListener   : 更新账户余额完毕:{"accountNo":"1001","payAmount":1000,"payResult":"success","payTime":"2022-05-09T22:17:50.456","txNo":"dbb01f7e-2894-4ab1-9e88-2e9a98da3cb8"}

看下数据库:

USE tx_notifymsg_account;
​
​
SELECT * FROM pay_info;
+--------------------------------------|------------|------------|------------|---------------------+
| tx_no                                | account_no | pay_amount | pay_result | pay_time            |
+--------------------------------------|------------|------------|------------|---------------------+
| dbb01f7e-2894-4ab1-9e88-2e9a98da3cb8 | 1001       |    1000.00 | success    | 2022-05-09 22:17:50 |
+--------------------------------------|------------|------------|------------|---------------------+SELECT * FROM account_info;
+------|------------|--------------|-----------------+
| id   | account_no | account_name | account_balance |
+------|------------|--------------|-----------------+
| 1001 | 1001       | yyf          |         2000.00 |
+------|------------|--------------|-----------------+
​
​
​
use tx_notifymsg_pay;
​
SELECT * FROM pay_info;
+--------------------------------------|------------|------------|------------|---------------------+
| tx_no                                | account_no | pay_amount | pay_result | pay_time            |
+--------------------------------------|------------|------------|------------|---------------------+
| dbb01f7e-2894-4ab1-9e88-2e9a98da3cb8 | 1001       |    1000.00 | success    | 2022-05-09 22:17:50 |
+--------------------------------------|------------|------------|------------|---------------------+