事务的概念

事务的简介

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  1. 原子性(atomicity): 整个事务的所有操作,要么全部成功,要么全部失败。
  2. 一致性(consistency):在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
  3. 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行serializable,解决幻读)。

  1. 持久性(durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

本地事务

大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务( Local
Transaction )。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:
image.png
很多java应用都整合了spring,并使用其声明式事务管理功能来完成事务功能。一般使用的步骤如下:

  1. 配置事务管理器。spring提供了一个 PlatformTransactionManager 接口,其有2个重要的实现

类:
DataSourceTransactionManager :用于支持本地事务,事实上,其内部也是通过操作java.sql.Connection来开启、提交和回滚事务。
JtaTransactionManager :用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server’s transaction coordinator),也可以不依赖容器,配置一个本地的JTA
provider。

  1. 在需要开启的事务的bean的方法上添加 @Transitional 注解可以看到,spring除了支持本地事务,也支持分布式事务

分布式事务

分布式事务就是为了保证不同资源服务器的数据一致性。

跨库事务

跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。下图演示了一
个服务同时操作2个库的情况:
image.png

分库分表事务

通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如下
图,将数据库B拆分成了2个库:
image.png
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于sql: insert into user(id,name) values (1,"gupaoedu"),(2,"gpvp")。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。

跨应用事务

微服务架构是目前一个比较一个比较火的概念。例如上面提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过RPC框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:
image.png

分布式理论

CAP理论

image.png
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的
CAP原理包含如下三个元素:
C:Consistency,一致性。在分布式系统中的所有数据 备份,在同一时刻具有同样的值,所有节
点在同一时刻读取的数据都是最新的数据副本。
A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有
限的时间内处理完成并进行响应。
P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。

BASE理论

BASE理论是指,Basically Available(基本可用)、Soft-state( 软状态/柔性事务)、Eventual Consistency(最终一致性)。是基于CAP定理演化而来,是对CAP中一致性和可用性权衡的结果。核心
思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。

  1. 基本可用BA(Basically Available):

指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜
索引擎0.5秒返回查询结果,但由于故障,2秒响应查询结果;网页访问过大时,部分用户提供降级服务
等。简单来说就是基本可用。

  1. 软状态S:(Soft State):

软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点
间副本同步的时候存在延时。简单来说就是状态可以在一段时间内不同步。

  1. 最终一致性E:(Eventually Consistent):系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID是传统数据库常用的概念设计,追求强一致性模型。

简单来说就是在一定的时间窗口内, 最终数据达成一致即可。

场景事务解决方案模型

TDP模型

应用程序(Application Program ,简称AP):用于定义事务边界(即定义事务的开始和结束),并且在事
务边界内对资源进行操作。
资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。
事务管理器(Transaction Manager ,简称TM):负责分配事务唯一标识,监控事务的执行进度,并负
责事务的提交、回滚等。
通信资源管理器(Communication Resource Manager,简称CRM):控制一个TM域(TM domain)内
或者跨TM域的分布式应用之间的通信。
通信协议(Communication Protocol,简称CP):提供CRM提供的分布式应用节点之间的底层通信服
务。

2PC

在分布式事务中,多个小事务的提交与回滚,只有当前进程知道,其 他进程是不清楚
image.png
而为了实现多个数据库的事务一致性,就必然需要引入第三方节点来进行事务协调,如下图所 示。
image.png
从图中可以看出,通过一个全局的分布式事务协调工具,来实现多个数据库事务的提交和回
滚,在这样的架构下,事务的管理方式就变成了两个步骤。
1. 第一个步骤,开启事务并向各个数据库节点写入事务日志。
2. 第二个步骤,根据第一个步骤中各个节点的执行结果,来决定对事务进行提交或者回滚。

这就是所谓的2PC提交协议。
image.png
2PC提交流程如下:

  1. 表决阶段:此时 TM(协调者)向所有的参与者发送一个 事务请求,参与者在收到这请 求后,如果准备好了(写事务日志)就会向 TM发送一个 执行成功 消息作为回应,告知 TM 自己已经做好了准备,否则会返回一个 失败 消息;
  2. 提交阶段:TM 收到所有参与者的表决信息,如果所有参与者一致认为可以提交事务,那 么 TM就会发送 提交 消息,否则发送 回滚 消息;对于参与者而言,如果收到 提交 消息,就会提交本地事务,否则就会取消本地事务。

3PC

三阶段提交又称3PC,其在两阶段提交的基础上增加了**CanCommit阶段,并引入了超时机制。一旦事
务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调
者单点故障的问题。但是性能问题和不一致问题仍然没有根本解决。

1PC

image.png
这个阶段类似于2PC中的第二个阶段中的Ready阶段,是一种事务询问操作,事务的协调者向所有参与
者询问“你们是否可以完成本次事务?”,如果参与者节点认为自身可以完成事务就返回“YES”,否则
“NO”。而在实际的场景中参与者节点会对自身逻辑进行事务尝试,简单来说就是检查下自身状态的健康
性,看有没有能力进行事务操作。

2PC

image.png
在阶段一中,如果所有的参与者都返回Yes的话,那么就会进入PreCommit阶段进行事务预提交。此时
分布式事务协调者会向所有的参与者节点发送PreCommit请求,参与者收到后开始执行事务操作,并将
Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会
向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。否则,如果阶段一中有任何一个参与者节点返回的结果是No响应,或者协调者在等待参与者节点反馈的过程中超时(2PC中只有协调者可以超时,参与者没有超时机制)。整个分布式事务就会中断,协调者就会向所有的参与者发送abort请求。

3PC

image.png
在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从预提交状态-》“提
交状态。然后向所有的参与者节点发送“doCommit”请求,参与者节点在收到提交请求后就会各自执
行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。
相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者
节点发送abort请求,从而中断事务

2PC vs 3PC

相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而
2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?这个优化点,主要是避免了参与者在长时
间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时
机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞
时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一
缓冲阶段**保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数
据不一致的问题。

TCC

TCC与2PC、3PC一样,也是分布式事务的一种实现方案。TCC(Try-Confifirm-Cancel)又称补偿事
务。其核心思想是:”针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)”。它分为三个操作:

  • Try阶段:主要是对业务系统做检测及资源预留。
  • Confifirm阶段:确认执行业务操作。
  • Cancel阶段:取消执行业务操作。

image.png

TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一
个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自
己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能

不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confifirm、cancel三个操
作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策
略。为了满足一致性的要求,confifirm和cancel接口还必须实现幂等。

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

image.png
image.png

这个方案存在两个问题:
1. 本地事务与消息发送的原子性问题
2. 事务参与方接收消息的可靠性
3. 消息重复消费的问题

基于本地消息表实现重发

image.png
image.png

MQ分布式事务

image.png
image.png

image.png

Seata分布式事务

Seata AT模式

介绍看官网
https://seata.io/zh-cn/docs/overview/what-is-seata.html

前提

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

    整体机制

    两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

具体工作流程补充说明如下:

  1. 第一阶段, 应用系统会把一个业务数据的事务操作和回滚日志记录在同一个本地事务中提交,在提交之前,会向TC(seata server)注册事务分支,并申请针对本次事务操作 的表的全局锁。 接着提交本地事务,本地事务会提交业务数据的事务操作以及UNDO LOG,放在一个事务中提交。
  2. 第二个阶段,这一个阶段会根据参与到同一个XID下所有事务分支在第一个阶段的执行结果来决定事务的提交或者回滚,这个回滚或者提交是TC来决定的,它会告诉当前XID下的 所有事务分支,提交或者回滚。
    1. 如果是提交, 则把提交请求放入到一个异步任务队列,并且马上返回提交成功给到 TC,这样可以避免阻塞问题。而这个异步任务,只需要删除UNDO LOG就行,因为原本的事务已经提交了。
    2. 如果是回滚,则开启一个本地事务,执行以下操作
      1. 通过XID和Branch ID查找到响应的UNDO LOG记录
      2. 数据校验,拿到UNDO LOG中after image(修改之后的数据)和当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改,这种情况需要根据配置策略来做理。
      3. 根据UNDO LOG中的before image和业务SQL的相关信息生成并执行回滚语句
      4. 提交本地事务,并把本地事务的执行结果上报给TC

        写隔离

        所谓的写隔离,就是多个事务对同一个表的同一条数据做修改的时候,需要保证对于这个数据更新操作的隔离性,在传统事务模型中,我们一般是采用锁的方式来实现。那么在分布式事务中,如果存在多个全局事务对于同一个数据进行修改,为了保证写操作的隔离,也需要通过一种方式来实现隔离性,自然也是用到锁的方法,具体来说。
  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

  1. tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。
  2. 如果tx1拿到了全局锁,则提交本地事务并释放本地锁。
  3. tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,全局锁的持有者是tx1,所以tx2拿不到全局锁,需要等待

image.png
接着, tx1在第二阶段完成事务提交或者回滚,并释放全局锁。此时tx2就可以拿到全局锁来提交本地事务。当然这里需要注意的是,如果tx1的第二阶段是全局回滚,则tx1需要重新获取这个数据的本地锁,然后进行反向补偿更新实现事务分支的回滚。 此时,如果tx2仍然在等待这个数据的全局锁并且同时持有本地锁,那么tx1的分支事务回滚 会失败,分支的回滚会一直重试直到tx2的全局锁等待超时,放弃全局锁并回滚本地事务并释放本地锁之后,tx1的分支事务才能最终回滚成功.

由于在整个过程中, 全局锁在tx1结束之前一直被tx1持有,所以并不会发生脏写问题。

image.png

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
image.png
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

工作机制

image.png
以一个示例来说明整个 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. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
    1. select id, name, since from product where name = 'TXC';
    得到前镜像:
id name since
1 TXC 2014
  1. 执行业务 SQL:更新这条记录的 name 为 ‘GTS’。
  2. 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
    1. select id, name, since from product where id = 1;
    得到后镜像:
id name since
1 GTS 2014
  1. 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。

    1. {
    2. "branchId": 641789253,
    3. "undoItems": [{
    4. "afterImage": {
    5. "rows": [{
    6. "fields": [{
    7. "name": "id",
    8. "type": 4,
    9. "value": 1
    10. }, {
    11. "name": "name",
    12. "type": 12,
    13. "value": "GTS"
    14. }, {
    15. "name": "since",
    16. "type": 12,
    17. "value": "2014"
    18. }]
    19. }],
    20. "tableName": "product"
    21. },
    22. "beforeImage": {
    23. "rows": [{
    24. "fields": [{
    25. "name": "id",
    26. "type": 4,
    27. "value": 1
    28. }, {
    29. "name": "name",
    30. "type": 12,
    31. "value": "TXC"
    32. }, {
    33. "name": "since",
    34. "type": 12,
    35. "value": "2014"
    36. }]
    37. }],
    38. "tableName": "product"
    39. },
    40. "sqlType": "UPDATE"
    41. }],
    42. "xid": "xid:xxx"
    43. }
  2. 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁

  3. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
  4. 将本地事务提交的结果上报给 TC。

    二阶段-回滚

  5. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。

  6. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  7. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
  8. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:

update product set name = ‘TXC’ where id = 1;

  1. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

    二阶段-提交

  2. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。

  3. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

    附录

    回滚日志表

    UNDO_LOG Table:不同数据库在类型上会略有差别。
    以 MySQL 为例:
Field Type
branch_id bigint PK
xid varchar(100)
context varchar(128)
rollback_info longblob
log_status tinyint
log_created datetime
log_modified datetime

— 注意此处0.7.0+ 增加字段 context

  1. -- 注意此处0.7.0+ 增加字段 context
  2. CREATE TABLE `undo_log` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  4. `branch_id` bigint(20) NOT NULL,
  5. `xid` varchar(100) NOT NULL,
  6. `context` varchar(128) NOT NULL,
  7. `rollback_info` longblob NOT NULL,
  8. `log_status` int(11) NOT NULL,
  9. `log_created` datetime NOT NULL,
  10. `log_modified` datetime NOT NULL,
  11. PRIMARY KEY (`id`),
  12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

TCC 模式

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

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为

image.png
image.png
根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.
AT 模式(参考链接 TBD)基于 支持本地 ACID 事务关系型数据库

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

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

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

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

Saga 模式

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
image.png
理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)

适用场景:

  • 业务流程长、业务流程多
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

    优势:

  • 一阶段提交本地事务,无锁,高性能

  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

    缺点:

  • 不保证隔离性(应对方案见用户文档

Seata-Server安装

我们在选择用Seata版本的时候,可以先参考下官方给出的版本匹配(Seata版本也可以按自己的要求选
择):https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

下载seat-server

https://github.com/seata/seata/releases/tag/v1.4.0

image.png

Seata集成SpringBoot

接下来开始在项目中集成使用Seata的AT模式实现分布式事务控制,关于如何集成,官方也给出了
很多例子,可以通过https://github.com/seata/seata-samples查看:
image.png
集成SpringBoot可以按照如下步骤实现:

1:引入依赖包spring-cloud-starter-alibaba-seata 2:配置Seata 3:创建代理数据源 4:@GlobalTransactional全局事务控制

提前搭建好基于springcloudalibaba的项目在项目中引入

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  4. <version>2.2.5.RELEASE</version>
  5. </dependency>

父工程依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.3.1.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <groupId>com.yehui</groupId>
  8. <artifactId>gulimall-sentinel</artifactId>
  9. <version>0.0.1-SNAPSHOT</version>
  10. <name>gulimall-sentinel</name>
  11. <description>Demo project for Spring Boot</description>
  12. <properties>
  13. <java.version>1.8</java.version>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  16. <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
  17. <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
  18. <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.alibaba.cloud</groupId>
  27. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. <exclusions>
  34. <exclusion>
  35. <groupId>org.junit.vintage</groupId>
  36. <artifactId>junit-vintage-engine</artifactId>
  37. </exclusion>
  38. </exclusions>
  39. </dependency>
  40. </dependencies>
  41. <dependencyManagement>
  42. <dependencies>
  43. <dependency>
  44. <groupId>org.springframework.boot</groupId>
  45. <artifactId>spring-boot-dependencies</artifactId>
  46. <version>${spring-boot.version}</version>
  47. <type>pom</type>
  48. <scope>import</scope>
  49. </dependency>
  50. <dependency>
  51. <groupId>com.alibaba.cloud</groupId>
  52. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  53. <version>${spring-cloud-alibaba.version}</version>
  54. <type>pom</type>
  55. <scope>import</scope>
  56. </dependency>
  57. <dependency>
  58. <groupId>org.springframework.cloud</groupId>
  59. <artifactId>spring-cloud-dependencies</artifactId>
  60. <version>${spring-cloud.version}</version>
  61. <type>pom</type>
  62. <scope>import</scope>
  63. </dependency>
  64. </dependencies>
  65. </dependencyManagement>
  66. <build>
  67. <plugins>
  68. <plugin>
  69. <groupId>org.springframework.boot</groupId>
  70. <artifactId>spring-boot-maven-plugin</artifactId>
  71. </plugin>
  72. </plugins>
  73. </build>

其中一个子工程依赖

  1. <properties>
  2. <maven.compiler.source>8</maven.compiler.source>
  3. <maven.compiler.target>8</maven.compiler.target>
  4. </properties>
  5. <dependencies>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-web</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-openFeign</artifactId>
  13. <version>2.1.1.RELEASE</version>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.mybatis.spring.boot</groupId>
  17. <artifactId>mybatis-spring-boot-starter</artifactId>
  18. <version>2.2.0</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>mysql</groupId>
  22. <artifactId>mysql-connector-java</artifactId>
  23. <version>8.0.16</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.alibaba.cloud</groupId>
  27. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  28. <version>2.2.5.RELEASE</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>com.alibaba.cloud</groupId>
  32. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  33. </dependency>
  34. </dependencies>

配置seata


依赖引入后,我们需要在项目中配置SeataClient端信息,关于SeataClient端配置信息,官方也给出了很
多版本的模板,可以打开https://github.com/seata/seata/tree/1.4.0/script,如下图:
image.png
我们可以选择spring,把 application.yml 文件直接拷贝到工程中,文件如下:
image.png

  1. seata_transaction: default:事务分组,前面的seata_transaction可以自定义,通过事务分组很
  2. 方便找到集群节点信息。
  3. tx-service-group: seata_transaction:指定应用的事务分组,和上面定义的分组前部分保持一致。
  4. default: 192.168.211.145:8091:服务地址,seata-server服务地址

通过代理数据源可以保障事务日志数据和业务数据能同步,关于代理数据源早期需要手动创建,但是随
着Seata版本升级,不同版本实现方案不一样了,下面是官方的介绍:

1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk proxy或者cglib proxy 1.0.0: client.support.spring.datasource.autoproxy=true 0.9.0: support.spring.datasource.autoproxy=true

我们当前的版本是1.3.0,所以我们创建代理数据源只需要在启动类上添加
@EnableAutoDataSourceProxy 注解即可,在 com.yehui.order.OrderBootstrapn 上添加该注解:

  1. @SpringBootApplication
  2. @EnableFeignClients
  3. @EnableDiscoveryClient
  4. /**
  5. * DataSourceProxy:代理数据源,为了保障日志操作和业务操作在同一个事务中
  6. * 1)业务操作(增删改)-》操作数据
  7. * 解析要操作的sQL-》根据sQL查询数据库数据
  8. * 2)将查询出的元数据保存到数据库中undo_Log
  9. * 3)执行业务操作
  10. * 4)把操作之后的数据查询出来修改到日志表中undo_Log
  11. */
  12. @EnableAutoDataSourceProxy
  13. public class OrderBootstrap {
  14. public static void main(String[] args) {
  15. SpringApplication.run(OrderBootstrap.class);
  16. }
  17. }

全局事务控制

加 @GlobalTransactional ,那么此时该方法就是全 局事务的入口,为了测试事务,我们在代码中添加一个异常,代码如下:
image.png

启动seata-server

nohup ./seata-server.sh -p 8092 > seata.log 2>&1 &

image.png

注册中心和配置中心看官网

https://seata.io/zh-cn/docs/user/registry/nacos.html

client端的配置

  1. 结合配置中心配置
  2. spring:
  3. cloud:
  4. alibaba:
  5. seata:
  6. tx-service-group: my_test_tx_group
  7. ##配置中心
  8. seata:
  9. config:
  10. type: nacos
  11. nacos:
  12. server-addr: 127.0.0.1:8848
  13. group: DEFAULT_GROUP
  14. username: nacos
  15. password: nacos
  16. #注册中心
  17. registry:
  18. type: nacos
  19. nacos:
  20. server-addr: 127.0.0.1:8848
  21. cluster: default
  22. group: DEFAULT_GROUP
  23. username: nacos
  24. password: nacos
  25. application: seata-server

配置中心
image.png