1当「必须强一致」遇上大促峰值:XA 误选导致的悬挂与重复发放
某会员电商平台在重构订单中台时,产品提出「下单、扣库存、发积分必须强一致,用户看到的积分与订单状态不能差一秒」。
架构评审会上,团队将需求直接映射为全局分布式事务,选型 Seata AT 模式,
在 order-service、inventory-service、points-service 三个微服务上开启
@GlobalTransactional,TC 集群部署 3 节点,压测环境验证通过。
大促 T+0 峰值 QPS 达设计值 8000(合成示例),前 10 分钟一切正常。
T+12 min,inventory-service 某分库出现热点 SKU 行锁等待,P99 从 80 ms 升至 2.3 s。
Seata TC 全局会话数从常态 200 飙至 1.8 万,branch_register 延迟上升,
部分全局事务在二阶段提交窗口内超时——订单库已 commit,库存 undo 待回滚,积分服务 Confirm 已执行。
T+18 min,对账脚本发现:悬挂订单 3400 笔(合成示例)——订单状态为「已支付」、库存未扣减、积分已到账; 另有 120 笔重复积分发放,因补偿任务与 Seata 异步回滚并发执行且缺少幂等键。 值班工程师的三个困惑正是本文要拆解的复合模式: 业务真的需要强一致吗?AT 为何在峰值下放大行锁?为何「上了 Seata」仍要本地消息表做对账?
T+25 min,架构组紧急切换:核心下单链路改为本地消息表 + 异步扣库存, 积分发放走 Outbox 消费者;仅「跨主体资金划转」子域保留 TCC。 事后复盘写入架构委员会结论:默认最终一致 + 显式对账,而非默认全局 XA。
上述场景折射出一个在《凤凰架构》中反复强调的误区:把「业务不能出错」等同于「必须强一致分布式事务」。 周志明在书中指出,分布式系统首先要接受分区与延迟客观存在, 架构师的工作是在可接受的异常窗口内选择协调机制—— 是同步阻塞的 2PC/XA,是业务层补偿的 TCC/Saga,还是异步可靠的本地消息表, 取决于资损边界、吞吐预算与运维复杂度,而非名词上的「一致性级别」。
版本说明:本文模式层与语言无关,示例基于 Seata 2.1.0(Apache Seata 2.x 主线,AT/TCC 语义与 1.8.x 兼容) 与 Spring Cloud Alibaba 2023.0.x 默认绑定版本。Seata 1.8.x 在大量生产环境仍运行,机制描述以 Seata 官方文档 与《凤凰架构》分布式事务章节为准。性能数字若无特别说明均为合成示例。
阅读前提:你已理解微服务拆分、数据库本地事务、RPC 超时与消息队列至少一次语义。 本文不再解释「什么是 ACID」,而是聚焦三种主流模式在失败窗口如何耦合、如何用架构手段选型。 55% 篇幅用于谱系与机制推导,30% 用于 Seata 2.1.x 落地与配置边界,15% 留给复合现场与排障顺序。 目标读者为已在生产环境处理过悬挂、重复与对账事故的 P6-P7+ 工程师。
在分区(P)发生时,分布式系统无法在「线性一致(C)」与「可用(A)」之间同时满足——这是 CAP 定理的直接推论。 全局 2PC/XA 在协调者可用时提供强一致,但在协调者故障或长事务持有锁时,可用性与吞吐显著下降。 来源:Brewer CAP、Gray & Reuter 事务处理理论;与《凤凰架构》「分布式的代价」一节一致。
1.1 告警时间线:四类信号交织
复合事务事故的识别特征是全局会话、行锁、业务对账、补偿任务四类信号在同一时间窗重叠。 下表按演练时间线整理(数字为合成示例):
| 时刻 | Seata TC | 数据库侧 | 业务侧 |
|---|---|---|---|
| T+0~10 min | 全局事务 ~200,延迟正常 | 行锁竞争低 | 下单成功率 99.95% |
| T+10~18 min | 会话 1.8 万,register 延迟升 | 热点 SKU 行锁等待 > 2 s | 支付成功但库存偶发超时 |
| T+18~25 min | 二阶段超时、异步回滚队列堆积 | undo_log 表膨胀 | 悬挂订单、重复积分 |
| T+25 min+ | 紧急降级,关闭非核心 @GlobalTransactional | 人工对账 SQL 执行 | 活动公告、补偿发券 |
1.2 与单次超时的边界区分
| 维度 | 单次 RPC 超时(可重试) | 复合事务事故(需架构介入) |
|---|---|---|
| 数据状态 | 单服务本地回滚 | 跨库不一致,需对账 |
| TC 指标 | 全局会话稳定 | branch 注册/报告延迟持续恶化 |
| 锁持有 | 短于单事务 | 全局锁跨多 RPC,放大热点 |
| 触发源 | 网络抖动、单依赖慢 | 误选 XA + 峰值 + 补偿缺幂等 |
1.3 现场应急:有序止损
复合事务事故的恢复顺序应为冻结全局事务入口 → 隔离热点 → 对账 → 补偿:
先关闭非核心链路上的 @GlobalTransactional、再扩容 TC 仅当 register 延迟是瓶颈(否则无效)、
再启动对账脚本标记悬挂、最后才批量补偿且必须带幂等键。
顺序颠倒——尤其「先批量补偿再对账」——往往把悬挂变成重复发放。
架构师在 war room 的第一职责是划定资损冻结窗口:禁止改 undo 策略、禁止手工删 global_table 记录。
1.4 需求翻译:把「强一致」改写成 invariant
复盘会上产品坚持「积分必须与订单同时成功」,架构组用一张 invariant 表完成需求翻译: 「用户最终看到的积分 = 已支付订单应付 × 比例」可在 T+15 min 内通过对账满足; 「支付成功瞬间积分栏非零」仅影响体验,不构成资损 invariant。 前者 Outbox 足够,后者若必须满足才考虑同步写——而同步写仍不必全局 AT,单库双表或 TCC Try 冻结即可。 这一翻译步骤若前置到立项阶段,可避免 80% 的误用 XA(合成经验比例,非统计结论)。 《凤凰架构》强调架构师是业务与技术之间的翻译者——分布式事务选型首先是语义翻译,其次才是框架选型。
2凤凰架构中的分布式事务谱系:从 2PC 到最终一致
《凤凰架构》将分布式事务问题置于演进叙事中:单机 ACID 是默认能力, 拆分微服务后,一次用户操作跨越多个自治数据库,「事务」从存储引擎内部机制变成跨进程协调协议。 周志明强调:架构师应先问「能否用单库事务 + 领域边界」消解问题,再问「若不能,接受何种异常窗口」。
2.1 协调模式谱系
- 强一致 2PC/XA:协调者统一投票,参与者阻塞至 commit/abort。代表:数据库 XA、Seata AT 底层两阶段。代价:锁、协调者单点、长事务吞吐低。
- TCC(Try-Confirm-Cancel):业务层三阶段,Try 预留资源,Confirm 确认,Cancel 释放。强一致语义由业务代码保证,无全局行锁,但侵入性高。
- Saga:长事务拆为本地事务 + 补偿链,正向执行、失败反向补偿。适合长流程,一致性强弱取决于补偿设计与对账。
- 本地消息表 / Outbox:同库写入业务数据与待发消息,异步投递下游。接受最终一致,通过对账与幂等收敛异常窗口。
- 最大努力通知:上游多次通知直至下游 ACK,适合支付回调等场景,需配套查询与对账接口。
用一个下单例子固定直觉:创建订单、扣库存、发积分涉及三个库。 若用 XA/AT,三次写在一个全局事务内——任一参与者慢,全局锁持有时间延长; 若用 TCC,Try 阶段冻结库存与积分额度,Confirm 异步或同步提交; 若用本地消息表,订单库内写订单 + 消息行,库存与积分由消费者至少一次处理并幂等。
在《凤凰架构》语境下,推荐默认立场为: 单库事务优先 → 本地消息表/Outbox → Saga → TCC → 全局 AT/XA(从严到宽排序的是一致性强弱, 从宽到严排序的是推荐优先级)。 仅当业务明确无法容忍秒级不一致且流量可控时,才将 AT/TCC 放上核心热路径。
2.2 一致性与隔离级别:别混淆名词
产品口中的「强一致」常混指三种不同约束: (1)用户可见状态不能矛盾;(2)财务账本不能差分;(3)实时读到的必须是写后最新值。 架构师应将其翻译为可测 invariant:例如「积分总额 = 订单应付金额 × 比例」、 「库存不能为负」。许多 invariant 可在最终一致 + 对账下满足,无需全局 XA。
| 模式 | 一致性强弱 | 吞吐与锁 | 侵入性 | 典型失败窗口 |
|---|---|---|---|---|
| XA / Seata AT | 强(分支全 commit/rollback) | 全局锁、热点敏感 | 低(AT) | 二阶段超时 → 悬挂 |
| TCC | 强(业务 Confirm/Cancel) | 无全局 DB 锁 | 高(三接口) | 空回、悬挂、幂等 |
| Saga | 最终 / 可配置 | 较高 | 中(补偿逻辑) | 补偿失败、乱序 |
| 本地消息表 | 最终一致 | 高 | 中(Outbox 组件) | 重复消费、消息滞后 |
2.3 与微服务边界的耦合
书中「限界上下文」与事务选型直接相关:理想情况下,一个聚合根的生命周期应落在单库内, 跨上下文协作优先用事件而非同步 XA。拆分过细的服务边界若强行 AT, 等于用基础设施掩盖建模缺陷——事务越全局,团队越难独立演进 schema 与发布节奏。 与 T14 微服务治理、T15 异地多活衔接:跨单元场景下全局 2PC 几乎不可用,Saga/TCC + 单元内消息表是常态。
2.4 演进视角:从 JTA 到 Seata 再到云原生
Java 传统 JTA 绑定应用服务器与单厂商数据源;Seata 将 TC/TM/RM 模型开源并适配微服务注册发现; 云厂商托管 DTS 进一步隐藏 TC 运维。选型时不应问「是否上 Seata」,而应问 协调边界放在哪一层、失败时谁对账:框架可以托管协议,资损边界仍属业务架构。
2.5 Saga 与 TCC 的边界(补充谱系)
评审材料常把 Saga 与 TCC 混为一谈。简要区分:Saga 每步是已提交的本地事务, 失败则按逆序调用补偿;中间态对外可见,适合长流程(差旅预订、保险核保)。 TCC 在 Try 与 Confirm 之间资源处于预留态,对外可设计为不可见或「处理中」, 适合短链路、高价值、需避免中间态被误用的场景。 Seata 2.1.0 同时提供 Saga 状态机与 TCC 注解,但状态机编排解决的是步骤顺序与持久化, 不替代补偿接口的幂等设计。若流程超过五步且可容忍中间态,优先 Saga;若涉及资金冻结且步骤少,优先 TCC。 二者均可与 Outbox 组合:Saga 某步完成后发事件驱动下一步,避免同步链过长。
3TCC:业务层三阶段与空回、悬挂、幂等
TCC 将两阶段提交上移到业务语义:Try 预留资源(冻结库存、预扣余额),
Confirm 确认消耗,Cancel 释放预留。Seata 2.1.x 的 TCC 模式通过 @TwoPhaseBusinessAction
注解绑定 Try 与 Confirm/Cancel 方法,TM 仍由 @GlobalTransactional 或 TCC 事务管理器驱动。
3.1 三阶段职责
- Try:检查并预留;必须幂等,重复 Try 返回相同预留结果。
- Confirm:确认业务;仅当 Try 成功后可执行;必须幂等,防重复 Confirm 重复扣款。
- Cancel:释放预留;Try 未 Confirm 时执行;须处理「Try 未到达」的空回。
Seata TCC 模式要求业务提供 Try、Confirm、Cancel 三个方法,
@TwoPhaseBusinessAction(name, commitMethod, rollbackMethod) 声明资源名与二阶段方法名。
TC 记录 branch 状态,二阶段由 TC 异步驱动或同步调用。来源:
Seata 官方文档 · TCC 模式。
3.2 三大经典异常:空回、悬挂、幂等
空回(Empty Rollback):Cancel 先于 Try 到达(网络重排),Cancel 不应报错但须识别「无预留可释」。
悬挂(Hang):Cancel 已执行而 Try 后到,Try 不得再创建有效预留——需事务控制表记录状态。
幂等:Confirm/Cancel 重复调用不得改变已终态结果。生产实现通常引入
tcc_transaction_log 表,以 xid + branch_id + action 为幂等键。
| 异常 | 触发条件 | 后果 | 防御 |
|---|---|---|---|
| 空回 | Cancel 先于 Try | 误释资源 | Cancel 检查 Try 标记;无则空操作 |
| 悬挂 | Cancel 后 Try 到达 | 预留复活,资损 | Try 前查 log,Cancel 后拒绝 Try |
| 重复 Confirm | TC 重试二阶段 | 重复扣款 | Confirm 幂等表终态短路 |
| Confirm 丢失 | TC 故障 | 长期冻结 | 定时对账 + 人工 Confirm |
3.3 TCC 适用边界
TCC 适合跨主体、高价值、流量可控的写路径:资金划转、授信占用、库存预占与确认分离。 不适合:只读聚合、可异步化的通知类副作用、超高 QPS 热点 SKU——Try/Confirm 双写与日志表会成为新热点。 《凤凰架构》在讨论服务容错时也指出:同步协调链越长,与 T10 可靠性四件套的耦合越紧,超时预算必须重算。
3.4 TCC 与 Seata 2.1.0 落地清单
框架训练侧最小清单:为每个参与方实现 Try/Confirm/Cancel;引入 fence 或事务日志表; 在 TM 层设置短于业务 SLA 的全局 timeout;Confirm/Cancel 注册为独立 RPC 且可重入; 压测覆盖「Try 超时」「Confirm 重复」「Cancel 先于 Try」三条路径。 与 AT 不同,TCC 分支注册不依赖 undo_log,但业务日志表成为新运维对象—— 需纳入备份、慢查询监控与数据保留策略,避免 fence 表膨胀成为下一个 hidden bottleneck。
团队仅在接口上加 @TwoPhaseBusinessAction,Try 直接扣减库存而非冻结,
Cancel 用「加回库存」——本质仍是非幂等写,Confirm 失败时无法恢复。
TCC 要求 Try 阶段可撤销的预留语义,不是把本地事务拆成三个 RPC 名字。
4Seata AT:无侵入两阶段与 undo_log 失败窗口
AT(Automatic Transaction)模式是 Seata 默认推广模式:业务 SQL 无需改写法,
RM 拦截本地 SQL,一阶段提交并写 undo_log,二阶段 TC 通知全局 commit 或 rollback。
Rollback 通过 undo 镜像生成反向 SQL,实现无业务侵入的补偿。
4.1 一阶段与二阶段机制
- 解析 SQL,查询前镜像(before image),执行业务 SQL,查询后镜像(after image)。
- 插入
undo_log,提交本地事务(释放本地锁,但全局锁由 TC 协调)。 - 向 TC 注册 branch;全局 commit 时异步删除 undo_log;rollback 时用 undo 生成补偿 SQL。
Seata AT 一阶段在本地事务提交后持有全局锁(默认 AT 模式), 二阶段 commit 仅删 undo_log;rollback 读 undo_log 生成反向更新。 要求数据库支持可解析元数据(MySQL/InnoDB 等)。来源: Seata 官方文档 · AT 模式。
4.2 全局锁与热点行
AT 在全局事务未 commit 前,对修改行持有全局锁,其他全局事务不能修改同一行—— 这是避免脏写的代价。大促热点 SKU 若走 AT,多个用户的全局事务竞争同一行, 等价于把分布式锁集中在单行上,P99 急剧上升。架构评审应识别热点实体, 热点写路径改用 TCC 预留、或异步扣减 + 对账,而非全局 AT。
4.3 AT 的典型失败窗口
- 二阶段 commit 超时:一阶段已提交,TC 未收到 commit 响应 → branch 状态 UNKNOWN,需 TC 重试与人工介入。
- rollback 失败:undo 与当前数据不一致(业务手工改数)→ 需对账修复。
- undo_log 堆积:大促峰值 + 长全局事务 → 表膨胀,影响磁盘与清理任务。
4.4 AT 与本地事务混用
同一服务内,未纳入全局事务的本地写与 AT 分支并发时,可能出现脏读业务逻辑: 本地写已可见,AT 分支随后 rollback。规范是:全局事务边界内全部 DML 走同一数据源代理, 只读查询若需一致视图应放在全局 commit 之后或使用快照读。Seata 2.x 支持多数据源代理, 但每个 RM 仍对应独立 branch,跨库热点问题不因此消失。
在 @GlobalTransactional 方法内声明
@Transactional(propagation = REQUIRES_NEW) 时,
内层提交的分支可能未被 Seata 代理正确注册,全局 rollback 时留下「已提交孤岛」。
复合场景中的部分悬挂订单,经排查根因是积分服务在独立新事务中写入了流水,
却未纳入 xid 管辖。规范:全局事务方法内禁止随意 REQUIRES_NEW;
若必须独立提交,该写路径应移出全局边界或改 Outbox。
4.5 Seata 1.8.x 与 2.1.0 迁移注意
生产环境大量集群仍运行 Seata 1.8.x(Spring Cloud Alibaba 2021.x 绑定)。
2.1.0 在 TC 高可用、配置中心集成与 metrics 暴露上增强,但 AT/TCC 核心语义不变。
升级路径建议:先升级 TC 至 2.1.0 并保持 RM 1.8.x 客户端兼容窗口,验证
global_table 迁移脚本与 Nacos 配置项命名变更;再分批升级 RM。
切勿在大促窗口做 TC 大版本跳跃——曾有机房在升级窗口丢失未完成二阶段会话,
触发批量 UNKNOWN branch,对账耗时整周末。
5本地消息表:最终一致、Outbox 与对账闭环
本地消息表(Transactional Outbox)的核心思想: 在同一本地数据库事务内完成业务写入与「待发送消息」写入, 再由独立投递器(Polling Publisher 或 CDC)将消息发往 MQ, 下游消费者幂等处理,达成跨服务最终一致。 《凤凰架构》将其归入「可靠事件/可靠消息」—— 用消息交换替代跨库 2PC,是互联网规模下最常用的基线方案。
5.1 表结构与状态机
典型 Outbox 表字段:id、aggregate_type、aggregate_id、
payload、status(NEW/SENT/FAILED)、created_at、sent_at。
投递器扫描 NEW 记录 → 发 MQ → 更新 SENT;失败则指数退避重试,超过阈值告警人工介入。
与 Seata 无关,不依赖 TC——一致性的锚点是本地 ACID + 至少一次投递 + 消费幂等。
轮询投递器切勿 SELECT * FROM outbox WHERE status='NEW' LIMIT 1000 无索引扫全表。
生产应使用 status + created_at 复合索引,或 Debezium CDC 读 binlog 推 Kafka。
曾有多起事故在峰值因 Outbox 扫表打满从库 IOPS,与业务 SQL 争抢连接——
Outbox 表应独立分库或限流投递,并与业务表分开容量规划。
5.2 与 Seata 的协作关系
复合场景紧急降级后,团队将「扣库存 + 发积分」改为 Outbox,并非否定 Seata, 而是按步骤拆分一致性强弱: 订单创建与支付状态仍可在单库本地事务内完成; 库存扣减消息被消费后,消费者用业务幂等键(orderId + skuId)扣减; 积分同理。悬挂订单通过对账脚本发现「已支付无扣库存消息 SENT」即可补偿重发。 此模式异常窗口可预测(MQ P99 + 消费延迟),且峰值不经过 TC。
5.3 Inbox 与幂等表
消费侧建议 Inbox 表或 Redis 幂等键,记录 messageId 或 bizId,
插入唯一约束防重复。复合场景中 120 笔重复积分,若消费端有
UNIQUE(order_id, event_type),第二次插入会失败并告警,而非静默重复。
架构评审检查项:生产端 Outbox + 消费端 Inbox 成对出现,缺一即半套方案。
5.4 CDC 投递 vs 轮询:容量与延迟权衡
轮询投递实现简单,但在 Outbox 表超过千万行后,即使有索引,峰值扫描仍可能与业务争抢 IOPS。 Debezium 等 CDC 方案读 binlog,延迟通常低于轮询一个数量级,且对业务库压力更小, 但引入 Kafka Connect 集群与 schema 变更协调成本。架构师选型应问: 异常窗口 P99 要求是否低于 500 ms?若是,倾向 CDC; 若可接受秒级且团队规模小,轮询 + 分区 Outbox 表足够。 《凤凰架构》在事件驱动章节强调:投递可靠性不取决于 MQ 品牌,而取决于 业务库与消息系统的原子写入边界是否画对。
复合场景降级后,团队将积分 Outbox 从轮询改为 Canal 推 MQ,投递 P99 从 2.1 s 降至 180 ms(合成示例), 近线对账悬挂笔数下降 70%——并非 CDC 魔法,而是缩短异常窗口使补偿任务不必与峰值重叠。
| 环节 | 失败模式 | 防护 |
|---|---|---|
| 本地事务 | 业务写成功 Outbox 未写 | 同一 @Transactional |
| 投递 | MQ 不可用 | 重试 + 死信 + 告警 |
| 消费 | 重复消息 | 幂等键 / Inbox |
| 消费 | 处理一半崩溃 | 可重入逻辑 + 状态机 |
| 对账 | 长期不一致 | 定时批对账 + 补偿 |
6Seata 2.1.0 落地:TC/TM/RM 与 SCA 集成要点
Seata 架构分三类角色:TC(Transaction Coordinator)维护全局会话;
TM(Transaction Manager)开启/提交/回滚全局事务;
RM(Resource Manager)管理分支事务与 undo。
Spring Cloud Alibaba 2023.0.x 通过 starter 自动注册 RM,TM 由 @GlobalTransactional 触发。
本节聚焦生产可运维的配置边界,而非 Hello World。
6.1 部署与 HA
- TC 2.x 默认内置 DB 存储(
store.mode=db),多节点需共享global_table/branch_table/lock_table。 - TC 不应与业务 RM 同库——曾有机房故障 TC 表与业务表同实例,主库切换后全局事务元数据丢失。
- Seata 2.1.0 支持 Raft(部分部署模式),生产更常见仍是 DB 模式 + 多 TC 无状态扩容。
6.2 关键配置项
# application.yml 片段(Seata 2.1.0 + SCA 2023.0.x)
seata:
enabled: true
application-id: order-service
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: seata-tc-1:8091,seata-tc-2:8091
registry:
type: nacos
nacos:
server-addr: ${NACOS_ADDR}
group: SEATA_GROUP
config:
type: nacos
client:
rm:
async-commit-buffer-limit: 10000
report-retry-count: 5
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
default-global-transaction-timeout 必须小于网关与调用链总 SLA,且大于最慢 RM P99 的 2 倍。
复合场景中 TC 会话 1.8 万,常与超时过长 + 重试叠加有关——
全局事务内 RPC 应禁止多层重试(见本系列 T10)。
6.3 undo_log 运维
每个 RM 库需 undo_log 表;Seata 2.x 提供定时清理已 commit 分支的 undo。
若回滚失败,undo 堆积会导致磁盘与回滚延迟上升——监控项:
undo_log 行数、SeataMetrics 中 rollback 失败率、TC branch_status 分布。
1.8.x 与 2.1.0 表结构兼容,升级时先升级 TC 再升级 RM。
tx-service-group 映射到 TC 集群通过 vgroup-mapping 与 registry 完成;
多环境必须隔离 Nacos namespace,避免 test TC 处理 prod 全局事务。
来源:Seata 2.1.0 部署文档与 SCA Seata 集成指南。
6.4 与 Nacos 的配置纪律
Seata 服务端 registry.conf / file.conf 在 2.x 可全部迁入 Nacos。
变更推送秒级生效,但大促期间禁止修改 global-transaction-timeout 与 store.mode。
配置中心需 prod 双人复核;TC 地址变更需同时验证所有 RM 的 grouplist 或 registry 缓存刷新。
6.5 TM 侧编程约束(框架训练)
@GlobalTransactional 应只出现在编排层(应用服务 / 领域服务入口),
不应散落在 DAO 或深层工具类——否则事务边界难以审计,且 AOP 代理可能因自调用失效。
全局事务方法内 RPC 调用须配置短于全局 timeout 的单跳超时,并与 T10 一致:
禁止在全局事务内对写操作多层重试。Seata 2.1.0 支持 rollbackFor / noRollbackFor
精细控制哪些异常触发全局回滚;业务校验失败(库存不足)应抛受检业务异常且
noRollbackFor 不应滥用——仅当确定该异常不破坏跨库 invariant 时使用。
压测时应单独打点 seata.global.tx.duration 与 branch.register.latency,
绘制「全局会话数 — TC CPU — 注册延迟」三维曲线,作为大促容量基线。
若 register P99 超过 200 ms(合成阈值),应优先减全局事务覆盖面,而非盲目扩容 TC——
会话数与业务误用 AT 强相关,扩容 TC 不解热点行锁。
7幂等、对账与悬挂治理:三种模式的公共底座
无论 TCC、AT 还是 Outbox,幂等与对账是生产安全的公共底座。 《凤凰架构》在「可靠通讯」中强调:分布式环境下「恰好一次」不可追求, 架构应设计为至少一次 + 幂等 + 对账。 复合场景中 3400 笔悬挂与 120 笔重复,根因是底座缺失而非 Seata 版本 bug。
7.1 幂等键设计
- 全局事务:以
xid+branchId或业务orderId为键。 - TCC:Try/Confirm/Cancel 各记录 fence 表,主键
xid + action。 - Outbox 消费:
messageId或(aggregate_id, event_type)唯一。
7.2 对账分层
| 层级 | 频率 | 发现 | 动作 |
|---|---|---|---|
| 实时 | 秒级 | 消费失败进 DLQ | 自动重试 N 次 |
| 近线 | 5~15 min | 订单已支付 vs 库存已扣 | 补偿消息 / 工单 |
| 日批 | T+1 | 积分总额 vs 订单应付 | 财务调账 |
| Seata 专项 | 小时 | global_table 长时间 committing | TC 运维 / 人工 rollback |
7.3 悬挂订单治理 Runbook
- 查
global_table中status=1( committing )超过 10 min 的 xid。 - 对照各 RM
undo_log与业务表,确定已 commit 与待 rollback 分支。 - 禁止直接 DELETE global 记录——应通过 TC 控制台或 API 触发 rollback。
- 业务对账脚本输出悬挂清单,补偿必须走幂等接口。
7.4 对账 SQL 设计原则(架构师视角)
对账不是 DBA 临时写的 JOIN,而是与 invariant 一一对应的可重复作业。 每条对账规则应声明:左表事实(订单已支付)、右表事实(库存已扣或 Outbox 已 SENT)、 差额动作(补发消息 / 触发 rollback / 工单)。 近线对账频率应短于业务可接受异常窗口——若产品接受积分 15 min 延迟,对账周期应 ≤ 5 min, 留出补偿执行时间。日批对账面向财务 invariant,与近线技术对账互补,不可互相替代。 Seata 专项对账应纳入同一平台,避免 TC 悬挂仅由中间件团队知晓而业务不知情。
8场景适配矩阵与 TCC / AT / Outbox 组合
单模式文档看多了容易「全局 @GlobalTransactional 一把梭」。 架构师需要的是场景 → 模式组合的决策矩阵,而非清单式堆叠。 下表基于《凤凰架构》一致性与《Seata 2.1.0 模式说明》整理,数字边界为合成建议。
8.1 场景适配矩阵
| 场景 | 首选 | 备选 | 避免 | 备注 |
|---|---|---|---|---|
| 大促下单 + 扣库存 + 积分 | Outbox + 幂等消费 | TCC 库存 Try | 全局 AT 热点 SKU | 与 T12 秒杀异步一致 |
| 跨主体资金划转 | TCC 冻结/Confirm | 隔离子域 XA | 纯 Outbox 无对账 | 监管可能强制同步 |
| 内部 Admin 批量调账 | 本地事务 + 日批对账 | Saga 编排 | 全局 AT 长事务 | 低 QPS 可接受 |
| 支付回调通知商户 | Outbox + 最大努力通知 | — | TCC | 外部系统不可控 |
| 跨单元多活写(T15) | 单元内本地 + Saga | TCC 少步 | 跨单元 2PC | 号段路由优先 |
8.2 模式对比四维表
| 维度 | TCC | Seata AT | 本地消息表 |
|---|---|---|---|
| 一致性强弱(正常路径) | 同步,预留态可见 | 同步,接近强一致 | 最终一致 |
| 开发成本 | 高 | 低 | 中 |
| 运维对象 | TC + fence 表 | TC + undo_log | Outbox + MQ + 投递器 |
| 峰值吞吐 | 较好 | 热点受限 | 核心路径短 |
| 故障典型态 | 空回/悬挂 | 二阶段超时悬挂 | 消息堆积 / 重复消费 |
组合得当
- 核心写 Outbox,仅资金子域 TCC
- 全局 AT 限定 2 RM、非热点、短超时
- 消费端 Inbox 幂等 + 分层对账
- 大促前冻结 Seata/Nacos 配置
组合失当
- 全链路 @GlobalTransactional
- AT + 热点 SKU + 全局事务内重试
- Outbox 无消费幂等
- 补偿与 Seata 回滚并发无锁
8.3 与《凤凰架构》章节映射
书中「分布式的代价」「可靠事件」「服务容错」三章与本篇三模式对应: 代价章讲为何不能默认 XA;可靠事件讲 Outbox;容错讲补偿与幂等。 架构师评审时应要求方案说清异常窗口长度、对账频率、TC 容量预算, 而非笼统「用 Seata 保证一致性」。与 T10 衔接:全局事务内禁止重试风暴; 与 T12 衔接:秒杀成交与库存确认应 Outbox 异步,而非同步 AT。
8.4 评审会三问(沉淀为团队 RFC)
建议在架构评审模板固定三问,避免每项目重复争论: (1)跨库写能否合并为单库或异步?——若不能,写清 invariant 与窗口; (2)同步路径是否含热点实体?——若有,默认禁止 AT; (3)对账与补偿谁负责、频率多少、幂等键是什么?——无答案则方案不完整。 三问耗时不足 15 分钟,却可过滤多数「全局事务一把梭」方案。 与 T14 微服务治理衔接:拆分越细,三问越应严格——边界越多,协调成本指数上升。
8.5 典型踩坑案例速查(合成摘要)
案例 A(全链路 AT):六个微服务全部 @GlobalTransactional,大促 TC 会话 2 万,register 超时。
规避:仅 2 RM 短事务 AT,其余 Outbox。案例 B(Outbox 无幂等):MQ 重复投递导致积分双倍。
规避:Inbox 唯一键 + 消费状态机。案例 C(TCC 空回):Cancel 未判 Try 是否到达,库存被误释。
规避:fence 表 + Try 前查状态。三案例共同规律:模式本身不错,边界画错。
9War Room:悬挂订单、undo 堆积与紧急降级
本篇 15% 现场权重集中在本章:当 TC Dashboard 仍显示「正常」、 而业务对账已发现悬挂时,排障顺序与 T10 雪崩不同—— 重点是全局会话状态、undo 队列、补偿并发三线并行。
9.1 三类表象与根因
| 表象 | 优先查 | 常见根因 |
|---|---|---|
| 订单已支付,库存未扣 | Outbox SENT / 消费 DLQ | 消费失败或全局 AT 二阶段 rollback 未完成 |
| 积分重复发放 | 消费幂等表 / fence | 补偿与 Confirm 并发无唯一键 |
| TC CPU 高、register 慢 | global_table 会话数 | 峰值全局事务 + 超时过长 |
| undo_log 暴涨 | rollback 失败率 | 大字段更新 / 从库延迟 |
9.2 紧急降级阶梯
- L0:关闭非核心链路的
@GlobalTransactional(Feature Flag)。 - L1:热点 SKU 改本地预扣 + Outbox,绕过 AT。
- L2:暂停积分等弱一致步骤,仅保障成交与库存。
- L3:TC 只读模式 + 人工对账(极端)。
复合场景在 T+25 min 执行的是 L1+L2:并非放弃一致性,而是收缩同步边界。 降级后 24 h 内必须完成悬挂清账与 Postmortem,写入 TC 容量与 Outbox 投递指标基线。
运维若直接 UPDATE global_table 状态为 committed,而 RM 侧 undo 未删,
后续异步回滚可能错误撤销已修复数据——形成「二次悬挂」。
正确路径:Seata Server API / 控制台触发 rollback,或按官方运维手册处理异常 session。
任何手工 SQL 修 TC 元数据需架构师双人复核并留审计。
9.3 Postmortem 必填字段
每次事务类事故复盘应写入: 峰值 global session 数、二阶段超时占比、 悬挂订单笔数与资损估算、Outbox 投递滞后 P99。 四字段缺失无法反馈到「是否误用 AT」的架构决策,只会重复「下次优化 TC」。
9.4 War Room 话术:避免错误共识
现场常见三句误判需架构师主动纠正: 「TC 监控正常说明事务没问题」—— TC 正常只说明协调者活着,不说明 branch 已一致; 「先补偿用户再查根因」—— 无幂等补偿扩大资损; 「临时把 timeout 调大撑过去」—— 会话堆积延迟爆炸,Undo 表膨胀。 正确话术是:先冻结全局入口、导出悬挂清单、并行查 TC 与 Outbox 两通道。 与 T10 雪崩排障不同,事务事故CPU 可能不高,因为线程阻塞在 RPC 与二阶段等待—— 勿因 CPU 低而否定事务层异常。合成场景里 order-service CPU 38%、悬挂 3400 笔,即属此类。
与系列 T07 衔接:若库存扣减通过 Kafka 消费,需明确消息语义与 Seata 分支的边界—— 同一业务步骤不能既走 AT 又走 MQ 且无对账,否则出现「双通道写」。
10分布式事务选型检查表、总结与延伸阅读
10.1 上线与大促前检查表
| 检查项 | 验收标准 | 责任人 |
|---|---|---|
| 强一致必要性论证 | 文档化异常窗口与资损边界;产品/架构签字 | 架构 |
| 默认路径 | 可异步步骤走 Outbox;非必要无 @GlobalTransactional | 架构 / 开发 |
| AT 使用边界 | ≤2~3 RM、非热点、timeout < 链 SLA、无内层 REQUIRES_NEW | 开发 |
| TCC 三接口 | 空回/悬挂/幂等测试用例通过;fence 表就绪 | 开发 / QA |
| Outbox + Inbox | 生产消费成对幂等;索引与 CDC 压测过 | 开发 / SRE |
| 对账分层 | 近线 + 日批脚本;DLQ 告警 | 业务 / SRE |
| TC 容量 | 峰值 global session 压测;TC 独立库 | SRE |
| 配置纪律 | Nacos 环境隔离;大促冻结 Seata 配置 | SRE |
| 降级预案 | L0~L2 Feature Flag 演练通过 | 架构 |
| 与 T10/T12 衔接 | 全局事务内无重试风暴;秒杀异步扣库存 | 架构 |
10.2 三句话总结
第一,「必须强一致」是需求翻译问题——默认应本地消息表 + 幂等 + 对账,而非默认全局 XA。 第二,Seata AT 适合短、非热点、少参与方同步写;TCC 适合可定义预留语义的资损路径; 二者都需要 TC 容量与 undo/fence 运维,不能替代对账。 第三,复合事故的共同特征是模式误选 + 峰值放大 + 补偿缺幂等—— 选型树与检查表的价值在于把 war room 经验前移到架构评审。
10.3 延伸阅读
- BOOK周志明:《凤凰架构》—— 分布式的代价、可靠事件、服务容错相关章节
- DOCApache Seata 2.1.0 官方文档 —— AT / TCC 模式、部署与运维
- DOCSpring Cloud Alibaba 2023.0.x · Seata 集成
- PAPERGray & Reuter:《Transaction Processing: Concepts and Techniques》—— 2PC 理论基础
- SERIES本系列 T10 可靠性 · T12 秒杀 · T15 多活 —— 事务边界与异步化衔接
下一篇 T14 微服务治理演进将进一步讨论拆分后治理阶段与事务边界的关系; 本篇强调的「先缩边界、再选协调」应作为拆分评审的默认前置问题。
选型没有银弹:Seata 2.1.0 降低 AT/TCC 接入成本,但不替业务定义 invariant; 本地消息表降低峰值 TC 压力,但把复杂度转移到投递与对账。 资深架构师的产出不是「选了哪个框架」,而是写清异常窗口、验收对账、冻结变更纪律的可执行文档—— 这才是在 composite 场景里比框架版本更长期有效的资产。 评审时请携带本文第 8 章决策树与第 10 章检查表,作为分布式事务选型的最小交付物。