MQ(消息队列)项目案例:异步导出、库存、支付、物流、Runner(执行器)与 IoT(物联网)
本篇只讨论业务方案。统一判断顺序是:先确认权威数据源,再划分同步正确性与异步吞吐边界,最后用幂等、状态机、补偿、指标和降级保证最终收敛。任何 MQ(消息队列)产品都不能替代业务事实与对账。
1. 六类项目的统一答题框架
1.1 从业务事实到异步闭环
六类案例都使用同一条设计链:权威数据源负责裁决事实,同步事务守住不可违反的不变量,MQ(消息队列)只传递已经发生或明确请求发生的业务意图;消费者用稳定幂等键和状态机吸收重复、乱序与并发;失败进入有期限的重试、补偿或人工队列;最终以业务对账而不是消息积压归零验收。
flowchart LR
A[业务请求] --> B{同步裁决}
B -->|拒绝| C[返回明确失败]
B -->|提交| D[权威数据源与发件记录]
D --> E[消息发布]
E --> F[幂等消费]
F --> G{状态机允许}
G -->|允许| H[业务副作用]
G -->|拒绝| I[重复或冲突分类]
H --> J[回执与对账]
I --> J
J -->|差异| K[补偿或人工介入]| 案例 | 权威数据源 | 同步正确性底线 | 消息承担的职责 | 最终验收不变量 |
|---|---|---|---|---|
| 异步导出 | 任务表、对象存储元数据 | 任务只创建一次 | 分片执行、合并、通知 | 成功任务有唯一可校验文件 |
| WMS(仓储管理系统)库存 | 库存余额与库存流水 | 条件扣减或预占原子提交 | 削峰、履约通知、投影同步 | 可用量、预占量与流水闭合 |
| 支付 | 渠道流水、支付单、账务流水 | 验签后状态条件更新 | 账务、订单、通知驱动 | 渠道金额与内部账务守恒 |
| 跨境物流 | 原始轨迹表与当前版本 | 原始事件不可丢、版本不倒退 | 归一化、分发、补拉 | 每运单最终状态单调收敛 |
| Runner(执行器) | 任务表与租约代次 | 当前围栏令牌才可提交 | 调度、执行、回执 | 一个代次只有一个有效结果 |
| IoT(物联网)报警 | 原始遥测与告警实例 | 严重状态变化不可采样 | 聚合、通知、工单 | 严重告警及时到达且状态可恢复 |
数据演绎 1:统一容量与收敛。 某业务峰值生产 12,000 条/秒,稳定消费 8,000 条/秒,峰值持续 300 秒,新增积压为 (12,000-8,000)×300=1,200,000 条。峰后生产降到 2,000 条/秒,消费仍为 8,000 条/秒,净消化 6,000 条/秒,理论 200 秒清空。这个数字只代表传输恢复;库存、支付等案例还必须逐业务键核对状态与流水。
热门面试题
问题(基础):为什么项目方案必须先说权威数据源? 考点:消息事实与业务事实的边界。 回答思路:先给结论,再举库存和支付反例。 详细答案:MQ(消息队列)保存的是传递记录,不天然知道库存是否充足、支付是否真实入账。库存要由数据库条件更新裁决,支付要由渠道流水、支付单和账务流水共同证明。权威数据源明确后,才能定义重复、冲突、补偿和对账,否则只能看到消息成功却无法判断业务是否正确。 进阶追问:消息已经持久化并确认,能否认为支付成功? 进阶回答:不能。它只证明消息达到代理节点承诺的边界;仍需验签、支付状态机、渠道查询和资金对账。
问题(原理):同步与异步边界如何划分? 考点:不变量与可延迟副作用。 回答思路:把用户承诺留在同步链,把可重试传播放到异步链。 详细答案:会直接决定请求能否成功的不变量必须同步裁决,例如库存是否够扣、回调签名是否合法、租约令牌是否仍有效。通知、投影、报表和可补拉的轨迹分发可以异步。判断标准不是“耗时长就异步”,而是失败后能否凭稳定事实重试、补偿并对账。 进阶追问:同步链越短是否一定越好? 进阶回答:不是。不能为了缩短延迟把库存裁决或支付验签移出同步事务,导致先承诺再发现事实不成立。
问题(项目):如何证明异步方案真正闭环? 考点:证据链与业务验收。 回答思路:从事件标识贯通发送、消费、结果和对账。 详细答案:每个案例都要能按事件标识查到权威记录、发件记录、消息位置、消费尝试、状态迁移和补偿结果;指标既看积压与失败,也看库存差异、资金差异、轨迹缺口、过期租约提交和严重告警时延。恢复后用不变量核对,消息归零但差异未清零不能结案。 进阶追问:最容易被遗漏的指标是什么? 进阶回答:最老未完成业务年龄和对账差异数;平均延迟、总积压可能掩盖少量长期卡死任务。
1.2 幂等键、状态机与失败窗口
幂等键标识一次稳定业务意图,状态机限制当前状态允许的动作,失败窗口描述两个不可原子步骤之间可能出现的未知结果。三者必须组合:唯一键只能挡住同一事件,挡不住不同事件造成的非法状态迁移;状态机能拒绝冲突,却不能识别同一外部副作用是否已经执行;失败窗口若不显式登记,就会被无限重试掩盖。
stateDiagram-v2
[*] --> 待处理
待处理 --> 处理中: 领取并生成围栏令牌
处理中 --> 成功: 权威结果提交
处理中 --> 待重试: 可恢复失败
处理中 --> 结果未知: 外部响应丢失
待重试 --> 处理中: 退避后重试
结果未知 --> 成功: 主动查询确认
结果未知 --> 待重试: 查询确认未执行
结果未知 --> 人工处理: 超过查证期限
待重试 --> 人工处理: 超过重试预算
成功 --> [*]
人工处理 --> [*]| 机制 | 回答的问题 | 示例键或条件 | 不能单独解决的问题 |
|---|---|---|---|
| 幂等键 | 是否是同一业务意图 | 支付单号+动作、任务号+分片号 | 不同事件间的状态冲突 |
| 唯一约束 | 并发时谁获得执行权 | 消费职责+事件号 | 外部调用结果未知 |
| 状态机 | 当前动作是否合法 | 已支付不能直接关闭 | 相同动作的重复调用 |
| 版本条件 | 来件是否更新 | 当前版本小于来件版本 | 缺失版本的自动恢复 |
| 围栏令牌 | 旧所有者能否提交 | 提交令牌等于当前租约代次 | 外部系统不校验令牌 |
| 对账 | 多源事实是否一致 | 金额、数量、版本闭合 | 实时阻止首次错误 |
数据演绎 2:失败窗口预算。 消费者业务提交耗时 80 毫秒,确认请求耗时 20 毫秒,实例在业务提交后到确认完成前宕机的暴露窗口约 20 毫秒。每天处理 5,000 万条,即使单次落入窗口概率只有千万分之一,也可能出现约 5 条重复。因此正确目标不是宣称“绝不重复”,而是让重复命中唯一约束并留下可计数证据。
热门面试题
问题(基础):幂等键应如何选择? 考点:稳定业务意图。 回答思路:说明键在所有重试中不变,并区分不同动作。 详细答案:支付用渠道交易号或支付单号加动作,退款用退款单号,库存用订单行和预占动作号,导出用任务号和分片号,轨迹用承运商、运单号和来源序号。键不能使用每次请求新生成的随机值;合法的确认与释放也不能共用同一个动作键。 进阶追问:为什么不能只用订单号做所有幂等? 进阶回答:一个订单可能有支付、取消、多次部分退款和多条库存行,粗粒度键会误杀合法动作。
问题(原理):唯一约束和状态机为何都需要? 考点:重复与冲突是两类问题。 回答思路:用支付成功与订单关闭乱序说明。 详细答案:同一支付成功事件重投时,唯一约束阻止重复记账;支付成功和订单关闭是两个不同事件,唯一约束都会放行,此时状态机必须拒绝“已支付直接关闭”。实现上去重、状态检查和业务更新应放在同一本地事务,避免去重凭证与结果分离。 进阶追问:条件更新影响行为零应直接确认吗? 进阶回答:不能一概而论。要读取当前状态,分类为已完成的重复、迟到事件或真正冲突,再分别确认、补查或隔离。
问题(排障):如何处理外部调用结果未知? 考点:查询优先与人工边界。 回答思路:保留原幂等号,先查后重试。 详细答案:调用超时只表示未知。先把本地状态置为结果未知,使用原请求号查询外部系统;明确未执行才有限重试,确认已执行则推进成功,超过查询和重试期限进入人工队列。资金类不能生成新请求号盲重试,导出上传也要先按对象键查文件是否已存在。 进阶追问:人工介入是否代表设计失败? 进阶回答:不是。对无法自动证明的少量高风险差异,受审计的人工裁决比自动猜测更可靠;设计目标是控制比例和处置时限。
2. 异步导出:分片、租约、文件与 OOM(内存溢出)
2.1 导出任务创建、分片与租约领取
异步导出的权威数据源是任务表、分片表和对象存储元数据。请求先固化查询条件、权限快照、数据版本和预估行数,返回任务号;MQ(消息队列)只携带任务号或分片号,不携带百万行数据。任务幂等键可取“租户、用户、规范化参数、数据版本”,分片幂等键取“任务号、分片号”。执行器领取时原子写入租约截止时间和递增围栏令牌,旧执行器即使晚到也不能覆盖新代次结果。
flowchart TD
A[提交导出条件] --> B[权限与参数校验]
B --> C[创建任务与分片]
C --> D[发布分片事件]
D --> E{原子领取租约}
E -->|失败| F[其他执行器持有]
E -->|成功| G[分页读取并流式写]
G --> H[续租与进度上报]
H --> I{分片完成}
I -->|否| G
I -->|是| J[记录分片校验值]
J --> K{全部分片完成}
K -->|是| L[触发合并]
K -->|否| M[等待其他分片]| 设计点 | 错误方案 | 正确方案 | 失败窗口与补偿 |
|---|---|---|---|
| 重复提交 | 每次都建新任务 | 参数摘要与数据版本做短期唯一 | 返回既有任务并记录命中 |
| 分片 | 按固定页码并发且数据变化 | 固定数据快照或稳定游标范围 | 缺片扫描后重发原分片号 |
| 领取 | 先查空闲再更新 | 条件更新租约并递增围栏令牌 | 租约过期可重领,旧令牌禁提交 |
| 进度 | 每行更新数据库 | 按批次或时间节流上报 | 心跳过期进入待重试 |
| 消息体 | 携带全部条件与数据 | 只传任务号、分片号、版本 | 从权威任务表恢复上下文 |
数据演绎 3:任务在途量。 每秒新增 40 个导出任务,平均完成时间 90 秒,按在途量约等于到达率乘平均时间,平均在途约 40×90=3,600 个。数据库变慢后平均完成时间升至 300 秒,在途增至 12,000 个。若每任务直接占 20 兆字节内存,需要约 240,000 兆字节,显然不可行,所以必须按实例限制并发并流式处理。
数据演绎 4:分片租约。 一个 1,200 万行任务按每片 20 万行拆成 60 片;单片正常 40 秒,租约设 120 秒,每 30 秒续租。执行器在第 70 秒宕机,最晚第 120 秒可重领,恢复时间上界约 50 秒。新执行器获得令牌 18,旧执行器令牌 17 即使恢复,也会因提交条件不匹配被拒绝。
热门面试题
问题(基础):异步导出为什么要有任务表? 考点:权威状态与可恢复性。 回答思路:说明消息不是任务状态库。 详细答案:任务表保存参数摘要、权限快照、数据版本、状态、进度、文件信息和错误;消息丢失、重复或延迟时都能由扫描器恢复。用户查询的是任务事实而不是消息位置,补偿器也依靠任务状态发现长期未推进对象。 进阶追问:能否只靠队列积压判断任务是否完成? 进阶回答:不能。消息可能已确认但文件合并失败,也可能进入重试通道;必须查任务、分片和对象存储三方证据。
问题(原理):租约为什么还要围栏令牌? 考点:暂停进程与过期所有者。 回答思路:说明旧执行器不知道自己已经失去所有权。 详细答案:旧执行器可能因长暂停错过续租,新执行器在租约过期后接管;旧执行器恢复时若只凭本地“曾经领取成功”继续提交,就会覆盖新结果。递增围栏令牌随写请求传递,权威存储只接受当前代次,从提交端阻断过期所有者。 进阶追问:只使用分布式锁是否足够? 进阶回答:不足。锁过期后旧持有者仍可能运行;没有围栏校验的外部副作用无法区分新旧所有者。
问题(项目):分片大小如何确定? 考点:恢复粒度、查询效率与调度开销。 回答思路:通过压测控制单片时长和内存。 详细答案:分片太大,失败重做成本和租约时间过长;太小则消息、数据库查询和合并元数据开销放大。我会压测不同游标范围,使单片稳定在几十秒到数分钟,并保证单片峰值内存、连接占用和对象大小都在预算内,再按租户公平调度。 进阶追问:数据持续变化时如何避免重复或漏行? 进阶回答:使用数据库一致性快照、固定截止版本或稳定主键游标;不能用变化中的页码偏移直接并发翻页。
2.2 文件落盘、通知、补偿与内存边界
文件阶段采用“流式读取、临时对象、校验后发布”。每批只保留有限行,写入本地受限临时文件或直接分段上传;分片完成记录行数、字节数和校验值。合并器以任务号和合并代次为幂等键,先生成不可见临时对象,核对所有分片后原子更新任务文件引用。通知只是成功后的派生动作,失败不能把已生成文件回滚为失败。
sequenceDiagram
participant W as 执行器
participant D as 任务数据库
participant O as 对象存储
participant N as 通知服务
W->>D: 校验租约令牌与分片状态
loop 有界批次
W->>D: 按稳定游标读取
W->>O: 追加临时分段
end
W->>O: 完成上传并取得校验值
W->>D: 条件提交文件元数据
D-->>W: 成功或幂等命中
W->>N: 发布完成通知
N-->>W: 失败则独立重试flowchart LR
A[内存告警] --> B[暂停领取新分片]
B --> C[保留执行中租约与现场]
C --> D[核对单批行数与对象膨胀]
D --> E[降低批次和实例并发]
E --> F[从已提交游标恢复]
F --> G[校验行数与文件摘要]| 状态 | 进入条件 | 可执行动作 | 超时或失败处理 |
|---|---|---|---|
| 待执行 | 任务与分片已创建 | 领取租约 | 超龄告警并补发事件 |
| 执行中 | 当前令牌领取成功 | 读取、写临时对象、续租 | 租约过期后重领 |
| 合并中 | 全部分片成功 | 生成最终对象 | 按任务与代次幂等重试 |
| 已成功 | 文件校验和任务提交成功 | 下载、通知 | 通知失败独立补偿 |
| 待重试 | 可恢复异常且预算未耗尽 | 退避重试 | 超预算转人工 |
| 已失败 | 参数、权限或数据永久错误 | 展示明确原因 | 修正后创建新任务 |
数据演绎 5:OOM(内存溢出)边界。 单实例内存预算 4 吉字节,基础占用 1.5 吉字节,保留 1 吉字节安全余量,可用于导出的只有 1.5 吉字节。若每个并发任务批次峰值 220 兆字节,并发上限应取 floor(1,500/220)=6,而不是按中央处理器核数盲设 16。改为每批 5,000 行、每行序列化后平均 1.2 千字节,原始批数据约 6 兆字节,再计对象膨胀和缓冲也远低于整表加载。
项目话术:“我没有把同步导出简单搬进 MQ(消息队列)。任务表是权威,按稳定快照拆片,执行器靠租约和围栏令牌防重复提交,文件流式写临时对象并校验后发布;OOM(内存溢出)时先停领、降批次和并发,从已提交游标恢复。通知与文件成功解耦,最终用行数、摘要、对象存在性和任务状态四方核对。”
热门面试题
问题(基础):如何避免百万行导出造成 OOM(内存溢出)? 考点:流式处理与有界资源。 回答思路:从读取、转换、写入和并发四层限界。 详细答案:使用稳定游标分页,每批转换后立即写出并释放对象;不把所有行放入集合,也不在消息中传大数据。实例设置并发、批次、临时磁盘和数据库连接上限,监控单任务峰值与垃圾回收。文件格式若需要全局结构,也应使用支持流式写入的库或分片后合并。 进阶追问:把堆内存调大是否能解决? 进阶回答:只能延后故障,还可能增加垃圾回收暂停;根因若是整表持有或无界并发,必须改成有界流式模型。
问题(原理):上传成功但任务状态更新失败怎么办? 考点:跨存储失败窗口。 回答思路:对象键稳定、提交幂等、孤儿清理。 详细答案:用任务号和合并代次生成稳定对象键;重试前先查询对象元数据和校验值,已存在且一致则只补任务提交,不重复生成。若对象不一致则隔离。定时任务扫描超过生命周期且没有任务引用的临时对象,延迟删除并保留审计记录。 进阶追问:能否先把任务标成功再上传? 进阶回答:不能,否则用户可能拿到不存在或不完整的文件;应在对象完成并校验后条件提交成功状态。
问题(项目):导出完成通知失败是否回滚任务? 考点:核心结果与派生副作用解耦。 回答思路:任务继续成功,通知单独补偿。 详细答案:文件已校验并被任务引用就是核心成功,通知失败只记录通知子状态,用同一通知幂等键退避重试;用户仍可在任务中心查询下载。若回滚任务,会制造文件事实与任务事实冲突,并导致重复生成大文件。 进阶追问:通知积压时如何降级? 进阶回答:保留站内任务中心,暂停低优先级短信或邮件,合并重复提醒;文件生成链路不受通知渠道拖累。
3. WMS(仓储管理系统)库存:条件扣减、预占与唯一流水
3.1 库存权威事实与防超卖事务
WMS(仓储管理系统)库存的权威数据源是库存余额、预占记录和唯一库存流水。下单时同步执行数据库条件更新,例如“可用量大于等于请求量且版本匹配”才扣减或转入预占;受影响行为零就是库存不足或并发冲突。MQ(消息队列)用于传播“已预占”“已确认”“已释放”等事实,不能先把扣减请求排队后就向用户承诺成功。
sequenceDiagram
participant O as 订单服务
participant I as 库存数据库
participant M as 消息队列
participant W as 仓库履约
O->>I: 条件预占并写唯一流水
alt 库存充足
I-->>O: 提交预占与发件记录
O->>M: 发布库存已预占事件
M->>W: 驱动履约
W->>W: 按职责与事件号去重
else 库存不足
I-->>O: 明确拒绝
end| 事实或动作 | 幂等键 | 条件 | 重复处理 |
|---|---|---|---|
| 预占 | 订单行号+预占 | 可用量大于等于数量 | 返回原预占结果 |
| 确认出库 | 预占号+确认 | 状态为已预占 | 已确认则幂等成功 |
| 释放 | 预占号+释放 | 状态为已预占且未确认 | 已释放则幂等成功 |
| 库存事件 | 库存流水号 | 与余额事务同提交 | 消费职责加事件号去重 |
| 投影更新 | 仓库+库存单位+版本 | 当前版本小于来件版本 | 旧版本记录后忽略 |
数据演绎 6:防超卖并发。 某库存单位可用量 100,200 个请求同时各买 1 件。应用层“先查再扣”可能让 200 个请求都读到大于零;数据库执行 available=available-1 where available>=1 时只有 100 次更新成功,另外 100 次受影响行为零。最终可用量为 0,不会为负;每个成功事务同时写一条唯一库存流水和发件记录。
热门面试题
问题(基础):为什么 MQ(消息队列)不能作为防超卖的唯一手段? 考点:排队顺序不等于库存事实。 回答思路:说明重复、故障和多入口会破坏单队列假设。 详细答案:消息会重复、重试和延迟,消费者也可能扩容;盘点、调拨、取消等入口并不一定经过同一队列。即使单线程顺序消费,向用户承诺成功与真实扣减之间仍有失败窗口。正确底线是权威数据库原子条件更新或等价事务约束。 进阶追问:串行消费还有价值吗? 进阶回答:有,可降低热点并发和锁冲突,但它是吞吐优化,不替代条件扣减与唯一流水。
问题(原理):条件扣减和唯一流水分别解决什么? 考点:数量不变量与动作去重。 回答思路:一个守余额,一个守业务意图。 详细答案:条件扣减保证并发下可用量不会小于零;唯一流水保证同一订单行的预占、确认或释放不会重复生效。不同订单同时竞争由条件更新裁决,同一事件重投由唯一键吸收,两者共同构成库存正确性底线。 进阶追问:为什么确认和释放不能共用一个幂等键? 进阶回答:它们是相反且都可能合法的动作;应分别唯一,并由预占状态机裁决谁能从已预占状态迁移。
问题(项目):高峰时如何既削峰又不超卖? 考点:同步裁决、异步传播和容量保护。 回答思路:同步只做最小库存事务,后续履约异步。 详细答案:入口先限流并同步完成条件预占、唯一流水和发件记录;成功后异步创建拣货、更新搜索投影和统计。消费者按仓库与库存单位路由,热点可合并中间投影,但库存流水不可采样。数据库达到安全水位时降低受理速率,而不是让队列替数据库承诺库存。 进阶追问:数据库短暂不可用如何降级? 进阶回答:停止新的库存成功承诺,返回繁忙或排队资格;可展示带时间戳的缓存库存,但必须明确不可作为成交裁决。
3.2 库存事件、补偿、对账与降级
库存状态机以预占单为核心:待处理只能进入已预占或失败;已预占可确认出库或释放;已确认不能被普通超时释放;已释放后迟到的确认必须进入冲突核验。余额更新、库存流水和 Outbox(发件箱)在同一本地事务提交。发送、消费或下游投影失败时用原流水号重试,扫描器比较余额版本、流水和发件状态补发。
stateDiagram-v2
[*] --> 待处理
待处理 --> 已预占: 条件扣减成功
待处理 --> 失败: 库存不足
已预占 --> 已确认: 出库确认
已预占 --> 已释放: 取消或超时释放
已确认 --> 退货处理中: 退货申请
退货处理中 --> 已退回: 入库确认
已释放 --> 冲突核验: 迟到出库确认
已确认 --> 冲突核验: 迟到释放| 指标 | 告警建议 | 诊断含义 | 降级或补偿 |
|---|---|---|---|
| 条件扣减失败率 | 同库存单位 1 分钟超过 30% | 热点或真实售罄 | 限流并快速售罄返回 |
| 重复流水冲突 | 5 分钟超过历史三倍 | 上游重试或键污染 | 核对同键参数,一致则幂等 |
| 发件滞留年龄 | 最老超过 60 秒 | 发布器或代理节点异常 | 扫描补发,暂停非关键投影 |
| 预占超龄数 | 超过 15 分钟未确认 | 订单回执缺失 | 查询订单后释放或保留 |
| 库存差异量 | 任一库存单位不为零 | 余额、流水或实物不一致 | 冻结该库存单位并盘点 |
数据演绎 7:峰值与恢复。 活动期间每秒成功预占 18,000 条,下游履约稳定处理 10,000 条,持续 300 秒形成 240 万条积压。峰后新增降到 2,000 条/秒,将履约提升到 14,000 条/秒,净消化 12,000 条/秒,理论 200 秒恢复。若数据库安全水位只允许 12,000 条/秒,则不能硬拉到 14,000,应按安全水位重新计算并接受更长恢复时间。
项目话术:“库存正确性不靠消息顺序,而靠数据库条件预占、预占状态机和唯一流水;消息只传播已裁决事实。确认与释放乱序时用状态机拒绝倒退,发件失败按流水补发,投影按库存版本更新。事故期保留库存事务,暂停报表和低价值投影,最终按可用、预占、已售和流水做逐库存单位对账。”
热门面试题
问题(基础):库存消息重复消费如何处理? 考点:流水唯一与消费职责去重。 回答思路:生产和消费两端都保留稳定标识。 详细答案:余额事务生成唯一库存流水号,事件以该流水号作为稳定标识。消费者在自己的数据库中按消费职责加事件号建立唯一约束,并与投影或履约更新同事务提交。重复命中读取原结果并确认,不能再次扣减或创建拣货任务。 进阶追问:缓存去重是否足够? 进阶回答:不足。缓存会过期、淘汰或在故障时不可用;关键库存副作用必须依赖持久唯一约束和状态条件。
问题(原理):预占超时为什么不能直接释放? 考点:迟到确认与状态冲突。 回答思路:先查询订单和出库事实,再做条件释放。 详细答案:超时只说明本地未收到确认,不代表订单取消或仓库未出库。补偿器先查询订单与仓库状态,只有仍处于可释放状态才用预占版本条件更新;已确认则保持,结果未知进入人工核验,避免释放后又收到出库确认造成超卖。 进阶追问:迟到确认到达已释放状态怎么办? 进阶回答:不直接覆盖,创建冲突单,核对实物出库;已出库则通过调整或补扣显式修正,未出库则拒绝确认。
问题(排障):消息积压时库存链路如何降级? 考点:保护权威事务和关键订阅。 回答思路:区分不可丢流水与可重建投影。 详细答案:继续保证条件预占、唯一流水和发件记录,暂停报表、推荐和低优先级同步;履约与释放保留独立配额。入口按数据库安全水位限流,恢复时实时事件与历史积压分配份额,并按库存单位保持版本单调。 进阶追问:何时才算恢复完成? 进阶回答:积压、最老年龄回到目标后,还要确认预占超龄清零、投影追上版本、余额与流水及实物盘点差异闭合。
4. 支付:验签、Outbox(发件箱)、账务与对账
4.1 回调验签、本地事务与可靠发件
支付回调的权威输入是渠道原始报文、渠道交易号和可验证签名,内部权威事实是支付单与账务流水。同步链先校验传输安全、商户号、金额、币种、订单关系和签名,再以渠道交易号建立唯一约束,用状态条件把待支付推进为成功;同一本地事务写支付单、回调审计与 Outbox(发件箱)。回调响应应在本地事实可靠提交后返回,账务、订单和通知由后续事件驱动。
sequenceDiagram
participant C as 支付渠道
participant P as 支付服务
participant D as 支付数据库
participant M as 消息队列
participant A as 账务服务
C->>P: 重复或乱序回调
P->>P: 校验签名与金额币种
P->>D: 唯一交易号和状态条件更新
alt 首次合法成功
D-->>P: 支付单与发件记录提交
P-->>C: 返回受理成功
P->>M: 发布支付成功事件
M->>A: 按支付单与职责幂等记账
else 合法重复
D-->>P: 返回原处理结果
P-->>C: 返回受理成功
else 验证失败
P-->>C: 拒绝并告警
end| 环节 | 权威校验或幂等键 | 失败窗口 | 处理原则 |
|---|---|---|---|
| 回调接入 | 签名、商户、金额、币种 | 验签前无业务写入 | 拒绝并保留脱敏证据 |
| 支付落库 | 渠道交易号或支付单号 | 提交成功但响应丢失 | 重复回调返回原结果 |
| 发件 | 支付事件号 | 数据库成功但尚未发送 | 扫描 Outbox(发件箱)补发 |
| 账务消费 | 账务职责+支付事件号 | 记账成功但确认丢失 | 唯一流水吸收重复 |
| 通知 | 支付单号+通知类型 | 渠道失败或超时 | 独立退避,不影响支付成功 |
数据演绎 8:重复回调与本地提交。 渠道因响应超时在 30 秒内重发 5 次,同一渠道交易号 T20260714001 的 5 个请求并发到达。唯一约束只允许 1 次把支付单从待支付改为成功并生成 1 条支付事件;其余 4 次读取既有成功结果并返回。若消息确认丢失导致事件再投 3 次,账务职责唯一键仍只产生 1 条金额为 1,288.00 元的入账流水。
热门面试题
问题(基础):支付回调为什么必须先验签再落库? 考点:可信输入边界。 回答思路:签名之外还要核对业务字段。 详细答案:未验签报文不能成为资金事实,否则攻击者可伪造成功。验签通过也只证明来源和完整性,还要核对商户、支付单、金额、币种以及当前状态。校验失败保留脱敏原文、摘要和渠道请求标识并告警,但不能推进支付状态。 进阶追问:重复的合法回调应返回失败吗? 进阶回答:不应。读取原成功结果并返回渠道要求的成功响应,避免渠道继续重试;内部不重复记账和发事件。
问题(原理):Outbox(发件箱)解决了什么,没解决什么? 考点:本地双写与端到端边界。 回答思路:先说同事务留下事件,再说下游仍需幂等对账。 详细答案:它让支付状态和待发送事件在一个本地事务中提交,避免支付成功后进程宕机而永久没有事件。发布器会重复发送,账务和订单仍要各自幂等;外部渠道与内部数据库也不在同一事务域,因此还要主动查单和资金对账。 进阶追问:发件记录标记已发送是否代表账务完成? 进阶回答:不代表。它只说明发布阶段有确认;账务要有独立消费记录、账务流水和处理回执。
问题(项目):支付成功事件发送超时怎么办? 考点:结果未知和稳定事件号。 回答思路:不回滚支付、不生成新事实、查证后补发。 详细答案:支付事实已经提交就不能因消息超时改回失败。发件记录置为未知,保留原事件号按退避重发;消费者用职责加事件号吸收重复。接口向前端提供支付单查询,而不是把消息超时冒充资金失败。 进阶追问:何时需要人工介入? 进阶回答:渠道与本地金额或状态长期不一致、查单持续未知、同一交易号对应不同业务字段时,冻结自动动作并生成差异单。
4.2 账务副作用、对账补偿与人工介入
账务消费先以“账务职责、支付事件号”插入 Inbox(收件箱)记录,再校验金额、币种和科目,以支付单号生成唯一账务流水,三者在同一本地事务提交。外部退款、代付等副作用必须携带渠道支持的稳定请求号;响应超时进入结果未知,优先查单。日内增量对账发现延迟,日终全量对账比较渠道账单、支付单、账务流水与订单状态,差异按“漏单、重复、金额错、状态错”分类补偿。
stateDiagram-v2
[*] --> 待支付
待支付 --> 支付成功: 合法成功回调或查单
待支付 --> 已关闭: 超时且渠道确认未支付
支付成功 --> 退款处理中: 创建唯一退款单
退款处理中 --> 已退款: 渠道确认退款
退款处理中 --> 结果未知: 调用超时
结果未知 --> 已退款: 查单确认成功
结果未知 --> 退款处理中: 查单确认未执行
结果未知 --> 人工核验: 超过查证期限
已关闭 --> 人工核验: 迟到支付成功| 差异类型 | 示例 | 自动补偿条件 | 必须人工的边界 |
|---|---|---|---|
| 渠道有、本地无 | 回调永久未达 | 签名账单可验证且订单唯一 | 无法匹配商户订单 |
| 本地成功、账务无 | 消费长期失败 | 支付单与渠道一致且无账务流水 | 科目或金额映射异常 |
| 账务重复 | 旧系统缺少唯一键 | 两笔完全同源且可安全冲正 | 已进入结算或下游清算 |
| 金额不一致 | 手续费或币种映射错 | 规则明确且差额在授权范围 | 涉及汇率、跨币种或大额 |
| 关闭后支付 | 回调严重迟到 | 规则允许自动退款 | 订单已履约或存在争议 |
数据演绎 9:支付积压恢复。 账务事件积压 900,000 条,渠道与数据库综合安全能力 3,000 条/秒,正常新增 1,000 条/秒。若 10 分钟恢复,需要净消化 1,500 条/秒,总处理 2,500 条/秒,低于安全水位;若要求 5 分钟,需要净消化 3,000 条/秒,总处理 4,000 条/秒,超过水位,不能靠加线程实现,只能延长目标或申请配额。
项目话术:“支付链路我先验签和核对金额币种,再用渠道交易号唯一约束推进支付单,同事务写 Outbox(发件箱)。账务按职责和事件号去重并写唯一流水;退款超时不盲重试,保留原请求号先查单。消息成功不等于资金正确,日终用渠道、支付单、账务和订单四方对账,自动补偿有明确授权边界,剩余差异双人审核。”
热门面试题
问题(基础):消息已消费成功为何仍要对账? 考点:传输成功与资金正确不同。 回答思路:列出金额映射、外部事实和历史数据边界。 详细答案:消费成功只说明代码返回成功,仍可能记错金额、币种或科目;渠道可能已扣款但回调丢失,人工重放也可能制造历史差异。对账用独立来源验证金额守恒,能发现消息指标看不到的静默错误。 进阶追问:对账是否可以只比总金额? 进阶回答:不能。总额可能被一多一少抵消;要先逐交易匹配,再做日、商户、币种维度汇总校验。
问题(原理):退款超时为什么不能直接重试? 考点:外部副作用结果未知。 回答思路:说明渠道可能已退款但响应丢失。 详细答案:超时无法证明渠道未执行,直接用新请求号重试可能重复退款。应使用稳定退款单号作为渠道幂等号,超时进入结果未知并主动查询;明确未执行才重试,持续未知转人工,内部账务也按退款单号唯一记账。 进阶追问:渠道不支持幂等号怎么办? 进阶回答:串行化同一退款单,超时只查不盲重试;无法确认时人工处理,并通过渠道账单对账兜底。
问题(项目):如何设计支付人工补偿? 考点:权限、证据、限额和可逆性。 回答思路:差异分类后才允许动作。 详细答案:差异单保存四方证据、原因、建议动作和影响金额;查询、补记、冲正、退款分权限,超过阈值双人审批。执行复用稳定业务号,记录操作前后状态,完成后再次对账。不能提供无条件“重放全部”的按钮。 进阶追问:如何验证补偿没有二次伤害? 进阶回答:先只读预演与小批执行,核对唯一流水、渠道结果和金额守恒,再扩大批次;任何差异放大立即停止。
5. 跨境轨迹:乱序、重复、限速与版本
5.1 轨迹归一化、版本推进与去重
跨境轨迹的权威数据源分两层:原始轨迹表保存承运商原报文与接收证据,聚合表保存按规则计算的当前状态。幂等键优先使用“承运商、运单号、来源事件号”;没有稳定事件号时组合事件码、发生时间、地点和报文摘要。原始明细允许乱序到达且不可覆盖,聚合状态按来源序号、业务里程碑优先级和版本条件单调推进;发现版本缺口时进入补拉,不把迟到事件误判为丢失。
flowchart TD
A[承运商回调或拉取] --> B[鉴权与格式校验]
B --> C[保存原始报文与唯一键]
C --> D{是否重复}
D -->|是| E[返回原处理结果]
D -->|否| F[映射统一事件码]
F --> G{版本连续}
G -->|是| H[条件推进聚合版本]
G -->|否| I[记录缺口并安排补拉]
H --> J[发布轨迹变化事件]
I --> K[按渠道配额限速查询]
K --> C| 问题 | 业务规则 | 失败窗口 | 补偿与降级 |
|---|---|---|---|
| 重复 | 稳定来源键唯一 | 保存成功但响应丢失 | 返回既有结果 |
| 乱序 | 明细全存,聚合按版本或偏序推进 | 新版本先到 | 旧事件保留但不倒退 |
| 缺口 | 当前 41 直接收到 43 | 42 未达或延迟 | 建缺口任务限速补拉 |
| 渠道限速 | 每承运商独立令牌预算 | 重试放大请求 | 熔断故障渠道,保留其他渠道 |
| 映射失败 | 原码无法归一 | 新事件码未兼容 | 原文隔离,聚合保持旧状态 |
| 查询不可用 | 第三方长期故障 | 最终状态未知 | 展示最后更新时间与“待同步” |
数据演绎 10:乱序与限速。 运单 WB9001 当前版本 41,先收到版本 43 的“已清关”,20 秒后收到版本 42 的“到达口岸”。明细表保存两条,聚合先推进到 43,版本 42 到达后不倒退;系统同时生成缺口 42 的补拉任务。某承运商配额 500 次/秒,正常查询 300 次/秒,补拉最多只能使用剩余 200 次/秒;20,000 个缺口理论至少需要 100 秒,不能用 2,000 个线程绕过配额。
项目话术:“轨迹原文是证据,聚合状态是计算结果。我按承运商、运单和来源事件号去重,原始明细全存,聚合用版本和业务偏序单调推进;版本跳跃生成缺口任务,每个承运商独立限速和熔断。故障时展示最后更新时间,恢复后补拉并逐运单核对最终里程碑,不让迟到事件覆盖较新状态。”
热门面试题
问题(基础):轨迹乱序为什么不能只按接收时间排序? 考点:网络到达顺序与业务发生顺序不同。 回答思路:使用来源序号、发生时间和业务偏序。 详细答案:跨境链路经过多系统重试,晚发生的事件可能先到。接收时间只证明本系统看到它的时间,不能代表物流过程。优先使用来源序号;没有序号时结合可信发生时间和“揽收、出境、清关、派送、签收”的业务偏序,冲突保留原文并人工核验。 进阶追问:发生时间也相同怎么办? 进阶回答:使用稳定来源标识和里程碑规则形成偏序;无法证明先后的事件并存,不能伪造全局顺序。
问题(原理):版本 43 先到时为何还要补 42? 考点:最终状态与过程完整性。 回答思路:聚合可前进,明细仍可能缺证据。 详细答案:43 可能足以更新当前状态,但 42 可能包含清关单号、异常原因或时效节点,影响客服和对账。系统记录缺口并限速补拉;补到 42 后补全明细,但不把聚合版本从 43 降回 42。 进阶追问:缺口补拉失败如何降级? 进阶回答:保留最后可信状态与更新时间,标记同步中;按承运商隔离重试,不阻塞其他运单和渠道。
问题(项目):第三方接口限速如何避免重试风暴? 考点:配额隔离、退避与实时流保护。 回答思路:每渠道独立预算,新旧任务分配份额。 详细答案:按承运商建立并发和每秒令牌,明确可重试错误,使用指数退避与随机抖动;持续失败触发熔断。正常回调、实时查询和历史补拉分别保留份额,恢复后小流量探测再阶梯放量,不能让旧缺口吃满配额。 进阶追问:某一承运商故障会影响全部轨迹吗? 进阶回答:不应。队列、线程池、重试预算和告警都按承运商隔离,故障渠道降级为待同步,其他渠道继续推进。
6. Runner(执行器):租约、围栏与重复执行
6.1 调度所有权与执行幂等
Runner(执行器)案例的权威数据源是任务表、执行代次和结果表。调度器只创建可执行任务与事件,执行器通过条件更新领取有期限租约并获得单调递增围栏令牌;心跳只延长当前令牌的租约。幂等键取“任务号、执行代次、动作”,结果提交必须同时满足任务仍在执行中且提交令牌等于当前令牌。MQ(消息队列)重复投递只会触发重复领取竞争,不能产生两个有效结果。
sequenceDiagram
participant M as 消息队列
participant A as 执行器甲
participant D as 任务数据库
participant B as 执行器乙
M->>A: 投递任务
A->>D: 领取租约,获得令牌 17
A--xD: 长暂停,续租失败
M->>B: 任务重新投递
B->>D: 租约过期后领取,获得令牌 18
B->>D: 以令牌 18 提交成功
A->>D: 恢复后以令牌 17 提交
D-->>A: 围栏拒绝过期结果| 状态或动作 | 条件 | 失败窗口 | 补偿与降级 |
|---|---|---|---|
| 待执行到执行中 | 租约为空或已过期 | 多执行器同时领取 | 条件更新只允许一个成功 |
| 续租 | 令牌和持有者匹配 | 长暂停或网络隔离 | 失败立即停止产生新副作用 |
| 提交成功 | 当前令牌匹配且状态可提交 | 结果已算出但租约失效 | 围栏拒绝,读取当前结果 |
| 外部调用 | 稳定任务请求号 | 执行成功但响应丢失 | 先查外部结果再重试 |
| 重复执行 | 同代次消息重投 | 确认丢失 | 结果唯一键返回原结果 |
| 调度降级 | 队列或数据库超载 | 任务持续新增 | 暂停低优先级与可重建任务 |
数据演绎 11:租约和重复执行。 任务正常执行 50 秒,租约 90 秒,每 20 秒续租。执行器甲在第 35 秒暂停 80 秒,租约于第 110 秒前后过期;执行器乙取得令牌 18 并在第 150 秒提交。甲在第 115 秒恢复,虽然本地仍持令牌 17,但续租和最终提交都被拒绝。若外部任务支持任务号幂等,两次计算也只留下一个外部结果。
项目话术:“调度消息至少一次投递,所以我不假设只执行一次。任务表是权威,领取使用租约,提交再校验递增围栏令牌,旧执行器恢复也无法覆盖新代次;结果按任务与执行代次唯一。外部副作用传稳定任务请求号,超时先查结果。积压时暂停报表类低优先级任务,保留库存补偿、支付对账等关键任务配额。”
热门面试题
问题(基础):租约与永久锁相比有什么优势? 考点:故障后的自动接管。 回答思路:租约有期限,但必须配合围栏。 详细答案:执行器宕机无法主动释放时,永久锁会让任务长期卡死;租约过期后其他实例可接管。代价是旧执行器可能恢复,因此必须用围栏令牌限制续租和结果提交,并让长任务周期心跳。 进阶追问:租约应设多长? 进阶回答:大于正常心跳间隔和短暂停顿,并小于可接受接管时间;根据处理时长分位值压测,不能用所有任务共用一个极端值。
问题(原理):围栏令牌在哪一层校验才有效? 考点:副作用提交端裁决。 回答思路:仅在执行器本地比较没有意义。 详细答案:令牌必须由权威任务存储单调生成,并在结果数据库或外部服务的写入条件中校验。旧执行器无法仅凭本地状态证明所有权。若外部系统不支持令牌,则至少传稳定幂等请求号,并把结果未知纳入查询和人工补偿。 进阶追问:时钟不一致会影响围栏令牌吗? 进阶回答:递增代次不依赖各实例墙上时钟;租约过期判断集中在权威存储,避免客户端各自裁决。
问题(项目):任务重复执行一定是故障吗? 考点:执行尝试与有效提交分离。 回答思路:允许重复计算,不允许重复业务效果。 详细答案:确认丢失、租约接管和进程暂停都可能导致重复尝试,这是至少一次调度的正常边界。通过结果唯一键、状态机、围栏令牌和外部幂等号,多个尝试只能有一个有效提交;重复率异常升高仍需告警并查续租、超时和实例稳定性。 进阶追问:哪些任务可以直接丢弃旧结果? 进阶回答:可重建且以最新版本为准的报表或投影可拒绝旧版本;支付补偿、库存释放等业务动作不能无证据丢弃。
7. IoT(物联网):窗口聚合、优先级与背压
7.1 报警窗口聚合与严重告警穿透
IoT(物联网)报警分为原始遥测事实、告警实例和通知工单三层。原始遥测按设备号、指标、设备序号去重;告警实例幂等键取“设备、规则、首次触发版本”,状态机管理正常、待确认、告警中、已恢复。窗口聚合可以合并同设备同规则的重复告警,但必须保留首次、最高级别、计数、最后一次和恢复事件。严重告警进入独立高优先级通道,绕过普通聚合等待并预留消费者与通知配额。
flowchart TD
A[设备遥测] --> B[序号去重与时间校正]
B --> C{严重级别}
C -->|严重| D[高优先级直通]
C -->|普通| E[窗口聚合]
E --> F[保留首次最高级别计数与末次]
D --> G[告警状态机]
F --> G
G --> H[通知与工单]
H --> I[确认或升级]
B --> J{恢复事件}
J -->|是| G| 类型 | 聚合规则 | 优先级与时限 | 降级边界 |
|---|---|---|---|
| 严重安全告警 | 不等待窗口,重复只合并通知 | 5 秒内进入处置链 | 不采样、不被普通流阻塞 |
| 高级故障 | 10 秒窗口保留最高级 | 30 秒内通知 | 可合并重复,不丢恢复 |
| 普通告警 | 60 秒窗口汇总 | 5 分钟内可见 | 风暴时延迟或批量通知 |
| 状态遥测 | 按设备和指标保留最新版本 | 分钟级投影 | 可采样但保留最终状态 |
| 调试日志 | 按设备限量 | 非实时 | 超载时优先丢弃并计数 |
数据演绎 12:报警风暴。 100,000 台设备因网关故障每 5 秒上报一次,即 20,000 条/秒;消费能力只有 8,000 条/秒,10 分钟新增积压 (20,000-8,000)×600=7,200,000 条。若 60 秒窗口把同设备同故障 12 条聚为 1 条,普通通知流降到约 1,667 条/秒;同时为严重告警预留 2,000 条/秒独立能力,可确保严重事件不在普通积压后排队。
热门面试题
问题(基础):窗口聚合会不会丢告警? 考点:语义压缩而非无记录丢弃。 回答思路:说明保留字段和不可聚合事件。 详细答案:普通重复告警可以压缩成一个实例,但要保留首次时间、最高级别、出现次数、最后时间和恢复状态,原始遥测按合规要求留存或归档。严重告警和状态迁移不等待窗口,不能被采样或仅保留最后一条。 进阶追问:只保留最后状态为何不够? 进阶回答:会丢失持续时长、峰值和是否曾达到严重级别,影响责任追溯与告警升级。
问题(原理):严重告警穿透如何实现? 考点:独立通道、资源预留和状态去重。 回答思路:从入口识别到通知配额全链隔离。 详细答案:规则计算后立即把严重事件写入高优先级主题或独立队列,消费者线程池、数据库连接和通知渠道均预留配额;仍按告警实例去重,避免重复短信风暴。普通流拥塞或聚合器降级时,高优先级链路保持运行并监控端到端时延。 进阶追问:所有事件都标严重会怎样? 进阶回答:高优先级会失去隔离意义,所以规则变更需审批和比例告警,超过预算时按安全策略升级人工值守而非静默降级。
问题(项目):告警幂等键为何不能永久使用设备号加规则号? 考点:多次合法告警实例。 回答思路:一次触发到恢复构成一个实例。 详细答案:设备恢复后可能再次发生同类故障,如果永久按设备和规则去重,第二次真实告警会被吞掉。应在正常到触发时创建新的告警实例号,重复上报更新同一实例,恢复关闭实例,再次触发生成新实例。 进阶追问:恢复事件先到、故障事件后到怎么办? 进阶回答:按设备序号或状态版本拒绝倒退,迟到故障保留为原始证据但不重新打开已恢复实例。
7.2 背压、降级与报警风暴处置
背压从通知末端反向传递到窗口聚合、规则计算和设备接入:每层队列有界,按优先级设置配额;下游变慢时先暂停调试日志和可重建投影,再扩大普通告警聚合窗口、限制重复通知,最后对非关键遥测采样。严重告警、首次触发与恢复事件是不可降级事实。失败重试使用独立预算和退避,通知渠道故障时更新告警实例的通知子状态,不把整个告警消费阻塞在外部调用上。
flowchart RL
A[通知渠道水位] --> B[通知消费者限流]
B --> C[普通告警扩大聚合窗口]
C --> D[规则计算暂停低价值规则]
D --> E[接入层采样非关键遥测]
F[严重告警预留通道] --> B
G[首次触发与恢复] --> FstateDiagram-v2
[*] --> 正常
正常 --> 待确认: 指标首次越阈
待确认 --> 告警中: 连续窗口满足规则
待确认 --> 正常: 抖动消失
告警中 --> 已确认: 人员确认
告警中 --> 已恢复: 恢复窗口满足
已确认 --> 已恢复: 恢复窗口满足
已恢复 --> [*]
已恢复 --> 待确认: 新实例再次触发| 指标 | 目标或阈值 | 对应动作 | 恢复验收 |
|---|---|---|---|
| 严重告警端到端时延 | 99% 小于 5 秒 | 预留通道扩容并压低普通流 | 故障回放仍达标 |
| 普通聚合输入积压 | 最老小于 120 秒 | 扩窗口、按设备合并 | 最终状态与原始遥测一致 |
| 通知失败率 | 5 分钟超过 10% | 熔断渠道并转备用渠道 | 无重复轰炸且送达闭合 |
| 设备序号缺口 | 任一严重设备出现缺口 | 主动补拉或现场核验 | 版本连续或有审批说明 |
| 降级丢弃量 | 必须按类型计数 | 只允许调试和非关键遥测 | 严重、首次、恢复丢弃为零 |
| 告警实例超龄 | 严重实例 5 分钟无人确认 | 自动升级值班层级 | 工单与实例状态一致 |
数据演绎 13:背压水位。 通知渠道配额 1,000 次/秒,正常 600 次/秒;风暴后普通告警产生 4,000 次/秒、严重告警 200 次/秒。系统先为严重告警锁定 300 次/秒,普通流仅使用剩余 700 次/秒,并通过 60 秒聚合把普通通知降到 500 次/秒,总量 700 次/秒,留出 300 次/秒余量。若不聚合而立即重试,积压会每秒增长 3,200 次调用。
数据演绎 14:恢复阶梯。 渠道恢复后有 180,000 条待通知,但其中 150,000 条属于同一批已恢复的普通告警,可按实例合并成 5,000 条摘要;剩余 30,000 条按 300、500、700 次/秒阶梯回放,每档观察 2 分钟。最终以告警实例、通知回执和工单状态核对,而不是把历史重复逐条发送给用户。
项目话术:“报警风暴治理不是粗暴丢消息。我把原始遥测、告警实例和通知分层,普通重复在窗口内保留首次、峰值、计数、末次与恢复,严重告警独立通道穿透并预留资源。下游变慢时背压逐层降级,只采样可重建遥测;恢复后按实例合并回放,用严重告警时延、状态版本、送达回执和工单闭合验收。”
热门面试题
问题(基础):什么是业务背压? 考点:下游能力约束向上游传递。 回答思路:不仅暂停拉取,还要按价值降级生产。 详细答案:当通知、数据库或规则引擎变慢时,继续无限拉取只会把积压搬到内存。业务背压用有界队列、限流和水位信号逐层减少输入,并按严重程度决定聚合、延迟或采样,同时保护不可丢状态变化。 进阶追问:降低消费者并发是否就是背压? 进阶回答:只是其中一环;还要控制入口与重试,并让上游知道哪些类型应减产,否则代理节点积压仍会无限增长。
问题(原理):通知失败为何不应阻塞告警状态更新? 考点:核心事实与外部副作用分离。 回答思路:先可靠提交告警实例,再驱动通知子任务。 详细答案:告警是否触发是规则事实,短信或电话只是传递渠道。若在一个消费事务中同步等待渠道,渠道变慢会占满线程并阻塞后续严重告警。应提交实例状态和通知任务,渠道按稳定通知键重试,失败可切备用渠道或升级人工。 进阶追问:这样会不会出现状态成功但无人收到? 进阶回答:可能,因此必须监控通知超龄、回执和升级链,并预留备用渠道;不能靠阻塞主链假装消除外部失败。
问题(排障):报警风暴恢复后为何不能全量立即回放? 考点:历史事件过期性与二次冲击。 回答思路:先按实例和最终状态裁剪,再阶梯回放。 详细答案:很多普通告警已经恢复,逐条补发会轰炸用户并再次压垮渠道。先保留严重、首次、升级和恢复,重复普通告警合并为摘要;确认幂等和当前状态后按配额阶梯回放,实时严重流始终拥有独立份额。 进阶追问:哪些历史消息绝不能直接丢弃? 进阶回答:未送达的严重告警、首次触发、级别升级、恢复事件和已关联工单的审计事件;它们要送达或形成有审批的人工结论。
题库前非知识型过渡
以下综合题按“结论、机制链、失败边界、项目证据、验证闭环”组织,不重复计算为知识型小节。
8. 综合复习清单
问题(异步导出):请完整设计一个千万行数据的异步导出系统。
口述答案:我的结论是,异步导出不能只把同步查询搬进消费者,而要建立“任务事实、分片执行、文件发布、通知补偿”四段闭环。入口先校验权限、查询参数和预估规模,把规范化参数、权限快照、数据截止版本写入任务表并返回任务号;任务表是权威数据源,MQ(消息队列)只携带任务号和分片号。数据持续变化时使用一致性快照、截止版本或稳定主键游标,不能按会漂移的页码并发翻页。执行器通过条件更新领取租约并获得递增围栏令牌,按有界批次读取、转换和流式写临时对象,每片记录行数、字节数与校验值;租约过期可由新实例接管,旧令牌不能提交。全部分片完成后,合并器以任务号和合并代次幂等生成最终对象,校验后再把任务改为成功。上传成功但数据库提交失败时,重试先按稳定对象键查询,匹配则补提交;没有任务引用的临时对象由延迟清理器回收。通知是派生动作,失败只重试通知,不回滚文件。容量上,1,200 万行按每片 20 万行拆 60 片,单片约 40 秒;实例内存预算 4 吉字节时,根据单批峰值限制并发,而不是一次加载全部行。指标包括最老任务年龄、执行中数量、租约过期率、单片耗时、文件校验失败、内存和数据库水位。故障期先停领新片、保留任务状态,降批次与并发后从已提交游标恢复。最终以任务、分片、对象元数据和文件行数四方核验,并抽样下载校验权限、字符编码与列顺序,避免“任务成功但文件不可用”。
追问 1:为什么消息中不直接放查询结果? 直接回答:大消息会放大网络、存储和重试成本,也会让重复投递携带过期数据;消息只放稳定引用,执行器从权威任务表恢复上下文。
追问 2:用户重复点击如何处理? 直接回答:用租户、用户、规范化参数和数据版本生成短期任务幂等键,命中时返回既有任务;用户明确要求新版本时才创建新任务。
追问 3:如何证明文件没有漏行? 直接回答:固定数据版本,记录每片范围、行数和摘要,合并时校验范围无重叠无缺口,发布后再核对总行数、文件摘要与对象长度。
问题(异步导出):导出任务重复领取时,租约和围栏令牌如何配合?
口述答案:我的结论是,租约解决“故障后谁能接管”,围栏令牌解决“旧所有者恢复后能否继续提交”,二者缺一不可。任务表保存持有者、租约截止时间、执行代次和当前令牌。执行器领取时使用条件更新,只有待执行、待重试或租约已过期的任务可以进入执行中,同时令牌递增;多个实例收到同一消息时,只有一个更新成功。长任务每隔固定时间续租,续租必须同时匹配任务号、持有者和令牌。若执行器发生长暂停或网络隔离,它可能不知道租约已经过期;新实例接管并获得更大的令牌后,旧实例即使恢复,也会在续租、分片完成和最终文件提交处被权威存储拒绝。令牌不能只在本地比较,必须进入结果数据库的更新条件;调用对象存储或外部转换服务时,若对方不能校验令牌,就至少使用任务号加分片号作为稳定幂等请求号,并在结果未知时先查询。举例:租约 120 秒,每 30 秒续租,旧实例令牌 17 在第 70 秒宕机,新实例第 120 秒后以令牌 18 接管;旧实例恢复后写入条件
token=17影响行为零,不能覆盖令牌 18 的结果。监控要看领取冲突、续租失败、过期接管、过期令牌提交和重复结果命中。降级时暂停低优先级新任务,仍允许当前持有者有界续租;数据库不可用时不能凭本地锁继续提交。最终通过故障注入验证在业务提交前后暂停进程,任一执行代次最多只有一个有效结果,重复尝试都有审计记录;同时核对接管耗时不超过任务服务目标。追问 1:租约时间越长越安全吗? 直接回答:不是。过长会延迟故障接管,过短会因正常暂停频繁误接管;应根据任务耗时分位值、心跳间隔和目标接管时间压测确定。
追问 2:使用分布式锁后还需要围栏吗? 直接回答:需要。锁过期不等于旧进程立即停止,旧持有者仍可能晚到提交;围栏令牌在副作用提交端拒绝它。
追问 3:领取消息后数据库更新失败是否确认? 直接回答:不确认并按预算重试;任务权威状态未变,不会产生所有权。若是永久数据错误则隔离,不能无限占用正常队列。
问题(异步导出):线上导出发生 OOM(内存溢出),你如何止血、定位与恢复?
口述答案:我会先确认影响范围,再按“停领、保现场、定根因、受控恢复、业务核验”处理。第一步暂停该消费者领取新分片,保留任务表、租约、已提交游标和临时对象,不删除任务也不批量重建;若实例仍存活,降低并发并让可安全完成的小批次收尾,若持续抖动则摘除实例,让租约到期后接管。第二步采集进程内存、垃圾回收、堆转储、线程池队列、单任务行数、文件缓冲和临时磁盘,区分整表加载、批次对象膨胀、库内部缓存、无界并发和大字段异常。第三步用预算反推并发:实例 4 吉字节,基础占用 1.5 吉字节,安全余量 1 吉字节,只剩 1.5 吉字节;单任务峰值 220 兆字节时并发不超过 6。实现改成稳定游标分页,每批例如 5,000 行,转换后立即流式写出并释放引用;消息只传任务号,不携带大对象。第四步恢复前先确认数据库安全水位和对象存储配额,按 1、2、4 个并发阶梯放量,观察老年代、完全垃圾回收、单片耗时和失败率;历史任务与新任务分配份额,防止老积压饿死实时请求。对已上传但未提交的分片按稳定对象键查证,匹配则补状态,不重复生成。最后核对每个成功任务的分片范围、总行数、摘要、对象长度和下载可用性;最老任务年龄回到目标且没有孤儿对象,才算恢复。长期为单任务预估行数、最大文件、并发和租户额度设置硬限制,并做大字段与进程暂停故障演练,确认告警能在内存耗尽前触发自动停领。恢复后的首批任务还要人工抽检文件内容、下载权限和字符编码,确认技术恢复没有留下用户侧错误。
追问 1:直接增大堆内存是否可行? 直接回答:只能作为短时缓解,不能替代流式和有界并发;堆越大还可能带来更长的垃圾回收暂停与更慢的故障恢复。
追问 2:实例宕机后从哪里续跑? 直接回答:从任务表中最后已提交的稳定游标或分片边界续跑;本地未提交进度不可信,重复范围由分片幂等键和对象校验吸收。
追问 3:何时应直接失败而不是重试? 直接回答:参数非法、权限失效、单文件超过产品上限或源数据格式永久不支持时直接失败,并给出可操作原因;资源瞬态故障才退避重试。
问题(异步导出):对象存储上传成功,但任务状态更新失败,如何保证不重复生成文件?
口述答案:这是典型的跨存储结果未知窗口,不能依赖数据库和对象存储做一个想象中的全局事务。我的方案是先让每次业务意图拥有稳定身份:分片对象键由任务号、分片号和数据版本生成,最终对象键由任务号和合并代次生成;上传先写不可见临时对象,完成后记录对象长度、摘要和版本标识。数据库提交使用任务状态、当前围栏令牌和对象摘要做条件更新。若上传响应成功但数据库更新失败,重试器先查询稳定对象键:对象存在且长度、摘要、数据版本都匹配时,只补数据库状态;对象不存在才重新上传;对象存在但内容不一致则立即隔离,不能覆盖。若上传本身超时,同样先查对象元数据,因为服务端可能已经完成。数据库最终提交成功后才向用户暴露下载引用,并发布通知事件。孤儿清理器不能见到“数据库无引用”就立即删除,要等待超过最大重试和人工处理窗口,再检查任务终态、上传代次和审计记录,先标记后延迟删除。容量方面,100 个执行器同时重试同一最终对象时,数据库唯一条件和对象键应让 99 个实例读到既有结果并退出,而不是各自复制一份。指标包括未知上传数、对象查证耗时、摘要冲突、孤儿对象年龄、重复合并命中和任务成功但通知失败数。降级时对象存储不可用就暂停文件阶段,任务保持待重试并展示明确状态;不能把未生成文件标成功。最终按任务记录、对象版本、摘要和实际下载验证,确保一个成功任务只有一个当前文件引用。
追问 1:为什么临时对象不能直接作为下载文件? 直接回答:它可能仍在分段上传或尚未完成校验;只有对象完成、摘要核对并被任务条件引用后,才具有稳定可见性。
追问 2:摘要相同就一定是同一文件吗? 直接回答:还要同时核对任务号、数据版本、长度和格式版本;摘要是完整性证据之一,不能替代业务上下文。
追问 3:孤儿对象清理失败会影响正确性吗? 直接回答:通常先影响成本而非任务正确性,但必须告警并重试;清理动作要幂等且绝不能删除仍被成功任务引用的对象。
问题(异步导出):导出积压时如何兼顾数据库安全、用户公平和恢复时间?
口述答案:我不会以最快拉取消息为目标,而是以数据库和对象存储安全水位内的有效完成吞吐为目标。先按任务最老年龄、租户、预估行数、数据库查询耗时和失败类型拆分积压,确认瓶颈是在源库、执行器内存、临时磁盘、对象存储还是通知。假设每秒新增 40 个任务、平均 90 秒完成,正常在途约 3,600;数据库变慢后完成时间到 300 秒,在途增至 12,000。此时盲目扩消费者会增加连接、锁等待和缓存抖动。止血阶段暂停低优先级和超大任务的新受理,对单用户设置在途上限;执行器采用加权公平队列,小任务可提高交互体验,但必须为老任务和大任务保留份额,避免永久饥饿。根据压测确定数据库连接、每秒扫描行数和对象上传带宽的安全水位,在水位内小步增加并发,每一步观察查询分位延迟、锁等待、内存、错误率和净完成速度。恢复时间使用“当前积压除以完成速率减新增速率”计算,而不是用消息拉取速率。执行、合并和通知使用独立队列与配额,通知故障不能拖住文件生成。已有租约任务优先有界完成,过期任务按原任务号和分片号恢复,不创建新任务绕过去重。降级时用户仍可查询进度与取消待执行任务,暂停精确进度刷新以减少写放大。最终验收不仅看积压归零,还要检查最老任务、租户等待分布、数据库水位、文件成功率与重复任务率恢复正常,并确认任何租户都未超过承诺等待上限。恢复报告同时闭合取消、失败和成功任务总量,并按租户核对等待分布,防止平均值掩盖长期饥饿。
追问 1:为什么不能总是优先小任务? 直接回答:纯最短任务优先会让大任务长期饥饿;应使用租户配额、任务年龄和大小加权,并为超龄任务保留最低服务份额。
追问 2:新增消费者何时反而降低吞吐? 直接回答:当源库连接、锁、磁盘或对象存储已过拐点时,更多并发会造成超时和重试放大,有效完成率下降。
追问 3:恢复时间如何动态更新? 直接回答:按最近窗口的真实成功任务或分片速率减新增速率计算净消化能力,并随任务大小分布与下游水位实时修正。
问题(仓储库存):请设计活动高峰下的库存防超卖方案。
口述答案:我的结论是,防超卖的正确性底线必须落在权威库存数据库的原子条件更新、预占状态机和唯一库存流水上,MQ(消息队列)只负责削峰后的传播与履约,不能替代库存裁决。下单同步链先校验订单行和仓库库存单位,用“可用量大于等于请求量且版本符合预期”的条件把可用转为预占;受影响行为为零就明确返回库存不足或并发冲突。成功事务同时写预占单、唯一流水和 Outbox(发件箱),随后异步驱动拣货、搜索投影和运营统计。幂等键不能只用订单号,而应使用订单行号加预占动作;确认和释放分别使用预占号加动作类型,并由状态机限制已预占只能进入已确认或已释放。举例:可用量 100,200 个请求并发各买 1 件,条件更新只有 100 次成功,余额不会为负;应用层先查后扣则可能让 200 个请求都读到有货。消息重复时,消费者按职责加库存流水号建立唯一约束;不同事件乱序时,库存版本和预占状态机阻止倒退。活动期间每秒成功预占 18,000 条、履约消费 10,000 条,持续 300 秒形成 240 万条积压;峰后按数据库安全水位恢复,不能盲目扩容压垮仓储库。事故期保留预占、确认、释放和发件记录,暂停报表、推荐等可重建订阅;数据库不可用时停止新的成功承诺。最终按库存单位核对可用、预占、已售、退回和流水,并结合实物盘点闭合差异;压测还要覆盖同一库存单位的极端热点竞争。压测还要覆盖单个库存单位的极端热点,并验证所有失败请求既没有扣减余额,也没有生成库存流水。
追问 1:消息串行消费能否彻底防超卖? 直接回答:不能。重试、多入口、消费者故障和用户承诺窗口仍存在;串行只能降低并发冲突,权威条件更新才守住数量不变量。
追问 2:缓存库存可以作为成交依据吗? 直接回答:只能用于展示、预热和快速拦截,最终成功必须由权威库存事务裁决;缓存过期或故障时不能继续无条件承诺。
追问 3:为什么要写唯一库存流水? 直接回答:它既是动作幂等凭证,也是余额变化的审计证据,补发事件、对账和人工调整都能追溯到具体业务意图。
问题(仓储库存):库存预占、确认和释放如何处理重复与乱序?
口述答案:我会把预占单设计成单调状态机,而不是收到什么消息就直接覆盖状态。一次预占从待处理开始,条件扣减成功进入已预占,库存不足进入失败;已预占可以被出库确认推进到已确认,也可以在订单取消且仓库未出库时进入已释放。已确认不能被普通超时释放,已释放后到达的出库确认也不能直接覆盖,而要生成冲突单核对实物。每个动作都有独立稳定幂等键:订单行号加预占、预占号加确认、预占号加释放;同一动作重投读取原结果,不同动作由状态条件裁决。消费者在一个本地事务中插入职责加事件号的收件记录、校验预占版本、更新状态并写反向库存流水,提交后才确认消息。若业务提交成功而确认丢失,重投只命中既有结果。超时释放器不能仅看创建时间直接加回库存,要先查订单、支付与仓库出库状态,并用“仍为已预占且版本未变”的条件更新;查证未知就延后或人工核验。举例:释放事件先把预占版本 8 改为已释放版本 9,随后迟到的确认仍携带版本 8,条件更新影响行为零;系统识别为冲突,若仓库确已出库则创建显式调整或补扣流水,而不是把状态倒退。监控包括非法迁移、重复命中、预占超龄、迟到确认和人工冲突数。降级时确认与释放使用独立关键配额,投影和报表可暂停。最终对每个预占号核对订单、仓库回执、状态版本与库存流水,并统计冲突单从发现到关闭的时长与责任来源。人工调整必须生成新的审计流水和更高状态版本,不能直接覆盖冲突记录,否则后续无法解释数量来源。
追问 1:条件更新影响行为零可以直接当重复成功吗? 直接回答:不能。要读取当前状态和版本,区分已完成重复、迟到冲突和库存不足;只有业务参数与既有结果一致时才幂等返回。
追问 2:释放成功后迟到支付怎么办? 直接回答:不能恢复原预占假装无事发生;应重新尝试新预占,失败则走取消或退款补偿,并保留资金与库存冲突证据。
追问 3:状态机是否需要全局锁? 直接回答:通常不需要。按预占主键使用数据库条件更新和版本字段即可串行裁决,热点严重时再配合按键路由降低冲突。
问题(仓储库存):库存事件发布失败或重复发布,如何最终收敛?
口述答案:我的方案把库存余额、唯一流水和 Outbox(发件箱)写在同一个本地事务中,先保证“事实已经发生且事件意图一定可恢复”。发布器按发件记录扫描或领取租约,使用库存流水号作为稳定事件号发送;明确失败退避重试,超时标记为结果未知并仍复用原事件号,不能重新生成业务事实。即使代理节点已经落盘但确认丢失,重复发布也会被下游按“消费职责加事件号”唯一约束吸收。账务式去重不能只插一条已见标记后再更新业务,两者必须同事务,否则业务失败会被去重记录永久挡住。投影消费者还要使用仓库、库存单位和库存版本做条件更新;版本 43 先到、42 后到时,明细可保留,当前库存投影不能倒退。补偿扫描器关注发件记录最老滞留时间、发送尝试和对应库存流水是否存在,发现余额事务有流水却无可发布记录属于严重数据缺口,需要从流水重建事件并审计。反方向也要核对下游回执:发件标已发送不代表履约、搜索和统计都处理完成,每个职责有自己的收件证据。事故期暂停非关键投影和历史重放,保留出库、释放和库存同步的关键通道;恢复按下游数据库安全水位小批放量。最终对账按事件号比较生产记录、消费职责记录、投影版本和业务流水,任何库存单位的可用、预占、已售、退回关系不闭合都不能以“队列无积压”结案;补发完成后还要抽查重复消息是否只增加幂等命中而未增加业务流水。补发完成后抽查重复消息只增加幂等命中而不增加业务流水,并确认每个订阅职责都有独立回执。
追问 1:Outbox(发件箱)会不会造成重复消息? 直接回答:会,尤其在发送成功但状态更新失败时;它选择可恢复的重复而不是不可见的丢失,重复由消费幂等吸收。
追问 2:发件记录多久可以删除? 直接回答:至少覆盖消息保留、自动重试和人工重放窗口;关键流水应归档事件号、业务键和摘要,不能清理后失去对账证据。
追问 3:下游投影缺少一个版本怎么办? 直接回答:标记缺口并从权威库存流水补拉;更高版本可以按规则展示,但必须保留“数据待核对”状态,不能伪造连续性。
问题(仓储库存):库存消息积压时如何止血和恢复?
口述答案:我先确认积压影响的是“库存事实裁决”还是“已裁决事实的异步传播”。正确架构下,条件预占、确认、释放、唯一流水和发件记录仍在权威数据库同步提交;积压主要影响履约、投影和通知。排障按主题、队列、仓库和库存单位看生产速率、真实业务成功速率、最老消息年龄、重试比例、数据库连接与锁等待,识别总量过载还是热点库存单位。假设活动生产 18,000 条/秒、消费 10,000 条/秒,5 分钟增加 240 万条;峰后生产 2,000 条/秒,数据库安全消费水位 12,000 条/秒,净消化 10,000 条/秒,理论至少 240 秒。止血先限制活动入口到库存库可承受水位,暂停报表、推荐和可重建投影,给出库确认、取消释放保留独立配额;停止即时无界重试,把永久状态冲突隔离。不能随机打散同一仓库库存单位以追求并发,否则确认、释放和版本更新会乱序;热点可合并投影中间态,但唯一库存流水不能采样。恢复时实时流和历史积压按比例服务,小步增加消费者,每步观察库存库延迟、锁等待、成功吞吐和重试,超过拐点立即回退。处理旧事件前校验当前状态与版本,过期投影只记录不覆盖;业务动作仍按原幂等键执行。基础指标恢复后,再核对预占超龄、履约缺口、搜索版本和库存差异。只有积压年龄回落且逐库存单位不变量闭合,事故才结束,并复盘热点键是否需要隔离主题或业务合并策略。恢复曲线要与事前容量估算对照,偏差过大就继续查热点键、重试放大和数据库有效吞吐,而非宣布结束。
追问 1:为何增加分区不一定解决热点? 直接回答:同一库存单位仍必须稳定路由,已有积压也留在旧分区;若单个热点键超过单通道能力,需要业务合并、隔离或拆细顺序粒度。
追问 2:可以先确认消息再异步更新库存吗? 直接回答:关键库存动作不可以,否则进程宕机会静默丢失;若必须本地异步,先可靠写入本地任务表再确认。
追问 3:恢复为何要给新消息保留份额? 直接回答:否则历史积压会让新取消和释放长期等待,扩大预占占用与用户影响,甚至形成新的库存热点。
问题(仓储库存):如何通过对账发现消息链路看不到的库存错误?
口述答案:我会把对账目标定义为业务不变量,而不是消息条数相等。第一层是数据库内部:每个仓库库存单位的期初、入库、预占、确认出库、释放、退货和人工调整流水,应能推导期末可用量与预占量;余额表与流水汇总不一致时,先冻结该库存单位的自动调整。第二层是订单与预占:每个有效订单行必须对应一个可解释的预占终态,已取消订单不应长期占用,已出库订单不能处于已释放。第三层是仓库实物:拣货、出库回执和盘点数量与系统账比较,区分在途延迟、设备漏报和真实货差。第四层是异步传播:库存流水号与发件记录、各消费职责收件记录、搜索投影版本一一关联,发现“已发送但无业务结果”或“主队列清零但进入死信”的静默差异。举例:余额显示可用 70、预占 20、已售 10,总账为 100;若流水推导可用应为 69,就生成差异 1 的核验单,不能用一条无来源的更新把余额改成 69。自动补偿只处理证据充分、可逆且在授权阈值内的情形,例如补发遗漏投影;涉及实物、已出库冲突或较大调整必须人工复核。指标包括余额流水差异数、超龄预占、订单状态冲突、仓库回执缺口、投影版本落后和人工调整金额。补偿动作也生成唯一调整流水和更高版本事件,再次进入对账,保证修复可审计且不会被重复执行;对账批次还要记录输入截止时间、规则版本和差异总额,保证复算结果一致。每个对账批次还要记录输入截止时间、规则版本、差异总额和关闭时限,保证同一批数据可以稳定复算。
追问 1:为什么不能只比较库存总量? 直接回答:总量可能被不同库存单位的一多一少抵消;必须按仓库、库存单位、批次或货权等实际管理粒度核对。
追问 2:发现差异可以直接覆盖余额吗? 直接回答:不可以。要确定来源并生成显式调整流水,保留前后值、原因、证据和审批,否则下一次对账无法解释。
追问 3:对账频率如何设置? 直接回答:关键库存做分钟级增量核对和日终全量核对;频率由损失上限、数据量和补偿时限共同决定。
- 问题(支付):请完整说明支付回调从验签到异步通知的可靠性设计。
口述答案:我的结论是,支付回调必须先建立可信输入,再把支付事实与待发送事件原子留下,最后让账务、订单和通知各自幂等收敛。接入层先校验协议要求的签名、时间戳和商户身份,同时核对支付单、渠道交易号、金额、币种与订单归属;验签通过不等于业务字段正确,任何不一致都拒绝推进并保存脱敏证据。合法回调进入本地事务,以渠道交易号或支付单号建立唯一约束,用状态条件把待支付推进为支付成功,同时写回调审计和 Outbox(发件箱)。事务成功后向渠道返回约定成功响应;重复回调读取原结果并同样返回成功,避免渠道持续重试。发布器使用稳定支付事件号发送,超时标记结果未知并复用原号退避重试,不能因消息失败把已确认资金改回失败。账务、订单和通知按“消费职责加事件号”分别去重,账务再以支付单号生成唯一流水;业务写入与去重记录同事务提交后才确认。通知失败只推进通知子状态,不影响支付成功。监控贯通渠道请求号、支付单、事件号、账务流水和消息位置,关注验签失败、字段不一致、重复回调、发件滞留、消费失败和最老未记账年龄。降级时保留支付落库、账务和查单,暂停营销通知;发件系统故障时接口返回已受理并提供支付单查询。最终用渠道流水、支付单、账务流水和订单状态四方对账,而不是依据消息确认判断资金正确;还要抽样验证重复回调的响应内容符合渠道协议,避免对方持续重发。此外抽样验证重复回调响应符合渠道协议,异常回调没有生成支付事件,避免传输正确却污染资金事实。
追问 1:验签失败的回调可以进入消息队列留待处理吗? 直接回答:不能进入业务成功链。可写安全审计或隔离通道,但不得生成支付成功事件;重复异常达到阈值应触发安全告警。
追问 2:重复回调为何仍返回成功? 直接回答:只要其业务字段与既有成功记录一致,就返回幂等结果以停止渠道重试;字段不一致则报警并拒绝。
追问 3:支付接口何时能向用户展示成功? 直接回答:本地支付事实可靠提交并能由渠道证据验证后可展示;账务和通知可异步,但页面应提供最终状态查询。
- 问题(支付):支付场景为何选择 Outbox(发件箱),它有哪些失败窗口?
口述答案:我选择 Outbox(发件箱)的原因是支付单和待发送事件都在同一个关系型数据库本地事务中,可以消除“支付事实已提交但应用在发消息前宕机”的不可恢复双写缺口。事务内写支付状态、渠道证据、事件号、事件类型和待发送状态;发布器通过条件领取和短租约发送,明确失败退避,超时保持未知。它并不保证只发一次:代理节点已收但确认丢失、发送成功后发件状态更新失败、租约过期被其他实例接管,都可能重复发布,所以事件号在所有尝试中必须不变,下游仍需持久幂等和状态机。它也不保证下游完成,发件记录“已发送”只说明达到消息产品约定的确认边界;账务、订单、通知分别保存收件结果和业务回执。扫描方式要有待发送状态与创建时间复合索引,批量领取有上限,避免长期扫描拖垮支付主库;历史记录按消息保留、重试、人工重放和审计期限归档。若发布器全面故障,支付事务继续留下发件记录,告警最老滞留年龄并暂停非关键通知,恢复后按事件原号限速补发。若数据库事务回滚,支付和事件一起不存在,不会出现幽灵消息。若发现支付成功却没有发件记录,说明绕过了事务约束或历史数据损坏,需要从支付审计重建事件并生成差异单。最终通过故障注入在业务提交前后、消息确认前后杀进程,验证结果只会是未提交或可观察重复,不会是已支付但永久无事件;归档前还要确认所有消费职责已超过最大重放窗口。归档前确认所有消费职责都超过最大重放窗口,并保留事件摘要、支付单号和发送证据供后续审计复算。
追问 1:发件表会不会拖慢支付事务? 直接回答:会增加一次同库顺序写,但字段应精简、索引克制,发布和归档异步;这是用可控本地成本换掉不可恢复双写窗口。
追问 2:可以先发消息再提交支付事务吗? 直接回答:不可以。消费者可能看到最终回滚的支付成功,形成幽灵记账;应先在同一事务留下事实与发件记录。
追问 3:何时考虑事务消息而不是 Outbox(发件箱)? 直接回答:团队已稳定运维对应事务消息协议、回查能力和版本边界时可评估;无论选择哪种,下游幂等、外部查单和对账仍不可省略。
- 问题(支付):账务消费者如何保证重复消息不会重复记账?
口述答案:我不会用进程内集合或“先查询是否处理过”保证账务幂等,而是在账务数据库中把收件凭证、状态校验和账务流水放进同一个本地事务。消息带稳定支付事件号、支付单号、金额、币种和事件版本;消费者先尝试插入“账务职责加事件号”的 Inbox(收件箱)唯一记录,再核对支付事件类型、金额币种、科目映射和当前账务状态,最后以支付单号加账务动作生成唯一流水。两个消费者并发收到同一事件时,数据库唯一约束只允许一个事务成功;另一个读取既有流水,字段一致则幂等返回,字段不一致则隔离为键污染。不能先提交收件记录再写流水,否则记账失败后重试会被误挡;也不能先写流水后补收件记录,否则崩溃会再次执行。业务事务成功后才确认消息,所以“记账已提交但确认丢失”只会形成可观察重复。退款、冲正和支付成功是不同动作,不能只用支付单号做一个永久去重键;每个动作有独立业务号和状态机。若账务还需调用外部总账,先可靠落本地分录和待同步任务,外部调用使用稳定请求号,超时先查询。指标不仅看消费成功率,还看重复命中、唯一冲突字段不一致、最老未记账支付、借贷不平和死信。恢复或人工重放必须复用原事件号,先小批验证金额守恒。最终以同一支付单的渠道金额、支付状态、账务分录和订单金额核对,证明业务效果只有一次,并检查借贷方向与会计日期没有因迟到消息错位。还要检查借贷方向和会计日期未因迟到消息错位,并让重复投递次数与账务幂等命中指标能够互相核对。
追问 1:为什么唯一键要包含消费职责? 直接回答:账务、订单和通知都应各处理一次;若共享一个全局事件去重键,先处理的职责会错误阻断其他职责。
追问 2:唯一冲突是否一律返回成功? 直接回答:不是。要核对既有金额、币种、对象和动作;完全一致才是重复,不一致说明事件号被错误复用,必须隔离告警。
追问 3:缓存去重能否提高性能? 直接回答:可作为前置快速过滤,但最终账务正确性必须由持久唯一约束裁决,缓存过期不能导致重复分录。
- 问题(支付):退款请求超时,如何处理“渠道可能已经退款”的未知结果?
口述答案:我的结论是,退款超时不能直接判断失败,也不能换新请求号盲目重试,因为渠道可能已经把钱退回,只是响应在网络中丢失。创建退款时先生成稳定退款单号,保存原支付单、金额、币种、原因和当前状态;调用支持幂等的渠道时把退款单号作为请求号。响应明确成功就把退款从处理中推进为已退款并写唯一退款账务流水;明确业务失败按错误分类终止或修正;超时则进入结果未知,保持原请求号主动查单。查到已退款就补本地状态和账务,查到明确未执行才在退避与次数预算内重试,持续未知超过期限进入人工核验。渠道不支持幂等号时,同一退款单必须串行,超时阶段只查询不自动重发;仍无法确认就等渠道账单或人工处理。退款消息重投由退款单号加动作和消费职责去重,不能用订单号,因为一个订单可能有多次部分退款。还要处理状态冲突:订单关闭后迟到支付成功可能触发退款,但若订单已经履约,就不能按普通规则自动退款,需要业务审核。监控结果未知数量、最老未知年龄、查单成功率、重复退款命中、渠道错误码和退款对账差异。渠道故障时熔断新自动退款,保留请求记录并向用户展示处理中,不能显示失败诱导重复申请。最终逐退款单比较渠道退款流水、本地退款状态、账务冲正和订单退款金额,金额闭合后才完成;人工裁决也必须记录证据来源、审批人和再次核对时间。任何人工退款都要保存渠道证据、金额上限、双人审批和再次对账时间,并在次日账单中确认资金已闭合。
追问 1:超时后为什么不能立即撤销本地退款单? 直接回答:渠道可能已成功,撤销会让本地允许再次发起退款,增加重复退款风险;应保留结果未知状态直到查证。
追问 2:查单也超时怎么办? 直接回答:继续有预算退避,超过时限转人工并等待渠道账单;资金副作用不能在无证据时自动猜测。
追问 3:用户重复提交退款申请如何处理? 直接回答:业务层先按原退款意图返回已有退款单;合法的第二次部分退款必须创建新退款单并校验剩余可退金额。
- 问题(支付):如何设计支付四方对账和自动补偿边界?
口述答案:我把支付对账分成日内增量和日终全量两层,比较的四方是渠道流水、内部支付单、账务流水和订单状态。先逐交易使用商户号、渠道交易号、支付单号、金额和币种匹配,再按商户、币种和日期汇总;不能只比总额,因为一多一少可能互相抵消。差异至少分为渠道有本地无、本地成功账务无、账务重复、金额币种不一致、订单关闭后支付和退款状态不一致。自动补偿必须满足证据充分、动作可幂等、风险可逆且金额在授权阈值内。例如渠道账单签名可信、订单唯一且金额一致时,可以补建支付成功事实和发件事件;支付单与渠道一致但账务遗漏时,可以按原支付事件号补记;涉及币种、汇率、大额、已结算账务或订单已履约时转人工。所有补偿先生成差异单,保存四方快照、原因、建议动作和审批信息;执行使用稳定补偿号,写显式补记或冲正流水,不能直接覆盖原记录。补偿后再次对账,直到差异关闭。假设账务积压 900,000 条,安全处理能力 3,000 条/秒、正常新增 1,000 条/秒,10 分钟恢复所需总吞吐 2,500 条/秒可行,5 分钟需要 4,000 条/秒则越过安全水位,必须调整目标。指标包括差异笔数与金额、最老差异、自动补偿成功率、人工超龄和再次对账失败。权限上补记、冲正和退款分级,大额双人审批,所有操作不可抵赖审计。最终目标是金额守恒、每笔可追溯,而不是让消息和表记录数量看起来相等,并能按对账批次完整复算。
追问 1:渠道有、本地无是否总能自动补单? 直接回答:不能。必须能唯一匹配订单、验证渠道证据且金额币种一致;无法匹配或订单已履约冲突时人工处理。
追问 2:为什么补偿也需要幂等键? 直接回答:补偿任务本身会重试和重放;稳定补偿号可保证补记、冲正或退款只产生一个有效业务效果。
追问 3:四方一致后还保留差异证据吗? 直接回答:保留。资金审计需要原差异、审批、执行前后状态和再次对账结果,不能因已修复而删除原因链。
- 问题(跨境轨迹):请设计一个能处理重复、乱序和缺口的跨境物流轨迹系统。
口述答案:我的结论是,跨境轨迹不能把“最后收到的事件”直接覆盖当前状态,而要分离不可变原始证据与可重算聚合结果。接入层对承运商回调或主动拉取做鉴权、格式校验和时区归一,原始报文、接收时间、来源事件号与摘要先写原始轨迹表;它是审计权威。幂等键优先使用承运商、运单号和来源事件号,没有稳定事件号时组合来源事件码、可信发生时间、地点和报文摘要,但要监控碰撞。归一化层把各承运商代码映射为统一里程碑,保留原码和映射版本。聚合层优先按来源序号条件推进;没有序号时使用可信发生时间与“揽收、出境、到港、清关、派送、签收”的业务偏序,无法证明顺序的冲突并存并转人工,不能伪造全序。当前版本 41 先收到 43 时,可以把聚合推进到 43,同时登记版本 42 缺口并按渠道配额补拉;20 秒后 42 到达,只补明细,不把当前状态倒退。成功保存原始记录与待发布事件同事务提交,下游按运单和版本幂等。指标包括重复率、乱序率、版本缺口、映射失败、每承运商最老同步年龄和最终状态差异。第三方故障时按渠道隔离,展示最后可信状态、更新时间和待同步标识,其他渠道继续。恢复后补拉缺口并逐运单核对原始明细、聚合版本、客户展示和签收事实,最终状态单调且过程证据完整才算闭环;规则映射升级还要用历史样本回放,确认没有批量误推进。归一化规则升级前使用历史样本回放,重点观察清关、签收和异常节点,确认不会批量误推进客户状态。
追问 1:为何原始轨迹表不能被归一化结果替代? 直接回答:映射规则会变化且可能出错,原文是追责、重算和对接承运商的证据;聚合结果可以重建,原始证据不可凭计算恢复。
追问 2:接收时间能否作为最终排序依据? 直接回答:不能。网络重试和多级转发会改变到达顺序;应优先来源序号或可信发生时间,再结合业务偏序。
追问 3:版本缺口一定阻塞客户展示吗? 直接回答:不一定。更高版本可信时可展示并标记待补全,但涉及异常原因或合规节点时应提示数据不完整并优先补拉。
- 问题(跨境轨迹):承运商没有稳定事件号时,轨迹幂等键如何设计?
口述答案:我会先推动对方提供稳定来源标识,因为它是最可靠的幂等依据;实在没有时才使用可解释的复合指纹,并明确它不是数学上绝对唯一。复合键可包含承运商、运单号、原始事件码、可信发生时间、地点代码、运输节点和规范化报文摘要。规范化要去掉每次请求都会变化的接收时间、签名和无业务意义字段,否则同一事件重试会生成不同键;同时不能只用运单号加状态码,因为同一运单可能多次进入异常、派送和恢复。数据库对该指纹建立唯一约束,冲突时读取既有原始记录:核心字段一致视为重复并返回原结果;同指纹却金额、地点或原文摘要不同则进入碰撞隔离,不可静默覆盖。若对方后续补充稳定事件号,保存“来源号到内部事件号”映射,不重写历史主键。幂等记录与原始报文、归一化结果和待发送事件在同一本地事务提交,避免已去重但明细没保存。保留期至少覆盖承运商最大重试、历史补拉和人工重放窗口;签收、清关等关键证据按审计要求长期归档。监控按承运商统计重复命中、指纹碰撞、同一发生时间多事件和键字段缺失,异常升高通常说明接口版本变化。降级时缺少关键字段的报文只保存原文并进入待映射,不推进聚合。最终用抽样重放验证同一原文多次接入只生成一条内部事件,而合法重复业务节点仍能建立不同记录;碰撞样本还要反向推动承运商补充稳定来源标识。碰撞样本按承运商统计并回传接口治理,推动对方补充稳定来源号,逐步降低复合指纹的误判风险。
追问 1:可以直接用原始报文摘要做幂等键吗? 直接回答:不稳妥。签名、字段顺序或无关时间变化都会改变摘要;应先规范化,并把业务字段作为可查询的键组成部分。
追问 2:指纹碰撞如何处置? 直接回答:保留两份原文并隔离,停止自动聚合,按运单和承运商人工核验;不能让后一条覆盖前一条。
追问 3:缓存去重是否适合高吞吐接入? 直接回答:可做短期快速过滤,但持久原始表的唯一约束仍是最终裁决;缓存过期后重放也必须安全。
- 问题(跨境轨迹):轨迹版本 43 先于 42 到达时,聚合状态和过程明细分别如何处理?
口述答案:我会把“当前状态是否可以前进”和“过程证据是否完整”分成两个判断。原始层对 43 和 42 都按来源事件号去重后保存,不因为迟到丢弃 42。聚合层读取当前版本,例如当前为 41,43 携带可信来源序号且映射合法,就用“当前版本小于 43”的条件更新推进到 43,同时写版本缺口 42、聚合规则版本和待补拉任务。后到的 42 保存后发现当前已是 43,不再覆盖当前状态,但可以关闭缺口、补充节点时效和原始证据。若 43 不是单调里程碑,例如它表示“异常待处理”,而 42 包含影响最终解释的清关失败原因,客户展示可以维持 43,但要标记过程待补全。没有来源序号时不能机械比较时间:需要结合发生时间可信度、承运商规则和业务偏序;“已签收”通常不能被迟到“运输中”倒退,但“签收撤销”是新的显式事件,应以更高业务版本推进,而不是修改旧事件。更新聚合、保存处理记录和发布变化事件应同事务;下游投影再按运单与聚合版本条件更新。指标监控版本跳跃、迟到深度、缺口年龄和状态倒退拒绝。第三方限速时缺口任务使用独立低优先级配额,不抢实时回调。最终按运单检查缺口是否关闭、当前版本是否最高可信版本、过程节点是否满足业务要求,并保留所有被拒绝倒退的证据;客服查询还应展示数据来源与最后核对时间。客服查询同时展示轨迹来源、业务发生时间和最后核对时间,让缺口期间的状态可信度对用户透明。
追问 1:版本号越大是否一定代表业务状态越新? 直接回答:仅在来源明确承诺单调序号时成立;否则版本可能是本地接收号,必须结合发生时间和业务偏序判断。
追问 2:迟到事件是否还要继续发布? 直接回答:若它补充了过程明细,可发布“明细补全”事件;不能伪装成当前状态回退,事件类型与聚合版本要明确。
追问 3:签收后收到异常事件怎么办? 直接回答:先识别它是签收前迟到异常还是签收撤销等新事实;前者只补明细,后者需要承运商可验证的新版本和显式状态迁移。
- 问题(跨境轨迹):第三方承运商限速且持续失败,如何避免补拉和重试拖垮全局?
口述答案:我的核心做法是按承运商建立独立的资源舱壁,把实时流、补拉流和失败重试都纳入明确配额。每个承运商有自己的队列或逻辑分区、线程池、并发上限、每秒令牌、超时和熔断器,任何一个渠道变慢都不能占满全局消费者。假设某渠道限制 500 次/秒,正常实时查询 300 次/秒,则历史缺口最多使用剩余 200 次/秒;20,000 个缺口理论至少 100 秒,增加线程不会突破配额,只会制造超时。错误分类上,鉴权失败、参数不合法和接口下线属于永久或需人工修复错误,直接隔离并告警;网络抖动和限流响应使用指数退避与随机抖动,设置最大次数和总时长。持续失败达到阈值后熔断,半开阶段只放少量探测;实时回调或高价值签收查询保留份额,历史补拉不能吃满。所有重试复用运单与查询窗口的稳定任务号,避免产生重复缺口任务。下游展示最后可信轨迹、更新时间和同步中标识,不编造最新状态。恢复时先确认鉴权、延迟和错误率,再按 50、100、200 次/秒阶梯放量,实时流优先,补拉按缺口年龄和业务优先级排序。指标包括每渠道成功率、分位延迟、令牌等待、熔断状态、最老缺口、重试放大和其他渠道是否受影响。最终逐运单验证缺口、聚合版本和客户展示收敛,不能只看第三方请求成功数;渠道恢复报告还要记录实际配额与超限响应证据。渠道恢复报告记录实际配额、超限响应和半开探测结果,使后续调整并发时有证据而不是继续凭经验猜测。
追问 1:限流响应是否可以立即重试? 直接回答:不应立即重试。遵守对方建议等待时间或本地退避并加随机抖动,否则同步重试会持续冲击配额。
追问 2:为什么实时流与补拉流要分配份额? 直接回答:若历史缺口占满配额,新运单会继续产生缺口;保留实时份额才能阻止故障面扩大。
追问 3:渠道长期不可用如何向用户降级? 直接回答:展示最后可信状态与更新时间,明确标记待同步;关键签收或清关可转人工查询,不能把未知展示成正常。
- 问题(跨境轨迹):轨迹消息严重积压时,如何恢复且不让状态倒退?
口述答案:我先按承运商、运单热度、分区和事件版本拆解积压,查看生产速率、真实聚合成功速率、最老消息、重试与死信,判断是渠道突增、映射错误、热点运单还是下游数据库变慢。止血时冻结有问题的映射发布和无界重试,故障承运商隔离;保留签收、清关异常等关键里程碑,暂停低价值统计与可重建投影。不能通过随机改变运单路由来扩并发,同一运单需要按键有序或至少由版本条件裁决;扩分区前后还要有路由版本,排空旧路由或依赖聚合版本拒绝倒退。恢复消费时,每条旧事件先落原始明细,再比较当前聚合版本:低版本只补过程证据,不覆盖较新状态;高版本按条件推进;版本跳跃创建缺口任务但不阻塞所有后续运单。永久映射失败进入隔离队列,修复映射后使用原事件号重放,不能生成新来源事实。第三方补拉严格遵守渠道配额,历史流与实时流分份额。假设积压 120 万条,稳定成功处理 8,000 条/秒、实时新增 2,000 条/秒,净消化 6,000 条/秒,理论 200 秒;若数据库在 7,000 条/秒出现拐点,就必须以实际安全成功率重算。监控状态倒退拒绝、版本缺口、最老运单未更新和客户展示延迟。基础积压回落后,抽样和全量对账原始最高版本、聚合版本、下游通知与签收事实,确保没有因提前确认或死信产生静默缺失;再按承运商复盘热点分布与路由偏斜,防止积压复发。最后按承运商复盘热点分布、路由偏斜和映射失败,确认本次恢复动作没有把同类积压转移到其他分区。
追问 1:增加消费者为何可能无效? 直接回答:分区数、单运单顺序和数据库水位都会限制有效并发;超过拐点只会增加锁、超时与重试。
追问 2:死信中的旧轨迹可以直接重放吗? 直接回答:先修复原因并确认事件仍有价值,复用原事件号小批重放;聚合层按当前版本裁决,避免旧状态倒退。
追问 3:何时算恢复完成? 直接回答:积压和最老年龄达标之外,还要版本缺口在目标内、状态倒退为零、关键运单最终状态与承运商证据一致。
- 问题(执行器):请设计一个支持故障接管且不重复提交结果的任务调度系统。
口述答案:我的结论是,调度系统要接受消息重复投递和任务重复尝试,但必须保证只有当前执行代次能够产生有效提交。权威数据源是任务表、执行代次和结果表;调度器在本地事务创建任务与待发送事件,MQ(消息队列)只负责唤醒。执行器收到任务后用条件更新领取:任务处于待执行、待重试或租约已过期时,写持有者、租约截止时间并递增围栏令牌;多个实例竞争只有一个成功。长任务定期续租,续租必须匹配任务号、持有者和令牌。结果提交以“状态仍为执行中且当前令牌等于提交令牌”为条件,并用任务号加执行代次建立结果唯一键。旧实例发生长暂停时,新实例可在租约到期后取得更大令牌;旧实例恢复即使算完,也无法覆盖新结果。外部副作用使用稳定任务请求号,对方支持时同时传递围栏令牌;响应超时先查结果,不生成新请求号盲重试。失败按可恢复、永久和结果未知分类,可恢复错误退避重试并创建新执行代次,参数错误进入失败终态,未知进入查证或人工。指标包括待执行最老年龄、领取冲突、续租失败、过期接管、旧令牌提交、重复结果命中和任务实际成功率。调度过载时暂停报表等低优先级任务,为库存补偿和支付对账保留配额。最终通过暂停进程、网络隔离和确认丢失演练,验证重复尝试存在但一个代次只有一个有效结果,任务状态、结果和外部副作用可追溯;每次演练还记录接管耗时和重复计算成本。每次演练记录接管耗时、重复计算成本和旧令牌拒绝数量,并与任务服务目标逐项核对后再调整参数。
追问 1:消息是否需要严格只投递一次? 直接回答:不需要也不应依赖。至少一次投递配合任务领取条件、结果唯一键和围栏令牌,可以把重复转成安全且可观测的竞争。
追问 2:为什么结果表还要唯一键? 直接回答:围栏限制所有权,唯一键继续防住同一有效执行因事务重试或确认丢失产生重复结果,两层关注点不同。
追问 3:调度器宕机后任务如何恢复? 直接回答:任务和发件记录已在权威库中,扫描器补发待执行事件;执行中任务由租约过期接管,不依赖原调度进程内存。
- 问题(执行器):如何确定租约、心跳和接管时间,避免误接管与任务卡死?
口述答案:租约参数不能拍脑袋统一配置,我会先按任务类型采集执行耗时、心跳延迟、进程暂停、网络抖动和数据库分位延迟,再在“误接管概率”和“故障恢复时间”之间取平衡。心跳间隔要显著小于租约,例如租约 90 秒、每 20 秒续租,可容忍若干次瞬态失败;但真正边界由目标接管时间和历史暂停决定。任务正常执行 50 秒,执行器在第 35 秒暂停 80 秒时,旧租约会过期,新实例获得更高令牌接管;旧实例恢复后必须发现续租失败并停止新副作用,最终提交也由围栏拒绝。续租使用权威存储的时间或统一过期判断,不能让各实例按本地时钟自行裁决。长短任务差异大时分任务类型设置租约,或使用短租约加可靠心跳;不能为极端长任务设置数小时租约,导致普通故障也数小时无法接管。心跳写入应节流和分片,避免大量任务同时续租形成数据库热点;一次续租失败先进入警戒,连续失败且接近截止时间时停止拉取新工作并尝试安全中断。任务若不可中断,外部提交仍必须携带幂等号和围栏。监控租约剩余量、续租分位延迟、过期率、误接管率、平均接管时间和旧令牌拒绝。降级时数据库延迟升高可暂停新任务并适度调整续租预算,但不能关闭围栏。最终用故障注入测得接管时间、重复计算成本和有效结果唯一性,再据此修正参数,并分别为短任务和长任务建立不同基线。短任务和长任务分别建立基线,定期回看误接管比例与真实故障接管时长,防止统一参数长期失真。
追问 1:心跳越频繁越好吗? 直接回答:不是。过频会增加数据库写压力和热点,反而导致续租失败;只需在租约预算内提供足够故障容忍。
追问 2:执行器发现续租失败后应立即杀进程吗? 直接回答:先停止产生新副作用并中断可取消任务;是否退出进程取决于失败范围,但最终提交必须被围栏控制。
追问 3:系统时间回拨会怎样? 直接回答:若客户端自行判断会误判,因此过期判断集中在权威存储;围栏令牌依赖单调代次而非墙上时钟。
- 问题(执行器):任务已执行成功但消息确认丢失,如何处理重复执行?
口述答案:这是至少一次调度最典型的失败窗口,我会把“执行尝试”和“有效业务结果”分开建模。执行器领取后获得任务号、执行代次和围栏令牌,先在本地结果事务中按任务号加代次插入唯一结果,校验当前令牌后推进任务成功,再确认消息。若业务事务已提交但确认丢失,MQ(消息队列)会再次投递;新的消费者读取任务已成功和既有结果,参数摘要一致就幂等返回并确认,不重新执行。若第一次执行包含外部调用,不能只靠本地结果唯一:调用前为任务生成稳定外部请求号,对方支持幂等时重复调用返回同一结果;响应超时先按该号查询。对方不支持幂等时,需要把任务按业务键串行,并将结果未知转人工,不能自动重复资金或设备控制副作用。若旧执行器在租约过期后晚到提交,它即使持有计算结果,也因令牌落后被拒绝;系统可以保留其尝试日志,但不能更新权威结果。重复消息与新一代重试要区分:同代次重投读取原结果,可恢复失败创建新代次但仍关联原任务,业务幂等号是否变化取决于业务意图是否变化。指标看重复投递、既有结果命中、外部查询、旧令牌拒绝和同任务多结果冲突。故障演练应在业务提交后、确认前强制终止进程,验证重投不会产生第二条业务流水。最终按任务、代次、结果和外部回执核对,而不是假设消费者日志只出现一次;日志里多次尝试必须能聚合到同一业务意图。日志中的多次尝试必须聚合到同一业务意图,并能解释哪次获得有效提交权、其余尝试为何被拒绝。
追问 1:重复计算完全可以避免吗? 直接回答:不一定。租约接管前后可能重复计算,但通过围栏和幂等可以避免重复有效副作用;是否优化计算浪费取决于成本。
追问 2:任务成功后再次收到不同参数怎么办? 直接回答:不能当重复吞掉。稳定任务号对应的参数摘要应一致,不一致说明键污染或上游错误,必须隔离告警。
追问 3:确认消息应放在事务内吗? 直接回答:通常消息确认与业务库不在同一事务域,所以业务先提交再确认,并依赖幂等吸收确认丢失造成的重投。
- 问题(执行器):执行器调用外部系统超时,围栏令牌和幂等请求号如何分工?
口述答案:围栏令牌与幂等请求号解决不同问题:围栏判断“当前哪个执行代次有资格提交”,幂等请求号判断“同一业务意图是否已经在外部执行”。执行器领取任务获得令牌 18,调用外部系统时传任务业务号作为稳定请求号;若外部接口支持围栏,还同时传 18 并要求它拒绝小于当前代次的写入。调用响应超时后,本地不能断言失败,应把任务子状态置为结果未知,停止用新请求号重试,先按原号查询。查到成功就用当前令牌补提交结果,查到明确未执行才在预算内重试;持续未知转人工。若在查证期间租约过期,新执行器取得令牌 19,它仍使用同一业务幂等号查询或继续原意图,防止外部重复;旧执行器令牌 18 即使晚到,也不能提交本地任务结果。仅有围栏而外部不校验时,旧实例仍可能造成外部副作用;仅有幂等号而没有围栏时,旧实例可能覆盖本地新结果。因此对不可幂等的资金、库存或设备操作要提高门槛:串行、查询优先、状态机限制、人工边界和对账同时存在。监控外部超时、结果未知年龄、按号查询成功率、重复命中、过期令牌外调和人工比例。降级时熔断故障外部依赖,任务保持可查询的处理中或待核验,不把未知展示成失败诱导用户重复提交。最终以外部回执、本地结果、当前令牌和业务流水核对同一意图只有一个有效效果;人工完成的结果也要回写同一证据链。人工查证完成后也要把外部回执、操作人和裁决时间回写同一证据链,避免形成系统之外的隐形结果。
追问 1:每次重试生成新请求号有什么风险? 直接回答:外部会把同一任务当成多个合法意图,绕过幂等,可能重复扣款、发货或控制设备。
追问 2:外部系统不支持查询怎么办? 直接回答:降低自动化重试,串行化同键调用,持续未知转人工并依赖对方账单或回执;不能无证据猜测。
追问 3:新执行代次是否总要换外部请求号? 直接回答:如果业务意图未变就不换,只增加内部执行代次用于审计;只有明确创建了新的业务动作才生成新号。
- 问题(执行器):调度任务大面积积压时,如何按优先级恢复而不压垮下游?
口述答案:我先把任务按业务价值、过期性、资源类型和失败原因分类,而不是统一增加执行器。支付对账、库存释放、严重告警补偿属于高优先级且有时限;报表刷新、历史归档和可重建投影可以延迟或取消。排障查看每类任务的新增速率、真实成功速率、最老年龄、平均执行时间、租约过期、重试放大,以及数据库、外部接口和对象存储水位。假设积压 600,000 个任务,稳定成功能力 4,000 个/秒、实时新增 1,000 个/秒,净消化 3,000 个/秒,理论 200 秒;如果扩容后下游超过安全水位,超时让 20% 任务重试,真实净速度可能更低。止血先暂停低优先级新调度和即时重试,为关键任务预留线程、连接与第三方配额;按业务键保持必要顺序,永久参数错误直接隔离。恢复使用加权公平队列,高优先级有保证但不能让超龄普通任务永久饥饿;实时流与历史积压分份额。执行器按小步增加,每一步观察成功吞吐、下游分位延迟、错误和租约稳定,超过拐点立即回退。旧任务执行前检查截止时间和当前业务状态,已过期报表可取消,库存释放或支付补偿必须继续按状态机裁决。所有重放复用任务号和业务幂等号,结果表与围栏防重复。最终不仅看待执行数量,还看关键任务时限、最老年龄、外部水位、重复有效结果为零和业务差异闭合。长期将优先级、过期策略和资源预算固化并演练,复盘时分别报告各优先级的服务目标达成率。复盘时分别报告各优先级服务目标达成率、饥饿任务数量和取消依据,证明恢复没有牺牲关键补偿任务。
追问 1:优先级队列是否会让低优先级永远执行不到? 直接回答:会有饥饿风险,所以使用年龄提升、最低服务份额和租户配额,而不是绝对优先级永久抢占。
追问 2:过期任务可以直接删除吗? 直接回答:只有明确可重建且无副作用的任务可进入取消终态;支付、库存等任务必须由业务状态机判断并保留审计。
追问 3:扩执行器前最重要的证据是什么? 直接回答:下游在当前并发下的成功吞吐与分位延迟曲线,确认尚未达到数据库、连接或外部配额拐点。
- 问题(物联网报警):请设计一个能治理报警风暴且不漏严重告警的系统。
口述答案:我的结论是,报警风暴治理不能靠粗暴丢消息,而要把原始遥测、告警实例和通知工单分层,并按业务优先级做语义聚合。接入层以设备号、指标和设备序号去重、校正可疑时间,原始遥测是权威证据;规则层把正常到触发创建为一个告警实例,幂等键包含设备、规则和首次触发版本,重复上报只更新该实例的最高级别、首次时间、计数、最后时间,恢复事件关闭实例,再次触发必须创建新实例。普通告警进入 60 秒窗口聚合,保留首次、峰值、次数、末次与恢复;严重安全告警不等待窗口,进入独立高优先级通道,消费者线程、数据库连接和通知渠道都预留配额。假设 100,000 台设备每 5 秒上报一次,入口 20,000 条/秒,普通消费者只有 8,000 条/秒,10 分钟会新增 720 万条;窗口把同设备同故障 12 条聚为 1 条后,普通流约降到 1,667 条/秒,同时为严重告警预留 2,000 条/秒。下游变慢时背压逐层传回:先停调试日志和可重建投影,再扩大普通窗口、限制重复通知,最后采样非关键遥测;严重、首次触发、级别升级和恢复不可采样。指标包括严重告警端到端时延、实例超龄、聚合压缩率、通知回执、降级丢弃分类和设备版本缺口。恢复后按告警实例合并历史重复,优先严重与恢复事件,核对原始遥测、实例状态、通知和工单,证明严重告警时效与最终状态都正确;演练报告还要证明普通流满载时严重链路仍达成时限。
追问 1:窗口聚合是否会掩盖故障持续时间? 直接回答:不会,只要保留首次、最后、计数和恢复时间;仅保留最后一条才会丢持续时长与峰值。
追问 2:为何严重告警也需要幂等? 直接回答:穿透不等于重复轰炸,同一实例的重复严重上报应快速到达但只产生受控升级和通知。
追问 3:所有规则都标严重怎么办? 直接回答:监控高优先级占比并对规则变更审批;超过预算时升级人工值守和容量,不能让高优先级退化成无意义的普通队列。
- 问题(物联网报警):告警实例的幂等键和状态机应如何设计?
口述答案:我会把一次“正常到触发,再到恢复”的生命周期定义为一个告警实例,不能永久使用设备号加规则号去重,否则设备恢复后第二次真实故障会被吞掉。原始遥测先按设备号、指标和设备序号去重;规则首次从正常进入待确认时生成候选版本,连续窗口满足条件后创建实例号,幂等键可由设备、规则、首次触发序号和规则版本组成。状态机从正常进入待确认,抖动消失回正常;满足持续阈值进入告警中,人员确认进入已确认,恢复窗口满足后进入已恢复;已恢复后再次越阈生成新实例,而不是重开旧实例。重复上报只更新当前实例的计数、最高级别和最后时间,使用设备序号或状态版本条件防止迟到故障覆盖较新的恢复。规则版本必须记录,因为阈值变化后同一数据可能得到不同结论;重算历史时生成重算记录,不篡改原实例。通知使用实例号加通知类型和升级级别作为幂等键,允许首次、升级、恢复分别通知一次。若恢复事件先到、迟到故障后到,原始证据都保存,但当前设备版本不倒退;若设备序号重置,先识别重启代次再比较。指标包括重复命中、非法迁移、迟到深度、实例重开、长期未恢复和规则版本分布。降级时可以延迟普通实例通知,不能跳过状态更新与恢复。最终通过同一故障重复 100 次、恢复后再次触发和乱序回放,验证只生成两个合法实例且每个状态单调闭合;再抽查升级通知次数与实例级别变化完全对应。再抽查升级通知次数与实例级别变化完全对应,并验证恢复后的第二次故障确实生成新的告警实例。
追问 1:告警确认和告警恢复是一回事吗? 直接回答:不是。确认表示人员已知晓,恢复表示设备指标回到规则定义的正常区间;已确认仍可能持续故障。
追问 2:规则阈值修改后旧实例怎么办? 直接回答:旧实例保留原规则版本和结论;新数据使用新版本,必要的历史重算单独记录,不能无痕改写审计事实。
追问 3:设备序号回绕如何处理? 直接回答:结合设备启动代次、网关会话或时间窗口识别新序列,不能简单把较小序号永久视为旧消息。
- 问题(物联网报警):下游通知渠道变慢时,背压如何逐层传递并保证严重告警穿透?
口述答案:我不会让通知调用阻塞规则消费者,而是先可靠提交告警实例与通知子任务,再把渠道水位反向传给通知、聚合、规则和接入层。通知层每渠道使用有界队列、并发上限、超时、熔断和稳定通知幂等键;渠道失败只改变通知子状态,告警事实仍可查询。资源按优先级预留,例如渠道总配额 1,000 次/秒,为严重告警锁定 300 次/秒,普通流最多 700 次/秒;风暴中严重流 200 次/秒、普通原始需求 4,000 次/秒,通过 60 秒窗口把普通通知压到 500 次/秒,总量 700 次/秒并保留余量。若普通流仍过载,先合并同实例重复通知,延迟普通摘要和暂停调试日志;规则层可停低价值规则,接入层只采样非关键遥测。严重告警、首次触发、级别升级和恢复事件走独立主题、消费者、连接池与备用渠道,不能仅在同一队列里设置一个字段却共享全部瓶颈。重试使用退避和随机抖动,历史失败与实时严重流分份额,避免恢复时回灌冲击。监控从渠道未确认、队列水位、最老通知、熔断状态一直到严重告警端到端时延;任何降级丢弃都按类型计数,严重丢弃必须为零。恢复先半开探测,再阶梯放量,普通历史按告警实例和当前状态合并,不逐条轰炸用户。最终核对实例、通知回执、升级链和工单,确保背压只降低低价值频率,没有改变安全事实;备用渠道也要定期演练而非事故时首次启用。备用通知渠道必须定期演练容量、权限和回执链路,不能等主渠道故障时才第一次验证是否真正可用。
追问 1:降低预取数量能否解决全部背压问题? 直接回答:不能。它只能限制消费者在途消息,还要限制入口、线程池、重试和外部调用,并实施业务优先级降级。
追问 2:严重与普通共用数据库是否仍可能互相影响? 直接回答:会,所以除了队列隔离,还要预留连接、写入配额和索引容量;必要时将严重链路的关键存储隔离。
追问 3:通知渠道熔断后严重告警怎么办? 直接回答:切备用渠道、升级值班工单或电话链,并持续保存待通知状态;不能因主渠道故障把告警直接标已送达。
- 问题(物联网报警):报警风暴结束后,历史消息如何裁剪、重放和验收?
口述答案:恢复阶段不能把所有历史原始告警逐条重新通知,因为大量重复已经过期,会造成二次风暴并误导值班人员。我先冻结自动全量回灌,按设备、规则、告警实例、严重级别和当前状态聚类,保留不可丢的首次触发、最高级别、升级、恢复、人工确认和工单关联事件;同一实例的普通重复压缩成包含次数、峰值和持续时间的摘要。举例:有 180,000 条待通知,其中 150,000 条属于已恢复普通实例,可合并成 5,000 条摘要;剩余 30,000 条按严重程度和年龄排序。重放复用原实例号和通知幂等键,另加重放批次号用于审计,不能把同一告警伪造成新实例。开始前确认通知根因已修复、状态机与版本条件能拒绝迟到倒退,先取代表样本验证,再按 300、500、700 次/秒阶梯放量,每档观察渠道延迟、失败回流、实时严重时延和工单创建。实时严重流始终有独立配额,历史流不得抢占。对已恢复告警,通知内容明确是历史摘要,不发送“当前仍故障”的误导文案。原始遥测根据合规和容量策略归档,不因通知裁剪而删除审计证据。指标看重放成功、重复命中、过期裁剪、再次失败、严重送达和降级丢弃分类。最终逐实例核对当前状态、关键通知回执、工单与恢复事实,确认严重和状态迁移零遗漏,普通压缩数量可解释,才解除风暴状态;重放批次、操作人和速率变化必须完整留痕。重放批次、操作人、速率变化和暂停原因全部留痕,使任何一条历史通知都能解释为何发送或裁剪。
追问 1:重放时为什么要保留原幂等键? 直接回答:业务意图没有变化,原键可阻止重复通知和重复工单;重放批次号只用于操作审计,不替代业务键。
追问 2:已经恢复的严重告警还要通知吗? 直接回答:要按安全策略补达首次、峰值和恢复摘要,并标明历史时间;不能因当前恢复就抹去未送达的严重事件。
追问 3:如何防止历史重放影响实时告警? 直接回答:独立队列或配额、阶梯限速和实时优先,任何实时严重时延越界立即暂停历史流。
- 问题(物联网报警):如何证明报警系统的降级没有丢失安全事实?
口述答案:我会在设计阶段先建立事件价值矩阵和可验证不变量,明确严重告警、首次触发、级别升级、恢复和人工确认属于安全事实,任何降级路径的丢弃数必须为零;可重建状态遥测允许按设备和指标保留最新版本,调试日志可限量丢弃,但都要计数。每层记录输入、输出、聚合和丢弃原因:接入层按设备序号发现缺口,规则层保存使用的规则版本与窗口证据,告警实例保存首次、峰值、次数、末次和恢复,通知层保存渠道请求号、回执与升级结果。严重链路端到端使用同一事件号和实例号串联,目标例如 99% 在 5 秒内进入处置;普通流拥塞不能共享完所有线程、连接和通知配额。故障演练用 100,000 台设备每 5 秒上报的风暴流量,同时注入通知渠道变慢、消息重复、恢复先到和进程重启,核对严重事件生产数、实例数、送达或人工接管数闭合,状态版本不倒退。降级期间采样的非关键遥测要能由设备最终状态或后续全量同步重建,并记录采样比例;若设备序号出现严重缺口,主动补拉或现场核验。恢复后比较原始遥测、告警实例、通知回执和工单,抽样还原规则计算,检查已恢复实例没有被迟到故障重开。报表必须分别展示“聚合压缩”“允许采样”“异常丢失”,不能把三者混成一个减少量。只有严重事实零异常丢失、最终状态一致、所有降级数量可解释且审计链完整,才能证明降级符合设计;这一证明还要由独立对账任务定期复核。这套证明还要由独立对账任务定期复核,并对严重事实缺口立即升级,而不是依赖同一处理链自证正确。
追问 1:代理节点没有积压是否能证明没有丢告警? 直接回答:不能。提前确认、错误采样或死信都可能让主队列清空;必须核对业务实例、版本、通知和工单不变量。
追问 2:聚合压缩与丢失如何区分? 直接回答:聚合有明确输入集合、窗口键、输出摘要和计数,可追溯到实例;无法解释来源和规则的减少才是异常丢失。
追问 3:严重告警未送达但已建工单算成功吗? 直接回答:取决于处置策略。若工单已被值班人员在时限内确认可算人工接管,但仍要记录通知失败,不能伪报渠道送达。
复习核对表
- 能先指出六类案例各自的权威数据源,而不是先报 MQ(消息队列)产品名。
- 能明确同步正确性底线与异步传播边界,并说出一个错误方案反例。
- 能为任务、库存、支付、轨迹、执行代次和告警实例给出稳定幂等键。
- 能画出状态机并解释重复、乱序、结果未知和过期所有者四类失败窗口。
- 能给出自动重试、主动查询、补偿、人工介入与死信重放的边界。
- 能用具体数字计算峰值积压、净消化能力、恢复时间和下游安全水位。
- 能说明事故期保留哪些关键事实、暂停哪些可重建副作用,以及如何恢复。
- 能用业务不变量验收,而不是以消息确认或积压归零替代业务正确性。
9. 三至五分钟项目口述总纲
面试中先用一句话定调:“我把 MQ(消息队列)当作异步传递和削峰工具,不把它当成业务权威事实;端到端可靠性依靠本地事务、稳定幂等键、状态机、补偿和对账。”随后按六例展开:异步导出以任务表和对象元数据为权威,分片流式写文件,租约配围栏,OOM(内存溢出)时停领并从游标恢复;WMS(仓储管理系统)库存以条件预占和唯一流水防超卖,消息只传播已裁决事实;支付先验签核对金额币种,同事务写支付单与 Outbox(发件箱),账务幂等,退款未知先查单,最终四方对账;跨境轨迹保存原文,按来源序号或业务偏序单调推进,缺口按承运商配额补拉;Runner(执行器)允许重复尝试,但只有当前围栏令牌可提交,外部副作用复用稳定请求号;IoT(物联网)报警按实例窗口聚合,严重告警独立通道穿透,背压只降级可重建数据。最后统一收束为“消息成功不等于业务成功”,用最老未完成年龄、重复命中、状态冲突、补偿成功和业务差异指标验收,并补充一次具体故障演练或数据演绎作为证据。
