🔥博客主页: 【小扳_-CSDN博客】**
❤感谢大家点赞👍收藏⭐评论✍**
文章目录
[1.0 分布式事务](#1.0 分布式事务)
[1.1 认识 Seata](#1.1 认识 Seata)
[2.0 部署 TC 服务](#2.0 部署 TC 服务)
[2.1 准备数据库表](#2.1 准备数据库表)
[2.2 准备配置文件](#2.2 准备配置文件)
[2.3 Docker 部署](#2.3 Docker 部署)
[3.0 微服务集成 Seata](#3.0 微服务集成 Seata)
[3.1 引入依赖](#3.1 引入依赖)
[3.2 改造配置](#3.2 改造配置)
[3.3 添加数据库表](#3.3 添加数据库表)
[3.4 具体使用分布式事务](#3.4 具体使用分布式事务)
[4.0 XA 模式](#4.0 XA 模式)
[4.1 两阶段提交](#4.1 两阶段提交)
[4.2 Seata 的 XA 模型](#4.2 Seata 的 XA 模型)
[4.3 XA 模型优缺点](#4.3 XA 模型优缺点)
[4.4 实现步骤](#4.4 实现步骤)
[5.0 AT 模式](#5.0 AT 模式)
[5.1 Seata 的 AT 模型](#5.1 Seata 的 AT 模型)
[5.2 AT 与 XA 的区别](#5.2 AT 与 XA 的区别)
1.0 分布式事务
通过一个例子来直观的认识在微服务中分布式事务。
首先看看项目中的下单业务整体流程:
由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,
因此下单过程中就会跨多个数据库完成业务,而每个微服务都会执行自己的本地事务:
1)交易服务:下单事务
2)购物车服务:清理购物车事务
3)库存服务:扣减库存事务
整个业务中,各个本地事务是由关联的。因此每个微服务的本地事务,也可以称为分支事
务。多个有关联的分支事务一起就组成了全局事务。必须要保证整个全局事务同时成功或失败。
每一个分支事务就是传统的单体事务,都可以满足 ACID 特性,但全局事务跨越多个服务,
多个数据库,是如何满足呢?
1.1 认识 Seata
解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来
解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在
2019 年开源的 Seata 了。
Seata 官网:Seata 是什么? | Apache Seata
其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼
此的执行状态。
因此解决分布式事务的思想非常简单:
就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证
全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来
实现的。
在 Seata 的事务管理中有三个重要的角色:
1)TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事
务提交或回滚。
2)TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或
回滚全局事务。
3)RM (Resource Manager) - 资源管理器:管理分支事务,与 TC 交谈以注册分支事务和报
告分支事务的状态,并驱动分支事务提交或回滚。
Seata 的工作架构如图所示:
其中,TM 和 RM 可以理解为 Seata 的客户端部分,引入到参与事务的微服务依赖中即可。
将来 TM 和 RM 就会协助微服务,实现本地分支事务与 TC 之间交互,实现事务的提交或回滚。
而 TC 服务则是事务协调中心,是一个独立的微服务,需要单独部署。
2.0 部署 TC 服务
2.1 准备数据库表
Seata 支持多种存储模式,但考虑到持久化的需要,一般选择基于数据库存储。
sqlCREATE DATABASE IF NOT EXISTS `seata`; USE `seata`; CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid_and_branch_id` (`xid` , `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
数据表结构分析:
1)branch_table:分支表,存放分支事务信息。
2)global_table:全局表,存放全局事务信息。
3)剩下两张 lock 表就是锁,tc 服务会基于数据表实现一个锁功能来确保分布式事务执行的
过程中的线程安全。
2.2 准备配置文件
seata 运行时所需要的配置文件:
解析 application.yml 配置文件:
bashserver: port: 7099 spring: application: name: seata-server logging: config: classpath:logback-spring.xml file: path: ${user.home}/logs/seata # extend: # logstash-appender: # destination: 127.0.0.1:4560 # kafka-appender: # bootstrap-servers: 127.0.0.1:9092 # topic: logback_to_logstash console: user: username: admin password: admin seata: config: # support: nacos, consul, apollo, zk, etcd3 type: file # nacos: # server-addr: nacos:8848 # group : "DEFAULT_GROUP" # namespace: "" # dataId: "seataServer.properties" # username: "nacos" # password: "nacos" registry: # support: nacos, eureka, redis, zk, consul, etcd3, sofa type: nacos nacos: application: seata-server server-addr: nacos:8848 group : "DEFAULT_GROUP" namespace: "" username: "nacos" password: "nacos" # server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 rollback-retry-timeout-unlock-enable: false enable-check-auth: true enable-parallel-request-handle: true retry-dead-threshold: 130000 xaer-nota-retry-timeout: 60000 enableParallelRequestHandle: true recovery: committing-retry-period: 1000 async-committing-retry-period: 1000 rollbacking-retry-period: 1000 timeout-retry-period: 1000 undo: log-save-days: 7 log-delete-period: 86400000 session: branch-async-queue-size: 5000 #branch async remove queue size enable-branch-async-remove: false #enable to asynchronous remove branchSession store: # support: file 、 db 、 redis mode: db session: mode: db lock: mode: db db: datasource: druid db-type: mysql driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC user: root password: 123 min-conn: 10 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock query-limit: 1000 max-wait: 5000 # redis: # mode: single # database: 0 # min-conn: 10 # max-conn: 100 # password: # max-total: 100 # query-limit: 1000 # single: # host: 192.168.150.101 # port: 6379 metrics: enabled: false registry-type: compact exporter-list: prometheus exporter-prometheus-port: 9898 transport: rpc-tc-request-timeout: 15000 enable-tc-server-batch-send-response: false shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker boss-thread-size: 1
由于根据 Docker 部署 Seata,因此将整个 seata 文件拷贝到 /root 目录下。
2.3 Docker 部署
需要注意,要确保 nacos、mysql 都在同一个网络中。如果某个容器不在同一个网络,可以
参考下面的命令将某容器加入指定网络:
bashdocker network connect [网络名] [容器名]
在 /root 目录执行下面的命令:
bashdocker run --name seata \ -p 8099:8099 \ -p 7099:7099 \ -e SEATA_IP=192.168.150.101 \ -v ./seata:/seata-server/resources \ --privileged=true \ --network hm-net \ -d \ seataio/seata-server:1.5.2
3.0 微服务集成 Seata
参与分布式事务的每一个微服务都需要集成 Seata 。
3.1 引入依赖
为了方便各个微服务集成 seata,需要把 seata 配置共享到 nacos 中,因此不仅仅要引入
seata 依赖,还要引入 nacos 配置依赖:
XML<!--统一配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
3.2 改造配置
在 nacos 上添加一个共享的 seata 配置,命名为 shared-seata.yaml:
内容如下:
bashseata: registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址 type: nacos # 注册中心类型 nacos nacos: server-addr: 192.168.150.101:8848 # nacos地址 namespace: "" # namespace,默认为空 group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP application: seata-server # seata服务名称 username: nacos password: nacos tx-service-group: hmall # 事务组名称 service: vgroup-mapping: # 事务组与tc集群的映射关系 hmall: "default"
然后,改造模块,添加 bootstrap.yaml:
内容如下:
bashspring: application: name: trade-service # 服务名称 profiles: active: dev cloud: nacos: server-addr: 192.168.150.101 # nacos地址 config: file-extension: yaml # 文件后缀名 shared-configs: # 共享配置 - dataId: shared-jdbc.yaml # 共享mybatis配置 - dataId: shared-log.yaml # 共享日志配置 - dataId: shared-swagger.yaml # 共享日志配置 - dataId: shared-seata.yaml # 共享seata配置
可以看到这里加载了共享的 seata 配置。
3.3 添加数据库表
seata 的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。
sql-- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id', `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
导入数据库之后:
3.4 具体使用分布式事务
配置好 TC 服务,微服务集成 Seata 之后,使用起来非常简单,只需要在对应的业务中添加
一个 Seata 提供的 @GlobalTransactional 注解。
@GlobalTransactional 注解就是在标记事务的起点,将来 TM 就会基于这个方法判断全局事
务范围,初始化全局事务。
4.0 XA 模式
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction
Processing)标准,XA 规范描述了全局的 TM 与局部的 RM 之间的接口,几乎所有主流的数据
库都对 XA 规范 提供了支持。
4.1 两阶段提交
目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。
正常情况:
异常情况:
一阶段:
1)事务协调者通知每个事务参与者执行本地事务。
2)本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库
锁。
二阶段:
事务协调者基于一阶段的报告来判断下一步操作:
1)如果一阶段都成功,则通知所有事务参与者,提交事务。
2)如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务。
4.2 Seata 的 XA 模型
Seata 对原始的 XA 模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:
RM 一阶段的工作:
1)注册分支事务到 TC
2)执行分支业务 SQL 但不提交
3)报告执行状态到 TC
TC 二阶段的工作:
TC检测各分支事务执行状态:
1)如果都成功,通知所有 RM 提交事务。
2)如果有失败,通知所有 RM 回滚事务 。
RM 二阶段的工作:
1)接收 TC 指令,提交或回滚事务。
具体流程:
请求进入全局事务对应的方法,TM 会向 TC 注册一个全局事务,TM 开始执行内部业务逻
辑,内部业务逻辑最终是要调用这些微服务的,其中每一个微服务都可以认为是全局事务中的分支
事务。当分支事务开始执行时候,资源管理器 RM 会拦截其对数据库的操作,会先向 TC 报告,注
册一下分支事务,属于哪个全局,然后才能去执行业务 sql,sql 执行完后,但是 XA 规范规定 sql
是不能提交的。(事务不提交,其对应的锁不会释放,事务写操作,往往会开启一个排他锁,占用
操作资源,只要锁没有释放,其他任何人无法对这些资源进行操作。)等到 TM 告诉 TC 事务结
束,TC 会检查所有分支事务的状态,若成功,则让所有事务提交,释放锁,否则回滚。
4.3 XA 模型优缺点
1)XA 模式的优点是什么?
事务的强一致性,满足 ACID 原则。
常用数据库都支持,实现简单,并且没有代码侵入。
2)XA 模式的缺点是什么?
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差。
依赖关系型数据库实现事务。
4.4 实现步骤
首先,要在配置文件中指定要采用的分布式事务模式。可以在 Nacos 中的共享 shared-
seata.yaml 配置文件中设置:
bashseata: data-source-proxy-mode: XA
其次,要利用 @GlobalTransactional 注解标记分布式事务的入口方法:
5.0 AT 模式
AT 模式同样是分阶段提交的事务模型,不过缺弥补了 XA 模型中资源锁定周期过长的缺陷。
5.1 Seata 的 AT 模型
基本流程图:
阶段一 RM 的工作:
1)注册分支事务
2)记录 undo-log(数据快照)
3)执行业务 sql 并提交
4)报告事务状态
阶段二提交时 RM 的工作:
1)删除 undo-log 即可
阶段二回滚时 RM 的工作:
1)根据 undo-log 恢复数据到更新前
5.2 AT 与 XA 的区别
简述 AT 模式与 XA 模式最大的区别是什么?
1)XA 模式一阶段不提交事务,锁定资源;AT 模式一阶段直接提交,不锁定资源。
2)XA 模式依赖数据库机制实现回滚;AT 模式利用数据快照实现数据回滚。
3)XA 模式强一致;AT 模式最终一致。
可见,AT 模式使用起来更加简单,无业务侵入,性能更好。因此企业 90% 的分布式事务都可以用
AT 模式来解决。