目录
前言
在微服务架构中,分布式事务是确保多个服务之间数据一致性和完整性的关键。随着微服务的拆分,传统的单体事务无法满足跨服务的事务管理需求,这时分布式事务变得尤为重要。Seata作为一款轻量级、高性能的分布式事务解决方案,通过提供全局事务管理、自动回滚等功能,帮助开发者轻松处理分布式系统中的事务问题,避免了数据不一致和服务失败的风险。
准备
- jdk17+
- maven3.9.4+
- idea2023
- spring cloud: 2023.0.1.0
- spring cloud alibaba: 2023.0.1
源码获取:GitHub - RemainderTime/spring-cloud-alibaba-base-demo: 基于spring cloud alibaba生态快速构建微服务脚手架
安装seata
分布式事务seata是一个单独的服务,通过这个服务来控制管理业务中的事物,所以我们首先的安装配置该seata服务
下载seata
github下载地址:https://github.com/apache/incubator-seata/releases
百度云地址:百度网盘 请输入提取码 提取码: 92h6
本篇seata版本为1.8,尽量保持一致。
seata下载到本地后完整目录如下
配置seata数据库
进入本地seata项目的db文件夹中
在mysql数据库中创建一个seata数据库
这里博主使用的是mysql数据库,所以选择mysql.sql执行,创建了4个表
global_table:
- 记录全局事务的状态信息。
- 主要用于保存全局事务的唯一标识(XID)和当前执行状态
branch_table:
- 记录分支事务的信息。
- 用于关联全局事务中的各个参与分支事务,追踪分支的执行状态。
lock_table:
- 保存全局锁信息。
- 用于防止并发操作对数据造成冲突,实现事务数据的锁定。
distributed_lock:
- 分布式锁表。
- 用于在 Seata 中协调全局的分布式锁,防止资源竞争。
创建undo_log表
在业务数据库中创建undo_log表
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE,
INDEX `ix_log_created`(`log_created`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Dynamic;
Seata 使用 AT 模式(自动提交模式)时,undo_log 表会存储业务操作前的镜像数据,用于在事务失败时回滚数据到操作前的状态。Seata 在事务完成后(无论是提交还是回滚),会自动清理 undo_log 数据,确保表的干净与高效运行。
详细了解seata模式:Seata AT 模式 | Apache Seata
seata配置文件
进入本地的seata项目的config文件中
对application.yml文件内容进行修改,下面展示主要的核心配置修改
seata:
service:
vgroup-mapping:
dubbo_tx_group: default # 映射 dubbo_tx_group 到 default 服务组
tx-service-group: dubbo_tx_group # 事务服务组名称
config:
# support: nacos, consul, apollo, zk, etcd3
type: file # 配置文件类型,可以选择 nacos, apollo 等
file:
name: file.conf # 使用 application.yml 作为配置文件
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos # 注册中心类型(根据实际情况选择,比如 nacos、eureka、zk 等)
nacos:
server-addr: 127.0.0.1:8848 # Nacos 注册中心地址(示例)
namespace: "" # Nacos 的命名空间,默认空即可
group: "SEATA_GROUP" # Seata 在 Nacos 中的分组名
username: "nacos" # 如果 Nacos 需要登录验证,请填入用户名
password:
store:
# support: file 、 db 、 redis 、 raft
mode: db # 使用数据库存储模式(也可以选择 file 或 redis)
db:
datasource: druid # 数据源类型,可以使用 druid、hikari 等
db-type: mysql # 数据库类型(mysql、oracle、postgresql、mariadb 等)
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: root
password:
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
query-limit: 100
max-wait: 5000
主要配置说明:
tx-service-group:事务组名称设置,名称自定义。
config: 配置中心,可设置适配多个配置中心组件。本篇没有使用配置中心组件,选择的是本地文件所以设置的是file。
registry:注册中心配置,seata作为一个服务提供者,也需要将服务注册到注册中心管理。这里选择nacos作为注册中心,在自己的nacos服务中创建命名空间,并在文件中进行替换配置。
store: 数据存储中心,用于存储服务中的全局事务相关数据。这里选择使用的mysql作为存储中心,替换自己的数据库配置即可。
启动seata服务
进入本地seata项目中bin文件夹中
window用户双击seata-server.bat启动
看到控制台打印上面的地址就表示启动成功了
8091 : 表示seata服务的端口
7091:表示seata控制台的端口
访问http://127.0.0.1:7091
默认账号密码:seata
查看seata服务在nacos注册中心也注册成功
项目集成
根据前面4篇的博文,目前我们已经创建了三个微服务
引入seata依赖
在消费者以及生产者服务都需要引入seata依赖
<!--分布式事务组件 seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
yml文件配置seata
消费者以及生产者服务的yml文件中都需要配置
seata:
server:
address: 127.0.0.1:8091
tm:
default-transaction-manager-type: AT # 默认使用 AT 模式
default-timeout: 30000 # 设置全局事务超时
application-id: ${spring.application.name} # 这里是应用的唯一标识,可以根据你的服务名称设置
tx-service-group: dubbo_tx_group # 事务分组名称,确保每个 seata 服务的 tx-service-group 一致
registry:
type: nacos # 注册中心类型,如 nacos,若使用其他注册中心需要调整
nacos:
server-addr: 127.0.0.1:8848 # Nacos 地址和端口
namespace:
group: SEATA_GROUP
username: nacos # 如果 Nacos 需要认证
password:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848 # Nacos 地址和端口
namespace: ""
group: SEATA_GROUP
username: "nacos" # 如果 Nacos 需要认证
password: ""
data-id: "seata-server.properties"
service:
grouplist:
default: ${seata.address:127.0.0.1}:8091
enable-degrade: false
disable-global-transaction: false
enabled: true
nacos地址自行替换为自己的服务地址和命名空间
tx-service-group: 该属性值必须和之前在seata服务配置文件中的值一致。
config: 该属性下的 data-id 为在nacos中维护的seata配置"seata-server.properties",需要自行创建。
注意!!!:该文件后缀必须是properties不能是yml,官方要求。
# 事务组名称
service.vgroupMapping.dubbo_tx_group=default
service.enableDegrade=false
service.disableGlobalTransaction=false
# 事务日志保留时间
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
注意!!!:service.vgroupMapping.dubbo_tx_group 这段一定要写对,不然项目会一直报错提示找不到该配置。其中后面的dubbo_tx_group 必须是自己前面配置 seata文件中tx-service-group 属性的值
模拟下单
数据库相关集成于配置本篇不在概述,没有自己项目的可以使用本文开头提供的项目地址进行下载查看使用
生产者提供扣减库存
@RestController
public class DemoController {
@Autowired
private SeataProductMapper seataProductMapper;
@GetMapping(value = "/test/seata/deInventory")
void deInventorySeata(@RequestParam("num") Integer num, @RequestParam("productId") Integer productId) {
seataProductMapper.deInventory(num, productId);
}
}
@Mapper
public interface SeataProductMapper {
@Update("UPDATE xf_product SET num = num - #{num} WHERE id = #{productId}")
void deInventory(@Param("num") Integer num, @Param("productId") Integer productId);
}
消费者进行下单
模拟创建订单
@Mapper
public interface SeataOrderMapper {
@Insert("INSERT INTO xf_order (user_id, product_id) values (#{userId}, #{productId})")
void createOrder(@Param("userId") Integer userId, @Param("productId") Integer productId);
}
使用@GlobalTransactional 实现分布式事务
@Service
public class SeataService {
@Autowired
private FeignService feignService;
@Autowired
private SeataOrderMapper seataOrderMapper;
@GlobalTransactional(name = "seata-order", rollbackFor = Exception.class)
public String placeOrderSeata() {
// 调用扣减库存的方法
feignService.deInventorySeata(1, 1);
// 调用创建订单的方法
seataOrderMapper.createOrder(1, 1);
// throw new RuntimeException("测试回滚");
return "下单成功";
}
定义下单接口
@RestController
public class FeignController {
@Autowired
private SeataService seataService;
/**
* 使用分布式事务 seata 远程调用Feign
* @return
*/
@GetMapping(value = "/seataOrFeignOrSentinel")
public String seataOrder() {
return seataService.placeOrderSeata();
}
}
模拟下单
启动全部服务并请求下单接口
在方法最后debug
查看业务数据库undo_log中的数据,存在2条操作记录
查看seata数据库的全局事务表,存在一条全局事务数据
查看seata控制台,统一正常展示了事务数据
放开dedug,重新查看相关表数据,会发现数据已经清空。
也可模拟异常情况,查看相关业务表数据是否回滚来验证。
注:本文使用的是seata的AT模式,业务相关表必须在一个库中,如果涉及到多个库操作分布式事务考虑使用其他模式实现。
至此spring cloud alibaba 微服务集成seata分布式事务完成了