六,高并发微服务

六,分布式事务

6.1 分布式事务介绍

6.1.1 什么是事务?

   数据库事务(简称:事务,Transaction)是指数据库执⾏过程中的⼀个逻辑单位,⼀个事务会有多个业务操作构成。

connection.setAutoCommit(false); //开启事务
业务操作A:扣减库存
业务操作B:创建订单
业务操作C:扣款
业务操作D:增加⽤户积分
connection.commit(); //提交事务
connection.rollback(); //有异常回滚事务

事务拥有以下四个特性,习惯上被称为ACID特性:
   原⼦性(Atomicity):事务作为⼀个整体被执⾏,包含在其中的对数据库的操作要么全部被执⾏,要么都不执⾏。
   ⼀致性(Consistency):事务应确保数据库的状态从⼀个⼀致状态转变为另⼀个⼀致状态。⼀致状态是指数据库中的数据应满⾜完整性约束。除此之外,⼀致性还有另外⼀层语义,就是事务的中间状态不能被观察到(这层语义也有说应该属于原⼦性)。
   隔离性(Isolation):多个事务并发执⾏时,⼀个事务的执⾏不应影响其他事务的执⾏,如同只有这⼀个操作在被数据库所执⾏⼀样。
   持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。在事务结束时,此操作将不可逆转。

6.1.2 本地事务

   起初,事务仅限于对单⼀数据库资源的访问控制,架构服务化以后,事务的概念延伸到了服务中。倘若将⼀个单⼀的服务操作作为⼀个事务,那么整个服务操作只能涉及⼀个单⼀的数据库资源,这类基于单个服务单⼀数据库资源访问的事务,被称为本地事务(Local Transaction)。
在这里插入图片描述

6.1.3 什么是分布式事务

   分布式事务指事务的参与者、⽀持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应⽤,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据⼀致性。

6.1.4 分布式事务应⽤架构

   本地事务主要限制在单个会话内,不涉及多个数据库资源。但是在基于SOA(Service-OrientedArchitecture,⾯向服务架构)的分布式应⽤环境下,越来越多的应⽤要求对多个数据库资源,多个服务的访问都能纳⼊到同⼀个事务当中,分布式事务应运⽽⽣。

(1)单⼀服务分布式事务

最早的分布式事务应⽤架构很简单,不涉及服务间的访问调⽤,仅仅是服务内操作涉及到对多个数据库资源的访问。
在这里插入图片描述

(2)多服务分布式事务

当⼀个服务操作访问不同的数据库资源,⼜希望对它们的访问具有事务特性时,就需要采⽤分布式事务来协调所有的事务参与者。

对于上⾯介绍的分布式事务应⽤架构,尽管⼀个服务操作会访问多个数据库资源,但是毕竟整个事务还是控制在单⼀服务的内部。如果⼀个服务操作需要调⽤另外⼀个服务,这时的事务就需要跨越多个服务了。在这种情况下,起始于某个服务的事务在调⽤另外⼀个服务的时候,需要以某种机制流转到另外⼀个服务,从⽽使被调⽤的服务访问的资源也⾃动加⼊到该事务当中来。下图反映了这样⼀个跨越多个服务的分布式事务:

在这里插入图片描述

(3) 多服务多数据源分布式事务

如果将上⾯这两种场景(⼀个服务可以调⽤多个数据库资源,也可以调⽤其他服务)结合在⼀起,对此进⾏延伸,整个分布式事务的参与者将会组成如下图所示的树形拓扑结构。在⼀个跨服务的分布式事务中,事务的发起者和提交均系同⼀个,它可以是整个调⽤的客户端,也可以是客户端最先调⽤的那个服务。
在这里插入图片描述
较之基于单⼀数据库资源访问的本地事务,分布式事务的应⽤架构更为复杂。在不同的分布式应⽤架构下,实现⼀个分布式事务要考虑的问题并不完全⼀样,⽐如对多资源的协调、事务的跨服务传播等,实现机制也是复杂多变。
在这里插入图片描述

6.2 CAP定理

6.2.1 定义

在理论计算机科学中,CAP定理(CAP theorem),⼜被称作布鲁尔定理(Brewer’s theorem),它指出对于⼀个分布式计算系统来说,不可能同时满⾜以下三点
在这里插入图片描述
⽽CAP指的就是上述三个指标的⾸字⺟

6.2.2 分别解释每个指标

⼀致性(Consistency)
   这⾥指的是强⼀致性,⽽最终⼀致性
   在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果
也就是说,在⼀致性系统中,⼀旦客户端将值写⼊任何⼀台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写⼊的数据
⽤如下系统进⾏解释
在这里插入图片描述

  1. 客户端向G1写⼊数据v1,并等待响应
  2. 此时,G1服务器的数据为v1,⽽G2服务器的数据为v0,两者不⼀致
  3. 接着,在返回响应给客户端之前,G2服务器会⾃动同步G1服务器的数据,使得G2服务器的数据是v1
  4. ⼀致性保证了不管向哪台服务器(⽐如这边向G1)写⼊数据,其他的服务器(G2)能实时同步数据
  5. G2已经同步了G1的数据,会告诉G1,我已经同步了
  6. G1接收了所有同步服务器的已同步的报告,才将“写⼊成功”信息响应给client
  7. client再发起请求,读取G2的数据
  8. 此时得到的响应是v1,即使client从未写⼊数据到G2

可⽤性(Availability)
   系统中⾮故障节点收到的每个请求都必须有响应在可⽤系统中,如果我们的客户端向服务器发送请求,并且服务器未崩溃,则服务器必须最终响应客户端,不允许服务器忽略客户的请求。
分区容错性(Partition tolerance)
   允许⽹络丢失从⼀个节点发送到另⼀个节点的任意多条消息,即不同步也就是说,G1和G2发送给对⽅的任何消息都是可以放弃的,也就是说G1和G2可能因为各种意外情况,导致⽆法成功进⾏同步,分布式系统要能容忍这种情况。
在这里插入图片描述

6.2.3 CAP三者不可能同时满⾜

假设确实存在三者能同时满⾜的系统
   那么我们要做的第⼀件事就是分区我们的系统,由于满⾜分区容错性,也就是说可能因为通信不佳等情况,G1和G2之间是没有同步。
在这里插入图片描述
   接下来,我们的客户端将v1写⼊G1,但G1和G2之间是不同步的,所以如下G1是v1数据,G2是v0数据。
在这里插入图片描述
   由于要满⾜可⽤性,即⼀定要返回数据,所以G1必须在数据没有同步给G2的前提下返回数据给client,如下
在这里插入图片描述
   接下去,client请求的是G2服务器,由于G2服务器的数据是v0,所以client得到的数据是v0
在这里插入图片描述
很明显,G1返回的是v1数据,G2返回的是v0数据,两者不⼀致。
其余情况也有类似推导,也就是说CAP三者不能同时出现。

6.2.4 CAP三者如何权衡

三选⼆利弊如何

  1. CA (Consistency + Availability):关注⼀致性和可⽤性,它需要⾮常严格的全体⼀致的协议,⽐如“两阶段提交”(2PC)。CA 系统不能容忍⽹络错误或节点错误,⼀旦出现这样的问题,整个系统就会拒绝写请求,因为它并不知道对⾯的那个结点是否挂掉了,还是只是⽹络问题。唯⼀安全的做法就是把⾃⼰变成只读的。
  2. CP (consistency + partition tolerance):关注⼀致性和分区容忍性。它关注的是系统⾥⼤多数⼈的⼀致性协议,⽐如:Paxos 算法 (Quorum 类的算法)。这样的系统只需要保证⼤多数结点数据⼀致,⽽少数的结点会在没有同步到最新版本的数据时变成不可⽤的状态。这样能够提供⼀部分的可⽤性。
  3. AP (availability + partition tolerance):这样的系统关⼼可⽤性和分区容忍性。因此,这样的系统不能达成⼀致性,需要给出数据冲突,给出数据冲突就需要维护数据版本。Dynamo 就是这样的系统。
    在这里插入图片描述
    例1:ZK遵循的是CP原则,即⼀致性和分区容错性,牺牲了可⽤性,具体牺牲在哪⾥呢?
       当Leader宕机以后,集群机器⻢上会进去到新的Leader选举中,但是选举时⻓在30s — 120s之间,这个选取Leader期间,是不提供服务的,不满⾜可⽤性,所以牺牲了可⽤性.
    经过上⾯的简单讲解,为什么选举时⻓会⻓达半分钟到2分钟呢?
    当然是为了保证⼀致性,为了保证集群中各个节点数据的⼀致性,ZK做了两类数据同步,初始化同步和更新同步。
       1:当新的Leader选举出来后,各个Follower需要将新的Leader的数据同步到⾃⼰的缓存中,这就是初始化同步
       2:当Leader数据被客户端修改后,其会向Follower发出⼴播,然后各个Follwer会⽵筒去同步更新的数据,这是更新同步
    ⽆论是初始化同步还是更新同步
    ZK集群为了保证数据的⼀致性,若发现超过半数的Follower同步超时,则其会再次进⾏同步,⽽这个过程中ZK集群同样出去不可⽤状态。

例2:Eureka保证AP
Eureka看明⽩了这⼀点,因此在设计时就优先保证可⽤性。Eureka各个节点都是平等的,⼏个节点挂掉不会影响正常节点的⼯作,剩余的节点依然可以提供注册和查询服务。⽽Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会⾃动切换⾄其它节点,只要有⼀台Eureka还在,就能保证注册服务可⽤(保证可⽤性),只不过查到的信息可能不是最新的(不保证强⼀致性)。除此之外,Eureka还有⼀种⾃我保护机制,如果在15分钟内超过85%的节点都没有正常的⼼跳,那么Eureka就认为客户端与注册中⼼出现了⽹络故障,此时会出现以下⼏种情况:

  1. Eureka不再从注册列表中移除因为⻓时间没收到⼼跳⽽应该过期的服务
  2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依
    然可⽤)
  3. 当⽹络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka可以很好的应对因⽹络故障导致部分节点失去联系的情况,⽽不会像zookeeper那样使整个注册服务瘫痪。
总结
权衡三者的关键点取决于业务。
   放弃了⼀致性,满⾜分区容错,那么节点之间就有可能失去联系,为了⾼可⽤,每个节点只能⽤本地数据提供服务,⽽这样会容易导致全局数据不⼀致性。对于互联⽹应⽤来说(如新浪,⽹易),机器数量庞⼤,节点分散,⽹络故障再正常不过了,那么此时就是保障AP,放弃C的场景,⽽从实际中理解,像⻔户⽹站这种偶尔没有⼀致性是能接受的,但不能访问问题就⾮常⼤了。
   对于银⾏来说,就是必须保证强⼀致性,也就是说C必须存在,那么就只⽤CA和CP两种情况,当保障强⼀致性和可⽤性(CA),那么⼀旦出现通信故障,系统将完全不可⽤。另⼀⽅⾯,如果保障了强⼀致性和分区容错(CP),那么就具备了部分可⽤性。实际究竟应该选择什么,是需要通过业务场景进⾏权衡的(并不是所有情况都是CP好于CA,只能查看信息但不能更新信息有时候还不如直接拒绝服务)

6.3 分布式事务解决⽅案

6.3.1 两阶段提交(2PC)

⼆阶段事务提交⽅案:强⼀致性
事务的发起者称协调者,事务的执⾏者称参与者。
处理流程:
1、准备阶段
    事务协调者,向所有事务参与者发送事务内容,询问是否可以提交事务,并等待参与者回复。
    事务参与者收到事务内容,开始执⾏事务操作,将 undo 和 redo 信息记⼊事务⽇志中(但此时
并不提交事务)。
    如果参与者执⾏成功,给协调者回复yes,表示可以进⾏事务提交。如果执⾏失败,给协调者回复no,表示不可提交。
2、提交阶段
    如果协调者收到了参与者的失败信息或超时信息,直接给所有参与者发送回滚(rollback)信息
进⾏事务回滚,否则,发送提交(commit)信息。
    参与者根据协调者的指令执⾏提交或者回滚操作,释放所有事务处理过程中使⽤的锁资源。(注意:必须在最后阶段释放锁资源) 接下来分两种情况分别讨论提交阶段的过程。
  在这里插入图片描述
简单⼀点理解,可以把协调者节点⽐喻为带头⼤哥,参与者理解⽐喻为跟班⼩弟,带头⼤哥统⼀协调跟班⼩弟的任务执⾏。
情况 1,当所有参与者均反馈 yes,提交事务,如上图:
   协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。
   参与者执⾏ commit 请求,并释放整个事务期间占⽤的资源。
   各参与者向协调者反馈 ack(应答)完成的消息。
   协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
在这里插入图片描述
情况 2,当任何阶段 1 ⼀个参与者反馈 no,中断事务,如上图:
   协调者向所有参与者发出回滚请求(即 rollback 请求)。
   参与者使⽤阶段 1 中的 undo 信息执⾏回滚操作,并释放整个事务期间占⽤的资源。
   各参与者向协调者反馈 ack 完成的消息。
   协调者收到所有参与者反馈的 ack 消息后,即完成事务中断。
⽅案总结
2PC ⽅案实现起来简单,主要因为以下问题:
   性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占⽤系统资源,容易导致性能瓶颈。
   可靠性问题:如果协调者存在单点故障问题,如果协调者出现故障,参与者将⼀直处于锁定状态。
   数据⼀致性问题:在阶段 2 中,如果发⽣局部⽹络问题,⼀部分事务参与者收到了提交消息,另⼀部分事务参与者没收到提交消息,那么就导致了节点之间数据的不⼀致。

6.3.2 补偿事务(TCC)

TCC 事务:最终⼀致性
⽅案简介
TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的⼀篇名为《Life beyondDistributed Transactions:an Apostate’s Opinion》的论⽂提出。
TCC 是服务化的⼆阶段编程模型,其 Try、Confirm、Cancel 3 个⽅法均由业务编码实现:
   Try 操作作为⼀阶段,负责资源的检查和预留。
   Confirm 操作作为⼆阶段提交操作,执⾏真正的业务。
   Cancel 是预留资源的取消。
TCC 事务的 Try、Confirm、Cancel 可以理解为 SQL 事务中的 Lock、Commit、Rollback。

处理流程
为了⽅便理解,下⾯以电商下单为例进⾏⽅案解析,这⾥把整个过程简单分为扣减库存,订单创建 2 个步骤,库存服务和订单服务分别在不同的服务器节点上。
①Try 阶段
从执⾏阶段来看,与传统事务机制中业务逻辑相同。但从业务⻆度来看,却不⼀样。
TCC 机制中的 Try 仅是⼀个初步操作,它和后续的确认⼀起才能真正构成⼀个完整的业务逻辑,这个阶段主要完成:
   完成所有业务检查( ⼀致性 ) 。
   预留必须业务资源( 准隔离性 ) 。
   Try 尝试执⾏业务。

TCC 事务机制以初步操作(Try)为中⼼的,确认操作(Confirm)和取消操作(Cancel)都是围绕初步操作(Try)⽽展开。
因此,Try 阶段中的操作,其保障性是最好的,即使失败,仍然有取消操作(Cancel)可以将其执⾏结果撤销。
在这里插入图片描述
假设商品库存为 100,购买数量为 2,这⾥检查和更新库存的同时,冻结⽤户购买数量的库存,同时创建订单,订单状态为待确认。
②Confirm / Cancel 阶段
根据 Try 阶段服务是否全部正常执⾏,继续执⾏确认操作(Confirm)或取消操作(Cancel)。
Confirm 和 Cancel 操作满⾜幂等性,如果 Confirm 或 Cancel 操作执⾏失败,将会不断重试直到执⾏完成。
Confirm:当 Try 阶段服务全部正常执⾏, 执⾏确认业务逻辑操作。
幂等性:就是⽤户对于同⼀操作发起的⼀次请求或者多次请求的结果是⼀致的,不会因为⽤户多次执⾏产⽣不同结果。
在这里插入图片描述
这⾥使⽤的资源⼀定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm ⼀定能完整正确的提交。
Confirm 阶段也可以看成是对 Try 阶段的⼀个补充,Try+Confirm ⼀起组成了⼀个完整的业务逻辑。
Cancel:当 Try 阶段存在服务执⾏失败, 进⼊ Cancel 阶段。
在这里插入图片描述
Cancel 取消执⾏,释放 Try 阶段预留的业务资源,上⾯的例⼦中,Cancel 操作会把冻结的库存释放,并更新订单状态为取消。
⽅案总结
TCC 事务机制相对于传统事务机制(X/Open XA),TCC 事务机制相⽐于上⾯介绍的 XA 事务机制,有以下优点:
   性能提升:具体业务来实现控制资源锁的粒度变⼩,不会锁定整个资源。
   数据最终⼀致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的⼀致性。
   可靠性:解决了 XA 协议的协调者单点故障问题,由主业务⽅发起并控制整个业务活动,业务活动管理器也变成多点,引⼊集群。
缺点: TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较⾼,提⾼了开发成本。

6.2.3 本地消息表

本地消息表:最终⼀致性
⽅案简介
   本地消息表的⽅案最初是由 eBay 提出,核⼼思路是将分布式事务拆分成本地事务进⾏处理。
   ⽅案通过在事务主动发起⽅额外新建事务消息表,事务发起⽅处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动⽅基于消息中间件消费事务消息表中的事务。
   这样设计可以避免”业务处理成功 + 事务消息发送失败",或"业务处理失败 + 事务消息发送成功"的棘⼿情况出现,保证 2 个系统事务的数据⼀致性。
处理流程
   下⾯把分布式事务最先开始处理的事务⽅称为事务主动⽅,在事务主动⽅之后处理的业务内的其他事务称为事务被动⽅。
   为了⽅便理解,下⾯继续以电商下单为例进⾏⽅案解析,这⾥把整个过程简单分为扣减库存,订单创建2 个步骤。
   库存服务和订单服务分别在不同的服务器节点上,其中库存服务是事务主动⽅,订单服务是事务被动⽅。
   事务的主动⽅需要额外新建事务消息表,⽤于记录分布式事务的消息的发⽣、处理状态。
整个业务处理流程如下:
在这里插入图片描述
步骤1:事务主动⽅处理本地事务。
   事务主动⽅在本地事务中处理业务更新操作和写消息表操作。上⾯例⼦中库存服务阶段在本地事务中完成扣减库存和写消息表(图中 1、2)。
步骤 2:事务主动⽅通过消息中间件,通知事务被动⽅处理事务通知事务待消息。
   消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动⽅主动写消息到消息队列,事务消费⽅消费并处理消息队列中的消息。
   上⾯例⼦中,库存服务把事务待处理消息写到消息中间件,订单服务消费消息中间件的消息,完成新增订单(图中 3 - 5)。
步骤 3:事务被动⽅通过消息中间件,通知事务主动⽅事务已处理的消息。
   上⾯例⼦中,订单服务把事务已处理消息写到消息中间件,库存服务消费中间件的消息,并将事务消息的状态更新为已完成(图中 6 - 8)。
   为了数据的⼀致性,当处理错误需要重试,事务发送⽅和事务接收⽅相关业务处理需要⽀持幂等。
具体保存⼀致性的容错处理如下:
   当步骤 1 处理出错,事务回滚,相当于什么都没发⽣。
   当步骤 2、步骤 3 处理出错,由于未处理的事务消息还是保存在事务发送⽅,事务发送⽅可以定时轮询为超时消息数据,再次发送到消息中间件进⾏处理。事务被动⽅消费事务消息重试处理。
   如果是业务上的失败,事务被动⽅可以发消息给事务主动⽅进⾏回滚。
   如果多个事务被动⽅已经消费消息,事务主动⽅需要回滚事务时需要通知事务被动⽅回滚。
⽅案总结
⽅案的优点如下:
   从应⽤设计开发的⻆度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
   ⽅案轻量,容易实现。
缺点如下:
   与具体的业务场景绑定,耦合性强,不可公⽤。
   消息数据与业务数据同库,占⽤业务系统资源。
   业务系统在使⽤关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。

6.2.4 MQ 事务消息

MQ 事务:最终⼀致性
⽅案简介
基于 MQ 的分布式事务⽅案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他⽅⾯的协议基本与本地消息表⼀致。
处理流程
下⾯主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务⽅案。
在本地消息表⽅案中,保证事务主动⽅发写业务表数据和写消息表数据的⼀致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ,相对于提供了 2PC 的提交接⼝,⽅案如下:
正常情况:事务主动⽅发消息
在这里插入图片描述
这种情况下,事务主动⽅服务正常,没有发⽣故障,发消息流程如下:
   图中 1:发送⽅向 MQ 服务端(MQ Server)发送 half 消息。
   图中 2:MQ Server 将消息持久化成功之后,向发送⽅ ack 确认消息已经发送成功。
   图中 3:发送⽅开始执⾏本地事务逻辑。
   图中 4:发送⽅根据本地事务执⾏结果向 MQ Server 提交⼆次确认(commit 或是 rollback)。
   图中 5:MQ Server 收到 commit 状态则将半消息标记为可投递,订阅⽅最终将收到该消息;MQServer 收到 rollback 状态则删除半消息,订阅⽅将不会接受该消息
异常情况:事务主动⽅消息恢复
在这里插入图片描述
在断⽹或者应⽤重启等异常情况下,图中 4 提交的⼆次确认超时未到达 MQ Server,此时处理逻辑如下:
   图中 5:MQ Server 对该消息发起消息回查。
   图中 6:发送⽅收到消息回查后,需要检查对应消息的本地事务执⾏的最终结果。
   图中 7:发送⽅根据检查得到的本地事务的最终状态再次提交⼆次确认。
   图中 8:MQ Server基于 commit/rollback 对消息进⾏投递或者删除。
介绍完 RocketMQ 的事务消息⽅案后,由于前⾯已经介绍过本地消息表⽅案,这⾥就简单介绍RocketMQ 分布式事务:
在这里插入图片描述
事务主动⽅基于 MQ 通信通知事务被动⽅处理事务,事务被动⽅基于 MQ 返回处理结果。
如果事务被动⽅消费消息异常,需要不断重试,业务处理逻辑需要保证幂等。
如果是事务被动⽅业务上的处理失败,可以通过 MQ 通知事务主动⽅进⾏补偿或者事务回滚。
⽅案总结
相⽐本地消息表⽅案,MQ 事务⽅案优点是:
   消息数据独⽴存储 ,降低业务系统与消息系统之间的耦合。
   吞吐量优于使⽤本地消息表⽅案。
缺点是:
   ⼀次消息发送需要两次⽹络请求(half 消息 +commit/rollback 消息) 。
   业务处理服务需要实现消息状态回查接⼝。

6.4Seata框架

2019 年 1 ⽉,阿⾥巴巴中间件团队发起了开源项⽬ Fescar(Fast & EaSy Commit And Rollback),和社区⼀起共建开源分布式事务解决⽅案。Fescar 的愿景是让分布式事务的使⽤像本地事务的使⽤⼀样,简
单和⾼效,并逐步解决开发者们遇到的分布式事务⽅⾯的所有难题。

为了打造更中⽴、更开放、⽣态更加丰富的分布式事务开源社区,经过社区核⼼成员的投票,⼤家决定对 Fescar 进⾏品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous TransactionArchitecture(简单可扩展⾃治事务框架),是⼀套⼀站式分布式事务解决⽅案。

Seata 融合了阿⾥巴巴和蚂蚁⾦服在分布式事务技术上的积累,并沉淀了新零售、云计算和新⾦融等场景下丰富的实践经验。

Seata 提供全⽅位分布式事务解决⽅案.

6.5 Seata⼊⻔案例

6.5.1 需求分析

在这里插入图片描述

  1. ⽤户请求下单业务微服务(bussiness),请求下单
  2. bussiness通过Feign调⽤库存微服务(storage),扣减库存
  3. bussiness通过Feign调⽤订单微服务(order),创建订单
  4. 订单微服务(order),通过Feign调⽤⽤户微服务(account),执⾏扣款操作.

先将 资料\all.sql 数据库脚本导⼊到数据库中。
在这里插入图片描述

6.5.2 ⽗⼯程

创建springcloud-eureka-seata
pom.xml依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>io.seata</groupId>
  <artifactId>springcloud-eureka-seata</artifactId>
  <version>1.1.0</version>

  <modules>
    <module>eureka</module>
    <module>storage</module>
    <module>account</module>
    <module>order</module>
    <module>bussiness</module>
  </modules>
  <packaging>pom</packaging>
  <properties>
    <seata.version>1.4.0</seata.version>
    <spring.version>5.1.3.RELEASE</spring.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Greenwich.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.1.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-netflix</artifactId>
        <version>2.1.1.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-seata</artifactId>
        <version>2.1.0.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>${seata.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.4.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <version>2.0.0.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        <version>2.0.0.RELEASE</version>
      </dependency>
      <dependency>
        <artifactId>spring-web</artifactId>
        <groupId>org.springframework</groupId>
        <version>5.1.2.RELEASE</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <repositories>
    <repository>
      <id>spring-snapshot</id>
      <name>Spring Snapshot Repository</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>mvnrepository</id>
      <name>mvnrepository</name>
      <url>http://www.mvnrepository.com/</url>
      <layout>default</layout>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>sonatype</id>
      <name>sonatype</name>
      <url>http://oss.sonatype.org/</url>
      <layout>default</layout>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!--<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
        </configuration>
      </plugin>-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
        <version>3.8</version>
        <configuration>
          <sourceEncoding>${project.build.sourceEncoding}
          </sourceEncoding>
          <minimumPriority>2</minimumPriority>
          <printFailingErrors>true</printFailingErrors>
          <rulesets>
            <ruleset>rulesets/java/ali-comment.xml</ruleset>
            <ruleset>rulesets/java/ali-concurrent.xml</ruleset>
            <ruleset>rulesets/java/ali-constant.xml</ruleset>
            <ruleset>rulesets/java/ali-exception.xml</ruleset>
            <ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
            <ruleset>rulesets/java/ali-naming.xml</ruleset>
            <ruleset>rulesets/java/ali-oop.xml</ruleset>
            <ruleset>rulesets/java/ali-orm.xml</ruleset>
            <ruleset>rulesets/java/ali-other.xml</ruleset>
            <ruleset>rulesets/java/ali-set.xml</ruleset>
          </rulesets>
        </configuration>
        <executions>
          <execution>
            <phase>verify</phase>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>com.alibaba.p3c</groupId>
            <artifactId>p3c-pmd</artifactId>
            <version>1.3.6</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>

  </build>
</project>

6.5.3 Eureka注册中⼼

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-eureka-seata</artifactId>
        <groupId>io.seata</groupId>
        <version>1.1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>eureka</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

application.properties

server.port=8761
eureka.instance.hostname=127.0.0.1
eureka.instance.prefer-ip-address=true
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false

io\seata\sample\EurekaServerApplication.java

package io.seata.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 17:01
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
 public static void main(String[] args) {
 SpringApplication.run(EurekaServerApplication.class, args);
 }
}

6.5.4 Seata Server

从官⽹下载seater server 1.4 ,这⾥可以直接从资料拷⻉即可,注意seata server需要配置注册到上⾯的Eureka,配置如下:
seata\conf\registry.conf
在这里插入图片描述
执⾏下⾯⽂件启动即可
在这里插入图片描述

6.5.5 库存微服务

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://maven.apache.org/POM/4.0.0"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <parent>
 <artifactId>springcloud-eureka-seata</artifactId>
 <groupId>io.seata</groupId>
 <version>1.1.0</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>storage</artifactId>
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 <exclusions>
 <exclusion>
 <artifactId>spring-web</artifactId>
 <groupId>org.springframework</groupId>
 </exclusion>
 </exclusions>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-sleuth</artifactId>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <scope>runtime</scope>
 </dependency>
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 <exclusions>
 <exclusion>
 <artifactId>spring-web</artifactId>
 <groupId>org.springframework</groupId>
 </exclusion>
 </exclusions>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-alibaba-seata</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
 <dependency>
 <groupId>io.seata</groupId>
 <artifactId>seata-all</artifactId>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>
(2) 配置⽂件

从seata官⽹下载相应配置⽂件,直接修改使⽤即可
在这里插入图片描述
配置⽂件具体讲解,在后⾯章节中,专题讲解,直接从资料中拷⻉即可

(3) 启动器

io\seata\sample\StorageApplication.java

package io.seata.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:19
 */
@SpringBootApplication
@EnableEurekaClient
public class StorageApplication {
    public static void main(String[] args) {
        SpringApplication.run(StorageApplication.class, args);
    }
}
2.配置类
package io.seata.sample.config;

import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:18
 */
@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    /**
     * 配置seata代理数据源
     * @param dataSource
     * @return
     */
    @Primary
    @Bean("dataSourceProxy")
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }


    @Bean("jdbcTemplate")
    @ConditionalOnBean(DataSourceProxy.class)
    public JdbcTemplate jdbcTemplate(DataSourceProxy dataSourceProxy) {
        return new JdbcTemplate(dataSourceProxy);
    }

}
3.业务层
package io.seata.sample.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:20
 */
@Service
public class StorageService {
 @Autowired
 private JdbcTemplate jdbcTemplate;
 public void deduct(String commodityCode, int count) {
 jdbcTemplate.update("update storage_tbl set count = count - ? where
commodity_code = ?",
 new Object[] {count, commodityCode});
 }
}
4 控制层

io\seata\sample\controller\StorageController.java

package io.seata.sample.controller;

import io.seata.sample.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:21
 */
@RestController
public class StorageController {

    @Autowired
    private StorageService storageService;

    @RequestMapping(value = "/deduct", produces = "application/json")
    public Boolean deduct(String commodityCode, Integer count) {
        storageService.deduct(commodityCode, count);
        return true;
    }

}

6.6 配置⽂件详解

这里标题差了

6.6.1 相关概念

XID:全局事务的唯⼀标识,由 ip:port:sequence 组成;
Transaction Coordinator (TC):事务协调器,维护全局事务的运⾏状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager (TM ):控制全局事务的边界,负责开启⼀个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM):控制分⽀事务,负责分⽀注册、状态汇报,并接收事务协调器的指令,驱动分⽀(本地)事务的提交和回滚;

seata使⽤ XID 表示⼀个分布式事务,XID 需要在⼀次分布式事务请求所涉的系统中进⾏传递,从⽽向seata-server 发送分⽀事务的处理情况,以及接收 seata-server 的 commit、rollback 指令。

6.6.2 配置讲解

在这里插入图片描述
seata的配置⼊⼝⽂件是 registry.conf, 查看代码 ConfigurationFactory 得知⽬前还不能指定该配置⽂件,所以配置⽂件名称只能为 registry.conf。
在 registry 中可以指定具体配置的形式,默认使⽤ file 类型,在 file.conf 中有 3 部分配置内容:
transport transport :⽤于定义 Netty 相关的参数,TM、RM 与 seata-server 之间使⽤ Netty 进⾏通信。
service:

service {
 #transaction service group mapping,配置Seata Server在注册中⼼注册的服务名
 vgroupMapping.my_test_tx_group = "default"
 #配置Client连接Seata Server的地址
 default.grouplist = "127.0.0.1:8091"
 #degrade, current not support
 enableDegrade = false
 #disable seata
 disableGlobalTransaction = false
}

client:

client {
 rm {
 #RM接收TC的commit通知缓冲上限
 asyncCommitBufferLimit = 10000
 lock {
 retryInterval = 10
 retryTimes = 30
 retryPolicyBranchRollbackOnConflict = true
 }
 reportRetryCount = 5
 tableMetaCheckEnable = false
 reportSuccessEnable = false
 }
 tm {
 commitRetryCount = 5
 rollbackRetryCount = 5
 }
 undo {
 dataValidation = true
 logSerialization = "jackson"
 logTable = "undo_log"
 }
 log {
 exceptionRate = 100
 }
}

seata 在 AT 模式下需要创建数据库代理.在io\seata\sample\DataSourceConfiguration.java 中代码如下:

package io.seata.sample.config;

import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
importorg.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:18
 */
@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    /**
     * 配置seata代理数据源
     * @param dataSource
     * @return
     */
    @Primary
    @Bean("dataSourceProxy")
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }


    @Bean("jdbcTemplate")
    @ConditionalOnBean(DataSourceProxy.class)
    public JdbcTemplate jdbcTemplate(DataSourceProxy dataSourceProxy) {
        return new JdbcTemplate(dataSourceProxy);
    }

}

使⽤ DataSourceProxy 的⽬的是为了引⼊ ConnectionProxy ,seata⽆侵⼊的⼀⽅⾯就体现在ConnectionProxy 的实现上,即分⽀事务加⼊全局事务的切⼊点是在本地事务的 commit 阶段,这样设计可以保证业务数据与 undo_log 是在⼀个本地事务中。
undo_log 是需要在业务库上创建的⼀个表,seata依赖该表记录每笔分⽀事务的状态及⼆阶段rollback 的回滚数据。不⽤担⼼该表的数据量过⼤形成单点问题,在全局事务 commit 的场景下事务对应的 undo_log 会异步删除。
所以在每个微服务对应的数据库中需要创建⼀张undo_log表。

CREATE TABLE `undo_log` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `branch_id` bigint(20) NOT NULL,
 `xid` varchar(100) NOT NULL,
 `context` varchar(128) NOT NULL,
 `rollback_info` longblob NOT NULL,
 `log_status` int(11) NOT NULL,
 `log_created` datetime NOT NULL,
 `log_modified` datetime NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

application.properties配置
在这里插入图片描述

6.6.3 ⽤户微服务

(1) 搭建⽤户微服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-eureka-seata</artifactId>
        <groupId>io.seata</groupId>
        <version>1.1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>account</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
配置⽂件

从seata官⽹下载相应配置⽂件,直接修改使⽤即可
在这里插入图片描述
配置⽂件具体讲解,在后⾯章节中,专题讲解,直接从资料中拷⻉即可

启动器

io\seata\sample\AccountApplication.java

package io.seata.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:54
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class AccountApplication {
    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }
}
(2)配置类
package io.seata.sample.config;

import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:18
 */
@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Primary
    @Bean("dataSourceProxy")
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean("jdbcTemplate")
    @ConditionalOnBean(DataSourceProxy.class)
    public JdbcTemplate jdbcTemplate(DataSourceProxy dataSourceProxy) {
        return new JdbcTemplate(dataSourceProxy);
    }

}
(3) 业务层

io\seata\sample\service\AccountService.java

package io.seata.sample.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:56
 */
@Service
public class AccountService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void reduce(String userId, int money) {
        jdbcTemplate.update("update account_tbl set money = money - ? where user_id = ?", new Object[] {money, userId});
    }
}
(4)控制层

io\seata\sample\controller\AccountController.java

package io.seata.sample.AccountController;

import io.seata.sample.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:56
 */
@RestController
public class AccountController {

    @Autowired
    private AccountService accountService;

    @RequestMapping(value = "/reduce", produces = "application/json")
    public Boolean debit(String userId, int money) {
        accountService.reduce(userId, money);
        return true;
    }

}

6.6.4 订单微服务

(1)搭建⽤户微服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-eureka-seata</artifactId>
        <groupId>io.seata</groupId>
        <version>1.1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>order</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
配置⽂件

从seata官⽹下载相应配置⽂件,直接修改使⽤即可
在这里插入图片描述
配置⽂件具体讲解,在后⾯章节中,专题讲解,直接从资料中拷⻉即可

启动器

io\seata\sample\OrderApplication.java

package io.seata.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 3.8.2 配置类
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:59
 */
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
(2)配置类
package io.seata.sample.config;

import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:18
 */
@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Primary
    @Bean("dataSourceProxy")
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean("jdbcTemplate")
    @ConditionalOnBean(DataSourceProxy.class)
    public JdbcTemplate jdbcTemplate(DataSourceProxy dataSourceProxy) {
        return new JdbcTemplate(dataSourceProxy);
    }

}
(3)UserFeign

通过UserFeignClient调⽤⽤户微服务实现扣款操作
io\seata\sample\feign\UserFeignClient.java

package io.seata.sample.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:01
 */
@FeignClient(name = "account-service", url = "127.0.0.1:8083")
public interface UserFeignClient {
    @GetMapping("/reduce")
    Boolean reduce(@RequestParam("userId") String userId,
                   @RequestParam("money") int money);
}
(4) 业务层

io\seata\sample\service\OrderService.java

package io.seata.sample.service;

import io.seata.sample.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:00
 */
@Service
public class OrderService {
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void create(String userId, String commodityCode, Integer count) {
        int orderMoney = count * 100;
        jdbcTemplate.update("insert order_tbl(user_id,commodity_code,count,money) values(?,?,?,?)",
        new Object[] {userId, commodityCode, count, orderMoney});
        userFeignClient.reduce(userId, orderMoney);
    }
}
(5)控制层

io\seata\sample\controller\OrderController.java

package io.seata.sample.controller;

import io.seata.sample.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:04
 */
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping(value = "/create", produces = "application/json")
    public Boolean create(String userId, String commodityCode, Integer count) {
        orderService.create(userId, commodityCode, count);
        return true;
    }

}

6.6.5 业务微服务

创建bussiness业务微服务,通过Feign调⽤库存微服务,和订单微服务,实现下单业务,分布式全局事务,也在此微服务控制

(1)搭建⽤户微服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-eureka-seata</artifactId>
        <groupId>io.seata</groupId>
        <version>1.1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>bussiness</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <artifactId>spring-web</artifactId>
            <groupId>org.springframework</groupId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>

    </dependencies>
</project>
配置⽂件

从seata官⽹下载相应配置⽂件,直接修改使⽤即可
在这里插入图片描述
配置⽂件具体讲解,在后⾯章节中,专题讲解,直接从资料中拷⻉即可

启动器

io\seata\sample\BusinessApplication.java

package io.seata.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:17
 */
@SpringBootApplication
@EnableFeignClients
public class BusinessApplication {
 public static void main(String[] args) {
 SpringApplication.run(BusinessApplication.class, args);
 }
}
(2) 配置类
package io.seata.sample;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 15:18
 */
@Configuration
public class DataSourceConfiguration {
 @Bean
 @ConfigurationProperties(prefix = "spring.datasource")
 public DataSource dataSource() {
 DruidDataSource druidDataSource = new DruidDataSource();
 return druidDataSource;
 }
 @Bean("jdbcTemplate")
 public JdbcTemplate jdbcTemplate(DataSource dataSource) {
 return new JdbcTemplate(dataSource);
 }
 
}

注意:在业务微服务中,只是执⾏基本的数据库操作,不涉及分布式事务,所以这⾥直接使⽤普通DataSource即可。

(3) FeignClient

通过OrderFeignClient调⽤订单微服务实现下单,订单微服务⼜通过Feign调⽤⽤户微服务实现扣款io\seata\sample\feign\OrderFeignClient.java

package io.seata.sample.feign;
io\seata\sample\feign\StorageFeignClient.java
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:12
 */
@FeignClient(name = "order-service", url = "127.0.0.1:8082")
public interface OrderFeignClient {
 @GetMapping("/create")
 void create(@RequestParam("userId") String userId,
 @RequestParam("commodityCode") String commodityCode,
 @RequestParam("count") Integer count);
}

通过io\seata\sample\feign\StorageFeignClient.java调⽤库存微服务,实现消减库存操作io\seata\sample\feign\StorageFeignClient.java

package io.seata.sample.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:13
 */
@FeignClient(name = "storage-service", url = "127.0.0.1:8081")
public interface StorageFeignClient {
 @GetMapping("/deduct")
 void deduct(@RequestParam("commodityCode") String commodityCode,
 @RequestParam("count") Integer count);
}
(4) 业务层

io\seata\sample\service\BusinessService.java

package io.seata.sample.service;
import java.util.Map;
import javax.annotation.PostConstruct;
import io.seata.sample.feign.OrderFeignClient;
import io.seata.sample.feign.StorageFeignClient;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:14
 */
@Service
public class BusinessService {
 @Autowired
 private StorageFeignClient storageFeignClient;
 @Autowired
 private OrderFeignClient orderFeignClient;
 @Autowired
 private JdbcTemplate jdbcTemplate;
 /**
 * 减库存,下订单
 *
 * @param userId
 * @param commodityCode
 * @param orderCount
 */
 @GlobalTransactional
 public void purchase(String userId, String commodityCode, int orderCount) {
 storageFeignClient.deduct(commodityCode, orderCount);
 orderFeignClient.create(userId, commodityCode, orderCount);
 if (!validData()) {
 throw new RuntimeException("账户或库存不⾜,执⾏回滚");
 }
 }
 @PostConstruct
 public void initData() {
 jdbcTemplate.update("delete from account_tbl");
 jdbcTemplate.update("delete from order_tbl");
 jdbcTemplate.update("delete from storage_tbl");
 jdbcTemplate.update("insert into account_tbl(user_id,money)
values('U100000','10000') ");
 jdbcTemplate.update("insert into storage_tbl(commodity_code,count)
values('C100000','200') ");
 }
 public boolean validData() {
 Map accountMap = jdbcTemplate.queryForMap("select * from account_tbl
where user_id='U100000'");
 if (Integer.parseInt(accountMap.get("money").toString()) < 0) {
 return false;
 }
 Map storageMap = jdbcTemplate.queryForMap("select * from storage_tbl
where commodity_code='C100000'");
 if (Integer.parseInt(storageMap.get("count").toString()) < 0) {
 return false;
 }
 return true;
 }
}
(5)控制层

io\seata\sample\controller\BusinessController.java

package io.seata.sample.controller;
import io.seata.sample.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Des 新职课商城项⽬
 * @Author 雪松
 * @Date 2020/12/28 16:15
 */
@RestController
public class BusinessController {
 @Autowired
 private BusinessService businessService;
 /**
 * 购买下单,模拟全局事务提交
 *
 * @return
 */
 @RequestMapping(value = "/purchase/commit", produces = "application/json")
 public String purchaseCommit() {
 try {
 businessService.purchase("U100000", "C100000", 30);
 } catch (Exception exx) {
 return exx.getMessage();
 }
 return "全局事务提交";
 }
 /**
 * 购买下单,模拟全局事务回滚
 * 账户或库存不⾜
 *
 * @return
 */
 @RequestMapping("/purchase/rollback")
 public String purchaseRollback() {
 try {
 businessService.purchase("U100000", "C100000", 99999);
 } catch (Exception exx) {
 return exx.getMessage();
 }
 return "全局事务提交";
 }
}

6.6 Seata四种模式

AT
TCC
Sage
XA

6.6.1 AT模式

Seata AT模式是基于XA事务演进⽽来的⼀个分布式事务中间件,AT模式分为如下3个⻆⾊
在这里插入图片描述
解释:
Transaction Coordinator (TC): 事务协调器,维护全局事务的运⾏状态,负责协调并驱动全局事务的提交或回滚。
Transaction Manager(TM): 控制全局事务的边界,负责开启⼀个全局事务,并最终发起全局提交或全局回滚的决议。
Resource Manager (RM): 控制分⽀事务,负责分⽀注册、状态汇报,并接收事务协调器的指令,驱动分⽀(本地)事务的提交和回滚。
协调执⾏流程如下:
在这里插入图片描述
Branch就是指的分布式事务中每个独⽴的本地局部事务。
在 AT 模式下,⽤户只需关⼼⾃⼰的 “业务SQL”
AT 模式分为两个阶段:
   ⼀阶段:执⾏⽤户 SQL
   ⼆阶段: Seata 框架⾃动⽣成。如图:
在这里插入图片描述
第⼀阶段
Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚⽇志,利⽤ 本地事务 的 ACID 特性,将业务数据的更新和回滚⽇志的写⼊在同⼀个 本地事务 中提交。
这样,可以保证:任何提交的业务数据的更新⼀定有相应的回滚⽇志存在。
在这里插入图片描述
基于这样的机制,分⽀的本地事务便可以在全局事务的第⼀阶段提交,并⻢上释放本地事务锁定的资源

这也是Seata和XA事务的不同之处,两阶段提交往往对资源的锁定需要持续到第⼆阶段实际的提交或者回滚操作,⽽有了回滚⽇志之后,可以在第⼀阶段释放对资源的锁定,降低了锁范围,提⾼效率,即使第⼆阶段发⽣异常需要回滚,只需找对undolog中对应数据并反解析成sql来达到回滚⽬的。

同时Seata通过代理数据源将业务sql的执⾏解析成undolog来与业务数据的更新同时⼊库,达到了对业务⽆侵⼊的效果。

第⼆阶段

如果决议是全局提交,此时分⽀事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚⽇志),Phase2 可以⾮常快速地完成.
在这里插入图片描述
如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚⽇志记录,通过回滚记录⽣成反向的更新 SQL 并执⾏,以完成分⽀的回滚。
在这里插入图片描述
AT模式优点
AT 模式的⼀阶段、⼆阶段提交和回滚均由 Seata 框架⾃动⽣成,⽤户只需编写“业务 SQL”,便能轻松接⼊分布式事务,AT 模式是⼀种对业务⽆任何侵⼊的分布式事务解决⽅案。

6.6.2 XA

XA 模式是 Seata 将会开源的另⼀种⽆侵⼊的分布式事务解决⽅案
(1)过程
(2)优点

  1. ⽆侵⼊
  2. 将快照数据和⾏锁等通过 XA 指令委托给了数据库来完成
    参考代码如下:

XA模式,⼤家可以资料⽬录下参考官⽹相关案例
在这里插入图片描述

6.6.3 TCC

TCC分为三个阶段:
Try:做业务检查和资源预留
Confirm:确认提交
Cancel:业务执⾏错误需要回滚的状态下执⾏分⽀事务的业务取消,预留资源释放。
在这里插入图片描述
(1)过程

  1. Try
  2. Confirm
  3. Cancel

(2)三种异常处理
4. 空回滚:Try未执⾏,Cancel 执⾏了
出现原因:
5. 幂等:多次调⽤⽅法(Confirm)
出现原因:

  1. ⽹络异常
  2. TC Server 异常
  3. 悬挂:Cancel接⼝ ⽐ Try接⼝先执⾏
    出现原因:
  4. 超时

(3)优点
相对于 AT 模式,TCC 模式对业务代码有⼀定的侵⼊性,但是 TCC 模式⽆ AT 模式的全局⾏锁,TCC 性能会⽐ AT 模式⾼很多。
参考代码如下:
TCC模式,⼤家可以资料⽬录下参考官⽹相关案例
在这里插入图片描述

6.6.4 Sage

Sage 是⻓事务解决⽅案,事务驱动
如图:
在这里插入图片描述
(1)过程
如图:
(2)适⽤场景
业务流程⻓/多
参与者包含其他公司或遗留系统服务,⽆法提供 TCC 模式要求的三个接⼝
典型业务系统:如⾦融⽹络(与外部⾦融机构对接)、互联⽹微贷、渠道整合、分布式架构服务集成等业务系统
银⾏业⾦融机构使⽤⼴泛

(3)三种异常

  1. 空补偿:原服务未执⾏,补偿服务执⾏了
    出现原因:
    原服务超时(丢包)
    Saga 事务触发回滚
    未收到原服务请求,先收到补偿请求
  2. 悬挂:补偿服务⽐原服务先执⾏
    原服务超时(拥堵)
    Saga 事务回滚,触发回滚
    拥堵的原服务到达
  3. 幂等:原服务与补偿服务都需要保证幂等性

(4)优点
4. ⼀阶段提交本地数据库事务,⽆锁,性能⾼
5. 补偿服务即正向服务的 “反向”,⾼吞吐
6. 参与者可异步执⾏,⾼吞吐

6.6.5 总结

四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适⽤场景AT 模式是⽆侵⼊的分布式事务解决⽅案,适⽤于不希望对业务进⾏改造的场景,⼏乎0学习成本。TCC 模式是⾼性能分布式事务解决⽅案,适⽤于核⼼系统等对性能有很⾼要求的场景。
Saga 模式是⻓事务解决⽅案,适⽤于业务流程⻓且需要保证事务最终⼀致性的业务系统,Saga 模式⼀阶段就会提交本地事务,⽆锁,⻓流程情况下可以保证性能,多⽤于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,⽆法进⾏改造和提供 TCC 要求的接⼝,也可以使⽤Saga 模式。
XA模式是分布式强⼀致性的解决⽅案,但性能低⽽使⽤较少。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值