Seata框架 分布式事务实战 Demo-1
以下是一个基于 Seata框架 的分布式事务实战 Demo,使用 AT模式(Automatic Transaction) 实现类似 3PC 的效果。AT 模式是 Seata 的核心模式,结合了 2PC 和乐观锁,通过自动记录 Undo Log 实现高效回滚。
环境准备
-
下载 Seata Server
访问 Seata官网 下载最新版(以 2.1.0 为例),解压后启动:# 启动 TC(Transaction Coordinator) sh seata-server.bat -p 8091 -h 127.0.0.1 # 启动 TM(Transaction Manager)和 RM(Resource Manager) sh seata-server.bat -p 8092 -h 127.0.0.1
-
配置数据库
创建 MySQL 数据库并初始化undo_log
表(Seata 回滚依赖):CREATE DATABASE seata_order DEFAULT CHARSET=utf8mb4; USE seata_order; CREATE TABLE `order` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` VARCHAR(50), `amount` DECIMAL(10,2), `status` ENUM('CREATED', 'COMMITTED', 'ROLLED_BACK') DEFAULT 'CREATED' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `inventory` ( `sku` VARCHAR(50) PRIMARY KEY, `stock` INT DEFAULT 100 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- undo_log 表(Seata 自动创建) CREATE TABLE `undo_log` ( `branch_transaction_id` BIGINT PRIMARY KEY, `xid` VARCHAR(100), `rollback_info` LONGBLOB, `log_table_name` VARCHAR(100), `log_table_column` VARCHAR(100), `log_key` VARCHAR(200), `timestamp` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
注册中心与服务发现
使用 Nacos 作为注册中心,启动 Nacos Server 并配置 Seata Server 的application.yml
:# seata-server/config/application.yml spring: application: name: seata-server datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order?useSSL=false&serverTimezone=UTC username: root password: root registry: type: nacos nacos: server-addr: 127.0.0.1:8848 config: center: type: nacos nacos: server-addr: 127.0.0.1:8848
Spring Boot 项目集成 Seata
1. 添加依赖
在 pom.xml
中添加 Seata 和 MySQL 驱动依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2. 配置数据源代理
在 application.yml
中配置 Seata 数据源代理:
spring:
datasource:
url: jdbc:mysql://localhost:3306/seata_order?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
max-active: 20
seata:
tx-service-group: my_test_tx_group
enable-auto-commit: false
service-component:
tx-manager:
service-name: seata-tm-service
register-center:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
tx-coordinator:
service-name: seata-tc-service
register-center:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
3. 启用全局事务注解
在启动类上添加 @EnableSeataTx
注解:
@SpringBootApplication
@EnableSeataTx
public class SeataOrderApplication {
public static void main(String[] args) {
SpringApplication.run(SeataOrderApplication.class, args);
}
}
业务代码实现
1. 订单服务(OrderService)
@Service
public class OrderService {
@Autowired
private OrderDAO orderDAO;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@GlobalTransactional(timeout=30000, name="createOrder", rollbackFor=Exception.class)
public void createOrder(Order order) {
// 1. 扣减库存
inventoryService.deduct(order.getSkuId());
// 2. 扣款(支付服务调用)
paymentService.charge(order.getUserId(), order.getAmount());
// 3. 生成订单
orderDAO.insert(order);
}
}
2. 库存服务(InventoryService)
@Service
public class InventoryService {
@Autowired
private InventoryDAO inventoryDAO;
@Transactional
public void deduct(String sku) {
Inventory inventory = inventoryDAO.findBySku(sku);
if (inventory.getStock() <= 0) {
throw new RuntimeException("库存不足,SKU: " + sku);
}
inventory.setStock(inventory.getStock() - 1);
inventoryDAO.update(inventory);
}
}
3. 支付服务(PaymentService)
@Service
public class PaymentService {
@Autowired
private PaymentDAO paymentDAO;
@Transactional
public void charge(String userId, BigDecimal amount) {
Payment payment = paymentDAO.findByUserId(userId);
if (payment == null) {
throw new RuntimeException("用户未找到,ID: " + userId);
}
payment.setBalance(payment.getBalance().subtract(amount));
paymentDAO.update(payment);
}
}
4. 数据库操作类
OrderDAO
@Repository
public class OrderDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert(Order order) {
String sql = "INSERT INTO `order` (user_id, amount, status) VALUES (?, ?, 'CREATED')";
jdbcTemplate.update(sql, order.getUserId(), order.getAmount());
}
}
InventoryDAO
@Repository
public class InventoryDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
public Inventory findBySku(String sku) {
String sql = "SELECT * FROM `inventory` WHERE sku = ?";
return jdbcTemplate.queryForObject(sql, Inventory.class, sku);
}
public void update(Inventory inventory) {
String sql = "UPDATE `inventory` SET stock = ? WHERE sku = ?";
jdbcTemplate.update(sql, inventory.getStock(), inventory.getSku());
}
}
5. 测试与验证
1. 触发全局事务
创建 OrderController
提供测试接口:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public String createOrder(@RequestBody Order order) {
try {
orderService.createOrder(order);
return "订单创建成功!";
} catch (Exception e) {
return "订单创建失败:" + e.getMessage();
}
}
}
2. 模拟失败场景
在 PaymentService
中故意抛出异常:
public void charge(String userId, BigDecimal amount) {
// 模拟支付失败
if (Math.random() > 0.5) {
throw new RuntimeException("支付失败!");
}
// 正常逻辑...
}
3. 验证回滚
• 成功场景:所有服务正常,订单提交成功。
• 失败场景:支付失败,触发全局回滚:
• 库存恢复(inventory
表的 stock
加回)。
• 订单状态保持为 CREATED
。
• 支付记录未提交(需手动回滚,由 Seata 自动处理)。
关键原理与优势
1. Seata AT模式的核心流程
• 阶段一(Branch Registration):
• TM(订单服务)启动全局事务,向 TC 注册分支事务。
• 阶段二(Local Transaction):
• 各个 RM(库存、支付、订单服务)执行本地事务,自动记录 Undo Log。
• 阶段三(Commit/Rollback):
• TC 收集所有分支事务状态,决定提交或回滚:
◦ 提交:各 RM 提交本地事务。
◦ 回滚:通过 Undo Log 恢复数据。
2. 优势对比传统2PC
• 无代码侵入:通过注解 @GlobalTransactional
简化开发。
• 高性能:基于乐观锁和异步通信,减少同步阻塞。
• 自动补偿:无需手动编写补偿逻辑(如 TCC 的 Cancel
方法)。
常见问题与解决
1. 事务未提交/回滚
• 检查点:
• 确保 @GlobalTransactional
注解正确标注在 TM 方法上。
• 确认 Seata Server 和 Nacos 服务正常运行。
2. 数据不一致
• 解决方案:
• 检查 undo_log
表是否正常记录回滚信息。
• 验证本地事务的 @Transactional
是否生效。
3. 超时问题
• 调整配置:
• 在 application.yml
中设置 global.tx.timeout=30000
(默认 30 秒)。
• 优化业务逻辑,减少事务执行时间。
总结
通过 Seata 的 AT 模式,我们无需手动实现复杂的 2PC 或 3PC 协议,即可轻松获得分布式事务的强一致性保障。其核心优势在于:
• 开发成本低:注解驱动,减少模板代码。
• 高性能:异步回滚机制,降低资源阻塞。
• 高可用:集群化部署,避免单点故障。
适用场景:
• 电商订单、金融转账等需要强一致性的场景。
• 微服务架构中需跨服务事务的场景。
进阶方向:
• 结合 Saga 模式处理长事务。
• 通过 Seata 的 @Compensable
注解实现自定义补偿逻辑。