分布式事务组件Seata简介与使用,搭配Nacos统一管理服务端和客户端配置
文章目录
- 一. Seata简介
- 二. 官方文档
- 三. Seata分布式事务代码实现
- 0. 环境简介
- 1. 添加undo_log表
- 2. 添加依赖
- 3. 添加配置
- 4. 开启Seata事务管理
- 5. 启动演示
- 四. Seata Server配置Nacos
- 1. 修改配置类型
- 2. 创建Nacos配置
- 五. Seata Client配置Nacos
- 1. 增加Seata关联Nacos的配置
- 2. 在Nacos中对应的train-group添加配置
- 3. 测试有效性
一. Seata简介
Seata
是一款开源的分布式事务解决方案,旨在解决微服务架构下的数据一致性问题。它支持 AT、TCC、Saga 和 XA 四种事务模式,能够适应不同的业务场景。
AT模式
,默认,简单,需要增加undo_log表,生成反向SQL,性能高;回滚后,原来没数据的,现在还是没数据TCC模式
,try confirm/cancel,三个阶段的代码都得自己实现,Seata只负责调度;对业务代码侵入性较强,必要时可能还要修改数据库SAGA模式
,长事务解决方案,需要程序员自己编写两阶段代码(AT模式不需要写第二阶段);基于状态机来实现的,需要一个JSON文件,可异步执行XA模式
,XA 协议是由 X/Open 组织提出的分布式事务处理规范,基于数据库的XA协议来实现2PC发称为XA方案,适用于强一致性的场景,比如金融、银行等
Seata 的核心架构包括事务协调器(TC)、事务管理器(TM)和资源管理器(RM),通过两阶段提交协议实现全局事务的管理。其优势在于对业务代码侵入性低、性能高效,且与 Spring Cloud、Dubbo 等主流微服务框架无缝集成,是构建高可靠分布式系统的理想选择。
二. 官方文档
官方文档
下载地址
GitHub 仓库
Seata 示例项目
三. Seata分布式事务代码实现
接下来将用代码模拟AT模式的分布式事务管理。
0. 环境简介
项目模拟12306会员购票场景,购票后需要进行余票更新以及会员购票信息保存的操作,分别对应business
模块和member
模块的操作。要求business
通过feign
调用member
的保存功能。
此时如果用传统事务注解@Transactional
,仅仅在business
模块的方法进行事务管理,是无法管理远程调用的模块同样进行回滚的。
此时就要用到Seata
进行分布式事务管理,同时演示的是AT模式,自动生成反向SQL。
1. 添加undo_log表
在business
和member
数据库中添加undo_log
表,执行SQL:
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,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2. 添加依赖
在business
和member
模块中添加Seata
依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
3. 添加配置
在business
和member
模块的bootstrap.properties
添加配置:
# 事务组名称service.vgroupMapping.train-group=default
seata.tx-service-group=train-group
# 事务组和seata集群做关联
seata.service.vgroup-mapping.train-group=default
# seata集群对应的机器
seata.service.grouplist.default=127.0.0.1:8091
一个项目的多个模块,配置成同一个事务组
4. 开启Seata事务管理
仅需要service业务逻辑方法上添加注解,即可开启Seata事务管理:
import io.seata.spring.annotation.GlobalTransactional;
@GlobalTransactional
5. 启动演示
- 启动Seata服务
解压Seata文件夹,双击bin目录下的seata-server.bat
Seata服务可视化界面启动在7091端口,而监听端口是8091
- 启动SpringBoot服务
可以看到启动后Seata打印的日志,表明SpringBoot应用和Seata成功连接,成功注册两个TM和RM
- 异常设置
为了显示事务回滚效果,在member模块保存的业务逻辑中,手动添加事务异常:
public void save(MemberTicketReq req) throws Exception {
LOG.info("seata全局事务ID save: {}", RootContext.getXID());
DateTime now = DateTime.now();
Ticket ticket = BeanUtil.copyProperties(req, Ticket.class);
ticket.setId(SnowUtil.getSnowflakeNextId());
ticket.setCreateTime(now);
ticket.setUpdateTime(now);
ticketMapper.insert(ticket);
// 模拟被调用方出现异常
if (1 == 1) {
throw new Exception("测试异常11");
}
}
预计效果为,Seata回滚余票更新,并向前端抛出异常
- 测试结果
与预期结果相同,测试成功!
四. Seata Server配置Nacos
让Nacos统一设置Seata Server的配置,以免再去手动修改配置文件。
nacos的介绍和使用可以参考我之前的博客:传送门
1. 修改配置类型
- 打开配置文件
seata\conf\application.yml
,修改以下内容:
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file
更改为nacos的注册中心和配置中心:
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: train
group: SEATA_GROUP
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: train
cluster: default
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
!!! 注意每个:后面都要空一格
namespace: 设置nacos中的命名空间,用于项目间隔离
group:nacos中分组名称
username和password:nacos的用户名和密码,默认都为nacos
- 重启Seata服务
可见配置已生效,注册服务成功。登录Nacos客户端也可以看到配置的服务:
2. 创建Nacos配置
- 创建本地数据库seata:
在seata数据库中执行以下sql语句,创建四张表:
-- -------------------------------- 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);
- 新建配置如下:
store.mode=db
store.db.datasource=druid
store.db.db-type=mysql
store.db.driver-class-name=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
store.db.user=seata
store.db.password=seata
数据库、用户名和密码需要修改为自己的
这样就成功利用Nacos管理Seata的服务端配置了!
五. Seata Client配置Nacos
接下来还需要用Nacos管理Seata的客户端配置。
1. 增加Seata关联Nacos的配置
在Seata管理的模块的bootstrap.properties中添加Nacos配置:
# seata注册中心,要和seata server的application.yml配置保持一致
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.namespace=train
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
# seata配置中心,要和seata server的application.yml配置保持一致
seata.config.type=nacos
seata.config.nacos.server-addr=127.0.0.1:8848
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.namespace=train
seata.config.nacos.dataId=seataServer.properties
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
# 事务组名称,必须在nacos中有配置过:service.vgroupMapping.train-group=default
seata.tx-service-group=train-group
2. 在Nacos中对应的train-group添加配置
service.vgroupMapping.train-group=default
service.default.grouplist=127.0.0.1:8091
解释:
原来这两行是在SpringBoot中配置的,而现在配置了Seata+Nacos,等于是Nacos统一管理配置了,不用在本地一处处修改了,只要在线上统一管理即可。而在本地仅需要配置事务组名称即可,简化了配置。
3. 测试有效性
还是手动抛出异常,查看是否回滚:
成功回滚,则线上配置生效