分布式事物
一、背景:从用户下单场景看分布式事务痛点
- 在电商场景中,用户下单涉及多个关键操作:
- 1)账户服务
扣减余额
; - 2)库存服务
扣减库存
; - 3)订单服务
生成订单记录
。 - 这三个操作需要
强一致性
保证 ,必须保持原子性------要么全部成功,要么全部回滚
。若其中一个操作失败(如库存不足),必须保证所有操作回滚,否则会导致超卖或用户资金异常。 - 传统单体应用可以通过数据库事务保证,但在微服务架构下,这三个服务使用独立的数据库,形成典型的分布式事务难题。
- 1)账户服务
java
// 伪代码示例:存在数据不一致风险的原始代码
public void createOrder(OrderDTO order) {
// 扣减库存(库存服务)
stockFeignClient.deduct(order.getSkuId(), order.getNum());
// 扣减余额(账户服务)
accountFeignClient.deduct(order.getUserId(), order.getMoney());
// 创建订单(订单服务)
orderMapper.create(order);
}
关键矛盾点:
- 网络抖动可能导致
部分服务调用失败
- 服务宕机可能造成
"半提交"
状态 - 传统单机事务无法跨数据库保证
原子性
二、Seata架构核心原理
java
// 全局事务启动示例
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
......
}
三、分布式事物模型
- 1、子系统之间
必须能感知
到彼此的事物状态
,才能保证状态一致。 - 2、需要事物协调者TC(Transaction Coordinator) 来协调每一个事物的参与者(子系统事物)。
- 3、子系统事物,称为分支事物 ,关联各个分支事物在一起称为全局事物。
四、名词解释
- 全局事物 :
整个分布式事物
- 分支事物 :
分布式事物中包含的每个子系统事物
- 最终一致性 :各
分支事物
分别执行并提交
,如果有不一致
的情况,想办法补偿恢复
,达到数据最终一致性
。 - 强一致性:各事务执行完业务不要提交,等待彼此结束,之后统一提交或回滚
五、Seata分布式事务架构
事务管理三大核心组件协同
- TC(Transaction Coordinator):事物协调者 ,
维护全局
和分支事物的状态,协调全局
事物提交或回滚 - TM(Transaction Manager):事物管理者 ,
定义全局
事物的范围、开始全局
事物、提交或者回滚全局事物。 - RM(Resource Manager):资源管理者 ,
管理
分支事物处理的资源,与TC(事物协调者)交谈以注册分支事物和报告分支事物的状态,并驱动
分支事物提交或回滚。
组件 | 角色 | 典型实现 |
---|---|---|
TC | 事务协调者 | Seata Server |
TM | 事务管理器 | @GlobalTransactional注解 |
RM | 资源管理器 | 数据源代理 |
Seata分布式事务的基本模型的执行流程
- 1、TM(Transaction Manager 事务管理者) 会首先
注册全局事务
。 - 2、之后业务调用各个微服务 ,由各自的RM(Resource Manager 资源管理者) 向TC(Transaction Coordinator 事务协调者)
发起分支事务的注册
。 - 3、之后执行各个分支事务的sql ,执行完毕之后RM(Resource Manager 资源管理者) 会向TC(Transaction Manager 事务管理者)
报告分支事务的状态
。 - 4、所有分支事务执行完毕之后 ,TM(事务管理者) 向 TC(事务协调者)
发起提交或回滚全局事务
。 - 5、此时TC(事务协调者) 会
检查分支事务的状态
来决定是提交还是回滚
发送给RM(资源管理者)。
Seata四种不同分布式事物解决方案
-
XA模式 :强一致性分阶段事物模式 ,牺牲了一定的
可用性
,无业务侵入
。一般由数据库实现
。- XA模式原理 :XA规范是
X/Open
组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准 ,XA规范描述了全局的TM(事务管理者) 与局部的RM(资源管理者) 之间的接口,几乎所有主流的数据库都对XA规范提供了支持。
- 第一阶段 :
事物协调者(TC)
向资源管理者(RM)
发起事物准备请求
,资源管理者(RM)
执行完毕之后,并不直接提交事务
,而是将执行的结果告知事物协调者(TC)
。 - 第二阶段 :
事物协调者(TC)
判断资源管理者(RM)
的返回结果,如果分支事物都成功了,向资源管理者(RM)
发起提交请求,资源管理者(RM)
执行事物并返回已提交请求。 - 如果事物执行过程中有一个失败了,事物协调者会回滚所有已执行事物。
- Seata的XA模式实现原理 :
- 资源管理者(RM)一阶段工作
- 1、注册分支事务到
事务协调者(TC)
. - 2、执行分支事务
SQL但不提交
. - 3、报告执行状态到
事务协调者(TC)
.
- 1、注册分支事务到
- 事务协调者(TC)一阶段工作
- 1、TC检测各分支事物执行状态
- 都成功,
通知
所有资源管理器(RM)
提交事务。 - 有失败,
通知
所有资源管理器(RM)
回滚事务。
- 都成功,
- 1、TC检测各分支事物执行状态
- 资源管理者(RM)二阶段工作
- 接受
事物协调者(TC)
指令,提交或回滚事物。
- 接受
- 资源管理者(RM)一阶段工作
- XA模式总结
- 优点:
- 事物强一致性,满足ACID原则
- 常用数据库都支持,实现简单,没有代码侵入
- 缺点:
- 一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务。
- 优点:
- XA模式原理 :XA规范是
sql
/* 数据库层XA命令示例 */
XA START 'xid1';
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';
适用场景 :传统金融系统
、政府业务等
对一致性要求极高的场景
-
TCC模式:最终一致的分阶段事务模式,有业务侵入。
- TCC模式原理 :与AT模式非常相似,每阶段都是独立事物 ,不同的是
TCC通过人工编码
来实现数据恢复
。
- 需实现的三个方法
- Try(检查):资源的检测跟预留。
- Confirm(提交):完成资源操作业务。要求Try和Confirm一定要能成功。
- Cancel(回滚):预留资源释放,可以理解为Try的反向操作。
- 举例:一个扣减用户余额业务。账户A原有余额100,需要扣减30元。
- 一阶段(Try):检查余额是否充足,如充足则冻结金额加30元,用户余额扣减30元。
- 二阶段(Confirm):如需提交,冻结金额扣减30元。
- 三阶段(Cancel):如需回滚,冻结金额扣减30元,可用余额增加30元。
- TCC模式总结
- 每个阶段是做啥?
Try:资源检查跟预留
。Confirm:业务执行和提交
。Cancel:预留资源的释放
。
- 优点:
- 一阶段完成直接提交事务,释放数据库资源,性能好。
- 相比AT模型,无需生成快照,无需使用全局锁,性能最强。
- 不依赖数据库事物,而是依赖补偿操作,可以用于非事务性数据库。
- 缺点:
- 有代码侵入,需要人为编写Try、Confirm和Cancel接口。
- 软状态,事物是最终一致。
- 需要考虑Confirm和Cancel失败的情况,做好幂等处理。
- 每个阶段是做啥?
- TCC模式原理 :与AT模式非常相似,每阶段都是独立事物 ,不同的是
java
// TCC接口定义示例
public interface AccountTccService {
@TwoPhaseBusinessAction(name = "deduct",
commitMethod = "confirm",
rollbackMethod = "cancel")
boolean deduct(BusinessActionContext context,
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "money") BigDecimal money);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
// Try阶段实现
public boolean deduct(...) {
// 检查余额
// 冻结资金(生成冻结记录)
}
// Cancel补偿示例
public boolean cancel(BusinessActionContext context) {
Long userId = context.getActionContext("userId");
BigDecimal money = context.getActionContext("money");
// 解冻资金 + 返还余额
}
开发建议:
- 做好
空回滚
和幂等处理
。 - 冻结记录表需要
包含xid等事务信息
。 - 设置
合理的重试机制
。
-
AT模式:最终一致的分阶段事物模式,无业务侵入,也是
Seata默认模式
- AT模式原理 :
- 同样是
分阶段提交的事物模型
,但弥补了XA模型中资源锁定周期过长
的缺陷。 - 在执行完SQL 之后会
直接提交事务
,而不是进行等待。 - 在执行的同时
资源管理器(RM)
拦截本次执行,记录更新前后的快照到数据库的undo_log中。
- 同样是
- Seata的AT模式实现原理 :
- 资源管理者(RM)一阶段工作 :
- 1、注册
分支事物
。 - 2、记录
undo_log(数据快照)
。 - 3、执行
业务SQL
并提交。 - 4、报告
事务状态
。
- 1、注册
- 资源管理器(RM)二阶段提交的工作 :
- 删除undo_log即可。
- 资源管理器(RM)二阶段回滚的工作 :
- 根据undo_log恢复数据到更新前。
- 资源管理者(RM)一阶段工作 :
- AT模式总结,与XA模式最大区别是
- XA模式一阶段不提交事物,
锁定资源
。AT模式一阶段直接提交,不锁定资源
- XA模式
依赖数据库机制实现回滚
,AT模式利用数据快照实现数据回滚
。 - XA模式
强一致
。AT模式最终一致
- 优点:
- 1、一阶段完成直接提交事务,释放数据库资源,性能比较好。
- 2、利用全局锁实现读写隔离。
- 3、没有代码侵入,框架自动完成回滚跟提交。
- 缺点:
- 1、两阶段之间属于软状态,属于最终一致。
- 2、框架快照功能会影响性能,但比XA模式好很多。
- XA模式一阶段不提交事物,
- AT模式原理 :
sql
-- undo_log表示例结构
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`)
);
执行过程示例:
java
// 原始业务代码无需修改
public void deduct(Long userId, BigDecimal money) {
accountMapper.deduct(userId, money);
}
// MyBatis Mapper
@Update("UPDATE account SET balance = balance - #{money} WHERE user_id = #{userId}")
void deduct(@Param("userId") Long userId, @Param("money") BigDecimal money);
注意事项:
- 需要
开启数据源代理
避免全局锁冲突
导致的性能问题- 快照存储可能
带来额外存储开销
- SAGA模式:长事务模式,有业务侵入。
-
SAGA原理 :业务流程中
每个参与者
都提交本地事务
,当出现某一个参与者
失败则补偿前面已经成功的参与者 ,一阶段正向服务和二阶段补偿服务都由业务开发实现。 -
两个阶段:
- 一阶段 :
直接提交
本地事务(TCC是预留) - 二阶段 :
成功
则什么都不做,失败
则通过编写补偿业务
来回滚。
- 一阶段 :
-
优点:
- 1、事物参与者可以基于事件驱动实现异步调用,吞吐高。
- 2、一阶段直接提交事务,无锁,性能好。
- 3、不用编写TCC的三个阶段,实现简单。
-
缺点:
- 软状态持续时间不确定,时效性差。
- 没有锁,没有事物隔离,会有脏写。
-
java
// 状态机配置示例
StateMachineBuilder<State, Event> builder = StateMachineBuilderFactory.create();
builder.configureStates()
.withStates()
.initial(State.INIT)
.state(State.INVENTORY_DEDUCT)
.state(State.ACCOUNT_DEDUCT)
.state(State.ORDER_CREATE);
builder.configureTransitions()
.withExternal()
.source(State.INIT).target(State.INVENTORY_DEDUCT)
.event(Event.DEDUCT_INVENTORY)
.action(deductInventoryAction)
.withExternal()
.source(State.INVENTORY_DEDUCT).target(State.ACCOUNT_DEDUCT)
.event(Event.DEDUCT_ACCOUNT)
.action(deductAccountAction)
.withExternal()
.source(State.ACCOUNT_DEDUCT).target(State.ORDER_CREATE)
.event(Event.CREATE_ORDER)
.action(createOrderAction)
.withExternal()
.source(State.ORDER_CREATE).target(State.SUCCESS)
.event(Event.SUCCESS);
// 补偿动作示例
builder.configureTransitions()
.withExternal()
.source(State.INVENTORY_DEDUCT).target(State.FAILURE)
.event(Event.FAILURE)
.action(compensateInventoryAction);
最佳实践:
- 为每个事务步骤
定义明确的补偿操作
设计幂等
的补偿接口
- 采用
事件驱动
架构提升吞吐量
六、四种模式对比
维度\模式 | XA | TCC | AT | SAGA |
---|---|---|---|---|
一致性 | 强一致 | 弱一致 | 弱一致 | 最终一致 |
隔离性 | 完全隔离 | 基于资源预留隔离 | 基于全局锁隔离 | 无隔离 |
侵入性 | 无 | 有,需要编写3个接口 | 无 | 有,需要编写状态机和补偿业务 |
数据库要求 | 关系型 | 任意 | 关系型 | 任意 |
性能 | 低 | 高 | 中 | 高 |
适用场景 | 对一致性、隔离性有高要求的业务,短事务 | 对性能要求较高的事务;有非关系型数据库要参与的事务,核心交易 | 基于关系型数据库的大多数分布式事务场景都可以,普通交易 | 业务流程长、业务流程多;参与者包含其它公司或遗留系统服务,无法提供TCC模式要求的三个接口,长事务 |
典型响应时间 | 100ms+ | 50ms+ | 80ms+ | 200ms+ |
开发复杂度 | 低 | 高 | 低 | 中 |
七、实战总结
- 黄金法则 :能不用分布式事务就不用,优先考虑业务拆分
- 降级策略 :在事务执行失败 时提供友好提示 和人工干预通道
- 监控要点 :
- 事务成功率
- 平均处理时间
- 资源锁竞争情况
- 死锁检测
yml
// 生产环境配置示例(Seata 1.5+)
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
避坑指南:
- 避免在事务中处理RPC文件操作
- 合理设置事务超时时间(建议3-8秒)
- 对补偿操作进行压力测试
- 做好分布式事务日志的归档分析
合理选择事务模式并配合完善的监控体系,可以有效构建高可靠的分布式系统。从AT模式入手,在特殊场景下结合TCC/SAGA方案,形成完整的事务解决方案。