【转】分布式事务的常见解决方案

一、事务起步

1. 什么是事务

事务这种东西大家都耳熟能详了,通常指由一组操作组成的一个工作单元,这一整个组合要么全部成功,要么全部失败。

2. 本地事务

在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库和应用在同一服务器,所以基于关系型数据库的事务又被称为本地事务。

数据库事务具有原子性(Atomacity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

3. 分布式事务

随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用。分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称为分布式事务。

在这里插入图片描述

以上面图示举例,当需要创建订单的时候,将会涉及到订单和库存两个操作。那么左图操作两个系统最终需要写入两个数据库,我们需要保证分布式事务的一致性,这一点应该很容易理解,如果我们只创建了订单而没有去减少相应的库存量,就会出现超卖的现象。同样的像中间,虽然最终只涉及到一个数据库,但是同样需要保证一致性,右图同理。

二、CAP理论与BASE理论

1. CAP理论

CAP是一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)三个词语的缩写,我们先简单解释下这三个词语:

在这里插入图片描述

  • 一致性:服务A、B、C三个节点都存储了用户数据,要求三个节点在写操作后的读操作可以读取到的数据都是最新的状态;
  • 可用性:可用性即整个系统可以外提供服务;
  • 分区容忍性:分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个网络断开的场景我们称为分区容忍性;

那么在网络分区出现,也就是有多个节点分布在不同的子网中时,如何实现一致性呢?过程大概如下:

  1. 写入主数据库后将数据同步到从数据库;
  2. 写入主数据库后,在向数据库同步期间要将从数据库锁定,待同步完成后再释放锁,以免在新数据写入成功后,向从数据库查询到旧的数据。

所以,如果我们在数据库同步期间锁定从数据库,那么可用性就无法保证;如果不锁定,那么就会有访问向从数据库读取数据,可能会读取到旧的数据,此时又无法保证一致性。

所以用一句话概括CAP原理就是:当网络分区发生时,一致性和可用性难以两全。只能选择CP或者AP。

2. BASE理论

2.1 强一致性和最终一致性

CAP理论告诉我们一个分布式系统只能同时满足CAP三项中的两项,其中AP在实际应用中较多,AP即舍弃一致性,保证可用性和分区容忍性,但是在实际生产中很多场景也是需要实现一致性的。

比如主数据库向从数据库中同步数据,即使不要一致性,但是最终也要将数据同步成功来保证数据一致,这种一致性和CAP中的一致性不同,CAP中的一致性是要求任何时间查询每个节点数据都必须一致,它所强调的是一致性,但是最终一致性是允许在一段时间内各节点的数据不一致,但是经过一段时间每个节点的数据必须一致,它所强调的是最终一致性。

2.2 Base理论介绍

BASE 是 Basically Available(基本可用)、Soft State(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保核心功能可用,允许数据在一段时间内是不一致的,但是最终要达到一致状态。满足BASE理论的事务,我们称之为“柔性事务”。

  • 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出现问题了,商品依然可以正常浏览;
  • 软状态:由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的“支付中”、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态;
  • 最终一致:最终一致是指经过一段时间后,所有节点数据都会达成一致。如订单的“支付中”状态,最终会变成“支付成功”或者“支付失败“,使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。

三、2PC两阶段提交

2PC即两阶段提交(Two-phase Commit, 2PC),通过引入协调者(Coordinate)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

1. 运行过程

1.1 准备阶段

协调者询问参与者事务是否执行成功,参与者发回事务执行结果,如下图所示:

在这里插入图片描述

1.2 提交阶段

如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。

注意:在准备阶段,参与者只是执行了事务,但是并没有提交,只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。

在这里插入图片描述

2. XA方案

2PC的传统方案是在数据库层面实现的,如Oracle和MySQL都支持2PC协议,为了统一标准和减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,国际开放组织Open Group定义了分布式事务处理模型DTP(Distributed Transaction Processing Reference Model)。

下面以新用户注册时送积分的案例来说明:

在这里插入图片描述

执行流程如下:

  1. 应用程序(AP)持有用户库和积分库两个数据源;
  2. 应用程序(AP)通过TM通知用户库RM新增用户,同时通知积分库RM为该用户新增积分,RM此时并未提交事务,此时用户和积分资源锁定;
  3. TM收到执行回复,只要有一方失败则分别向其他RM发起事务回滚,回滚完毕,资源锁释放;
  4. TM收到执行回复,全部成功,此时向所有RM发起提交事务,提交完毕,资源锁释放。

DTP模型定义如下角色:

  • AP(Application Program):即应用程序,可以理解为使用DTP分布式事务的程序;
  • RM(Resource Program):即资源管理器,可以理解为事务的参与者,一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务;
  • TM(Transaction Manager):事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM。全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。

DTP模型定义TM和RM之间通讯的接口规范叫XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现2PC又称为XA方案

以上三个交互角色之间的交互方式如下:

  1. TM向AP提供应用程序编程接口,AP通过TM提交以及回滚事务;
  2. TM中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。

XA方案的缺陷:

  • 需要本地数据库支持XA协议;
  • 资源锁需要等待两个阶段结束才释放,性能较差。

3. Seata方案

Seata是由阿里开源的分布式事务框架。传统2PC的问题在Seata中得到了解决,通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能较好,且不长时间占用连接资源,以高效且对事务零侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。

3.1 设计思想

Seata的设计目标包括了对业务的无侵入,因此从业务无侵入的2PC方案着手,在传统的2PC的基础上演进,并解决2PC方案面临的问题。

Seata把一个分布式事务理解成一个包含了若干个分支事务全局事务。全局事务的职责是协调其下管辖的分支事务达成一致,要么一起提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据库的本地事务,下图是全局事务与分支事务的关系图:

在这里插入图片描述

与传统的2PC模型相比,Seata定义了三个组件来协调分布式事务的管理过程:

在这里插入图片描述

  • Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各个分支事务的提交或回滚;
  • Transaction Manager(TM):事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令;
  • Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚。

3.2 执行流程

依旧以新用户注册送积分举例Seata的分布式事务过程:

在这里插入图片描述

具体的执行流程如下:

  1. 用户服务的 TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个唯一的XID;
  2. 用户服务的 RM 向TC 注册分支事务,该分支事务在用户执行新增用户逻辑,并将其纳入 XID 对应全局事务的管辖;
  3. 用户服务执行分支事务,向用户表插入一条记录;
  4. 逻辑执行到远程调用积分服务时(XID在微服务调用链路的上下文传播)。积分服务的RM向TC注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入 XID 对应的全局事务的管辖;
  5. 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕,返回用户服务;
  6. 用户服务分支事务执行完毕;
  7. TM向TC发起针对XID的全局提交或回滚决议;
  8. TC调度XID下管辖的全部分支事务完成提交或回滚请求。

3.3 Seata的优势

架构层次方面,传统2PC方案的RM实际上是在数据库层,RM本质上就是数据库本身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。

两阶段提交方面,传统2PC无论第二阶段的决是commit还是rollback,事务性资源的锁都要保持到Phase2完成才能释放,而Seata的做法是在Phase1就将本地事务提交,这样就可以省去 Phase2 持锁的时间,整体提高效率。

3.4 二阶段回滚

二阶段如果是提交的话就很容易理解,因为”业务SQL“在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的”业务SQL“,还原业务数据。回滚方式便是用”before image“还原业务数据;但在还原前要首先校验脏写,对比”数据库当前业务数据“和”after image“,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

在这里插入图片描述

四、TCC事务补偿

TCC事务补偿是基于2PC实现的业务层事务控制方案,TCC这一词是由Try、Confirm和Cancel三个单词的首字母,含义如下:

  • Try阶段做业务检查(一致性)及资源预留(隔离),此阶段仅为一个初步操作,它和后续的Confirm一起才能真正构成一个完整的业务逻辑;
  • Confirm阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行Confirm。通常情况下,采用TCC则认为Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引入重试机制或人工处理;
  • Cancel阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采用TCC则认为Cancel阶段也是一定会成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。

TM首先发起所有的分支事务的Try操作,任何一个分支事务的Try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有的分支事务的Confirm操作,其中Conrirm/Cancel操作若执行失败,TM会进行那个重试。

我们用一个下单同时减少库存的业务来进行说明:

在这里插入图片描述

  1. Try

    下单业务由订单服务和库存服务协同完成,在try阶段订单和库存服务完成检查和预留资源;

    订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成的订单,不允许提交新订单);

    库存服务检查是否有充足的资源,并锁定资源;

  2. Confirm

    订单服务和库存服务完成Try后开始执行资源操作;

    订单服务向订单写一条订单信息;

    库存服务减去库存。

  3. Cancel

    如果订单服务和库存服务有一方出现失败则全部操作取消;

    订单服务需要删除新增的订单信息。

    库存服务将减去的库存再还原。

1. 空回滚

在没有调用TCC资源Try方法的情况下,调用了二阶段的Cancel方法,Cancel方法需要识别出这是一个空回滚,然后直接返回成功。

出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

解决思路的关键是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行了,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。前面已经说过TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链。再额外增加一张分支事务记录表,表中有全局事务ID贯穿和分支事务ID,第一阶段Try方法里会插入一条记录,表示一阶段执行了。Cancel接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

2. 幂等性

TM(即事务管理器)在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下s文,追踪和记录状态,由于Confirm和Cancel失败需进行重试,因此需要实现为幂等。幂等性是指同一操作无论请求多少次,其结果都相同。

所以为了保证TCC二阶段提交重试机不会引发数据不一致,要求TCC的二阶段Try、Confirm和Cancel接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没做好,很有可能导致数据不一致等严重问题。

3. 悬挂

悬挂就是对于一个分布式事务,其二阶段Cancel接口比Try接口先执行。

出现原因是在RPC调用分支事务Try时,先注册分支事务,再执行RPC调用,如果此时RPC调用的网络发生拥堵,通常RPC调用是有超时时间的,RPC超时以后,TM就会通知RM回滚分布式事务,可能回滚完成后,RPC请求才到达参与者真正执行,而一个Try方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们称之为悬挂,即业务资源预留后没法继续处理。

解决思路是如果二阶段执行完成,那一阶段就不再继续执行。在执行一阶段事务时判断在该全局事务下,”分支事务记录“表中是否已有二阶段事务记录,如果有则不执行Try。

五、可靠消息最终一致性

可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。

此方案是利用消息中间件完成,如下图:

事务发起方(消息生成方)将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间,事务参与方(消息消费方)和消息中间件之间都是通过网络通信,由于网络通信的不确定性会导致分布式事务问题。

在这里插入图片描述

因此可靠消息最终一致性解决方案要解决以下几个问题:

1. 方案问题

1.1 本地事务与消息发送的原子性问题

本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发送出去,否则就丢弃消息。即实现本地消息和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。

先来尝试下这种操作,先发送消息,再操作数据库:

begin transaction:// 1. 发送MQ// 2. 数据库操作
commit transaction;
  • 1
  • 2
  • 3
  • 4

这种情况下无法保证数据库操作与发送消息的一致性,因为可能发送消息成功,数据库操作失败。

那么现在来看第二种方案,先进行数据库操作,再发送消息:

begin transaction:// 1. 数据库操作// 2. 发送MQ
commit transaction;
  • 1
  • 2
  • 3
  • 4

这种情况下貌似没有问题,如果发送MQ消息失败,就会抛出异常,导致数据库事务回滚。但如果是超时异常,数据库回滚,但MQ其实已经正常发送了,同样会导致不一致。

1.2 事务参与方接收消息的可靠性

事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息。

1.3 消息重复消费的问题

由于网络2的存在,若某一消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。

要解决消息重复消费的问题就要实现事务参与方的方法幂等性。

2. 解决方案

2.1 本地消息表方案

本地消息方案是通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。

以注册送积分为例来说明:

一共有两个微服务交互,用户服务和积分服务,用户服务负责添加用户,积分服务负责添加积分:

在这里插入图片描述

交互流程如下:

2.1.1 用户注册

用户服务在本地事务新增用户增加”积分消息日志“。(用户表和消息表通过本地事务保证一致)

下边是伪代码

begin transaction;// 1. 新增用户// 2. 存储积分消息日志
commit transaction;
  • 1
  • 2
  • 3
  • 4

这种情况下,本地数据库操作与存储积分消息日志处于同一事务中,本地数据库操作与记录消息日志操作具备原子性。

2.1.2 定时任务扫描日志

如何保证将消息发送给消息队列呢?

经过第一步消息已经写到消息日志表中,可以启动独立的线程,定时对消息日志表中的消息进行扫描并发送至消息中间件,在消息中间件反馈成功后删除该消息日志,否则等待定时任务下一周期重试。

2.1.3 消费消息

如何保证消费者一定能消费到消息呢?

这里可以使用MQ的ACK(即消息确认)机制,消费者监听MQ,如果消费者接受到消息并且业务处理完成后MQ发送ACK(即消息确认),此时说明消费者正常消费消息完成,MQ将不再向消费者推送消息,否则消费者会不断重试向消费者来发送消息。

积分服务接收到”增加积分“消息,开始增加积分,积分增加成功后向消息中间件回应ACK,否则消息中间件将重复投递此消息。

由于消息会重复投递,积分服务的”增加积分“功能需要实现幂等性。

2.2 RocketMQ事务消息方案

RocketMQ是一个来自阿里巴巴的分布式消息中间件。RocketMQ事务消息设计则主要为了解决Producer端的消息发送与本地事务执行的原子性问题,RocketMQ设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力;RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。

在RocketMQ 4.3 后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决了 Producer 端的消息发送与本地事务执行的原子性问题。

在这里插入图片描述

执行流程如下:

我们还以注册送积分的例子来描述整个流程。

Producer即MQ发送方,本例中是用户服务,负责新增用户。MQ订阅方即消息消费方,本例中是积分服务,负责新增积分。

  1. Producer发送事务消息

    Producer(MQ发送方)发送事务消息至MQ Server,MQ Server将消息状态标记为 Prepared(预备状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。

    本例中,Producer发送”增加积分消息“到 MQ Server。

  2. MQ Server回应消息发送成功

    MQ Server接收到Producer发送给的消息则回应发送表示MQ已接受到消息。

  3. Producer执行本地事务

    Producer端执行业务代码逻辑,通过本地数据库事务控制。本例中,Producer执行添加用户操作。

  4. 消息投递

    若Producer本地事务执行成功功则自动向MQServer发送commit消息,MQ Server接收到commit消息后将”增加积 分消息“ 状态标记为可消费,此时MQ订阅方(积分服务)即正常消费消息;

    若Producer 本地事务执行失败则自动向MQServer发送rollback消息,MQ Server接收到rollback消息后 将删 除”增加积分消息“ 。

    MQ订阅方(积分服务)消费消息,消费成功则向MQ回应ack,否则将重复接收消息。这里ack默认自动回应,即 程序执行正常则自动回应ack。

  5. 事务回查

    如果执行Producer端本地事务过程中,执行端挂掉,或者超时,MQ Server将会不停的询问同组的其他 Producer 来获取事务执行状态,这个过程叫事务回查。MQ Server会根据事务回查结果来决定是否投递消息。

    以上主干流程已由RocketMQ实现,对用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此 只需关注本地事务的执行状态即可。

    RoacketMQ提供RocketMQQLocalTransactionListener接口:

    public interface RocketMQLocalTransactionListener {/** ‐ 发送prepare消息成功此方法被回调,该方法用于执行本地事务 ‐ @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id ‐ @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到 ‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调 */RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg); /** ‐ @param msg 通过获取transactionId来判断这条消息的本地事务执行状态 ‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调 */	RocketMQLocalTransactionState checkLocalTransaction(Message msg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    发送事务消息:以下是RocketMQ提供用于发送事务消息的API:

    TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup"); 
    producer.setNamesrvAddr("127.0.0.1:9876"); 
    producer.start(); 
    // 设置TransactionListener实现 
    producer.setTransactionListener(transactionListener); 
    // 发送事务消息 
    SendResult sendResult = producer.sendMessageInTransaction(msg, null);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

六、最大努力通知

1. 最大努力通知介绍

最大努力通知也是一种解决分布式事务的方案,下面以一个充值的案例举例:

在这里插入图片描述

交互流程:

  1. 账户系统调用充值系统接口

  2. 充值系统完成支付处理向账户系统发起充值结果通知若通知失败,则充值系统按策略进行重复通知

  3. 账户系统接收到充值结果通知修改充值状态。

  4. 账户系统未接收到通知会主动调用充值系统的接口查询充值结果。

通过上边的例子我们总结最大努力通知方案的目标:

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

具体包括:

  1. 有一定的消息重复通知机制

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

  2. 消息校对机制

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

最大努力通知与可靠消息一致性有什么不同?

  1. 解决方案思想不同

    可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知 方来保证。

    最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。

  2. 两者的业务应用场景不同

    可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。

    最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。

  3. 技术解决方向不同

    可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到。

    最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消 息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)。

2. 解决方案

通过对最大努力通知的理解,采用MQ的ack机制就可以实现最大努力通知。

方案1:

在这里插入图片描述

本方案是利用MQ的ack机制由MQ向接收通知方发送通知,流程如下:

  1. 发起通知方将通知发给MQ。

使用普通消息机制将通知发给MQ。

注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。(后边会讲)

  1. 接收通知方监听 MQ。

  2. 接收通知方接收消息,业务处理完成回应ack。

  3. 接收通知方若没有回应ack则MQ会重复通知。

MQ会按照间隔1 min、5 min、10 min、30 min、1 h、2 h、5 h、10 h的方式,逐步拉大通知间隔(如果 MQ 采用rocketMQ,在broker中可进行配置),直到达到通知要求的时间窗口上限。

5、接收通知方可通过消息校对接口来校对消息的一致性。

方案2:

本方案也是利用MQ的ack机制,与方案1不同的是应用程序向接收通知方发送通知,如下图:

在这里插入图片描述

交互流程如下:

  1. 发起通知方将通知发给MQ。

    使用可靠消息一致方案中的事务消息保证本地事务与消息的原子性,最终将通知先发给MQ。

  2. 通知程序监听 MQ,接收MQ的消息。

    方案1中接收通知方直接监听MQ,方案2中由通知程序监听MQ。通知程序若没有回应ack则MQ会重复通知。

  3. 通知程序通过互联网接口协议(如http、webservice)调用接收通知方案接口,完成通知。

    通知程序调用接收通知方案接口成功就表示通知成功,即消费MQ消息成功,MQ将不再向通知程序投递通知消 息。

  4. 接收通知方可通过消息校对接口来校对消息的一致性。

方案1和方案2的不同点:

  1. 方案1中接收通知方与MQ接口,即接收通知方案监听 MQ,此方案主要应用与内部应用之间的通知。

  2. 方案2中由通知程序与MQ接口,通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收 通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。

资料收集于网络。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436772.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java s.charat_Java中s.charAt(index)用于提取字符串s中的特定字符操作

charAt(int index)方法是一个能够用来检索特定索引下的字符的String实例的方法.charAt()方法返回指定索引位置的char值。索引范围为0~length()-1.如: str.charAt(0)检索str中的第一个字符,str.charAt(str.length()-1)检索最后一个字符.警告:在字符串s中越界访问字符…

【转】.NET框架简介

.NET 框架是由微软开发的软件开发平台,其最主要的两个组成部分是公共语言运行时 (CLR) 和框架类库 (FCL),基础类库 (BCL)是框架类库的一个子集。 .NET 框架简介 下图展示了 .NET 框架的主要结构。 其中,最下层的无疑就是操作系统了。 在 …

java比赛题目_【蓝桥杯2016第七届比赛题目】JAVA A组

1 煤球数目有一堆煤球,堆成三角棱锥形。具体:第一层放1个,第二层3个(排列成三角形),第三层6个(排列成三角形),第四层10个(排列成三角形),....如果一共有100层,共有多少个煤球?请填表…

【转】C#技术漫谈之垃圾回收机制(GC)

摘要:今天我们漫谈C#中的垃圾回收机制,本文将从垃圾回收机制的原理讲起,希望对大家有所帮助。 GC的前世与今生 虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John…

【转】git hub 使用小结

【转自:https://blog.csdn.net/yj310873325/article/details/79255134】 1.创建账号: https://github.com/ 2.下载客户端:https://git-scm.com/download 这是命令行模式,用着比较舒服,不是github的客户端,一路默认安…

【转】细说.NET 中的多线程 (一 概念)

为什么使用多线程 1.使用户界面能够及时响应用户的输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时响应客户的输入,这个时候我们往往需要让大量运算和响应用户输入这两个行为在不同的线程中进行。 2.效率原因 应用程序经常需要等待一…

【转】细说.NET中的多线程 (二 线程池)

上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源。最好的办法是使用线程池,线程池能够避免当前进程中大量的线程导致操作系统不停的进行线程切换,当线程数量到达…

java第二章_零基础学Java第二章

一、第一个代码案例1.1. HelloWorld案例1.1.1 代码执行流程我们写的代码都将以.java开头的文件保存,经过类编译器编译成.class的字节码文件,然后通过解释器翻译与机器交流1.1.1 代码执行流程1. 编写代码步骤首先定义一个类:public class 类名…

java volidate_volidate 学习

一:Volatile 变量具有synchronized的可见性,有序性 特性,但是不具备原子特性二:java memory model(jmm) java 内存模型形象理解见下图Java Memory Modela:java 线程读取共享内存变量流程:线程2 --> JMM …

【转】ABP源码分析一:整体项目结构及目录

ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序。 整个Abp的Infrastructure是以Abp这个package为核心模块(core)15个模块(module).其中13个依赖于Abp这个核心包。另外两个包(FluentMigration,Web.Resources)相对独…

【转】ABP源码分析二:ABP中配置的注册和初始化

一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法。执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数必然已完成了执行。 ABP开始的地方就是HttpApplication的构造函数。 如下图一,Abp定义了一…

【转】ABP源码分析三:ABP Module

Abp是基于模块化设计思想进行构建的。开发人员可以将自定义的功能以模块(module)的形式集成到ABP中。具体的功能都可以设计成一个单独的Module。Abp底层框架提供便捷的方法集成每个Module.下图是所有Abp自带的module.AbpModule是所有Module的基类&#x…

java weka 聚类_简单开源数据挖掘工具weka进行文本聚类

目前非代码的数据挖掘工具很多,但非开源,weka是一款开源软件。只要安装jdk环境就可使用(具体安装jdk可以百度)本文将论述如何不用代码,使用weka操作,通过与文档频数与单词权的特征选择方法进行文本聚类(数据为附件)第一步&#xf…

java 不识别enum_Java enum关键字不识别的快速解决办法

从别人那儿拷贝过来的myeclipse java工程,打开一看标红了一大片,仔细一看,原来是不识别enum关键字,这就有点尴尬了。我自己重新建了一个java工程,测试了下,假如我在新建工程的时候选择javase-1.6&#xff1…

【转】ABP源码分析四:Configuration

核心模块的配置 Configuration是ABP中设计比较巧妙的地方。其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配置中心化。配置中心化是一个支持模块开发的框架必备功能。 ABP中核心功能模块中的一些功能的运行时…

【转】ABP源码分析五:ABP初始化全过程

ABP在初始化阶段做了哪些操作,前面的四篇文章大致描述了一下。 为个更清楚的描述其脉络,做了张流程图以辅助说明。其中每一步都涉及很多细节,难以在一张图中全部表现出来。每一步的细节(会涉及到较多接口,类&#xff0…

【转】ABP源码分析六:依赖注入的实现

ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架。2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入的约定(规则),然后通过IocManager来读取这个规则完成依赖注入。…

【转】ABP源码分析七:Setting 以及 Mail

本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration。 Setting一般用于需要通过外部配置文件(或数据库)设置的简单类型数据(一般就是字符串),比如SMTP HOST.…

【转】ABP源码分析八:Logger集成

ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等。对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需…

java并发锁获取的方式_Java精通并发-notify方法详解及线程获取锁的方式分析

wait():在上一次https://www.cnblogs.com/webor2006/p/11404521.html中对于无参数的wait()方法的javadoc进行了解读,而它是调用了一个参数的重载方法,回忆下:其中如果传0代表无限等待,否则是等待指定的时间就会停止等待…