微服务架构下跨服务的数据一致性是核心工程挑战。Seata的AT(Automatic Transaction)模式通过SQL解析和undo_log自动回滚机制,对业务代码侵入性最低,只需加@GlobalTransactional注解即可实现跨库事务。本文围绕Seata 2.0在Spring Boot 3.x微服务中的集成部署,拆解事务协调器配置、全局事务传播和常见故障排查。
Seata Server部署与存储模式配置
Seata Server(TC,Transaction Coordinator)是事务协调器,负责维护全局事务和分支事务的状态。生产环境使用Nacos作为注册中心,MySQL作为存储模式:
# application.yml(Seata Server)
seata:
server:
service-port: 8091
max-commit-retry-timeout: 100
max-rollback-retry-timeout: 100
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
retry_dead-threshold: 130000
store:
mode: db
db:
datasource: druid
db-type: mysql
url: jdbc:mysql://mysql.internal:3306/seata?useUnicode=true&characterEncoding=utf8
user: seata
password: ${SEATA_DB_PASSWORD}
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacos.internal:8848
namespace: seata
group: SEATA_GROUP
cluster: default
Seata 2.0新增Raft集群模式,不再依赖外部数据库存储事务日志,适合不想引入额外MySQL依赖的场景。但Raft模式要求至少3节点部署,延迟比DB模式高10-15ms。存储选型:已有MySQL基础设施用DB模式,云原生环境用Raft模式。
初始化Seata数据库表:
-- global_table: 全局事务表
CREATE TABLE IF NOT EXISTS `global_table` (
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status`,`gmt_modified`)
) ENGINE=InnoDB;
-- branch_table: 分支事务表
-- lock_table: 全局锁表
-- distributed_lock: 分布式锁表
Spring Boot微服务客户端集成
订单服务和库存服务都需要集成Seata Client。以Maven依赖和配置为例:
<!-- pom.xml -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- 数据源代理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.20</version>
</dependency>
# application.yml(微服务端)
seata:
enabled: true
application-id: order-service
tx-service-group: yunthe_tx_group
service:
vgroup-mapping:
yunthe_tx_group: default
registry:
type: nacos
nacos:
server-addr: nacos.internal:8848
namespace: seata
group: SEATA_GROUP
data-source-proxy-mode: AT
client:
rm:
report-success-enable: true
table-meta-check-enable: false
async-commit-buffer-limit: 10000
lock-retry-times: 30
lock-retry-interval: 10
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
每个参与全局事务的数据库都需要创建undo_log表:
-- 在业务数据库中创建undo_log表
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚信息',
`log_status` INT NOT NULL COMMENT '状态',
`log_created` DATETIME NOT NULL COMMENT '创建时间',
`log_modified` DATETIME NOT NULL COMMENT '修改时间',
PRIMARY KEY (`branch_id`),
KEY `idx_log_created` (`log_created`)
) ENGINE=InnoDB COMMENT='AT模式事务日志表';
全局事务注解与业务代码实现
订单服务作为事务发起方,使用@GlobalTransactional注解开启全局事务:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryClient;
@Autowired
private AccountFeignClient accountClient;
@GlobalTransactional(name = "createOrder", timeoutMills = 60000, rollbackFor = Exception.class)
public OrderResult createOrder(OrderRequest request) {
// 1. 创建订单(本地事务,自动注册为分支事务)
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(request.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用,通过Feign传播xid)
inventoryClient.deduct(request.getProductId(), request.getQuantity());
// 3. 扣减账户余额
accountClient.debit(request.getUserId(), request.getAmount());
// 4. 更新订单状态
order.setStatus("PAID");
orderMapper.updateStatus(order);
return new OrderResult(order.getId(), "SUCCESS");
}
}
Feign拦截器自动传播全局事务ID(xid),下游服务无需额外配置。Seata通过SeataFeignClient拦截请求头中的TX_XID字段,将xid绑定到当前线程的RootContext:
// 库存服务——分支事务自动加入全局事务
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Transactional(rollbackFor = Exception.class)
public void deduct(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getStock() < quantity) {
throw new BusinessException("库存不足");
}
inventoryMapper.deductStock(productId, quantity);
// Seata AT模式自动记录undo_log
// 全局回滚时根据undo_log自动恢复库存
}
}
AT模式工作原理与undo_log机制
AT模式分为两阶段:一阶段执行业务SQL并记录undo_log,二阶段根据全局事务状态提交或回滚。一阶段流程:
1. Seata拦截业务SQL,执行前查询并缓存前镜像(before image)。
2. 执行业务SQL,查询后镜像(after image)。
3. 将前后镜像和SQL类型组装成undo_log插入undo_log表。
4. 获取全局锁(lock_table),提交本地事务。
二阶段提交:异步删除undo_log记录,释放全局锁。二阶段回滚:根据undo_log的前镜像生成反向SQL恢复数据,释放全局锁。
// undo_log中存储的回滚信息JSON结构
{
"branchId": 1234567890,
"sqlUndoLogs": [{
"sqlType": "UPDATE",
"tableName": "inventory",
"beforeImage": {
"rows": [{
"fields": [
{"name": "id", "value": 1},
{"name": "stock", "value": 100}
]
}]
},
"afterImage": {
"rows": [{
"fields": [
{"name": "id", "value": 1},
{"name": "stock", "value": 95}
]
}]
}
}]
}
常见故障排查与解决方案
故障一:全局事务超时回滚。默认超时60秒,如果业务逻辑执行时间较长(如等待外部接口响应),超时后TC强制回滚。调整timeoutMills参数,或优化业务逻辑减少事务持有时间。原则:全局事务内不要包含RPC长等待操作。
故障二:undo_log表不存在导致分支事务注册失败。错误日志Table 'business_db.undo_log' doesn't exist。确保每个参与全局事务的数据库都创建了undo_log表。
故障三:全局锁冲突。两个全局事务同时修改同一行数据,后到的事务获取全局锁超时。检查lock_table中的table_name和pk字段定位冲突行。业务层建议避免热点数据并发修改,或使用乐观锁减少锁竞争。
// 全局锁冲突排查
// 查看lock_table中的锁记录
SELECT * FROM seata.lock_table WHERE table_name = 'inventory' AND row_key LIKE '%1:%';
// 查看全局事务状态
SELECT xid, status, transaction_name, gmt_create
FROM seata.global_table
WHERE status IN (0, 1) -- 0=Begin, 1=Committing
ORDER BY gmt_create DESC;
// 手动清理悬挂事务(谨慎操作)
DELETE FROM seata.global_table WHERE status = 4 AND gmt_modified < DATE_SUB(NOW(), INTERVAL 1 HOUR);
DELETE FROM seata.branch_table WHERE xid NOT IN (SELECT xid FROM seata.global_table);
故障四:xid未传播到下游服务。检查Feign拦截器是否正确注册,Seata 2.0自动配置SeataFeignClient。如果使用RestTemplate或WebClient,需要手动添加SeataRequestInterceptor。Dubbo场景需要配置seata-spring-boot-starter的Dubbo过滤器。
生产环境建议配合Seata控制台监控全局事务执行情况,关注事务成功率、平均耗时和回滚率三个指标。正常水平下回滚率应低于0.1%,如果回滚率突然升高,优先排查全局锁冲突和事务超时问题。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seata-fen-bu-shi-shi-wu-shi-zhan-at-mo-shi-zai-springboot/