分布式事务问题与Seata AT模式原理
微服务架构下,一个业务操作跨多个服务和多个数据库,本地事务无法保证数据一致性。Seata是阿里开源的分布式事务中间件,AT(Automatic Transaction)模式是其最常用的模式。AT模式对业务零侵入,通过自动生成补偿SQL实现回滚,适合大多数Spring Boot框架项目。
AT模式的核心机制分两阶段:一阶段拦截业务SQL,生成前置镜像(before image)和后置镜像(after image)并写入undo_log表,在本地事务中提交业务数据和undo_log;二阶段如果全局事务提交,异步删除undo_log;如果回滚,根据before image自动生成反向SQL补偿。
Seata Server部署
使用Docker部署Seata Server:
docker run -d --name seata-server \
-p 8091:8091 \
-p 7091:7091 \
-e SEATA_IP=192.168.1.100 \
-v /root/seata/config:/seata-server/resources \
apache/seata-server:2.0.0
Seata 2.0推荐使用Nacos作为注册中心和配置中心。在application.yml中配置:
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: ""
dataId: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: ""
store:
mode: db
db:
datasource: druid
dbType: mysql
url: jdbc:mysql://127.0.0.1:3306/seata
user: root
password: password
Spring Boot微服务接入Seata
添加依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.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
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
dataId: seataServer.properties
每个参与分布式事务的数据库需要创建undo_log表:
CREATE TABLE `undo_log` (
`branch_id` BIGINT(64) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT transaction mode undo table';
高并发设计中的@GlobalTransactional使用
在业务入口方法上添加@GlobalTransactional注解开启全局事务:
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private StorageFeignClient storageClient;
@Resource
private AccountFeignClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务)
orderMapper.insert(dto);
// 2. 扣减库存(远程调用,独立本地事务)
storageClient.deduct(dto.getProductId(), dto.getCount());
// 3. 扣减余额(远程调用,独立本地事务)
accountClient.debit(dto.getUserId(), dto.getMoney());
}
}
如果步骤3抛出异常,Seata TC会通知步骤1和步骤2的分支事务回滚。步骤1根据undo_log中的before image生成反向SQL(DELETE订单),步骤2生成补偿SQL(恢复库存数量)。
服务治理:事务分组与隔离级别
tx-service-group定义事务分组,不同业务可以使用不同分组实现隔离。Seata AT模式的全局锁机制保证写隔离:一阶段提交前需要获取全局锁,如果另一个全局事务正在修改同一行数据,当前事务必须等待。这种机制牺牲了一定并发性能换取一致性保证。
对于读操作,AT模式提供两种隔离级别:读未提交(默认,读本地事务已提交的数据)和读已提交(通过SELECT FOR UPDATE获取全局锁后再读)。
// 读已提交隔离级别
@GlobalTransactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 使用SELECT FOR UPDATE触发全局锁查询
Account from = accountMapper.selectByIdForUpdate(fromId);
Account to = accountMapper.selectByIdForUpdate(toId);
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
}
常见问题排查
Q: 全局事务回滚但不生效,undo_log表无数据
检查数据源代理是否生效。Seata通过DataSourceProxy拦截SQL,必须确保Seata代理了业务数据源。使用seata-spring-boot-starter时自动代理,但如果手动配置了多数据源,需要用DataSourceProxy手动包装。
Q: 全局事务超时回滚
默认全局事务超时时间60秒。如果业务链路较长(多个远程调用),在@GlobalTransactional中设置timeoutMills:
@GlobalTransactional(timeoutMills = 300000, rollbackFor = Exception.class)
Q: 并发场景下出现global lock conflict
多个全局事务同时修改同一行数据。评估业务是否能接受最终一致性,如果可以考虑改用Saga模式或消息中间件实现最终一致性方案,避免全局锁争用。对于API接口规范要求高的核心交易场景,AT模式仍是平衡一致性和开发效率的合理选择。业务中台建设中,订单、库存、账户三个域的跨服务事务是典型应用场景。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seata-fen-bu-shi-shi-wu-at-mo-shi-yuan-li-yu-springboot-wei/