前言
微服务开发中,分布式事务是必须解决的核心问题。单体项目可依靠数据库本地事务ACID特性,彻底保证数据一致性;但业务服务拆分、分库分表落地后,一次完整业务操作会横跨多个独立服务、多个数据源,数据库原生事务机制完全失效,极易出现部分业务成功、部分业务失败的脏数据问题。
本文面向开发新手与一线工程师,聚焦生产落地、面试高频、极简上手三大核心,摒弃空洞理论堆砌。所有技术术语配套通俗解读,主流分布式事务方案提供SpringBoot/SpringCloud生产级可运行代码,同步梳理线上踩坑解决方案与业务选型标准,读完即可直接应用于项目开发。
一、前置基础:本地事务ACID特性
分布式事务的所有实现方案,均基于本地事务的ACID四大特性衍生而来,吃透该基础才能理解分布式场景下的事务取舍逻辑。
原子性(Atomic):事务整体不可拆分,执行过程要么全部成功提交,要么全部失败回滚。典型场景:转账业务中,扣款和入账操作必须同时生效或同时撤销,不存在单方执行的中间状态。
一致性(Consistent):事务执行前后,数据库整体数据约束、业务状态完全合法统一。以转账为例,交易前后两个账户的资金总金额保持不变,不会出现资金凭空增加或丢失的情况。
隔离性(Isolation):多并发事务之间相互独立、互不干扰。多个用户同时发起转账操作时,各自的事务执行互不篡改数据,数据库通过事务隔离级别控制并发冲突。
持久性(Durable):事务一旦提交成功,数据会永久落地入库,即便服务器即刻宕机、重启,已提交的数据也不会丢失或回滚。
实战核心要点:ACID是单库单服务的事务标准,仅适用于单体架构。微服务跨服务、跨库场景下,数据库原生ACID特性无法保证一致性,这也是分布式事务方案存在的核心意义。
二、分布式事务核心概念与诞生背景
2.1 分布式事务诞生原因
传统单体架构所有业务数据存储在同一数据库,Spring的@Transactional注解即可完美实现事务管控。但微服务架构落地后,业务按领域拆分为多个独立服务,每个服务对应独立的业务数据库。
以电商下单核心场景为例:创建订单(订单库)、扣减库存(库存库)、扣除用户余额(用户库)分属三个独立微服务。若订单创建成功后,库存扣减环节出现异常失败,就会产生「有订单、无库存」的脏数据,破坏业务数据一致性。
综上,分布式事务的核心作用,就是解决跨服务、跨数据库业务的数据一致性问题。
2.2 核心术语(生产/面试必记)
全局事务:一次完整的跨服务业务流程,是分布式事务的整体单元,例如电商完整的下单、扣库存、扣余额履约流程。
分支事务:全局事务中,单个微服务对应的单次数据库操作,下单、扣库存、扣余额各自独立为一个分支事务。
协调者:全局事务调度核心,负责统一管控所有分支事务的执行、提交、回滚,统筹整体事务状态。
参与者:参与全局事务的所有微服务,仅负责执行自身对应的分支事务逻辑。
最终一致性:互联网主流事务方案,允许业务执行过程中短暂数据不一致,通过重试、补偿机制,最终实现数据统一,兼顾性能与一致性。
强一致性:事务执行全程保证数据统一,无中间异常状态,无数据不一致窗口期,是金融、支付等核心资金业务的强制标准。
三、六大主流分布式事务方案实战详解
市面上所有分布式事务方案,本质都是在数据一致性、并发性能、开发运维成本三者之间做取舍。SpringBoot单体与SpringCloud微服务的事务核心原理一致,仅跨服务调用、上下文传递、容错处理存在差异。下文按企业实际使用优先级排序,覆盖单体、微服务全场景,所有代码均为生产可落地版本。
3.1 2PC 两阶段提交(强一致、低并发专用)
3.1.1 核心原理与执行流程
2PC是最基础的强一致性分布式事务方案,将完整事务拆分为准备、提交两个阶段,由全局协调者统一调度所有分支参与者。
通俗类比:团队集体报名活动,负责人(协调者)先逐一确认所有成员是否可参与(准备阶段),全员确认可行后,统一发起报名(提交阶段);任意一人无法参与,全员取消报名(全局回滚)。
准备阶段:协调者通知所有分支参与者执行事务预处理,完成参数校验、业务资源锁定、预执行逻辑,不正式提交数据库事务,最终向协调者返回执行结果。
提交/回滚阶段:若所有分支准备阶段执行成功,协调者通知所有参与者统一提交事务;若任意分支准备失败,协调者触发全局回滚,所有分支撤销预执行操作。
3.1.2 SpringCloud微服务落地代码(集群幂等版)
单体SpringBoot的2PC实现为本地类调用,无网络风险;但SpringCloud微服务通过OpenFeign实现跨服务远程调用,存在超时、重试、节点切换问题,必须基于全局事务ID(txId)做全链路管控、幂等防重,避免脏数据。
以下代码适配微服务集群场景,整合TxId上下文透传、幂等拦截、异常回滚、线程上下文清理能力,可直接落地生产。
全局事务协调者
java
/**
* 2PC全局事务协调者(SpringCloud生产版)
* 适配Feign远程调用、集群部署、幂等防重、全链路TxId管控
*/
@Service
@Slf4j
public class TwoPcTransactionCoordinator {
@Autowired
private OrderFeignClient orderFeignClient;
@Autowired
private StockFeignClient stockFeignClient;
/**
* 全局事务入口
* @param order 下单业务参数
* @return 事务执行结果
*/
public boolean executeGlobalTransaction(Order order) {
// 生成全局唯一事务ID,绑定全链路上下文
String globalTxId = UUID.randomUUID().toString().replace("-", "");
TransactionContextUtil.setTxId(globalTxId);
try {
// 幂等拦截:防止Feign重复重试发起事务
if (TransactionContextUtil.hasTransaction()) {
log.warn("事务已执行,禁止重复发起,txId:{}", globalTxId);
return true;
}
// 第一阶段:所有分支服务资源预锁定、参数预校验
log.info("2PC事务准备阶段开始,txId:{}", globalTxId);
boolean orderReady = orderFeignClient.prepareCreateOrder(order);
boolean stockReady = stockFeignClient.prepareDeductStock(order.getStockNum());
// 任意分支准备失败,触发全局回滚
if (!orderReady || !stockReady) {
log.error("2PC分支准备失败,触发全局回滚,txId:{}", globalTxId);
globalRollback(order);
return false;
}
// 第二阶段:全部分支准备成功,统一提交事务
log.info("2PC事务提交阶段开始,txId:{}", globalTxId);
orderFeignClient.commitCreateOrder();
stockFeignClient.commitDeductStock();
log.info("2PC全局事务执行成功,txId:{}", globalTxId);
return true;
} catch (Exception e) {
log.error("2PC全局事务异常,强制回滚,txId:{}", globalTxId, e);
globalRollback(order);
return false;
} finally {
// 兜底清理上下文,避免线程池复用污染
TransactionContextUtil.clear();
}
}
/**
* 全局统一回滚补偿
*/
private void globalRollback(Order order) {
orderFeignClient.rollbackOrder(order.getId());
stockFeignClient.rollbackStock(order.getStockNum());
}
}
分支服务业务实现
java
/**
* 订单分支服务(2PC微服务分支实现)
* 自动获取上游透传TxId,实现幂等与事务状态管控
*/
@Service
@Slf4j
public class OrderBranchService {
@Autowired
private OrderMapper orderMapper;
/**
* 准备阶段:资源预锁定、参数校验,不正式提交事务
*/
@Transactional(rollbackFor = Exception.class)
public boolean prepareCreateOrder(Order order) {
String txId = TransactionContextUtil.getTxId();
log.info("订单分支准备阶段,txId:{}", txId);
if (Objects.isNull(order) || order.getAmount() <= 0) {
return false;
}
// 预创建待确认订单,锁定业务资源
order.setStatus(0);
orderMapper.insert(order);
return true;
}
/**
* 提交阶段:正式落地业务数据
*/
@Transactional(rollbackFor = Exception.class)
public void commitCreateOrder() {
String txId = TransactionContextUtil.getTxId();
log.info("订单分支提交阶段,txId:{}", txId);
// 可补充订单状态更新、业务生效逻辑
}
/**
* 回滚阶段:撤销预执行业务
*/
@Transactional(rollbackFor = Exception.class)
public void rollbackOrder(Long orderId) {
String txId = TransactionContextUtil.getTxId();
log.info("订单分支回滚,txId:{}", txId);
if (Objects.nonNull(orderId)) {
orderMapper.deleteById(orderId);
}
}
}
Feign远程调用客户端
java
/**
* 订单服务Feign远程调用客户端
* 适配2PC三阶段调用规范
*/
@FeignClient(name = "order-service")
public interface OrderFeignClient {
@PostMapping("/order/prepare")
boolean prepareCreateOrder(@RequestBody Order order);
@PostMapping("/order/commit")
void commitCreateOrder();
@PostMapping("/order/rollback")
void rollbackOrder(@RequestParam("orderId") Long orderId);
}
3.1.3 生产踩坑与解决方案
单体2PC无网络风险、上下文共享稳定,可简单落地;但微服务集群架构下,远程调用的超时、重试、节点切换问题会放大2PC原生缺陷,必须针对性优化。
核心改造逻辑:摒弃本地内存存储事务状态,基于全局TxId+Redis实现全集群事务状态共享,解决跨节点状态丢失、幂等失效问题。
完整生产执行流程:协调者生成全局TxId并全链路透传 → 所有分支执行资源预锁定与校验 → 汇总分支状态,失败则全局回滚 → 全部成功则统一提交 → 事务结束清理上下文。
核心坑点与闭环解决方案
-
事务长期阻塞:准备阶段持续占用数据库行锁,协调者宕机、网络中断会导致资源无法释放,引发接口卡死。解决方案:严格限制2PC仅用于短耗时、低并发事务,配置3s事务超时自动回滚机制。
-
Feign重试产生脏数据:自动重试机制会重复触发分支预执行、提交逻辑。解决方案:基于全局TxId做全链路幂等拦截,已执行事务直接放行。
-
集群节点状态不一致:重试请求被负载均衡分发至不同节点,本地内存状态失效。解决方案:使用Redis缓存全局事务状态,集群节点共享数据。
-
事务状态不一致漏洞:部分分支提交成功、部分分支网络异常未执行,无兜底机制。解决方案:新增定时状态校验任务,定时修复异常烂尾事务。
3.1.4 优缺点与生产选型
优势:逻辑简单、开发成本低、天然强一致性、无需复杂补偿逻辑,运维成本极低。
缺陷:数据库行锁长期占用,并发性能极差;容错能力弱,服务宕机易引发事务永久阻塞;微服务场景缺陷进一步放大。
生产选型规范 :仅适用于企业内部低并发、短耗时跨库事务;严禁用于电商、秒杀、对外高并发互联网业务,微服务核心业务不推荐使用。
3.2 3PC 三阶段提交(仅原理了解,无生产落地)
3.2.1 优化原理与核心缺陷
3PC是2PC的迭代优化方案,核心目的是解决2PC事务永久阻塞问题,新增预准备阶段与全局超时容错机制。完整流程分为:预准备 → 准备 → 提交/回滚。
预准备阶段仅做业务状态校验,不锁定数据库资源,规避无效资源占用;所有事务节点配置超时自动收尾机制,避免事务永久卡死阻塞。
3.2.2 生产淘汰原因
-
存在致命一致性漏洞:超时自动收尾机制会导致部分分支提交成功、部分分支超时回滚,直接产生永久脏数据,无法修复。
-
性价比极低:相比2PC,代码复杂度大幅提升,仅优化阻塞问题,未解决核心的数据一致性问题,无实际生产收益。
-
微服务完全不适配:微服务远程调用超时、重试概率高,会放大3PC的一致性漏洞,极易引发批量数据异常。
总结:3PC仅用于面试理论提问,所有互联网企业生产环境全线禁用,无需落地开发,仅掌握基础原理即可。
3.3 TCC 补偿事务(金融核心强一致方案)
3.3.1 核心原理
TCC是金融、支付、对账等核心资金业务的唯一首选强一致方案,无数据库锁阻塞,并发性能优异,可彻底保证分布式数据强一致。
通俗类比:酒店预约订房,Try阶段锁定房间、预留名额;Confirm阶段确认入住、正式核销资源;Cancel阶段取消预约、释放房源,全程无资源浪费、无数据错乱。
Try(资源预占):参数合法性校验、业务资源预先锁定,不做正式数据变更,无业务数据落地。
Confirm(业务确认):所有分支Try阶段全部执行成功后,统一正式落地业务数据,完成事务提交。
Cancel(事务补偿):任意分支Try阶段失败或全局事务异常,统一释放所有预占资源,回滚所有分支预执行数据。
3.3.2 微服务集群落地代码(Redis分布式状态版)
单体TCC可通过本地内存存储事务状态,但微服务集群部署时,Feign负载均衡会将重试、补偿请求分发至不同节点,导致本地内存状态丢失,引发幂等失效、空回滚、事务悬挂等问题。生产环境必须将事务状态改造为Redis全局存储,实现集群状态共享。
java
/**
* TCC订单微服务实现(Redis分布式状态生产版)
* 解决集群状态不一致、重试失效、空回滚、事务悬挂问题
*/
@Service
@Slf4j
public class OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
// Redis事务状态Key前缀
private static final String TX_STATUS_PREFIX = "tcc:tx:status:";
// 事务状态枚举:1-Try完成 2-Confirm完成 3-Cancel完成
private static final Integer TX_TRY_SUCCESS = 1;
private static final Integer TX_CONFIRM_SUCCESS = 2;
private static final Integer TX_CANCEL_SUCCESS = 3;
// 事务Key过期时间,避免无效脏数据堆积
private static final long TX_EXPIRE_SECONDS = 3600;
/**
* Try阶段:资源预占、参数校验
*/
@Transactional(rollbackFor = Exception.class)
public boolean tryCreateOrder(Order order) {
String txId = TransactionContextUtil.getTxId();
log.info("TCC-Try阶段执行,txId:{}", txId);
// 幂等拦截:已执行Try的事务直接放行,防止重复执行
String status = stringRedisTemplate.opsForValue().get(TX_STATUS_PREFIX + txId);
if (StringUtils.isNotBlank(status)) {
return true;
}
// 业务参数校验
if (order.getAmount() <= 0) {
return false;
}
// 预占资源:创建待确认订单
order.setStatus(0);
orderMapper.insert(order);
// 写入分布式事务状态
stringRedisTemplate.opsForValue().set(TX_STATUS_PREFIX + txId,
String.valueOf(TX_TRY_SUCCESS), TX_EXPIRE_SECONDS, TimeUnit.SECONDS);
return true;
}
/**
* Confirm阶段:正式提交业务
*/
@Transactional(rollbackFor = Exception.class)
public void confirmCreateOrder(Long orderId) {
String txId = TransactionContextUtil.getTxId();
log.info("TCC-Confirm阶段执行,txId:{}", txId);
String statusKey = TX_STATUS_PREFIX + txId;
// 状态校验:仅Try完成的事务允许提交
String status = stringRedisTemplate.opsForValue().get(statusKey);
if (!String.valueOf(TX_TRY_SUCCESS).equals(status)) {
return;
}
// 正式生效订单业务
orderMapper.updateStatus(orderId, 1);
// 更新事务终态
stringRedisTemplate.opsForValue().set(statusKey,
String.valueOf(TX_CONFIRM_SUCCESS), TX_EXPIRE_SECONDS, TimeUnit.SECONDS);
}
/**
* Cancel阶段:事务补偿回滚
* 解决空回滚、事务悬挂核心逻辑
*/
@Transactional(rollbackFor = Exception.class)
public void cancelCreateOrder(Long orderId) {
String txId = TransactionContextUtil.getTxId();
log.info("TCC-Cancel阶段执行,txId:{}", txId);
String statusKey = TX_STATUS_PREFIX + txId;
String status = stringRedisTemplate.opsForValue().get(statusKey);
// 防空回滚:未执行Try的事务,无资源可回滚,直接跳过
if (StringUtils.isBlank(status)) {
return;
}
// 防悬挂:已提交的终态事务,禁止回滚
if (String.valueOf(TX_CONFIRM_SUCCESS).equals(status)) {
return;
}
// 正常事务回滚补偿
orderMapper.deleteById(orderId);
stringRedisTemplate.opsForValue().set(statusKey,
String.valueOf(TX_CANCEL_SUCCESS), TX_EXPIRE_SECONDS, TimeUnit.SECONDS);
}
}
3.3.3 生产核心坑点与解决方案
1. 幂等性问题:网络抖动、Feign重试会导致Confirm/Cancel重复执行,引发数据异常。解决方案:通过全局TxId+Redis状态标记,拦截已完成操作,杜绝重复执行。
2. 空回滚问题:部分分支Try阶段失败未执行,全局触发Cancel补偿,导致无资源回滚、程序报错。解决方案:回滚前校验Redis事务状态,无Try执行记录直接跳过回滚逻辑。
3. 事务悬挂问题:网络超时导致Try请求延迟送达,此时全局事务已完结,延迟执行的Try会产生脏数据。解决方案:通过Redis终态标记,拦截所有过期延迟请求。
4. 跨服务状态不统一:多分支独立执行无全局管控,易出现部分成功部分失败。解决方案:全链路透传TxId,统一所有分支事务状态。
3.3.4 优缺点与生产选型
优势:无数据库锁阻塞,并发性能优异;三段式架构容错性强;可实现绝对强一致性,是金融资金行业合规标准方案;微服务适配性强。
缺陷:代码侵入性极高,每个业务需手动实现Try/Confirm/Cancel三套逻辑,开发、测试、维护成本高;需手动处理各类分布式事务异常。
生产选型规范 :强制用于支付、转账、充值、对账等资金核心业务;普通高并发、非资金业务不推荐使用,性价比极低。
3.4 SAGA 长事务补偿方案(长流程业务专用)
3.4.1 核心原理
SAGA是长耗时、多步骤、串行流转业务的专属分布式事务方案,属于最终一致性方案,核心逻辑为「正向执行、逆向补偿」。
通俗类比:网购退货流程,依次执行提交退货、退回商品、退款到账三步操作;任意一步执行失败,反向执行对应撤销操作,逐级回滚已完成步骤。
核心设计:将超长分布式事务拆分为多个独立本地事务,每个正向业务方法绑定专属逆向补偿方法,事务执行失败时,自动倒序执行补偿逻辑,实现数据最终一致。
3.4.2 微服务落地代码
java
@Service
@Slf4j
public class OrderSagaService {
@Autowired
private OrderService orderService;
@Autowired
private RefundService refundService;
/**
* SAGA正向主流程:售后退货长流程
*/
public boolean sagaRefundProcess(RefundOrder refundOrder) {
Long orderId = refundOrder.getOrderId();
try {
// 步骤1:提交退货申请
orderService.applyRefund(orderId);
// 步骤2:执行退款核心业务
refundService.doRefund(orderId);
log.info("SAGA退货长流程执行成功");
return true;
} catch (Exception e) {
log.error("SAGA流程执行失败,触发逆向补偿", e);
// 倒序补偿回滚已完成步骤
orderService.cancelRefund(orderId);
return false;
}
}
}
3.4.3 生产踩坑与适配场景
微服务适配原因:长流程、多步骤微服务业务无法使用2PC(锁阻塞)、TCC(开发成本过高),SAGA无需资源预占、无锁阻塞,仅通过正向执行+逆向补偿即可实现事务闭环,完美适配长耗时串行业务。
核心坑点与解决方案
-
中间态数据不一致:属于最终一致性方案,业务执行过程中存在短暂数据不一致。解决方案:严格规避资金业务,仅用于可短暂容忍数据不一致的场景。
-
补偿失败死循环:补偿逻辑异常、数据状态错乱会导致事务无法收尾。解决方案:新增事务状态记录表,记录每一步执行状态,失败后支持人工兜底修复。
-
业务步骤乱序:微服务异步调用可能导致步骤错乱,补偿逻辑失效。解决方案:强制业务串行执行,锁定步骤执行顺序。
3.4.4 优缺点与生产选型
优势:无数据库锁阻塞、高并发友好、支持超长业务流程、开发成本远低于TCC、微服务适配性极强。
缺陷:仅支持最终一致性,无法用于强一致场景;复杂流程需手动编写多步补偿逻辑,代码繁琐。
生产选型规范 :唯一适配物流履约、售后退货、订单超长流转、多级审批等长流程微服务业务;严禁用于所有资金类核心业务。
3.5 本地消息表(极简稳定、中小电商主流)
3.5.1 核心原理
本地消息表是经典稳定的最终一致性分布式事务方案,核心核心逻辑:业务数据与消息台账数据在同一个本地事务中写入,保证原子性,配合定时任务无限重试兜底。
通俗类比:办事前先登记台账,业务完成后标记完结,未完成的任务定时反复重试,保证业务最终落地。
3.5.2 生产落地代码(带重试兜底)
java
@Service
@Slf4j
@EnableScheduling
public class LocalMessageTransactionService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private MessageRecordMapper messageMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 核心事务:业务数据+消息台账同事务写入,保证原子性
*/
@Transactional(rollbackFor = Exception.class)
public void createOrderWithMessage(Order order) {
// 1. 写入核心订单业务数据
orderMapper.insert(order);
// 2. 写入消息投递台账,绑定业务ID
MessageRecord message = new MessageRecord();
message.setOrderId(order.getId());
message.setMsgStatus(0); // 0-未投递 1-已投递
messageMapper.insert(message);
}
/**
* 定时重试兜底:10秒轮询处理未投递消息,保证消息最终投递成功
*/
@Scheduled(fixedRate = 10000)
public void retrySendUnHandleMessage() {
// 分页查询未投递消息,避免大数据量DB压力
List<MessageRecord> unSendList = messageMapper.selectUnSendMessage();
for (MessageRecord record : unSendList) {
try {
// 投递MQ消息,触发下游业务执行
rabbitTemplate.convertAndSend("order.topic", record.getOrderId());
// 投递成功更新状态,防止重复消费
messageMapper.updateStatus(record.getId(), 1);
} catch (Exception e) {
log.error("消息投递失败,等待下次重试,消息ID:{}", record.getId(), e);
}
}
}
}
3.5.3 生产踩坑与解决方案
微服务适配原因:中小微项目无高规格中间件运维能力,RocketMQ事务消息运维成本高,而本地消息表无需额外中间件,依托数据库+定时任务即可实现最终一致性,极简稳定、故障率极低。
核心坑点与优化方案
-
数据库轮询压力:定时任务频繁查询未投递消息,大数据量下占用DB资源。解决方案:新增消息状态索引、优化轮询频率、分页查询限流。
-
消息重复投递:定时重试导致消息重复发送,下游业务重复执行。解决方案:下游基于订单ID、消息ID实现幂等拦截。
-
集群定时任务重复执行:微服务多节点部署,定时任务多节点同时触发。解决方案:通过分布式锁保证定时任务全局单节点执行。
-
消息堆积过期:服务宕机、MQ故障导致消息长期堆积。解决方案:新增消息超时机制,超时未处理标记为异常,支持人工兜底修复。
3.5.4 优缺点与生产选型
优势:无第三方中间件强依赖、逻辑极简、线上故障率极低、落地成本低、适配绝大多数中小微异步业务。
缺陷:需自建消息数据表、维护定时任务,存在少量DB轮询开销,无法支撑极致高并发场景。
生产选型规范:适配中小电商下单、积分发放、消息通知、库存异步更新等异步业务;大型电商极致高并发场景优先替换为RocketMQ事务消息。
3.6 RocketMQ 事务消息(高并发电商终极方案)
3.6.1 核心原理与生产流程
RocketMQ事务消息是互联网高并发分布式事务最终一致性的工业级标准方案,是本地消息表的官方升级版、中间件托管方案。
传统本地消息表需要开发者手动建表、写定时任务、维护重试逻辑,重复造轮子;而RocketMQ事务消息将消息存储、状态流转、失败重试、事务回查、超时兜底全部内置,开发者仅需专注核心业务逻辑,大幅降低基建成本。
核心设计思想:先发送半消息占位 → 执行本地事务 → 确认消息投递或回滚,彻底解决「消息先发业务后执行、业务成功消息丢失」的经典一致性问题。
完整生产执行四阶段
-
发送半消息:生产者向RocketMQ发送对消费者不可见的半事务消息,MQ暂存消息、锁定事务占位,不触发消费逻辑。
-
执行本地事务:MQ接收半消息成功后,回调生产者本地事务方法,执行业务数据库操作,依托本地事务保证业务原子性。
3.提交/回滚消息:本地事务执行成功,向MQ发送COMMIT指令,消息对外可见,消费者正常消费;本地事务失败,发送ROLLBACK指令,MQ直接删除半消息。
- 定时事务回查(核心兜底):若生产者宕机、网络超时,MQ未收到确认指令,会定时主动回查生产者事务状态,阶梯式重试,彻底解决烂尾事务问题。
3.6.2 全套可落地生产代码
以下为完整链路代码,包含生产者、事务监听器、消费者,可直接复制用于SpringCloud微服务生产环境。
事务消息生产者
java
/**
* RocketMQ事务消息生产者
* 负责发送半消息、绑定事务上下文
*/
@Service
@Slf4j
public class OrderTransactionProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 下单事务消息入口
*/
public void createOrderTransaction(Order order) {
// 生成全局事务ID,用于日志追踪、事务对账
String txId = UUID.randomUUID().toString().replace("-", "");
Message<Order> message = MessageBuilder
.withPayload(order)
.setHeader(RocketMQHeaders.TRANSACTION_ID, txId)
.build();
// 发送事务半消息,绑定事务监听器
rocketMQTemplate.sendMessageInTransaction("order-transaction-topic", message, order);
log.info("事务半消息发送完成,事务ID:{}", txId);
}
}
事务监听器(本地事务+回查兜底核心)
java
/**
* 全局事务监听器
* 处理本地事务执行、MQ定时回查兜底逻辑
*/
@Component
@Slf4j
public class OrderTransactionListener implements TransactionListener {
@Autowired
private OrderMapper orderMapper;
/**
* 执行本地核心事务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
Order order = (Order) arg;
// 执行下单本地事务
orderMapper.insert(order);
// 业务成功,允许消息投递
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
log.error("本地事务执行失败,事务回滚", e);
// 业务异常,丢弃消息
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
/**
* MQ定时回查兜底(生产核心容错)
* 服务宕机、超时未知状态时,MQ反复回调此方法
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
try {
Order order = JSON.parseObject(msg.getBody(), Order.class);
Order existOrder = orderMapper.selectById(order.getId());
// 存在业务数据则提交消息,无数据则回滚
return Objects.nonNull(existOrder)
? LocalTransactionState.COMMIT_MESSAGE
: LocalTransactionState.ROLLBACK_MESSAGE;
} catch (Exception e) {
log.error("事务回查异常,等待下次重试", e);
// 异常状态返回未知,MQ继续回查
return LocalTransactionState.UNKNOW;
}
}
}
事务消息消费者
java
/**
* 订单事务消息消费者
* 仅消费COMMIT成功的消息,天然保证最终一致
*/
@Component
@Slf4j
public class OrderTransactionConsumer {
@RocketMQMessageListener(
topic = "order-transaction-topic",
consumerGroup = "order-transaction-consumer-group"
)
public void consume(MessageExt messageExt) {
try {
Order order = JSON.parseObject(messageExt.getBody(), Order.class);
// 执行下游业务:扣库存、发积分、用户通知等
log.info("事务消息消费成功,执行下游业务,订单ID:{}", order.getId());
} catch (Exception e) {
log.error("事务消息消费失败", e);
// 抛出异常触发MQ重试,保证消息必达
throw new RuntimeException("消息消费失败");
}
}
}
3.6.3 生产核心规范与避坑要点
三大事务状态释义(面试+生产高频)
COMMIT_MESSAGE:本地事务成功,MQ放行消息,消费者正常消费;
ROLLBACK_MESSAGE:本地事务失败,MQ删除半消息,事务终止;
UNKNOW:事务状态未知,MQ持续阶梯回查,是宕机恢复的核心兜底。
相比本地消息表的核心优势
-
无需自建消息台账表,消息存储、状态流转全由MQ托管;
-
内置定时回查机制,无需手动开发定时重试任务,无DB轮询开销;
-
宕机容错能力更强,服务重启后MQ继续回查事务,无事务丢失风险;
-
基于MQ天然削峰,完美适配电商大流量、秒杀等高并发场景。
生产强制避坑规则
-
回查方法必须幂等,仅做状态查询,禁止新增/修改业务数据;
-
本地事务必须严格原子性,所有业务逻辑被数据库事务包裹;
-
消费者消费失败必须抛出异常,禁止静默失败,保证MQ重试兜底;
-
仅适配最终一致性场景,严禁用于支付、转账等强一致资金业务。
3.6.4 优缺点与生产选型
优势:零自建台账、零手动定时任务、高并发性能极强、工业级重试容错、微服务适配性拉满,无需手动透传事务上下文。
缺陷:强依赖RocketMQ中间件,Kafka、RabbitMQ不支持事务消息能力。
生产选型规范:大型电商、高并发下单、库存更新、异步履约等核心高并发场景首选,是SpringCloud微服务高并发分布式事务的最优解。
四、Seata 框架全模式生产实战
前文手写方案适合原理学习与面试备考,企业生产环境统一使用Seata框架,规避手写方案基建繁琐、容错不全、状态错乱等问题。Seata原生适配SpringBoot单体、SpringCloud微服务双架构,内置XA/AT/TCC/SAGA四大事务模式,覆盖所有生产业务场景。
4.1 Seata核心架构
TC(事务协调器):独立部署的全局事务调度中心,统一管控全局事务状态、通知分支提交/回滚,单体与微服务环境无差异。
TM(事务管理器):集成在业务项目中,负责开启、提交、终止全局事务,微服务场景自动透传事务上下文。
RM(资源管理器):集成于所有业务服务,负责分支事务资源管控、状态上报,适配多数据源、跨服务场景。
4.2 四大模式核心差异与选型总览
-
XA模式:强一致、零代码侵入、依赖数据库XA协议,锁阻塞严重、并发低,仅适配内部低并发短事务。
-
AT模式(生产首选):最终一致、零侵入、高并发无锁,适配90%普通微服务异步业务,替代本地消息表简易方案。
-
TCC模式:强一致、无锁高并发,需手动实现三段式逻辑,适配金融支付、对账等核心资金业务。
-
SAGA模式:最终一致、适配超长流程,无需资源预占,适配售后、物流、多级审批等长流程业务。
4.3 公共依赖与通用配置
4.3.1 核心Maven依赖
xml
<!-- Seata通用核心依赖,兼容SpringBoot2.x/3.x -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
4.3.2 SpringBoot单体配置
yaml
seata:
application:
name: ${spring.application.name}
registry:
type: file
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
enable-auto-data-source-proxy: true
global-transaction-timeout: 60000
data-source-proxy-mode: AT
4.3.3 SpringCloud微服务Nacos配置
yaml
seata:
application:
name: ${spring.application.name}
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
enable-auto-data-source-proxy: true
global-transaction-timeout: 60000
data-source-proxy-mode: AT
spring:
cloud:
loadbalancer:
nacos:
enabled: true
4.4 AT模式(生产首选)
4.4.1 核心原理
Seata AT模式是零侵入、高并发的最优通用方案,执行流程:业务执行SQL时,Seata自动拦截数据源,记录数据前置/后置快照(Undo Log);业务正常执行则删除快照、提交事务;业务异常则基于快照自动反向回滚数据,实现最终一致性。
4.4.2 落地代码
SpringBoot单体版
java
@Service
@Slf4j
public class OrderAtService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockMapper stockMapper;
// 全局事务注解,零侵入实现多库事务
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 库1:新增订单
orderMapper.insert(order);
// 库2:扣减库存
stockMapper.deductStock(order.getStockNum());
// 任意异常自动基于快照回滚所有数据
}
}
SpringCloud微服务版
java
@Service
@Slf4j
public class OrderSeataService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockFeignClient;
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 本地事务
orderMapper.insert(order);
// Feign跨服务调用,Seata自动透传事务上下文
stockFeignClient.deductStock(order.getStockNum());
}
}
4.4.3 生产坑点与解决方案
-
高并发快照失效:多线程修改同一数据,快照与实时数据不匹配。解决方案:叠加乐观锁/分布式锁控制并发。
-
微服务上下文丢失:自定义Feign拦截器覆盖Seata原生透传。解决方案:兼容X-GLOBAL-TX-ID请求头透传。
-
事务烂尾:服务熔断超时小于Seata事务超时。解决方案:统一熔断超时时间 < 事务超时时间。
4.5 TCC模式(金融专属)
4.5.1 核心原理
Seata TCC框架托管事务状态、重试、幂等、兜底逻辑,无需手动维护Redis状态,沿用Try/Confirm/Cancel三段式架构,自动解决空回滚、事务悬挂、重复重试三大核心问题。
4.5.2 SpringBoot落地代码
java
@Service
@Slf4j
public class OrderTccService {
@Autowired
private OrderMapper orderMapper;
// 绑定确认、回滚方法
@TccAction(commitMethod = "confirm", rollbackMethod = "cancel")
public boolean tryCreateOrder(Order order) {
// Try:资源预占,创建待确认订单
order.setStatus(0);
orderMapper.insert(order);
return true;
}
// Confirm:事务确认,正式生效业务
public void confirm(Order order) {
orderMapper.updateStatus(order.getId(), 1);
}
// Cancel:事务回滚,释放资源
public void cancel(Order order) {
orderMapper.deleteById(order.getId());
}
}
4.5.3 微服务适配与坑点解决
微服务场景无需修改业务代码,Seata自动完成TxId透传、集群状态同步,框架原生解决幂等、空回滚、悬挂问题,完全适配支付、对账等金融场景。
4.6 SAGA模式(长流程专属)
Seata SAGA模式专为长耗时、多步骤串行业务设计,将长事务拆分为多个独立本地事务,每个正向方法绑定逆向补偿方法,任意步骤失败自动逐段逆向补偿,适配售后退货、物流履约、多级审批等场景,仅支持最终一致性,禁止用于资金业务。
4.7 XA模式(低并发内部专用)
XA模式是标准2PC的框架封装版,零代码侵入、强一致性,依托数据库XA协议实现事务管控。但完全继承2PC锁阻塞、并发低、容错差的缺陷,仅适用于内部低并发、短耗时跨库事务,互联网高并发业务全面禁用。
4.8 Seata生产全套规范与避坑总结
4.8.1 全局通用生产约束
Seata框架虽大幅简化分布式事务开发,但微服务集群、高并发场景下存在大量隐性问题,需统一遵循生产规范,避免线上事务失效、数据错乱、事务烂尾问题。所有规范均基于线上故障复盘总结,可直接落地执行。
1. 事务超时统一规范:全局事务默认超时时间建议设置为60s,服务熔断、接口超时配置必须小于Seata事务超时时间。若接口熔断超时更早,会导致客户端请求失败、服务端事务继续执行,产生部分事务提交、部分回滚的数据不一致问题。
2. 上下文透传规范 :自定义Feign拦截器、网关过滤器、请求转发逻辑中,必须保留Seata原生事务请求头X-GLOBAL-TX-ID与X-BRANCH-TX-ID,禁止覆盖、清空事务上下文,否则跨服务事务失效,分支事务无法被全局事务管控。
3. 异常捕获规范 :业务代码中禁止全局捕获Exception且不抛出,@GlobalTransactional依赖异常触发自动回滚。若异常被静默捕获,框架无法感知业务失败,会导致异常事务正常提交,产生脏数据。
4. 多数据源适配规范:项目中存在多数据源、分库分表场景时,必须开启Seata自动数据源代理,所有业务数据库连接统一被框架托管,否则部分数据源无法加入全局事务,出现事务割裂问题。
4.8.2 四大模式专属生产坑点
AT模式专属问题
-
快照更新失效:高并发下多条SQL连续修改同一行数据,前置快照与数据库实时数据不匹配,导致回滚失败。解决方案:核心业务叠加乐观锁版本号控制,更新语句携带
version条件,避免并发覆盖。 -
大事务性能问题:单次全局事务包含大量数据库操作,Undo Log日志过大,事务提交、回滚耗时激增,引发接口超时。解决方案:拆分大事务,非强一致异步逻辑剥离出全局事务。
-
只读事务浪费资源:纯查询接口添加全局事务注解,无效占用TC资源。解决方案:查询业务不开启
@GlobalTransactional。
TCC模式专属问题
-
重复重试幂等:Seata会对超时、异常的TCC事务自动重试,Confirm、Cancel方法必须全局幂等,禁止重复新增、修改数据。
-
空执行拦截:框架可能提前触发Cancel,此时Try阶段未执行,需依托Seata状态机自动防空回滚、事务悬挂,无需手动维护Redis状态。
-
三段逻辑幂等对齐:Try、Confirm、Cancel的业务参数、数据查询条件必须完全一致,避免参数不一致导致补偿失效。
SAGA模式专属问题
-
补偿不可逆:SAGA无资源预占,正向事务已提交入库,补偿逻辑执行失败会导致数据永久不一致。解决方案:所有补偿方法单独编写兜底逻辑,支持人工手动修复。
-
步骤乱序失效:异步调用、多线程执行业务步骤,会导致正向流程乱序、补偿逻辑匹配失败。解决方案:长流程业务强制串行执行,锁定步骤顺序。
XA模式专属问题
全程规避高并发场景,XA协议持有数据库物理锁直至全局事务结束,长耗时业务会导致锁积压、接口阻塞、数据库性能下降,仅可用于内部后台低频次操作。
4.8.3 Seata落地部署规范
1. TC集群部署:生产环境禁止单节点TC部署,需搭建Seata TC集群+Nacos注册中心,保证事务调度高可用,避免TC宕机导致全局事务全部卡死。
2. 持久化配置:TC事务状态必须开启数据库持久化,禁止内存存储,服务重启后可恢复未完成事务,避免烂尾事务丢失、数据错乱。
3. 日志清理规范:定期清理Undo Log历史数据、已完结事务日志,避免日志表无限膨胀影响数据库性能。
五、全场景分布式事务选型终版总结
结合六大原生方案+Seata四大框架方案,基于业务场景、一致性要求、并发量级、开发成本四个维度,整理企业生产唯一选型标准,覆盖99%微服务业务场景,可直接作为项目技术规范使用。
1. 金融资金类业务(支付、转账、对账、充值)
强制选型:Seata TCC模式
排除方案:所有最终一致性方案、2PC/3PC、SAGA,杜绝资金数据不一致风险。优势为无锁高并发、强一致性、框架兜底容错,适配金融合规要求。
2. 普通高并发电商业务(下单、扣库存、积分发放)
大型项目选型:RocketMQ事务消息,极致高并发、无DB压力、运维成熟;
中小项目选型:Seata AT模式,零代码侵入、落地简单、维护成本低,替代手写本地消息表。
3. 长流程串行业务(售后退货、物流履约、多级审批)
唯一选型:Seata SAGA模式,无资源预占、支持长耗时事务,规避TCC开发成本高、2PC锁阻塞问题。
4. 内部低并发短事务(后台管理、企业内部操作)
选型:Seata XA模式 / 手写2PC,开发极简、无需复杂适配,低并发下无性能瓶颈。
5. 中小团队极简兜底方案
无中间件运维能力、追求稳定低故障:手写本地消息表+定时重试,逻辑透明、无框架黑盒问题、线上故障率极低。
六、线上通用问题排查思路
生产环境分布式事务异常,优先按固定流程排查,快速定位根因,避免盲目调试:
-
日志追踪:通过全局TxId串联全链路日志,确认分支事务执行状态、提交/回滚节点是否异常;
-
状态校验:查询Seata事务表、MQ事务状态、本地消息表台账,判断事务是正常完结、烂尾还是补偿失败;
-
场景定位:区分是并发冲突、上下文丢失、幂等失效,还是超时熔断导致的事务割裂;
-
兜底修复:异常烂尾事务优先人工补偿修复,同步迭代代码规避同类问题复现。
七、Seata 生产全套建表语句(MySQL)
Seata 生产环境必须依赖数据库表存储事务快照、事务状态、分支信息、锁信息等数据,否则服务重启、宕机后会丢失事务数据,引发事务烂尾、回滚失效、数据不一致问题。以下为 Seata1.7.x 官方完整版生产建表语句,包含业务端Undo日志表、TC服务全局事务表、分支事务表、锁表,适配MySQL5.7/8.0,可直接线上执行。
7.1 业务服务端必建表(所有微服务都需要)
核心为 undo_log 表,AT模式核心依赖,用于存储数据快照,实现事务自动回滚,所有接入Seata的业务库必须创建。
sql
-- Seata AT模式 Undo日志表(业务库必备)
CREATE TABLE IF NOT EXISTS `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`branch_id` BIGINT(20) NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文序列化信息',
`rollback_info` LONGBLOB NOT NULL COMMENT '事务回滚快照详细数据',
`log_status` INT(11) NOT NULL DEFAULT 0 COMMENT '日志状态:0-正常 1-已归档',
`log_created` DATETIME NOT NULL COMMENT '日志创建时间',
`log_modified` DATETIME NOT NULL COMMENT '日志修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT = 'Seata事务Undo日志表';
7.2 TC服务端必建表(Seata服务端库专属)
Seata TC协调服务专用数据表,用于存储全局事务状态、分支事务信息、事务锁信息,仅需在Seata服务端数据库创建一次,业务微服务无需重复创建,是集群高可用、宕机恢复的核心基础。
sql
-- 1. 全局事务状态表
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`transaction_id` BIGINT(20) NULL COMMENT '事务唯一标识',
`status` TINYINT(4) NOT NULL COMMENT '全局事务状态',
`application_id` VARCHAR(32) NULL COMMENT '应用ID',
`transaction_service_group` VARCHAR(32) NULL COMMENT '事务分组',
`transaction_name` VARCHAR(128) NULL COMMENT '事务名称',
`timeout` INT(11) NULL COMMENT '事务超时时间',
`begin_time` BIGINT(20) NULL COMMENT '事务开始时间戳',
`application_data` VARCHAR(2000)NULL COMMENT '应用自定义数据',
`gmt_create` DATETIME NULL COMMENT '创建时间',
`gmt_modified` DATETIME NULL COMMENT '修改时间',
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'Seata全局事务状态表';
-- 2. 分支事务状态表
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT(20) NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '关联全局事务ID',
`transaction_id` BIGINT(20) NULL COMMENT '全局事务唯一标识',
`resource_group_id` VARCHAR(32) NULL COMMENT '资源分组ID',
`resource_id` VARCHAR(256) NULL COMMENT '资源ID',
`branch_type` VARCHAR(8) NULL COMMENT '分支类型:AT/TCC/SAGA/XA',
`status` TINYINT(4) NULL COMMENT '分支事务状态',
`client_id` VARCHAR(64) NULL COMMENT '客户端标识',
`application_data` VARCHAR(2000)NULL COMMENT '自定义数据',
`gmt_create` DATETIME NULL COMMENT '创建时间',
`gmt_modified` DATETIME NULL COMMENT '修改时间',
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'Seata分支事务状态表';
-- 3. 全局事务锁表(防并发脏数据)
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL COMMENT '行唯一标识',
`xid` VARCHAR(128) NULL COMMENT '全局事务ID',
`transaction_id` BIGINT(20) NULL COMMENT '事务ID',
`branch_id` BIGINT(20) NOT NULL COMMENT '分支ID',
`resource_id` VARCHAR(256) NULL COMMENT '资源ID',
`table_name` VARCHAR(32) NULL COMMENT '数据表名',
`pk` VARCHAR(36) NULL COMMENT '主键值',
`gmt_create` DATETIME NULL COMMENT '创建时间',
`gmt_modified` DATETIME NULL COMMENT '修改时间',
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'Seata全局锁表';
-- 4. 事务回查日志表(1.7+新增,容错兜底)
CREATE TABLE IF NOT EXISTS `retry_deadletter_table`
(
`id` BIGINT AUTO_INCREMENT NOT NULL COMMENT '主键',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`branch_type` VARCHAR(8) NOT NULL COMMENT '分支类型',
`status` TINYINT NOT NULL COMMENT '状态',
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数',
`max_retry_count`INT NOT NULL DEFAULT 30 COMMENT '最大重试次数',
`gmt_create` DATETIME NOT NULL COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xid_branch_type` (`xid`, `branch_type`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'Seata死信重试表';
7.3 生产建表与运维规范
1. 建表部署规范
所有业务微服务数据库,统一执行 undo_log 建表语句;独立的Seata TC服务数据库,执行剩余四张系统表,禁止将TC数据表创建在业务库中,实现业务与中间件数据隔离。
2. 索引优化规范
所有数据表已预设生产最优索引,聚焦XID、分支ID、时间字段,适配事务查询、回滚、定时清理场景,无需手动新增索引,避免索引冗余拖慢数据库性能。
3. 日志清理生产规则
undo_log 表数据会随事务执行持续增长,必须配置定时清理任务:仅保留近7天日志,已提交、已回滚的完结事务快照数据定时删除,防止单表数据量过大引发回滚超时、查询卡顿问题。
4. 字符集规范
统一使用 utf8mb4 字符集,兼容所有特殊字符与业务数据,规避中文、表情符乱码问题,完全适配线上生产环境。
7.4 Undo 日志自动清理定时任务(SpringBoot/SpringCloud通用)
Seata AT模式的undo_log表会持续累积事务快照数据,长期不清理会导致单表数据臃肿、事务回滚查询变慢、数据库IO升高。本节提供同时适配SpringBoot单体、SpringCloud微服务集群的自动清理方案,通过分布式锁解决集群多节点定时任务重复执行问题,生产可直接落地。
核心清理规则:仅清理【已提交、已回滚】的完结事务日志,保留近7天日志用于故障回溯,删除超时冗余数据,兼顾性能与容错性。
7.4.1 前置依赖(双环境通用)
集群环境需借助分布式锁防止多节点重复执行,引入Redis依赖适配SpringCloud集群,单体SpringBoot可兼容复用,无需额外改造。
xml
<!-- SpringBoot/SpringCloud通用Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 分布式锁适配(适配集群环境) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.7</version>
</dependency>
7.4.2 数据库Mapper清理方法
通用Mapper接口,适配所有接入Seata的业务库,双环境统一使用。
java
/**
* Undo日志清理Mapper
* 适配SpringBoot单体、SpringCloud微服务所有业务库
*/
@Mapper
public interface UndoLogCleanMapper {
/**
* 清理指定时间前的已完结Undo日志
* @param cleanTime 清理时间节点
* @return 删除条数
*/
@Delete("DELETE FROM undo_log WHERE log_modified < #{cleanTime} AND log_status = 1")
int cleanFinishUndoLog(@Param("cleanTime") LocalDateTime cleanTime);
}
7.4.3 双环境通用定时任务核心代码
通过Redisson分布式锁适配集群,无锁冲突时执行清理,单体环境自动兼容,定时周期可自定义配置。
java
/**
* Seata Undo日志自动清理定时任务
* 适配:SpringBoot单体 + SpringCloud集群双环境
* 集群通过分布式锁保证单节点执行,避免重复删除、DB压力
*/
@Component
@EnableScheduling
@Slf4j
public class SeataUndoLogCleanTask {
@Autowired
private UndoLogCleanMapper undoLogCleanMapper;
@Autowired
private RedissonClient redissonClient;
// 分布式锁Key
private static final String UNDO_LOG_CLEAN_LOCK = "seata:undo:log:clean:lock";
// 日志保留天数:默认保留7天,用于故障回溯
private static final int RETAIN_DAY = 7;
// 锁等待时间、持有时间
private static final long LOCK_WAIT_TIME = 0;
private static final long LOCK_HOLD_TIME = 60;
/**
* 定时清理任务
* 执行周期:每日凌晨2点执行,避开业务高峰
*/
@Scheduled(cron = "0 0 2 * * ?")
public void cleanUndoLog() {
// 1. 获取分布式锁,集群单节点执行
RLock lock = redissonClient.getLock(UNDO_LOG_CLEAN_LOCK);
boolean tryLock = false;
try {
tryLock = lock.tryLock(LOCK_WAIT_TIME, LOCK_HOLD_TIME, TimeUnit.SECONDS);
if (!tryLock) {
log.info("Seata Undo日志清理任务已被其他节点执行,当前节点跳过");
return;
}
// 2. 计算清理时间节点:删除7天前的历史日志
LocalDateTime cleanTime = LocalDateTime.now().minusDays(RETAIN_DAY);
// 3. 仅清理已归档完结的日志(log_status=1)
int deleteCount = undoLogCleanMapper.cleanFinishUndoLog(cleanTime);
log.info("Seata Undo日志自动清理完成,清理时间节点:{},删除冗余日志条数:{}", cleanTime, deleteCount);
} catch (Exception e) {
log.error("Seata Undo日志自动清理任务执行异常", e);
} finally {
// 4. 释放锁,避免死锁
if (tryLock && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
7.4.4 配置文件适配(双环境差异化配置)
1. SpringBoot单体配置(application.yml)
yaml
# 定时任务开启
spring:
task:
scheduling:
enabled: true
# Redis单机配置
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
2. SpringCloud微服务配置(bootstrap.yml)
yaml
# 适配Nacos配置中心、集群环境
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
task:
scheduling:
enabled: true
# Redis集群/单机通用配置(适配微服务)
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
7.4.5 生产核心适配规范与避坑
1. 双环境兼容原理
单体SpringBoot:Redisson分布式锁仅单节点持有,无竞争冲突,任务正常执行; SpringCloud集群:通过全局Redis锁保证整个集群仅有一个节点执行清理任务,彻底避免多节点重复删除、数据库压力叠加问题。
2. 日志保留策略规范
禁止清理7天内日志,线上事务故障、数据回滚问题大概率可通过近期Undo日志溯源,超时冗余日志可安全清理,兼顾性能与可排查性。
3. 状态过滤规则
仅删除log_status=1已归档日志,log_status=0的未完结、异常事务日志永久保留,防止烂尾事务丢失兜底数据。
4. 定时周期优化
默认凌晨低峰期执行,避免业务高峰期大量删除SQL锁表、影响线上事务执行;高并发业务可调整为每3天清理一次,进一步降低DB压力。
5. 极端兜底方案
若Redis故障、分布式锁失效,任务不会报错阻塞,仅单次跳过清理,不影响业务正常运行;可搭配Nacos动态开关控制任务启停,紧急场景手动关闭。
结语
分布式事务的核心本质并非追求万能方案,而是根据业务一致性等级、并发量级、运维能力做合理取舍。新手常陷入"全场景强一致"的误区,实际生产中,互联网绝大多数业务均可容忍短暂最终一致,仅核心资金业务需要强一致保障。