Seata AT(Auto Transaction)模式
一、AT 模式的核心原理
Seata AT 模式是基于 两阶段提交(2PC) 优化的分布式事务解决方案,通过 数据源代理 和 全局锁 实现事务的自动提交与回滚,对业务代码低侵入。其核心思想是:
- 一阶段:执行业务 SQL 并生成回滚日志(
undo_log
),提交本地事务。 - 二阶段:
- 提交:异步删除回滚日志。
- 回滚:根据
undo_log
生成反向 SQL 恢复数据。
二、AT 模式的工作流程
1. 一阶段(本地事务提交)
- 解析 SQL:Seata 代理数据源,解析业务 SQL(如
UPDATE product SET stock = stock - 1 WHERE id = 1
)。 - 生成回滚日志:记录数据修改前的快照(
before image
)到undo_log
表。 - 提交本地事务:业务 SQL 和
undo_log
在同一本地事务中提交。
undo_log
表示例:
sql
CREATE TABLE undo_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
branch_id BIGINT NOT NULL, -- 分支事务 ID
xid VARCHAR(100) NOT NULL, -- 全局事务 ID
context VARCHAR(128) NOT NULL, -- 上下文(数据源、序列化格式)
rollback_info LONGBLOB NOT NULL, -- 回滚信息(before/after image)
log_status INT NOT NULL, -- 日志状态(0:正常,1:已回滚)
log_created TIMESTAMP NOT NULL,
log_modified TIMESTAMP NOT NULL
);
2. 二阶段(全局事务提交/回滚)
提交阶段:
全局事务成功 → 异步删除所有参与者的undo_log
记录。回滚阶段:
全局事务失败 → 根据undo_log
生成反向 SQL(如UPDATE product SET stock = stock + 1 WHERE id = 1
)并执行,恢复数据。
三、AT 模式的关键机制
1. 全局锁(Global Lock)
- 作用:防止其他事务修改已被 AT 事务锁定的数据(避免脏写)。
- 实现:
- 在一阶段提交前,Seata 向 TC(事务协调器)申请全局锁。
- 若其他事务尝试修改同一数据,需等待锁释放。
2. 数据快照(Snapshot)
before image
:记录 SQL 执行前的数据状态,用于回滚。after image
:记录 SQL 执行后的数据状态,用于二阶段提交后的一致性校验(可选)。
3. 隔离级别
- 默认读未提交:AT 模式不保证全局隔离性,可能出现脏读。
- 读隔离增强:通过
SELECT FOR UPDATE
获取全局锁,实现读已提交。
四、AT 模式的适用场景
场景特征 | 说明 |
---|---|
中低并发 | 全局锁可能成为高并发场景的性能瓶颈 |
简单业务逻辑 | 适合单库多服务事务,避免跨复杂业务链路的协调问题 |
低侵入性需求 | 无需修改业务代码,仅需引入 Seata 依赖并配置数据源代理 |
支持本地事务的数据库 | 如 MySQL、Oracle、PostgreSQL 等(依赖数据库的本地事务能力) |
五、AT 模式的优缺点
优点 | 缺点 |
---|---|
低代码侵入:无需业务层改造 | 全局锁性能损耗:高并发下锁竞争影响吞吐量 |
自动回滚:基于 undo_log 实现反向补偿 | 脏读风险:默认不保证全局隔离性 |
支持多数据源:统一代理管理 | 依赖数据库能力:需支持本地事务和行锁 |
六、AT 模式实战示例
1. 配置 Seata
- 引入依赖(Spring Boot):xml
<dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.7.0</version> </dependency>
- 配置文件(
application.yml
):yamlseata: 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 config: type: nacos nacos: server-addr: 127.0.0.1:8848
2. 业务代码示例
java
@GlobalTransactional // 开启全局事务
public void createOrder(Order order) {
// 1. 扣减库存
productService.reduceStock(order.getProductId(), order.getCount());
// 2. 创建订单
orderMapper.insert(order);
// 3. 扣款
accountService.debit(order.getUserId(), order.getAmount());
}
七、常见问题与解决方案
1. 全局锁冲突
- 现象:高并发下事务等待超时(
Global lock wait timeout
)。 - 解决:
- 优化 SQL 性能,减少锁持有时间。
- 使用 TCC 模式替代 AT 模式(避免全局锁)。
2. 回滚失败
- 原因:
undo_log
丢失或反向 SQL 执行异常。 - 解决:
- 确保
undo_log
与业务数据在同一本地事务中提交。 - 监控告警并人工介入处理异常事务。
- 确保
3. 跨服务数据源问题
- 限制:AT 模式不支持跨服务的同一数据库实例操作。
- 解决:改用 TCC 模式或 Saga 模式。
八、总结
- 核心价值:AT 模式通过数据源代理和自动补偿机制,以低侵入方式实现分布式事务,适合传统项目改造。
- 使用建议:
- 避免在高并发热点数据场景使用 AT 模式(如秒杀库存扣减)。
- 结合 Seata 控制台(TC)监控事务状态,及时处理异常。
- 在需要更高性能的场景,评估切换为 TCC 或 Saga 模式。
最佳实践:
- 确保所有参与者服务使用相同的事务分组(
tx-service-group
)。 - 对关键业务表添加索引,优化
undo_log
查询效率。 - 定期清理已完成的
undo_log
记录(通过 Seata 内置任务或自定义脚本)。
TCC 事务
以下是 TCC(Try-Confirm-Cancel)分布式事务 的详细解析,涵盖核心原理、实现流程、关键问题及最佳实践:
一、TCC 的核心思想
TCC 是一种 业务层补偿型事务,通过 预留资源 和 补偿机制 保证分布式事务的最终一致性。其核心思想是将事务拆分为三个阶段:
- Try(尝试):预留资源,完成业务检查(如冻结库存、预扣款)。
- Confirm(确认):提交资源,完成业务操作(如实际扣减库存、完成支付)。
- Cancel(取消):释放预留资源,回滚 Try 阶段的操作(如解冻库存、退款)。
二、TCC 的适用场景
- 强一致性要求:如金融交易、库存扣减等不允许最终一致性的场景。
- 跨服务调用:涉及多个服务的业务操作(如订单创建 → 支付 → 物流)。
- 高并发场景:通过资源预留避免全局锁,提升吞吐量。
三、TCC 的执行流程
1. 正常流程(Try → Confirm)
2. 异常流程(Try → Cancel)
四、TCC 的关键问题与解决方案
1. 空回滚(Cancel 未执行 Try)
- 场景:Try 阶段未执行(如超时),但 Cancel 被触发。
- 解决:
- 在 Cancel 操作中检查 Try 是否已执行(通过事务日志)。
- 若未执行 Try,直接返回成功,避免误补偿。
2. 悬挂(Try 在 Cancel 之后到达)
- 场景:Try 请求因网络延迟在 Cancel 之后到达,导致资源被锁定。
- 解决:
- 在 Try 阶段检查事务状态,若已回滚则拒绝执行。
- 通过事务日志记录状态(如
init
→trying
→confirmed/cancelled
)。
3. 幂等性
- 要求:每个阶段的接口需支持多次调用(如网络重试)。
- 实现:
- 通过 事务 ID 唯一标识操作,记录已处理的事务状态。
- 使用数据库唯一索引或 Redis 原子操作去重。
五、TCC 的实现步骤
1. 定义 TCC 接口
每个参与者需实现 Try、Confirm、Cancel 接口:
java
public interface TccOrderService {
@Transactional
boolean tryCreateOrder(String orderId, BigDecimal amount);
@Transactional
boolean confirmCreateOrder(String orderId);
@Transactional
boolean cancelCreateOrder(String orderId);
}
2. 事务日志记录
- 记录事务状态:在 Try 阶段插入事务日志,标记为
trying
。 - 更新状态:Confirm/Cancel 阶段更新日志为
confirmed
或cancelled
。
sql
CREATE TABLE tcc_transaction (
tx_id VARCHAR(64) PRIMARY KEY, -- 事务 ID
status VARCHAR(20), -- 状态(trying/confirmed/cancelled)
create_time TIMESTAMP
);
3. 事务协调器(Coordinator)
- 职责:
- 发起全局事务,调用各参与者的 Try 方法。
- 根据 Try 结果决定提交(Confirm)或回滚(Cancel)。
- 重试失败的操作(需保证幂等性)。
六、最佳实践
1. 设计原则
- 资源预留轻量化:Try 阶段仅冻结必要资源,避免过度占用。
- 快速失败:在 Try 阶段完成所有业务检查,减少 Confirm 阶段失败概率。
- 超时机制:设置事务超时时间,避免资源长期锁定。
2. 事务监控与恢复
- 监控:跟踪事务状态(如
trying
状态的未完成事务)。 - 恢复:定时任务扫描超时事务,触发补偿(Cancel)操作。
3. 案例:电商下单流程
- Try 阶段:
- 订单服务:生成订单(状态为
预创建
)。 - 库存服务:冻结商品库存。
- 支付服务:预扣用户余额。
- 订单服务:生成订单(状态为
- Confirm 阶段:
- 订单服务:更新订单状态为
已创建
。 - 库存服务:扣减冻结库存。
- 支付服务:完成扣款。
- 订单服务:更新订单状态为
- Cancel 阶段:
- 订单服务:删除预创建订单。
- 库存服务:解冻库存。
- 支付服务:返还预扣余额。
七、TCC 的优缺点
优点 | 缺点 |
---|---|
无全局锁,高并发 | 代码侵入性高,需实现补偿逻辑 |
业务灵活,可自定义补偿逻辑 | 需处理悬挂、空回滚等边界问题 |
避免资源长期占用 | 事务日志管理复杂 |
八、与其他方案的对比
方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
---|---|---|---|---|
TCC | 强一致性 | 高 | 高 | 金融交易、库存扣减 |
Seata AT | 强一致性 | 中 | 低 | 简单跨服务事务 |
消息队列 | 最终一致性 | 高 | 中 | 异步通知、数据同步 |
九、总结
- 核心价值:TCC 通过业务层补偿机制,在无全局锁的情况下实现强一致性,适合高并发、高可靠场景。
- 关键挑战:需处理幂等性、悬挂、空回滚等问题,并保证事务日志的可靠性。
- 实施建议:
- 使用框架(如 ByteTCC、Seata TCC 模式)减少编码量。
- 结合熔断、限流机制提升系统容错能力。
- 通过压测验证事务恢复机制的稳定性。
RocketMQ 的事务消息
RocketMQ 的事务消息是一种确保消息发送与本地事务执行具备原子性的机制,适用于需要保证业务操作与消息投递一致性的场景(如支付成功后通知发货)。以下是其核心原理、实现流程及最佳实践的详细介绍:
一、事务消息的核心目标
解决 “本地事务执行” 与 “消息发送” 的原子性问题,避免以下问题:
- 消息发送成功,本地事务失败:消费者收到消息但业务未实际完成。
- 本地事务成功,消息发送失败:业务已提交但下游服务未感知。
二、事务消息的工作流程
RocketMQ 事务消息通过 两阶段提交(半消息 + 事务状态确认) 实现:
1. 第一阶段:发送半消息(Half Message)
- Producer 向 Broker 发送半消息(对 Consumer 不可见)。
- Broker 存储半消息,但不会将其投递到目标 Topic。
- 代码示例:java
TransactionMQProducer producer = new TransactionMQProducer("group"); producer.sendMessageInTransaction(msg, null); // 发送半消息
2. 第二阶段:执行本地事务
- Producer 执行本地数据库事务(如订单状态更新)。java
@Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { try { // 执行本地事务(如更新订单状态) boolean success = orderService.updateOrderStatus(msg.getKeys()); return success ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE; } catch (Exception e) { return LocalTransactionState.UNKNOW; } }
3. 第三阶段:提交或回滚事务消息
- Broker 根据 Producer 返回的事务状态处理半消息:
- COMMIT_MESSAGE:将消息投递到目标 Topic,Consumer 可见。
- ROLLBACK_MESSAGE:丢弃半消息。
- UNKNOW:触发 事务状态回查(Broker 主动询问 Producer 事务结果)。
4. 事务状态回查(Transaction Check)
- 触发条件:Producer 未明确提交或回滚(如网络超时、服务宕机)。
- Broker 定时回调 Producer 的
checkLocalTransaction
方法,获取事务最终状态。java@Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { // 根据消息 ID 查询本地事务状态(如检查订单是否已更新) OrderStatus status = orderService.getStatusByMsgId(msg.getTransactionId()); return status.isSuccess() ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE; }
三、事务消息的关键机制
1. 消息存储与重试
- 半消息存储:半消息存储在
RMQ_SYS_TRANS_HALF_TOPIC
队列,不暴露给 Consumer。 - 回查重试:Broker 默认每 1 分钟回查一次,最多 15 次(可配置)。
2. 消息恢复
- 超时未确认的消息:若 Producer 未响应回查,Broker 在超时(默认 7 天)后自动回滚消息。
3. 幂等性处理
- Producer 端:通过事务 ID(
transactionId
)避免重复提交。 - Consumer 端:需自行实现幂等(如数据库唯一键、Redis 原子操作)。
四、使用场景
1. 订单支付后通知发货
- 本地事务:支付系统更新订单状态为“已支付”。
- 消息投递:支付成功后发送“发货通知”到物流系统。
2. 用户注册后发送优惠券
- 本地事务:注册系统写入用户表。
- 消息投递:注册成功后发送“发放优惠券”消息到营销系统。
3. 跨系统数据同步
- 本地事务:主系统更新业务数据。
- 消息投递:同步数据变更到其他子系统(如缓存、搜索引擎)。
五、最佳实践
1. 事务消息配置
- Producer 配置:java
TransactionMQProducer producer = new TransactionMQProducer("group"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.setTransactionListener(new YourTransactionListener()); // 设置事务监听器 producer.start();
- Broker 配置(
broker.conf
):propertiestransactionTimeout=60000 # 事务回查超时时间(毫秒) transactionCheckMax=15 # 最大回查次数
2. 避免消息堆积
- 缩短事务执行时间:确保本地事务快速完成,减少回查概率。
- 优化回查逻辑:在
checkLocalTransaction
中高效查询事务状态。
3. 异常处理
- 本地事务失败:返回
ROLLBACK_MESSAGE
,Broker 丢弃消息。 - 事务状态未知:记录日志并告警,人工介入处理。
4. 消费者幂等设计
- 唯一业务 ID:消息体中包含唯一标识(如订单 ID)。
- 去重表:在消费前检查该 ID 是否已处理。sql
CREATE TABLE msg_idempotent ( id VARCHAR(64) PRIMARY KEY, status INT );
六、常见问题与解决方案
1. 消息重复消费
- 原因:网络重发或事务回查导致消息多次投递。
- 解决:消费者端实现幂等性(如数据库唯一约束)。
2. 事务回查失败
- 原因:Producer 宕机或查询接口异常。
- 解决:监控回查失败次数,设置告警并人工处理。
3. 半消息堆积
- 原因:大量事务处于
UNKNOW
状态。 - 解决:优化本地事务稳定性,排查超时原因。
七、与其他方案的对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
RocketMQ 事务消息 | 解耦业务与消息、支持异步 | 需处理消费者幂等、存在短暂延迟 | 跨系统数据同步、事件通知 |
Seata AT 模式 | 强一致性、低侵入 | 性能损耗(全局锁)、依赖数据库 | 同一数据库实例的跨服务事务 |
TCC | 无资源锁定、高并发 | 代码侵入高、需实现补偿逻辑 | 高一致性要求的金融交易 |
八、总结
RocketMQ 事务消息通过 半消息 + 事务状态确认 + 回查机制 确保本地事务与消息投递的原子性,适用于需要最终一致性的分布式场景。使用时需注意:
- 本地事务高效执行,减少回查概率。
- 消费者严格幂等,避免重复处理。
- 监控事务消息状态,及时处理异常。