1. 分布式事务的痛点与挑战
在微服务架构中,"钱不能算错,库存不能扣重复"这句话道出了分布式事务最核心的业务诉求。我经历过一个电商项目,用户支付成功后库存却没扣减,直接导致超卖事故。这种场景下,传统的ACID事务在跨服务调用时完全失效。
SpringCloud环境下,订单服务、支付服务、库存服务各自独立部署,一次购买行为需要跨三个服务完成操作。CAP定理告诉我们,在分布式系统中无法同时满足一致性、可用性和分区容错性。这时候就需要引入分布式事务解决方案。
2. 主流分布式事务方案对比
2.1 两阶段提交(2PC)
典型的XA协议实现,包含准备阶段和提交阶段。MySQL的XA事务就是典型代表。但存在同步阻塞问题,协调者单点故障会导致整个系统卡死。曾经有个金融项目使用2PC,协调者宕机后需要人工介入处理悬挂事务,运维成本极高。
2.2 TCC模式
Try-Confirm-Cancel三阶段设计,需要业务层实现补偿逻辑。比如库存服务的try阶段是预扣库存,confirm是正式扣减,cancel是恢复预扣。某跨境电商平台采用TCC后,异常订单率从5%降到0.3%,但开发成本增加了40%。
2.3 本地消息表
最轻量级的方案,通过本地事务+异步消息保证最终一致。我们在物流系统中用这种方式处理运单状态同步,关键是要处理好消息幂等和防丢失。有个坑是MySQL事务隔离级别设为READ-COMMITTED时可能出现消息重复。
2.4 Saga模式
长事务拆分为多个本地事务,每个事务都有对应的补偿操作。适合业务流程长的场景,比如保险理赔。但要注意设计好逆操作,某次系统升级漏了一个补偿接口,导致数据回滚不彻底。
3. Seata实战:AT模式详解
3.1 整体架构设计
我们最终选择Seata的AT模式,主要考虑点是:
- 对业务代码侵入小(只需@GlobalTransactional注解)
- 支持SpringCloud Alibaba生态
- 社区活跃度高
架构包含三个核心组件:
- TC(Transaction Coordinator):事务协调器,独立部署
- TM(Transaction Manager):事务发起方
- RM(Resource Manager):事务参与方
3.2 关键配置项
yaml复制seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
特别注意:
- tx-service-group需要与nacos配置一致
- 生产环境建议TC集群部署
- 客户端需要配置undo_log表
3.3 事务隔离级别处理
Seata默认是读未提交隔离级别,通过全局锁实现脏读防护。在商品秒杀场景下,我们额外增加了Redis分布式锁来防止超卖:
java复制@GlobalTransactional
public void placeOrder(Long productId) {
String lockKey = "product:" + productId;
try {
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("当前商品正在被抢购");
}
// 扣减库存等业务逻辑
} finally {
redisTemplate.delete(lockKey);
}
}
4. 生产环境踩坑实录
4.1 事务悬挂问题
现象:全局事务已回滚,但分支事务提交成功。原因是TC在Phase2收到回滚指令时,RM已经超时提交。
解决方案:
- 调整客户端超时时间:
client.tm.commit.retry.count=5 - 增加事务状态检查任务
4.2 连接池耗尽
高并发时出现JDBC连接不够用,因为Seata代理数据源会占用额外连接。最终方案:
- 增加Druid最大连接数
- 设置
seata.max.commit.retry.timeout=1000减少重试占用时间
4.3 性能优化技巧
- 关闭不必要的SQL解析:
client.support.spring.datasource.autoproxy=false - 批量操作使用@GlobalLock而非@GlobalTransactional
- UNDO_LOG表单独放在高性能SSD磁盘
5. 监控与运维方案
5.1 监控指标配置
prometheus复制seata_transaction{role="tc",status="active"} 当前活跃事务数
seata_transaction{role="tc",status="committed"} 已提交事务数
seata_transaction{role="tc",status="rollbacked"} 已回滚事务数
配合Grafana看板可以清晰掌握事务健康度。某次大促前,我们通过监控发现事务成功率突然降到90%,及时排查出是Nacos配置被误改。
5.2 日志分析要点
重点关注三类日志:
- TC日志:搜索"GlobalSession["开头的行
- RM日志:搜索"BranchTransaction"关键词
- 业务日志:结合traceId串联全链路
我们开发了一个日志分析脚本自动统计事务成功率:
python复制# 分析seata-server.log中的事务成功率
def analyze_success_rate():
committed = count_logs('commit succeeded')
rollbacked = count_logs('rollback succeeded')
return committed / (committed + rollbacked)
6. 容灾与降级方案
6.1 TC集群部署
采用3节点集群,使用Nacos持久化存储:
shell复制# 启动参数示例
sh seata-server.sh -p 8091 -h 192.168.1.101 -m db -n 1
sh seata-server.sh -p 8092 -h 192.168.1.102 -m db -n 2
sh seata-server.sh -p 8093 -h 192.168.1.103 -m db -n 3
6.2 降级策略配置
在application.yml中设置降级阈值:
yaml复制seata:
disable-global-transaction: true # 手动开关
degrade:
check-period: 2000
check-times: 5
allow-times: 3
当TC不可用时,系统会自动降级为本地事务,并记录异常日志。我们在支付回调等关键路径上额外增加了对账job保证最终一致。
7. 最佳实践总结
经过多个项目实践,我们梳理出以下黄金准则:
- 事务粒度控制:单个@GlobalTransactional内不超过3个RPC调用
- 超时设置:全局事务超时建议5-10秒,分支事务2-5秒
- 异常处理:RuntimeException会触发回滚,业务异常需要显式标记
- 幂等设计:所有服务接口必须实现幂等,建议使用业务唯一键+状态机
- 压测标准:事务成功率>99.9%,TP99<500ms
在最近的双十一大促中,这套方案支撑了每秒3000+的订单创建量,事务成功率达到99.97%。关键是在预演阶段用JMeter模拟了各种异常场景,包括:
- 随机杀死TC节点
- 模拟网络分区
- 注入数据库延迟
- 强制触发分支事务冲突