分布式-分布式事务理论、模型、方案、Seata框架

一、分布式事务理论模型

分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的分布性,比如多个数据库,或者MySQL和Redis两种不同存储设备的数据一致性等。在实际应用中,我们应该尽可能地从设计层面去避免分布式事务的问题,因为任何一种解决方案都会增加系统的复杂度。接下来我们了解一下分布式事务问题的常见解决方案。

1.1 X/Open分布式事务模型

X/Open DTP(X/Open Distributed Transaction Processing Reference Model)是X/Open这个组织义的一套分布式事务的标准这个标准提出了使用两阶段提交(2PC,Two-Phase-Commit)来保证分布式事的完整性。如图8-2所示,X/Open DTP中包含以下三种角色。

  • AP:Application,表示应用程序
  • RM:Resource Manager,表示资源管理器,比如数据库
  • TM:Transaction Manager,表示事务管理器,一般指事务协调者,负责协调和管理事务,提供AP编程接口或管理RM。可以理解为Spring中提供的Transaction Manager。
    在这里插入图片描述

图8-2所展示的角色和关系与本地事务的原理基本相同,唯一不同的在于,如果此时RM代表数据库,那么TM需要能够管理多个数据库的事务,大致实现步骤如下:

  • 配置TM,把多个RM注册到TM,相当于TM注册RM作为数据源

  • AP从TM管理的RM中获取连接,如果RM是数据库则获取JDBC连接

  • AP向TM发起一个全局事务,生成全局事务ID(XID),XID会通知各个RM

  • AP通过第二步获得的连接直接操作RM完成数据操作。这时,AP在每次操作时会把XID传递给RM。

  • AP结束全局事务,TM会通知各个RM全局事务结束

  • 根据各个RM的事务执行结果,执行提交或者回滚操作。

    实际上这里会涉及全局事务的概念。也就是说,在原本的单机事务下,会存在跨库事务的可见性问题,导致无法实现多节点事务的全局可控。而TM就是一个全局事务管理器,它可以管理多个资源管理器的事务。TM最终会根据各个分支事务的执行结果进行提交或者回滚,如果注册的所有分支事务中任何一个节点事务执行失败,为了保证数据的一致性,TM会触发各个RM的事务回滚操作。

    需要注意的是,TM和多个RM之间的事务控制,是基于XA协议(XA Specification)来完成的。XA协议是X/Open提出的分布式事务处理规范,也是分布式事务处理的工业标准,它定义了xa和ax系列的函数原型及功能描述、约束等。目前Oracle、MySQL、DB2都实现了XA接口,所以它们都可以作为RM。

二、分布式一致性协议

2.1 两阶段提交协议:准备阶段、提交阶段

在这里插入图片描述

2.1.1致命的问题

  1. 阻塞:准备阶段会锁定资源,直到下一步完成。
  2. 单点故障:如果协调者(事务管理器)宕机,会一直阻塞。
  3. 脑裂:协调者发送提交命令,有些参与者没有收到,不会执行事务。多个参与者之间不一致。

2.2.2 两阶段提交协议的流程

  • 准备阶段:协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则写redo或undo日志(Write-Ahead Log的一种),然后锁定资源,执行操作但不提交
  • 提交阶段:如果每个参与者都明确返回准备成功,则协调者向参与者发起提交指令,参与这提价资源变更的事务,释放锁定的资源.如果任何一个参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源.

致命问题

  • 阻塞:任何指令都要收到明确的响应,否则处于阻塞状态占用资源被一直锁定,不会被释放
  • 单点故障:协调者宕机,会一直阻塞,选举新的协调者替代原有协调者,但如果协调者发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,新上任的协调者无法处理这种情况.
  • 脑裂:协调者发送提交指令,有的参与者执行,有的参与者没有接收到事务就没执行,多个参与者之间不一致.

事务协调者的单点故障:如果协调者在第二阶段出现了故障,那么其他的参与者(RM)会一直处于锁定状态。

脑裂:导致数据不一致问题:在第二阶段中,事务协调者向所有参与者(RM)发送commit请求后,发生局部网络异常导致只有一部分参与者(RM)接收到了commit请求,这部分参与者(RM)收到请求后会执行commit操作,但是未收到commit请求的节点由于事务无法提交,导致数据出现不一致问题

2.2 三阶段提交协议:询问阶段、准备阶段、提交阶段

在这里插入图片描述
三阶段提交协议是两阶段提交协议的改进版本.它通过超时机制解决了阻塞的问题,把两个阶段增加为三个

  • 询问阶段:参与者只要回答是或不是,不需要真正操作,这个阶段超时会导致中止
  • 准备阶段:如果在寻味阶段所有参与者都返回可以执行操作,则协调者向参与者发送预执行请求,参与者写redo和undo日志,执行操作但不提交,这里逻辑和两阶段提交的准备阶段相似
  • 提交阶段:如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的操作,释放锁定的资源;如果任意参返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发送终止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两段提交协议的;

2.2.1 三阶段提交协议和两阶段提交协议相比有一些不同点

增加了一个CanCommit阶段,用于询问所有参与者是否可以执行事务操作并且响应,它的好处是,可以尽早发现无法执行操作而中止后续的行为。
在准备阶段之后,事务协调者和参与者都引入了超时机制,一旦超时,事务协调者和参与者会继续提交事务,并且认为处于成功状态,因为在这种情况下事务默认为成功的可能性比较大。

实际上,一旦超时,在三阶段提交协议下仍然可能出现数据不一致的情况,当然概率是比较小的。另外,最大的好处就是基于超时机制来避免资源的永久锁定。

*需要注意的是,不管是两阶段提交协议还是三阶段提交协议,都是数据一致性解决方案的实现,我们可以在实际应用中灵活调整。比如ZooKeeper集群中的数据一致性,就用到了优化版的两阶段提交协议,优化的地方在于,它不需要所有参与者在第一阶段返回成功才能提交事务,而是利用少数服从多数的投票机制来完成数据的提交或者回滚。

可以看出,从时序上来说,如果遇到极端情况,则 TCC 会有很多问题,例如,如果在取消时一些参与者收到指令,而另 些参与者没有收到指令,则整个系统仍然是不 致的 。对于这种复杂的情况,系统首先会通过补偿的方式尝试自动修复,如果系统无法修复,则必须由人参与解决TCC 的逻辑上看,可以说 TCC 是简化版的 阶段提交协议,解决了两阶段提交协议的阻塞问题,但是没有解决极端情况下会出现不一致和脑裂的问题 然而, TCC 通过自动化补手段 ,将需要人工处理的不 致情况降到最少,也是 种非常有用的解决方案。某著名 互联网公司在内部的 些中间件上实现了 TCC 模式。我们给出 个使用 TCC 的实际案例,在秒杀的场景中,用户发起下订单请求,应用层先询库存,确认商品库存还有余量,则锁定库存,此时订单状态为待支付,然后指引用户去支付由于某种原因用户支付失败或者支付超时,则系统会自动将锁定的库存解锁以供其他用户秒杀。

三、 分布式一致性理论

前面提到的两阶段提交和三阶段提交是XA协议解决分布式数据一致性问题的基本原理,但是这两种方案为了保证数据的强一致性,降低了可用性。实际上这里涉及分布式事务的两个理论模型。

3.1数据库一致性理论基础

ACID(酸)

  • A: Atomicity,原子性
  • C: Consistency,一致性
  • I: Isolation,隔离性
  • D: Durability,持久性

关系型数据库天生用于解决具有复杂事务场景的问题,完全满足ACID的特性。
具有ACID特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,每个事务都是原子的,或者成功或者失败,事务间是隔离的,互相完全不受影响,而且最终状态是持久落盘的

NoSQL完全不适合交易场景,主要用来做数据分析、ETL、报表、数据挖掘、推荐、日志处理、调用链跟踪等非核心交易场景
​现在我们来看看下订单和扣库存一致性问题,如果是在数据量较小的情况下,可以利用关系型数据库的强一致性解决,也就是把订单表和库存表放在同一个关系型数据库中,利用关系型数据库进行下订单和扣库存两个紧密相关的操作,达到订单和库存实时一致的结果。如果是大规模高并发的情况,由于业务规则的限制,无法将相关数据分到同一个数据库分片,这时就需要实现最终一致性。

3.2 CAP定理 (帽子原理)

由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统。

  • C: Consistency,一致性。在分布式系统中的所有数据备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本
  • A: Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
  • P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。
    CAP原理证明,任何分布式系统只可同时满足以上两点,无法三者兼顾。由于关系型数据库是单节点无复制的,因此不具有分区容忍性,但是具有一致性和可用性,而分布式的服务化系统都需要满足分区容忍性,那么我们必须在一致性和可用性之间进行权衡。

所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。
redis 就是 AP 模型 ,zk 就是 CP
一般来说基于CP 一般来说具有性能瓶颈问题;

基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等互联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BASE理论来实现的事务模型,它有两个特性:基本可用、柔性状态。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案。

3.3 BASE(碱)

BASE思想解决了CAP提出的分布式系统的一致性和可用性不可兼得的问题。BASE思想与ACID原理截然不同,它满足CAP原理,通过牺牲强一致性获得可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。
BASE平衡理论,简单来说就是在不同的场景下,可以分别利用ACID和BASE来解决分布式服务化系统的一致性问题。

  • BA: Basically Available,基本可用
  • S: Soft State,软状态,状态可以在一段时间内不同步
  • E: Eventually Consistent,最终一致,在一定的时间窗口内,最终数据达成一致即可
    软状态是实现BASE思想的方法,基本可用和最终一致是目标。以BASE思想实现的系统由于不保证强一致性,所有系统在处理请求的过程中存在短暂的不一致,在短暂的不一致的时间窗口内,请求处理处于临时状态中,系统在进行每步操作时,通过记录每个临时状态,在系统出现故障时可以从这些中间状态继续处理未完成的请求或者退回到原始状态,最终达到一致状态。

BASE对酸碱平衡的总结
解决一致性问题的三条实践经验:使用向上扩展(强悍的硬件)并运行专业的关系型数据库,能够保证强一致性;关系型数据库水平伸缩和分片,将相关数据分到数据库的同一个片上;最终一致性。

四、保证最终一致性的模式

在这里插入图片描述

4.0 两端协议、TCC协议缺点

在大规模、 高并发服务化系统中,一 个功能被拆分成多个具有单 功能的子功能, 一个流程会有多个系统的多个单 功能的服务组合实现,如果使用两阶段提交协议和 阶段提交协议,能解决系统间的 致性问题 除了这两个协议的自身问题,其实现也比较复杂、成本比较高,最要的是性能不好,相比来看, TCC协议更简单且更容易实现,但是 TCC协议由于个事务都需要执行 Try ,再执行 Confirm 略显雕肿,因此,现实系统的底线是仅仅 要达到最终一致性,而不需要实现专业 业的、复杂的一致 致性协议。

实现最终一致性有些非常有效、简单式的模式,下面就介绍这些模式及其应用场景实现最终一致性非常有效、简单的模式。

  • 查询模式:每个服务操作都需要有唯一的流水号标识,用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作。
    补偿模式:自动恢复、通知运营、技术运营。
  • 异步确认模式:将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式。
  • 定期校对模式:需要全局唯一ID
  • 可靠消息模式
  • 缓存一致性模式

4.1查询模式

在这里插入图片描述
每个服务操作都需要有唯一的流水号标识,用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作。​为了能够实现查询,每个服务操作都需要有唯 的流水号标识,也可使用此次服务操作对应的资源 ID 来标识,例如:请求流水号、订单号等首先,单笔查询操作是必须提供的,也鼓励使用单笔订单查询,这是因为每次调用 需要占用的负载是可控的。批量查询则根据 要来提供,如果使用了批 查询,则 要有合理的分页机制,并且必须限制分页的大小,以及对批量查询的吞吐量有容 评估、熔断、隔离和限流等措施;

4.2 补偿模式

有了上面的查询模式,在任何情况下,我们都能得知具体的操作所处的状态,如果整个操作都处于不正常的状态,则我们需要修正操作中有问题的子操作,这可能需要重新执行未完成的子操作,后者取消己经完成的子操作,通过修复使整个分布式系统达到 为了让系统终达到 致状态而做的努力都叫作补偿。
若业务操作发起方还没有收到业务操作执行方的 明确返回或者调用超时,则用查询模式查询业务执行方的执行状态,在获取业务执行方的状态后,如果业务员执行方已经执行成功,则业务发起方回调,告诉业务发起方执行成功;如果业务发起方的执行状态为失败或者未知,也叫作快速失败策略,然后执行业务操作的逆向操作,保证不被执行或者回滚已经执行的操作,让业务发起方、业务操作方的状态最终一致;

4.2.1补偿操作根据发起形式分为以下几种。

  • ​自动恢复:程序根据发生不一致的环境,通过继续进行未完成的操作,或者回滚己经完成的操作,来自动达到 致状态。
  • 通知运营:如果程序无法自动恢复,并且设计时考虑到了不一致的场景,则可以提供运营功能,通过运营手工进行补偿。
  • 技术运营:如果很不巧,系统无法自动回复,又没有运营功能,那么必须通过技术手段来解决,技术手段包括进行数据库变更或者代码变更,这是最糟的 种场景,也是我
    们在生 中尽 避免的场景;

4.3 异步确保模式

在这里插入图片描述
异步确保模式是补偿模式的 个典型案例,经常应用到使用方对响应时间要求不太高的场景中,通常把这类操作从主流程中摘除,通过异步的方式进行处理,处理后把结果通过通知系统通知给使用方 。这个方案的最大好处是能够对高并发流量进行消峰,例如:电商系统中的物流、配迭,以及支付系统中的计费、入账等。
在实践中将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式,只要定时系统足够健壮,则任何任务最终都会被成功执行
若对某个操作迟迟没有收到响应,则通过查询模式、补偿模式和异步确保模式来继续未完成的操作。

4.4 定期校对模式

在这里插入图片描述
系统在没有达到 致之前,系统间的状态是不 致的,甚至是混乱的, 要通过补偿操作来达到最终 致性的目的,但是如何来发现需要补偿的操作呢?
在操作主流程中的系统间执行校对操作,可以在事后异步地批量校对操作的状态,如果发现不致的操作,则进行补偿,补偿操作与补偿模式中的补偿操作是一致的。另外,实现定期校对的 个关键就是分布式系统中需要有 个自始 终唯 ID局唯一ID 有以下两种方法(分布式ID)。

  • 持久型: 使用数据库表自增字段或者 Sequence 生成,为了提高效率,每个应用节点可以缓存一个批次的 ID ,如果机器重启则可能会损失 部分 ID ,但是这并不会产问题。
  • 时间型:一般由机器号、业务号、时间、单节点内自增 组成,由于时间 般精确秒或者毫秒,因此不需要持久就能保证在分布式系统中全局唯 、粗略递增等;
    在实践中想在分布式系统中迅速定位问题时,可通过分布式系统的调用链跟踪系统进行,它能够跟踪 个请求的调用链。调用链是从二维的维度跟踪一个调用请求 最后形成一个调用树,其原理可参考谷歌 Dapper 论文及它的 个流行的开源实现项目 Pinpoint;

4.5 可靠消息模式

在分布式系统中,对于主流程中优先级 比较低的操作,大多采用异步的方式执行,也就是前面提到的异步确保模型,为了让异步操作的调用方和被调用方充分理解,也由于专业的消息队列本身具有可伸缩、可分片、可持久等功能,我们通常通过消息队列实现异步化。对于消息队列,我们需要建立特殊的设施来保证可靠的消息发送及 处理机的幕等性

4.5.1 消息的可靠发送

在这里插入图片描述
消息的可靠发送可以认为是尽最大努力发送消息通知,有以下两种实现方法。

  • 第一种,在发送消息之前将消息持久到数据库,状态标记为待发送 然后发送消息,如果发送成功,则将消息改为发送成功。定时任务定时从数据库捞取在一定时间内未发送的消息并将消息发送。可靠消息发送模式 1 ,如图2-10 所示。
  • 第二种,该实现方式与第一种类似,不同的是持久消息的数据库是独立的, 并不藕合在业务系统中。发送消息前,先发送 个预消息给某个第三方的消息管理器,消息管理器将其持到数据库,并标记状态为待发送,在发送成功后,标记消息为发送成功。定时任务定时从数据库中捞取一定时间内未发送的消息,查询业务系统是否要继续发送,根据查询结果来确定消息的状态。可靠消息发送模式 如图 2-11 所示。

4.5.2 消息处理器的幂等性处理

如果我们要保证可靠地发送消息,简单来说就是要保证消息一定发送出去,那么需要有重试机制。有了重试机制后,消息就一定会重复,那么我们需要对重复的问题进行处理。处理重复问题的最佳方式是保证操作的幂等性;
保证操作的幂等性的常用方法如下。
1)使用数据库表的唯一键进行滤重,拒绝重复的请求。
2)使用分布式表对请求进行滤重。
3)使用状态流转的方向性来滤重,通常使用数据库的行级锁来实现。
4)根据业务的特点,操作本身就是幕等的 例如 删除 个资源、增加一个资源、获得个资源等。

4.6 缓存一致性模式

在大规模、高并发系统中的一个常见的核心需求就是亿级的读需求,显然,关系型数据库并不是解决高并发读需求的最佳方案,互联网 的经典做法就是使用缓存来抗住读流量。下面是使用缓存来保证一致性的最佳实践。
1) 如果性能要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存。
2) 写缓存时数据一定要完整, 如果缓存数据的一部分有效 另一部分无效,则宁可在需要时回源数据库,也不要把部分数据放入缓存中。
3)使用缓存牺牲了一 致性,为了提高性能,数据库与缓存只需要保持弱一致性,而不需要保持强 致性,否则违背了使用缓存的初衷。
4) 读的顺序是先读缓存,后读数据库,写的顺序要先写数据库,后写缓存。

五、超时处理模式​

在这里插入图片描述
微服务的交互模式:同步调用模式、接口异步调用模式、消息队列异步处理模式

  • 同步与异步的抉择
  • 交互模式下超时问题的解决方案
  • 超时补偿的原则

5.1 接口调用模式

5.1.1 同步调用模式

在这里插入图片描述

在同步调用模式中,服务 1调用服务2 ,服务1 的线程阻塞等待服务2 返回处理结果,如果服务 2一直不返回处理结果, 则服务2一 直等待到超时为止;
同步调用模式适用于大规模、高并发的短小操作,而不适用于后端负载较高的场景,例如:几乎所有 JDBC 的实现完全使用 BIO 同步阻塞模式

5.1.2 接口异步调用模式

在这里插入图片描述

在接口异步调用模式中 ,服务1 请求服务2接 受理某项任务,服务2 受理后即刻返回给服1其受理结果 ,如果受理成功,则服务1继续做其他任务,而服务异2步地处理这项任务,直到服务2处理完这项任务后,才反向地通知服务1 任务已经完成,服务1再做后续处理;
接口异步调用模式适用于非核心链路上负载较高的处理环节,这个环节经常耗时较长,并且对时效性要求不高。 例如:在 B2C 电商系统中,一件商品售卖成功后,需要给相应的商户入账收入,这个过程对时效性要求不高,可以使用接口异步调用模式。

5.1.3 消息队到异步处理模式

在这里插入图片描述
**消息队列异步处理模式利用消息队列作为通信机制 ,在这种交互模式中 ,通常服1 只需将某种事件传递给服务2 ,而不需要等待服务2返回结果。**在这样的场景下,服务1 与服务2可以充分理解 ,并且在大规模、高并发的微服务系统中,消息队列对流量具有消峰的功能。
​消息队列异步处理模式与接口异步调用模式类 ,多应用于非核心 链路上负载较高的处理环节中,井且服务的上游不关心下游的处理结果,下游也不需要向上游返回处理结果 例如:
在电商系统中,用户下订单支付且交易成功后,后续的物流处理适合使用消息队列异步处理模式,因为物流发货属于物流和配送系统的职责,不应该影响交易,所以交易系统不需要对其有感知;
以上 种交互模式普遍应用于服务化和微服务架构中 ,它们之间没有绝对的好坏,只需要在特定场景下做出更适合的选择。

5.1.4 同步与异步的抉择

一些互联网公司试图通过规范来约束这三种方式的使用和选择,下面是笔者在工作中收集的两个不同的团队倡导的关于同步和异步选择的原则。

  • 1.尽量使用异步来替换同步操作。
  • 2.能用同步解决的问题,不要引入异步。

这两个原则从字面意义上看是完全不同的,甚至是矛盾的。实际上 这里的原则都没有错,只不过原则抽象得太干净利落,以至于没有给出适合这些原则的环境信息;

  • ​第一条原则是从业务功能的角度出发的,也就是从与用户或者使用方的交互模式出发的,如果业务逻辑允许,用户对产品的交互形态没有异议,则我们可以将一些耗时较长的、用户对响应没有特别要求的操作异步化,以此来减少核心链路的层级,释放系统的压力 。例如 12306在订票高峰期会开启订票异步模式 在购票后用户并不会马上得知购票的结果,而是后续通过查询得知结果,这样系统便赢得了为成千上万的用户处理购票逻辑的时间。

  • ​第二条原则是从技术和架构的角度出发的,这条原则应用的前提是同步能够解决问题,隐含了 个含义:如果性能不是问题,或者所处理的操作是短小的轻量级处理逻辑,那么同步调用方式是最理想不过的,因为这样不需要引入异步化的复杂处理流程。例如 :所有 JDBC实现使用同步阻塞的 BIO 模型,即访问数据库操作时无论是查询还是更新,原则上都是短小操作,不需要异步化,而是在同步过程中完成请求的受理和处理过程,这也是为什么不推荐将大数据存储到关系型数据库中,关系型数据库只存储交易相关的最小化核心信息。

5.2 交互模式下超时问题的解决方案

5.3 超时补偿的原则

在这里插入图片描述
对于 2.4.3 节的多个场景来说,
我们都需要对服务间同步超时造成的后果进行处理,而处理方法有快速失败和内部补偿两种,补偿模式也有调用方补偿和接收方补偿两种

具体使用哪种方式呢?
通过这个案例,我们很容易理解服务间调用超时补偿的原则服务 1调用服务2 ,如果服务2 响应服务1并且告诉服务 消息己接收,那么服务1任务就结束了;如果服务2 处理失败,那么服务2 应该负责重试或者补偿。在这种情况下,服务 2通常接收消息后先持久再告诉服务1 接收成功,随后服务2 才开始处理持久的消息,避免服务进程被杀掉而导致消息丢失,;

​服务1 调用服务2 ,如果服务2 没有给出明确的接收响应,例如网络超时,那么服务1应该持续进行重试,直到服务2 明确表示己经接收消息 在这种情况下容易出现重复
的消息,因此在服务 2中通常要保证滤重或者幕等性

六、 分布式事务问题的常见解决方案

在前面的章节中已经详细分析了分布式事务的问题及理论模型,并且基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等与联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BAS理论来实现的事务模型,它有两个特性:基本可用、柔性状态。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案。

6.1TCC 补偿型方案

TCC(Try-Confirm-Cancel)是一种比较成熟的分布式数据一致性解决方案它实际上是把一个完整的业务拆分为如下三个步骤。

  • Try:这个阶段主要是对数据的校验或者资源的预留
  • Confirm:确认真正执行的任务,只操作Try阶段预留的资源
  • Cancel:取消执行,释放Try阶段预留的资源

其实TCC是一种两阶段提交的思想,第一阶段通过Ty进行准备工作,第二阶段Confirm/Cancel表示Try阶段操作的确认和回滚。在分布式事务场景中,每个服务实现TCC之后,就作为其中的一个资源,参与到整个分布式事务中。然后主业务服务在第一阶段中分别调用所有TCC服务的Try方法。最后根据第一个阶段的执行情况来决定对第二阶段的Confirm或者Cancel
在这里插入图片描述
对于TCC的工作机制,我们举一个比较简单的例子。在一个理财App中,用户通过账户余额购买一个理财产品,这里涉及两个事务操作:

  • 在账户服务中,对用户账户余额进行扣减
  • 在理财产品服务中,对指定理财产品可申购金额进行扣减。

这两个事务操作在微服务架构下分别对应的是两个不同的微服务,以及独立的数据库操作,在TCC的工作机制中,首先针对账户服务和理财产品服务分别提供Try、Confirm和Cancel三个方法。

  • 在账户服务的Try方法中对实际申购金额进行冻结,Confirm方法把Try方法冻结的资金进行实际的扣减,Cancel方法把Try方法冻结的资金进行解冻。
  • 理财产品服务的Try方法中将本次申购的部分额度进行冻结,Confirm方法把Try方法中冻结的额度进行实际扣减,Cancel方法把Ty方法中冻结的额度进行释放。

在一个主业务方法中,分别调用这两个服务对外提供的处理方法(资金扣减、理财产品可申购额度扣减)这两个服务做实际业务处理时,会先调用Trv方法来做资源预留,如果这两个方法处理都正常,TCC事务协调器就会调用Confirm方法对预留资源进行实际应用。否则TCC事物协调器一旦感知到任何一个服务的Try方法处理失败,就会调用各个服务的Cancel方法进行回滚,从而保证数据的一致性。

在一些特殊情况下,比如理财产品服务宕机或者出现异常,导致该服务并没有收到TCC事务协调器的Cance或者Confirm请求,怎么办呢?没关系,TCC事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态。TCC事务协调器会根据操作日志来进行重试,以达到数据的最终一致性。

需要注意的是,TCC服务支持接口调用失败发起重试,所以TCC暴露的接口都需要满足幂等性;

6.2 基于可靠性消息的最终一致性方案

基于可靠性消息的最终一致性是互联网公司比较常用的分布式数据一致性解决方案,它主要利用消息中间件(KafkaRocketMQ或RabbitMQ)的可靠性机制来实现数据一致性的投递。

以电商平台的支付场景为例,用户完成订单的支付后不需要同步等待支付结果,可以继续做其他事情。但是对于系统来说,大部分是在发起支付之后,等到第三方支付平台提供异步支付结果通知,再根据结果来设置该订单的支付状态。并目如果是支付成功的状态,大部分电商平台基于营销策略还会给账户增加一定的积分奖励。所以,当系统接收到第三方返回的支付结果时,需要更新支付服务的支付状态,以及更新账户服务的积分余额,这里就涉及两个服务的数据-致性问题。

从这个场景中可以发现这里的数据一致性并不要求实时性,所以我们可以采用基于可靠性消息的最终一致性方案来保证支付服务和账户服务的数据一致性。如图8-7所示,支付服务收到支付结果通知后,先更新支付订单的状态,再发送一条消息到分布式消息队列中,账户服务会监听到指定队列的消息并进行相应的处理,完成数据的同步。

在这里插入图片描述
在这里插入图片描述
在图8-7的解决方案中,我们不难发现一些问题,就是支付服务的本地事务与发送消息这个操作的原子性问题,具体描述如下;
在这里插入图片描述
以上问题也有很多成熟的解决方案,以RocketMQ为例,它提供了事务消息模型,如图8-8所示,具体的执行逻辑如下:

  • 生产者发送一个事务消息到消息队列上,消息队列只记录这条消息的数据,此时消费者无法消费这条消息。
  • 生产者执行具体的业务逻辑,完成本地事务的操作
  • 接着生产者根据本地事务的执行结果发送一条确认消息给消息队列服务器,如果本地事务执行成功,则发送一个Commit消息,表示在第一步中发送的消息可以被消费,否则,消息队列服务器会把第一步存储的消息删除。
  • 如果生产者在执行本地事务的过程中因为某些情况一直未给消息队列服务器发送确认,那么消息队列服务器会定时主动回查生产者获取本地事务的执行结果,然后根据回查结果来决定这条消息是否需要投递给消费者。
  • 消息队列服务器上存储的消息被生产者确认之后,消费者就可以消费这条消息,消息消费完成之后发送一个确认标识给消息队列服务器,表示该消息投递成功。

在这里插入图片描述
在RocketMQ事务消息模型中,事务是由生产者来完成的,消费者不需要考虑,因为消息队列可靠性投递机制的存在,如果消费者没有签收该消息,那么消息队列服务器会重复投递,从而实现生产者的本地数据和消费者的本地数据在消息队列的机制下达到最终一致。
不难发现,在RocketMQ的事务消息模型中最核心的机制应该是事务回查,实际上查询模式在很多类似的场景中都可以应用。在分布式系统中,由于网络通信的存在,服务之间的远程通信除成功和失败两种结果外,还存在一种未知状态,比如网络超时。服务提供者可以提供一个查询接口向外部输出操作的执行状态,服务调用方可以通过调用该接口得知之前操作的结果并进行相应的处理。

6.3 最大努力通知型

最大努力通知型和基于可靠性消息的最终一致性方案的实现是类似的,它是一种比较简单的柔性事务解决方案也比较适用于对数据一致性要求不高的场景,最典型的使用场景是支付宝支付结果通知,实现流程如图8-9 所示;

在这里插入图片描述
在这里插入图片描述

七、分布式事务框架Seata

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。它提供了AT、TCC、Saga和XA事务模式,为开发者提供了一站式的分布式事务解决方案。其中TCC和XA我们前面分析过,AT和Saga这两种事务模式是什么呢?下面先来简单介绍一下这两种事务模式;

7.1 AT模式

AT模式是Seata最主推的分布式事务解决方案,它是基于XA演进而来的一种分布式事务模式,所以它同样分为三大模块,分别是TM、RM和TC,其中TM和RM作为Seata的客户端与业务系统集成,TC作为Seata的服务器独立部署。TM表示事务管理器(Transaction Manager),它负责向TC注册一个全局事务,并生成一个全局唯一的XID。在AT模式下,每个数据库资源被当作一个RM(Resource Manager),在业务层面通过JDBC标准的接口访问RM时,Seata会对所有请求进行拦截。每个本地事务进行提交时,RM都会向TC(TransactionCoordinator,事务协调器)注册一个分支事务。Seata的AT事务模式如图8-10所示。

在这里插入图片描述
具体执行流程如下:

  • TM向TC注册全局事务,并生成全局唯一的XID。
  • RM向TC注册分支事务,并将其纳入该XID对应的全局事务范围
  • RM向TC汇报资源的准备状态
  • TC汇总所有事务参与者的执行状态,决定分布式事务是全部回滚还是提交
  • TC通知所有RM提交/回滚事务

AT模式和XA一样,也是一个两阶提交事务模型,不过和XA相比,做了很多优化,笔者会在后续的章节中重点分析AT模式的实现原理

7.2 Saga模式

Saga模式又称为长事务解决方案,主要描述的是在没有两阶段提交的情况下如何解决分布式事务问题。其核心思想是:把一个业务流程中的长事务拆分为多个本地短事务,业务流程中的每个参与者都提交真实的提交给该本地短事务,当其中一个参与者事务执行失败,则通过补偿机制补偿前面已经成功的参与者。
如图8-11所示,Saga由一系列sub-transactionT阻成,每个T都有对应的补偿动作C补偿动作用于撤销T造成的数据变更结果。它和TCC相比,少了Try这个预留动作,每一个操作都真实地影响到数据库。

在这里插入图片描述
在这里插入图片描述

7.2.1Saga的优劣势

和XA或者TCC相比,它的优势包括:一阶段直接提交本地事务;没有锁等待,性能较高,在事件驱动的模式下短事务可以异步执行;补偿机制的实现比较简单
缺点是Saga并不提供原子性和隔离性支持,隔离性的影响是比较大的,比如用户购买一个商品后系统赠送一张优惠券,如果用户已经把优惠券使用了,那么事务如果出现异常要回滚时就会出现问题。

7.2.2Saga的实现方式

在一个电商平台的下单场景中,一般会涉及订单的创建、商品库存的扣减、钱包支付、积分赠送等操作,整体的时序图如图8-12所示。
电商平台下单的流程是一个典型的长事务场景,根据Saga模式的定义,先将长事务拆分成多个本地短事务每个服务的本地事务按照执行顺序逐一提交,一旦其中一个服务的事务出现异常,则采用补偿的方式逐一撤回这一过程的实现会涉及Saga的协调模式,它有两种常用的协调模式。

  • 事件/编排式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与者中,它们通过交换事件的方式来进行沟通。
  • 命令/协同式:把Saga的决策和执行顺序逻辑集中在一个Saga控制类中,它以命令/回复的方式与每项服务进行通信,告诉它们应该执行哪些操作。

在这里插入图片描述

7.2.2.1事件/编排式

在基于事件的编排模式中,第一个服务执行完一个本地事务之后,发送一个事件。这个事件会被一个或者多个服务监听,监听到事件的服务再执行本地事务并发布新的事件,此后一直延续这种事件触发模式,直到该业务流程中最后一个服务的本地事务执行结束,才意味着整个分布式长事务也执行结束,如图8-13所示。
在这里插入图片描述
这个流程看起来很复杂,但是却是比较常见的解决方案,下面简单描述一下具体的步骤

  1. 订单服务创建新的订单,把订单状态设置为待支付,并发布一个ORDERCREATE_EVENT事件
  2. 库存服务监听到ORDER_CREATEEVENT事件后,执行本地的库存冻结方法,如果执行成功,则发布一个ORDER_PREPARED_EVENT事件。
  3. 支付服务监听ORDERPREPAREDEVENT事件后,执行账户扣款方法,并发布PAY_ORDER
    EVENT事件·最后,积分服务监听PAY_ORDEREVENT事件,增加账户积分,并更新订单状态为成功。上述任一步骤执行失败,都会发送一个失败的事件,每个服务需要监听失败的情况根据实际需求进行逐一回滚。
7.2.2.2命令/协同式

命令/协同式需要定义一个Saga协调器,负责告诉每一个参与者该做什么,Saga协调器以命令/回复的方式与每项服务进行通信,如图8-14所示

在这里插入图片描述
命令/协同式的实现步骤如下

  1. 订单服务首先创建一个订单,然后创建一个订单Saga协调器,启动订单事务。
    Saga协调器向库存服务发送冻结库存命令,库存服务通过Order Saga Reply Queue回复执行结果。
  2. 接着,Saga协调器继续向支付服务发起账户扣款命令,支付服务通过Order Saga Reply Queue回复执行结果
  3. 最后,Saga协调器向积分服务发起增加积分命令,积分服务回复执行结果
    需要注意的是,订单Saga协调器必须提前知道“创建订单事务”的所有流程(Seata是通过基于JSON的状态机引擎来实现的),并且在整个流程中任何一个环节执行失败,它都需要向每个参与者发送命令撤销之前的事务操作。

7.3 Seata的安装

Seata是一个需要独立部署的中间件,除直接部署外,它还支持多种部署方式,比如Docker、KubernetesHelm。本节主要讲解直接安装的方式

  1. 在Seata官网下载1.0.0版本的安装包,100是笔者写作时最新的发布版本
  2. 进入$(SEATAHOMENbin目录,根据系统类型执行相应的启动脚本,在Linux/Max下的执行命令如下。

在这里插入图片描述

7.3.1Seata 的存储方式

在这里插入图片描述设置事务日志和数据库连接
在这里插入图片描述
参数说明如下

  • h:注册到注册中心的IP地址,Seata-Server可以把自己注册到注册中心,支持Nacos、EurekaRedisZooKeeper.Consul、 Etcd3、Sofa.
  • p:Server RPC监听端口。
  • m:全局事务会话信息存储模式,包括fledb,优先读取启动参数
  • n:Servernode,有多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突;

7.3.2 Seata服务端配置中心说明

在${SEATA_HOME)\conf目录下有两个配置文件,分别是registry.con和file.conf.

registry.conf配置说明

registry
registry.conf中包含两项配置:registry、config,完整的配置内容如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
registry表示配置Seata服务注册的地址,支持目前市面上所有主流的注册中心组件。它的配置非常简单,通过type指定注册中心的类型,然后根据指定的类型配置对应的服务地址信息,比如当type=nacos时,则匹配到Nacos的配置项如下。
在这里插入图片描述
type默认为file,它表示不依赖于配置中心,在fle类型下,可以不依赖第三方注册中心快速集成Seata。不过,file类型不具备注册中心的动态发现和动态配置功能。
config
config配置用于配置Seata服务端的配置文件地址,也就是可以通过config配置指定Seata服务端的配置信息的加载位置,它支持从远程配置中心读取和本地文件读取两种方式。如果配置为远程配置中心,可以使用type指定,配置形式和registry相同。
在这里插入图片描述
在默认情况下type=file,它会加载file.conf文件中的配置信息

file.conf配置说明

file.conf存储的是Seata服务端的配置信息,完整配置如下。它包含transport、server、metrics,分别表示协议配置、服务端配置、监控。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Seata服务端启动时会加载file.conf中的配置参数,这些参数读者不需要记,只需要知道这些参数可以优化即在Seata官网上对参数有非常详细的说明。

7.3.3 从配置中心加载Seata的配置

从前面的分析过程中我们知道,Seata服务在启动时可以将自己注册到注册中心上,并且file.conf文件中的配置同样可以保存在配置中心,接下来我们尝试把配置信息存储到Nacos上

将配置上传到Nacos

在GitHub的官方代码托管平台下载Seata的源码,在源码包中有一个script文件夹(目前只在源码包中存在),目录结构如下:

  • client:存放客户端的SQL脚本,参数配置
  • ·config-center:各个配置中心参数导入脚本,config.txt(包含server和client)为通用参数文件
  • server:服务端数据库脚本及各个容器配置

进入config-center目录,包含config.txt和不同配置中心的目录(该目录下包含shel脚本和py脚本)。其中config.txt存放的是Seata客户端和服务端的所有配置信息。
在config-center\nacos目录下,执行如下脚本:

sh nacos-config.sh-h 192168.216.128-p 8848 -g SEATE_GROUP

该脚本的作用是把config.txt中的配置信息上传到Nacos配置中心。由于confiq.txt中提供的是默认配置,在实际使用时可以先修改该文件中的内容,再执行上传操作(当然,也可以上传完成之后在Nacos控制台上根据实际需求修改对应的配置项)
脚本如果执行正确,将会在Nacos配置中心看到如图8-15所示的配置列表
在这里插入图片描述

Seata服务端修改配置加载位置

进入$SEATA_HOME)\conf目录,修改registry.conf文件中的config段,完整配置如下:
在这里插入图片描述

7.4 关于事务分组的说明

在Seata Client端的file.conf置中有一个属性vgroup_mapping,它表示事务分组映射,是Seata的资源逻辑,类似于服务实例,它的主要作用是根据分组来获取Seata Server的服务实例。

7.4.1服务分组的工作机制

首先,在应用程序中需要配置事务分组,也就是使用GlobalTransactionScanner构造方法中的txServiceGroup参数,这个参数有如下几种赋值方式

  • 默认情况下,获取springapplicationname的值+"-seata-service-group”。
  • 在Spring Cloud Alibaba Seata中,可以使用spring.cloud.alibaba.seata.tx-service-group赋值
  • 在Seata-Spring-Boot-Starter中,可以使用seata.tx-service-group赋值

然后,Seata客户端会根据应用程序的txServiceGroup去指定位置(file.conf或者远程配置中心)查找servicevgroup_mapping{txServiceGroup)对应的配置值,该值代表TC集群(Seata Server)的名称
最后,程序会根据集群名称去配置中心或者file.conf仲获得对应的服务列表,也就是custerName.grouplist

对应的TC集群真实的服务列表。实现原理如图8-17所示,具体步骤描述如下

  • 获取事务分组spring-cloud-seata-repo配置的值Agroup。
  • 拿到事务分组的值Agroup,拼接成servicevgroup_mapping.Agroup,去配置中心查找集群名得到default。
    拼接service.default.grouplist,查找集群名对应的Seata Server服务地址:192.16811:8091。

在这里插入图片描述

思考事务分组设计

通过上述分析可以发现,在客户端获取服务器地址并没有直接采用服务名称,而是增加了一层事务分组映射到集群的配置。这样做的好处在于,事务分组可以作为资源的逻辑隔离单位,当某个集群出现故障时,可以把故障缩减到服务级别,实现快速故障转移,只需要切换对应的分组即可。

7.5 SeataAT模式的实现原理

在前面的章节中提到过,AT模式是基于XA事务模型演进而来的,所以它的整体机制也是一个改进版的两阶段提交协议。

  • 第一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 第二阶段:提交异步化,非常快速地完成。回滚通过第一阶段的回滚日志进行反向补偿。

AT模式事务的整体执行流程在8.3.1节中讲过,读者可以将书翻回去再复习一遍。

下面我们详细分析在整个执行流程中,每一个阶段的具体实现原理。同时,为了更好地理解AT模式的工作机制,我们以库存表tbl_repo来描述整个工作过程,表结构及数据如图8-18所示

在这里插入图片描述

7.5.1AT模式第一阶段的实现原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.5.2 AT模式第二阶段的原理分析

TC接收到所有事务分支的事务状态汇报之后,决定对全局事务进行提交或者回滚。

事务提交

如果决定是全局提交,说明此时所有分支事务已经完成了提交,只需要清理UNDO_LOG日志即可。这也是和XA最大的不同点,其实在第一阶段各个分支事务的本地事务已经提交了,所以这里并不需要TC来触发所有分支事务的提交,如图8-21所示。
在这里插入图片描述
图8-21中
事务提交的执行流程是:

  • 1.分支事务收到TC的提交请求后把请求放入一个异步任务队列中,并马上返回提交成功的结果给TC。
  • 2.从异步队列中执行分支,提交请求,批量删除相应UNDO_LOG日志。

在第一步中,TC并不需要同步知道分支事务的处理结果,所以分支事务才会采用异步的方式来执行。因为对于提交操作来说,分支事务只需要清除UNDO_LOG日志即可,而即便日志清除失败,也不会对整个分布式事务产生任何影响。

事务回滚

在整个全局事务链中,任何一个事务分支执行失败,全局事务都会进入事务回滚流程。各位读者应该不难猜出,所谓的回滚无非就是根据UNDO LOG中记录的数据镜像进行补偿。如果全局事务回滚成功,数据的一致性就得到了保证。全局事务回滚流程如图8-22所示。
在这里插入图片描述
在这里插入图片描述

7.5.3 关于事务的隔离性保证

我们知道,在ACID事务特性中,有一个隔离性,所谓的隔离性是指多个用户并发访问数据库时,数据库为每个用户开启的事务不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
在AT模式中,当多个全局事务操作同一张表时,它的事务隔离性保证是基于全局锁来实现的,本节分别针对写隔离与读隔离进行分析。

7.5.3.1 写隔离

写隔离是为了在多个全局事务针对同一张表的同一个字段进行更新操作时,避免全局事务在没有被提交之前被其他全局事务修改。写隔离的主要实现是,在第一阶段本地事务提交之前,确保拿到全局锁。如果拿不到全局锁,则不能提交本地事务。并且获取全局锁的尝试会有一个范围限制,如果超出范围将会放弃全局锁的获取,并且回滚事务,释放本地锁。
以一个具体的案例来分析,假设有两个全局事务tx1和tx2,分别对tbl_repo表的count字段进行更新操作,count的初始值为100

  • tx1先执行,开启本地事务,拿到本地锁(数据库级别的锁),更新count=count-1=99,在本地事务提交之前,需要拿到该记录的全局锁,然后提交本地事务并释放本地锁。
  • tx2接着执行,同样先开启本地事务,拿到本地锁,更新count=count-1=98本地事务提交之前,也尝试获取该记录的全局锁(全局锁由TC控制),由于该全局锁已经被tx1获取了,所以tx2需要等待以重新获取全局锁。如果全局事务执行整体提交,那么提交时序图如图8-23所示
    在这里插入图片描述
    **如果tx1在第二阶段执行全局回滚,那么txl需要重新获得该数据的本地锁,然后根据UNDO_LOG进行事务回滚。此时,如果tx2仍然在等待该记录的全局锁,同时持有本地锁,那么tx1分支事务的回滚会失败。tx1分支事务的回滚过程会一直重试,直到tx2的全局锁获取超时,放弃全局锁并回滚本地事务、释放本地锁,之后tx1的分支事务才会回滚成功。而在整个过程中,全局锁在tx1结束之前一直被tx1持有,所以不会发生脏写的问题。**全同事务回滚时序图如图8-24所示。
    在这里插入图片描述
7.5.3.2 读隔离

我们知道数据库有如下4种隔离级别。

  • Read Uncommitted:读取未提交内容
  • Read Committed:读取提交内容。
  • Repeatable Read:可重读
  • Serializable:可串行化。
    在数据库本地事务隔离级别为Read Committed或者以上时,SeataAT事务模式的默认全局事务隔离级别是Read Uncommitted,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果,产生脏读。这在最终一致性事务模型中是允许存在的,并且在大部分分布式事务场景中都可以接受脏读。

在某些特定场景中要求事务隔离级别必须为Read Committed,目前Seata是通过SelectForUpdateExecutor执行器对SELECT FOR UPDATE语句进行代理的,SELECT FOR UPDATE语句在执行时会申请全局锁。如图8-25所示,如果全局锁已经被其他分支事务持有,则释放本地锁(回滚SELECT FORUPDATE语句的本地执行)并重试。在这个过程中,查询请求会被“BLOCKING”,直到全局锁被拿到,也就是读取的相关数据已提交时才返回。

在这里插入图片描述

8 小结

这一章的篇幅较长,主要是在分布式事务领域有太多的理论模型,同时涉及分布式事务的场景也相对复杂一本章主要针对Seata中的AT事务模式进行了详细的讲解,它是Seata主推的一个分布式事务解决方案。在使用AT模式时有一个前提,RM必须是支持本地事务的关系型数据库
Seata的AT模式基于本地事务的特性,通过拦截并解析SQL的方式,记录自定义的回滚日志。虽然是根据XA事务模型演进而来的,但是它打破了XA协议阻塞性的制约,在一致性、性能、易用性3个方面取得了平衡:在达到确定一致性(非最终一致)的前提下,即保障较高的性能,又能完全不侵入业务。
Seata支持TCC、AT、Saga,在大部分场景中,Seata的AT模式都适用。同时可以根据实际需求选择TCC或者Saga等解决方案。

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

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

相关文章

安全护航:迅软DSE加密软件在设计院所图纸文件中的成功案例分享

近年来,随着信息化强国战略和可持续发展方针的推动,国内各大设计院所和建筑机构积极推进信息化建设,将电子文件作为主要的信息存储方式,并将其作为单位内外部信息交换的关键载体。在这一背景下,创新设计作为建筑设计单…

springoot集成kafka

1.常见两种模式 2.高可用 和 负载均衡 组内:消费者 一个只能消费一个分区 组外:消费者消费是订阅者模式

ac转dc电源芯片SM7025 支持12V/18V输出电压

AC转DC电源芯片是一种能够将交流电转换为直流电的重要器件,广泛应用于电子设备和电源系统中。它可以提供稳定的直流电源,为设备的正常运行提供保障。 AC转DC电源芯片的工作原理是利用内部的整流、滤波、变压器和稳压等电路,将输入的交流电转换…

Nginx与keepalived高可用节点搭建实验

本文主要介绍了nginxkeepalived的部署实验,并简单说明了nginx的集中负载分担模式 简介: nginx可以通过反向代理功能对后端服务器实现负载均衡功能 keepalived 是一种高可用集群选举软件 keepalived架构 分为三个模块: 1、keepalived core …

消息队列(MQ)

对于 MQ 来说,不管是 RocketMQ、Kafka 还是其他消息队列,它们的本质都是:一发一存一消费。下面我们以这个本质作为根,一起由浅入深地聊聊 MQ。 01 从 MQ 的本质说起 将 MQ 掰开了揉碎了来看,都是「一发一存一消费」&…

arthas一次操作实现递归分析下游方法的耗时

背景 使用arthas的trace分析方法的耗时时,我们一般只能分析下一层的方法的耗时,然后一层一层的递归进去找到耗时最长的那个方法,有没有一种方式可以一次trace分析就可以把所有要关注的下层所有的耗时都打印出来? 解决方式 使用…

vue的slot插槽详解

目录 一、基本用法 在上面的例子中,我们在子组件中定义了一个插槽,然后在父组件中使用标签,并在标签内部放置了一个 标签作为插槽的内容。当父组件被渲染时,插槽的内容将被替换为实际传入的内容。 二、具名插槽 在上面的例子…

CleanMyMac X这一款mac电脑清理垃圾文件软件好用吗?

CleanMyMac X您的 Mac。极速如新。点按一下,即可优化调整整个 Mac畅享智能扫描 — 这款超级简单的工具用于优化您的 Mac。只需点按一下,即可运行所有任务,让您的 Mac 保持干净、快速并得到最佳防护。CleanMyMac 是一款功能强大的 Mac 清理程序…

一篇文章了解Flutter Json系列化和反序列化

目录 一. 使用dart:convert实现JSON格式编解码1. 生成数据模型类2. 将JSON数据转化成数据模型类3. 数据模型类转化成JSON字符串 二、借助json_serializable实现Json编解码1.添加json_annotation、build_runner、json_serializable依赖2. 创建一个数据模型类3. 使用命令行生成JS…

【科研论文】检索证明、科技查新、查收查引(附教育部、科技部查新工作站名单)

文章目录 1、什么是科技查新 & 查收查引2、科技查新 & 查收查引有什么用3、如何办理科技查新 & 查收查引4、教育部科技查新工作站5、科技部认定的查新机构名单 1、什么是科技查新 & 查收查引 科技查新是国家科技部为避免科研课题重复立项和客观正确地判别科研…

STM32 寄存器配置笔记——USART DMA接收

一、简介 本文主要介绍STM32如何配合USART的IDLE中断实现USART DMA接收不定长的数据。其中使用的接收缓存还是延用前面博客写的乒乓缓存。使用DMA USART接收来替代中断方式或轮询方式的接收主要是为了提高代码的运行效率,中断方式的接收,每接收一个字节便…

5G边缘网关如何助力打造隧道巡检机器人

我国已建成全世界里程最长的公路网、铁路网,是国民经济发展与国家现代化的重要支撑。我国幅员辽阔,地理环境复杂,公路/铁路的延伸也伴随着许多隧道的建设,由于隧道所穿越山体的地质条件复杂,对于隧道的监测、管理与养护…

芒果RT-DETR改进实验:深度集成版目标检测 RT-DETR 热力图来了!支持自定义数据集训练出来的模型

💡该教程为改进RT-DETR指南,属于《芒果书》📚系列,包含大量的原创改进方式🚀 💡🚀🚀🚀内含改进源代码 按步骤操作运行改进后的代码即可💡更方便的统计更多实验数据,方便写作 芒果RT-DETR改进实验:深度集成版目标检测 RT-DETR 热力图来了!支持自定义数据集…

STM32在CTF中的应用和快速解题

题目给的是bin文件,基本上就是需要我们手动修复的固件逆向。 如果给的是hex文件,我们可能需要使用MKD进行动态调试 主要还是以做题为目的 详细的可以去看文档:https://pdf1.alldatasheet.com/datasheet-pdf/view/201596/STMICROELECTRONIC…

五、Java核心数组篇

1.数组 概念: ​ 指的是一种容器,可以同来存储同种数据类型的多个值。 ​ 但是数组容器在存储数据的时候,需要结合隐式转换考虑。 比如: ​ 定义了一个int类型的数组。那么boolean。double类型的数据是不能存到这个数组中的&…

23.Java程序设计--基于SSM框架的移动端家庭客栈管理系统的设计与实现

第一章:引言 1.1 背景 客栈业务背景移动端应用需求增长趋势 1.2 研究动机 移动端管理系统的需求SSM框架的选择和优势 1.3 研究目的与意义 提高家庭客栈管理效率移动端解决方案的创新 第二章:相关技术和理论综述 2.1 SSM框架简介 Spring框架Spri…

swagger的ApiModelProperty设置字段的顺序

需求 让前端可以直接通过swagger就能知道各个字段是什么意思 如何配置 比如,我们设置了ApiModelProperty ApiModelProperty("用户主键")private Long userId;在swagger页面能直接看到注释 但是这个顺序是按照字母排序的,明显不符合我们的要…

在IDEA中使用Git 、远程仓库克隆工程到本地

4.1 在IDEA中配置Git 安装好IntelliJ IDEA后,如果Git安装在默认路径下,那么idea会自动找到git的位置,如果更改了Git的安装位置则需要手动配置下Git的路径。 选择File→Settings打开设置窗口,找到Version Control下的git选项&…

Java 基础学习(十)包装类、异常

1 包装类 1.1 包装类概述 1.1.1 什么是包装类 在进行类型转换时,有一种特殊的转换:将 int 这样的基本数据类型转换为对象,如下图所示: 所有基本类型都有一个与之对应的类,即包装类(wrapper)。…

机器学习--归一化处理

归一化 归一化的目的 归一化的一个目的是,使得梯度下降在不同维度 θ \theta θ 参数(不同数量级)上,可以步调一致协同的进行梯度下降。这就好比社会主义,一小部分人先富裕起来了,先富带后富&#xff0c…