程序员子龙(Java面试 + Java学习) 程序员子龙(Java面试 + Java学习)
首页
学习指南
工具
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

    • 分布式基础

    • zookeeper

    • 分布式事务

      • 分布式事务
      • 柔性事务:最大努力通知
        • 最大努力通知的解决方案
        • 最大努力通知方案的实现
        • 最大努力通知方案的特点
        • RocketMQ实现最大努力通知型事务
    • 分布式锁

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

  • Nginx

  • 面试

  • 生产问题

  • 系统设计

  • 消息中间件

  • Java
  • 分布式
  • 分布式事务
程序员子龙
2024-01-29
目录

柔性事务:最大努力通知

最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,是分布式事务中对一致性要求最低的一种,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。

最大努力通知的目标:发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。

最大努力通知型的实现方案,一般符合以下特点:

1、不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。

2、定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。

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

1、消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。

2、消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。

最大努力通知方案主要也是借助MQ消息系统来进行事务控制,这一点与可靠消息最终一致方案一样。看来MQ中间件确实在一个分布式系统架构中,扮演者重要的角色。最大努力通知方案是比较简单的分布式事务方案,它本质上就是通过定期校对,实现数据一致性。

常见的充值案例:

支付流程

  • 用户在浏览器发起充值请求
  • 电商服务生成充值订单,状态为0:待支付(0:待支付、100:支付成功、200:支付失败)
  • 电商服务携带订单信息请求支付宝,生成支付宝订单,组装支付宝支付请求地址(订单信息、支付成功之后展示给用户的页面return_url、支付异步通知地址notify_url),将组装的信息返回给用户
  • 用户浏览器跳转至支付宝支付页面,确认支付
  • 支付宝携带支付结果同步回调return_url,return_url将支付结果展示给用户

支付宝将支付结果异步通知给商户

用户支付流程完毕之后,此时支付宝中支付订单已经支付完毕,但电商中的充值订单状态还是0(待支付),此时支付宝会通过异步的方式将支付结果通知给notify_url,通知的过程中可能由于网络问题,导致支付宝通知失败,此时支付宝会通过多次衰减式的重试,尽最大努力将结果通知给商户,这个过程就是最大努力通知型。

商户接收到支付宝通知之后,通过幂等性的方式对本地订单进行处理,然后告知支付宝,处理成功,之后支付宝将不再通知。

什么是衰减式的通知

比如支付宝最大会尝试通知100次,每次通知时间间隔会递增。比如第1次失败之后,隔10s进行第2次通知,第2次失败之后,隔30s进行第三次通知,间隔时间依次递增的方式进行通知。

为什么需要进行异步通知

支付宝支付成功之后会携带支付结果同步调用return_url这个地址,那么商户直接在这个return_url中去处理一下本地订单状态不就可以了么?这种做法可以,但是有可能用户的网络不好,调用return_url失败了,此时还得依靠异步通知notify_url的方式将支付结果告知商户。

如果支付宝一直通知不成功怎么办

商户可以主动去调用支付宝的查询接口,查询订单的支付状态。

# 最大努力通知的解决方案

最大努力通知方案设计上比较简单,主要是由两部分构成。 1.实时消息服务(MQ):接收主动方发送的MQ消息。 2.通知服务子系统:监听MQ消息,当收到消息后,向被动方发送通知(一般是URL方式),同时生成通知记录。如果没有接收到被动方的返回消息,就根据通知记录进行重复通知。 仅适用于实时性不太高的场合:最大努力通知方案实现方式比较简单,本质上就是通过定期校对,适用于数据一致性时间要求不太高的场合,其实不把它看作是分布式事务方案,只认为是一种跨平台的数据处理方案也是可以的。

方案1:利用 MQ的 ack机制由 MQ向接收通知方发送通知

1、发起通知方将通知发给MQ。使用普通消息机制将通知发给MQ。 注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。 2、接收通知方监听 MQ。 3、接收通知方接收消息,业务处理完成回应ack。 4、消息重复机制(consumer-mq)的应用:接收通知方若没有回应ack则MQ会重复通知。 MQ会按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 (如果MQ采用rocketMq,在broker中可进行配置),直到达到通知要求的时间窗口上限。 5、消息校对机制(consumer-producer)的应用:接收通知方可通过消息校验接口来校验消息的一致性。

注意1:消息重复机制 mq-consumer:通知发起方将消息生产到MQ中,通知接收方来取出消息,如果没有来取,MQ按时间间隔通知。 注意2:消息重复机制 mq-consumer:去做通知的是MQ,而不是通知发起方。

方案2:利用 MQ的 ack机制,应用程序向接收通知方发送通知**

1、发起通知方将通知发给MQ。 使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ。 2、通知程序监听 MQ,接收MQ的消息。 区别:方案1中接收通知方直接监听MQ,方案2中由通知程序监听MQ。 消息重复机制(mq-application):通知程序若没有回应ack则MQ会重复通知。 3、通知程序收到通知后,通过互联网接口协议(如http) 调用 接收通知方案接口,完成通知。 通知程序 调用 接收通知方接口成功就表示通知成功,即消费MQ消息成功,MQ将不再向通知程序投递通知消息。 4、消息校对机制(consumer-producer):接收通知方可通过消息校对接口来校对消息的一致性。

方案1和方案2区别:

方案1和方案2的不同点:消息重复机制不同,消息校对机制是相同的 1、消息重复机制:方案1中接收通知方与MQ接口(mq-consumer),即接收通知方案监听 MQ,此方案主要应用producer与内部应用consumer之间的通知。 2、消息重复机制:方案2中由通知程序与MQ接口(mq-application),通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用producer与外部应用application之间的通知,例如支付宝、微信的支付结果通知。

# 最大努力通知方案的实现

1.业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。 2.主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。 3.主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。 4.业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。 5.如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。

# 最大努力通知方案的特点

1.用到的服务模式:可查询操作、幂等操作。 2.被动方的处理结果不影响主动方的处理结果; 3.适用于对业务最终一致性的时间敏感度低的系统; 4.适合跨企业的系统间的操作,或者企业内部比较独立的系统间的操作,比如银行通知、商户通知等;

# RocketMQ实现最大努力通知型事务

通过 RocketMQ中间件实现最大努力通知分布式事务,模拟转账过程。

在这里插入图片描述

两个账户在分别在不同的银行(张三在bank1、李四在bank2),bank1、bank2是两个微服务。交易过程是,张三给李四转账指定金额。

上述交易,张三扣减金额与给bank2发转账消息,两个操作必须是一个整体性的事务。

交互流程如下:

1、Bank1向MQ Server发送转账消息

2、Bank1执行本地事务,扣减金额

3、Bank2接收消息,执行本地事务,添加金额

4、当转账指定金额时候,模拟发生异常的情况。

准备工作:

安装RocketMQ

启动rocketMQ报错: 错误:找不到或无法加载主类 Files\Java\jdk1.8.0_131\jre\lib\ext

原因:JAVA_HOME的环境变量包含空格

JAVA_HOME=C:\Program Files\Java\jdk1.8.0_131

解决方法:修改runserver.cmd和runbroker.cmd文件

两个文件都做这样的修改,然后重新启动,就不会报那个错误了 。 注意最下面的%CLASSPATH%也是引用的上面定义的变量,也需要有双引号。否则可能也会报那个错误。

RocketMQ 默认允许每条消息最多重试 16 次,每次重试的间隔时间如下:

重试次数 与上次重试的间隔时间 重试次数 与上次重试的间隔时间
1 10 秒 9 7 分钟
2 30 秒 10 8 分钟
3 1 分钟 11 9 分钟
4 2 分钟 12 10 分钟
5 3 分钟 13 20 分钟
6 4 分钟 14 30 分钟
7 5 分钟 15 1 小时
8 6 分钟 16 2 小时

如果消息重试16次后仍然失败,消息将不再投递。转为进入死信队列。

导入bank1.sql和bank2.sql

项目地址:https://github.com/zysspace/pbdtx/tree/master/dtx-txmsg-demo

上次更新: 2024/01/30, 15:08:57
分布式事务
什么是分布式锁?

← 分布式事务 什么是分布式锁?→

最近更新
01
一个注解,优雅的实现接口幂等性
11-17
02
MySQL事务(超详细!!!)
10-14
03
阿里二面:Kafka中如何保证消息的顺序性?这周被问到两次了
10-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024

    辽ICP备2023001503号-2

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式