3. 分布式事务进阶:三阶段提交(3PC)、TCC模式、Saga模式、本地消息表

在分布式系统中,两阶段提交(2PC)虽然提供了强一致性保证,但其同步阻塞、单点故障和脑裂风险在高并发场景下逐渐成为瓶颈。本章深入剖析三阶段提交(3PC)、TCC模式、Saga模式与本地消息表四种进阶方案,从设计原理、适用场景到可观测性埋点策略,帮助你在连接池水位巡检与OpenTelemetry集成中精准定位事务异常。

3.1 三阶段提交(3PC)—— 引入超时与预提交

3PC是2PC的改进版本,将2PC的“提交请求-提交”两阶段拆分为“CanCommit、PreCommit、DoCommit”三个阶段,并在协调者和参与者中均引入超时机制,降低阻塞范围。

核心改进点: 参与者拥有超时后的自动中止能力,避免2PC中参与者无限等待协调者恢复的“悬挂”问题。
三阶段提交(3PC)时序流程 协调者 参与者1 参与者2 CanCommit? Yes Yes PreCommit ACK ACK DoCommit Done Done 说明:参与者若在PreCommit后超时未收到DoCommit,自动中止事务,避免悬挂
对比维度2PC3PC
阶段数2(准备+提交)3(CanCommit+PreCommit+DoCommit)
阻塞点参与者准备后阻塞等待PreCommit后参与者可超时中止
脑裂风险协调者故障时参与者无法决策参与者超时后自行中止,降低脑裂
网络开销2轮交互3轮交互,延迟更高
一致性强度强一致性最终一致性(可能部分中止)
运维警示: 3PC在PreCommit阶段后如果网络分区,部分参与者可能提交而部分中止,导致数据不一致。在连接池水位巡检中,需监控PreCommit阶段的超时次数和参与者响应分布。

3.2 TCC模式——Try-Confirm-Cancel

TCC模式是一种补偿型事务方案,将每个服务操作拆分为Try(预留资源)、Confirm(确认执行)、Cancel(取消回滚)三个阶段。业务方需要自行实现正向和反向操作,适用于短事务、高吞吐场景。

最佳实践: TCC的Try阶段应尽量轻量,只做资源预留而非实际提交;Confirm和Cancel必须幂等,以应对网络重试。
TCC模式执行流程 事务管理器 服务A 服务B Try OK OK Confirm Done Done 说明:若任意服务Try失败,事务管理器对所有已Try成功的服务执行Cancel Confirm/Cancel必须幂等,支持重试;Try阶段只做资源预留

TCC与2PC/3PC的对比

特性2PC/3PCTCC
资源锁定数据库层面锁定(XA)业务层面预留(如冻结库存)
性能低(锁竞争+多轮交互)高(Try轻量,无长锁)
侵入性低(中间件代理)高(需业务实现Try/Confirm/Cancel)
一致性强一致性最终一致性
典型场景跨库转账、金融核心订单支付、库存扣减
可观测性埋点建议: 在TCC的Try、Confirm、Cancel三个入口和出口分别记录Span,标记事务ID、服务名、资源类型、耗时和结果。连接池水位巡检中,若Try阶段连接池占用率持续高于70%,需排查资源预留是否未及时释放。

3.3 Saga模式——长事务的补偿艺术

Saga模式将一个大事务拆分为多个本地事务,每个本地事务对应一个补偿操作。Saga有两种执行方式:Choreography(编排)和Orchestration(编排器)。前者通过事件驱动,后者由中央协调器控制流程。

选型建议: 服务数量少(≤5)且流程固定时用Choreography;服务多、流程复杂或需要统一监控时用Orchestration。
Saga Orchestration 模式流程 Saga协调器 订单服务 支付服务 库存服务 ①创建订单 ②扣款 ③扣库存 取消订单 退款 加回库存 说明:正向流程按顺序执行本地事务;任一失败则按反向顺序执行补偿操作 补偿操作必须幂等,且能处理部分成功场景

Saga与TCC的核心差异

维度TCCSaga
资源预留Try阶段预留资源不预留,直接提交本地事务
补偿时机Cancel阶段释放预留失败后执行反向补偿操作
隔离性Try阶段提供一定隔离无隔离,需业务层处理脏读
实现复杂度高(需三阶段逻辑)中(需正向+补偿逻辑)
典型场景短事务、高并发扣减长事务、跨多服务流程
连接池水位风险: Saga的补偿操作可能因网络延迟或服务故障而重试多次,导致数据库连接池被补偿事务占满。建议在补偿操作中设置独立的连接池隔离池,并监控补偿事务的并发数。

3.4 本地消息表——最终一致性的经典实现

本地消息表将分布式事务拆解为“本地事务+消息队列”的组合:业务操作和消息写入在同一个本地事务中完成,消息由异步任务确保最终投递到下游服务。该方案无XA开销,适用于对实时性要求不高的场景。

核心原理: 消息表与业务表在同一个数据库中,利用本地事务保证“业务操作”和“消息记录”的原子性;后台定时任务扫描未投递消息并重试。

本地消息表执行步骤

  1. 开启本地事务: 在服务A中开启数据库事务。
  2. 执行业务操作: 更新业务表(如订单状态)。
  3. 插入消息记录: 在本地消息表中插入一条状态为“待投递”的消息。
  4. 提交本地事务: 事务提交后,业务数据和消息同时持久化。
  5. 异步投递消息: 后台定时任务或消息队列客户端读取“待投递”消息,发送到MQ。
  6. 下游消费: 服务B消费消息并执行本地操作,成功后回调确认消息状态为“已投递”。
  7. 失败重试: 投递失败或下游处理失败时,消息保持“待投递”状态,定时任务重试。

本地消息表结构示例

CREATE TABLE local_message (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    business_id VARCHAR(64) NOT NULL COMMENT '业务ID',
    message_body TEXT NOT NULL COMMENT '消息体(JSON)',
    status TINYINT NOT NULL DEFAULT 0 COMMENT '0:待投递 1:已投递 2:已消费',
    retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
    max_retry INT NOT NULL DEFAULT 5 COMMENT '最大重试次数',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_status_retry (status, retry_count)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

四种方案综合对比

方案一致性隔离性性能侵入性典型场景
3PC强(可能降级)高低低跨库强一致
TCC最终中(Try预留)高高高并发扣减
Saga最终低(无隔离)高中长事务流程
本地消息表最终低高中异步解耦
可观测性集成要点: 在本地消息表的定时任务扫描逻辑中,记录每次扫描的消息数量、投递成功/失败次数、重试分布。结合OpenTelemetry,为每条消息生成独立的Span,追踪从写入到消费的全链路耗时,帮助定位消息堆积或投递延迟。