在微服务架构中,业务逻辑被拆分为多个独立服务后,原本在单体应用中简单的事务操作变得异常复杂。以电商下单场景为例,创建订单、扣减库存、扣减账户余额这三个操作可能分别属于订单服务、库存服务和账户服务。当库存扣减成功但账户余额不足时,如何保证数据一致性?这就是典型的分布式事务问题。
Alibaba Seata(Simple Extensible Autonomous Transaction Architecture)正是为解决这类问题而生。我在实际金融级系统中使用Seata近三年,它通过AT、TCC、SAGA和XA四种模式,覆盖了从强一致到最终一致的不同业务场景需求。相比传统2PC方案,Seata最大的突破在于将事务协调器从数据库层提升到应用层,通过全局锁+本地事务的巧妙设计,实现了高性能与高可用性的平衡。
Seata的架构设计体现了"关注点分离"原则,主要包含三个核心组件:
Transaction Coordinator (TC):事务协调器大脑,维护全局事务状态。在生产环境中建议独立部署,我们采用3节点集群保证高可用,通过Raft协议实现选主。关键配置项包括:
properties复制server.raft.serverAddr=192.168.1.101:8091,192.168.1.102:8091,192.168.1.103:8091
store.mode=raft
Transaction Manager (TM):定义事务边界,在业务服务中通过@GlobalTransactional注解触发。我们曾在注解中遗漏timeout属性导致死锁,建议必设超时:
java复制@GlobalTransactional(timeoutMills = 60000, name = "createOrder")
public void createOrder(OrderDTO order) {
// 跨服务调用
}
Resource Manager (RM):管理分支事务资源,与TC通信上报状态。数据库代理是核心创新点,通过解析SQL自动生成undo log。需要注意JDBC驱动兼容性问题,MySQL 8.x需额外配置:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
跨服务调用时,Seata通过拦截器自动传播XID(全局事务ID)。我们在网关层遇到过Filter顺序问题,导致XID丢失。正确配置示例:
java复制public class SeataFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 必须放在其他Filter之前
String xid = request.getHeader("TX_XID");
if(StringUtils.isNotBlank(xid)){
RootContext.bind(xid);
}
chain.doFilter(request, response);
}
}
AT模式是Seata的招牌特性,其核心在于"一阶段提交+二阶段异步回滚"。具体实现分为三个阶段:
执行阶段:RM拦截业务SQL,生成before image(前置镜像)和after image(后置镜像)。我们曾因大字段未加@Transient导致undo log膨胀,需注意:
java复制@Entity
public class Product {
@Lob
@Transient // 避免存入undo_log
private String description;
}
提交阶段:TC收到所有分支的提交报告后,异步删除undo log。这里要关注磁盘IO性能,我们通过调整刷盘策略优化:
properties复制server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
回滚阶段:发生异常时,TC根据undo log生成反向SQL。遇到过JSON字段类型不匹配问题,解决方案:
sql复制ALTER TABLE undo_log MODIFY rollback_info LONGTEXT;
Seata通过SELECT FOR UPDATE实现全局锁,这要求业务表必须有主键。我们在分库分表环境中踩过坑,解决方案:
sql复制-- 必须显式指定主键
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 错误写法(无法加锁)
UPDATE account SET status = 0 WHERE user_id = 100;
锁冲突时的等待策略可通过参数调整:
properties复制client.lock.retryInterval=10
client.lock.retryTimes=30
TC集群建议采用奇数节点(3/5/7),注册中心优先选用Nacos:
properties复制registry.type=nacos
config.type=nacos
nacos.serverAddr=127.0.0.1:8848
nacos.namespace=seata-cluster
我们通过K8s StatefulSet实现动态扩缩容,关键配置:
yaml复制apiVersion: apps/v1
kind: StatefulSet
metadata:
name: seata-server
spec:
serviceName: "seata"
replicas: 3
template:
spec:
containers:
- name: seata
env:
- name: SEATA_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
连接池优化:默认Druid配置不适合高并发,建议调整:
properties复制store.db.maxConn=50
store.db.minConn=5
store.db.maxWait=5000
批量操作支持:1.5.0+版本支持批量INSERT优化:
java复制@GlobalTransactional
public void batchInsert(List<Order> orders) {
// 需开启allowMultiQueries
jdbcTemplate.batchUpdate("INSERT INTO orders(...) VALUES(...)");
}
监控集成:通过Prometheus暴露指标:
properties复制metrics.enabled=true
metrics.registryType=compact
metrics.exporterList=prometheus
| 特性 | AT模式 | TCC模式 | SAGA模式 | XA模式 |
|---|---|---|---|---|
| 一致性 | 最终一致 | 强一致 | 最终一致 | 强一致 |
| 侵入性 | 低 | 高 | 中 | 低 |
| 性能 | 高 | 中 | 高 | 低 |
| 适用场景 | 常规CRUD | 资金交易 | 长事务流程 | 传统数据库 |
是否需要强一致?
是否有大量本地事务?
是否遗留系统集成?
检查XID传播:
bash复制# 查看日志中的XID
grep "RootContext" application.log
验证TC连接:
java复制// 在TM端注入GlobalTransactionScanner
@Autowired
private GlobalTransactionScanner globalTransactionScanner;
检查数据源代理:
properties复制spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filters=stat,seata
当出现"Global lock conflict"时:
我们曾用以下脚本定位问题:
sql复制SELECT COUNT(*) FROM undo_log WHERE status = 0 AND gmt_create < DATE_SUB(NOW(), INTERVAL 1 HOUR);
表设计规范:
事务粒度控制:
java复制// 错误示范(事务过大)
@GlobalTransactional
public void processBatch() {
// 处理1000条记录
}
// 正确做法
public void processBatch() {
List<Item> items = fetchItems();
for(Item item : items) {
processSingle(item);
}
}
@GlobalTransactional
private void processSingle(Item item) {
// 单条处理
}
混合模式使用:
java复制@GlobalTransactional(timeoutMills = 30000)
public void hybridOperation() {
// AT模式操作
orderService.create();
// TCC模式操作
accountService.debitTry();
inventoryService.deductTry();
}
在实际金融项目中,我们通过AT+TCC混合模式,将事务成功率从98.7%提升到99.99%。关键是在资金操作类服务使用TCC,普通CRUD使用AT,既保证核心交易强一致,又维持系统整体高性能。