分布式事务、补偿与消息一致性
知识图谱编号:
2.2.5。本文消费 2.2.2 领域边界与数据所有权 和 2.2.3 一致性理论 的结论,面向 Java(编程语言)全栈高级开发面试建立事务、补偿与消息一致性的决策树。最重要的边界:外部支付渠道、第三方物流、短信、文件和设备控制通常不参加本地数据库事务,也不能被 Seata(分布式事务框架)、TCC(Try Confirm Cancel,尝试确认取消)或任何全局事务框架自动回滚。它们必须依靠稳定业务号、幂等、状态机、主动查询、语义补偿、对账和人工闭环收敛。
阅读地图与迁移边界
分布式一致性不是先选框架,而是先回答四个问题:必须原子成立的不变量在哪里;哪些参与者只受本方控制;失败后能否安全重试或补偿;业务允许多长的不一致窗口。订单、库存、支付、账务、履约各自拥有权威状态,流程服务只拥有推进进度,不能越权修改其他领域事实。
本章闭环旧材料中的 legacy-k-4.3 三题、旧图 5、旧表 5、旧表 10 和旧话术 2。旧模块题 legacy-bank-06、legacy-bank-07、legacy-bank-08、legacy-bank-15 的机制答案在本章展开,稳定题号最终由 2.2.8 综合题库承接。
| 迁移对象 | 原内容 | 本章处理 | 验收证据 |
|---|---|---|---|
legacy-k-4.3 | 分布式事务难点、TCC(Try Confirm Cancel,尝试确认取消)选型、库存失败链 | 保留题意并补协调失败、不确定状态、空回滚、悬挂和审计 | 14 个知识节各 3 道六字段题 |
legacy-fig-05 | 本地消息表最终一致流程 | 重画发布、消费、外部副作用和补偿失败窗口 | 15 幅可渲染 Mermaid(图表语法)图 |
legacy-table-05 | 分布式事务模式 | 增加资源要求、阻塞、隔离、侵入和不可逆副作用 | 13 张决策或排障表 |
legacy-table-10 | 一致性方案 | 增加权威状态、收敛时限与人工出口 | 14 个可复算数据演绎 |
legacy-talk-02 | 分布式事务项目话术 | 改写为库存、支付、履约的完整决策与事故闭环 | 32 道综合口述题 |
flowchart TD
A["先定位必须原子成立的不变量"] --> B{"是否位于同一数据库资源?"}
B -->|是| C["优先本地事务"]
B -->|否| D{"所有资源是否支持 XA(扩展架构)且能容忍阻塞?"}
D -->|是| E["评估 2PC(两阶段提交)或 XA(扩展架构)"]
D -->|否| F{"能否显式预留并释放资源?"}
F -->|是| G["评估 TCC(Try Confirm Cancel,尝试确认取消)"]
F -->|否| H{"长流程是否有可定义的语义补偿?"}
H -->|是| I["评估 Saga(长事务模式)"]
H -->|否| J["Outbox(发件箱)/事务消息/对账"]
J --> K["外部支付或物流:查单、幂等、人工闭环"]1. 本地事务先守住单资源原子边界
本地事务由一个事务资源管理一组读写,使它们在提交点同时可见或同时不可见。ACID(原子性、一致性、隔离性、持久性)不是“业务永远正确”的同义词:原子性只保证事务内操作共同成败;一致性仍依赖唯一约束、外键、检查约束、条件更新和应用校验;隔离级别决定并发现象;持久性也受数据库日志、复制和故障恢复配置影响。
在 WMS(仓储管理系统)库存预占中,库存余量条件更新、预占流水插入和请求幂等记录应尽量放在同一数据库事务。这样即使进程在响应前退出,数据库仍给出一个可查询的确定结果。若把三步拆成“先查余量、再扣数量、最后写流水”,并发请求会同时看到旧余量,重试也无法判断首次是否成功。
| 本地事务内对象 | 必须保证的不变量 | 推荐约束 | 失败后的判断 |
|---|---|---|---|
| 库存余额 | 可售量不得小于零 | 带余量条件的原子更新 | 受影响行数与当前版本 |
| 预占流水 | 同一订单行只预占一次 | 业务键唯一约束 | 查询原流水并重放结果 |
| 支付请求 | 同一商户单与支付意图唯一 | 商户单号和渠道组合唯一 | 查询支付单状态 |
| Outbox(发件箱)事件 | 业务提交必有待发布事件 | 与业务数据同事务插入 | 扫描未发布状态 |
sequenceDiagram
participant O as 订单服务
participant D as 库存数据库
O->>D: 开启本地事务
O->>D: 条件更新可售量
O->>D: 插入唯一预占流水
O->>D: 插入 Outbox(发件箱)事件
alt 三步都成功
D-->>O: 提交并返回预占号
else 任一步失败
D-->>O: 回滚全部写入
end数据演绎 1:库存条件更新与并发超卖。 某商品可售量为 100,同时到达 60 个请求,每个购买 2 件。若每个请求先读取 100 再各自写回 98,最终可能只减少 2 件却承诺 120 件。改为“可售量至少为 2 时减 2”的原子条件更新,最多 50 个请求成功,成功量为 50 × 2 = 100,剩余 10 个请求受影响行数为零。再以订单行号建立预占流水唯一约束,重复请求不会额外占用。
线上排查要点: 先查数据库提交事实,不凭应用超时判断回滚。证据顺序是业务唯一键、状态流水、事务日志与应用日志;若出现余额变化但流水不存在,应检查是否确实同库同事务、是否存在绕过事务代理的写入,以及异常是否被捕获后仍提交。
热门面试题
问题:本地事务为什么仍是分布式一致性的基础?
- 考点:原子边界、权威状态、失败判定。
- 回答思路:先把单服务内部不变量做成确定提交,再讨论跨服务传播。
- 详细答案:分布式流程最终由多个本地提交组成。若库存余额、预占流水和待发布事件在本地都可能互相矛盾,外层补偿只会放大混乱。先用数据库事务、条件更新和唯一约束建立可查询事实,跨服务超时后才能依据该事实重试、发布或补偿。
- 进阶追问:应用收到超时能否认定本地事务失败?
- 进阶回答:不能,响应可能在提交后丢失;应按稳定业务键查询权威记录,再决定返回原结果或继续收敛。
问题:ACID(原子性、一致性、隔离性、持久性)能自动保证库存不超卖吗?
- 考点:数据库事务与业务不变量的区别。
- 回答思路:指出事务提供机制,业务必须把规则编码为约束或原子条件。
- 详细答案:不能。若代码先查后改且没有锁、版本或条件更新,每个事务都可能合法提交错误业务结果。库存不负需要带余量条件的原子更新,重复预占需要唯一业务键,事务负责让这些约束与流水共同提交。
- 进阶追问:只提高隔离级别是否足够?
- 进阶回答:不一定,更高隔离会增加冲突和等待,仍应直接表达余量条件与唯一性,并按冲突成本选择隔离级别。
问题:库存预占成功但事件没有发出,怎样避免双写不一致?
- 考点:本地事务、Outbox(发件箱)、异步发布。
- 回答思路:把业务事实与待发布意图放在同一事务,再独立投递。
- 详细答案:在库存本地事务中同时提交预占流水和 Outbox(发件箱)事件,后台发布器扫描或通过变更数据捕获发送。发布成功前事件保持待处理,重复发布由事件标识和消费端幂等吸收,不能在事务提交后只调用一次消息接口便假设成功。
- 进阶追问:事件发布成功后更新状态失败怎么办?
- 进阶回答:允许再次发布同一事件,消费者按业务键幂等;发布状态是效率优化,不应要求绝对仅发送一次。
2. 2PC(两阶段提交)与 XA(扩展架构)以阻塞换取统一决定
2PC(两阶段提交)包含协调者和多个参与者。第一阶段由协调者要求参与者准备:参与者执行分支事务、持久化足以恢复的准备日志并保留相关资源,然后回答可以提交或必须回滚。只有全部回答可以提交,协调者才持久化全局提交决定;否则记录全局回滚决定。第二阶段把决定发送给各参与者,参与者提交或回滚后释放锁和连接。
XA(扩展架构)把事务管理器与资源管理器之间的接口标准化。准备成功不是业务已完成,而是参与者承诺在恢复后仍能按最终决定提交或回滚。若参与者处于准备完成状态却收不到决定,它通常不能自行猜测,只能继续持有锁并等待协调者恢复,这就是阻塞窗口。网络分区下,为避免两个相反结果,保守等待会牺牲可用性。
| 失败位置 | 已知事实 | 主要风险 | 恢复动作 |
|---|---|---|---|
| 准备前参与者失败 | 尚未承诺 | 全局回滚 | 重启后清理未准备分支 |
| 全部准备后协调者失败 | 分支已承诺可完成 | 锁与连接长期占用 | 从协调日志恢复全局决定 |
| 提交决定后部分通知丢失 | 全局决定已确定 | 各参与者可见状态暂时不同 | 重发第二阶段并查询分支 |
| 运维强制单边处理 | 可能违背全局决定 | Heuristic Outcome(启发式结果) | 对账、修复并保留审计 |
sequenceDiagram
participant C as Coordinator(协调者)
participant I as 库存资源
participant L as 账务资源
C->>I: Prepare(准备)
C->>L: Prepare(准备)
I-->>C: Prepared(已准备)
L-->>C: Prepared(已准备)
Note over I,L: 持锁等待全局决定
alt 协调者持久化 Commit(提交)
C->>I: Commit(提交)
C->>L: Commit(提交)
else 任一参与者拒绝
C->>I: Rollback(回滚)
C->>L: Rollback(回滚)
end数据演绎 2:准备阶段的锁放大。 库存热点行正常本地事务持锁 20ms(毫秒),吞吐上限粗略为 1000 ÷ 20 = 50 次每秒。接入 2PC(两阶段提交)后,跨服务调用、协调日志和第二阶段使准备锁平均持有 200ms(毫秒),单热点行理论吞吐降到 1000 ÷ 200 = 5 次每秒。若协调者故障让 100 个已准备分支等待 30s(秒),这些分支会阻塞同一批业务键,恢复时还会形成集中提交与唤醒峰值。
启发式结果边界: Heuristic Outcome(启发式结果)是资源管理器或运维在无法获得全局决定时,单方面提交或回滚分支后产生的结果。它不是正常自动恢复策略,而是承认全局原子性已经可能破坏。系统必须记录全局事务号、分支号、原决定、单边动作和操作者,随后用业务账本对账,不得用删除日志掩盖。
项目边界: 同一组织控制、低并发、事务很短且全部是支持 XA(扩展架构)的数据库资源时,可以评估 2PC(两阶段提交)。外部支付与物流没有向本系统暴露可恢复的准备分支,不能因为应用层调用位于全局事务注解内就宣称它们会一起回滚。
热门面试题
问题:分布式事务为什么比本地事务难?
- 考点:网络分区、独立故障、观察不确定性、协调恢复。
- 回答思路:从一个提交点扩展到多个提交点和不可靠通信。
- 详细答案:本地事务由一个资源给出提交事实;分布式事务中参与者、协调者和网络可独立失败,调用超时只表示调用方未观察到结果。即使采用 2PC(两阶段提交),全部准备后的协调者故障仍会让参与者阻塞等待;若人工单边处理,还可能形成启发式不一致。
- 进阶追问:为什么不把协调者做成高可用就结束?
- 进阶回答:高可用降低故障概率,但不能消除网络分区、陈旧领导者、参与者恢复和协调日志一致性问题,业务仍需对账与超时治理。
问题:2PC(两阶段提交)为什么会阻塞?
- 考点:准备承诺、锁持有、最终决定缺失。
- 回答思路:解释参与者准备成功后不能自行改变决定。
- 详细答案:参与者回答已准备后,必须保证未来可以提交,也要保留回滚能力,因此通常持有锁和恢复信息。若协调者的提交或回滚决定暂时不可达,参与者自行猜测可能与其他分支相反,只能等待恢复,业务键和连接资源因而被阻塞。
- 进阶追问:参与者之间互相询问能否彻底解除阻塞?
- 进阶回答:只能在部分协议和已知决定场景辅助恢复;若所有可达参与者都只知道已准备,仍无法安全推断全局决定。
问题:什么是 Heuristic Outcome(启发式结果),线上如何处理?
- 考点:单边决策、原子性破坏、审计修复。
- 回答思路:先说明它是异常终局,再给证据和业务修复顺序。
- 详细答案:资源在长期得不到全局决定时被人工或本地策略强制提交、回滚,会形成启发式提交、启发式回滚或混合结果。处置时冻结相关业务键,保存全局与分支日志,确认各资源真实状态,再按库存、账务等权威流水执行受审批的补记或冲正,最后验证对账差额归零。
- 进阶追问:可以直接重放全局事务修复吗?
- 进阶回答:不能盲目重放,部分分支可能已产生不可重复副作用;必须先逐分支查明事实,再选择幂等补做、反向补偿或人工调整。
3. TCC(Try Confirm Cancel,尝试确认取消)把业务资源显式拆成预留、确认与释放
TCC(Try Confirm Cancel,尝试确认取消)是业务层两阶段方案。Try(尝试)检查条件并预留资源,Confirm(确认)消费预留资源完成最终动作,Cancel(取消)释放预留资源。它不是把原接口简单复制三份:预留必须是其他请求不可重复占用、又能在取消时准确释放的业务资产,例如库存冻结量、账户冻结额、优惠券锁定记录。
| 阶段 | 库存语义 | 资金语义 | 必须持久化的证据 |
|---|---|---|---|
| Try(尝试) | 可售转冻结 | 可用转冻结 | 全局号、分支号、业务键、数量、状态 |
| Confirm(确认) | 冻结转已扣减 | 冻结转已支付 | 确认时间、前置状态、结果流水 |
| Cancel(取消) | 冻结回可售 | 冻结回可用 | 取消原因、前置状态、释放流水 |
| 超时回收 | 只处理仍冻结且过期的资源 | 受资金规则与查单约束 | 到期时间、所有者、版本与审批 |
stateDiagram-v2
[*] --> 未预留
未预留 --> 已预留: Try(尝试)成功
已预留 --> 已确认: Confirm(确认)
已预留 --> 已取消: Cancel(取消)
未预留 --> 空回滚记录: Cancel(取消)先到
空回滚记录 --> 空回滚记录: 拒绝迟到 Try(尝试)
已确认 --> 已确认: 重复 Confirm(确认)
已取消 --> 已取消: 重复 Cancel(取消)数据演绎 3:预留容量与过期窗口。 秒杀库存 1000 件,Try(尝试)成功后允许用户在 15min(分钟) 内完成支付。若有 800 个用户预留但只有 40% 支付,未及时释放会让 480 件库存被无效占用。设置 15min(分钟) 到期并每分钟扫描,最坏额外占用约 1min(分钟);但扫描只能取消状态仍为已预留且版本未变的记录,支付已成功却回调延迟的订单必须先查支付权威状态,不能到点直接释放。
资源预留还要考虑额度。若库存 Try(尝试)把可售转冻结,而支付 Try(尝试)只校验账户余额却不冻结,第二阶段可能因余额被其他请求消费而失败,破坏“Try(尝试)成功则 Confirm(确认)应可完成”的设计目标。相反,冻结过多会降低可用性,所以预留时限、用户提示和过期回收必须成为业务规则。
热门面试题
问题:TCC(Try Confirm Cancel,尝试确认取消)的 Try(尝试)阶段到底要做什么?
- 考点:业务检查、资源预留、可确认性。
- 回答思路:强调成功后要为第二阶段保留确定资源。
- 详细答案:Try(尝试)不仅校验参数,还要把库存从可售转冻结、把资金从可用转冻结,并记录全局号、分支号和业务键。成功意味着资源不会被其他交易抢走,后续 Confirm(确认)可幂等完成;做不到预留就不应伪装成 TCC(Try Confirm Cancel,尝试确认取消)。
- 进阶追问:Try(尝试)阶段能直接扣减库存吗?
- 进阶回答:可以改变为冻结态,但不应混淆最终扣减;否则 Cancel(取消)难以区分原库存、已售库存和并发变化。
问题:为什么说 TCC(Try Confirm Cancel,尝试确认取消)业务侵入大?
- 考点:三阶段接口、状态存储、异常语义。
- 回答思路:从代码数量转向业务模型改造成本。
- 详细答案:每个参与者都要识别可预留资源,设计 Try(尝试)、Confirm(确认)、Cancel(取消)及其幂等状态,还要处理空回滚、悬挂、过期和人工修复。资金、库存的补偿规则不同,框架只能调度,不能替业务定义正确语义。
- 进阶追问:框架生成三个接口是否就能降低侵入?
- 进阶回答:只能降低接入样板,无法代替冻结模型、状态迁移、并发约束和异常恢复设计。
问题:库存冻结到期能否由定时任务直接释放?
- 考点:权威状态、并发条件、迟到结果。
- 回答思路:先查前置状态和支付事实,再做带版本释放。
- 详细答案:不能只按时间删除冻结。任务要确认记录仍处于已预留、版本未变化,并核对支付是否已成功或仍未知;支付成功时应继续确认,支付明确失败才取消。释放用原冻结号幂等执行,迟到 Confirm(确认)必须被状态机拒绝或转人工。
- 进阶追问:为什么不能让 Confirm(确认)覆盖已取消状态?
- 进阶回答:资源可能已重新售给其他订单,覆盖会造成超卖或无来源扣减,必须按业务事实决定补货、退款或人工履约。
4. TCC(Try Confirm Cancel,尝试确认取消)必须同时处理幂等、空回滚、悬挂与防悬挂
二阶段调用会因超时和重试重复到达,所以 Confirm(确认)与 Cancel(取消)必须幂等。空回滚指 Try(尝试)请求尚未到达或未成功,Cancel(取消)已先到;Cancel(取消)不能凭空释放资源,但必须记录“该分支已取消”。悬挂指空回滚完成后,迟到的 Try(尝试)又成功预留资源,而协调者不会再发第二次 Cancel(取消),资源永久挂起。
防悬挂的关键不是调整调用顺序,而是在参与者本地用同一事务检查分支屏障。Try(尝试)插入预留前确认没有取消屏障;Cancel(取消)未找到预留时写入空回滚屏障;Confirm(确认)和 Cancel(取消)都根据全局号与分支号执行条件状态迁移。唯一约束让并发到达只有一个合法结果。
| 异常 | 到达顺序 | 错误实现 | 正确约束 |
|---|---|---|---|
| 重复确认 | 确认、确认 | 重复扣冻结量 | 已确认直接返回首次结果 |
| 重复取消 | 取消、取消 | 重复增加可售量 | 已取消直接返回首次结果 |
| 空回滚 | 取消早于尝试 | 当成异常反复重试 | 写取消屏障但不释放资源 |
| 悬挂 | 取消、迟到尝试 | 迟到尝试继续预留 | 尝试发现屏障后拒绝 |
sequenceDiagram
participant C as 协调者
participant T as TCC(Try Confirm Cancel,尝试确认取消)参与者
C--xT: Try(尝试)请求延迟
C->>T: Cancel(取消)先到
T->>T: 未见预留,写空回滚屏障
T-->>C: 取消成功
C->>T: 迟到 Try(尝试)
T->>T: 命中取消屏障,拒绝预留
T-->>C: 已取消数据演绎 4:重复二阶段的数量守恒。 原可售 100、冻结 0,Try(尝试)预留 10 后应为可售 90、冻结 10。网络重试让 Cancel(取消)到达三次。若每次都执行“可售加 10”,结果变成 120;若以分支号唯一并只允许“已预留转已取消”一次,首次变成可售 100、冻结 0,后两次读取已取消结果,守恒式 可售 + 冻结 + 已扣减 = 100 不变。
防悬挂记录不能过早清理。全局事务重试、消息积压或灾备恢复可能让迟到 Try(尝试)在数小时后出现;清理时要结合协调器保留期、最长网络重试期和业务审计期。资金类记录通常要保留更久,不能为了表小而牺牲可证明性。
热门面试题
问题:TCC(Try Confirm Cancel,尝试确认取消)和可靠消息最终一致怎么选?
- 考点:资源预留、时延、侵入、隔离。
- 回答思路:按是否必须同步占住稀缺资源决策。
- 详细答案:库存冻结、额度预占等必须在主流程内确定占用时,可用 TCC(Try Confirm Cancel,尝试确认取消),代价是三阶段改造和异常屏障。报表、通知、积分等允许异步收敛的动作更适合可靠消息,避免长期预留与同步耦合;支付外部渠道仍要查单对账。
- 进阶追问:账务入账能否只靠消息?
- 进阶回答:可以由消息驱动,但账务消费必须有唯一事件、借贷约束、状态流水和对账,消息本身不保证资金规则。
问题:空回滚为什么不能只返回成功而不落记录?
- 考点:迟到 Try(尝试)、取消屏障、持久化。
- 回答思路:说明返回成功只处理当前调用,无法约束未来迟到请求。
- 详细答案:Cancel(取消)先到时没有资源可释放,返回成功是对的,但若不持久化取消屏障,迟到 Try(尝试)会正常预留且再也收不到取消。屏障要与分支号唯一绑定,在 Try(尝试)的本地事务中被检查。
- 进阶追问:用短期缓存记录屏障可以吗?
- 进阶回答:不适合作为最终依据,缓存丢失或过期会重新暴露悬挂;应持久化到与业务状态一致的可靠存储。
问题:Confirm(确认)失败后能否改发 Cancel(取消)?
- 考点:全局决定、状态不可逆、重试策略。
- 回答思路:区分全局已提交与尚未决定。
- 详细答案:全局已决定提交后不能因一次 Confirm(确认)超时就改为 Cancel(取消),其他分支可能已确认。应幂等重试确认、查询参与者状态并告警;只有业务定义了新的反向交易,才能在原事务结束后发起独立补偿。
- 进阶追问:确认永久失败怎么办?
- 进阶回答:冻结业务键,保留全局与分支证据,按业务账本人工补记或退款,不能偷偷改写原决定。
5. Saga(长事务模式)的补偿是新业务动作,不是时间倒流
Saga(长事务模式)把长流程拆为多个已提交的本地事务;后续失败时,按业务定义执行补偿动作。补偿可能恢复数量或余额,但无法抹去已发送短信、用户已看到的价格、仓库已拣货、物流已揽收等现实影响。因此补偿目标是把业务带到可接受且可解释的新状态,而不是假装从未发生。
Orchestration(编排)由中心流程管理器显式决定下一步与补偿顺序,便于观察全局进度和处理复杂分支;Choreography(协同)由参与者消费事件并发布新事件,耦合较松,但流程散落、环路和排障难度更高。二者都需要持久化状态、幂等、超时、版本与人工出口。
| 对比维度 | Orchestration(编排) | Choreography(协同) |
|---|---|---|
| 流程所有者 | 中心流程管理器 | 分散在事件参与者 |
| 变更可见性 | 状态图集中 | 需跨主题还原 |
| 适用复杂度 | 分支、补偿和超时较多 | 简单线性传播 |
| 主要风险 | 中心逻辑过重 | 隐式耦合、事件环路 |
flowchart LR
A["订单已创建"] --> B["库存已预占"]
B --> C["支付已确认"]
C --> D["履约已建单"]
D --> E{"物流下单是否成功?"}
E -->|是| F["流程完成"]
E -->|否且未出库| G["取消履约"]
G --> H["释放库存"]
H --> I["发起退款"]
E -->|已揽收| J["转人工拦截或退货流程"]数据演绎 5:补偿成功率的链路效应。 一个 Saga(长事务模式)失败后需要依次取消履约、释放库存、发起退款,三步单次成功率分别为 99.9%、99.5%、99%,一次全部成功概率约为 0.999 × 0.995 × 0.99 ≈ 98.41%。每万次补偿约有 159 次至少一步失败,因此“每步都接近百分之百”仍不足以取消人工队列;必须监控补偿年龄、分步重试和最终差错率。
不可逆动作应尽量后置。例如先完成支付和库存确认,再向仓库释放拣货任务;发短信可延迟到关键状态确定后。若业务必须提前执行,就定义前向修复:已揽收不能“回滚物流”,只能请求拦截、退回并承担费用;已支付不能删流水,只能发起有独立单号的退款或冲正。
热门面试题
问题:Saga(长事务模式)补偿与数据库回滚有什么本质区别?
- 考点:已提交事实、语义反向动作、外部世界。
- 回答思路:强调补偿本身是新的可失败交易。
- 详细答案:数据库回滚让未提交修改不可见;Saga(长事务模式)前序本地事务已经提交并可能被观察。补偿通过退款、释放、冲正等新动作抵消业务影响,会留下新流水,也可能失败,无法删除短信、实物移动等现实副作用。
- 进阶追问:补偿后状态必须回到初始值吗?
- 进阶回答:不必须,已出库订单可能转退货中,资金转待退款;关键是状态合法、责任清晰且最终可对账。
问题:Saga(长事务模式)编排与协同如何选择?
- 考点:流程复杂度、耦合、可观测性。
- 回答思路:简单传播可协同,复杂长流程优先显式编排。
- 详细答案:参与者少、事件方向单一时协同较轻;订单、库存、支付、履约含条件分支、超时和多级补偿时,中心流程管理器更容易持久化进度与人工介入。编排器只拥有流程,不得直接修改各领域权威表。
- 进阶追问:编排器故障会不会成为单点?
- 进阶回答:流程状态需持久化并可由其他实例接管,命令带版本与幂等键;高可用不能省略参与者自身状态机。
问题:补偿动作连续失败应该怎么处理?
- 考点:有限重试、隔离队列、人工修复。
- 回答思路:区分瞬时故障和业务拒绝,限制自动动作。
- 详细答案:瞬时错误按退避预算幂等重试,业务拒绝如已出库则切换到拦截或退货分支。超过时限后冻结后续动作,进入差错队列,展示原动作、补偿尝试和权威状态,由审批执行补记、退款或线下处理。
- 进阶追问:为什么不能无限重试补偿?
- 进阶回答:会持续占用资源、轰击外部渠道,并可能在业务条件变化后执行过时动作;必须有年龄和次数上限。
6. Seata(分布式事务框架)四种模式的资源前提完全不同
Seata AT(自动事务模式)通过数据源代理,在第一阶段把业务数据与 undo log(回滚日志)同本地事务提交,并在全局锁约束下处理第二阶段;它适合受支持关系型数据库中的短事务,不覆盖任意 SQL(结构化查询语言)、绕过代理的写入和外部副作用。回滚前还要校验当前镜像,避免覆盖其他已提交变化。
Seata(分布式事务框架) TCC(Try Confirm Cancel,尝试确认取消)模式调度业务实现的三阶段资源,性能和资源粒度可控,但侵入最高,正确性取决于业务屏障。Seata(分布式事务框架) Saga(长事务模式)由状态机驱动正向服务和补偿服务,不保证隔离。Seata(分布式事务框架) XA(扩展架构模式)依赖资源原生支持 XA(扩展架构),准备后持锁等待,隔离较强但阻塞与性能成本明显。
| 模式 | 资源前提 | 业务侵入 | 锁与隔离 | 不能覆盖的边界 |
|---|---|---|---|---|
| Seata AT(自动事务模式) | 受支持数据库、数据源代理、可解析写入 | 低 | 全局锁与镜像补偿 | 支付渠道、物流、复杂不可代理写入 |
| Seata(分布式事务框架) TCC(Try Confirm Cancel,尝试确认取消)模式 | 可设计业务预留 | 高 | 业务级冻结 | 无法预留或不可逆动作 |
| Seata(分布式事务框架) Saga(长事务模式) | 可定义正向与补偿服务 | 中高 | 不保证隔离 | 无补偿且必须同步强一致动作 |
| Seata(分布式事务框架) XA(扩展架构模式) | 资源原生支持 XA(扩展架构) | 低 | 准备后阻塞持锁 | 非 XA(扩展架构)外部系统 |
flowchart TD
A["受控关系型数据库短事务"] --> B["评估 Seata AT(自动事务模式)"]
C["可显式冻结库存或额度"] --> D["评估 Seata(分布式事务框架) TCC(Try Confirm Cancel,尝试确认取消)模式"]
E["长履约且可语义补偿"] --> F["评估 Seata(分布式事务框架) Saga(长事务模式)"]
G["全部资源原生支持 XA(扩展架构)"] --> H["评估 Seata(分布式事务框架) XA(扩展架构模式)"]
I["支付/物流外部副作用"] --> J["状态机、查单、对账、人工闭环"]数据演绎 6:全局锁冲突窗口。 某热点库存行本地更新平均 15ms(毫秒),若 Seata AT(自动事务模式)因全局锁等待把平均事务拉到 120ms(毫秒),单行串行能力从约 66.7 次每秒降到 8.3 次每秒。若调用方超时为 80ms(毫秒),会在事务仍等待时发起重试,进一步增加冲突。应先缩短事务、拆热点和限制重试,再评估是否改用业务预留或消息方案,而不是只扩大超时。
版本核对以执行时官方文档为准。本文于 2026-07-14 核对 Seata(分布式事务框架)官方文档当前展示的 2.6 文档线;具体项目仍须记录实际客户端、服务端、数据库、代理方式与 SQL(结构化查询语言)支持清单,不能把文档线等同于生产版本。
热门面试题
问题:Seata AT(自动事务模式)为什么不等于无条件自动回滚?
- 考点:数据源代理、回滚镜像、全局锁、资源边界。
- 回答思路:先说自动化范围,再列出绕过代理和外部副作用。
- 详细答案:它自动处理的是受支持数据库写入,依靠代理记录 undo log(回滚日志)并协调全局锁。未代理数据源、不支持的 SQL(结构化查询语言)、直接外部调用和物理世界动作不在镜像回滚范围;脏写校验失败时也需要人工判断。
- 进阶追问:把物流接口放进全局事务注解会怎样?
- 进阶回答:只能让调用发生在该方法期间,物流平台不会因此拥有可准备、可回滚分支;超时后仍要按运单号查单和补偿。
问题:Seata(分布式事务框架) XA(扩展架构模式)与 Seata AT(自动事务模式)如何选择?
- 考点:原生协议、隔离、阻塞、补偿。
- 回答思路:按资源支持和锁周期权衡。
- 详细答案:全部数据库资源原生支持 XA(扩展架构)、事务短且可承受准备阻塞时可评估前者;希望第一阶段尽快释放本地锁且写入可被代理解析时可评估后者。两者都不覆盖外部支付或物流,也都要压测热点与恢复窗口。
- 进阶追问:资金场景是否必然选 XA(扩展架构)?
- 进阶回答:不是,外部渠道通常不参与 XA(扩展架构);内部账务可用本地事务与唯一分录,跨渠道通过查单、消息和对账收敛。
问题:Seata(分布式事务框架) Saga(长事务模式)能保证隔离吗?
- 考点:本地提交、脏读业务态、补偿可见性。
- 回答思路:明确官方边界并给业务缓解手段。
- 详细答案:不能天然保证。每个正向本地事务提交后,其状态可能被其他流程看到,随后补偿又改变状态。可通过状态标识、业务锁定、版本条件、读模型过滤和流程规则降低影响,但不能宣传成数据库隔离。
- 进阶追问:用户看到“已预占”后又被释放算错误吗?
- 进阶回答:若产品语义明确为处理中且最终状态可查询,不一定;若已承诺成交再释放,则是业务契约错误,需要赔付或人工履约。
7. TX-LCN(事务协调框架)只作为存量系统的历史方案评估
TX-LCN(事务协调框架)通过事务管理端协调多个事务客户端,历史上用于 Spring Cloud(微服务框架)、Dubbo(远程调用框架)等调用链的分布式事务接入。它的价值在于帮助理解代理、事务组、协调命令和连接资源保持,但不能仅凭简历中“用过”便把它推荐为新系统默认方案。
截至 2026-07-14 核对官方仓库,页面展示的最新发布仍为 2020-07-02 的 v5.x.last,默认分支显示 dev6.0。这只能作为维护活跃度和生态兼容风险的证据之一,不能推断所有存量部署都不可用;真实迁移还要核对生产版本、数据库驱动、Spring(Java 应用框架)版本、事务管理端高可用、恢复日志和故障演练。
| 评估项 | 存量继续使用的证据 | 迁移触发信号 | 迁移目标 |
|---|---|---|---|
| 兼容性 | 当前框架与驱动组合已验证 | 升级被旧代理阻塞 | 本地事务加消息或受维护方案 |
| 协调端 | 双机故障演练可恢复 | 协调端故障导致长时间悬挂 | 降低同步全局事务范围 |
| 可观测性 | 全局号可关联分支与业务号 | 只能看到成功或失败 | 建立状态流水与对账 |
| 外部副作用 | 已明确排除支付与物流 | 误以为注解能回滚渠道 | 状态机、查单和语义补偿 |
flowchart TD
A["发现存量 TX-LCN(事务协调框架)"] --> B["盘点事务组、数据库与调用链"]
B --> C["演练协调端故障与参与者重启"]
C --> D{"恢复时间和兼容性是否达标?"}
D -->|是| E["受控维持并补业务对账"]
D -->|否| F["按业务不变量拆迁移批次"]
F --> G["短数据库事务迁本地事务"]
F --> H["异步动作迁消息一致性"]
F --> I["外部动作迁查单与补偿"]数据演绎 7:存量协调端故障成本。 某系统每秒创建 80 个事务组,平均每组占用 3 个数据库连接,协调端故障检测与切换需要 20s(秒)。若分支在等待期间不释放连接,理论上会积累 80 × 3 × 20 = 4800 个连接占用,远超三个服务各 200 的连接池总量。即使实际入口很快限流,也说明恢复时间必须进入容量模型,不能只验证正常提交。
迁移时不应一次性替换所有事务。先按业务号建立新旧结果对照,再把通知、报表等天然异步分支移出全局事务;库存与账务保留各自本地不变量,通过 Outbox(发件箱)传播;最后收缩协调范围。双写阶段要有单一权威状态,不能让两个协调方案同时决定同一业务结果。
热门面试题
问题:为什么 TX-LCN(事务协调框架)不应作为新系统默认推荐?
- 考点:维护状态、生态兼容、协调故障、替代路径。
- 回答思路:以官方仓库事实和生产验证作判断,不做情绪化否定。
- 详细答案:官方仓库发布节奏、现代框架兼容和故障恢复能力都需要重新验证;新系统还有本地事务加消息、Seata(分布式事务框架)等受维护选择。存量可在证据充分时维持,但必须补协调端演练、连接容量、业务对账与迁移出口。
- 进阶追问:最后发布较早是否等于框架一定有漏洞?
- 进阶回答:不能直接等同,只能说明需提高审查强度;还要看代码、依赖、漏洞公告、运行隔离和实际维护能力。
问题:存量 TX-LCN(事务协调框架)迁移为什么要先拆异步分支?
- 考点:风险分层、事务范围、渐进迁移。
- 回答思路:先移动不要求同步原子的低风险动作。
- 详细答案:通知、报表、积分等通常只需最终一致,移出后可立即缩短事务组和连接占用,且失败能用消息重试。库存、资金等核心不变量后迁,先补本地约束、流水、对账和回滚方案,避免大爆炸式切换。
- 进阶追问:迁移期间怎样避免重复执行?
- 进阶回答:使用稳定业务键、唯一约束和单一状态所有者,新旧链路都读取同一迁移标记,影子链路只比对不产生副作用。
问题:协调端恢复后是否可以直接重放全部事务?
- 考点:分支事实、外部副作用、幂等恢复。
- 回答思路:先分类全局决定和各分支状态。
- 详细答案:不能。部分分支可能已提交,外部调用也可能成功但响应丢失。恢复程序要读取协调日志,逐分支查询并幂等补发确定决定;没有协议保护的支付、物流必须按业务号查单,未知项进入对账而非全量重做。
- 进阶追问:怎样证明恢复没有重复扣库存?
- 进阶回答:核对请求唯一键、库存流水和数量守恒,并用故障注入复现提交后断联窗口。
8. Outbox(发件箱)把业务提交与待发布事件合并为一个本地事实
Outbox(发件箱)解决“业务数据库已经提交,但消息发送失败”的双写窗口。业务表与事件表在同一个本地事务中提交,发布器随后扫描待发布事件并发送到 MQ(消息队列);也可由 CDC(变更数据捕获)读取数据库变更日志。它保证业务事实存在时发布意图也存在,但不保证只发送一次,重复仍由消费端吸收。
| 字段 | 作用 | 关键约束 | 常见错误 |
|---|---|---|---|
| 事件标识 | 端到端去重与追踪 | 全局唯一且稳定 | 每次重试生成新标识 |
| 聚合标识 | 同一订单或库存键排序 | 与业务实体一致 | 用随机分区导致乱序 |
| 事件类型与版本 | 契约演进 | 兼容旧消费者 | 直接复用数据库行结构 |
| 发布状态与尝试次数 | 调度与告警 | 条件抢占、有限重试 | 多发布器重复无限发送 |
| 业务发生时间 | 还原事件语义 | 与提交事实关联 | 用发送时间冒充发生时间 |
sequenceDiagram
participant S as 库存服务
participant D as 业务数据库
participant P as Publisher(发布器)
participant M as MQ(消息队列)
S->>D: 同一本地事务写预占流水与 Outbox(发件箱)
D-->>S: 提交成功
Note over S,P: 即使进程此时退出,发布意图仍在数据库
P->>D: 条件抢占待发布事件
P->>M: 发送稳定事件标识
M-->>P: Acknowledgement(确认)丢失
P->>M: 重发同一事件数据演绎 8:扫描容量与积压年龄。 峰值业务提交 2000 条每秒,每个发布器每批读取 500 条、单批发送耗时 200ms(毫秒),单发布器理论能力为 500 ÷ 0.2 = 2500 条每秒。表面上一个实例足够,但数据库抖动到 500ms(毫秒) 时能力降为 1000 条每秒,每秒新增 1000 条积压,持续 10min(分钟) 就积累 600000 条。至少配置容量余量、积压年龄告警和分片抢占,不能只看总行数。
发布器更新“已发布”失败时会重发,这是可接受的至少一次语义。若先把状态改为已发布再发送,发送失败会永久漏消息。清理 Outbox(发件箱)记录要晚于消息最大回放与审计周期,并把历史归档到可查询存储;删除未发布记录等同于删除业务承诺。
热门面试题
问题:Outbox(发件箱)真正解决了什么问题?
- 考点:数据库与消息双写、本地原子性、至少一次。
- 回答思路:准确限定为“业务事实与发布意图不分离”。
- 详细答案:它让业务数据和待发布事件在一个本地事务中共同提交,消除业务成功却没有任何发布线索的窗口。后台发送仍可能重复或延迟,所以消费者必须幂等,系统还要监控积压年龄、失败次数和消息端到端状态。
- 进阶追问:Outbox(发件箱)能保证消息只发送一次吗?
- 进阶回答:不能,发送成功后状态更新可能失败;应重发同一事件并让消费端按业务语义去重。
问题:Outbox(发件箱)轮询与 CDC(变更数据捕获)如何选择?
- 考点:实现复杂度、数据库压力、顺序和运维。
- 回答思路:小规模先可靠轮询,高吞吐再评估日志捕获。
- 详细答案:轮询易理解、回放和控制,但要设计索引、分片与抢占,避免扫表;CDC(变更数据捕获)延迟低且少轮询压力,却引入连接器、日志保留、位点和结构变更治理。两者都不能省略事件契约与消费者幂等。
- 进阶追问:可以同时运行轮询和 CDC(变更数据捕获)吗?
- 进阶回答:迁移影子期可以,但必须标记单一正式发布者或共享幂等事件标识,否则会主动制造双倍消息。
问题:Outbox(发件箱)表无限增长如何治理?
- 考点:冷热分离、审计周期、索引与归档。
- 回答思路:先保护未发布记录,再按可证明周期归档已完成数据。
- 详细答案:按状态与创建时间建立扫描索引,未发布和重试记录保留在线;已发布记录在超过回放、对账和审计周期后分区归档,再受控删除。监控活跃分区大小、扫描耗时和最老事件,不能只用总行数触发粗暴清表。
- 进阶追问:删除已发布记录会影响幂等吗?
- 进阶回答:生产端重发判断可能受影响,消费端仍应有独立去重;删除前必须确认所有回放和对账窗口都结束。
9. Inbox(收件箱)把消息去重、业务状态与确认时机绑定
Inbox(收件箱)用于消费端记录“某个稳定事件对某个业务处理器已产生什么结果”。典型做法是在同一数据库事务中插入去重记录、执行条件状态迁移并写业务流水,事务提交后才向 MQ(消息队列)返回 Acknowledgement(确认)。若提交后确认丢失,消息会重复到达,但唯一约束和状态机返回首次结果。
| 去重键选择 | 是否足够 | 原因 | 适用补充 |
|---|---|---|---|
| 中间件投递标识 | 通常不足 | 重发或迁移可能变化 | 结合业务事件标识 |
| 事件标识 | 基础可用 | 同一意图可能有多个事件 | 再加处理器与事件类型 |
| 订单号 | 通常过粗 | 一个订单存在多次合法状态变化 | 加状态版本或动作类型 |
| 业务号 + 动作 + 版本 | 推荐 | 能表达同一业务语义 | 唯一约束与参数摘要 |
sequenceDiagram
participant M as MQ(消息队列)
participant C as 账务消费者
participant D as 账务数据库
M->>C: 投递支付成功事件
C->>D: 开启本地事务
C->>D: 插入 Inbox(收件箱)去重键
C->>D: 插入唯一账务分录
D-->>C: 提交成功
C--xM: Acknowledgement(确认)丢失
M->>C: 重复投递
C->>D: 唯一键命中,读取首次结果
C-->>M: 安全确认数据演绎 9:确认窗口造成的重复量。 消费者每秒处理 1000 条消息,业务提交到 Acknowledgement(确认)发出平均间隔 10ms(毫秒)。实例突然断电时,理论平均约有 1000 × 0.01 = 10 条已提交但未确认消息会重投;若批量确认 500 条,最坏可重复接近整批 500 条。消费者容量测试必须包含重复洪峰,不能按“正常没有重复”设计。
去重记录不能先于业务事务独立提交,否则业务失败后消息再次到达会被误判已处理。外部调用也不能直接塞入数据库事务等待:先持久化待调用状态,事务外用稳定业务号调用;超时后查单,将结果写回。Inbox(收件箱)只证明本消费者本地处理,不证明第三方一定成功。
热门面试题
问题:消费者如何做到重复消息不重复入账?
- 考点:业务去重键、同一事务、唯一分录。
- 回答思路:把去重、账务写入和结果流水做成一个原子提交。
- 详细答案:以支付单号、账务方向和事件版本形成唯一键,在同一本地事务插入 Inbox(收件箱)记录与账务分录。重复命中后校验参数摘要并返回首次结果;事务提交后再确认消息,确认丢失只会触发安全重投。
- 进阶追问:用 Redis(远程字典服务)记录已消费可以吗?
- 进阶回答:可作快速过滤,不能作为资金最终证据;缓存与数据库无法原子提交,过期或丢失也会重新放行。
问题:为什么消息标识不一定等于业务幂等键?
- 考点:投递身份与业务意图。
- 回答思路:举同一订单多事件和同一事件多投递的双向差异。
- 详细答案:同一业务意图可能因重建消息产生不同投递标识,一个订单也会合法产生支付、退款、关闭等多个事件。去重键应表达“哪个业务对象的哪个动作和版本”,消息标识用于追踪,两者关联但不能机械等同。
- 进阶追问:参数不同却复用同一业务键怎么办?
- 进阶回答:不能当普通重复,应比较参数摘要并报警,阻止覆盖首次事实,转入契约或人工调查。
问题:消费者先确认消息再提交数据库有什么风险?
- 考点:消息丢失窗口、确认顺序、恢复。
- 回答思路:指出确认成功后中间件不会再投递。
- 详细答案:若确认后进程在数据库提交前崩溃,中间件认为消息已完成,业务写入却不存在,形成永久漏处理。正确顺序是本地事务先提交,再确认;重复由 Inbox(收件箱)吸收,漏处理由积压、对账和状态年龄监控发现。
- 进阶追问:数据库提交很慢会拖延确认怎么办?
- 进阶回答:优化事务并调整消费可见性超时与并发,必要时续期处理权;不能用提前确认交换数据丢失。
10. 事务消息协调本地提交与消息可见性,但不替消费者完成业务
以 RocketMQ(分布式消息队列)事务消息为例,生产者先发送对消费者不可见的 Half Message(半消息),再执行本地事务并提交 Commit(提交)或 Rollback(回滚)决定。若第二次确认丢失,Broker(代理节点)会回查生产者本地事务状态。回查依据必须是稳定业务记录,不能读取内存变量或看到异常就猜回滚。
| 阶段 | 消息状态 | 本地事务状态 | 异常处理 |
|---|---|---|---|
| 半消息写入 | 不可投递 | 尚未执行 | 半消息失败则不开始本地事务 |
| 本地事务执行 | 仍不可投递 | 成功、失败或进行中 | 持久化业务结果 |
| 二次确认 | 提交或回滚 | 已确定 | 丢失时触发回查 |
| 消费执行 | 可投递 | 上游已提交 | 消费者幂等、重试与隔离 |
sequenceDiagram
participant P as 支付服务生产者
participant B as Broker(代理节点)
participant D as 支付数据库
participant C as 账务消费者
P->>B: 发送 Half Message(半消息)
B-->>P: 半消息成功
P->>D: 提交支付状态本地事务
P--xB: Commit(提交)确认丢失
B->>P: 回查本地事务状态
P->>D: 按支付单号查询权威记录
P-->>B: Commit(提交)
B->>C: 投递可见消息数据演绎 10:回查压力。 峰值每秒发送 3000 条事务消息,二次确认因网络抖动有 2% 丢失,则每秒新增 60 条待回查。若抖动持续 5min(分钟),累计 60 × 300 = 18000 条。生产者回查接口每秒只能查 200 条时可在约 90s(秒) 清空,但还会与实时支付查询竞争数据库,必须独立限流、索引业务号并监控最老半消息年龄。
官方文档明确事务消息保证生产端本地事务与消息投递的最终一致,不保证消费结果自动与上游一致。消费者仍会失败或重复,必须使用 Inbox(收件箱)、状态机和隔离队列。外部物流消费成功与否更不能由消息提交状态代替,超时后仍需查运单。
热门面试题
问题:事务消息与 Outbox(发件箱)主要区别是什么?
- 考点:协调位置、半消息、数据库耦合、运维。
- 回答思路:比较消息代理回查与数据库待发布表。
- 详细答案:事务消息由消息代理保存半消息并回查本地事务;Outbox(发件箱)把发布意图放入业务数据库,由轮询或 CDC(变更数据捕获)发布。前者依赖产品特性与回查服务,后者更通用但增加表、扫描和归档治理;两者都是至少一次。
- 进阶追问:能否两种方案叠加?
- 进阶回答:通常没有必要,会增加状态与故障窗口;除迁移验证外应选一个生产端原子桥接方案,并保留消费幂等。
问题:事务回查时本地事务仍在进行怎么办?
- 考点:未知状态、误提交、误回滚。
- 回答思路:按业务记录返回进行中,而不是猜结论。
- 详细答案:生产者应识别持久化的处理中状态并返回 Unknown(未知),让消息代理稍后再查;过早提交会让下游看到未完成事实,过早回滚会丢失已可能成功的业务。超过回查预算后进入告警与人工核验。
- 进阶追问:回查接口查不到业务记录就一定回滚吗?
- 进阶回答:需确认本地事务不可能仍在提交、查询没有读延迟且业务键正确;否则先返回未知并保留证据。
问题:消息已提交但消费者一直失败,生产者事务会回滚吗?
- 考点:生产与消费边界、最终一致、补偿。
- 回答思路:明确可见消息已经代表上游决定,不会跨时空回滚本地事务。
- 详细答案:不会自动回滚。消费者按重试策略处理,超过预算进入死信或隔离队列,再由业务决定补做、补偿或人工修复。上游若需撤销,必须发起新的取消或冲正流程,不能修改已经提交的消息历史。
- 进阶追问:如何发现“消息消费成功但业务没成功”?
- 进阶回答:消费确认必须晚于业务提交,并用业务状态年龄、生产消费流水和对账交叉验证,不能只看中间件消费位点。
11. CDC(变更数据捕获)传递提交后的变化,不替业务定义事件语义
CDC(变更数据捕获)从数据库提交日志读取行级变化并发布,避免应用轮询整张 Outbox(发件箱)表。它能保持同一日志分区内的提交顺序并记录读取位点,但连接器重启、位点提交和消息发送之间仍可能重复;日志保留不足、表结构变更、快照与增量切换也会形成缺口。
| 风险 | 证据 | 处置 | 业务保护 |
|---|---|---|---|
| 位点落后 | 当前日志位置与连接器位点差 | 扩容、限流、修复下游 | 监控最老事件年龄 |
| 日志被清理 | 位点早于最早可用日志 | 从一致快照重建 | 以业务账本核对缺口 |
| 结构变更不兼容 | 解析失败与字段缺失 | 先扩展契约再迁移 | 事件版本与默认值 |
| 重复发布 | 位点提交晚于消息发送 | 重放同一事件标识 | 消费者幂等 |
flowchart LR
A["业务事务提交 Outbox(发件箱)行"] --> B["数据库提交日志"]
B --> C["CDC(变更数据捕获)连接器"]
C --> D["Outbox Event(发件箱事件) Router(路由器)"]
D --> E["按聚合类型与标识路由"]
E --> F["MQ(消息队列)"]
C --> G["持久化读取位点"]
G -.重启恢复.-> C数据演绎 11:日志保留与恢复时间。 数据库变更日志保留 24h(小时),业务峰值每小时产生 500GB(千兆字节) 日志,连接器正常处理能力比峰值高 20%。一次下游故障让连接器停 20h(小时),恢复后若仍按峰值输入,额外 20% 能力清理 20h(小时) 积压需要约 100h(小时),远超剩余 4h(小时) 保留窗口。必须暂停非核心发布、临时扩容或延长保留,不能只看“位点还存在”。
CDC(变更数据捕获)不应直接把所有业务表更新当领域事件。数据库一行可能因修复脚本、技术字段或多次内部更新变化,消费者不应猜业务含义。推荐由业务事务写入稳定 Outbox(发件箱)事件,CDC(变更数据捕获)只承担可靠搬运;事件包含聚合标识、类型、版本、发生时间和参数摘要。
热门面试题
问题:CDC(变更数据捕获)能否完全替代 Outbox(发件箱)?
- 考点:捕获机制与事件语义。
- 回答思路:区分“怎么搬运”与“要表达什么”。
- 详细答案:直接捕获业务表可减少一张表,但会泄露存储结构并难以区分技术更新与领域事实。更稳妥的是业务事务写 Outbox(发件箱)事件,CDC(变更数据捕获)读取其提交日志;前者定义语义,后者负责搬运。
- 进阶追问:只捕获订单状态字段可以吗?
- 进阶回答:仍需处理一次业务动作多次更新、修复脚本和版本兼容;必须有明确事件标识与发布规则。
问题:CDC(变更数据捕获)位点丢失怎样恢复?
- 考点:一致快照、增量衔接、重复与缺口。
- 回答思路:先冻结清理,再建立快照边界和业务核对。
- 详细答案:确认最早可用日志与业务时间窗,保留现场;从一致快照建立基线并记录对应日志位置,再接增量。快照重放会重复,消费者需幂等;日志已缺失的区间用业务表、Outbox(发件箱)归档和下游账本对账补发。
- 进阶追问:从最新位置启动为什么危险?
- 进阶回答:会静默跳过丢失区间,技术指标恢复正常但业务事件永久缺失。
问题:表结构变更如何避免击穿 CDC(变更数据捕获)链路?
- 考点:兼容发布、事件版本、连接器验证。
- 回答思路:采用扩展、迁移、收缩顺序。
- 详细答案:先新增兼容字段和事件版本,升级连接器与消费者并验证双读,再回填和切换生产者,最后在旧消费者清零后删除旧字段。灰度期间监控解析失败、未知字段和位点延迟,禁止先删列再期待连接器自愈。
- 进阶追问:事件中可以直接放完整数据库行吗?
- 进阶回答:不推荐,会放大隐私、体积和结构耦合;只发布消费者所需且有语义版本的字段。
12. 最大努力通知承认对方不受控,并以主动查询和对账兜底
Best Effort Notification(最大努力通知)适合支付回调、物流轨迹推送等跨组织场景。通知方按计划多次发送,接收方以业务号幂等处理;通知超过预算后不再无限轰击,接收方通过主动查询获取权威状态,双方最终依靠账单或业务清单对账。它追求可接受概率和最终可发现性,不承诺实时必达。
| 环节 | 通知方责任 | 接收方责任 | 共同证据 |
|---|---|---|---|
| 发送 | 稳定通知号、签名、退避重试 | 限流、验签、防重放 | 请求与响应摘要 |
| 处理 | 保留可查询权威状态 | 业务键幂等、状态条件 | 处理流水 |
| 超时 | 不把超时等于失败 | 主动查单,不换号重做 | 查询记录 |
| 收口 | 输出日终清单 | 差异识别与修复审批 | 对账批次与差额 |
sequenceDiagram
participant P as 支付渠道
participant S as 支付服务
participant R as 对账任务
P->>S: 第一次回调(支付单号)
S--xP: 响应丢失
P->>S: 退避后重复回调
S->>S: 验签、幂等、返回首次结果
alt 长期未收到回调
S->>P: 主动查单
P-->>S: 权威渠道状态
end
R->>P: 获取渠道账单
R->>S: 比对本地支付与账务流水数据演绎 12:通知重试覆盖率与流量。 单次通知成功率假设为 90%,各次失败近似独立,最多重试 5 次,则至少一次成功概率为 1 - 0.1^5 = 99.999%。但每日 1000000 笔通知在第一轮仍有约 `100000“ 笔进入第二轮;若故障相关而非独立,实际覆盖率会低得多。重试必须指数退避并带抖动,对剩余差异通过主动查询和对账闭环,不能拿公式替代演练。
支付服务收到成功回调,只能推进本地支付状态和创建账务事件,不能直接认定所有下游都完成。重复回调返回首次处理结果;签名失败、金额不符和商户不符进入安全告警,不应为了让渠道停止重试而返回虚假业务成功。
热门面试题
问题:最大努力通知为什么不等于“随便重试几次”?
- 考点:幂等、退避、主动查询、对账。
- 回答思路:说明通知只是发现事实的一条路径。
- 详细答案:完整方案要有稳定业务号、签名、防重放、有限退避、接收幂等和状态查询;通知失败后由接收方主动获取权威状态,最终用账单对账发现遗漏。只有重试而没有查询与对账,会留下不可见的永久差异。
- 进阶追问:通知方收到业务失败要继续重试吗?
- 进阶回答:参数或签名等确定性失败应进入人工或配置修复,系统拥塞等瞬时失败才按预算重试。
问题:支付回调处理成功但响应渠道失败怎么办?
- 考点:重复通知、结果重放、本地幂等。
- 回答思路:允许渠道重试,接收方返回首次结果。
- 详细答案:本地事务已提交就保持成功事实,渠道再次回调时按支付单号和渠道流水命中原处理记录,校验金额一致后返回成功,不重复入账或发货。响应丢失是通信问题,不应回滚已确定的支付事实。
- 进阶追问:可以缓存首次响应吗?
- 进阶回答:可加速,但权威依据应是持久支付与处理流水,缓存丢失不能导致重复副作用。
问题:主动查单和对账有什么区别?
- 考点:实时修复、批量兜底、证据来源。
- 回答思路:按时间尺度与覆盖范围区分。
- 详细答案:主动查单针对单笔未知状态,尽快从渠道获取结果并推进流程;对账按批次比较双方全量或增量清单,发现回调与查单都遗漏的差异。二者都要记录查询批次、权威响应和修复动作。
- 进阶追问:对账发现渠道成功、本地无单怎么办?
- 进阶回答:先按渠道号和金额确认归属,冻结后续结算,受审批补建或退款,不能自动造一笔无法关联的订单。
13. 外部支付与物流的未知结果只能查证、补偿和对账
外部支付、物流、短信和设备控制是独立系统或现实世界动作。本系统无法持有它们的数据库锁,也不能写它们的准备日志。调用超时只说明没有观察到响应:支付可能已扣款,物流可能已创建运单,设备可能已经执行。任何框架注解都不能把这些动作自动回滚。
flowchart TD
A["外部调用超时"] --> B["本地状态设为 Unknown(未知)"]
B --> C["按原业务号主动查询"]
C --> D{"外部权威结果"}
D -->|成功| E["幂等推进本地状态"]
D -->|明确失败| F["按规则重试或结束"]
D -->|仍未知| G["退避查询并限制年龄"]
G --> H["对账差错队列"]
H --> I["退款/拦截/退货/人工修复"]数据演绎 13:未知支付的资金敞口。 日支付 200000 笔,平均金额 300 元,渠道调用超时率 0.2%,则每日约 400 笔进入未知态,名义金额 120000 元。若主动查单在 5min(分钟) 内收敛 98%,仍有 8 笔、约 2400 元进入长尾;这部分必须暂停自动发货并进入对账,而不是因比例小就忽略。
WMS(仓储管理系统)履约同样如此。运单创建超时后,以客户单号查询;查到已创建则记录承运商运单号,查到明确无单才允许同号重试。已经揽收不能回滚数据库恢复为“未发货”,只能请求拦截、退回或进入售后。库存释放要与真实货权状态一致,不能为了订单页面好看先增加可售量。
热门面试题
问题:为什么外部支付不能被 Seata(分布式事务框架)自动回滚?
- 考点:资源参与协议、控制边界、现实副作用。
- 回答思路:检查渠道是否提供可恢复分支,而不是看本地注解。
- 详细答案:支付渠道不使用本系统的数据源代理,也没有向本系统提供 XA(扩展架构)准备分支或 TCC(Try Confirm Cancel,尝试确认取消)冻结接口。本地回滚只能撤销本地数据库写;渠道扣款需用独立退款单处理,并保留原支付流水和对账证据。
- 进阶追问:渠道提供撤销接口就等于 TCC(Try Confirm Cancel,尝试确认取消)吗?
- 进阶回答:不一定,要验证撤销时限、幂等、已结算边界和失败处理;很多撤销本质是新的补偿交易。
问题:物流下单超时能否换新请求号重试?
- 考点:未知结果、稳定业务号、重复运单。
- 回答思路:先查询原请求,再决定同号重放。
- 详细答案:不能直接换号,原请求可能已经创建运单,新号会生成重复运单和重复费用。应以客户单号主动查单;明确无单且协议允许时复用原号重试,仍未知则进入延迟查询和人工队列。
- 进阶追问:承运商不支持按客户单号查询怎么办?
- 进阶回答:降低自动重试,保存完整请求证据,通过账单、客服或批量清单核验;选型时把可查询性列为硬要求。
问题:支付未知期间订单应该显示什么状态?
- 考点:业务诚实、状态机、风险控制。
- 回答思路:使用处理中或待确认,不猜成功失败。
- 详细答案:订单显示支付确认中并提供查询入口,后台按预算查单;高价值订单暂停发货和库存最终扣减,预留可按规则延长。确认成功后继续履约,失败后释放,长期未知进入对账和人工服务。
- 进阶追问:用户再次支付怎么办?
- 进阶回答:先提示原支付处理中;业务允许多次支付时也要创建独立支付意图,并在确认重复扣款后自动退款而非覆盖流水。
14. 方案选择、线上排查与人工闭环必须围绕权威状态
选型顺序是:先缩小事务边界,再判断同步原子、资源预留、语义补偿或异步收敛。能放回同一服务同一数据库的不变量优先本地事务;内部短数据库事务才评估 2PC(两阶段提交)、XA(扩展架构)或 Seata AT(自动事务模式);可预留稀缺资源评估 TCC(Try Confirm Cancel,尝试确认取消);长履约评估 Saga(长事务模式);通知、账务事件和读模型优先 Outbox(发件箱)、事务消息、Inbox(收件箱)与 CDC(变更数据捕获)。外部动作永远补查单、对账和人工出口。
flowchart TD
A["发现订单/库存/支付不一致"] --> B["按业务号冻结自动推进"]
B --> C["查询各领域权威表与状态流水"]
C --> D["核对 Outbox(发件箱)/MQ(消息队列)/Inbox(收件箱)"]
D --> E["查询外部支付与物流"]
E --> F{"差异类型"}
F -->|漏发布| G["重发原事件"]
F -->|漏消费| H["幂等重放消费"]
F -->|外部未知| I["查单与对账"]
F -->|非法状态| J["审批后补记或冲正"]
G --> K["验证数量守恒与资金差额归零"]
H --> K
I --> K
J --> K数据演绎 14:对账分层缩小人工量。 每日订单 1000000 笔,先用业务号自动比对发现 0.1% 即 1000 笔状态差异;主动查单收敛 90% 后剩 100 笔;按重复事件、延迟消息和已知维护窗口规则再自动修复 70%,最终约 30 笔进入人工。若没有分层证据,人工需要逐笔看 1000 笔,既慢又容易误改。
线上排查遵循“先止血、再取证、后修复、最后验证”。止血可以暂停某业务键推进、关闭无限重试或隔离故障渠道,不能全表改成功。取证至少包含业务号、全局与分支号、数据库状态流水、消息事件与消费记录、外部查单和发布版本。修复动作生成新流水并审批,验证库存守恒、账务差额、未知状态年龄和重复副作用全部归零。
项目话术: “我不会把分布式事务等同于 Seata(分布式事务框架)注解。WMS(仓储管理系统)库存先用本地事务、条件更新和唯一预占流水守住不超卖;跨服务用 Outbox(发件箱)和 Inbox(收件箱)传递至少一次事件。支付超时进入未知态并按商户单号查单,账务用唯一分录消费,物流已揽收只能拦截或退货。框架只覆盖受控资源,最后用状态机、对账和人工差错队列收口。”
热门面试题
问题:订单创建成功但库存扣减失败怎么办?
- 考点:状态机、权威库存、补偿与审计。
- 回答思路:订单先保持待确认,不伪造成功,再按失败类型收敛。
- 详细答案:先按订单行号查询库存预占流水,区分明确余量不足、处理超时和消息漏消费。明确不足则取消订单;超时先查原请求,漏消息重发原事件。库存成功但订单未推进时幂等补状态,长期差异进入人工,所有修复保留流水。
- 进阶追问:可以直接给库存加回去吗?
- 进阶回答:不能,先确认是否真的扣减及货权状态;重复释放会制造虚假库存,已出库更不能增加可售。
问题:如何选择强事务、TCC(Try Confirm Cancel,尝试确认取消)、Saga(长事务模式)和消息一致性?
- 考点:不变量、资源控制、时延、补偿能力。
- 回答思路:从业务约束而非框架熟悉度决策。
- 详细答案:同库不变量优先本地事务;全部内部资源支持协议且事务短才评估强事务;必须同步占住库存或额度且可明确释放时评估 TCC(Try Confirm Cancel,尝试确认取消);长履约且有语义补偿用 Saga(长事务模式);允许异步的通知、账务事件和投影用消息一致性。
- 进阶追问:性能要求高就一定选消息吗?
- 进阶回答:不一定,还要看用户能否接受中间态、下游是否幂等以及对账能否在业务时限内收敛。
问题:分布式不一致事故怎样做到可审计修复?
- 考点:证据链、审批、新流水、验证。
- 回答思路:禁止直接改终态,按事实生成修复交易。
- 详细答案:冻结自动推进并保存现场,按业务号串联数据库、消息和外部查单,确定差异类型。补发使用原事件,补记或冲正生成独立修复号、原因、操作者和审批记录;修复后重跑对账,验证库存数量守恒、资金差额为零且未知态清空。
- 进阶追问:紧急事故可以先改表后补审批吗?
- 进阶回答:高风险资金与库存不应无痕改表;应使用预授权应急工具生成审计流水,事后复核而不是补写无法证明的说明。
知识节收口
前 14 节已经形成从单资源不变量到跨组织对账的完整决策链。本小节只用于终止知识节审计范围,不新增知识标记或六字段题;下面的综合题负责把机制转成可复述、可追问、可验证的项目表达。
综合口述题库
问题(综合题):请系统说明分布式事务方案,并给出选型顺序。
口述答案:我不会先报框架名,而是先找不变量和权威写入方。若库存余量、预占流水和事件意图能够放在同一数据库,就优先本地事务,用条件更新保证数量不负、唯一键保证同一订单行只处理一次,再用 Outbox(发件箱)传播。跨多个内部数据库且事务很短、所有资源都支持 XA(扩展架构)、业务能接受准备阶段持锁时,才评估 2PC(两阶段提交)或 Seata(分布式事务框架) XA(扩展架构模式);它们的代价是阻塞和恢复窗口,不是免费强一致。
若库存、额度可显式拆为冻结、确认、释放,并且主流程必须同步知道是否占住资源,我会评估 TCC(Try Confirm Cancel,尝试确认取消),同时实现 Confirm(确认)与 Cancel(取消)幂等、空回滚、防悬挂和过期治理。订单到履约是长流程,前序本地事务已提交且允许语义补偿时,用 Saga(长事务模式);已拣货、已揽收等不可逆动作转拦截、退货或人工,不称为数据库回滚。通知、积分、账务事件和读模型允许异步,就用事务消息或 Outbox(发件箱)加 Inbox(收件箱),承认至少一次并靠状态机收敛。
外部支付和物流永远单独判断。它们不受本地数据源代理控制,超时只代表未知,必须复用稳定业务号查单,明确成功后推进,明确失败后按规则重试,长期未知进入对账。最终决策还要量化峰值、锁时长、不一致窗口、人工量和恢复目标,并通过提交后断网、重复投递、协调者故障、补偿失败演练验证。这样选的是业务可证明性,不是熟悉哪个注解。
追问 1:为什么不统一使用 Seata(分布式事务框架)?
直接回答:不同模式资源前提不同,且外部支付、物流不参加受控数据库分支;统一套用会掩盖未知结果和补偿责任。
追问 2:最终一致是否等于可以无限延迟?
直接回答:不等于,必须定义业务可接受窗口、最老状态告警、自动收敛预算和人工服务目标。
追问 3:选型最先看的性能指标是什么?
直接回答:先看正确性和失败可恢复,再量化锁持有、峰值吞吐、积压年龄与人工差错量。
问题(综合题):为什么本地事务、唯一约束和状态流水是所有分布式方案的地基?
口述答案:分布式流程不是一个神奇的全局提交,而是多个本地事实通过网络逐步关联。如果库存服务内部都可能出现“余额已减但预占流水不存在”,或者支付状态成功却没有唯一账务事件,那么外层重试根本不知道应该补做还是撤销。因此我先把每个领域的权威不变量放进本地事务:库存用带余量条件的原子更新,预占流水以订单行和动作建立唯一键,支付用商户订单号约束支付意图,账务用支付单、方向和版本约束唯一分录;状态迁移同时检查前置状态和版本。
本地事务还要与待发布意图共同提交。库存预占成功时在同一事务写 Outbox(发件箱),即使进程在响应前崩溃,恢复后仍能查到业务事实和未发布事件。应用收到超时不能猜回滚,因为数据库可能已经提交,只能按稳定业务键查询并重放首次结果。消息消费也是同理:Inbox(收件箱)去重、业务更新和结果流水同事务提交,之后才确认消息;确认丢失会重复投递,但不会重复产生副作用。
线上排查时我按业务号从权威表开始,而不是先看某条 Trace(链路追踪)。检查唯一键、前后状态、事件和消费流水,再查外部结果。修复不直接改终态,而是生成补发、补记或冲正流水。只有每个本地提交都确定、可查询、可幂等,TCC(Try Confirm Cancel,尝试确认取消)、Saga(长事务模式)和消息最终一致才有可靠锚点,否则所谓补偿只是猜测。验收时还会在数据库提交后、应用返回前强制退出,确认重启查询能还原首次结果,待发布事件仍可被扫描,重复请求不会生成第二条业务流水。
追问 1:提高数据库隔离级别能替代条件更新吗?
直接回答:不能机械替代;条件更新直接表达余量不负,隔离级别只决定并发可见性且可能增加等待。
追问 2:分布式锁能作为最终地基吗?
直接回答:不能,租约过期和网络分区会产生并发持有者,最终写仍需数据库约束和版本条件。
追问 3:为什么修复要生成新流水?
直接回答:新流水保留原事实、修复原因、审批人与影响量,便于对账和追责,直接改值无法证明过程。
问题(综合题):请从正常流程、阻塞和恢复三个角度解释 2PC(两阶段提交)。
口述答案:2PC(两阶段提交)由协调者和参与者组成。第一阶段协调者发送 Prepare(准备),参与者执行分支事务,持久化恢复日志并保留必要锁与连接,回答能否提交。只有所有参与者都回答已准备,协调者才持久化全局 Commit(提交)决定;任一拒绝则记录 Rollback(回滚)。第二阶段协调者反复发送这个确定决定,参与者完成提交或回滚后释放资源。准备成功不是业务已完成,而是参与者承诺即使重启也能服从未来决定。
阻塞发生在参与者已准备却拿不到全局决定时。此时它不能自行猜提交或回滚,否则不同分支可能走向相反结果,只能持锁等待协调者或日志恢复。热点库存行原本持锁几十毫秒,跨服务准备、日志和通知把锁周期放大到数百毫秒,吞吐会按锁时间反向下降;协调端故障还会占满连接池。因此必须限制事务长度、分支数量和入口并发,监控已准备分支数、最老年龄与第二阶段重试。
恢复时先从协调日志确认全局决定,再逐分支查询并幂等补发。若运维在长期等待中强制某分支单边处理,就可能出现 Heuristic Outcome(启发式结果),全局原子性已不能保证,必须冻结业务键、保留日志并按业务账本对账。2PC(两阶段提交)适合全部受控且支持协议的短事务,不覆盖支付渠道、物流或实物动作;这些外部结果仍需查单、补偿和人工闭环。容量验收会把协调者停机时间逐级拉长,记录已准备分支、锁等待、连接池占用和恢复峰值,证明故障窗口没有超过核心接口的服务目标。
追问 1:协调者高可用后还会阻塞吗?
直接回答:仍可能,网络分区、日志复制、参与者恢复和领导切换都有窗口,高可用只降低概率。
追问 2:准备成功后为什么还保留回滚能力?
直接回答:全局可能因其他参与者拒绝而决定回滚,当前分支必须能在重启后执行该决定。
追问 3:2PC(两阶段提交)适合秒杀库存吗?
直接回答:通常不适合热点高并发,准备持锁会显著降低吞吐并放大协调故障影响。
问题(综合题):出现 Heuristic Outcome(启发式结果)时,怎样完成一次安全修复?
口述答案:Heuristic Outcome(启发式结果)不是普通重试失败,而是资源管理器或运维在得不到全局决定时单边提交或回滚,导致各分支可能不一致。第一步是止血:按全局事务号和业务号暂停订单继续履约、冻结相关库存与结算,禁止自动任务反复改状态。第二步保留证据,包括协调日志、每个分支的准备与终局日志、数据库状态流水、应用版本、操作者和外部调用记录;不能为了释放锁先删事务日志。
第三步确定权威事实。库存看余额、冻结、扣减和释放流水是否守恒,账务看唯一分录与借贷方向,支付与物流用原业务号向外部查单。然后分类处理:全局决定明确而某分支未执行,就幂等补发原决定;分支被强制走向相反结果,则发起新的业务补偿,例如恢复库存、补记账务或退款冲正。已出库、已揽收等不可逆动作进入拦截、退货和人工履约,不能用数据库更新伪造回滚。
所有修复都使用独立修复号、前置状态、数量或金额、原因、审批人和执行结果,失败可再次查询。最后重跑订单、库存、支付、账务和外部清单对账,验证差额归零、未知态清空、锁释放,并补故障演练与告警。若只让技术事务状态变成完成,却没有核对业务守恒,事故并未结束。复盘还要区分协议内恢复失败和越权人工操作,前者补协调日志与自动接管测试,后者收紧工具权限、双人审批和影响量预览;同类差异再次出现时必须能被相同规则自动识别。
项目验证会随机选择已准备分支制造单边提交与单边回滚,修复后逐笔核对原决定、修复流水和业务余额,确保任何人工动作都能从全局号追溯。
追问 1:能否统一选择提交较多的一侧作为最终状态?
直接回答:不能投票决定,必须看已对客户承诺、资金和实物事实,再选择合法补偿路径。
追问 2:为什么不直接恢复数据库备份?
直接回答:备份会覆盖事故后合法交易,且不能回滚外部支付和物流,风险通常更大。
追问 3:修复完成的唯一标准是什么?
直接回答:技术分支终结只是条件,最终要业务不变量成立、对账差额归零且审计链完整。
问题(综合题):如何为库存与资金设计一套可落地的 TCC(Try Confirm Cancel,尝试确认取消)?
口述答案:我先定义资源模型,而不是先写三个方法。库存记录拆为可售、冻结和已扣减,资金拆为可用、冻结和已支付;每次分支持久化全局号、分支号、业务号、数量或金额、状态、版本和到期时间。Try(尝试)在一个本地事务中校验条件并把可用转冻结,成功的含义是资源已经排他预留,后续 Confirm(确认)不应再依赖可能变化的余额。库存与资金任一 Try(尝试)失败,全局进入取消决定。
Confirm(确认)只允许已预留转已确认,把冻结转扣减或支付,重复调用返回首次结果。Cancel(取消)只允许已预留转已取消,把冻结释放;重复取消不重复增加可售或可用。三阶段都以分支号和状态条件幂等,参数摘要不同却复用同键时报警。Cancel(取消)先到且查不到预留时写空回滚屏障,迟到 Try(尝试)在插入预留前检查屏障并拒绝,防止悬挂。屏障保留期覆盖协调重试与审计窗口。
过期任务只处理仍为已预留且版本未变的记录。支付结果未知时先查渠道,不能到期直接释放;全局已决定提交后 Confirm(确认)失败,应继续幂等确认并告警,不能改发取消。监控 Try(尝试)成功率、冻结量、最老预留、二阶段重试、空回滚和悬挂拒绝。故障测试覆盖取消先到、重复二阶段、进程重启和确认永久失败,最终验证库存与资金守恒。压测还要比较预留时限与成交率,确认大量未支付订单过期时,回收任务不会抢占正常确认资源,也不会因全表扫描把数据库拖慢。
追问 1:Try(尝试)只做校验可以吗?
直接回答:通常不够,资源未预留会在 Confirm(确认)时被其他交易抢走,违背可确认目标。
追问 2:冻结时间越长越安全吗?
直接回答:不是,会降低库存和资金可用性;时限应匹配业务承诺并有续期、回收和用户提示。
追问 3:Confirm(确认)还需要幂等吗?
直接回答:必须,响应丢失会触发重复确认,不幂等会重复扣冻结资源。
问题(综合题):请解释 TCC(Try Confirm Cancel,尝试确认取消)的空回滚、悬挂与防悬挂实现。
口述答案:空回滚的典型时序是 Try(尝试)请求因网络延迟尚未到参与者,协调者已因超时决定回滚并发送 Cancel(取消)。参与者查不到预留资源时不应报错或凭空释放,而要把该分支的取消事实持久化后返回成功。如果只返回成功不落记录,迟到 Try(尝试)随后可能成功预留,而协调者认为取消已结束,不会再次发送 Cancel(取消),这笔冻结资源就永久悬挂。
防悬挂要在参与者本地建立分支屏障。屏障以全局号、分支号和动作类型唯一;Cancel(取消)未找到 Try(尝试)记录时,在本地事务插入空回滚屏障。Try(尝试)真正预留前,在同一事务检查取消屏障并尝试插入预留记录;唯一约束和锁保证并发的 Try(尝试)与 Cancel(取消)只有一种合法顺序。命中屏障后 Try(尝试)返回已取消,不再改变可售或可用资源。Confirm(确认)和 Cancel(取消)也只执行合法前置状态迁移。
屏障不能放在易丢失缓存,也不能按很短时间清理,因为灾备恢复、消息积压或网络重试可能让迟到请求跨越很久。保留期至少覆盖协调器日志、最大重试和业务审计窗口;清理前确认全局终局。线上监控空回滚数量、悬挂拒绝、最老冻结和分支状态冲突,异常上升通常说明超时预算、网络或协调恢复有问题,而不是简单扩大重试次数。并发测试会让 Try(尝试)与 Cancel(取消)在数据库提交点交错上万次,核对每个分支最终只有屏障或有效预留中的一种状态,资源总量始终守恒。
追问 1:Cancel(取消)先到为什么返回成功?
直接回答:它的业务目标是确保没有预留资源;写入取消屏障后该目标成立,重复调用也可安全成功。
追问 2:数据库唯一索引能单独防悬挂吗?
直接回答:要配合正确屏障键和同事务状态判断;只有一个普通唯一索引无法表达取消先于尝试。
追问 3:屏障表故障时是否可跳过检查?
直接回答:不能,关键资源应失败关闭或进入待处理,跳过会重新开放悬挂与重复扣减窗口。
问题(综合题):全局已经决定提交,但某个 TCC(Try Confirm Cancel,尝试确认取消)参与者长期确认失败,怎么办?
口述答案:全局 Commit(提交)决定一旦持久化,其他分支可能已经 Confirm(确认),因此不能因为某个参与者一次超时就改发 Cancel(取消)。我会先按全局号和分支号查询参与者状态,区分请求未到、确认执行中、已成功但响应丢失、业务数据损坏和确定性拒绝。前三类使用同一分支号幂等重试 Confirm(确认),每次记录尝试、错误和下一次时间;调用方设置退避与预算,避免故障期间轰击数据库。
如果参与者已确认,直接回放首次结果并修复协调状态。如果仍处于已预留,检查冻结资源、版本和业务参数,修复瞬时依赖后继续确认。若预留被错误释放或数据缺失,原子提交能力已经破坏,应冻结相关订单、库存或资金,保留现场并进入人工差错队列;根据其他分支和外部事实选择补记扣减、补充库存、退款或客户补偿,不能篡改全局决定掩盖问题。
监控不只看确认调用失败率,还看最老未确认分支、冻结金额与数量、重试收益和人工量。故障演练在 Confirm(确认)提交后丢响应、数据库短时不可用和数据校验冲突三个位置注入,验证重复确认不重复扣减,恢复任务可接管,永久失败有审批出口。原事务终局与后续业务补偿要用不同流水表示。若未确认分支持续增长,我会先限制新交易并保护查询和修复容量,再分析是协调通知、参与者数据还是错误超时造成,避免恢复流量与实时流量相互挤压。
发布门禁还会统计最老未确认分支及冻结资源总量;超过时限时自动限制新预留,为确认重试和人工核验保留独立容量。
追问 1:可以设置确认失败后自动取消吗?
直接回答:全局已提交时不可以,自动取消会与已确认分支形成相反结果。
追问 2:为什么 Confirm(确认)应尽量只消费预留资源?
直接回答:这样不再依赖实时余额或新外部条件,能提高“Try(尝试)成功则确认可完成”的概率。
追问 3:永久失败是否说明 TCC(Try Confirm Cancel,尝试确认取消)不适用?
直接回答:若业务无法保证预留或无法提供可靠人工修复,确实应重新评估边界和方案。
问题(综合题):如何为订单、库存、支付与履约设计 Saga(长事务模式)?
口述答案:我把每一步看作已提交的本地交易,而不是全局数据库回滚。订单先创建为处理中,库存用本地事务预占并发布结果,支付按商户单号发起并在未知时查单,履约在支付与库存事实满足后建单。流程管理器持久化当前步骤、已完成动作、补偿栈、业务号、版本和超时,只拥有流程进度,不直接写库存、支付或履约表。复杂分支和超时较多时选择 Orchestration(编排);简单事件传播可用 Choreography(协同),但要避免隐式环路。
后续失败时按反向业务依赖补偿:未拣货可取消履约,再释放库存,已支付则发起独立退款;每个补偿有稳定业务号、前置状态和幂等流水。补偿不是删除原事实,退款保留支付与退款两笔记录,库存释放关联原预占。已拣货需回库复核,已出库或物流已揽收不能自动回滚,只能拦截、退货、客户协商或人工履约。不可逆动作应尽量后置,用户看到的中间态要诚实标为处理中。
补偿失败按错误分类有限退避,超过年龄进入隔离队列,暂停会扩大损失的后续动作。监控每个步骤成功率、流程年龄、补偿次数、库存冻结、待退款与人工工单。演练包括支付成功后履约失败、物流成功但响应丢失、补偿服务不可用和事件乱序,最终用订单、库存、支付、账务和物流清单对账证明收敛。流程版本升级采用旧实例可继续旧状态机、新实例兼容旧事件的方式,不能让发布中的半程订单因找不到补偿节点而永久停留。
追问 1:Saga(长事务模式)能保证隔离吗?
直接回答:不能天然保证,前序本地提交可被观察,需要处理中状态、版本条件和业务规则降低影响。
追问 2:补偿顺序一定与正向严格相反吗?
直接回答:通常按依赖反向,但要服从业务事实,例如先拦截物流再释放库存,不能机械倒序。
追问 3:流程管理器如何避免单点?
直接回答:状态持久化、实例无状态接管、命令幂等和版本竞争;参与者仍保留自身状态机。
问题(综合题):Seata AT(自动事务模式)的工作原理、适用范围和风险是什么?
口述答案:Seata AT(自动事务模式)通过数据源代理拦截受支持数据库写入。第一阶段在同一个本地事务中提交业务数据和 undo log(回滚日志),并在提交前取得全局锁;提交后释放本地连接和锁。全局提交时第二阶段主要异步清理 undo log(回滚日志),全局回滚时根据前镜像生成补偿写入,并用后镜像检查当前数据是否仍符合预期,避免覆盖其他已提交变化。它相对业务侵入低,但并非没有协调、锁冲突和回滚失败。
适用前提是短事务、受支持关系型数据库、写入经过代理且 SQL(结构化查询语言)在支持范围内。热点库存行会竞争全局锁,远程调用放在全局事务中会拉长锁窗口;调用方超时重试又会放大冲突。绕过代理的数据源、脚本直写、复杂不支持语句和其他存储不会自动获得镜像补偿。生产接入前要核对实际 Seata(分布式事务框架)版本、数据库、驱动、代理方式和语句清单,并压测最坏回滚与协调端故障。
外部支付、物流、短信和设备控制完全不在自动回滚范围。即使调用位于全局事务注解方法内,渠道也没有 undo log(回滚日志)和全局锁;超时后仍需查单,成功后的撤销是退款、拦截或退货。线上监控全局锁等待、分支年龄、回滚校验冲突、协调日志和业务对账。若锁窗口或外部动作占主导,应缩小事务边界,改用 TCC(Try Confirm Cancel,尝试确认取消)、Saga(长事务模式)或消息一致性。正式上线前用实际语句集执行提交、回滚和脏写校验测试,并核对代理是否覆盖所有数据源。
追问 1:第一阶段提交后其他事务能看到数据吗?
直接回答:本地数据已提交,但全局锁用于约束受代理的冲突写;绕过代理的访问仍需单独治理。
追问 2:后镜像不匹配还能强制回滚吗?
直接回答:不应盲目覆盖,需识别脏写来源并人工决定补偿,否则会破坏其他合法提交。
追问 3:Seata AT(自动事务模式)适合长履约吗?
直接回答:通常不适合,长流程会扩大协调和锁风险,且包含不可逆外部动作。
问题(综合题):同一项目中如何选择或混用 Seata AT(自动事务模式)、TCC(Try Confirm Cancel,尝试确认取消)模式、Saga(长事务模式)和 XA(扩展架构模式)?
口述答案:我按参与者资源能力分段选择,不要求一个全局流程只有一种模式。两个内部关系型数据库的短写入若都经过代理、语句受支持且锁冲突可控,可评估 Seata AT(自动事务模式);若数据库原生支持 XA(扩展架构)、需要更强隔离且能承受准备后阻塞,可评估 Seata(分布式事务框架) XA(扩展架构模式)。两者都要限制事务长度,远程慢调用和人工步骤不能包在锁周期里。
库存冻结、优惠额度等能明确预留且主流程必须同步得知结果的参与者,可实现 Seata(分布式事务框架) TCC(Try Confirm Cancel,尝试确认取消)模式。Try(尝试)冻结,Confirm(确认)消费冻结,Cancel(取消)释放,并处理幂等、空回滚与防悬挂。长时间订单履约、跨多个内部或遗留系统的流程用 Seata(分布式事务框架) Saga(长事务模式),每个本地步骤独立提交,失败执行语义补偿;必须承认不保证隔离,并把不可逆动作后置。
混用的边界由流程管理器与全局业务号连接,但每个资源仍保留自己的权威状态。外部支付和物流不因“混用”而自动加入事务,它们走稳定业务号、查单、补偿和对账。上线前逐模式记录版本、资源支持、故障点、恢复目标和人工出口,用锁冲突、协调端宕机、二阶段重复和补偿失败演练验证。若一个流程需要大量模式才能成立,通常说明服务与事务边界值得重新划分。架构评审还要求画出每个模式的提交点、可见中间态和最终证据,防止团队只看到统一注解而忽略不同恢复责任。
追问 1:模式混用会自动保证统一终局吗?
直接回答:框架可协调受支持分支,但业务补偿和外部结果仍需状态机与对账证明。
追问 2:隔离要求高就一定选 XA(扩展架构模式)吗?
直接回答:还要看资源支持、锁周期、峰值和外部参与者;有时缩小本地不变量更合理。
追问 3:如何判断模式选错?
直接回答:锁等待、长期冻结、人工差错和恢复时间持续超标,或业务无法解释中间态,都是重构信号。
- 问题(综合题):如何评估并迁移一个使用 TX-LCN(事务协调框架)的存量系统?
口述答案:我先做事实盘点,不以“旧”直接判死刑。记录生产 TX-LCN(事务协调框架)版本、Spring(Java 应用框架)与数据库驱动组合、事务管理端部署、事务客户端数量、事务组范围、平均与最大锁时长、恢复日志和已知事故。再核对官方仓库发布与维护情况;截至本文核对日,官方页面展示的最新发布停留在
2020-07-02,这意味着新框架兼容和安全修复要由项目自行验证,而不是证明现网立即不可用。然后进行故障演练:事务管理端重启或分区、参与者提交后断联、连接池耗尽和重复协调命令,观察已准备或等待分支数、连接占用、最老事务和业务差异。尤其检查外部支付、物流是否被错误包进事务注解却没有查单与对账。若系统能够在目标时间恢复且短期无法迁移,就补高可用、限流、业务流水、告警和人工出口,受控维持。
迁移按风险分层。先把通知、报表和积分等最终一致分支改为 Outbox(发件箱)与 Inbox(收件箱),缩短事务组;再为库存与账务建立本地条件约束、唯一流水和对账,以影子比对验证新链路;最后收缩或移除协调事务。迁移期间只有一个权威写路径,影子链路不产生副作用,重复通过业务键吸收。每批具备回退开关和差异报表,不能一次性替换所有核心交易。切换门禁同时观察新旧结果一致率、连接占用、锁时间和人工差异,连续满足业务周期后才删除旧协调入口与恢复数据。
每个迁移批次至少跨过一个完整结算周期,再比较新旧链路的差异率、锁时间和恢复时间;任一指标退化都只回退该批次,不恢复已移除的异步分支。
追问 1:为什么迁移先从非核心异步分支开始?
直接回答:它们不要求同步原子,能低风险缩短锁周期并验证消息治理能力。
追问 2:保留旧框架最少要补什么?
直接回答:协调端恢复演练、连接容量、业务流水、外部查单、对账和明确人工处理服务目标。
追问 3:双跑期间如何避免双扣库存?
直接回答:新链路先影子比对不写,切换后用同一业务唯一键和条件更新,旧入口被明确关闭。
- 问题(综合题):请设计一个可靠的 Outbox(发件箱)发布链路并解释全部失败窗口。
口述答案:业务事务中同时写权威数据和 Outbox(发件箱)事件。事件包含稳定事件标识、聚合标识、类型、契约版本、业务发生时间、参数摘要和待发布状态;不能直接把数据库整行当事件。事务回滚时两者都不存在,事务提交时发布意图必然可查,解决“业务成功但没有任何消息线索”的双写问题。发布器按索引条件抢占待发布记录,发送同一事件标识,成功后条件更新状态。
失败窗口有三类。业务提交后发布器崩溃,事件仍为待发布,恢复后继续扫描;消息代理已接收但确认丢失,发布器会重发同一事件,所以消费者必须幂等;发送成功后发布状态更新失败,也会重发,不能为追求仅一次而先标记已发布。多发布器使用分片、租约或条件抢占减少重复,但正确性仍建立在事件稳定和消费幂等上。失败重试要退避并限制次数,确定性契约错误进入隔离队列。
容量治理看每秒新增、发布能力、最老待发布年龄和数据库扫描耗时,不能只看总行数。索引以状态、下一次时间和分片键支持顺序读取,历史已发布记录在超过回放、对账和审计周期后归档。若使用 CDC(变更数据捕获),仍保留 Outbox(发件箱)事件语义和位点监控。故障测试在事务提交后、消息发送后和状态更新前分别强杀进程,验证不漏事件、允许重复且业务只生效一次。还要模拟消息代理长时间不可用,确认发布器退避后不会占满业务连接池,恢复时按预算清积压而非瞬间打垮消费者。
追问 1:发布状态有什么价值?
直接回答:用于调度、退避、告警和归档,是效率状态,不是仅发送一次的正确性保证。
追问 2:事件按什么键分区?
直接回答:通常按需要局部顺序的聚合标识,如订单号或库存业务键,同时关注热点倾斜。
追问 3:Outbox(发件箱)表能与业务库分开吗?
直接回答:若分开且没有共同本地事务,原子桥接价值就丢失;应与业务事实位于同一事务资源。
- 问题(综合题):请设计一个支持重复、乱序和崩溃恢复的 Inbox(收件箱)消费者。
口述答案:消费者先定义业务幂等键,而不是只拿中间件投递标识。支付入账可以用支付单号、账务方向和事件版本,库存释放可以用原预占号、动作和版本;同一键携带不同金额或参数摘要时视为冲突并告警。收到消息后开启本地事务,插入 Inbox(收件箱)去重记录,执行带前置状态和版本的业务更新,写结果流水,全部成功后提交,再向 MQ(消息队列)确认。
若业务提交后确认丢失,消息会重复投递。唯一约束命中后读取首次结果,校验参数一致并安全确认,不重复入账或释放。若先确认再提交,进程崩溃会永久漏处理;若去重表先独立提交,后续业务失败会被误判已经完成。乱序事件由状态版本处理:当前已退款时,迟到的支付成功不能把状态逆向覆盖,应忽略、转冲突或触发对账,具体取决于业务事实。
外部物流或短信调用不放在长数据库事务中。先在消费事务持久化待调用任务,再用稳定客户单号执行;超时保持未知并查单,结果回写状态机。监控重复率、冲突率、消费年龄、失败重试和隔离队列。故障演练在业务提交前后、确认前和外部调用后强制退出,并回放历史消息,验证消费者可恢复、同一业务副作用一次、不同合法版本按规则推进。对于批量消费还要验证单条失败不会把已提交记录整体重复执行,确认粒度、事务粒度与回放策略保持一致。
消费组扩缩容和分区转移也纳入验证:新实例只能依据数据库中的去重与业务状态接管,不能依赖旧实例内存;参数冲突单独告警,禁止当普通重复吞掉。
追问 1:Inbox(收件箱)记录何时清理?
直接回答:晚于消息保留、最大回放和业务审计窗口,资金类还需匹配账务留存要求。
追问 2:重复消息直接丢弃可以吗?
直接回答:要先确认首次处理结果存在且参数一致,冲突重复不能静默丢弃。
追问 3:内存集合去重有什么问题?
直接回答:重启即丢失,无法与业务事务原子提交,也覆盖不了长期回放。
- 问题(综合题):事务消息如何解决生产端一致性,又有哪些明确做不到的事?
口述答案:以 RocketMQ(分布式消息队列)为例,生产者先发送对消费者不可见的 Half Message(半消息),消息代理保存成功后才执行本地事务。事务成功则提交消息使其可见,失败则回滚半消息。若第二次确认因网络或进程故障丢失,消息代理回查生产者;回查服务必须按业务号查询持久化本地事实,返回提交、回滚或 Unknown(未知),不能看内存标志,也不能把一次查询超时猜成回滚。
这套机制解决的是生产者本地事务与消息可见性之间的原子桥接:本地明确成功时最终会有可投递消息,本地明确失败时消息不应交给消费者。它仍是最终一致,半消息回查期间存在延迟;大量未知会形成回查压力,因此本地事务要短、业务号有索引、进行中状态可识别,并监控最老半消息与回查次数。选择它意味着接受特定 MQ(消息队列)产品协议和版本边界。
它做不到自动保证消费业务成功。消费者可能重复、失败、乱序或在业务提交后确认丢失,仍需 Inbox(收件箱)、状态机、有限重试、隔离队列和对账。更做不到回滚外部支付或物流;消息提交只代表上游本地决定,物流消费超时后仍要查单。故障测试要覆盖半消息成功后本地失败、本地成功后二次确认丢失、回查时仍进行中和消费者提交后崩溃。验收还应限制回查查询对业务数据库的压力,避免网络抖动时大量半消息回查反过来拖慢正常支付提交。
回查服务与正常交易使用独立线程和连接预算,故障期间按最老半消息优先;验收同时检查回查峰值没有拖慢支付本地事务,也没有把进行中状态误判为回滚。
追问 1:回查查不到业务记录就返回回滚吗?
直接回答:需排除事务仍在提交、读延迟和业务键错误;不确定时返回未知并告警。
追问 2:事务消息会不会重复投递?
直接回答:会,发送、回查和消费确认都有重试窗口,消费者必须幂等。
追问 3:为什么不把本地事务做得很长?
直接回答:会增加半消息未知和回查压力,占用资源并让最终一致窗口不可控。
- 问题(综合题):CDC(变更数据捕获)接入 Outbox(发件箱)时,如何保证可恢复和可演进?
口述答案:我让业务事务写稳定 Outbox(发件箱)事件,CDC(变更数据捕获)只读取数据库提交日志并路由,而不是让消费者直接理解订单、库存表的每次行变化。事件包含聚合标识、类型、版本、发生时间和载荷,数据库字段只是存储实现。连接器持久化读取位点,监控当前日志位置、位点差、每秒处理量和最老事件年龄;消息发送成功但位点提交失败会重复,消费者照常幂等。
恢复能力取决于日志保留和追赶余量。停机时间加预计追赶时间必须小于剩余日志保留窗口;只看“保留一天”不够,若恢复能力仅比峰值高少量,积压可能在日志清理前追不完。位点丢失时先保护日志和现场,从一致快照建立基线并记录对应日志位置,再衔接增量;快照重放允许重复,日志已缺失区间用 Outbox(发件箱)归档、业务表和下游账本对账补发,不能从最新位置静默跳过。
结构演进采用扩展、迁移、收缩。先新增兼容字段和事件版本,升级连接器与消费者,灰度观察未知字段和解析失败,再切换生产者,最后在旧消费者清零后删除旧结构。敏感字段不因 CDC(变更数据捕获)便利而整行发布。演练包含连接器停机超过高水位、位点回退、重复快照和删改字段,验收不漏业务事件、重复可吸收且旧消费者仍兼容。灾备切换时还要确认新主库日志时间线与连接器位点关系,禁止把旧位点直接套到不连续日志上造成静默缺口。
灾备切换后抽取切换点前后的业务号,逐笔比较源表、事件、消息和下游状态;只有位点连续、重复可吸收且没有静默缺口,才恢复常规日志清理。
追问 1:CDC(变更数据捕获)延迟低是否就优于轮询?
直接回答:不一定,它增加日志、位点、快照和结构治理;应按吞吐、团队运维和恢复要求选择。
追问 2:位点前进能证明下游业务成功吗?
直接回答:不能,只证明连接器读取和发布进度,消费与外部副作用要独立审计。
追问 3:为何推荐捕获 Outbox(发件箱)而非所有业务表?
直接回答:Outbox(发件箱)显式表达领域事件,减少存储耦合、技术更新噪声和隐私泄露。
- 问题(综合题):最大努力通知怎样用于支付回调并保证最终可发现?
口述答案:最大努力通知的前提是双方独立、网络不可靠,目标不是一次必达,而是通过通知、主动查询和对账让差异最终可发现。支付渠道以稳定通知号和支付单号发送,包含金额、币种、商户、状态、时间和签名;接收方先验签、防重放和校验商户金额,再以渠道流水与支付单号幂等推进本地状态,在同一事务写支付处理流水和账务事件。响应丢失时渠道重试,接收方返回首次结果,不重复入账或发货。
通知方按指数退避与抖动有限重试,区分系统拥塞和签名、参数等确定性失败,避免无限轰击。接收方长时间没收到回调时主动按商户单号查单:明确成功则补推进,明确失败则结束或允许新支付,仍未知则保持处理中并限制自动发货。主动查单解决单笔及时性,日内或次日渠道账单对账覆盖通知和查单都遗漏的长尾。
对账差异进入受审批修复:渠道成功本地无状态时核对归属后补记或退款,本地成功渠道失败时冻结履约并冲正。监控回调成功率、重复率、最老未知、主动查询收敛率和对账差额,不能只看接口返回。最大努力通知不保证实时一致,也不自动保证消费者后续动作;它的可靠性来自幂等、权威查询和可审计兜底的组合。演练会让回调端连续不可用、响应丢失和签名配置错误分别发生,确认瞬时故障走退避、确定性错误停止盲重试、遗漏最终被账单发现。
演练分别覆盖接收端连续不可用、响应丢失和签名配置错误,验证瞬时故障退避、确定性错误停止盲重试,最终遗漏一定能被主动查询或账单对账发现。
追问 1:回调验签失败要返回成功避免重试吗?
直接回答:不能伪造业务成功,应记录安全事件并按协议返回,通知方走人工或配置修复。
追问 2:主动查单可以替代回调吗?
直接回答:可以作为兜底但成本更高,通常两者结合,并用对账覆盖最终长尾。
追问 3:重试五次为何仍需对账?
直接回答:故障往往相关而非独立,五次可能都落在同一故障窗口,对账提供全量差异发现。
- 问题(综合题):如何设计库存、支付、账务和履约的分层对账体系?
口述答案:我把对账看作一致性方案的一部分,不是事故后的临时脚本。第一层是实时状态对照:订单待支付、库存冻结、支付处理中和履约待创建都有最大年龄,超过阈值触发按业务号查单或重放。第二层是日内增量对账,用订单行号、预占号、支付单号、账务事件和运单客户单号关联各领域流水,找出状态缺失、数量不守恒、金额不一致和重复动作。第三层是外部账单对账,把支付渠道账单、承运商运单及费用清单与本地权威记录比较。
差异先分类再修复。漏发布且业务事实已提交,重发原 Outbox(发件箱)事件;消息已到但漏消费,用原事件幂等重放;渠道成功本地未知,按查单结果推进并补账务事件;本地记成功而渠道失败,冻结履约并冲正。库存校验可售、冻结、已扣减和释放守恒,账务校验唯一分录、方向、金额和币种。任何人工调整都生成修复号、原因、前置状态、审批与结果,不直接改余额。
对账任务本身也要幂等、可分片、可续跑。Runner(执行器)用批次号、分片和版本抢占,重复执行返回同一差异结果;大表按业务日期与主键游标扫描,避免全表锁。指标包括差异率、自动收敛率、最老差异、人工量和修复后二次差异。只有技术状态、资金差额、库存数量与外部清单同时归零,才能宣布闭环。每次规则升级先用历史批次影子运行,比较新增与消失差异,避免错误规则把大量正常订单标成异常并触发危险自动修复。
对账规则升级先对历史批次影子运行,比较新增、消失和改变分类的差异,并抽样人工复核;没有通过门禁的规则只能报告,不能触发自动修复。
追问 1:为什么实时查单不能替代日终对账?
直接回答:实时查单按已知未知单触发,无法发现本地根本没有记录或通知、任务均遗漏的差异。
追问 2:对账差异能否全部自动修复?
直接回答:只有事实明确、动作幂等且风险可控的类型自动修复,金额冲突和不可逆履约需审批。
追问 3:对账批次重复运行怎么办?
直接回答:批次与差异键唯一,修复动作检查前置状态,重复运行只读取原结果。
- 问题(综合题):支付接口超时后,如何处理成功、失败和未知三种状态?
口述答案:支付请求超时只说明调用方在截止时间内没有看到响应,可能请求未到渠道、渠道已扣款但响应丢失、渠道仍在处理或本地响应线程异常。因此支付状态必须有处理中或 Unknown(未知),不能把超时映射为失败,也不能自动再生成新支付号。商户订单号和支付意图号保持稳定,保存请求摘要、渠道、金额、发送时间和每次查询证据。
处置先查本地支付请求与状态流水,再按原业务号调用渠道查单。渠道明确成功时,使用状态条件和版本幂等推进支付单,在同一事务创建唯一账务事件;明确失败时记录原因,按订单规则释放库存或允许重新支付;仍未知时退避查询,设置最大自动年龄并暂停高风险发货。若协议允许重放,也复用原业务号,避免渠道创建第二笔交易。
超过自动预算后进入差错队列,通过渠道账单对账。发现重复扣款时不删除支付记录,而是发起独立退款单;发现渠道成功本地无记录时核对订单归属后补记或退款。用户界面展示支付确认中和查询入口,不承诺成功或失败。故障测试覆盖发送前断网、渠道提交后断网、重复回调、查单超时和账务重复消费,验收一笔支付意图只有一个有效资金结果。查单任务按渠道限额控制并发,并优先处理高金额、长年龄订单,防止故障恢复时所有未知单同时冲击渠道和本地数据库。
查单任务按渠道配额控制并发,优先处理高金额和长年龄订单;用户反复刷新只合并到原任务,渠道原始响应摘要和查询时间都进入审计,便于处理状态反转。
追问 1:查单也超时怎么办?
直接回答:继续保持未知,按预算退避并最终转账单对账或人工,不能用第二次超时猜结论。
追问 2:可以立即退款降低风险吗?
直接回答:未确认扣款时退款也可能无单或重复,应先取得渠道事实,再用独立幂等退款号执行。
追问 3:库存冻结是否一直保留?
直接回答:按订单价值和支付确认服务目标设置延长上限,长期未知转人工,不能无限占用。
- 问题(综合题):跨境物流下单超时后怎样避免重复运单并最终履约?
口述答案:物流创建是外部副作用,超时不能判失败。下单前本地生成稳定客户单号,在履约事务中记录待创建、承运商、包裹摘要和请求版本;事务外调用承运商。响应成功后条件更新承运商运单号,超时则进入未知,不换新客户单号盲重试。优先按客户单号查单,查到已创建就补写本地结果,明确无单且协议允许才复用原号重放,仍未知则退避查询并限制并发。
承运商若不支持幂等创建或客户单号查单,选型风险显著上升。自动重试次数要更保守,保留完整请求、网络和渠道响应,通过批量运单清单、账单或人工客服核验。消息重复消费时先查本地履约状态与外部结果,不能每次都调用创建。费用、包裹和地址摘要不同却复用同号视为冲突,禁止覆盖。
已创建但后续订单取消时,要按物流阶段补偿:未揽收请求取消,已揽收请求拦截,无法拦截转退货;库存只有在货物真实回库并复核后才恢复可售。监控未知运单年龄、重复运单、查单成功率、取消与拦截结果、账单差异。演练响应丢失、查单也超时、重复消息和已揽收取消,证明不会重复下单且每个包裹有可追踪终局。跨境场景还要保存渠道时区、服务等级、面单版本和费用币种,避免同一客户单在重试时因参数变化被承运商识别为新业务。
若账单出现同一客户单对应两个运单,系统立即停止面单打印与发货,核对请求时间、包裹摘要和费用,取消重复运单并登记承运商幂等缺陷,防止继续扩散;核验结果同步履约审计台账。
追问 1:承运商返回系统错误可以立即重试吗?
直接回答:仍要看错误是否明确未受理;无法证明时先查单,重试复用原客户单号。
追问 2:本地状态写失败但拿到运单号怎么办?
直接回答:按客户单号再次查询并幂等补写,不能重新创建;原响应和请求摘要进入审计。
追问 3:取消运单成功就能释放库存吗?
直接回答:还要看货物是否已出库、拣货或回库,物流状态不等于库存货权状态。
- 问题(综合题):WMS(仓储管理系统)如何结合事务与消息实现库存防超卖?
口述答案:正确性底线放在库存数据库,而不是 Redis(远程字典服务)锁或消息顺序。库存行使用“可售量大于等于请求量”的原子条件更新,受影响行数决定成功;同一订单行、仓库、商品和预占动作建立唯一流水,余额变化、预占流水与 Outbox(发件箱)事件同本地事务提交。重复下单命中唯一键后返回原预占号和结果,参数冲突则报警,不能再次扣减。
跨服务流程可以由订单发送预占命令,库存用 Inbox(收件箱)去重和状态机处理,再发布预占成功或失败事件。消息至少一次,所以扣减、确认和释放都使用原预占号与前置状态;迟到释放不能覆盖已扣减,重复释放不能增加两次可售。热点商品可用分布式锁、分片或排队降低数据库冲突,但锁过期后旧持有者仍必须被版本和条件更新拒绝,锁故障只影响性能不影响正确性。
支付成功后库存从冻结转已扣减,支付失败或订单取消则释放;支付未知时不直接释放,先查渠道并设置冻结年龄。已出库取消要等回库复核,不能凭订单状态增加可售。对账检查期初、入库、出库、冻结、释放、调整与期末守恒,人工调整生成审批流水。压测并发不同订单和同键重试,注入数据库提交后断网、消息重复、锁过期和释放失败,验收库存不负且流水可解释。热点降级时可以拒绝或排队新预占,但绝不返回虚假成功;恢复放量逐仓逐商品进行,并观察数据库冲突和冻结年龄。
热点降级可以明确拒绝或返回排队凭证,但绝不伪造预占成功;恢复时按仓库和商品逐级放量,同时观察条件更新冲突、冻结年龄与库存守恒差异。
追问 1:Redis(远程字典服务)先扣库存再异步落库可以吗?
直接回答:除非有完整持久化、回放和对账设计,否则缓存不是权威,故障会丢扣减或重复回补。
追问 2:唯一流水能单独防超卖吗?
直接回答:只能防同一意图重复,还需带余量条件的原子更新保护不同订单并发。
追问 3:为什么支付未知不能立即释放?
直接回答:渠道可能已成功,释放后库存会被他人占用,最终确认时形成无货可履约。
- 问题(综合题):支付成功后如何保证账务只入一次,并处理退款与冲正?
口述答案:支付渠道成功是外部资金事实,本地支付服务先以渠道流水和商户支付单唯一约束推进支付状态,在同一本地事务写支付流水和 Outbox(发件箱)账务事件。账务消费者使用支付单号、账务方向和事件版本形成 Inbox(收件箱)唯一键,插入借贷分录与消费结果后再确认消息。消息重复、确认丢失或历史回放都返回首次结果,不产生第二笔入账。
账务不是把支付表金额复制一遍。消费时校验商户、币种、金额、科目和方向,分录满足借贷平衡;参数不同却复用事件键进入冲突队列。支付服务超时未知时不提前入账,查单明确成功后再发布。若账务消费长期失败,支付事实不回滚,事件有限重试后隔离,暂停依赖账务完成的结算,并通过状态年龄与对账补做。
退款和冲正是新的资金交易,各有独立业务号、渠道号、金额和分录,不能删除原支付记录。退款超时同样查单,部分退款按累计已退金额做条件约束,防止超过原支付。日内核对支付状态与账务事件,日终对渠道账单、支付流水和总账,差异修复使用审批补记或反向分录。故障测试覆盖重复回调、事件重复、账务提交后崩溃和退款响应丢失,验收每个方向只有合法唯一分录。结算前设置账务完整性门禁,长时间未入账或金额冲突的支付暂停结算并升级财务核验,避免技术积压演变为真实资损。
结算前设置完整性门禁,长时间未入账或金额冲突的支付暂停结算;多币种交易固定汇率来源与记账时点,退款产生的汇兑差额也用独立分录表达。
追问 1:支付成功但账务未入能否回滚支付?
直接回答:不能自动回滚渠道支付,应修复账务消费;业务需要撤销时发起独立退款。
追问 2:为什么退款不能更新原支付为失败?
直接回答:原支付确实发生,退款是后续反向资金事实,覆盖会破坏审计和对账。
追问 3:账务去重只看渠道流水够吗?
直接回答:不够,还要区分支付、退款、冲正方向与版本,并校验金额和币种。
- 问题(综合题):订单取消涉及库存、支付和履约时,怎样设计补偿顺序?
口述答案:取消不是把三张表改回原值,而是一个有条件的长流程。订单先用幂等取消号进入取消处理中,流程管理器查询履约阶段。未分配或未拣货时可以先取消履约任务;已拣货要生成回库任务并复核数量;已出库或已揽收则请求物流拦截,无法拦截转退货。只有确认货权仍在仓内或完成回库后,库存才按原预占号释放或恢复可售,避免页面取消成功却实际货物已离仓。
支付未发生可直接关闭支付意图;支付处理中先查渠道;已支付则创建独立退款单,退款响应超时保持未知并查单。退款失败不把订单恢复为正常履约,而是进入待退款和人工服务。每一步使用自己的权威状态、前置条件、版本和流水,补偿命令重复返回首次结果;流程管理器记录已完成步骤和下一动作,不直接修改参与者数据库。
顺序根据风险动态决定:高价值已出库订单先拦截物流再退款,避免钱退了货仍送达;未出库可并行取消履约与发起退款,但库存释放仍等待仓内事实。监控取消年龄、待回库、待释放、待退款和拦截失败,超过服务目标升级人工。演练迟到支付成功、重复取消、物流超时和回库数量不符,最终对账订单承诺、货权与资金一致。客户侧展示每个子进度及预计时间,客服看到同一证据链,避免因页面只显示“取消成功”而重复发起退款或库存调整。
客户侧展示取消、回库、退款和拦截的子进度,客服读取同一证据链;长期停留按订单价值和货物阶段升级,恢复后只续跑当前失败步骤,不重放已成功副作用。
追问 1:订单取消后可以立即显示已完成吗?
直接回答:跨域补偿未完成时应显示取消处理中,并展示退款或退货进度,不能伪造终局。
追问 2:库存释放和退款能否总是并行?
直接回答:取决于货权与风险;已出库场景要先确认拦截或回库,不能机械并行。
追问 3:补偿某一步失败是否重跑全部流程?
直接回答:不重跑已成功副作用,按持久化步骤从失败点幂等续跑,并核对前置状态。
- 问题(综合题):MQ(消息队列)在微服务一致性中解决什么,又引入什么?
口述答案:MQ(消息队列)把同步调用变为持久化异步传递,能解耦订单与库存、账务、通知等下游,削峰并允许故障后重放。配合 Outbox(发件箱)或事务消息,它把本地提交后的业务事件可靠传播出去;配合 Inbox(收件箱)、状态机和唯一约束,下游可以在至少一次投递下最终收敛。它适合允许短暂中间态的账务事件、履约创建、报表和通知,不代表所有业务都应异步。
引入的问题包括重复、乱序、积压、发送或确认不确定、契约演进和死信治理。生产端不能“先发普通消息再提交数据库”,否则消息成功而业务失败;也不能业务提交后只发一次,否则进程崩溃会漏消息。消费端在业务提交后确认,重复用业务键吸收;同一聚合需要顺序时按聚合标识分区并带状态版本,不能依赖全局顺序。积压监控看最老消息年龄而非只看数量。
MQ(消息队列)更不能自动回滚消费者或外部副作用。账务消费失败时上游支付不会穿越时间回滚,应重试、隔离和对账;物流消费超时要按客户单号查单。设计时给出事件所有者、模式版本、保留与回放周期、重试预算、隔离队列和人工出口。故障演练覆盖发送确认丢失、重复消费、乱序、长时间积压和旧消费者回放,证明不漏业务事实且重复不重复生效。发布变更时先扩展事件契约并升级消费者,再切生产者,历史回放也使用对应版本解析器,避免恢复动作击穿新系统。
事件契约采用先扩展、后迁移、再收缩,登记所有者、版本与弃用日期;历史回放使用对应版本解析器,避免一次恢复把旧消息变成新的批量业务错误。
追问 1:消息成功发送等于业务成功吗?
直接回答:不等于,只证明消息代理接收;业务本地事实和消费者结果需分别确认。
追问 2:死信队列是否就是最终兜底?
直接回答:只是隔离载体,还需告警、业务查询、修复工具、责任人与服务目标。
追问 3:消息顺序能替代状态版本吗?
直接回答:不能,重试、分区迁移和多来源会破坏观察顺序,参与者仍需拒绝非法状态迁移。
- 问题(综合题):为什么任何分布式事务框架都不能自动回滚外部支付和物流?
口述答案:自动回滚要求参与资源受协议控制:数据库分支能准备、持久化恢复信息,并按协调决定提交或回滚;Seata AT(自动事务模式)还要求写入经过数据源代理并有 undo log(回滚日志)。外部支付渠道和承运商不使用本系统的数据源,也没有把内部事务锁、准备日志和回滚能力交给本系统。把调用写在全局事务注解方法里,只改变本地代码组织,不会改变对方协议。
支付扣款成功后,本地数据库回滚无法把钱自动退回;退款是有独立单号、时限、费用和失败可能的新交易。物流运单已创建甚至已揽收后,也不能通过删除本地行让包裹回仓,只能取消、拦截、退货或人工处理。短信、邮件和设备控制一旦被人看到或设备执行,更不存在时间倒流。框架如果报告本地全局事务回滚成功,只能说明受管理分支完成,不证明外部世界恢复。
正确设计是调用前持久化业务意图和稳定业务号,超时设为未知并主动查单;明确成功后幂等推进,明确失败后按规则重试,长期未知进入对账。撤销使用语义补偿并保留原事实,已不可逆动作转前向修复。项目评审时逐个列参与者是否受控、是否可查询、是否幂等、补偿时限和人工出口,禁止用一个“全局事务”框遮住外部失败窗口。验收报告必须分别列框架内分支与框架外副作用,展示外部响应丢失时系统仍能靠原业务号收敛,而不是只截图全局事务控制台。
验收报告将框架内数据库分支和框架外副作用分栏展示,分别记录恢复时间、退款或拦截失败率与最长年龄,不能只用全局事务控制台的绿色状态证明业务恢复。
追问 1:渠道支持退款是否就能加入全局回滚?
直接回答:通常不能,退款是独立异步交易,可能失败、部分成功或超过时限,不等于原子回滚。
追问 2:TCC(Try Confirm Cancel,尝试确认取消)能包装任意外部接口吗?
直接回答:只有对方真正提供可预留、可幂等确认和取消的资源语义才成立,简单方法名包装无效。
追问 3:如何向面试官证明理解边界?
直接回答:明确说出超时未知、稳定业务号、查单、退款或拦截、对账和人工闭环,并给失败演练。
- 问题(综合题):如何演练补偿失败,而不是只测试正常回滚?
口述答案:我先画出每个可失败点和权威证据。TCC(Try Confirm Cancel,尝试确认取消)覆盖 Try(尝试)提交后响应丢失、Cancel(取消)先到、重复 Confirm(确认)、全局提交后确认长期失败;Saga(长事务模式)覆盖正向第三步失败、前两步补偿之一失败、补偿提交后响应丢失和不可逆动作已发生;消息链路覆盖业务提交后发布器崩溃、发送确认丢失、消费提交后确认丢失和隔离队列积压。每个故障都用稳定业务号复现,不能只拔一次网线看错误率。
演练前定义不变量和预期终局:库存可售、冻结、扣减与释放守恒,同一支付意图只有一个有效渠道结果,账务每个方向唯一,运单不重复。注入故障后先观察系统是否进入已设计的中间态,重试是否退避、是否复用原键、是否有最老年龄告警;再恢复依赖,验证任务可由其他实例接管,重复调用返回首次结果。对于永久失败,确认会自动进入差错队列并冻结会扩大损失的后续动作。
演练结束不以接口恢复为标准,而是运行订单、库存、支付、账务和物流对账,检查差额归零、未知态收敛、人工工单字段完整。记录故障时间线、版本、重试量、恢复时间和人工步骤,把超过目标的窗口转成容量、状态机或流程改造。生产前还要演练修复工具权限与审批,避免真正事故时只能直接改表。每个故障点保留可重复脚本和预期证据清单,版本升级后重新执行,防止曾经通过的恢复能力因依赖变化悄悄失效。
每个注入点保留可重复脚本、预期状态和对账清单,依赖或框架升级后重新执行;季度演练统计恢复目标达成率,失败项必须形成明确容量或状态机改造。
追问 1:为什么提交后丢响应是必测点?
直接回答:它制造“操作已生效但调用方未知”,最容易诱发重复扣减、重复下单和错误补偿。
追问 2:演练可以只检查技术事务状态吗?
直接回答:不能,技术完成不代表资金、货权和外部清单一致,必须业务对账。
追问 3:永久失败怎么在测试环境模拟?
直接回答:注入确定性业务拒绝或持续数据冲突,验证有限重试后进入人工而非无限循环。
- 问题(综合题):线上发现订单、库存和支付状态不一致,完整排查路径是什么?
口述答案:先止血而不是立即修数。我按订单号、支付单号和预占号暂停该业务键自动推进,关闭异常无限重试,必要时隔离渠道,避免重复扣库存或发货。然后建立时间线:查订单状态与版本、库存余额和预占流水、支付请求与回调、账务事件和分录、Outbox(发件箱)发布、MQ(消息队列)投递、Inbox(收件箱)消费以及外部支付、物流查单。Trace(链路追踪)和日志用于定位路径,但权威结论来自业务流水与外部结果。
按差异类型处理。业务事务已提交而 Outbox(发件箱)未发布,重发原事件;消息已投递但消费缺失,检查隔离队列并幂等重放;库存预占成功订单未推进,补订单状态;支付渠道成功本地未知,按查单结果推进并生成唯一账务事件;本地显示成功渠道失败,冻结履约并冲正。若参数、金额或数量冲突,停止自动修复,保留现场进入审批。
修复动作使用独立修复号、前置状态和结果流水,不覆盖原记录。完成后重跑数量守恒、支付与账务、运单与费用对账,观察一段业务窗口确认没有迟到消息再次逆转。最后定位根因是事务边界、事件漏发、幂等键、超时误判、契约兼容还是人工脚本,并补测试、告警和操作手册。只把三张表改成同一个状态不叫修复。事故期间所有查询和操作使用统一时间基准并记录数据来源,跨区域或跨渠道时校正时区,避免错误拼接时间线得出相反结论。
事故证据统一使用可比时间基准并记录来源,导出快照带采集时间和校验摘要;止血开关恢复前抽样验证新老交易,避免迟到消息或错误修复造成第二次状态反转。
追问 1:为什么先暂停单业务键而非停全站?
直接回答:优先缩小影响并保留正常交易;若差异是系统性且快速扩散,再升级全局止血。
追问 2:Trace(链路追踪)没有记录说明调用没发生吗?
直接回答:不能,采样、导出或上下文传播可能缺失,需结合访问日志、流水和外部查单。
追问 3:迟到消息修复后到达怎么办?
直接回答:状态版本和前置条件应拒绝非法逆向迁移,并记录冲突供复盘。
- 问题(综合题):高峰下单链路如何在一致性与吞吐之间做量化选择?
口述答案:我先拆同步必须项和可延后项。用户提交时必须确定订单意图唯一、库存是否成功预占,这两个本地不变量用唯一键和条件更新完成;支付创建、账务、履约和通知按业务时限异步推进。若把所有数据库放进 2PC(两阶段提交),热点库存行的锁从几十毫秒延长到跨服务数百毫秒,单行吞吐会按锁时间下降,协调故障还占连接;因此高峰核心通常不追求把所有步骤塞进同步强事务。
容量模型包含入口每秒请求、热点比例、数据库冲突、Outbox(发件箱)新增与发布能力、MQ(消息队列)积压年龄和消费者吞吐。例如峰值每秒
5000单、库存成功率60%,每秒约产生3000个成功事件;发布与消费能力至少留故障和回放余量,不能刚好等于峰值。重试使用统一业务键、退避和总预算,避免调用超时后把冲突翻倍。热点可分仓、分片或排队,但数据库不变量不变。用户可见状态区分已受理、库存确认中、支付确认中和履约中,每个状态有服务目标与降级。高峰时通知、报表和低优先级履约延迟,库存与支付查单保留资源;超过积压年龄触发限流和人工。压测不仅看平均响应,还注入发布器停机、消费者变慢和热点倾斜,检查峰值期间不超卖、无重复资金、积压在目标时间清空。容量报告同时给出失败后追赶时间,若恢复吞吐只略高于实时流量,就必须预留临时扩容或入口削峰方案。
容量报告还要给出故障后的积压追赶时间;若恢复吞吐只略高于实时流量,就预留临时扩容、分级消费或入口削峰方案,避免全速追赶再次压垮库存数据库。
追问 1:异步化是否一定提高吞吐?
直接回答:只移动等待位置,还需队列、消费者和数据库有容量,否则只是把超时变成积压。
追问 2:库存预占也能完全异步吗?
直接回答:可以返回排队受理,但产品必须接受稍后失败;若下单即承诺库存,就需同步获得预占事实。
追问 3:为什么看积压年龄而非数量?
直接回答:同样数量在不同流量下影响不同,最老年龄直接对应业务承诺是否超时。
- 问题(综合题):消息乱序时,订单、库存和支付状态机如何保持正确?
口述答案:我不假设 MQ(消息队列)提供全局顺序,而是让每个聚合事件携带聚合标识、业务动作、状态版本、发生时间和稳定事件标识。同一订单或预占尽量按聚合标识路由到同一分区,减少乱序概率,但消费者仍以当前状态和版本决定能否执行。库存已从冻结转已扣减后,迟到的释放事件不能把它改回可售;支付已退款后,迟到的支付成功事件不能覆盖退款终局,而要核对是否存在合法的原支付事实。
状态迁移使用条件更新,例如只有当前版本为
5且状态为已预留,版本6的确认才能执行。收到未来版本时可短暂缓冲或查询权威源,超过等待窗口进入冲突队列;收到旧版本时,如果对应动作已完成则返回首次结果,若参数冲突则告警。时间戳不能单独排序,因为服务时钟会漂移,重放事件的发送时间也晚于业务发生时间。多来源事件更要定义所有权。支付服务决定支付状态,库存服务不能因订单关闭直接覆盖库存流水,只能接收释放命令并按货权判断。修复脚本也产生新版本与事件,不能绕过状态机。测试随机交换支付成功、订单取消、库存释放和退款事件顺序,重复每条消息并跨分区回放,验收非法逆向迁移被拒绝、合法缺口可补齐且对账最终一致。冲突事件进入独立审计主题并保留当前快照、事件版本和决策原因,避免静默丢弃掩盖生产者契约错误。
冲突事件进入独立审计队列,保存当前快照、事件版本与拒绝原因;同一聚合连续冲突时暂停自动推进,查询权威状态并补齐缺口后才从明确版本恢复。
追问 1:同一分区能彻底消除乱序吗?
直接回答:不能,生产重试、多生产者、回放和消费并发仍可能改变观察顺序。
追问 2:未来版本一直等不到怎么办?
直接回答:查询权威状态或缺失事件,超过窗口进入差错队列,不能无限占内存等待。
追问 3:业务发生时间有什么用?
直接回答:用于审计和冲突分析,但最终合法性由权威状态、版本和业务规则决定。
- 问题(综合题):如何建设一个不会“越修越错”的人工一致性修复平台?
口述答案:平台首先是证据聚合器,不是任意改表工具。输入稳定业务号后,展示订单、库存、支付、账务、履约的权威状态与版本,关联全局和分支号、Outbox(发件箱)、MQ(消息队列)、Inbox(收件箱)、外部查单和历史人工动作,形成不可篡改时间线。系统自动分类常见差异并给出可执行候选,但金额、货权或参数冲突不自动下结论。
每个修复动作有独立修复号、适用前置状态、预期数量或金额、幂等键、风险等级和审批规则。补发只能重用原事件标识,补消费先检查首次结果,库存调整生成入出库或盘点流水,账务修复使用补记或冲正分录,支付和物流通过原业务号查单或新补偿单处理。执行前再次比较版本,状态已变化则拒绝,防止审核期间自动流程已经收敛。
权限采用最小化和双人复核,高风险动作分离申请、审批与执行;所有查询、导出和操作记录操作者、理由、前后快照和结果。批量修复先影子计算差异和影响量,小批执行后重跑对账,再逐步扩大;随时可停止但不靠删除流水回退。平台指标看待处理年龄、自动与人工收敛率、失败重试、二次差异和同类根因,推动代码修复而不是长期依赖人工。修复规则像生产代码一样版本化、评审和回归,旧批次能还原当时规则,避免同一差异因规则变化得到不可解释的不同处理。
修复规则像生产代码一样版本化、评审和回归,平台默认只读,执行权限按时申请并自动过期;敏感数据脱敏,批量动作先影子计算影响量再小批放行。
追问 1:为什么不能提供通用 SQL(结构化查询语言)输入框?
直接回答:它绕过状态机、审计和影响评估,容易破坏数量与资金不变量,权限边界也无法控制。
追问 2:人工动作需要幂等吗?
直接回答:需要,页面超时和重复审批同样会发生,修复号和业务前置状态必须防重复。
追问 3:平台何时算建设成功?
直接回答:差异可快速取证、受控修复且二次差异下降,同时同类根因被持续消除,而非按钮越多越好。
- 问题(综合题):IoT(物联网)设备控制与报警通知如何应用消息一致性,哪些动作不可补偿?
口述答案:设备控制是外部现实副作用,指令超时不代表设备未执行。控制服务先在本地事务记录指令号、设备、期望动作、参数摘要、状态和 Outbox(发件箱)事件,再异步下发;设备或网关回执按指令号幂等处理。超时进入未知,优先查询设备影子、网关记录或设备状态,不能换新指令号无脑重发开门、停机等高风险动作。设备不支持查询时降低自动重试并升级人工现场确认。
报警链路采用至少一次消息,Inbox(收件箱)以设备、规则、事件时间窗和告警版本去重,同一故障可聚合而不丢原始严重事件。短信、电话和工单是外部通知,发送成功响应丢失会重复,因此使用通知号、有限退避和渠道查询;用户已收到的短信无法回滚,误报只能发送更正或恢复通知。严重告警保留独立容量,低级重复告警可静默,但静默决策有流水。
对账包括设备原始事件、规则命中、聚合告警、通知尝试和工单状态,监控未知指令年龄、重复执行、严重告警送达和静默量。故障演练让设备执行后断开、网关重复回执、消息乱序和短信渠道超时,验收高风险指令不会重复、告警可追溯且人工能识别未知。框架只能保证本地记录和消息传播,不能让设备物理动作自动撤销。高风险指令还要设置操作者确认、设备能力版本和有效时间,迟到指令超过窗口直接拒绝,防止网络恢复后执行过时控制。
高风险指令还携带设备能力版本和有效时间,迟到指令超过窗口直接拒绝;固件升级期间按能力路由,恢复通知关联原告警,值班人员能看到完整处置时间线。
追问 1:设备指令可以设计成 TCC(Try Confirm Cancel,尝试确认取消)吗?
直接回答:只有设备协议真正支持预留、确认和取消且可查询时才可能,普通控制命令不能靠接口命名包装。
追问 2:报警去重会不会丢事故信息?
直接回答:聚合展示不等于删除原始事件,原始严重记录要可靠保存并可回放审计。
追问 3:误发短信如何补偿?
直接回答:无法收回已读信息,只能发更正、标记误报、控制后续升级并复盘规则。
- 问题(综合题):Runner(执行器)调度补偿、发布和对账任务时,如何避免重复执行扩大事故?
口述答案:Runner(执行器)只负责可靠调度,不拥有业务终局。每个任务用任务类型、业务号或批次、分片和计划版本形成唯一键,持久化待执行、执行中、成功、失败与下次时间。实例通过带版本的条件抢占获得有期限所有权,执行超过租约时可能被其他实例接管,因此业务动作必须幂等;仅靠分布式锁无法防止旧持有者恢复后继续写,关键更新还要比较任务版本或 Fencing Token(栅栏令牌)。
Outbox(发件箱)发布任务重发同一事件标识,对账任务按游标和批次续跑,补偿任务按原动作号查询首次结果。任务超时不立即标失败重做,而是查业务权威状态:支付退款查渠道,物流取消查运单,库存释放查原预占流水。确定性参数错误进入隔离队列,瞬时故障有限退避并带抖动;超过年龄升级人工,禁止无限重试占满线程和外部配额。
调度指标包括待执行数量、最老年龄、租约失效接管、重复命中、每类成功率和外部限流。发布新版本时先停领取、排空或安全交接在途任务,旧版本事件仍要兼容。演练实例执行中暂停超过租约、数据库提交后断网、两个实例并发接管和批次重跑,验证业务副作用一次、任务状态可恢复、旧令牌被拒绝,且调度故障不会改变资金与库存事实。恢复后实时任务与历史积压分配独立预算,优先处理临近业务时限的工作,避免全速追赶再次压垮下游。
恢复后实时任务与历史积压使用独立预算,优先处理临近业务时限的工作;任务载荷升级保留旧解析器,无法识别的历史任务进入隔离,绝不能标记成功。
追问 1:分布式锁自动续期能解决重复执行吗?
直接回答:不能彻底解决,暂停和网络分区会失租,最终资源仍需幂等键、状态条件或栅栏令牌。
追问 2:任务成功以函数返回为准吗?
直接回答:不够,外部响应可能丢失,必须查询业务事实后再记录成功。
追问 3:批量对账怎样安全续跑?
直接回答:持久化批次、分片与游标,差异键唯一,重复扫描不重复修复。
- 问题(综合题):请用一个完整项目话术串讲库存、支付、账务、履约和消息一致性。
口述答案:在 WMS(仓储管理系统)交易链路中,我先按领域划分权威状态:订单管理客户承诺,库存管理可售、冻结和扣减,支付管理渠道事实,账务管理唯一分录,履约管理仓内与物流进度。下单时库存数据库用余量条件更新、预占唯一流水和 Outbox(发件箱)同本地事务提交,守住不超卖;订单通过事件获得预占结果,不直接写库存表。消息至少一次,消费端用 Inbox(收件箱)、状态版本和前置条件吸收重复与乱序。
支付调用使用稳定商户单号。响应超时进入 Unknown(未知),先查渠道而不是新建请求;明确成功后本地事务推进支付并发布唯一账务事件,账务消费者只入一次。履约在库存和支付事实满足后创建,物流下单以客户单号幂等,超时查单。订单取消是 Saga(长事务模式):未拣货取消任务并释放库存,已出库先拦截或退货,已支付发起独立退款;外部支付和物流从不宣称被 Seata(分布式事务框架)自动回滚。
技术选型上,同库不变量优先本地事务;可明确预留且必须同步占用的核心资源才评估 TCC(Try Confirm Cancel,尝试确认取消);短内部数据库事务按锁成本评估 Seata AT(自动事务模式)或 XA(扩展架构模式);长流程采用显式补偿;异步传播采用 Outbox(发件箱)、事务消息、CDC(变更数据捕获)和 Inbox(收件箱)。最后以最老中间态、重复率、补偿失败、库存守恒、支付账务差额和人工工单监控,故障演练提交后断网、重复消息和外部未知,用对账证明最终收敛。
追问 1:这套设计最核心的原则是什么?
直接回答:每个领域先有可查询的本地权威事实,跨域传播允许重复,未知结果查证,最终用对账收口。
追问 2:什么时候会推翻现有方案?
直接回答:锁等待、冻结年龄、人工差错或恢复时间持续超目标,说明事务边界或模式需要重划。
追问 3:如何证明不是只会背概念?
直接回答:给出业务键、状态迁移、失败注入点、容量数字、监控指标和一笔差异的完整修复链。
官方资料与复习清单
本文于 2026-07-14 核对以下一手资料;生产使用时仍需固定实际版本并重新验证支持矩阵:
- Seata AT(自动事务模式)官方文档
- Seata(分布式事务框架) TCC(Try Confirm Cancel,尝试确认取消)模式官方文档
- Seata(分布式事务框架) Saga(长事务模式)官方文档
- Seata(分布式事务框架) XA(扩展架构模式)官方文档
- Seata(分布式事务框架)数据源支持矩阵
- RocketMQ(分布式消息队列)事务消息官方文档
- Debezium(变更数据捕获平台)Outbox Event(发件箱事件) Router(路由器)官方文档
- TX-LCN(事务协调框架)官方仓库
复习时必须能脱稿回答:本地事务如何表达业务不变量;2PC(两阶段提交)为何阻塞及如何恢复;TCC(Try Confirm Cancel,尝试确认取消)如何处理空回滚与悬挂;Saga(长事务模式)为何不是数据库回滚;Seata(分布式事务框架)四模式各自的资源前提;TX-LCN(事务协调框架)为什么只作存量评估;Outbox(发件箱)与事务消息如何封闭生产端双写;Inbox(收件箱)如何处理重复;CDC(变更数据捕获)如何恢复位点;最大努力通知为何必须搭配查单与对账;外部支付与物流为什么不能自动回滚;人工修复如何保留新流水和审批证据。
