Seata是阿里巴巴开源的分布式事务解决方案,提供AT、TCC、SAGA和XA四种事务模式。AT模式凭借低侵入性和自动补偿机制,成为微服务架构中最常用的分布式事务方案。本文剖析AT模式的两阶段提交机制,并给出Spring Boot集成Seata的完整部署方案。
Seata AT模式两阶段提交机制
Seata架构包含三个核心角色:Transaction Coordinator(TC,事务协调器)、Transaction Manager(TM,事务管理器)和Resource Manager(RM,资源管理器)。TC独立部署,TM和RM以SDK形式集成在应用中。
AT模式的两个阶段:
阶段一(Phase 1):各RM执行本地业务SQL并提交本地事务。在提交前,RM拦截SQL解析前后镜像数据,生成undo log并写入undo_log表。本地事务提交后,RM向TC注册分支事务并上报执行结果。
阶段二(Phase 2):TM根据所有分支事务的执行结果向TC发送全局提交或回滚指令。如果全部成功,TC通知各RM异步删除undo log;如果有任一分支失败,TC通知各RM根据undo log反向补偿数据。
-- undo_log表结构
CREATE TABLE 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 '状态:0正常,1全局已完成',
log_created DATETIME NOT NULL COMMENT '创建时间',
log_modified DATETIME NOT NULL COMMENT '修改时间',
UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE=InnoDB;
undo log存储的是变更前后的数据快照。回滚时,Seata根据before image恢复数据,并校验after image与当前数据库中的值是否一致。如果不一致,说明数据在事务期间被其他操作修改,Seata会抛出BranchRollbackFailed_Retriable异常,记录脏写并人工介入。
Seata Server部署与Nacos注册中心配置
TC独立部署,以下为基于Docker的Seata Server 2.0部署方案,使用Nacos作为注册和配置中心:
# docker-compose.yml
version: '3'
services:
seata-server:
image: seataio/seata-server:2.0.0
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_PORT=8091
- STORE_MODE=db
- SEATA_CONFIG_NAME=file:/root/seata-config/registry
volumes:
- ./seata-config:/root/seata-config
- ./application.yml:/seata-server/resources/application.yml
application.yml核心配置:
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
data-id: seataServer.properties
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
store:
mode: db
db:
datasource: druid
db-type: mysql
url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
user: root
password: ${DB_PASSWORD}
global-table: global_table
branch-table: branch_table
lock-table: lock_table
Seata的三个核心存储表(global_table、branch_table、lock_table)存储全局事务和分支事务的元数据。lock_table实现全局锁,保证同一行数据在同一全局事务内不会被并发修改。
Spring Boot微服务集成Seata AT模式
以订单服务(创建订单)和库存服务(扣减库存)的分布式事务为例:
<!-- pom.xml -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.3.0</version>
</dependency>
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata
data-source-proxy-mode: AT
data-source-proxy-mode: AT启用AT模式,Seata会自动代理DataSource,拦截所有SQL操作生成undo log。业务代码只需在入口方法添加@GlobalTransactional注解:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryClient;
@GlobalTransactional(timeoutMills = 60000, name = "createOrder")
public void createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务,RM自动注册分支事务)
Order order = new Order();
order.setProductId(dto.getProductId());
order.setQuantity(dto.getQuantity());
order.setUserId(dto.getUserId());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 远程调用库存服务扣减库存(分支事务)
Result<Void> result = inventoryClient.deduct(
dto.getProductId(), dto.getQuantity()
);
if (!result.isSuccess()) {
throw new RuntimeException("库存扣减失败: " + result.getMessage());
}
// 3. 远程调用账户服务扣款(分支事务)
Result<Void> payResult = accountClient.debit(
dto.getUserId(), dto.getAmount()
);
if (!payResult.isSuccess()) {
throw new RuntimeException("扣款失败");
}
}
}
@GlobalTransactional标记的方法内,所有数据库操作和远程调用都纳入同一个全局事务。任一步骤抛出异常,TC会通知所有已注册的RM执行回滚补偿。库存服务的扣减操作不需要任何事务注解,Seata通过DataSource代理自动参与全局事务。
全局锁与并发冲突处理
AT模式通过全局锁实现写隔离。阶段一本地事务提交前,RM会向TC申请获取涉及数据行的全局锁。如果该行已被其他全局事务锁定,当前事务会等待最多10秒(可配置),超时后抛出LockConflictException。
# 锁超时配置
seata:
client:
rm:
lock:
retry-interval: 30
retry-times: 10
retry-policy-branch-rollback-on-conflict: true
读隔离方面,AT模式默认使用读已提交级别。阶段一本地事务提交后、全局事务尚未完成时,其他事务可以读到未提交的脏数据。如果需要读已提交的全局隔离级别,需要在查询SQL前使用@GlobalLock + SELECT FOR UPDATE:
@Service
public class InventoryService {
@GlobalLock
@Transactional
public Inventory getInventoryForUpdate(Long productId) {
// SELECT ... FOR UPDATE 会触发全局锁检查
return inventoryMapper.selectForUpdate(productId);
}
}
生产环境部署注意事项
undo_log清理:全局事务完成后,undo log由RM异步删除。如果应用异常退出,可能残留undo log记录。定期执行清理任务,删除超过24小时的已完成事务记录。
TC高可用:部署至少3个TC实例,通过Nacos实现服务发现和负载均衡。TC使用DB模式存储事务状态,多个TC实例共享同一数据库,天然支持水平扩展。
事务超时配置:timeoutMills默认60秒。涉及多个远程调用的长链路事务需适当增大超时时间,但不宜超过120秒,避免长时间持有全局锁导致性能问题。
幂等性设计:网络抖动可能导致TC的重试请求到达RM。库存扣减等操作需保证幂等性,通过唯一请求ID或状态机防止重复执行。
AT模式通过自动生成undo log实现补偿回滚,业务代码侵入性低,适合大多数OLTP场景。但对于涉及外部系统调用(如第三方支付)的操作,AT模式无法自动补偿,需配合TCC模式或SAGA模式处理。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seata-fen-bu-shi-shi-wu-at-mo-shi-yuan-li-pou-xi-yu-shi/