文章目录
一、Seata是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
1.相关事务概念
-
全局事务:全局事务指的是一次性操作多个资源管理器完成的事务,由一组分支事务(本地事务)组成。
-
分支事务(本地事务):本地事务由本地资源管理器(通常指数据库管理系统 DBMS,例如 MySQL、Oracle 等)管理,严格地支持 ACID 特性,高效可靠。本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器,即本地事务只能对自己数据库的操作进行控制,对于其他数据库的操作则无能为力。
工作流程
Seata 对分布式事务的协调和控制,主要是通过 XID 和 3 个核心组件实现的。
XID
- XID 是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。(很重要,用于判断是否是一个事务)
核心组件
- Seata的核心组件可分为Seata服务端和Seata客户端两类
Seata 定义了 3 个核心组件:
-
TC(Transaction Coordinator):事务协调器,直接调度事务参与者RM。负责将RM的反馈结果响应给TM,并听从TM的最终决议,将具体决议(提交或回滚)发送给RM执行。相当于中间人,主要负责维护全局事务和分支事务的状态。
-
TM(Transaction Manager):事务管理器,它是事务的发起者(具体的微服务)。根据RM第一阶段的执行结果,进行决议。并将决议反馈给TC。相当于发号施令的
-
RM(Resource Manager):资源管理器,其实就是事务的参与者。获取TC的执行命令具去执行分支事务的第一阶段以及第二阶段,并将执行结果反馈给TC,相当于具体做事的
以上三个组件相互协作,TC 以 Seata 服务器(Server)形式独立部署,TM 和 RM 则是以 Seata Client 的形式集成在微服务中运行。
Seata 的整体工作流程如下:
- TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID(此时,由TM发起的全局事务已经开启)
- XID 通过服务的调用链传递到其他服务
- RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖(事务参与者执行本地事务,此时分支事务已经执行完成,并反馈给TC执行结果。可以理解为AT模式下的第一个阶段)
- TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议(事务协调者根据事务管理者的决议,发送提交或回滚的调度命令,可以理解为AT模式下的第二阶段)
- TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作
二、seata安装和部署
0.前提环境
注:本文使用Mysql为例
需要使用Nacos 教程:https://gaohuanjie.blog.csdn.net/article/details/135294828
需要git Bash窗口
官方下载地址: https://www.git-scm.com/download/
git bash下载安装就行,如果不会提供一个教程 https://blog.csdn.net/qq_33204709/article/details/133963278
1.下载seata
官网下载地址:https://seata.io/zh-cn/blog/download.html
下载完成后,将文件解压到任意地方 (不要忘记解压的地方)
接下来的大部分操作都在下图的根目录展开
2.创建seata数据库
新建数据库seata,然后在解压的seata文件找到script->server->db->mysql.sql,执行这个sql脚本。
3.修改config.txt文件
config.txt
文件在seata/script/config-center
目录下,需要修改的地方如下:
4.seata服务注册到Nacos
1)创建命名空间
首先在nacos控制台添加新的命名空间
新建命名空间
空间ID可以不填,但是自动生成之后需要记住,接下来配置需要很重要
命名空间和描述可以任意但是最好写seata见名知意
1)将config.txt信息注册到Nacos
进入seata目录,找到nacos-config.sh,路径为:script->config-center->nacos->nacos-config.sh
进入git bash窗口,执行以下命令:
sh nacos-config.sh -h nacos服务地址 -p 8848 -g SEATA_GROUP -t 刚刚注册时候的命名空间id -u nacos -w nacos
案例
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t cc7b3c50-d6e9-4b86-934a-1cd7e5a6a0e2 -u nacos -w nacos
参数详解:
- -h nacos服务IP
- -p nacos服务端口
- -u nacos登录名
- -w nacos登录密码
- -g nacos 配置的分组名称,默认设置SEATA_GROUP 修改了后续的文件的group设置也要改
- -t 上一步配置的命名空间ID
注意:-u 和 -w 后面是Nacos的登录账户和密码,最好不要修改,除非你能记住,修改后下面配置文件的nacos账号密码就要修改为对应的
执行完上述命令之后 配置数应该为103,可能不一样和(图为107是因为后续需要根据你的微服务数量添加对应的配置,后续说明)
5.修改seata配置文件
进入seata/conf目录下,有两个配置文件,把application.yml 随意修改一个名字,然后把 application.example.yml修改成application.yml 作为主要配置文件。
修改application.yml文件,这里使用的nacos作为注册中心,所以需要修改的地方有:
第一步将原来application.yml 文件中的console和security拷贝走,复制到主要配置文件(最初的application.example.yml)文件中
console放在这个位置
security放在最下面,注意缩进格式
然后开始修改主要配置文件(最初的application.example.yml)中的设置
首先修改seata
修改seata下的registry
修改seata下的store
至此文件就修改完成了
6.启动服务
找到seata-server.bat,点击启动,路径为:seata->bin->seata-server.bat,成功后可以在nacos控制台服务列表中看到多了一个服务。
如果闪退了说明有错误,需要在该目录下打开cmd控制台输入seata-server.bat 启动,错误是什么
常见问题
- java不是命令,java环境的问题,配置环境即可解决
- javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate),数据库的url链接没有加useSSL=false或者数据库是8.0的但是驱动没有加cj
- xxx.security 该问题是复制的security缩进不对,没有被识别
部署成功后就会有seata-server的服务名的服务(其他服务是分布式服务,后续说明)
三、整合Nacos+Fegin+springCloud项目
注意所有的服务模块都需要该操作,在此只演示一个,但是所以服务都需要这些操作
1.添加seata依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2.设置application.properties添加
1)创建Nacos配置
第一步 在该页面创建配置
信息设置为这样,Data ID格式为service.vgroupMapping.XXX XXX可以任意起名,但是之后需要和properties中seata.tx-service-group和seata.service.vgroup-mapping.XXX 保持一致
Group改为SEATA_GROUP (Group是上文中设置为SEATA_GROUP的)
配置内容设置为Default
2)修改服务模块中的properties
seata.enabled=true
# 服务id,不重复即可
seata.application-id=wallet
seata.enable-auto-data-source-proxy=false
# group名,需要跟Nacos添加的配置保持一致
seata.tx-service-group=service-wallet-group
# seata.service.vgroup-mapping.XXX XXX需要跟上面保持一致
seata.service.vgroup-mapping.service-wallet-group=default
seata.service.disable-global-transaction=false
# seata.registry的信息,跟application.yml的registry信息保持一致
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=cc7b3c50-d6e9-4b86-934a-1cd7e5a6a0e2
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
seata.registry.nacos.cluster=default
# seata.config的信息,跟application.yml的config信息保持一致
seata.config.type=nacos
seata.config.nacos.server-addr=127.0.0.1:8848
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.namespace=cc7b3c50-d6e9-4b86-934a-1cd7e5a6a0e2
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
seata.client.rm.report-success-enable=true
seata.client.rm.report-retry-count=5
3.在需要事务的方法上添加注解
该步不需要每个都配置,只需要在主要的方法上添加开启事务即可
// name任意,不重复即可 rollbackFor最好设置为Exception.class,不然有些异常不回滚
@GlobalTransactional(name = "order",rollbackFor = Exception.class)
// 案例
/**
*
* @param userId 用户id 目前只有1
* @param productId 商品id 目前有1-4
* @param quantity 购买数量
* @param addressId 地址信息id
* @return
*/
@GetMapping("/order/add.do")
@GlobalTransactional(name = "order",rollbackFor = Exception.class)
public Map<String,Object> add(Long userId, Long productId, Integer quantity, Long addressId) {
System.out.println("事务id是"+ RootContext.getXID());
Order order = new Order();
order.setUserId(userId);
//通过productId获取商品信息
Product product = IProductService.get(productId);
order.setProductId(product.getId());
order.setProductName(product.getName());//商品名称在后期可能发生变化,这里记录的是下单时商品名称
order.setProductPrice(product.getPrice());//商品名称在后期可能发生变化,这里记录的是下单时商品名称
//通过addressId获取地址信息
Address address = IAddressService.get(addressId);
order.setRealName(address.getRealName());//收件人姓名在后期可能发生变化,这里记录的是下单时用户选中的邮寄地址对应的收件人姓名
order.setMobile(address.getMobile());//收件人手机号在后期可能发生变化,这里记录的是下单时用户选中的邮寄地址对应的收件人手机号
order.setAddressDetail(address.getDetail());//收件人详细地址在后期可能发生变化,这里记录的是下单时用户选中的邮寄地址对应的收件人详细地址
order.setQuantity(quantity);
double amount = product.getPrice() * quantity;//计算商品总额
Map<String,Object> map = new HashMap<>();
if(IProductService.update(addressId,quantity) //修改商品库存
&& IWalletService.getWallet(userId,amount) //修改钱包余额
&& orderService.save(order)){//添加订单信息
map.put("state","200");
map.put("message","订单创建成功!");
}else{
map.put("state","100");
map.put("message","订单创建失败!");
}
return map;
4.在启动类上添加启动注解
添加@EnableAutoDataSourceProxy
// 案例
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
@EnableAutoDataSourceProxy
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
至此就已经将seata事务管理添加进入项目了
5.结果展示
添加@GlobalTransactional注解日志打印,显示已经回滚
正常执行的子服务
异常的子服务
6.常见问题
xxx.undo_log不存在
java.sql.SQLSyntaxErrorException: Table ‘mall.undo_log’ doesn’t exist
在服务操作的目标数据库执行下SQL添加表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(0) NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(0) NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
事务不回滚
在添加@GlobalTransactional注解的服务中,日志显示status:RollBacked但是在其他服务模块中日志打印的是Commited
该问题一般是因为XID不一致导致的,一般不会出现该问题本文没有遇到该问题
提供一个方法来判断是否是XID不一致或者子服务中XID为null
// RootContext.getXID()就是获取事务的XID 在子服务和总服务下输出一下就能知道是否一致
System.out.println("事务id是"+ RootContext.getXID());
第一种情况
如果使用的不是本文的pring-cloud-starter-alibaba-seata这个依赖,而是使用的这以下依赖有可能出现该问题
<!--seata1.7.0-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2022.0.0.0-RC2</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
第二种情况
seata 分布式事务没有传递xid导致事务失效
本文没有出现这种情况,是第一种情况所以在此提供的一种解决方案
https://blog.csdn.net/qq_45278455/article/details/124364505
参考
安装和部署参考了该文章,并在此基础上改进