某电商平台在 "会员积分兑换商品" 业务中出现严重数据不一致问题:用户积分成功扣减(积分服务),但商品库存未同步减少(库存服务),导致用户 "花了积分却没拿到商品",客服投诉量激增。事后排查发现,由于库存服务数据库临时宕机,积分扣减后库存更新失败,但积分服务已提交本地事务无法回滚,最终造成跨服务数据不一致。这正是微服务架构下分布式事务的典型痛点:本地事务独立提交 / 回滚、跨服务数据一致性难保障、故障后恢复机制缺失。本文将以 Seata、Saga 等框架为核心,结合实际业务场景,详解 "AT、TCC、Saga、TXC" 四种分布式事务模式的实现方案、适用场景与故障处理策略,帮助团队在性能与一致性之间找到最佳平衡。
一、分布式事务的核心痛点与一致性需求
1. 微服务架构下的事务挑战
在单体架构中,所有业务操作都在同一数据库内,可通过 ACID 事务(原子性、一致性、隔离性、持久性)保障数据一致性;但微服务拆分后,业务操作需跨多个服务(如 "下单" 涉及订单、库存、支付服务)和多个数据库,传统本地事务机制完全失效,主要痛点体现在:
- 事务边界跨服务:一个业务流程(如 "积分兑换商品")涉及积分服务(扣减积分)、库存服务(减少库存)、订单服务(创建兑换订单),每个服务有独立数据库,本地事务仅能控制自身数据,无法联动其他服务;
- 网络不可靠性:服务间调用依赖网络传输,可能出现 "调用超时""网络中断" 等问题(如积分服务调用库存服务时网络卡顿),导致部分服务执行成功、部分失败,数据不一致;
- 服务故障不可预测:服务或数据库可能突发宕机(如库存服务数据库崩溃),若此时已有部分服务完成本地事务提交(如积分已扣减),故障恢复后无法回滚已提交的操作,导致数据永久不一致;
- 性能与一致性冲突:若为追求强一致性而采用 "全链路锁定" 机制(如分布式锁),会导致服务并发能力急剧下降(如 QPS 从 1000 降至 100),无法满足高并发场景(如秒杀、大促)需求;
- 故障恢复复杂:当事务执行失败时(如部分服务操作失败),需手动排查每个服务的执行状态(如积分是否扣减、库存是否更新),再通过接口或脚本手动恢复数据,耗时且易出错(如漏恢复、重复恢复)。
支付退款业务的事务困境:
用户发起退款申请后,退款流程需调用支付服务(发起退款)、订单服务(更新订单状态为 "已退款")、财务服务(记录退款流水)。某次支付服务退款成功,但订单服务因 JVM 内存溢出未更新状态,导致订单显示 "未退款" 而用户已收到退款,财务流水也未记录,后续对账时发现大量账实不符。
2. 分布式事务的一致性需求分级
不同业务场景对数据一致性的要求不同,盲目追求强一致性会牺牲性能,需根据业务重要性分级选择:
- 强一致性(Linearizability):事务执行后,所有服务的数据立即保持一致,且所有后续访问都能看到事务执行的结果(如银行转账:A 账户扣款成功,B 账户必须同时到账);适用场景:金融转账、支付对账等核心资金场景;
- 最终一致性(Eventual Consistency):事务执行后,短期内可能存在数据不一致,但通过补偿机制(如重试、回滚),最终所有服务的数据会达到一致状态(如电商订单:下单后 10 秒内,库存服务与订单服务数据可能不同步,但 1 分钟后会一致);适用场景:商品库存扣减、积分兑换、物流状态更新等非实时资金场景;
- 弱一致性(Weak Consistency):事务执行后,数据一致性没有时间保证,甚至可能永远不一致(需人工干预);适用场景:非核心数据统计(如商品浏览量计数,偶尔少算 1-2 次不影响业务);
3. 主流分布式事务模式对比
不同分布式事务模式在一致性、性能、适用场景上差异显著,需根据业务需求选择:
|-------------------------------------|---------------------------------------------------------|-------------------|-------------|-----------------------------------|-------------------------|
| 事务模式 | 核心原理 | 一致性级别 | 性能表现 | 适用场景 | 典型框架 / 工具 |
| AT 模式(Automatic Transaction) | 基于两阶段提交(2PC),自动生成 undo/redo 日志,支持自动回滚与提交 | 最终一致性(接近强一致) | 中(有锁开销) | 无侵入式开发、业务逻辑简单(如单表增删改)、Java 微服务 | Seata |
| TCC 模式(Try-Confirm-Cancel) | 拆分为 Try(资源检查与预留)、Confirm(确认执行)、Cancel(取消释放)三阶段,手动实现业务逻辑 | 强一致性 / 最终一致性(可控制) | 高(无锁,纯业务逻辑) | 业务逻辑复杂(如多表操作)、高并发场景(如秒杀) | Seata、Hmily |
| Saga 模式 | 基于状态机,将长事务拆分为多个短本地事务,每个事务执行后记录状态,失败时调用补偿事务回滚 | 最终一致性 | 高(无锁,异步执行) | 长事务场景(如订单履约:下单→支付→发货→确认收货)、跨语言微服务 | Seata、Axon、Apache Camel |
| TXC 模式(Transaction Cross-chain) | 基于本地消息表,事务执行时先写消息表,再调用下游服务,失败时通过消息表重试 | 最终一致性 | 高(异步,无锁) | 非实时依赖场景(如退款通知、日志同步)、高可用需求场景 | RocketMQ 事务消息、本地消息表 |
| S2F 模式(Seata for Fescar) | 基于 AT 模式优化,支持多数据源、微服务跨库事务,简化配置 | 最终一致性 | 中高 | 多数据库、复杂服务依赖的 Java 微服务 | Seata 1.6+ |
二、Seata AT 模式实战:无侵入式分布式事务
Seata 是 Alibaba 开源的分布式事务框架,AT 模式是其核心模式,支持无侵入式开发(无需修改业务代码),通过 "两阶段提交 + undo/redo 日志" 实现最终一致性,适合大多数 Java 微服务场景。
1. AT 模式核心原理
Seata AT 模式将分布式事务拆分为 "全局事务" 和 "分支事务":
- 全局事务:整个分布式事务的入口(如 "下单" 事务),由事务协调器(TC)管理;
- 分支事务:每个服务的本地事务(如订单服务的 "创建订单"、库存服务的 "扣减库存"),由资源管理器(RM)管理;
- 核心流程(两阶段):
-
- 第一阶段(Prepare):
-
-
- 全局事务发起者(如订单服务)向 TC 申请全局事务 ID(XID);
-
-
-
- 订单服务执行本地事务(创建订单),RM 自动生成 undo 日志(记录事务前数据状态,用于回滚)和 redo 日志(记录事务后数据状态,用于提交),本地事务暂不提交;
-
-
-
- 订单服务调用库存服务,传递 XID,库存服务执行本地事务(扣减库存),同样生成 undo/redo 日志,暂不提交;
-
-
-
- 所有分支事务执行完成后,向 TC 汇报执行状态(成功 / 失败);
-
-
- 第二阶段(Commit/Rollback):
-
-
- 若所有分支事务均成功,TC 通知所有 RM 提交本地事务,RM 删除 undo 日志;
-
-
-
- 若任一分支事务失败,TC 通知所有 RM 回滚本地事务,RM 通过 undo 日志恢复数据到事务前状态;
-
2. 环境准备与部署
2.1 组件版本选择
- Seata Server:1.8.0(稳定版);
- 微服务框架:Spring Cloud Alibaba 2022.0.0.0;
- 数据库:MySQL 8.0(支持 InnoDB 引擎,需开启事务日志);
- 注册中心:Nacos 2.2.3(Seata Server 与微服务需注册到同一 Nacos);
2.2 部署 Seata Server(事务协调器 TC)
- 下载 Seata Server :从 Seata 官网(https://seata.io/zh-cn/blog/download.html)下载 1.8.0 版本压缩包,解压后进入conf目录;
- 配置 Seata Server:
-
- 修改application.yml,配置 Nacos 注册中心、数据库存储(用于存储全局事务状态):
server:
port: 7091 # Seata Server端口
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
seata:
config:
type: nacos # 配置中心类型
nacos:
server-addr: 192.168.1.100:8848 # Nacos地址
group: SEATA_GROUP # 配置分组
namespace: # Nacos命名空间(可选)
username: nacos
password: nacos
registry:
type: nacos # 注册中心类型
nacos:
application: seata-server
server-addr: 192.168.1.100:8848
group: SEATA_GROUP
namespace:
username: nacos
password: nacos
store:
mode: db # 存储模式(db/file/redis,推荐db)
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.101:3306/seata?useUnicode=true&rewriteBatchedStatements=true
user: root
password: 123456
min-conn: 5
max-conn: 100
global-table: global_table # 全局事务表
branch-table: branch_table # 分支事务表
lock-table: lock_table # 全局锁表
distributed-lock-table: distributed_lock
query-limit: 100
- 初始化 Seata 数据库:在 MySQL 中创建seata数据库,执行conf/db/schema-mysql.sql脚本,创建global_table、branch_table、lock_table等核心表;
- 启动 Seata Server :执行bin/seata-server.sh(Linux)或bin/seata-server.bat(Windows),启动后在 Nacos 控制台可看到seata-server服务已注册;
2.3 微服务集成 Seata AT 模式
以 "订单服务" 和 "库存服务" 为例,集成 Seata 实现 "下单扣库存" 的分布式事务:
2.3.1 数据库准备(资源管理器 RM)
在订单服务数据库(order_db)和库存服务数据库(inventory_db)中,分别创建 Seata 所需的undo_log表(用于存储 undo 日志),执行以下 SQL:
CREATE TABLE `undo_log` (
```id` bigint NOT NULL AUTO_INCREMENT,``
```branch_id` bigint NOT NULL COMMENT '分支事务ID',``
```xid` varchar(100) NOT NULL COMMENT '全局事务ID',``
```context` varchar(128) NOT NULL COMMENT '上下文信息',``
```rollback_info` longblob NOT NULL COMMENT '回滚信息',``
```log_status` int NOT NULL COMMENT '日志状态:0-未提交,1-已提交,2-已回滚',``
```log_created` datetime NOT NULL COMMENT '创建时间',``
```log_modified` datetime NOT NULL COMMENT '修改时间',``
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Seata undo日志表';
2.3.2 微服务依赖配置(Spring Cloud Alibaba)
在订单服务和库存服务的pom.xml中添加 Seata 依赖:
Seata Spring Cloud Starter -->
.cloud arter-alibaba-seata</artifactId>
>2022.0.0.0</version>
clusions>
排除旧版本Seata,引入指定版本 -->
</groupId>
<artifactId>seata-spring-boot-starter
</dependency>
<dependency>
>io.seata>
seata-spring-boot-starter .0</dependency>
2.3.3 微服务配置文件
在application.yml中配置 Seata 相关参数(订单服务与库存服务配置类似):
spring:
application:
name: order-service # 服务名(需与Seata配置中的service.vgroup-mapping对应)
cloud:
alibaba:
seata:
tx-service-group: order-service-group # 事务组名
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.101:3306/order_db?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
# Seata配置
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: ${spring.cloud.alibaba.seata.tx-service-group}
service:
vgroup-mapping:
order-service-group: default # 事务组与Seata Server集群映射(default为Seata Server默认集群名)
grouplist:
default: 192.168.1.102:8091 # Seata Server地址(注意:8091为Seata Server的RPC端口,非7091)
registry:
type: nacos
nacos:
server-addr: 192.168.1.100:8848
group: SEATA_GROUP
namespace:
username: nacos
password: nacos
2.3.4 业务代码实现(无侵入式)
Seata AT 模式无需修改业务代码,仅需在全局事务发起者(订单服务)的方法上添加@GlobalTransactional注解,标记为全局事务入口:
- 订单服务(全局事务发起者):
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper; // 订单DAO
@Autowired
private InventoryFeignClient inventoryFeignClient; // 库存服务Feign客户端(调用库存扣减接口)
/**
* 创建订单(全局事务入口)
* @GlobalTransactional:标记为全局事务,rollbackFor:指定异常时回滚
*/
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public String createOrder(OrderDTO orderDTO) {
// 1. 本地事务:创建订单
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setQuantity(orderDTO.getQuantity());
order.setStatus(0); // 0-待支付
orderMapper.insert(order);
System.out.println("订单创建成功,订单号:" + order.getOrderNo());
// 2. 远程调用:库存服务扣减库存
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setReduceQuantity(orderDTO.getQuantity());
String inventoryResult = inventoryFeignClient.reduceInventory(inventoryDTO);
if (!"success".equals(inventoryResult)) {
// 若库存扣减失败,抛出异常,触发全局回滚
throw new RuntimeException("库存扣减失败,订单创建回滚");
}
return "订单创建成功,订单号:" + order.getOrderNo();
}
}
- 库存服务(分支事务):
库存服务无需添加 Seata 注解,仅需实现库存扣减的本地事务逻辑:
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private InventoryMapper inventoryMapper; // 库存DAO
/**
* 扣减库存(本地事务,Seata自动管理分支事务)
*/
@Override
public String reduceInventory(InventoryDTO inventoryDTO) {
// 1. 检查库存是否充足
Inventory inventory = inventoryMapper.selectByProductId(inventoryDTO.getProductId());
if (inventory == null || inventory.getStock() < inventoryDTO.getReduceQuantity()) {
return "fail:库存不足";
}
// 2. 扣减库存(本地事务)
int rows = inventoryMapper.reduceStock(
inventoryDTO.getProductId(),
inventoryDTO.getReduceQuantity()
);
if (rows > 0) {
System.out.println("库存扣减成功,商品ID:" + inventoryDTO.getProductId() + ",扣减数量:" + inventoryDTO.getReduceQuantity());
return "success";
} else {
return "fail:库存扣减失败";
}
}
}
3. 功能验证与故障测试
3.1 正常场景验证(所有分支事务成功)
- 调用订单服务的createOrder接口,传入参数(userId=123,productId=456,quantity=2);
- 观察日志:订单服务创建订单成功,库存服务扣减库存成功;
- 检查数据库:
-
- order_db.order表新增订单记录;
-
- inventory_db.inventory表中商品 ID=456 的库存减少 2;
-
- 两个数据库的undo_log表无残留记录(已提交,自动删除);
-
- Seata 数据库的global_table中全局事务状态为 "已提交"(status=1);
3.2 异常场景验证(分支事务失败,触发回滚)
- 模拟库存服务异常:在库存服务的reduceInventory方法中手动抛出异常(如throw new RuntimeException("库存服务临时故障"));
- 调用订单服务的createOrder接口;
- 观察日志:订单服务创建订单成功,但调用库存服务时抛出异常,触发@GlobalTransactional回滚;
- 检查数据库:
-
- order_db.order表无新增订单记录(订单服务本地事务已回滚);
-
- inventory_db.inventory表库存无变化(库存服务本地事务未提交,或已回滚);
-
- 两个数据库的undo_log表记录已删除(回滚完成后清理);
-
- Seata 数据库的global_table中全局事务状态为 "已回滚"(status=2);
3.3 故障恢复验证(Seata Server 宕机后恢复)
- 启动订单服务和库存服务,调用createOrder接口,在库存服务执行过程中强制关闭 Seata Server;
- 观察日志:订单服务和库存服务的分支事务处于 "未提交" 状态;
- 重启 Seata Server,Seata 会自动读取global_table和branch_table中的未完成事务,重新发起第二阶段(Commit/Rollback);
- 检查结果:若宕机前所有分支事务已成功,重启后 Seata 会通知所有 RM 提交事务;若存在失败分支,会通知回滚,确保数据一致性;
三、TCC 模式实战:高并发场景下的强一致性保障
Seata AT 模式虽无侵入,但依赖数据库 undo 日志和全局锁,在高并发场景(如秒杀)下会因锁竞争导致性能下降。TCC 模式通过 "Try-Confirm-Cancel" 三阶段手动拆分业务逻辑,无锁竞争,性能更高,适合高并发、业务逻辑复杂的场景(如秒杀库存扣减、多表关联操作)。
1. TCC 模式核心原理
TCC 模式将分布式事务拆分为三个独立的业务操作,由业务代码手动实现,不依赖数据库事务:
- Try 阶段:资源检查与预留(如检查库存是否充足,并冻结部分库存,防止其他事务占用);
- Confirm 阶段:确认执行(如将冻结的库存正式扣减,此阶段操作必须成功,无回滚机制);
- Cancel 阶段:取消释放(若 Try 阶段预留资源后其他事务失败,释放预留的资源,如解冻冻结的库存);
- 核心特点:
-
- 无锁竞争:Try 阶段仅预留资源,不修改最终数据,避免全局锁;
-
- 强一致性可控:若所有 Try 成功,Confirm 执行后数据立即一致;若任一 Try 失败,Cancel 执行后资源释放,数据恢复初始状态;
-
- 业务侵入性强:需手动实现 Try/Confirm/Cancel 三个方法,业务代码复杂度高;
2. Seata TCC 模式实战(秒杀库存扣减)
以 "秒杀商品库存扣减" 为例,用 Seata TCC 模式实现高并发下的库存一致性管理:
2.1 数据库设计(库存表拆分)
为支持 TCC 的 "资源预留",将库存表拆分为 "基础库存表" 和 "冻结库存表":
- 基础库存表(inventory_base):存储商品总库存;
CREATE TABLE `inventory_base` (
```id` bigint NOT NULL AUTO_INCREMENT,``
```product_id` bigint NOT NULL COMMENT '商品ID',``
```total_stock` int NOT NULL COMMENT '总库存',``
```available_stock` int NOT NULL COMMENT '可用库存(total_stock - frozen_stock)',``
```create_time` datetime NOT NULL,``
```update_time` datetime NOT NULL,``
PRIMARY KEY (`id`),
UNIQUE KEY `uk_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='基础库存表';
- 冻结库存表(inventory_frozen):存储 Try 阶段冻结的库存,关联全局事务 ID;
CREATE TABLE `inventory_frozen` (
```id` bigint NOT NULL AUTO_INCREMENT,``
```product_id` bigint NOT NULL COMMENT '商品ID',``
```frozen_quantity` int NOT NULL COMMENT '冻结数量',``
```xid` varchar(100) NOT NULL COMMENT '全局事务ID',``
```branch_id` bigint NOT NULL COMMENT '分支事务ID',``
```status` tinyint NOT NULL COMMENT '状态:0-冻结中,1-已确认(扣减),2-已取消(解冻)',``
```create_time` datetime NOT NULL,``
```update_time` datetime NOT NULL,``
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xid_branch_id` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='冻结库存表';
2.2 业务代码实现(TCC 三阶段)
Seata TCC 模式需通过@LocalTCC注解标记 TCC 接口,并手动实现 Try/Confirm/Cancel 方法:
2.2.1 TCC 接口定义(InventoryTccService)
/**
* 库存TCC接口
* @LocalTCC:标记为TCC接口,Seata自动识别Try/Confirm/Cancel方法
*/
@LocalTCC
public interface InventoryTccService {
/**
* Try阶段:冻结库存(资源检查与预留)
* @TwoPhaseBusinessAction:指定Confirm和Cancel方法名,businessKey:业务标识(商品ID)
*/
@TwoPhaseBusinessAction(
name = "reduceInventoryTcc",
commitMethod = "confirm",
rollbackMethod = "cancel",
businessKey = "productId"
)
String tryFreezeInventory(
@BusinessKeyParam("productId") Long productId,
Integer quantity,
@Param("xid") String xid,
@Param("branchId") Long branchId
);
/**
* Confirm阶段:确认扣减库存(Try成功后执行)
*/
String confirm(
@Param("xid") String xid,
@Param("branchId") Long branchId,
@Param("productId") Long productId,
@Param("quantity") Integer quantity
);
/**
* Cancel阶段:取消冻结(Try失败或其他事务失败后执行)
*/
String cancel(
@Param("xid") String xid,
@Param("branchId") Long branchId,
@Param("productId") Long productId,
@Param("quantity") Integer quantity
);
}
2.2.2 TCC 接口实现(InventoryTccServiceImpl)
@Service
public class InventoryTccServiceImpl implements InventoryTccService {
@Autowired
private InventoryBaseMapper baseMapper; // 基础库存DAO
@Autowired
private InventoryFrozenMapper frozenMapper; // 冻结库存DAO
/**
* Try阶段:检查可用库存,冻结对应数量
*/
@Override
public String tryFreezeInventory(Long productId, Integer quantity, String xid, Long branchId) {
// 1. 检查可用库存是否充足(使用悲观锁防止并发问题)
InventoryBase base = baseMapper.selectByProductIdForUpdate(productId);
if (base == null || base.getAvailableStock()
return "fail:可用库存不足";
}
// 2. 冻结库存:减少可用库存,新增冻结记录
// 2.1 更新基础库存表的可用库存
int updateRows = baseMapper.reduceAvailableStock(productId, quantity);
if (updateRows return "fail:可用库存更新失败";
}
// 2.2 新增冻结库存记录
InventoryFrozen frozen = new InventoryFrozen();
frozen.setProductId(productId);
frozen.setFrozenQuantity(quantity);
frozen.setXid(xid);
frozen.setBranchId(branchId);
frozen.setStatus(0); // 0-冻结中
frozen.setCreateTime(new Date());
frozen.setUpdateTime(new Date());
frozenMapper.insert(frozen);
System.out.println("Try阶段:冻结库存成功,商品ID:" + productId + ",冻结数量:" + quantity + ",XID:" + xid);
return "success";
}
/**
* Confirm阶段:将冻结库存转为正式扣减(删除冻结记录,无需修改总库存,Try阶段已减少可用库存)
*/
@Override
public String confirm(String xid, Long branchId, Long productId, Integer quantity) {
// 1. 查询冻结记录
InventoryFrozen frozen = frozenMapper.selectByXidAndBranchId(xid, branchId);
if (frozen == null || frozen.getStatus() != 0) {
return "fail:冻结记录不存在或已处理";
}
// 2. 更新冻结记录状态为"已确认"(或直接删除,视业务需求)
frozen.setStatus(1);
frozen.setUpdateTime(new Date());
int updateRows = frozenMapper.updateById(frozen);
if (updateRows > 0) {
System.out.println("Confirm阶段:库存扣减成功,商品ID:" + productId + ",XID:" + xid);
return "success";
} else {
return "fail:冻结记录更新失败";
}
}
/**
* Cancel阶段:解冻冻结的库存(恢复可用库存,更新冻结记录状态)
*/
@Override
public String cancel(String xid, Long branchId, Long productId, Integer quantity) {
// 1. 查询冻结记录
InventoryFrozen frozen = frozenMapper.selectByXidAndBranchId(xid, branchId);
if (frozen == null || frozen.getStatus() != 0) {
return "fail:冻结记录不存在或已处理";
}
// 2. 解冻库存:恢复基础库存表的可用库存
int updateRows = baseMapper.increaseAvailableStock(productId, quantity);
if (updateRows
return "fail:可用库存恢复失败";
}
// 3. 更新冻结记录状态为"已取消"
frozen.setStatus(2);
frozen.setUpdateTime(new Date());
frozenMapper.updateById(frozen);
System.out.println("Cancel阶段:库存解冻成功,商品ID:" + productId + ",解冻数量:" + quantity + ",XID:" + xid);
return "success";
}
}
2.2.3 全局事务发起者(秒杀服务)
秒杀服务作为全局事务发起者,调用库存服务的 TCC 接口:
@Service
public class SeckillServiceImpl implements SeckillService {
@Autowired
private SeckillMapper seckillMapper; // 秒杀活动DAO
@Autowired
private InventoryTccService inventoryTccService; // 库存TCC接口(本地调用,若跨服务需用Feign+Seata TCC)
@Autowired
private GlobalTransactionContext globalTransactionContext; // Seata全局事务上下文
/**
* 秒杀下单(全局事务入口)
*/
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public String seckillOrder(Long seckillId, Long userId, Integer quantity) {
// 1. 获取全局事务信息(XID、分支ID)
GlobalTransaction tx = globalTransactionContext.getCurrent();
String xid = tx.getXid();
Long branchId = tx.getBranchId();
// 2. 检查秒杀活动是否有效
Seckill seckill = seckillMapper.selectById(seckillId);
if (seckill == null || seckill.getStatus() != 1 || seckill.getEndTime().before(new Date())) {
throw new RuntimeException("秒杀活动已结束或无效");
}
// 3. 调用库存TCC的Try阶段:冻结库存
String tryResult = inventoryTccService.tryFreezeInventory(
seckill.getProductId(),
quantity,
xid,
branchId
);
if (!"success".equals(tryResult)) {
throw new RuntimeException("库存冻结失败,秒杀下单回滚:" + tryResult);
}
// 4. 本地事务:创建秒杀订单(省略订单创建逻辑,若失败会触发Cancel阶段)
// ...
return "秒杀下单成功,XID:" + xid;
}
}
3. TCC 模式性能优化与注意事项
3.1 性能优化点
- 减少锁竞争:Try 阶段使用行锁(如select ... for update)而非表锁,降低并发阻塞;
- 异步化处理:Confirm/Cancel 阶段可异步执行(如通过消息队列),减少同步等待时间;
- 资源预分配:秒杀场景下,提前将库存加载到 Redis,Try 阶段先检查 Redis 库存,再同步到数据库,提升并发能力;
3.2 注意事项
- 幂等性设计:Confirm/Cancel 阶段可能因网络重试执行多次,需确保方法幂等(如通过xid+branchId判断是否已处理);
- 空回滚防护:若 Try 阶段未执行(如网络超时),Cancel 阶段可能空执行,需在 Cancel 方法中判断冻结记录是否存在;
- 悬挂问题:若 Try 阶段执行超时,但实际已成功,后续 Cancel 阶段执行后,Confirm 阶段又执行,导致数据不一致;需通过冻结记录状态严格控制执行顺序(如 Confirm 仅处理 "冻结中" 状态,Cancel 后状态改为 "已取消",拒绝 Confirm);
四、Saga 模式实战:长事务与跨语言微服务场景
Saga 模式适合长事务场景(如订单履约流程:下单→支付→发货→确认收货,每个步骤间隔可能达数小时)和跨语言微服务(如 Java 订单服务调用 Python 物流服务),通过 "拆分短事务 + 补偿事务" 实现最终一致性,无锁且支持异步执行。
1. Saga 模式核心原理
Saga 模式将分布式长事务拆分为多个独立的短本地事务(Step),每个 Step 执行后记录事务状态,若某个 Step 失败,调用对应的补偿事务(Compensation)回滚前面的 Step:
- 正向流程:Step1(下单)→ Step2(支付)→ Step3(发货)→ Step4(确认收货);
- 补偿流程:若 Step3(发货)失败,触发补偿流程:Compensation3(取消发货)→ Compensation2(退款)→ Compensation1(取消订单);
- 实现方式:
-
- 编排式(Choreography):无中心协调器,每个服务通过事件通知触发下一个服务(如订单服务创建订单后发送 "订单创建事件",支付服务监听事件后执行支付);
-
- 协同式(Orchestration):有中心协调器(Saga Orchestrator),统一管理所有 Step 的执行与补偿(如订单履约协调器调用订单、支付、物流服务);
2. Seata Saga 模式实战(订单履约流程)
以 "订单履约流程" 为例,用 Seata Saga 模式实现协同式分布式事务,协调器统一管理 "创建订单→支付→发货→确认收货" 四个 Step:
2.1 环境准备
- Seata Server:1.8.0(支持 Saga 模式);
- 服务列表:订单服务(Java)、支付服务(Java)、物流服务(Python)、Saga 协调器(基于 Seata Saga Orchestrator);
- 通信方式:HTTP(跨语言服务调用)、Dubbo(Java 服务间调用);
2.2 Saga 流程定义(JSON 格式)
通过 JSON 定义