seata-1.8.0 分布式事务
微服务架构下,各个服务之间互相调用,用的是不同的数据库、不同的线程等等,这样原本单体架构的事务就无法起作用,比如交易服务写入创建订单数据后去调用商品服务扣库存,如果扣库存失败了,但交易服务创建订单成功了,商品服务能回滚,但交易服务完全不知道,就提交事务,这不好。
1. 选型
要解决这种问题,可以找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。
这里用到了seata这个组件,他主要用这仨来管理事务
-
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
-
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
-
RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

这里面的TM、RM算是seata的客户端,需要被各微服务依赖,然后与TC配合协调;TC是服务端,是一个单独的服务。
2. 部署TC服务
这里我们采用docker来部署seata1.8版本
1. 数据库
CREATE DATABASE `seata` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
USE `seata`;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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;
-- the table to store BranchSession data
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;
-- the table to store lock data
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` (`xid`)
) 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);
SET FOREIGN_KEY_CHECKS = 1;
一共四张表。
2. 配置文件
这里我们要先启动seata服务,然后去获取默认的配置文件,再拿来改造。
docker run --name seata -p 8091:8091 -p 7091:7091 -d seataio/seata-server:1.8.0
# /seata-server/resources 就是seata配置文件所在地
docker cp seata:/seata-server/resources /root/seata
来到目录中,可以看到配置文件,TC其实也是个spring项目

然后我们主要就是编辑application.yml,这里我以我的配置为主,里面还有别的选项可配置
server:
port: 7091
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: seata
password: seata
seata:
config:
type: nacos
nacos:
server-addr: nacos:8848
namespace:
group: DEFAULT_GROUP
username: nacos
password: nacos
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacos:8848
group: DEFAULT_GROUP
namespace:
cluster: default
username: nacos
password: nacos
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: mysql
password: mysql
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
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/**/*.woff,/**/*.woff2,/api/v1/auth/login
需要注意:
- nacos地址、服务名、nacos账号密码、分组、命名空间、集群名
- mysql地址、账号、密码、jdbc驱动
- 需要与nacos、mysql在同一网络下
3. 启动!
docker run --name seata \
-p 8091:8091 \
-p 7091:7091 \
-e SEATA_IP=192.168.10.10 \
-v /root/seata/resources:/seata-server/resources \
--privileged=true \
--network your-networker \
-d \
seataio/seata-server:1.8.0
启动后可以看看日志

可以访问其7091端口,登录看看

再看看nacos,有没有注册服务

3. 微服务集成
1. 引入依赖
这里如果spring-cloud-alibaba依赖比较旧的话,其管理的seata依赖也会比较旧(JDK版本较高的话会不适配),我们这里需要1.8.0的版本(至少可以支持到JDK21)
在父工程
<properties>
<seata.version>1.8.0</seata.version>
</properties>
微服务工程
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
2. 配置application.yml
seata的配置我们放在nacos上

内容是:
seata:
# TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
registry:
# 注册中心类型 nacos
type: nacos
nacos:
# nacos地址
server-addr: ${spring.cloud.nacos.server-addr}
# namespace,默认为空
namespace: ""
# 分组,默认是DEFAULT_GROUP
group: DEFAULT_GROUP
# seata服务名称
application: seata-server
username: nacos
password: nacos
# 事务组名称
tx-service-group: ylz
service:
# 事务组与tc集群的映射关系
vgroup-mapping:
ylz: "default"
需要注意的是,事务组名称可以随便取,然后seata.service.vgroup-mapping中的键要指定为事务组每次,值是集群名称(nacos查看seata服务详情,如下图)

3. 配置bootstrap.yml
spring:
cloud:
nacos:
server-addr: 192.168.10.10:8848
config:
file-extension: yaml
shared-configs:
- data-id: share-seata.yml
4. 验证
启动服务后,去查看seata服务的日志
显示TM和RM都有注册信息,且有 success、对应的服务名,就是连上了
4. 使用
seata共提供了几种分布式事务的解决方案
1. XA模式
分为两个阶段:
- 第一阶段,TC通知所有服务跑本地事务,各服务跑完不提交候着,通知状态给TC
- 第二阶段,如果都没有异常的话,TC通知所有RM,让服务提交本地事务;如果有异常的话,通知RM,让所有服务回滚
正常情况:

有异常通知全部回滚:

从这里也可以看出:
- 优点是强一致性
- 缺点是第一阶段事务不提交等待会占用数据库锁
1. 配置application.yml,使用XA模式
seata:
data-source-proxy-mode: XA
2. 在入口方法加上@GlobalTransactional注解

需要注意:分布式事务回滚是通知各个服务本地回滚,被调用的服务接口需要有@Transactional注解来开启本地事务
2. AT模式
分为两个阶段:
- 第一阶段,TC通知所有服务跑本地事务,跑前会先记录当前的数据快照到undo_log表,然后各服务跑完直接提交,通知状态给TC
- 第二阶段,如果都没有异常的话,TC通知所有RM,删除数据快照;如果有异常的话,通知RM,根据undo_log恢复快照

这与XA模式比起来同样有优缺点:
- 优点是事务马上提交,不会占用锁,性能更好
- 缺点是最终一致,可能会脏读(不过恢复快照说是很快,出现脏读几率较小)
现在主流也是用AT模式
1. 创建undo_log表
在需要分支持布式的微服务的数据库中加上这张AT模式事务快照表
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT '分支事务ID',
`xid` varchar(128) NOT NULL COMMENT '全局事务ID',
`context` varchar(128) NOT NULL COMMENT '序列化格式',
`rollback_info` longblob NOT NULL COMMENT '回滚数据',
`log_status` int(11) NOT NULL COMMENT '0-正常,1-全局已完成',
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB
AUTO_INCREMENT=1
DEFAULT CHARSET=utf8mb4 COMMENT='AT mode transaction undo';
2. 修改配置,将模式改为AT
当然,它默认的模式就是AT,也可以选择注释掉
seata:
data-source-proxy-mode: AT
3. 在入口方法加上@GlobalTransactional注解
与4.1.2一样
更多推荐


所有评论(0)