在微服务架构成为主流的今天,一个业务请求往往需要跨多个服务完成数据更新。去年我们电商系统拆分微服务时就遇到经典案例:用户支付成功后需要同时更新订单状态、扣减库存、增加会员积分。这三个操作分别属于订单服务、库存服务和会员服务,如何保证它们要么全部成功,要么全部回滚?这就是分布式事务要解决的核心问题。
传统XA协议采用两阶段提交(2PC)虽然能保证强一致性,但存在性能低下(全局锁阻塞)、协议侵入性强(需要数据库支持XA)等问题。而Alibaba开源的Seata(Simple Extensible Autonomous Transaction Architecture)通过AT模式创新性地解决了这些问题,目前已成为国内分布式事务的事实标准。我在金融支付和物流系统中多次实践验证,Seata在保证一致性的同时,性能损耗可控制在5%以内。
Transaction Coordinator(TC)是Seata的大脑,负责维护全局事务状态。实际部署时建议独立部署TC服务,我们生产环境采用3节点集群保证高可用。关键配置参数包括:
properties复制# 事务日志存储模式(推荐db模式)
store.mode=db
# 全局锁重试间隔(默认10ms)
server.lock.retryInterval=10
# 全局锁重试次数(默认30次)
server.lock.retryTimes=30
Transaction Manager(TM)定义事务边界,通过@GlobalTransactional注解开启全局事务。特别注意注解的rollbackFor属性需要明确指定异常类型:
java复制@GlobalTransactional(
name = "createOrder",
rollbackFor = {Exception.class},
timeoutMills = 60000
)
public void createOrder(OrderDTO order) {
// 跨服务调用
}
Resource Manager(RM)负责分支事务注册和状态报告。在AT模式下,RM会通过代理数据源自动生成undo_log记录。关键实现原理是:
与传统2PC不同,Seata AT模式的一阶段就会提交本地事务,这得益于undo_log机制。我们通过压测发现,undo_log表需要特殊优化:
sql复制CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
KEY `idx_log_created` (`log_created`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
当TC检测到全局事务需要回滚时,会异步通知各RM执行补偿。补偿过程是:
重要提示:undo_log表必须与业务数据在同一个库,否则无法保证本地事务原子性
我们采用的集群部署架构:
code复制 +-----------+
| Nginx LB |
+-----+-----+
|
+---------------+---------------+
| | |
+--------+-------+ +-----+--------+ +----+--------+
| Seata-Server1 | | Seata-Server2 | | Seata-Server3 |
+----------------+ +---------------+ +--------------+
| | |
+-------+-------+-------+
| |
+------+------+ +-----+------+
| MySQL M | | MySQL S |
| (主库) | | (从库) |
+-------------+ +------------+
在金融场景下我们调整的关键参数:
properties复制# TC处理线程数(建议CPU核数*2)
server.executorThreadSize=16
# RM客户端连接池大小
client.rm.asynccommit.bufferlimit=10000
# 全局锁检查间隔(毫秒)
server.max.commit.retry.timeout=1000
现象:业务日志出现"Global lock wait timeout"错误
解决方案:
现象:事务回滚后数据状态不一致
排查步骤:
在物流系统中我们对比了多种方案:
| 方案 | 一致性 | 性能 | 侵入性 | 适用场景 |
|---|---|---|---|---|
| Seata AT | 强一致 | 高 | 中 | 大部分业务场景 |
| TCC | 强一致 | 中 | 高 | 资金类核心交易 |
| SAGA | 最终 | 高 | 高 | 长流程业务 |
| 本地消息表 | 最终 | 中 | 低 | 非核心业务 |
实际选择时需要权衡业务特性。对于订单创建这类需要强一致但非高频的场景,Seata AT是最佳选择。而在秒杀库存扣减场景,我们改用TCC模式避免长事务阻塞。