在分布式系统中,服务之间往往涉及多个数据库操作,这就需要一个可靠的分布式事务解决方案来确保数据的一致性。Seata(Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,提供了高性能和易用的分布式事务服务。
本文将详细介绍如何在 Spring Boot 项目中集成 Seata,并通过具体的代码案例实现分布式事务管理。以下内容包括配置、代码实现以及一些注意事项,帮助你快速上手。
整体步骤
1、准备工作:
- 搭建 Seata Server 环境
- 创建两个模拟服务 order-service 和 account-service,分别代表订单和账户的服务
- 准备一个 Eureka/Nacos 注册中心(推荐 Nacos)
2、配置 Seata:
- 在项目中引入 Seata 依赖
- 配置 Seata 客户端
- 配置数据源代理
3、代码实现:
- 使用 @GlobalTransactional 注解实现全局事务管理
- 编写业务逻辑和分布式事务示例
环境搭建
1、下载并启动 Seata Server
从 Seata 官方仓库下载 Seata Server:https://github.com/seata/seata/releases。解压后,修改 conf/registry.conf 文件,将注册中心改为 Nacos:
registry {
type = "nacos"
nacos {
serverAddr = "localhost:8848" # Nacos 服务地址
namespace = "" # Nacos 命名空间
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
group = "SEATA_GROUP"
}
}
启动 Seata Server:
sh bin/seata-server.sh
依赖引入
在两个 Spring Boot 项目中添加以下依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.4.0</version>
</dependency>
配置文件详解
以下是 application.yml 配置文件示例(order-service 和 account-service 的配置基本相同,区别仅在服务名上)。
order-service 配置
server:
port: 8081
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
seata:
enabled: true
tx-service-group: my_tx_group # 事务组名,与 Seata Server 配置一致
account-service 配置
server:
port: 8082
spring:
application:
name: account-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/account_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
seata:
enabled: true
tx-service-group: my_tx_group # 事务组名
数据库表设计
订单表 order_tbl
CREATE TABLE order_tbl (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
product_id VARCHAR(64) NOT NULL,
count INT NOT NULL,
money DECIMAL(10,2) NOT NULL,
status INT DEFAULT 0 COMMENT '0:待支付, 1:已支付'
);
账户表 account_tbl
CREATE TABLE account_tbl (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
total DECIMAL(10,2) NOT NULL,
used DECIMAL(10,2) NOT NULL,
residue DECIMAL(10,2) NOT NULL
);
核心代码实现
1、全局事务注解
Seata 提供了 @GlobalTransactional 注解,用于在分布式事务中管理多个服务调用。
2、OrderService 示例
OrderService 中模拟订单创建逻辑,同时调用 AccountService 扣减账户余额。
代码实现
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountClient accountClient; // Feign 客户端调用 AccountService
@GlobalTransactional(name = "create-order-transaction", rollbackFor = Exception.class)
public void createOrder(String userId, String productId, int count, BigDecimal money) {
log.info("-----> 开始新建订单");
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setMoney(money);
order.setStatus(0);
orderMapper.insert(order);
log.info("-----> 订单服务调用账户,开始扣减余额");
accountClient.decreaseAccount(userId, money);
log.info("-----> 订单服务调用账户,扣减余额完成");
log.info("-----> 修改订单状态为已完成");
order.setStatus(1);
orderMapper.updateById(order);
log.info("-----> 订单处理结束");
}
}
Mapper 层
@Mapper
public interface OrderMapper extends BaseMapper<Order> {}
3、AccountService 示例
AccountService 中实现账户余额的扣减逻辑。
代码实现
@Service
@Slf4j
public class AccountService {
@Autowired
private AccountMapper accountMapper;
public void decreaseAccount(String userId, BigDecimal money) {
log.info("-----> 扣减账户余额开始");
accountMapper.decrease(userId, money);
log.info("-----> 扣减账户余额结束");
}
}
Mapper 层
@Mapper
public interface AccountMapper {
@Update("UPDATE account_tbl SET residue = residue - #{money}, used = used + #{money} WHERE user_id = #{userId}")
void decrease(@Param("userId") String userId, @Param("money") BigDecimal money);
}
4、Feign Client 示例
@FeignClient(name = "account-service")
public interface AccountClient {
@PostMapping("/account/decrease")
void decreaseAccount(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}
测试分布式事务
启动服务后,通过 Postman 或其他工具发送请求:
curl -X POST http://localhost:8081/order/create \
-H "Content-Type: application/json" \
-d '{
"userId": "1",
"productId": "1",
"count": 10,
"money": 100.00
}'
重要注意事项
1、Seata Undo Log 表: 在每个数据库中创建 undo_log 表,供 Seata 记录回滚日志。
CREATE TABLE undo_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
UNIQUE KEY ux_undo_log (xid, branch_id)
);
2、Seata 配置中心: 确保 Seata Server 的配置与服务端一致,事务组名必须统一。
总结
通过以上配置和代码示例,我们实现了基于 Seata 的分布式事务管理。在实际项目中,可以根据业务需求灵活调整服务和表设计,确保数据一致性。
如果需要更深入的优化或其他集成方法,可以进一步研究 Seata 的 AT、TCC 模式以及高可用配置。