vlambda博客
学习文章列表

你使用分布式项目却不知道这个分布式事务?

哈喽,大家好,我是指北君

不知道大家有没有发现,无论是 Dubbo的推出与使用,又或者是SpringCloud横空出世,可能自19年以来,分布式,微服务这些词语就不绝于耳。

对于微服务来说 简单理解起来就是每一个服务都能够独立的部署与运行,服务之前通过RPC来进行互相交互,每一个微服务都是由独立的小团队开发,测试,部署以及上线。对于分布式 又是一个整体的概念,顾名思义就是服务都是分散部署在不同的机器上,每一个服务可能负责几个功能等……

然后这些也都是服务级别,但是小伙伴们有没有思考一个数据库层面的问题:我们在使用Dubbo 时候进行dubbo接口调用的时候,调用完dubbo接口进行了上游数据的修改,下游出现了异常,但是这种时候,我们使用传统的 @Transactional注解,并不能够使得上游已经修改过的数据进行回滚。这样也就很有可能出现数据不一致的情况。

这个时候就引出了我们今天介绍的主角:Seata——是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

前驱知识

什么是分布式事务

如下图所示:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上

注意:本篇文章所讲的分布式事务特指在多个服务同时访问多个数据源的事务处理机制,请注意它与DTP 模型中“分布式事务”的差异。DTP 模型所指的“分布式”是相对于数据源而言的,并不涉及服务。下面所指的“分布式”是相对于服务而言的,如果严谨地说,它更应该被称为“在分布式服务环境下的事务处理机制”。

在这里插入图片描述

(百度百科)

通俗解释

就是 在我们的系统体量还没有达到一定的规模的时候,我们单体架构是能够支撑起来所有的服务请求,此时所有对于数据库的操作都在一个库中,此时无论是出错的回滚还是说异常的处理也更加容易处理。

但是在业务逻辑更加复杂的情况下,很多时候我们都是采用**RPC远端调用来完成业务逻辑的拆分,将不同的业务逻辑和功能模块进行拆分,使得其在各自实现自己的逻辑的情况下还能够为其他子应用提供服务,这也是目前为止市面上上常用到的对于业务逻辑增加的应对方法,但是导致的问题就是,在不同的子模块中,我们无法保证在本地事务成功之后,调用其他服务是否成功**,不成功数据能否进行一个正常的回滚,所以也就引入了分布式事务-使其能够在不同的服务和不同的数据源中保证数据的一致性。

CAP是什么以及原理

既然讨论到了分布式事务,就不得不来简单概述一下CAP原理,其最开始起源于在 2000 年 7 月,是加州大学伯克利分校的一个教授于“ACM 分布式计算原理研讨会(PODC)”上提出的一个猜想。

你使用分布式项目却不知道这个分布式事务?
在这里插入图片描述

两年之后,麻省理工学院的两名教授以严谨的数学推理证明了 CAP 猜想。自此,CAP 正式从猜想变为分布式计算领域所公认的著名定理。这个定理里描述了一个分布式的系统中,涉及共享数据问题时,以下三个特性最多只能同时满足其中两个:

  • 一致性(Consistency):代表数据在任何时刻、任何分布式节点中所看到的都是符合预期的。
  • 可用性(Availability):代表系统不间断地提供服务的能力,理解可用性要先理解与其密切相关两个指标:可靠性(Reliability)和可维护性(Serviceability)。可靠性使用平均无故障时间(Mean Time Between Failure,MTBF)来度量;可维护性使用平均可修复时间(Mean Time To Repair,MTTR)来度量。可用性衡量系统可以正常使用的时间与总时间之比,其表征为:A=MTBF/(MTBF+MTTR),即可用性是由可靠性和可维护性计算得出的比例值,譬如 99.9999%可用,即代表平均年故障修复时间为 32 秒。
  • 分区容忍性(Partition Tolerance):代表分布式环境中部分节点因网络原因而彼此失联后,即与其他节点形成“网络分区”时,系统仍能正确地提供服务的能力。

但是仅仅论述概念,对于CAP的理解来说还是比较难的,下面我们以一个实际的案例(如下场景案例)来说明,这三种不同的特质对于分布式系统来说意味着什么。

场景案例:

有一个在线书店。每当一本书被成功售出时,需要确保以下三件事情被正确地处理:1  用户的账号扣减相应的商品款项。2  商品仓库中扣减库存,将商品标识为待配送状态。3  商家的账号增加相应的商品款项。

如下图所示,一个用户购买一本书籍的过程将由账号服务、商家服务和库存服务对应各自集群中的某一个节点来完成响应。你使用分布式项目却不知道这个分布式事务?在这套系统中,每一个单独的服务节点都有自己的数据库(这里是为了便于说明问题的假设,在实际生产系统中,一般应避免将用户余额这样的数据设计成存储在多个可写的数据库中),假设某次交易请求分别由“账号节点 1”、“商家节点 2”、“仓库节点 N”联合进行响应。当用户购买一件价值 100 元的商品后,账号节点 1 首先应给该用户账号扣减 100 元货款,它在自己数据库扣减 100 元很容易,但它还要把这次交易变动告知本集群的节点 2 到节点 N,并要确保能正确变更商家和仓库集群其他账号节点中的关联数据,此时将面临以下可能的情况。

  • 如果该变动信息没有及时同步给其他账号节点,将导致有可能发生用户购买另一商品时,被分配给到另一个节点处理,由于看到账号上有不正确的余额而错误地发生了原本无法进行的交易, 此为一致性问题。
  • 如果由于要把该变动信息同步给其他账号节点,必须暂时停止对该用户的交易服务,直至数据同步一致后再重新恢复,将可能导致用户在下一次购买商品时,因系统暂时无法提供服务而被拒绝交易, 此为可用性问题。
  • 如果由于账号服务集群中某一部分节点,因出现网络问题,无法正常与另一部分节点交换账号变动信息,此时服务集群中无论哪一部分节点对外提供的服务都可能是不正确的,整个集群能否承受由于部分节点之间的连接中断而仍然能够正确地提供服务, 此为分区容忍性。

以上还仅仅涉及了账号服务集群自身的 CAP 问题,对于整个 站点来说,它更是面临着来自于账号、商家和仓库服务集群带来的 CAP 问题,譬如,用户账号扣款后,可能出现的如下问题:

  1. 由于未及时通知仓库服务中的全部节点,导致另一次交易中看到仓库里有不正确的库存数据而发生 超售
  2. 又譬如因涉及仓库中某个商品的交易正在进行,为了同步用户、商家和仓库的交易变动,而暂时锁定该商品的交易服务,导致了的可用性问题,等等。

由于 CAP 定理已有严格的证明,我们这里不去探讨如何进行严格的证明,而是直接分析如果舍弃 C、A、P 时所带来的不同影响。

  • 如果放弃分区容忍性(CA without P),意味着我们将假设节点之间通信永远是可靠的。永远可靠的通信在分布式系统中必定不成立的,这不是你想不想的问题,而是只要用到网络来共享数据,分区现象就会始终存在。在现实中,最容易找到放弃分区容忍性的例子便是传统的关系数据库集群,这样的集群虽然依然采用由网络连接的多个节点来协同工作,但数据却不是通过网络来实现共享的。以 Oracle 的  RAC 集群为例,它的每一个节点均有自己独立的  SGA、重做日志、回滚日志等部件,但各个节点是通过共享存储中的同一份数据文件和控制文件来获取数据的,通过共享磁盘的方式来避免出现网络分区。因而  Oracle RAC 虽然也是由多个实例组成的数据库,但它并不能称作是分布式数据库。
  • 如果放弃可用性(CP without A),意味着我们将假设一旦网络发生分区,节点之间的信息同步时间可以无限制地延长,因为我们放弃了可用性。
  • 如果放弃一致性(AP without C),意味着我们将假设一旦发生分区,节点之间所提供的数据可能不一致。 选择放弃一致性的 AP 系统目前是设计分布式系统的主流选择,因为 P 是分布式网络的天然属性,你再不想要也无法丢弃;而 A 通常是建设分布式的目的,如果可用性随着节点数量增加反而降低的话,很多分布式系统可能就失去了存在的价值,除非银行、证券这些涉及金钱交易的服务,宁可中断也不能出错,否则多数系统是不能容忍节点越多可用性反而越低的。目前大多数 NoSQL 库和支持分布式的缓存框架都是 AP 系统,以 Redis 集群为例,如果某个 Redis 节点出现网络分区,那仍不妨碍各个节点以自己本地存储的数据对外提供缓存服务,但这时有可能出现请求分配到不同节点时返回给客户端的是不一致的数据。

讨论到了CAP在实际使用过程中,如何具体的使用和如何具体的分析,虽然我们一直心心念念的分布式事务最终要保存的“一致性”,但是最终的结论却是“选择放弃一致性的 AP 系统目前是设计分布式系统的主流选择”,在分布式环境中“一致性”却不得不成为通常被牺牲,被放弃的那一项属性,但是无论如何,最终确保操作结果在最终交付的时候是正确的,这句话的意思是允许数据在中间过程出错(不一致),但应该在输出时被修正过来。为此,人们又重新给一致性下了定义,将前面我们在 CAP、ACID 中讨论的一致性称为“强一致性”(Strong Consistency),有时也称为“线性一致性”(通常是在讨论共识算法的场景中),而把牺牲了 C 的 AP 系统又要尽可能获得正确的结果的行为称为追求“弱一致性”。不过,如果单纯只说“弱一致性”那其实就是“不保证一致性”的意思。在弱一致性里,人们又总结出了一种稍微强一点的特例,被称为“最终一致性”(Eventual Consistency),它是指:如果数据在一段时间之内没有被另外的操作所更改,那它最终将会达到与强一致性过程相同的结果,有时候面向最终一致性的算法也被称为“乐观复制算法”。

如上,我们讨论的分布式事务中,最开始我们引入各种分布式的处理方式,本意上是追求强一致性,到现在降低成为了“最终一致性”。所以说,在一致性变动的基础上,“事务”这个词的含义我们也同样需要为其进行扩展。我们通常把事务中的ACID称为刚性事务,于是我们在分布式系统中的处理也就可以随之称其为柔性事务,他们包括 可靠事件队列、以及ATTCCSAGA 和 XA 等事务模式,于是也就引出来我们今天的主人公 **分布式事务的解决方案者 seata**。

Seata

什么是二段提交

如图所示:

你使用分布式项目却不知道这个分布式事务?

就是说执行的流程如下:

  1. 首先想要修改A数据的时候把对应的记录A(之前的记录)加载到Buffer Pool(就是说缓冲区里面) 然后把记录A的旧值写入到  undo log 日志里面 便于回滚的操作。
  2. 执行器来更新内存里面的数据
    1. 首先会先执行redo log 日志此时是第一阶段的提交,然后将记录写入到 bin log 里面,最后一步就在执行器写 redo log(commit)

两个阶段的第一个阶段:(prepare) 写redo log 并将其标记为prepare状态

然后写bin log

两个阶段的第二个阶段:(commit)写redo log 并将其标记为commit状态

因为在操作的过程中 MySQL的日志有两个 我们需要在操作的时候  两个日志都保证是一致的,下面来分析一下在不使用两段提交的时候直接写入redo log 或者说 bin log的时候会出现什么样的问题:

  1. 就是说在写完redo log 日志之后 系统出现了异常,这个时候重启之后 InnoDB存储引擎会根据redo log 里面的数据进行数据的恢复,这个时候 主数据库是没有问题的,但是若是我们还有从库,这个时候就会出现从数据库并不能够将对应的数据值与主库进行一个正确的匹配, 因为我们的从库进行数据同步的时候依赖的是我们的bin log。
  2. 若是我们把两者颠倒过来,先写入bin log 之后在一段提交redo log 出现的问题就是没有能够正确的写入redo log 日志,从库能够根据 bin log 进行值的正确恢复,但是主库还是不能够拿到正确的数据,所以最后出现的情况还是不能够正确匹配。

所以就需要 两段式  来进行一个确定处理,如果只是在prepare 阶段没有提交,重启之后因为 redo 和bin 都没有数据,所以说也不会有影响,此时也会回滚事务,如果都写入,但是没有提交,此时重启之后就会对事务进一个提交。

Seata是什么

你使用分布式项目却不知道这个分布式事务?

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了ATTCCSAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata能够做什么

是一款开源的分布式事务解决方案,能够帮助我们解决前面提及到的分布式事务中出现的问题。即在分布式系统中,能够保证事物的一致性。具体更多的内容可以去官网查阅相关资料

Seata TC(Transaction Coordinator),事务协调者,会接受全局事务的开启、提交、回滚,分支事务的注册。通过它的协调,达到多事务的一致性。Seata TM(Transaction Manager),事务管理器,或者可以理解成事务的发起者,负责向 TC 发起全局事务的开启、提交、回滚。Seata RM(Resource Manager),资源管理器,或者可以理解成事务的参与者,负责向 TC 发起分支事务的注册、提交,接收自 TC 请求的分支事务的提交、回滚。

你使用分布式项目却不知道这个分布式事务?

不同模式

AT

前提
  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。
整体机制

前面我们已经介绍过两段提交,现在来在两段提交的基础上进行具体的演变

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个 本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。
写隔离
  • 一阶段本地事务提交前,需要确保先拿到  全局锁 。
  • 拿不到  全局锁 ,不能提交本地事务。
  • 拿  全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。

本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。

你使用分布式项目却不知道这个分布式事务?
Write-Isolation: Commit

tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

你使用分布式项目却不知道这个分布式事务?
Write-Isolation: Rollback

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必须要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

你使用分布式项目却不知道这个分布式事务?
Read Isolation: SELECT FOR UPDATE

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

工作机制

以一个示例来说明整个 AT 分支的工作过程。

业务表:product

Field Type Key
id bigint(20) PRI
name varchar(100)
since varchar(100)

AT 分支事务的业务逻辑:

update product set name = 'GTS' where name = 'TXC';
一阶段

过程:

  1. 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
  2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select idname, since from product where name = 'TXC';

得到前镜像:

id name since
1 TXC 2014
  1. 执行业务 SQL:更新这条记录的 name 为 'GTS'。
  2. 查询后镜像:根据前镜像的结果,通过  主键 定位数据。
select idname, since from product where id = 1;

得到后镜像:

id name since
1 GTS 2014
  1. 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到  UNDO_LOG 表中。
{
 "branchId"641789253,
 "undoItems": [{
  "afterImage": {
   "rows": [{
    "fields": [{
     "name""id",
     "type"4,
     "value"1
    }, {
     "name""name",
     "type"12,
     "value""GTS"
    }, {
     "name""since",
     "type"12,
     "value""2014"
    }]
   }],
   "tableName""product"
  },
  "beforeImage": {
   "rows": [{
    "fields": [{
     "name""id",
     "type"4,
     "value"1
    }, {
     "name""name",
     "type"12,
     "value""TXC"
    }, {
     "name""since",
     "type"12,
     "value""2014"
    }]
   }],
   "tableName""product"
  },
  "sqlType""UPDATE"
 }],
 "xid""xid:xxx"
}
  1. 提交前,向 TC 注册分支:申请  product 表中,主键值等于 1 的记录的  全局锁 。
  2. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
  3. 将本地事务提交的结果上报给 TC。
二阶段-回滚
  1. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
  2. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  3. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
  4. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
  1. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
  1. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

TCC

回顾总览中的描述:一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为
你使用分布式项目却不知道这个分布式事务?
Overview of a global transaction

根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.

AT 模式(参考链接 TBD)基于 支持本地 ACID 事务 的 关系型数据库:

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束, 自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志, 自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用  自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用  自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用  自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

SAGA

TCC 事务具有较强的隔离性,避免了“超售”的问题,而且其性能一般来说是本篇提及的几种柔性事务模式中最高的,但它仍不能满足所有的场景。TCC 的最主要限制是它的业务侵入性很强,这里并不是重复上一节提到的它需要开发编码配合所带来的工作量,而更多的是指它所要求的技术可控性上的约束。

譬如,把我们的场景事例修改如下:由于中国网络支付日益盛行,现在用户和商家在书店系统中可以选择不再开设充值账号,至少不会强求一定要先从银行充值到系统中才能进行消费,允许直接在购物时通过 U 盾或扫码支付,在银行账号中划转货款。这个需求完全符合国内网络支付盛行的现状,却给系统的事务设计增加了额外的限制:如果用户、商家的账号余额由银行管理的话,其操作权限和数据结构就不可能再随心所欲的地自行定义,通常也就无法完成冻结款项、解冻、扣减这样的操作,因为银行一般不会配合你的操作。所以 TCC 中的第一步 Try 阶段往往无法施行。我们只能考虑采用另外一种柔性事务方案:SAGA 事务。SAGA 在英文中是“长篇故事、长篇记叙、一长串事件”的意思。对应的由如下两个部分组成:

  • 大事务拆分若干个小事务,将整个分布式事务 T 分解为 n 个子事务,命名为 T1,T2,…,Ti,…,Tn。每个子事务都应该是或者能被视为是原子行为。如果分布式事务能够正常提交,其对数据的影响(最终一致性)应与连续按顺序成功提交 Ti等价。

  • 为每一个子事务设计对应的补偿动作,命名为 C1,C2,…,Ci,…,Cn。Ti与 Ci

    必须满足以下条件:

    • Ti与 Ci都具备幂等性。
    • Ti与 Ci满足交换律(Commutative),即先执行 Ti还是先执行 Ci,其效果都是一样的。
    • Ci必须能成功提交,即不考虑 Ci本身提交失败被回滚的情形,如出现就必须持续重试直至成功,或者要人工介入。

如果 T1到 Tn均成功提交,那事务顺利完成,否则,要采取以下两种恢复策略之一:

  • 正向恢复(Forward Recovery):如果 Ti事务提交失败,则一直对 Ti进行重试,直至成功为止(最大努力交付)。这种恢复方式不需要补偿,适用于事务最终都要成功的场景,譬如在别人的银行账号中扣了款,就一定要给别人发货。正向恢复的执行模式为:T1,T2,…,Ti(失败),Ti(重试)…,Ti+1,…,Tn。
  • 反向恢复(Backward Recovery):如果 Ti事务提交失败,则一直执行 Ci对 Ti进行补偿,直至成功为止(最大努力交付)。这里要求 Ci必须(在持续重试后)执行成功。反向恢复的执行模式为:T1,T2,…,Ti(失败),Ci(补偿),…,C2,C1。

与 TCC 相比,SAGA 不需要为资源设计冻结状态和撤销冻结的操作,补偿操作往往要比冻结操作容易实现得多。譬如,前面提到的账号余额直接在银行维护的场景,从银行划转货款到 Fenix's Bookstore 系统中,这步是经由用户支付操作(扫码或 U 盾)来促使银行提供服务;如果后续业务操作失败,尽管我们无法要求银行撤销掉之前的用户转账操作,但是由 Fenix's Bookstore 系统将货款转回到用户账上作为补偿措施却是完全可行的。

实战

这里使用到A项目调用B项目的dubbo接口,查看在下游逻辑出错和不出错情况下上游的修改会不会回滚。

前提准备

环境展示

这里使用nacos作为seata的注册中心,将主要数据配置搭载在注册中心上,对于每一个项目的入侵性最小。这里就跳过如何搭建本地的nacos和本地的seata服务,具体如何实施和搭建,可以参见指向君的个人**seata 无坑搭建**博客。

完成基础环境的启动之后,可以看到如下nacos上基础配置:

你使用分布式项目却不知道这个分布式事务?
image-20211224225552147

和已经注册到nacos上的seata服务:

你使用分布式项目却不知道这个分布式事务?
image-20211224225636179

配置

seata.enabled=true
# 默认配置数据分组
seata.registry.nacos.group=DEFAULT_GROUP
# 注册数据信息
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=127.0.0.1
seata.registry.nacos.namespace=3c628611-8dc8-4968-b99e-f7873e3350b6
seata.registry.nacos.cluster=default
# 配置数据信息
seata.config.type=nacos
seata.config.nacos.namespace=3c628611-8dc8-4968-b99e-f7873e3350b6
seata.config.nacos.server-addr=127.0.0.1
seata.config.nacos.group=SEATA_GROUP

依赖

<seata.version>1.4.2</seata.version> 
<dependency>
     <groupId>io.seata</groupId>
     <artifactId>seata-spring-boot-starter</artifactId>
     <version>${seata.version}</version>
</dependency>

食用

事物分组概念

如下所示  其实就是类比于服务实例,通过这个配置的事务分组去找寻真正的后端集群。在服务启动的时候,拿到对应的配置项即对应的集群名字,然后通过特定的前后缀+集群服务名称去构造服务名,各配置中心的服务名实现不同。拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的 TC 服务列表。

service.vgroupMapping.{your-server-name}-seata-service-group

对于每一个环境,都会有自己的applicationID,需要在nacos中添加对应的配置项:

你使用分布式项目却不知道这个分布式事务?
image-20211224230543690

演示

  1. 在上游对应的代码上添加对应的注解:
 @GlobalTransactional
image-20211229201141751
  1. 如上如图所示,上游调用下游 dubbo接口,在上游接口出现异常之后,下游接口进行的修改应该进行一个回滚。

提问

  1. 通过实战发现上游获取到的全局 XID同下游获取到的 XID是相同的,但是在配置信息中,我们并没有发现有关 dubbo相关联的配置,又或者说 seata是如何与 dubbo进行一个调度处理,使得上下游项目能够获取到一致的全局 XID。
  2. 通过实战发现,在对下游数据的修改之后,数据真的进入到了数据库中,并不像我们传统的事务(锁住数据,全局提交),在这个时候,数据真的进入到了数据库,并且也能够被查看(表示有读权限)。那么不会出现 不可重复读事件的发生吗?又或者说 seata还是已经默许了这种事情的存在?
  3. 什么是事务分组的概念?为什么在nacos配置中不添加 service.vgroupMapping.{your-server-name}-seata-service-group就会一直出现错误日志的打印?作用是什么?

注:以下内容部分出自凤凰架构以及seata 官网

指北君有话说

可以说seata算是目前为止用于解决分布式事务的最好方法啦,无论是从文档的完善性与各种情况下的解决方案,又或者是社区的活跃度与大厂光环的加持等。所以目前为止有相关困惑的小伙伴快些用起来吧!

关注开源指北,后台回复seata获取资源。

这里是开源指北,立志做最好的开源分享平台,分享有趣实用的开源项目。同时也欢迎加入开源指北交流群,群里你可以摸鱼、划水、吐槽、咨询,还有简历模板、各种技术面试资料等100G的资源等着你领取哦。快来一起聊一聊吧!

以上就是本次推荐的全部内容,我是指北君,感谢各位的观看。