分布式事务问题背景与Seata架构原理
微服务架构下,单个业务操作可能跨多个独立数据库,传统本地事务无法保证数据一致性。例如电商下单场景涉及订单服务、库存服务、账户服务三个独立数据库,任一环节失败都需要全部回滚。Seata是阿里巴巴开源的分布式事务解决方案,AT(Automatic Transaction)模式通过自动生成回滚SQL实现无损补偿,业务代码零侵入。
Seata架构包含三个核心角色:TC(Transaction Coordinator)事务协调器,负责维护全局事务和分支事务的状态;TM(Transaction Manager)事务管理器,定义全局事务的开始、提交和回滚;RM(Resource Manager)资源管理器,管理分支事务上的本地资源。微服务架构中每个服务实例既是TM也是RM。
Seata Server部署与Nacos注册配置
Seata Server(TC)需要独立部署,生产环境建议使用Nacos作为注册中心和配置中心。以下是基于Docker的部署方案:
version: '3'
services:
seata-server:
image: seataio/seata-server:2.0.0
container_name: seata-server
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=seata.example.com
- SEATA_PORT=8091
- STORE_MODE=db
- SEATA_CONFIG_NAME=file:/root/seata-config/registry
volumes:
- ./seata-config:/root/seata-config
depends_on:
- mysql
registry.conf配置Nacos作为注册中心:
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "nacos:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "nacos:8848"
group = "SEATA_GROUP"
namespace = ""
dataId = "seataServer.properties"
}
}
STORE_MODE设置为db模式,事务日志持久化到数据库,相比file模式支持多TC节点高可用部署。Seata 2.0默认使用db存储模式,需要创建global_table、branch_table、lock_table三张表,建表SQL在Seata源码的script/server/db目录下。
Spring Boot集成Seata AT模式配置
微服务工程中集成Seata需要引入依赖并配置数据源代理。Maven依赖配置:
<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客户端:
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: seata.example.com:8091
registry:
type: nacos
nacos:
server-addr: nacos:8848
group: SEATA_GROUP
application: seata-server
config:
type: nacos
nacos:
server-addr: nacos:8848
group: SEATA_GROUP
data-source-proxy-mode: AT
data-source-proxy-mode设置为AT后,Seata自动代理数据源,拦截SQL执行生成undo_log。业务代码无需任何改动,只需在事务入口添加@GlobalTransactional注解:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryClient;
@Autowired
private AccountFeignClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务,分支事务1)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setAmount(dto.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用,分支事务2)
Result invResult = inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
if (!invResult.isSuccess()) {
throw new RuntimeException("库存扣减失败: " + invResult.getMessage());
}
// 3. 扣减余额(远程调用,分支事务3)
Result accResult = accountClient.debit(dto.getUserId(), dto.getAmount());
if (!accResult.isSuccess()) {
throw new RuntimeException("余额扣减失败: " + accResult.getMessage());
}
return order;
}
}
当步骤3抛出异常时,TC会通知所有分支事务回滚。订单服务的undo_log记录了insert操作的反向SQL(DELETE),库存和账户服务各自执行补偿操作。整个回滚过程对业务代码透明。
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(6) NOT NULL COMMENT '创建时间',
`log_modified` datetime(6) NOT NULL COMMENT '修改时间',
`ext` varchar(100) DEFAULT NULL COMMENT '扩展字段',
PRIMARY KEY (`branch_id`),
KEY `idx_log_created` (`log_created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
rollback_info字段存储JSON格式的回滚数据,包含执行前快照(beforeImage)和执行后快照(afterImage)。以UPDATE操作为例,beforeImage记录修改前的行数据,afterImage记录修改后的行数据。回滚时Seata根据beforeImage生成反向UPDATE SQL,将数据恢复到修改前状态。
对于INSERT操作,回滚SQL是DELETE;对于DELETE操作,回滚SQL是INSERT。Seata通过解析SQL语法树自动生成这些反向操作,开发人员不需要手动编写补偿逻辑。
高可用部署与常见问题诊断
Seata Server的高可用通过多实例部署实现。多个TC实例注册到同一个Nacos集群,客户端通过注册中心自动发现可用的TC节点。当某个TC实例宕机,客户端自动切换到其他实例。事务日志存储在共享的MySQL数据库中,保证状态一致性。
常见问题:全局事务超时
默认全局事务超时时间为60秒,复杂业务场景下可能不够。通过@GlobalTransactional注解的timeoutMills参数调整:
@GlobalTransactional(
name = "create-order",
timeoutMills = 120000, // 120秒超时
rollbackFor = Exception.class
)
超时后TC会主动触发全局回滚。如果业务方法执行时间超过超时时间,后续的分支事务注册会被拒绝。建议根据业务实际耗时设置合理的超时时间,预留30%的余量。
常见问题:undo_log清理
事务完成后undo_log记录会被标记为log_status=1,但不会立即删除。需要配置定时任务定期清理历史记录:
@Scheduled(cron = "0 0 2 * * ?")
public void cleanUndoLog() {
// 删除7天前的已完成undo_log
undoLogMapper.deleteExpired(LocalDateTime.now().minusDays(7));
}
也可以配置Seata的undo.log.save.days参数,由框架自动清理。默认保留7天,生产环境建议设置为3天以减少存储占用。
常见问题:数据源代理不生效
如果使用了Druid、HikariCP等连接池,需要确保Seata的数据源代理在连接池之上生效。检查启动日志中是否出现”Seata AT mode data source proxy”相关信息。如果使用了自定义DataSource Bean,需要手动包装为DataSourceProxy:
@Bean
public DataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = properties
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
return new DataSourceProxy(dataSource);
}
Seata AT模式通过自动化的SQL解析和undo_log机制实现了低侵入的分布式事务管理。配合Spring Boot的声明式事务注解,微服务架构下的跨库事务一致性可以像本地事务一样简单地处理。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seata-fen-bu-shi-shi-wu-shi-zhan-at-mo-shi-ji-cheng/