分布式事务实战教程

前言

微服务开发中,分布式事务是必须解决的核心问题。单体项目可依靠数据库本地事务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并全链路透传 → 所有分支执行资源预锁定与校验 → 汇总分支状态,失败则全局回滚 → 全部成功则统一提交 → 事务结束清理上下文。

核心坑点与闭环解决方案

  1. 事务长期阻塞:准备阶段持续占用数据库行锁,协调者宕机、网络中断会导致资源无法释放,引发接口卡死。解决方案:严格限制2PC仅用于短耗时、低并发事务,配置3s事务超时自动回滚机制。

  2. Feign重试产生脏数据:自动重试机制会重复触发分支预执行、提交逻辑。解决方案:基于全局TxId做全链路幂等拦截,已执行事务直接放行。

  3. 集群节点状态不一致:重试请求被负载均衡分发至不同节点,本地内存状态失效。解决方案:使用Redis缓存全局事务状态,集群节点共享数据。

  4. 事务状态不一致漏洞:部分分支提交成功、部分分支网络异常未执行,无兜底机制。解决方案:新增定时状态校验任务,定时修复异常烂尾事务。

3.1.4 优缺点与生产选型

优势:逻辑简单、开发成本低、天然强一致性、无需复杂补偿逻辑,运维成本极低。

缺陷:数据库行锁长期占用,并发性能极差;容错能力弱,服务宕机易引发事务永久阻塞;微服务场景缺陷进一步放大。

生产选型规范 :仅适用于企业内部低并发、短耗时跨库事务;严禁用于电商、秒杀、对外高并发互联网业务,微服务核心业务不推荐使用。

3.2 3PC 三阶段提交(仅原理了解,无生产落地)

3.2.1 优化原理与核心缺陷

3PC是2PC的迭代优化方案,核心目的是解决2PC事务永久阻塞问题,新增预准备阶段与全局超时容错机制。完整流程分为:预准备 → 准备 → 提交/回滚。

预准备阶段仅做业务状态校验,不锁定数据库资源,规避无效资源占用;所有事务节点配置超时自动收尾机制,避免事务永久卡死阻塞。

3.2.2 生产淘汰原因

  1. 存在致命一致性漏洞:超时自动收尾机制会导致部分分支提交成功、部分分支超时回滚,直接产生永久脏数据,无法修复。

  2. 性价比极低:相比2PC,代码复杂度大幅提升,仅优化阻塞问题,未解决核心的数据一致性问题,无实际生产收益。

  3. 微服务完全不适配:微服务远程调用超时、重试概率高,会放大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无需资源预占、无锁阻塞,仅通过正向执行+逆向补偿即可实现事务闭环,完美适配长耗时串行业务。

核心坑点与解决方案

  1. 中间态数据不一致:属于最终一致性方案,业务执行过程中存在短暂数据不一致。解决方案:严格规避资金业务,仅用于可短暂容忍数据不一致的场景。

  2. 补偿失败死循环:补偿逻辑异常、数据状态错乱会导致事务无法收尾。解决方案:新增事务状态记录表,记录每一步执行状态,失败后支持人工兜底修复。

  3. 业务步骤乱序:微服务异步调用可能导致步骤错乱,补偿逻辑失效。解决方案:强制业务串行执行,锁定步骤执行顺序。

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事务消息运维成本高,而本地消息表无需额外中间件,依托数据库+定时任务即可实现最终一致性,极简稳定、故障率极低。

核心坑点与优化方案

  1. 数据库轮询压力:定时任务频繁查询未投递消息,大数据量下占用DB资源。解决方案:新增消息状态索引、优化轮询频率、分页查询限流。

  2. 消息重复投递:定时重试导致消息重复发送,下游业务重复执行。解决方案:下游基于订单ID、消息ID实现幂等拦截。

  3. 集群定时任务重复执行:微服务多节点部署,定时任务多节点同时触发。解决方案:通过分布式锁保证定时任务全局单节点执行。

  4. 消息堆积过期:服务宕机、MQ故障导致消息长期堆积。解决方案:新增消息超时机制,超时未处理标记为异常,支持人工兜底修复。

3.5.4 优缺点与生产选型

优势:无第三方中间件强依赖、逻辑极简、线上故障率极低、落地成本低、适配绝大多数中小微异步业务。

缺陷:需自建消息数据表、维护定时任务,存在少量DB轮询开销,无法支撑极致高并发场景。

生产选型规范:适配中小电商下单、积分发放、消息通知、库存异步更新等异步业务;大型电商极致高并发场景优先替换为RocketMQ事务消息。

3.6 RocketMQ 事务消息(高并发电商终极方案)

3.6.1 核心原理与生产流程

RocketMQ事务消息是互联网高并发分布式事务最终一致性的工业级标准方案,是本地消息表的官方升级版、中间件托管方案。

传统本地消息表需要开发者手动建表、写定时任务、维护重试逻辑,重复造轮子;而RocketMQ事务消息将消息存储、状态流转、失败重试、事务回查、超时兜底全部内置,开发者仅需专注核心业务逻辑,大幅降低基建成本。

核心设计思想:先发送半消息占位 → 执行本地事务 → 确认消息投递或回滚,彻底解决「消息先发业务后执行、业务成功消息丢失」的经典一致性问题。

完整生产执行四阶段

  1. 发送半消息:生产者向RocketMQ发送对消费者不可见的半事务消息,MQ暂存消息、锁定事务占位,不触发消费逻辑。

  2. 执行本地事务:MQ接收半消息成功后,回调生产者本地事务方法,执行业务数据库操作,依托本地事务保证业务原子性。

3.提交/回滚消息:本地事务执行成功,向MQ发送COMMIT指令,消息对外可见,消费者正常消费;本地事务失败,发送ROLLBACK指令,MQ直接删除半消息。

  1. 定时事务回查(核心兜底):若生产者宕机、网络超时,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持续阶梯回查,是宕机恢复的核心兜底。

相比本地消息表的核心优势

  1. 无需自建消息台账表,消息存储、状态流转全由MQ托管;

  2. 内置定时回查机制,无需手动开发定时重试任务,无DB轮询开销;

  3. 宕机容错能力更强,服务重启后MQ继续回查事务,无事务丢失风险;

  4. 基于MQ天然削峰,完美适配电商大流量、秒杀等高并发场景。

生产强制避坑规则

  1. 回查方法必须幂等,仅做状态查询,禁止新增/修改业务数据;

  2. 本地事务必须严格原子性,所有业务逻辑被数据库事务包裹;

  3. 消费者消费失败必须抛出异常,禁止静默失败,保证MQ重试兜底;

  4. 仅适配最终一致性场景,严禁用于支付、转账等强一致资金业务。

3.6.4 优缺点与生产选型

优势:零自建台账、零手动定时任务、高并发性能极强、工业级重试容错、微服务适配性拉满,无需手动透传事务上下文。

缺陷:强依赖RocketMQ中间件,Kafka、RabbitMQ不支持事务消息能力。

生产选型规范:大型电商、高并发下单、库存更新、异步履约等核心高并发场景首选,是SpringCloud微服务高并发分布式事务的最优解。

四、Seata 框架全模式生产实战

前文手写方案适合原理学习与面试备考,企业生产环境统一使用Seata框架,规避手写方案基建繁琐、容错不全、状态错乱等问题。Seata原生适配SpringBoot单体、SpringCloud微服务双架构,内置XA/AT/TCC/SAGA四大事务模式,覆盖所有生产业务场景。

4.1 Seata核心架构

TC(事务协调器):独立部署的全局事务调度中心,统一管控全局事务状态、通知分支提交/回滚,单体与微服务环境无差异。

TM(事务管理器):集成在业务项目中,负责开启、提交、终止全局事务,微服务场景自动透传事务上下文。

RM(资源管理器):集成于所有业务服务,负责分支事务资源管控、状态上报,适配多数据源、跨服务场景。

4.2 四大模式核心差异与选型总览

  1. XA模式:强一致、零代码侵入、依赖数据库XA协议,锁阻塞严重、并发低,仅适配内部低并发短事务。

  2. AT模式(生产首选):最终一致、零侵入、高并发无锁,适配90%普通微服务异步业务,替代本地消息表简易方案。

  3. TCC模式:强一致、无锁高并发,需手动实现三段式逻辑,适配金融支付、对账等核心资金业务。

  4. 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 生产坑点与解决方案

  1. 高并发快照失效:多线程修改同一数据,快照与实时数据不匹配。解决方案:叠加乐观锁/分布式锁控制并发。

  2. 微服务上下文丢失:自定义Feign拦截器覆盖Seata原生透传。解决方案:兼容X-GLOBAL-TX-ID请求头透传。

  3. 事务烂尾:服务熔断超时小于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-IDX-BRANCH-TX-ID,禁止覆盖、清空事务上下文,否则跨服务事务失效,分支事务无法被全局事务管控。

3. 异常捕获规范 :业务代码中禁止全局捕获Exception且不抛出,@GlobalTransactional依赖异常触发自动回滚。若异常被静默捕获,框架无法感知业务失败,会导致异常事务正常提交,产生脏数据。

4. 多数据源适配规范:项目中存在多数据源、分库分表场景时,必须开启Seata自动数据源代理,所有业务数据库连接统一被框架托管,否则部分数据源无法加入全局事务,出现事务割裂问题。

4.8.2 四大模式专属生产坑点

AT模式专属问题

  1. 快照更新失效:高并发下多条SQL连续修改同一行数据,前置快照与数据库实时数据不匹配,导致回滚失败。解决方案:核心业务叠加乐观锁版本号控制,更新语句携带version条件,避免并发覆盖。

  2. 大事务性能问题:单次全局事务包含大量数据库操作,Undo Log日志过大,事务提交、回滚耗时激增,引发接口超时。解决方案:拆分大事务,非强一致异步逻辑剥离出全局事务。

  3. 只读事务浪费资源:纯查询接口添加全局事务注解,无效占用TC资源。解决方案:查询业务不开启@GlobalTransactional

TCC模式专属问题

  1. 重复重试幂等:Seata会对超时、异常的TCC事务自动重试,Confirm、Cancel方法必须全局幂等,禁止重复新增、修改数据。

  2. 空执行拦截:框架可能提前触发Cancel,此时Try阶段未执行,需依托Seata状态机自动防空回滚、事务悬挂,无需手动维护Redis状态。

  3. 三段逻辑幂等对齐:Try、Confirm、Cancel的业务参数、数据查询条件必须完全一致,避免参数不一致导致补偿失效。

SAGA模式专属问题

  1. 补偿不可逆:SAGA无资源预占,正向事务已提交入库,补偿逻辑执行失败会导致数据永久不一致。解决方案:所有补偿方法单独编写兜底逻辑,支持人工手动修复。

  2. 步骤乱序失效:异步调用、多线程执行业务步骤,会导致正向流程乱序、补偿逻辑匹配失败。解决方案:长流程业务强制串行执行,锁定步骤顺序。

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. 中小团队极简兜底方案

无中间件运维能力、追求稳定低故障:手写本地消息表+定时重试,逻辑透明、无框架黑盒问题、线上故障率极低。

六、线上通用问题排查思路

生产环境分布式事务异常,优先按固定流程排查,快速定位根因,避免盲目调试:

  1. 日志追踪:通过全局TxId串联全链路日志,确认分支事务执行状态、提交/回滚节点是否异常;

  2. 状态校验:查询Seata事务表、MQ事务状态、本地消息表台账,判断事务是正常完结、烂尾还是补偿失败;

  3. 场景定位:区分是并发冲突、上下文丢失、幂等失效,还是超时熔断导致的事务割裂;

  4. 兜底修复:异常烂尾事务优先人工补偿修复,同步迭代代码规避同类问题复现。

七、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动态开关控制任务启停,紧急场景手动关闭。

结语

分布式事务的核心本质并非追求万能方案,而是根据业务一致性等级、并发量级、运维能力做合理取舍。新手常陷入"全场景强一致"的误区,实际生产中,互联网绝大多数业务均可容忍短暂最终一致,仅核心资金业务需要强一致保障。

相关推荐
heimeiyingwang2 小时前
【架构实战】Kubernetes调度器深度剖析:从Pod调度到自定义调度器
java·架构·kubernetes
笨蛋不要掉眼泪3 小时前
RabbitMQ消息队列:MQ的可靠性
分布式·rabbitmq·java-rabbitmq
纳兰青华4 小时前
回文侦探:三种境界破解最长回文子串
java·算法·动态规划
Zane19944 小时前
CAS 与原子类:Java 如何实现无锁编程
java·后端
TunerT_TQ4 小时前
【智能体安全治理|专栏第9期】从“一堆规则”到“数字宪法”:智能体治理的下一个阶段
java·开发语言·安全·开源治理·大模型安全·ai基础设施·智能体安全
legendary_bruce4 小时前
RAG知识库进阶-1
java·aigc
必须会一定会4 小时前
大模型手搓文件对比工具(6):差异不用再手选
java·人工智能·ai编程
yio_yin5 小时前
MyBatis
java·前端·mybatis
程序员-Benothing5 小时前
Java ForkJoinPool 详解:从分治思想到高性能并行计算
java·开发语言·后端·面试·职场和发展