4. 分布式事务(下):TCC、Saga、本地消息表,风控中的选型对比

好,咱们接着聊分布式事务的下半场。

上一章我们把两阶段提交(2PC)和最大努力通知讲透了。这一章,我重点聊聊风控场景里真正用得上的三种模式:TCCSaga本地消息表

说实话,2PC 在风控里几乎没人用——性能太差,而且阻塞。你想想看,风控系统每天要处理几千万次请求,要是每个请求都锁资源,系统早挂了。

4.1 TCC 模式:Try-Confirm-Cancel

TCC 是「补偿型事务」的典型代表。它把一笔分布式事务拆成三个阶段:

  • Try:预留资源,检查业务可行性
  • Confirm:确认执行,真正提交
  • Cancel:取消执行,释放预留资源

我在风控项目中用过 TCC 来处理「额度扣减」的场景。举个例子:用户发起一笔转账,风控系统需要先冻结这笔额度,等业务系统确认转账成功后,再真正扣减。

// TCC 接口定义示例
public interface QuotaTccService {
    
    // 第一阶段:Try,预留额度
    boolean tryFreeze(QuotaRequest request);
    
    // 第二阶段:Confirm,确认扣减
    boolean confirmDeduct(QuotaRequest request);
    
    // 第三阶段:Cancel,释放额度
    boolean cancelFreeze(QuotaRequest request);
}

核心要点:TCC 要求每个参与者都实现 Try、Confirm、Cancel 三个接口。Confirm 和 Cancel 必须保证幂等。

嗯,这里要注意。TCC 的 Try 阶段如果失败了,整个事务直接回滚,不会执行 Confirm。但如果 Try 成功了,Confirm 或 Cancel 就必须被执行成功——哪怕重试也要成功。

我曾经踩过的坑:有一次 Confirm 接口因为数据库连接池满了,一直重试失败。结果用户的额度被冻结了整整 30 分钟。后来我加了个「异步补偿线程」,专门处理那些重试失败的 Confirm/Cancel 请求。

4.2 Saga 模式:长事务的救星

Saga 模式跟 TCC 有点像,但思路完全不同。

Saga 把一个大事务拆成多个本地事务,每个本地事务都有对应的补偿操作。如果某个本地事务失败了,就依次执行前面所有事务的补偿操作。

我个人习惯把 Saga 分成两种实现方式:

  • 编排式(Choreography):每个服务自己监听事件,自己决定下一步做什么
  • 协调式(Orchestration):一个中央协调器统一调度所有步骤

在风控场景里,我推荐用协调式。为什么?因为风控流程太复杂了,编排式容易让逻辑散得到处都是,出了问题很难排查。

// Saga 协调器伪代码
public class RiskSagaOrchestrator {
    
    public void executeRiskCheck(RiskContext ctx) {
        try {
            // 步骤1:用户身份校验
            identityService.check(ctx);
            // 步骤2:规则引擎匹配
            ruleEngine.evaluate(ctx);
            // 步骤3:额度检查
            quotaService.check(ctx);
            // 步骤4:写入风控结果
            riskResultService.save(ctx);
        } catch (Exception e) {
            // 补偿:逆序执行
            riskResultService.compensate(ctx);
            quotaService.compensate(ctx);
            ruleEngine.compensate(ctx);
            identityService.compensate(ctx);
        }
    }
}

我的经验:Saga 的补偿操作一定要设计成「可重入」的。我曾经遇到过补偿操作执行了一半,系统又挂了,结果数据处于「半补偿」状态,查了好久才定位到问题。

4.3 本地消息表:最朴素但最可靠

本地消息表,说白了就是「业务操作 + 消息记录」放在同一个本地事务里。

流程是这样的:

  1. 业务操作和消息记录在同一个数据库事务中提交
  2. 后台有个定时任务,扫描未发送的消息,重新投递
  3. 消费者处理成功后,回调更新消息状态

你想想看,这个方案虽然「土」,但胜在简单可靠。我在早期做风控系统时,就用本地消息表来处理「风控结果同步到业务系统」的场景。

-- 本地消息表结构
CREATE TABLE local_message (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    business_id VARCHAR(64) NOT NULL COMMENT '业务ID',
    message_body TEXT NOT NULL COMMENT '消息内容',
    status TINYINT DEFAULT 0 COMMENT '0:待发送 1:已发送 2:已确认',
    retry_count INT DEFAULT 0 COMMENT '重试次数',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 定时任务扫描待发送消息
SELECT * FROM local_message 
WHERE status = 0 
  AND retry_count < 3 
  AND create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR);

注意:本地消息表有个硬伤——它要求业务数据库和消息表在同一个数据库实例里。如果业务库已经分库分表了,这个方案就不太适用了。

4.4 风控场景选型对比

好了,三种模式都讲完了。咱们直接上对比表,一目了然:

对比维度 TCC Saga 本地消息表
一致性级别 强一致性 最终一致性 最终一致性
性能 中等(有网络开销) 高(异步执行) 高(本地事务)
实现复杂度 高(需实现3个接口) 中(需设计补偿逻辑) 低(表结构+定时任务)
适用场景 额度冻结、库存扣减 长流程风控、多步骤审核 结果同步、状态通知
隔离性 支持(Try阶段加锁) 不支持(需业务层处理) 不支持
典型风控用例 交易额度预占 反欺诈多步骤审核 风控结果推送

我个人建议这样选:

  • 如果业务要求强一致性,比如额度预占、资金冻结,选 TCC
  • 如果业务流程很长,比如反欺诈审核要经过 5-6 个步骤,选 Saga
  • 如果只是异步通知,比如风控结果同步给下游,本地消息表就够了

一句话总结:没有银弹。TCC 适合短事务强一致,Saga 适合长事务弱一致,本地消息表适合简单场景。选型时别贪心,够用就好。

4.5 核心知识体系

下面这张图,是我梳理的本章知识结构。你可以把它当作选型时的「速查地图」:

分布式事务模式选型对比 TCC 模式 Try → Confirm / Cancel 强一致性 适用:额度冻结 Saga 模式 编排式 / 协调式 最终一致性 适用:长流程审核 本地消息表 业务 + 消息同事务 最终一致性 适用:结果同步 选型建议 强一致 → TCC | 长流程 → Saga | 简单通知 → 本地消息表 ⚠ 避坑:补偿操作必须幂等,重试机制必须健壮 分布式风控系统一致性保障实战 · 第4章

好了,这一章的内容就到这里。三种模式各有优劣,关键是根据你的业务场景做取舍。记住,没有完美的方案,只有合适的方案。