一、什么是分布式事务?
在分布式系统中,当一个业务跨越多个服务或数据源时,每个服务作为一个分支事务。为了确保所有分支事务的最终状态一致,就需要使用分布式事务。
场景案例:电商下单流程涉及订单、库存、账户3个服务,需保证:
- 订单创建成功
- 库存扣减成功
- 账户扣款成功
任一服务失败,所有操作必须回滚——这就是分布式事务的核心需求。
二、分布式事务核心理论
CAP定理
1998年,加州大学的计算机科学家Eric Brewer提出,分布式系统有三个指标:Consistency ( 一致性)、Availability (可用性)、Partition tolerance (分区容错性)
Eric Brewer说,分布式系统无法同时满足这三个指标。这个结论就叫做CAP定理。
特性 | 说明 | 生活类比 |
---|---|---|
C一致性 | 所有节点数据实时一致 | 银行ATM机与柜台余额一致 |
A可用性 | 任何请求都能快速响应 | 医院急诊科24小时接诊 |
P分区容错性 | 网络故障时系统仍能运行 | 地铁某线路故障,其他线路照常 |
总结:
分布式系统通过网络连接,一定会出现分区问题( P);当分区出现时,系统的一致性(C)和可用性(A)就无法满足
现实选择:
- CP模式(如银行系统):宁可暂时不可用,也要保证数据绝对一致
- AP模式(如电商库存):允许短暂库存超卖,优先保证服务可用
思考:elasticsearch集群是CP还是AP?
ES集群出现分区时,故障节点会被剔除集群,数据分片会重新分配到其它节点,保证数据一致。因此是低可用性,高一致性,属于CP
BASE理论: 柔性事务解决方案
BASE 理论是对 CAP 的补充,强调:
- Basically Available (基本可用) :允许在故障时部分损失可用性,即保证核心可用(如双11期间关闭评价功能)。
- Soft State (软状态) :在一定时间内,允许出现中间状态,比如临时的不一致状态(如"支付中"状态)。
- Eventually Consistent (最终一致性) :在软状态结束后,通过重试/补偿机制达到数据最终一致。
而分布式事务最大的问题是各个子事务的一致性
问题,因此可以借鉴CAP定理和BASE理论:
- AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现
最终一致
。 - CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于
弱可用
状态。
分布式事务模型
解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致,因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。
三、Seata架构全景图
初识Seata
Seata 是一个开源的分布式事务解决方案,由阿里巴巴和蚂蚁金服共同开发,旨在提供高性能和易用的分布式事务服务。
官网地址:https://seata.io/zh-cn/ 其中的文档、播客中提供了大量的使用说明、源码分析。
Seata事务管理中有三个核心角色:
- TC (Transaction Coordinator) -事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。类比交通指挥中心
- TM (Transaction Manager)-事务管理器:管理事务的范围和生命周期。开始全局事务、提交或回滚全局事务。类比项目总负责人
- RM (Resource Manager) -资源管理器:管理分支事务的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。类比各部门执行人员
Seata提供了四种不同的分布式事务解决方案:
- XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
- TCC模式:最终一致的分阶段事务模式,有业务侵入
- AT模式:最终一致的分阶段事务模式, 无业务侵入,也是Seata的默认模式
- SAGA模式:长事务模式,有业务侵入
四、实战演练
部署TC服务
参考资料:《seata的部署和集成》
-
准备数据库表
-
application.yml 做好seata的配置 注:tc也需要注册到nacos中
3. Docker部署
需要注意,要确保nacos、mysql都在同一网络hm-net中。如果某个容器不再这个网络中,可以参考下面的命令将某容器加入指定网络:
docker network connect [网络名] [容器名]
在虚拟机的/root目录执行下面的命令:
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.200.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2
微服务集成Seata
- 首先,引入seata相关依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
- 然后配置application.yml(所有参与同一个事务的微服务的都要配置), 让微服务通过注册中心找到
seata-tc-server
:
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址 #参考tc服务自己的registry.conf中的配置,包括:地址、namespace、group、 application-name 、cluster
type: nacos
nacos: # tc
server-addr: 127.0.0.1:8848
namespace : ""
group: DEFAULT_GROUP
application: seata-tc-server # tc服务在nacos中的服务名称
username: nacos
password: nacos
tx-service-group: seata-demo #事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: #事务组与TC服务cluster的映射关系
seata-demo: WH
报错1:Caused by: java.lang.IllegalArgumentException: applicationId: null, txServiceGroup: null-seata-service-group
原因:target目录classes目录下没有application.yml文件
解决:maven需要clean后重新编译
报错2:io.seata.common.exception.FrameworkException: can not register RM,err:can not connect to services-server.
原因:网络连接桥段问题
解决:启动时带上主机号 seata-server.bat -p 8091 -h 192.168.200.1
动手实践-XA模式
XA模式原理
XA规范是x/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持。
XA模式的优点
是什么?
- 事务的强一致性,满足ACID原则。
- 常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点
是什么?
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
实现XA模式
AT模式
AT模式原理
AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长
的缺陷。
简述AT模式与XA模式最大的区别是什么?
- XA模式一阶段不提交事务, 锁定资源;AT模式一阶段直接提交,不锁定资源。
- XA模式依赖
数据库机制
实现回滚;AT模式利用数据快照
实现数据回滚。 - XA模式强一致;AT模式最终一致
AT模式的脏写问题
AT模式的写隔离
非seata管理的全局事务发生异常情况
AT模式的优点
:
- 一阶段完成直接提交事务,释放数据库资源,性能比较好
- 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式的缺点
: - 两阶段之间属于软状态,属于最终一致
- 框架的快照功能会影响性能,但比XA模式要好很多
实现AT模式
undo_log表:
TCC模式
TCC模式原理
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
- Try:资源的检测和预留;
- Confirm: 完成资源操作业务;要求Try成功Confirm一定要能成功。
- Cancel:预留资源释放,可以理解为try的反向操作。
TCC的优点
是什么?
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比AT模型,无需生成快照,无需使用全局锁,性能最强
- 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点
是什么?
- 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
- 软状态,事务是最终一致
- 需要考虑Confirm和Cancel的失败情况,做好幂等处理
实现TCC模式
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void dedect(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money") Integer money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
@Override
@Transactional
public void dedect(String userId, Integer money) {
//0.获取事务id
String xid = RootContext.getXID();
//0.1 判断freeze中是否有冻结记录,如果有,一定是cancel执行过,需要拒绝业务,避免业务悬挂
AccountFreeze oldfreeze = freezeMapper.selectById(xid);
if (oldfreeze != null){
return;
}
//1.扣减可用余额
accountMapper.deduct(userId, money);
//2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freeze.setUserId(userId);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
//根据事务id,删除冻结金额
String xid = ctx.getXid();
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
//空回滚的判断,判断freeze是否为null,为null证明try没执行,需要空回滚
if (null == freeze){
freeze = new AccountFreeze();
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
freeze.setXid(xid);
freeze.setUserId(ctx.getActionContext("userId").toString());
freezeMapper.insert(freeze);
return true;
}
//判断幂等 已经处理过一次calcel了,无需重复处理,避免多次执行回滚操作
if (freeze.getState() == AccountFreeze.State.CANCEL){
return true;
}
//1.恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
//2.将冻结金额清0,状态改为cancel
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}
SAGA模式
Saga模式是SEATA提供的长事务解决方案。也分为两个阶段:
- 一阶段:直接提交本地事务
- 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
Saga模式优点
:
- 事务参与者可以基于事件驱动实现异步调用,吞吐高
- 一阶段直接提交事务,无锁,性能好
- 不用编写TCC中的三个阶段,实现简单
Saga模式 缺点
:
- 软状态持续时间不确定,时效性差
- 没有锁,没有事务隔离,会有脏写
4种模式对比与选型
五、常见坑点解决方案
1. 空回滚问题
现象:未执行Try却触发Cancel
解决:Cancel时检查冻结记录是否存在
public boolean cancel(BusinessActionContext ctx) {
if(freezeMapper.selectById(xid) == null) {
// 插入标记防止重复回滚
freezeMapper.insertEmptyRollback(xid);
return true;
}
// 正常回滚逻辑...
}
2. 幂等控制
方案:数据库唯一索引 + 状态机检查
CREATE TABLE undo_log (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
xid VARCHAR(100) NOT NULL UNIQUE,
status TINYINT NOT NULL
);
3. TC集群部署
# 多节点配置
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata-cluster
六、性能优化建议
- 异步化:将非核心操作(如发短信)异步处理
- 批量提交:合并多个分支事务的TC请求
- 热点数据优化:
@GlobalTransactional(timeoutMills = 60000)
public void batchProcess() {
// 分批处理数据
List<Data> partition = splitData();
partition.parallelStream().forEach(this::processSingle);
}
七、监控体系搭建
Seata监控看板
- Metrics监控:集成Prometheus收集事务指标
- 日志追踪:通过xid串联全链路日志
- 告警设置:
- 事务成功率低于99.9%
- 平均事务耗时超过500ms
八、总结
选型原则:
- 强一致选XA,常规业务用AT,
- 高并发用TCC,长流程选SAGA。
核心口诀:
- TC协调全局事,TM注解定边界,
- RM管资源,快照保回滚。
参考:黑马程序员视频笔记