分布式事务为什么选Seata AT模式
微服务架构下,一次业务操作可能涉及多个服务的本地事务。比如电商下单:订单服务创建订单、库存服务扣减库存、账户服务扣减余额,三者必须原子性完成。网络故障或服务异常导致部分成功部分失败时,数据就不一致了。
Seata的AT(Automatic Transaction)模式对业务代码侵入最小——只需加一个@GlobalTransactional注解,框架自动处理两阶段提交的回滚逻辑。相比TCC模式需要编写Try/Confirm/Cancel三个方法,AT模式通过拦截SQL自动生成回滚日志,开发效率高出数倍。
AT模式的代价是性能:一阶段提交时需要生成undo_log,二阶段回滚时需要反向补偿。对于高并发场景,这个开销需要评估。
Seata Server部署与配置
Seata 1.8+推荐使用TC(Transaction Coordinator)独立部署模式:
# 下载Seata Server
wget https://github.com/seata/seata/releases/download/v2.0.0/seata-server-2.0.0.tar.gz
tar -xzf seata-server-2.0.0.tar.gz
application.yml核心配置(以Nacos为注册中心,MySQL为存储):
seata:
server:
service-port: 8091
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: DEFAULT_GROUP
application: seata-server
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: seata
password: seata_pwd
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
初始化Seata数据库表:
-- global_table: 全局事务表
CREATE TABLE global_table (
xid VARCHAR(128) PRIMARY KEY,
transaction_id BIGINT,
status TINYINT NOT NULL,
application_id VARCHAR(32),
transaction_service_id VARCHAR(32),
transaction_name VARCHAR(128),
timeout INT,
begin_time BIGINT,
application_data VARCHAR(2000),
gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
gmt_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- branch_table: 分支事务表
CREATE TABLE branch_table (
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
transaction_id BIGINT,
resource_group_id VARCHAR(32),
resource_id VARCHAR(256),
branch_type VARCHAR(8),
status TINYINT,
client_id VARCHAR(64),
application_data VARCHAR(2000),
gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
gmt_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (branch_id, xid)
);
-- lock_table: 全局锁表
CREATE TABLE lock_table (
row_key VARCHAR(128) NOT NULL,
xid VARCHAR(128),
transaction_id BIGINT,
branch_id BIGINT NOT NULL,
resource_id VARCHAR(256),
table_name VARCHAR(32),
pk VARCHAR(36),
status TINYINT NOT NULL DEFAULT 0,
gmt_create TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
gmt_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (row_key, branch_id)
);
Spring Boot微服务端集成
每个参与分布式事务的微服务都需要配置Seata客户端。以Maven依赖为例:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
客户端application.yml配置:
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: DEFAULT_GROUP
application: seata-server
每个微服务的业务数据库中需要undo_log表:
CREATE TABLE undo_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
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 TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
log_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY ux_undo_log (xid, branch_id)
);
业务代码实现分布式事务
订单服务——事务发起方:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", timeoutMills = 60000)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setAmount(dto.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
// 3. 扣减账户余额(远程调用)
accountClient.debit(dto.getUserId(), dto.getAmount());
return order;
}
}
库存服务——事务参与方,无需额外注解:
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Transactional // 本地事务即可,Seata AT模式自动纳入全局事务
public void deduct(String productId, int quantity) {
Inventory inv = inventoryMapper.selectByProductId(productId);
if (inv.getStock() < quantity) {
throw new BusinessException("库存不足");
}
inv.setStock(inv.getStock() - quantity);
inventoryMapper.updateById(inv);
}
}
如果步骤3(扣减余额)抛出异常,Seata会自动回滚步骤1和步骤2。回滚原理是:一阶段提交时,Seata拦截SQL并记录修改前的数据到undo_log,回滚时读取undo_log执行反向SQL。
生产环境关键调优
全局事务超时:默认60秒。跨服务调用链较长时,合理设置timeoutMills。超时后Seata自动回滚,业务侧要做好超时异常处理。
全局锁超时:当两个全局事务操作同一行数据时,后到的事务会等待全局锁。默认30秒获取不到锁就抛出LockConflictException。高并发场景下调低重试间隔:
seata:
client:
rm:
lock:
retry-interval: 10 # 锁重试间隔10ms
retry-times: 30 # 重试30次
async-commit-buffer-limit: 10000
undo_log清理:二阶段完成后的undo_log不会自动删除。配置定时清理任务:
@Scheduled(cron = "0 0 3 * * ?")
public void cleanUndoLog() {
undoLogMapper.deleteByStatusAndGmtModified(
TransactionStatus.Finished.getCode(),
LocalDateTime.now().minusDays(7)
);
}
Seata Server高可用:至少部署3个TC实例,通过Nacos注册中心实现服务发现。TC实例无状态,global_table中的数据是共享的,任一实例故障不影响全局事务的提交和回滚。
常见异常与排查
Could not found global transaction xid:XID未传递到下游服务。检查SeataFilter是否注册,Feign/RestTemplate拦截器是否配置。Spring Cloud 2021+需要手动注册SeataFeignClientAutoConfiguration。
LockConflictException:全局锁冲突。两个事务竞争同一行数据。业务上应该避免热点数据的高并发写,或改用TCC模式在Try阶段预留资源。
RollbackFailedException:回滚失败,undo_log已被清理或数据已被手动修改。这种情况下需要人工介入,根据业务日志补齐数据。
Seata AT模式在中小规模业务中足够实用,核心优势是低侵入性。但万级TPS场景下,undo_log写入和全局锁等待会成为瓶颈,那时需要考虑TCC或Saga模式,将补偿逻辑的细粒度控制权交给开发者。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-pei/