1. 分布式事务与Seata核心架构解析
第一次接触Seata源码是在处理一个电商订单与库存联动的分布式事务问题时。当时我们的系统已经拆分为微服务架构,订单服务和库存服务分别部署在不同节点,如何保证"创建订单+扣减库存"这两个操作的原子性成为棘手难题。Seata的AT模式最终帮我们解决了这个问题,但真正理解其工作原理还是得深入源码。
分布式事务本质上要解决的是跨服务数据一致性问题。在微服务架构下,传统的单机事务(ACID)无法直接适用,CAP理论告诉我们必须在一致性和可用性之间做出权衡。Seata提供的AT、TCC、Saga和XA四种模式,实际上是对不同业务场景下一致性需求的灵活适配。
提示:阅读Seata源码前建议先掌握其核心架构。1.4.2版本的核心模块包括:
- Transaction Coordinator (TC):事务协调器,全局事务的大脑
- Transaction Manager (TM):定义事务边界
- Resource Manager (RM):管理分支事务资源
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Seata AT模式源码深度剖析
2.1 全局事务启动过程
在订单服务中通过@GlobalTransactional注解开启全局事务时,TM会向TC发送Begin请求。关键代码在DefaultGlobalTransaction类的begin()方法:
java复制public void begin(int timeout, String name) throws TransactionException {
// 向TC注册全局事务
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
this.xid = response.getXid();
this.status = GlobalStatus.Begin;
}
这里有几个关键点需要注意:
- 默认超时时间是60秒,对于长事务需要显式设置更大值
- XID(全局事务ID)由TC生成并返回,格式通常是ip:port:sequence
- 事务状态由TC统一管理,本地只是缓存状态
2.2 分支事务注册机制
当订单服务执行INSERT操作、库存服务执行UPDATE操作时,各自的RM会向TC注册分支事务。这个过程隐藏在SQL解析阶段,以MySQL为例,关键类在MySQLInsertExecutor:
java复制protected TableRecords beforeImage() throws SQLException {
// 生成前置镜像(业务数据变更前的状态)
TableRecords beforeImage = buildTableRecords(getTableMeta());
// 向TC注册分支事务
branchRegister(buildBranchTransaction(beforeImage));
return beforeImage;
}
实际项目中我们遇到过分支注册失败的坑:
- 网络抖动导致注册超时:需要合理设置client.rm.report.retry.count参数
- 表未添加索引导致前置镜像查询性能差:所有参与分布式事务的表必须有主键
- 字段类型不匹配导致序列化异常:建议统一使用包装类型而非基本类型
2.3 两阶段提交实现
全局事务提交时,TC会分两个阶段协调所有分支:
- 第一阶段:TC向所有分支发送prepare请求,各分支记录undo_log并返回准备结果
- 第二阶段:根据prepare结果决定全局提交或回滚
核心逻辑在DefaultCoordinator类的doGlobalCommit方法:
java复制public GlobalStatus doGlobalCommit(GlobalSession globalSession) {
// 异步执行各分支的commit
for (BranchSession branchSession : globalSession.getSortedBranches()) {
BranchStatus branchStatus = doBranchCommit(branchSession);
if (branchStatus != BranchStatus.PhaseTwo_Committed) {
// 出现异常转为异步重试
asyncCommitBranch(globalSession, branchSession);
}
}
return GlobalStatus.Committing;
}
我们在生产环境发现的问题及解决方案:
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 部分分支长时间处于PhaseTwo_Committing状态 | 网络分区导致分支未收到commit通知 | 调整server.recovery.committingRetryPeriod参数 |
| undo_log表空间增长过快 | 事务未及时清理 | 配置undo_log.log_save_days参数 |
| 热点数据冲突率高 | 全局锁竞争激烈 | 业务上避免单行记录高频更新 |
3. Seata TCC模式实现原理
3.1 TCC接口契约设计
TCC模式相比AT需要显式定义三个方法:
- prepare:资源预留
- commit:确认执行业务
- rollback:取消预留
以资金转账为例,典型的TCC接口定义:
java复制public interface AccountService {
@TwoPhaseBusinessAction(name = "transfer", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepare(BusinessActionContext context,
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
boolean commit(BusinessActionContext context);
boolean rollback(BusinessActionContext context);
}
实际开发中的经验教训:
- prepare方法必须实现幂等性,可能被重复调用
- commit/rollback方法需要处理空回滚问题(prepare未执行直接调rollback)
- 业务参数必须通过@BusinessActionContextParameter注解明确声明
3.2 TCC事务协调流程
TC对TCC事务的管理体现在ActionInterceptorHandler类中:
java复制public Object proceed() throws Throwable {
// 一阶段:执行prepare
if (isFirstPhase()) {
try {
businessActionContext.setActionName(actionName);
// 保存上下文到TC
registerBranchTransaction();
return method.invoke(target, args);
} catch(...) {
// 异常时触发回滚
triggerRollback();
}
}
// 二阶段:执行commit/rollback
else {
return handlePhaseTwo();
}
}
我们在金融系统中使用TCC时总结的最佳实践:
- 资金类操作优先使用TCC模式
- prepare阶段建议添加过期时间(通过@TwoPhaseBusinessAction的expireTime属性)
- 对于长事务,需要实现状态查询接口供TC补偿查询
4. Seata高可用部署方案
4.1 基于Docker Compose的生产级部署
Seata 1.4.2官方推荐的docker-compose.yml配置:
yaml复制version: '3'
services:
seata-server:
image: seataio/seata-server:1.4.2
ports:
- "8091:8091"
environment:
- SEATA_IP=your_server_ip
- SEATA_PORT=8091
- STORE_MODE=db
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=seata
- DB_PASSWORD=seata
depends_on:
- mysql
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=seata
- MYSQL_USER=seata
- MYSQL_PASSWORD=seata
关键配置说明:
- STORE_MODE支持file/db/redis,生产环境建议db
- 集群部署时需要配置SEATA_IP为真实IP(不能是127.0.0.1)
- 高可用方案需要配合注册中心(nacos、eureka等)
4.2 客户端配置优化
client.properties中的关键参数:
properties复制# RM配置
client.rm.async.commit.buffer.limit=10000 # 异步提交缓冲区大小
client.rm.lock.retry.internal=10 # 全局锁重试间隔(ms)
client.rm.lock.retry.times=30 # 全局锁重试次数
# TM配置
client.tm.commit.retry.count=5 # 提交重试次数
client.tm.rollback.retry.count=5 # 回滚重试次数
# 日志配置
client.log.exceptionRate=100 # 异常日志采样率
我们在千万级交易系统中验证过的优化方案:
- 对于高频小事务,适当增大async.commit.buffer.limit
- 竞争激烈的场景增加lock.retry.times并减小internal
- 网络不稳定的环境提高commit/rollback重试次数
5. 常见问题排查手册
5.1 事务不生效排查步骤
- 检查XID是否传递:通过日志搜索"RootContext.getXID()"
- 验证代理是否生效:检查Bean是否被SeataProxy增强
- 查看TC日志:搜索全局事务ID确认状态流转
- 检查undo_log表:确认分支事务记录是否存在
5.2 性能问题优化指南
我们压测过程中发现的性能瓶颈及解决方案:
| 场景 | TPS | 瓶颈点 | 优化措施 |
|---|---|---|---|
| 短事务 | 1200 | 全局锁获取 | 使用@GlobalLock替代@GlobalTransactional |
| 长事务 | 300 | TC内存占用 | 调整server.session.branch.async.queue.size |
| 大事务 | 150 | undo_log写入 | 拆分大事务为多个小事务 |
5.3 监控与告警配置
建议监控以下关键指标:
- TC内存使用率(超过70%需要告警)
- 全局事务平均耗时(超过1s需要关注)
- 分支事务失败率(超过1%需要排查)
- 全局锁竞争次数(持续增长需要扩容)
通过Prometheus配置的示例规则:
yaml复制groups:
- name: seata-alerts
rules:
- alert: HighTCMemoryUsage
expr: process_resident_memory_bytes / process_memory_limit_bytes > 0.7
for: 5m
labels:
severity: warning
annotations:
summary: "Seata TC memory usage high (instance {{ $labels.instance }})"
阅读Seata源码最大的收获是理解了分布式事务没有银弹这个事实。在实际项目中,我们最终采用了混合模式:核心资金链路用TCC保证强一致,普通订单用AT提升性能,对账系统用Saga实现最终一致。这种灵活应对不同场景的能力,正是Seata设计的精妙之处。
