Runner(执行器)调度租约恢复与可观测性串讲
事实边界:简历材料可证明常驻任务承载订单、计费、轨迹与回调重试;是否已在生产使用 Lease(租约)、Fencing Token(栅栏令牌)及其真实参数仍需源码、表结构、配置和运行记录核对。本文未被直接证实的方案与数字统一标为 E3(演练设计)。

图解读:正式图把调度控制面、任务账本、当前执行者、接管执行者、外部业务系统与审计验收连接起来。正常路径由当前 Lease(租约)持有者执行并提交;失败路径在续租中断后冻结旧世代提交,新持有者先查业务结果再从可信检查点恢复。箭头的前提是资源端比较 Fencing Token(栅栏令牌),外部副作用另有稳定 Idempotency Key(幂等键);结论是“拿到执行权、获准提交、业务只生效一次”必须由三层控制分别保证。可编辑源见 runner-schedule-talk.puml。
1. 知识主线
1.1 任务注册、分片与权威事实
任务注册不是把方法名塞进定时器,而是把“谁请求、处理什么、何时可执行、如何分片、哪种结果算完成”固化成可审计合同。E1(直接证据)只支持 Runner(执行器)承载订单、计费、轨迹与回调重试;下面的注册表、分片键和状态字段属于 E3(演练设计)。权威事实应落在持久化任务账本:注册定义可变更但要带版本,运行实例只能引用确定版本;分片依据必须稳定且可重放,不能随执行实例数量变化而改变业务身份。
典型任务主键可由“租户、任务类型、业务对象、计划窗口”组成,分片键选择订单号、支付单号或轨迹单号等稳定对象。任务定义与任务实例分开:定义描述并发、超时、重试和隔离策略,实例保存业务幂等键、当前状态、尝试号、Lease(租约)世代、检查点与结果摘要。分片只缩小领取和恢复单元,不改变外部业务键;否则重分片会让同一计费或回调被当成新动作。
| 控制对象 | 权威事实 | 稳定身份 | 失败时的判定 |
|---|---|---|---|
| 任务定义 | 版本化注册记录 | 任务类型加定义版本 | 未发布版本不得生成实例 |
| 任务实例 | 状态、计划时间、参数摘要 | 租户加业务对象加计划窗口 | 同键同参数返回既有实例 |
| 分片 | 范围、游标、期望总数 | 任务实例加分片号 | 分片重跑不创建新业务意图 |
| 外部动作 | 订单、计费、轨迹或回调系统 | 稳定业务 Idempotency Key(幂等键) | 超时先按原键查询 |
| 审计记录 | 注册、领取、续租、提交与人工动作 | 任务实例加事件序号 | 日志缺口不得宣告恢复 |
flowchart LR
A["任务定义与版本"] --> B["生成任务实例"]
B --> C{"稳定分片规则"}
C --> D1["分片 0 与检查点"]
C --> D2["分片 1 与检查点"]
C --> D3["分片 N 与检查点"]
D1 --> E["领取与执行"]
D2 --> E
D3 --> E
E --> F{"业务结果可确认?"}
F -->|是| G["条件提交结果摘要"]
F -->|未知| H["保留未知态并按原键查证"]
H --> E
G --> I["任务账本与审计闭环"]图解读:节点从版本化定义生成实例,再按稳定规则拆成可独立恢复的分片;箭头前提是定义版本、参数摘要和业务键在重试期间不变。正常路径确认外部结果后条件提交,失败路径保留未知态并按原键查证,不能换键重做。图中多个分片只代表并行单元,不代表多个业务副作用;结论是任务账本保存调度事实,订单、计费或轨迹系统保存业务事实。
数据演绎 1:稳定分片为何不能等同于实例取模
E3(演练设计):假设窗口内有 12000 个订单,按 12 个逻辑分片,每片 1000 个;3 个执行实例各领取 4 片。执行中扩到 4 个实例,若按“订单号对实例数取模”,分母从 3 变 4,约有 12000×(1-1/4)=9000 个订单可能改变归属并被重新扫描。若逻辑分片号始终为 hash(订单号)%12,扩容只改变 12 个分片的领取者,订单业务身份不变。状态变化是执行归属迁移而非任务重建;观测信号是分片领取次数、重复命中数和外部幂等冲突;结论是逻辑分片与运行实例解耦。
热门面试题
- 问题:任务注册最少要保存哪些信息?
- 考点:定义与实例分离、权威事实、可恢复性。
- 回答思路:从稳定身份、执行策略、状态和审计四类信息回答。
- 详细答案:任务定义至少保存类型、版本、参数模式、计划策略、分片规则、并发上限、超时、重试分类和隔离组;任务实例保存稳定业务键、参数摘要、计划时间、状态、尝试号、Lease(租约)世代、检查点、结果摘要和审计时间。定义升级只影响明确选择的新实例,旧实例仍引用原版本,避免恢复时语义漂移。
- 进阶追问:为什么不能只保存一个可执行类名?
- 进阶回答:类名不能证明业务身份、输入版本、执行边界和历史策略,重启或升级后无法判断应继续、重做还是转人工。
- 问题:任务分片的底层原则是什么?
- 考点:稳定映射、扩缩容、重放边界。
- 回答思路:区分逻辑分片与 Worker(工作线程)实例归属。
- 详细答案:逻辑分片必须由稳定业务字段和固定分片空间决定,Worker(工作线程)只动态领取分片。这样扩缩容改变执行者,不改变业务对象所属分片;检查点、重放和审计都能以分片为边界。若直接按当前实例数取模,实例变化会大面积迁移对象并放大重复扫描。
- 进阶追问:分片越细是否越好?
- 进阶回答:不是。过细会增加账本、领取和检查点写放大;应按单片可重做成本、热点分布与恢复时间目标选择。
- 问题:Runner(执行器)调度中的权威事实在哪里?
- 考点:控制面与业务面分离、未知态。
- 回答思路:分别指出任务账本和外部业务系统的职责。
- 详细答案:任务账本权威记录注册版本、状态、Lease(租约)、检查点和提交历史;订单、计费、轨迹或支付系统权威记录业务副作用。任务显示执行中不代表外部未完成,任务显示超时也不代表外部失败。恢复必须同时读取两侧事实,以稳定业务键查证后再推进任务状态。
- 进阶追问:两侧事实冲突时信谁?
- 进阶回答:业务结果以业务系统及其账本为准,调度账本保留冲突并进入对账或人工裁决,不能用调度状态覆盖业务终态。
1.2 抢占、Lease(租约)与失租判定
抢占解决“多个执行者谁先获得有限期执行权”,Lease(租约)解决“持有权多久必须重新证明”。它不是永久锁,也不能让已经进入内核调用、远程请求或长暂停的旧执行者自动停止。可靠实现通常由同一权威存储原子比较任务状态、到期时间和世代:领取成功写入持有者、到期时间并递增世代;续租只能由当前持有者在当前世代内完成;提交前必须再次校验世代。
失租判定要尽量使用权威存储的时间或单调时间差,避免各实例墙上时钟漂移直接裁决。执行者本地发现续租失败、连续超时或剩余安全窗口不足时,应立即进入“禁止新副作用、允许收尾取证”的状态。调度端只能在 lease_until 明确过期后重新领取;对于长外部调用,新持有者也不能假设旧调用未成功,必须先查外部结果。Zookeeper(分布式协调服务)临时顺序节点、会话和选主边界可回链临时顺序节点与锁与注册发现和主节点选举。
| 阶段 | 原子条件 | 成功动作 | 失败动作 |
|---|---|---|---|
| 首次领取 | 待运行且无有效 Lease(租约) | 写持有者、到期时间、世代加一 | 读取当前持有者后退出 |
| 正常续租 | 持有者和世代均匹配且未终结 | 延长到期时间 | 禁止新增副作用 |
| 超时接管 | 到期时间早于权威当前时间 | 新持有者领取并递增世代 | 未过期不抢占 |
| 结果提交 | 世代匹配且状态允许 | 推进状态并写结果摘要 | 旧世代结果仅审计 |
| 主动释放 | 世代匹配且无在途提交 | 清除持有权或结束任务 | 不删除新持有者 Lease(租约) |
sequenceDiagram
participant A as 旧执行者
participant L as Lease(租约)账本
participant B as 新执行者
participant R as 业务资源
A->>L: 领取任务并获得世代 7
L-->>A: 返回到期时间
A->>R: 使用稳定业务键执行
A-xL: 暂停导致续租中断
B->>L: 到期后条件领取
L-->>B: 返回世代 8
B->>R: 按原业务键查询结果
alt 原动作已经成功
R-->>B: 返回既有结果
else 原动作未发生
B->>R: 幂等执行并保存结果
end
A->>L: 世代 7 尝试提交
L-->>A: 条件失败并进入审计
B->>L: 世代 8 条件提交图解读:节点分别代表旧执行者、Lease(租约)账本、新执行者和业务资源。正常路径是世代 7 续租并提交;失败路径中旧执行者暂停,世代 8 接管后先按原业务键查证。箭头前提是领取与提交都执行原子条件比较;旧进程恢复并不等于重新获得权利。结论是到期允许接管,但不证明旧业务动作失败,恢复仍要依赖幂等查询。
数据演绎 2:Lease(租约)安全窗口如何计算
E3(演练设计):设 Lease(租约)时长 30 秒,每 10 秒续租,调度存储高分位往返 2 秒,本地保护余量 3 秒。一次续租最晚应在剩余 2+3=5 秒 前完成,10 秒周期理论上留出 20 秒缓冲。旧执行者暂停 40 秒后恢复,权威账本已在第 30 秒过期,新执行者于第 32 秒取得世代 8;旧世代 7 即使在第 40 秒返回,也必须拒绝提交。状态由“持有世代 7”转为“失租只读取证”;观测信号是续租延迟、剩余窗口、世代冲突和接管次数;结论是参数必须覆盖暂停与存储抖动,但正确性最终由提交栅栏保证。
热门面试题
- 问题:Lease(租约)和普通互斥锁有什么区别?
- 考点:有限授权、故障恢复、双持有窗口。
- 回答思路:说明 Lease(租约)会到期,旧持有者却可能继续运行。
- 详细答案:普通进程内互斥锁由同一运行时维护持有关系;Lease(租约)是分布式环境中的有限期授权,持有者必须续租,超时后其他实例可接管。网络分区、长暂停或时钟问题会让旧持有者不知道自己已经失租,因此资源端还需比较单调世代,业务侧还需幂等。
- 进阶追问:Lease(租约)越长是否越安全?
- 进阶回答:不是。长 Lease(租约)减少误接管却延长故障恢复,短 Lease(租约)恢复快但更易受抖动影响,应结合暂停分布、存储延迟和恢复目标取值。
- 问题:执行者怎样知道自己已经失租?
- 考点:续租结果、本地门禁、保守停止。
- 回答思路:区分确定失租与无法确认仍持有。
- 详细答案:续租条件失败可确定失租;续租超时则属于无法确认,执行者也应在安全窗口耗尽前禁止新副作用。它可以保留上下文、记录检查点并查询业务结果,但不能继续提交。调度端以权威到期时间决定是否接管,不能依赖旧执行者主动通知。
- 进阶追问:续租请求成功但响应丢失怎么办?
- 进阶回答:旧执行者按无法确认处理并停止新增动作,再读取权威账本;即使实际续租成功,保守停顿只影响可用性,不应冒险造成双写。
- 问题:抢占后为什么先查询外部结果?
- 考点:超时未知态、重复副作用、原键查证。
- 回答思路:用计费或回调已成功但本地未提交解释。
- 详细答案:旧执行者可能已经完成计费、渠道查单或轨迹拉取,只是在写任务结果前失租。新执行者若直接重做,可能重复扣费、重复回调或放大下游压力。正确做法是携带原 Idempotency Key(幂等键)查询,确认未发生后再执行,已发生则复用结果,仍未知则保留未知态并升级。
- 进阶追问:外部系统不支持按幂等键查询怎么办?
- 进阶回答:只能缩小自动重试范围,结合业务单号、时间窗和对账查证,高风险动作转人工;这也是该自动恢复方案的不适用边界。
1.3 Fencing Token(栅栏令牌)、隔离与失败域
Fencing Token(栅栏令牌)是每次成功领取都单调递增的世代号,资源端只接受不小于已见世代且满足业务状态条件的提交。它防的是“旧执行者失租后迟到写入”,不是加密令牌,也不是业务幂等键。世代必须由权威账本原子产生,不能使用随机数、实例启动时间或本地计数;随机值无法比较新旧,本地计数会在重启或多节点间重复。
栅栏要落到真正产生副作用的资源边界:任务状态表可以条件更新 where generation = 当前世代,内部业务表可以保存最后接受世代;无法改造的第三方接口则必须用稳定业务 Idempotency Key(幂等键)、查询和对账补足。隔离用于限制故障传播:支付回调、计费、轨迹拉取和普通同步按资源池、队列、并发与重试预算分舱;租户或任务类型再设上限。协调服务本身的脑裂、会话与运维证据可回链集群运维、脑裂与排障和项目案例与综合题库。
| 失败域 | 可能表现 | 隔离手段 | 正确性兜底 |
|---|---|---|---|
| 单执行者 | 暂停、崩溃、线程池耗尽 | 单实例并发与看门限额 | Lease(租约)接管加栅栏 |
| 单任务类型 | 轨迹或回调重试激增 | 独立队列、线程池和重试预算 | 业务 Idempotency Key(幂等键) |
| 单租户 | 大客户任务占满容量 | 租户配额与公平调度 | 任务实例唯一键 |
| 协调存储 | 延迟、不可用或会话异常 | 停止新领取、保留在途查证 | 权威世代条件提交 |
| 外部依赖 | 限流、超时、结果未知 | 并发舱壁、熔断与降级 | 原键查询、对账或人工裁决 |
flowchart TD
A["权威账本分配世代 21"] --> B["执行者携带世代执行"]
B --> C{"资源端世代检查"}
C -->|当前仍为 21| D["检查业务状态与幂等键"]
C -->|已推进到 22| E["拒绝旧结果并写审计"]
D -->|允许迁移| F["提交业务结果"]
D -->|重复或非法迁移| G["返回既有结果或拒绝"]
F --> H["更新最后接受世代"]
E --> I["失租冲突指标"]
G --> J["幂等或状态冲突指标"]图解读:节点从权威账本发放单调世代开始,箭头把执行权带到资源端检查。正常路径同时满足当前世代、业务状态和幂等条件后提交;失败路径把旧世代、重复动作和非法迁移分别拒绝并观测。前提是资源端能够持久化最后接受世代;若第三方无法比较,Fencing Token(栅栏令牌)只能保护本地提交,外部效果仍靠业务幂等与查证。结论是栅栏和幂等互补而非替代。
数据演绎 3:隔离如何阻止轨迹重试拖垮支付回调
E3(演练设计):总执行能力为每秒 200 个任务,若支付回调、计费、轨迹和普通同步共享一个池,轨迹故障瞬间到达每秒 300 个,队列会持续增长并让支付回调排队。改为支付保留 60、计费保留 40、轨迹上限 70、普通同步上限 30,总计仍为 60+40+70+30=200。轨迹每秒超出的 230 个被限流或延迟,不占用支付的 60 个配额。状态从共享饥饿转为单舱退化;观测信号是各舱到达率、完成率、最老年龄和拒绝数;结论是隔离牺牲部分总体利用率,换取关键链路可恢复。
热门面试题
- 问题:Fencing Token(栅栏令牌)解决什么问题?
- 考点:旧持有者迟到写、单调世代、资源端拒绝。
- 回答思路:用暂停超过 Lease(租约)后恢复的双执行者场景说明。
- 详细答案:旧执行者在 Lease(租约)过期后仍可能继续运行,新执行者又已接管。Fencing Token(栅栏令牌)为每次领取分配更大的世代,资源端记录当前或最后接受世代,拒绝旧世代提交。它让旧执行者即使恢复也不能覆盖新结果,但不能撤销已经发生的外部副作用。
- 进阶追问:用随机唯一令牌可以替代吗?
- 进阶回答:不可以。随机令牌只能判断是否相同,不能判断谁更新;栅栏必须具备由权威源产生的全序或资源范围内单调顺序。
- 问题:为什么有栅栏仍要做幂等?
- 考点:执行权与业务效果分层。
- 回答思路:区分本地结果提交和外部计费已经发生。
- 详细答案:栅栏可以拒绝旧执行者更新任务表或受控业务表,却无法让第三方撤销已经完成的扣费、回调和通知。同一当前世代内也可能因响应丢失发生重复调用。因此外部动作仍要使用稳定 Idempotency Key(幂等键),支持原结果查询;不支持时需要对账和人工边界。
- 进阶追问:幂等能否反过来替代栅栏?
- 进阶回答:也不能。幂等保护同一业务动作,旧执行者仍可能用过期计算覆盖新状态;栅栏保护提交顺序,二者防护对象不同。
- 问题:任务调度怎样划分失败域?
- 考点:舱壁、优先级、共享依赖。
- 回答思路:按实例、任务类型、租户、协调存储和外部依赖展开。
- 详细答案:先识别共享线程、连接、队列和下游额度,再按关键程度设置独立资源池、并发、队列和重试预算。支付回调与计费不能被轨迹全量补跑挤占,大租户不能耗尽全局容量;协调存储异常时停止新领取,已领取任务只做有边界查证,不继续无控制扩散。
- 进阶追问:隔离会降低资源利用率怎么办?
- 进阶回答:可允许安全范围内借用空闲配额,但关键舱必须有最低保留和快速回收;稳定性目标高于瞬时满载率。
1.4 幂等键、状态迁移与结果提交
幂等键标识稳定业务意图,不标识每次网络尝试。订单同步可由“租户、订单、动作、业务版本”组成,计费可由“账期、费用对象、费用类型、规则版本”组成,回调重试应复用原事件或请求标识。任务实例键用于防止重复建任务,分片键用于防止重复建分片,业务 Idempotency Key(幂等键)用于防止外部效果重复,三者不能混成一个粗粒度编号。
状态机必须显式表达未知和暂停。推荐的 E3(演练设计)状态为待运行、执行中、暂停中、重试等待、结果未知、成功、失败、取消;终态不接受普通迟到事件倒退。执行结果提交同时校验任务版本、当前世代、前置状态和参数摘要;“同键同参数”返回原结果,“同键不同参数”拒绝并告警。消息至少一次投递、消费幂等和重试机制可回链可靠性语义、幂等与重试和容量、积压与排障。
| 当前状态 | 允许事件 | 目标状态 | 必要条件 |
|---|---|---|---|
| 待运行 | 领取 | 执行中 | Lease(租约)领取成功并产生新世代 |
| 执行中 | 外部结果未知 | 结果未知 | 保留原业务键与请求证据 |
| 执行中 | 可重试失败 | 重试等待 | 未超预算且分类允许 |
| 执行中 | 管理暂停 | 暂停中 | 停止新分片并保存检查点 |
| 重试等待 | 到期重领 | 执行中 | 原业务键不变且新世代生效 |
| 结果未知 | 查证成功 | 成功 | 外部结果、摘要与状态一致 |
| 任意非终态 | 取消 | 取消 | 不可逆副作用已查证或转人工 |
| 成功、失败、取消 | 普通迟到事件 | 保持终态 | 仅记录审计,不倒退 |
stateDiagram-v2
[*] --> 待运行
待运行 --> 执行中: 领取并获得新世代
执行中 --> 成功: 结果确认且条件提交
执行中 --> 重试等待: 可重试失败且预算充足
执行中 --> 结果未知: 调用超时或回执丢失
执行中 --> 暂停中: 管理暂停或保护动作
重试等待 --> 执行中: 到期并按原键重领
结果未知 --> 成功: 查得原动作已完成
结果未知 --> 重试等待: 查得原动作未发生
结果未知 --> 失败: 证据确认不可重试
暂停中 --> 待运行: 恢复并校验检查点
待运行 --> 取消: 合法取消
暂停中 --> 取消: 合法取消
成功 --> [*]
失败 --> [*]
取消 --> [*]图解读:节点给出任务生命周期,箭头都带触发条件。正常路径由待运行进入执行中并在结果确认后成功;失败路径把可重试失败、结果未知和管理暂停分开,避免一个失败状态同时承担三种语义。前提是每次迁移均比较当前状态、世代和版本,终态只接受审计事件。结论是“超时”不能直接迁移到失败,“恢复”也不能绕过原业务键与检查点。
数据演绎 4:三次投递为什么只能产生一次计费
E3(演练设计):计费任务以 租户 A + 账期 2026-06 + 订单 9001 + 运费 + 规则 4 为业务 Idempotency Key(幂等键)。第一次调用已写费用 80 元但响应丢失,任务进入结果未知;第二次由旧世代 11 重试,被世代 12 的提交门禁拒绝;第三次由新执行者按原键查询,取得既有费用 80 元并提交成功。接收次数为 3,业务费用记录仍为 1,金额仍为 80 元。状态从结果未知经查证到成功;观测信号是重复命中 1 次、旧世代拒绝 1 次、费用唯一冲突 0 次;结论是任务重试次数不能等同于业务执行次数。
热门面试题
- 问题:怎样设计 Runner(执行器)任务的幂等键?
- 考点:业务意图、参数摘要、键的生命周期。
- 回答思路:按任务实例、分片和外部动作分层。
- 详细答案:先确定业务上什么动作只能生效一次,再组合租户、对象、动作、窗口或业务版本形成稳定键,并保存影响结果的参数摘要。同键同参数重放返回原结果,同键不同参数拒绝。任务实例和分片另有唯一键,不能用随机尝试号充当业务身份,也不能让重试换键绕过去重。
- 进阶追问:幂等记录多久可以清理?
- 进阶回答:至少覆盖消息保留、最大重试、对账、人工重放和业务追溯窗口;高风险计费还要服从财务审计期限,不能只按缓存过期决定。
- 问题:为什么状态机需要“结果未知”?
- 考点:超时语义、查证、不可逆副作用。
- 回答思路:强调没有响应不代表没有成功。
- 详细答案:调用超时、连接断开或进程重启都只说明本地未确认结果。若直接标失败并换键重试,可能重复计费或重复回调。结果未知保留原请求、业务键、下游单号和时间线,恢复任务先查询;确定未发生才进入重试,确定已发生则复用原结果,无法确认则人工裁决。
- 进阶追问:结果未知是否会无限积压?
- 进阶回答:不会无限自动等待,应设置查证次数、年龄和风险分级;超过边界转人工,但不能为了清指标强行改成失败。
- 问题:任务成功提交要检查哪些条件?
- 考点:条件迁移、世代、参数一致性。
- 回答思路:列出当前世代、前置状态、版本和结果证据。
- 详细答案:提交时至少比较任务标识、定义版本、当前状态、Lease(租约)世代和参数摘要,结果还要包含外部业务键、业务状态与摘要。条件失败时读取当前记录:若已由同键完成则返回原结果,若世代过期则只审计,若参数冲突或非法迁移则告警,不能用无条件更新覆盖。
- 进阶追问:数据库事务能否覆盖外部调用?
- 进阶回答:通常不能。外部调用与本地提交之间存在未知窗口,应通过幂等、查询、Outbox(发件箱)或对账闭环,而不是持有长事务等待远程结果。
1.5 重试分类、退避与风暴治理
重试不是统一的异常捕获。至少分为瞬时可重试、业务不可重试、结果未知和系统性过载:网络短抖动可在预算内退避;参数非法、状态冲突或权限拒绝直接失败;结果未知先查证;下游容量不足先降到达率和并发,不能用更多重试施压。每次重试复用原业务 Idempotency Key(幂等键),递增的是尝试号和 Lease(租约)世代,不是业务身份。
退避需要指数增长、上限和随机抖动,避免同一批任务在固定时刻同时醒来;更重要的是设置任务级、任务类型级和下游级总预算。实时任务与历史补偿分配独立配额,毒任务达到阈值后进入隔离队列并保留错误指纹。线程池、队列、拒绝和上下文传播可回链线程池与异步编排及线上排障项目话术,消息项目落地参见异步导出、库存、支付与物联网案例。
| 错误类别 | 示例证据 | 自动动作 | 停止条件 |
|---|---|---|---|
| 瞬时可重试 | 连接重置且下游健康 | 有上限退避与随机抖动 | 超次数、超年龄或预算耗尽 |
| 业务不可重试 | 参数校验、权限或终态冲突 | 直接失败并记录原因 | 不进入自动重试 |
| 结果未知 | 请求发出后超时、回执丢失 | 原键查询和对账 | 已确认、转人工或超过查证边界 |
| 系统性过载 | 限流、连接池等待、下游高延迟 | 降并发、熔断、延后历史流 | 净完成率恢复且错误回落 |
| 毒任务 | 相同数据稳定触发同一错误 | 隔离、修复后受控重放 | 未修复前禁止循环投递 |
flowchart TD
A["执行失败"] --> B{"错误分类"}
B -->|业务不可重试| C["失败并审计"]
B -->|结果未知| D["按原业务键查证"]
B -->|系统性过载| E["降并发与熔断"]
B -->|瞬时可重试| F{"预算是否充足"}
D --> G{"结果是否明确"}
G -->|已成功| H["复用结果并提交"]
G -->|未发生| F
G -->|仍未知| I["延迟查证或人工"]
F -->|是| J["指数退避加随机抖动"]
F -->|否| K["隔离或失败"]
J --> L["重新领取并复用原键"]
E --> M["实时与历史流分舱恢复"]图解读:节点先分类失败,再分别走直接失败、未知查证、过载保护或预算内重试。正常恢复路径只有在确认未发生且预算充足时才退避重领;失败路径把毒任务隔离,把持续过载转换为降并发和分舱。箭头前提是错误分类有稳定指纹并保留原业务键。结论是重试控制器的目标不是提高调用次数,而是在不放大故障的前提下提高最终有效完成率。
数据演绎 5:固定重试如何形成放大器
E3(演练设计):入口每秒 100 个任务,下游故障率 50%。若每个失败任务立即重试 3 次且仍按 50% 失败,第一轮额外 50 次,第二轮 25 次,第三轮 12.5 次,总调用约 100+50+25+12.5=187.5 次/秒,比入口放大 87.5%。若下游当前安全能力仅 120 次/秒,重试会持续制造过载。把重试预算限制为每秒 15 次并加随机抖动后,总调用上限约 115 次/秒。状态由同步重试转为延迟等待;观测信号是重试比、预算消耗、下游延迟和净完成率;结论是先控制放大系数,再谈成功率。
热门面试题
- 问题:哪些错误不应该自动重试?
- 考点:错误分类、业务终态、过载反馈。
- 回答思路:列业务不可重试、结果未知和持续过载三类反例。
- 详细答案:参数非法、权限拒绝、终态冲突等确定性业务错误直接失败;结果未知先查证而不是重做;下游限流、连接池耗尽或持续高延迟表示系统性过载,应降并发、熔断和延后历史任务。只有短暂且明确可恢复的错误才在预算内重试,所有重试都复用原业务键。
- 进阶追问:数据库死锁可以重试吗?
- 进阶回答:可以有限重试,但要缩短事务、统一锁顺序并加随机抖动;持续死锁说明设计问题,不能靠无限重试掩盖。
- 问题:指数退避为什么还要随机抖动?
- 考点:同步唤醒、惊群、恢复尖峰。
- 回答思路:解释同批失败任务会拥有相同重试时刻。
- 详细答案:只有指数退避时,同一秒失败的任务仍可能在 1、2、4、8 秒整齐醒来,形成周期性尖峰。随机抖动把每次等待分散到区间内,降低调度存储、线程池和下游的瞬时争用。它不增加总预算,只改变时间分布;预算、并发和停止条件仍是必要门禁。
- 进阶追问:随机范围越大越好吗?
- 进阶回答:不是。范围过大会损害恢复时间,应根据下游恢复特征、任务时效和允许延迟设置,并用高分位任务年龄验收。
- 问题:怎样处理重试风暴中的毒任务?
- 考点:错误指纹、隔离、受控重放。
- 回答思路:先隔离稳定失败对象,再保护正常流量。
- 详细答案:按任务类型、异常类别、根因摘要和输入版本形成错误指纹;同一任务达到阈值后移入隔离队列,保留原业务键、尝试历史、参数摘要和最后证据。修复后先只读预览和小批重放,设置速率与停止条件,正常实时流保留独立资源,不能把隔离队列一次性倒回主队列。
- 进阶追问:隔离队列为空是否代表恢复?
- 进阶回答:不代表。还要确认业务结果、任务账本、下游状态和积压年龄收敛,避免任务被删除却没有完成业务修复。
1.6 暂停、恢复、执行中重启与积压排空
暂停有三种粒度:全局保护、任务类型隔离、单任务人工冻结。正确语义是停止新领取或停止下一分片,已发出的外部动作进入查证,已完成结果允许在当前有效世代内提交;不能把所有执行中任务直接改回待运行。长任务在稳定业务边界写检查点,检查点要包含游标、输入版本、已完成分片摘要和外部结果引用,不能只记录一个页码。
执行中重启后,新实例扫描过期 Lease(租约),先判断旧世代是否仍有效,再检查任务状态、检查点和外部结果。恢复优先级通常是支付回调与计费、订单关键同步、轨迹与普通补偿;实时新流量与历史积压使用独立配额。恢复完成必须满足服务健康、有效完成率大于新到达率、最老任务年龄下降、过期世代写入为零、外部重复副作用为零或已裁决。容量与恢复计算可回链容量排队与资源模型、故障模型与恢复目标和压测恢复与故障演练。
| 控制动作 | 新任务 | 在途任务 | 恢复门禁 |
|---|---|---|---|
| 全局暂停 | 全部停止领取 | 保存证据并停止新副作用 | 协调与业务权威源均可用 |
| 类型暂停 | 指定类型停止 | 其他类型继续,目标类型查证 | 目标依赖错误率和延迟回落 |
| 单任务暂停 | 不再领取该任务 | 到分片边界保存检查点 | 人工原因解除且版本未漂移 |
| 进程重启 | 新实例扫描过期 Lease(租约) | 原键查证后接管 | 旧世代提交持续为零 |
| 积压排空 | 实时流保留配额 | 历史流限速分批执行 | 净完成率为正且资源有余量 |
sequenceDiagram
participant O as 值班人员
participant S as 调度控制面
participant W as 执行者
participant L as 任务账本
participant D as 外部依赖
O->>S: 按任务类型发起暂停
S->>L: 写暂停版本并停止新领取
W->>L: 在分片边界保存检查点
W->>D: 查询已发出动作的结果
D-->>W: 返回成功、未发生或未知
W->>L: 条件提交已确认结果
O->>S: 故障消除后申请恢复
S->>L: 校验版本、积压和过期 Lease(租约)
S->>W: 小配额恢复实时任务
S->>W: 限速排空历史积压
W-->>O: 上报最老年龄与净完成率图解读:节点覆盖人工控制、调度面、执行者、任务账本和外部依赖。正常路径在暂停版本落账后停止新领取,在分片边界保存检查点;失败路径把已发出动作逐一查证,未知结果不会被改回待运行。恢复箭头的前提是版本、Lease(租约)和依赖健康通过门禁,先小配额实时流,再限速历史流。结论是暂停与恢复都是状态迁移和容量控制,不是简单关闭再开启开关。
数据演绎 6:积压需要多久才能安全排空
E3(演练设计):故障 20 分钟期间任务到达每秒 80 个、有效完成每秒 30 个,积压为 (80-30)×20×60=60000 个。恢复后总完成能力每秒 140 个,新到达仍为 80 个,理论净排空每秒 60 个,理想时间 60000/60=1000 秒,约 16.7 分钟。若为数据库保留 20% 余量,只允许历史流使用每秒 40 个,净排空时间变成 60000/40=1500 秒,即 25 分钟。状态从积压增长转为有界下降;观测信号是到达率、有效完成率、最老年龄和数据库等待;结论是宁可延长排空,也不能用满负载制造第二次故障。
热门面试题
- 问题:暂停任务时为什么不能把执行中状态直接改回待运行?
- 考点:在途副作用、未知态、状态竞争。
- 回答思路:说明旧执行者可能仍在调用外部系统。
- 详细答案:执行中任务可能已经完成部分分片或发出计费、回调、轨迹请求。直接改回待运行会让新执行者立刻重做,同时旧执行者仍可能返回。应先写暂停版本、停止新领取,要求执行者在安全边界保存检查点;已发出动作按原键查证,只有明确未发生的部分才允许恢复执行。
- 进阶追问:进程已经失联,怎样保存检查点?
- 进阶回答:只能使用失联前持久化的最后可信检查点,并把其后的范围视为未知;新执行者逐项查证,不能假设内存进度已经提交。
- 问题:执行中重启如何避免从头重复?
- 考点:检查点、分片、业务幂等。
- 回答思路:从最后可信检查点恢复,逐个确认边界后的动作。
- 详细答案:长任务按可独立验证的分片记录输入版本、游标、结果摘要和外部业务键。重启后新世代先读取检查点,核对已完成分片的业务结果;边界后的未知分片按原键查询,未发生才重做。检查点只减少重做量,真正防重复仍依赖外部幂等和状态条件。
- 进阶追问:检查点应该多频繁写?
- 进阶回答:按单片重做成本、检查点写放大、外部副作用和恢复目标权衡;高风险不可逆动作前后要留证,纯计算可适当放大分片。
- 问题:怎样判断积压恢复可以结束?
- 考点:净完成率、任务年龄、业务验收。
- 回答思路:拒绝只看队列长度,给出多信号门禁。
- 详细答案:需要持续看到有效完成率大于新到达率、最老任务年龄下降、过期 Lease(租约)提交为零、错误和重试预算回落、关键任务结果可对账。队列归零但任务被丢弃、隔离或卡在结果未知都不算恢复;还要按计费、回调和轨迹抽样业务终态。
- 进阶追问:为什么有效完成率不等于消费确认率?
- 进阶回答:消息可能确认后业务失败,也可能重复确认原结果;有效完成必须以业务状态和任务账本共同验收,不能用传输层确认替代。
1.7 失租、重复执行与线上排查
线上看到任务重复、Lease(租约)过期或积压时,先区分调度重复与业务重复。调度重复可能只是同一业务键被再次领取并返回原结果;真正事故是外部效果重复、状态倒退或旧世代覆盖。排障先固定任务标识、业务 Idempotency Key(幂等键)、世代、持有者、定义版本、检查点、消息位置、外部单号和时间线,再提出可证伪假设:续租存储抖动、执行者长暂停、线程池饥饿、旧世代提交门禁缺失、业务键变化、消费确认丢失或下游持续过载。
止血动作按失败域执行:暂停目标任务类型的新领取,保留支付回调和计费关键配额;冻结高风险自动重试;不删除积压、不批量改成功、不无条件释放 Lease(租约)。查证后,旧世代写入由资源端拒绝,重复业务结果通过原键复用,未知结果进入查询或人工,毒任务隔离。指标告警、链路和事故响应方法可回链Prometheus(监控系统)指标与告警治理、OpenTelemetry(开放遥测标准)与链路追踪和服务等级目标、事故响应与容量案例。
| 现象 | 优先假设 | 第一证据 | 止血与查证 |
|---|---|---|---|
| Lease(租约)过期激增 | 协调存储慢或执行者暂停 | 续租延迟、剩余窗口、线程暂停 | 停止新领取,比较权威世代 |
| 同任务多执行者 | 接管正常或旧实例恢复 | 持有者、世代、领取时间线 | 拒绝旧世代,核对业务结果 |
| 外部效果重复 | 业务键变化或下游不幂等 | 请求键、参数摘要、外部单号 | 冻结重试,逐笔对账裁决 |
| 消费积压增长 | 完成率低于到达率 | 到达、有效完成、最老年龄 | 分舱限流,定位共享瓶颈 |
| 重启后大量失败 | 检查点漂移或定义升级 | 输入版本、游标、定义版本 | 回退版本,按可信分片恢复 |
flowchart TD
A["告警:失租、重复或积压"] --> B["固定任务键、业务键、世代与时间线"]
B --> C["提出可证伪假设"]
C --> D{"业务副作用是否扩大"}
D -->|是| E["暂停目标类型并冻结自动重试"]
D -->|否| F["保留实时流并限制历史流"]
E --> G["按原键查询外部结果"]
F --> G
G --> H{"证据是否唯一"}
H -->|已成功| I["复用结果并修正任务账本"]
H -->|未发生| J["新世代从检查点恢复"]
H -->|仍冲突| K["人工裁决与差异单"]
I --> L["对账、排空与分批放量"]
J --> L
K --> L图解读:节点从告警进入证据保全和假设验证,箭头先判断业务副作用是否扩大。正常路径在没有扩大时保留关键实时流并限制历史流;失败路径暂停目标类型、冻结自动重试。查证分支只允许“已成功复用、未发生恢复、证据冲突转人工”三种收束。前提是保留原业务键与时间线;结论是修复调度状态后还必须对账外部业务,并通过分批放量验证。
数据演绎 7:用时间线定位失租与重复提交
E3(演练设计):任务 T 在 10:00:00 以世代 31 领取,Lease(租约)到 10:00:30;10:00:10、10:00:20 两次续租正常,第二次把到期推到 10:00:50。执行者从 10:00:22 暂停到 10:01:02,新执行者于 10:00:52 取得世代 32,并在 10:00:58 查得原计费已成功。旧执行者 10:01:03 用世代 31 提交,被条件拒绝;若外部只有一笔费用,属于调度重复但业务未重复。状态从世代 31 失租转为世代 32 接管成功;观测信号是 1 次接管、1 次旧世代拒绝、1 次幂等查询命中;结论是必须把续租、外部调用和提交三条时间线对齐。
热门面试题
- 问题:线上出现同一任务两个执行者,是否一定是事故?
- 考点:接管窗口、业务效果、栅栏验证。
- 回答思路:区分并发运行、提交权和外部副作用。
- 详细答案:不一定。Lease(租约)到期接管时旧进程可能尚未停止,短时双执行是必须考虑的故障模型。关键看新旧世代、资源端是否拒绝旧提交、外部业务键是否只产生一个结果。若只有旧世代冲突且业务唯一,说明防线生效;若状态倒退或重复计费,才是正确性事故。
- 进阶追问:怎样快速证明业务没有重复?
- 进阶回答:按稳定业务键查询外部单据与不可变流水,比较参数摘要和结果数量;只看任务日志或执行次数不足以证明。
- 问题:遇到失租激增时第一步做什么?
- 考点:证据保全、失败域、可逆止血。
- 回答思路:先限定任务类型和影响,再停止放大。
- 详细答案:先按任务类型、实例、租户和协调存储定位范围,保存续租延迟、剩余窗口、线程与进程暂停、世代冲突和外部调用时间线。若副作用风险上升,暂停目标类型新领取并冻结自动重试,关键任务保留独立配额。不要先全量重启、删 Lease(租约)或清队列,这些动作会改变现场。
- 进阶追问:协调存储恢复后能否立即全量放开?
- 进阶回答:不能。先验证续租延迟与世代冲突回落,小配额恢复关键任务,再观察净完成率和业务对账后逐档放量。
- 问题:消费者积压为什么会引发 Lease(租约)问题?
- 考点:共享线程池、长等待、反馈放大。
- 回答思路:连接消息处理、续租调度和下游阻塞。
- 详细答案:若消费处理、外部调用和续租共享线程或连接,下游变慢会占满资源,续租任务得不到调度,Lease(租约)过期后新实例接管;旧任务恢复又产生重复调用。积压还会触发更多重试,进一步挤占续租。应隔离续租控制面、限制在途和重试,并按有效完成率排空。
- 进阶追问:只增加消费者数量为什么可能更差?
- 进阶回答:共享瓶颈在数据库或下游时,更多消费者只增加并发、超时和重试;必须先证明净完成能力可提升,并保留资源余量。
1.8 可观测性、审计与八段式项目话术
可观测性不能停在“任务成功率”。业务层看订单同步、计费、轨迹和回调的有效完成率、最老未决年龄与重复副作用;调度层看领取、续租、接管、旧世代拒绝、重试预算和状态分布;资源层看线程池、连接池、协调存储、数据库与下游延迟。每条日志至少关联任务实例、分片、业务 Idempotency Key(幂等键)、持有者、世代、尝试号、定义版本和 Trace(链路追踪)标识,审计还要记录操作者、暂停/恢复原因、前后状态、审批与结果摘要。
告警优先采用症状和业务风险:最老高优任务超过目标、净完成率连续为负、旧世代写入非零、结果未知年龄增长或外部重复记录出现。低风险的单次重试、单实例重启和瞬时续租抖动用于诊断,不直接轰炸值班人员。恢复判定需要跨一个完整业务窗口,证明实时流稳定、历史积压下降、结果未知和人工队列收敛、临时权限回收。若外部系统既不支持稳定幂等键也不能查询结果,或任务包含不可分割且不可补偿的物理动作,自动接管方案不适用,应缩小自动化范围并设置人工确认。
| 观测层 | 核心信号 | 告警意义 | 恢复证据 |
|---|---|---|---|
| 业务结果 | 有效完成率、重复副作用、未知年龄 | 用户或资金风险 | 业务账本与外部单据一致 |
| 调度状态 | 领取、续租、接管、世代冲突 | 执行权异常 | 旧世代写入持续为零 |
| 队列容量 | 到达率、完成率、最老年龄 | 是否形成积压 | 净完成率为正并持续下降 |
| 运行资源 | 线程池、连接池、数据库和下游延迟 | 共享瓶颈位置 | 高分位延迟和等待回到预算 |
| 人工审计 | 暂停、恢复、补偿、审批与权限 | 操作风险 | 差异单关闭且临时权限回收 |
mindmap
root((Runner(执行器)项目话术))
背景
订单计费轨迹回调常驻任务
约束
重复超时重启积压
目标
当前世代可提交且业务只生效一次
方案
注册分片租约栅栏幂等状态机
权衡
恢复速度与误接管
隔离余量与利用率
失败
失租重复未知重试风暴
结果证据
业务账本积压世代冲突审计
复盘
故障注入门禁与不适用边界图解读:中心节点是 Runner(执行器)项目串讲,八个一级分支严格对应背景、约束、目标、方案、权衡、失败、结果证据和复盘。分支之间不是并列技术清单:背景产生约束,约束决定方案和权衡,失败路径用观测证据验收,复盘再补故障注入与边界。前提是所有生产陈述按 E1(直接证据)、E2(已有材料映射)、E3(演练设计)或 E0(待核对)区分;结论是面试话术必须能从业务不变量下钻到状态和数据。
八段式项目话术:第一,背景,简历材料能证明 Runner(执行器)常驻任务承载订单、计费、轨迹和回调重试,但框架版本和生产参数保持 E0(待核对)。第二,约束,任务会重复投递、执行者会重启、外部调用会超时未知,关键任务还会与历史补偿争抢资源。第三,目标,同一 Lease(租约)世代只有当前持有者可提交,同一业务意图只产生一次效果,恢复过程可审计。第四,方案,任务定义与实例分离,稳定逻辑分片,领取递增世代,资源端校验 Fencing Token(栅栏令牌),外部动作复用 Idempotency Key(幂等键),状态机保留结果未知、暂停和检查点。第五,权衡,较长 Lease(租约)减少误接管却延长恢复,隔离配额保护关键链路却降低满载利用率。第六,失败,失租后旧世代拒写,执行中重启先查外部结果,重试风暴按预算、退避和分舱收敛。第七,结果证据,只能用任务账本、业务单据、世代冲突、最老年龄、净完成率和审计记录证明;真实提升数据缺证据时不声称收益。第八,复盘,继续演练长暂停、协调存储抖动、重复消息和积压恢复,并明确第三方不支持幂等查询时转人工。
数据演绎 8:从接口成功率修正为业务有效完成率
E3(演练设计):观察窗口接收 1000 次任务处理,其中 900 次返回成功、50 次返回失败、50 次超时;900 次成功里有 80 次只是幂等命中,20 次对应的外部业务状态尚未确认,另有 5 笔重复计费。接口成功率是 900/1000=90%,但可确认的新业务有效完成只有 900-80-20-5=795,有效完成率为 795/1000=79.5%。状态从技术成功重新分类为新完成、重复命中、未知和错误副作用;观测信号是业务键去重结果、外部状态与差异数;结论是恢复门禁应看业务有效完成和未知年龄,不能只看返回码。
热门面试题
- 问题:Runner(执行器)最重要的观测指标是什么?
- 考点:业务、调度、容量三层信号。
- 回答思路:先给业务有效完成,再补世代和积压指标。
- 详细答案:首要是按任务类型定义的业务有效完成率、重复副作用和最老未决年龄;其次看 Lease(租约)续租延迟、接管次数、旧世代拒绝、结果未知和重试预算;最后看到达率、有效完成率、线程连接等待与下游延迟。单一任务成功率无法识别幂等命中、未知结果和错误业务效果。
- 进阶追问:接管次数增加是否一定要告警?
- 进阶回答:不一定。发布或正常重启会接管,应结合旧世代冲突、任务年龄和业务影响设症状告警,接管次数主要用于定位。
- 问题:审计日志为什么不能只记录异常堆栈?
- 考点:因果链、人工操作、业务裁决。
- 回答思路:列任务、业务、世代、版本和操作者字段。
- 详细答案:异常堆栈只描述某次代码失败,不能回答同一业务是否被其他世代完成。审计应关联任务、分片、业务 Idempotency Key(幂等键)、持有者、世代、尝试号、定义版本、前后状态、外部单号和结果摘要;人工暂停、恢复和补偿还要记录操作者、审批、原因与权限范围。
- 进阶追问:审计存储不可用时怎么办?
- 进阶回答:高风险补偿和批量恢复默认拒绝或进入可靠缓冲,普通任务降级也要限制范围;不能执行不可追踪的资金或状态修复。
- 问题:怎样用八段式讲清 Runner(执行器)项目?
- 考点:项目叙事、证据等级、不变量。
- 回答思路:按背景到复盘依次落到事实、状态和恢复证据。
- 详细答案:先说常驻任务服务的订单、计费、轨迹和回调背景,再说重复、重启、未知态与资源竞争约束;目标是当前世代可提交且业务只生效一次。方案讲注册分片、Lease(租约)、栅栏、幂等和状态机,权衡恢复速度与误接管;失败讲失租、重启与风暴;结果只报可核验证据;复盘补演练和不适用边界。
- 进阶追问:没有真实性能数字怎样体现深度?
- 进阶回答:明确标 E0(待核对),给取证口径和 E3(演练设计)的输入、公式、状态与信号,展示可验证推理而不是虚构收益。
2. 综合题库
问题:请从零设计一个支持租约恢复的 Runner(执行器)调度系统。
- 考点:权威事实、分片、Lease(租约)、栅栏、业务幂等、恢复验收。
- 回答思路:先澄清业务副作用和时效,再按控制面、执行面与业务面分层。
- 详细答案:核心是让执行权、提交权和业务效果分别由 Lease(租约)、栅栏与幂等约束,并以业务结果验收恢复。
- 进阶追问:调度系统应先建设高可用还是先建设业务幂等?
- 进阶回答:先守住业务幂等和权威状态,再提升调度可用性;否则更快接管只会更快复制错误副作用。
- 口述答案:我会先确认任务类型、是否允许重复、外部动作能否按稳定业务键查询、最长执行时间和恢复目标,因为计费与普通轨迹同步的失败成本不同。权威任务账本把定义和实例分开:定义保存版本、分片、并发、超时、重试及隔离策略,实例保存业务 Idempotency Key(幂等键)、状态、尝试号、Lease(租约)世代、检查点和结果摘要。逻辑分片由稳定业务字段映射到固定空间,实例扩缩容只改变分片领取者。领取、续租和提交都在权威存储做条件更新,每次重新领取递增 Fencing Token(栅栏令牌);执行者续租失败或安全窗口耗尽就停止新增副作用,资源端拒绝旧世代迟到提交。外部计费、回调和通知仍复用原业务键,超时进入结果未知,新执行者先查询,确定未发生才重做。状态机包含待运行、执行中、暂停、重试等待、结果未知和终态,终态不被迟到事件倒退。任务类型、租户和下游依赖分别设置资源舱、并发与重试预算。观测同时看业务有效完成、最老未决年龄、续租延迟、接管、旧世代拒绝、重试比和资源等待。恢复不是进程启动,而是旧世代写入为零、有效完成率大于到达率、积压持续下降、外部无重复副作用且未知态与人工队列收敛。真实框架版本和参数没有源码证据时保持 E0(待核对),方案数字只作为 E3(演练设计)。落地时我会先做单任务类型和单租户灰度,让旧路径保留可回退入口;只有状态迁移、业务对账和失租演练都通过,才扩大接管范围。
- 追问1:为什么定义升级不能直接影响执行中的任务?
- 直答1:恢复必须复用原语义;执行中切换分片或幂等规则会让检查点和结果无法比较,应由旧实例固定引用原定义版本。
- 追问2:调度账本能否直接作为业务结果账本?
- 直答2:不能默认合并;调度账本证明执行过程,计费、订单或轨迹系统证明业务结果,两者通过稳定业务键关联并对账。
- 追问3:这个方案最关键的不适用边界是什么?
- 直答3:外部动作既不可幂等、不可查询又不可补偿时,自动接管无法证明安全,只能缩小自动化范围并引入人工确认。
- 对应知识节
问题:任务注册和分片怎样设计,才能支持扩缩容与重放?
- 考点:逻辑分片、稳定身份、定义版本、检查点。
- 回答思路:区分业务对象归属、分片身份和运行实例归属。
- 详细答案:注册合同固定执行语义,逻辑分片固定业务归属,运行实例只承担动态领取,三层身份不可混用。
- 进阶追问:分片映射规则升级时怎样避免双算?
- 进阶回答:建立旧新映射、冻结迁移水位并做影子校验,确认业务键唯一后再切换,失败可退回旧规则。
- 口述答案:我先把任务定义、任务实例和分片拆开。任务定义记录类型、版本、参数模式、计划策略、固定逻辑分片空间、并发、超时、重试分类与隔离组;任务实例由租户、任务类型、业务对象和计划窗口形成稳定身份,并保存参数摘要;分片则由任务实例加分片号唯一标识。订单号、支付单号或轨迹单号映射到固定逻辑分片,不能直接对当前 Worker(工作线程)数量取模,否则实例从 3 个扩到 4 个会让大量对象改变归属并被重复扫描。执行实例只动态领取逻辑分片,扩缩容改变领取者,不改变业务 Idempotency Key(幂等键)。长任务按分片保存输入版本、游标、已完成数量、结果摘要和外部单号,重启后从最后可信检查点恢复;检查点之后的范围视为未知,逐项按原键查询。定义升级生成新版本,新实例明确选择新版本,执行中的实例仍固定旧版本。热点不均时可以拆分逻辑分片或做二级范围,但要保存迁移映射和双读校验,不能静默重算。验收时并发注入扩容、缩容、重复领取和进程重启,检查对象只归属一个稳定分片,外部业务记录仍唯一,分片期望数、完成数、异常数可复算。真实分片数和单片大小缺证据时标 E3(演练设计),由对象量、高分位处理时间与恢复目标反推。还要对分片清单做总数与摘要核对,扫描失败对象单列,不能用“已遍历到末尾”替代全量覆盖证明;迁移期间旧新规则的差异必须可定位到具体业务对象。
- 追问1:热点分片长期拖尾时怎么办?
- 直答1:先确认是数据倾斜还是下游慢,再对热点范围做可追踪二级拆分,保留原分片与新分片映射,避免全局重分片。
- 追问2:检查点只保存页码有什么风险?
- 直答2:输入变化会让同一页内容漂移;应保存稳定游标、输入版本、结果摘要和业务键边界。
- 追问3:分片领取记录要保留多久?
- 直答3:至少覆盖最大执行、重试、对账和人工重放窗口,高风险任务还要满足业务审计期限。
- 对应知识节
问题:旧执行者失租后恢复,如何阻止它覆盖新结果?
- 考点:双执行窗口、Fencing Token(栅栏令牌)、资源端条件提交。
- 回答思路:说明 Lease(租约)只允许接管,栅栏才拒绝旧写。
- 详细答案:Lease(租约)提供有限期执行权,Fencing Token(栅栏令牌)在资源端建立新旧顺序,业务幂等吸收外部重复。
- 进阶追问:世代计数达到上限应怎样处理?
- 进阶回答:选择足够宽的持久化整数并监控余量;迁移计数空间要停写或做严格版本切换,不能回绕后继续比较。
- 口述答案:我把这个问题拆成执行权、提交权和业务效果三层。执行者领取任务时,权威账本原子写入持有者、到期时间并递增世代;续租只能由当前持有者和当前世代完成。旧实例因长暂停或网络分区错过续租后,本地一旦无法确认仍持有,就应停止新副作用,但系统不能依赖它自觉停止。到期后新实例取得更大的 Fencing Token(栅栏令牌),所有受控资源在提交时比较当前世代、前置状态和版本,旧世代即使恢复也只能得到条件失败并写审计,不能覆盖新状态。对于已经发出的外部计费或回调,栅栏无法撤销副作用,所以新实例先按原 Idempotency Key(幂等键)查询:已成功就复用结果,未发生才执行,仍未知则保留未知态或转人工。释放 Lease(租约)也要比较持有者和世代,避免旧实例删除新实例授权。测试会让旧实例暂停超过 Lease(租约),新实例接管并提交,再恢复旧实例;预期是任务表只有新世代结果、旧世代冲突计数增加、外部业务记录仍只有一笔。若第三方不支持世代比较,Fencing Token(栅栏令牌)只能保护本地账本,必须明确其边界,不能宣称已经实现端到端栅栏。线上还要观察“旧世代被拒绝”和“旧世代实际写入”两个不同指标:前者说明防线工作,后者一旦非零就是正确性事故,需要立即暂停对应任务类型并对账。解除暂停前还要连续跨过一个最大租约窗口,确认旧世代实际写入保持为零、未知外部结果已查清且差异单有负责人,防止旧实例再次恢复形成第二次污染。
- 追问1:为什么随机唯一令牌不够?
- 直答1:随机值只能判断相等,资源端无法比较新旧;栅栏需要权威源产生的单调顺序。
- 追问2:旧执行者已经拿到数据库连接,还能被挡住吗?
- 直答2:只要最终更新带当前世代和状态条件就能拒绝;无条件写或资源端不存世代则挡不住。
- 追问3:新执行者接管后发现旧动作仍未知怎么办?
- 直答3:保留原业务键继续查证并限制自动重试,高风险对象超过边界转人工,不用新键强行完成。
- 协调锁与选型边界
问题:同一任务被投递三次,怎样保证业务只生效一次?
- 考点:三层唯一键、参数冲突、消息至少一次语义。
- 回答思路:从任务实例、分片和业务副作用分别设防。
- 详细答案:任务、分片和业务动作分别建立唯一身份,重复接收可以多次,最终业务效果必须由持久化约束保持唯一。
- 进阶追问:批量任务中的单条失败如何保持幂等?
- 进阶回答:每个业务对象有独立动作键和结果,批次只负责聚合进度;恢复只处理未确认对象,不重做整批。
- 口述答案:我不会把“任务表只有一条”当作业务幂等。第一层,任务实例以租户、任务类型、业务对象和计划窗口唯一,重复注册同键同参数返回既有实例,同键不同参数拒绝并告警。第二层,任务加分片号唯一,避免重平衡或重复扫描创建两个分片。第三层,真正的计费、订单同步、轨迹拉取或回调使用稳定 Idempotency Key(幂等键),它由业务意图组成,重试时不变,同时保存影响结果的参数摘要。消费者收到消息后,去重记录与本地业务更新在同一事务提交;外部调用超时则进入结果未知,按原键查询,不能换尝试号绕过去重。当前执行者提交任务结果还要校验 Lease(租约)世代、前置状态、定义版本和结果摘要,旧世代只审计。假设第一次计费成功但响应丢失,第二次是旧世代重试,第三次是新世代接管:第二次被栅栏拒绝,第三次查询到原费用并复用,最终接收三次、任务尝试多次、业务费用仍一笔。幂等记录清理窗口至少覆盖消息保留、最大重试、人工重放和对账周期;不能只依赖 Redis(远程字典服务)短期键,因为过期、淘汰和故障都会失去长期保护。验收同时核对接收数、幂等命中、唯一业务记录和金额,避免数值没变但通知或实物动作重复。对于历史上没有稳定键的数据,迁移时按订单、动作和原流水建立映射,冲突对象进入人工清单,绝不能批量生成随机键后宣称完成幂等改造。
- 追问1:同键不同参数为什么不能覆盖?
- 直答1:它表示调用方对同一业务意图产生冲突解释,覆盖会让重放不可预测,应拒绝并保留双方摘要。
- 追问2:去重记录先写、业务后写可以吗?
- 直答2:分开提交会出现去重成功但业务失败的永久遗漏,或业务成功但去重失败的重复执行,应尽量同事务。
- 追问3:缓存幂等键有什么价值?
- 直答3:可快速拦截短时重复和减压,但持久化唯一约束、业务状态与对账仍是长期正确性底线。
- 消息幂等与重试正文
问题:Runner(执行器)执行到一半重启,如何正确恢复?
- 考点:检查点、结果未知、世代接管、定义版本。
- 回答思路:先查旧动作,再从最后可信业务边界继续。
- 详细答案:重启恢复以最后可信检查点为起点,以外部结果查证为裁决,以新世代条件提交为终点。
- 进阶追问:检查点损坏时还能自动恢复吗?
- 进阶回答:只能退回更早的可信检查点并扩大查证范围;没有可验证边界时,高风险对象转人工而不是猜测进度。
- 口述答案:重启恢复不能简单把执行中改回待运行。我会先等待或确认旧 Lease(租约)过期,由新实例领取并获得更大世代;读取任务固定的定义版本、输入摘要和最后可信检查点。检查点必须落在可独立验证的业务边界,包含分片号、稳定游标、输入版本、完成摘要和外部业务键,不只是内存页码。检查点之前的分片逐个抽样或按摘要确认,检查点之后凡是旧实例可能已经发出的计费、回调或轨迹请求都视为结果未知,按原 Idempotency Key(幂等键)查询;已完成复用结果,明确未发生才重新执行,证据冲突进入人工差异单。新执行者每完成一个分片都在当前世代下条件提交,旧实例恢复后的迟到写由 Fencing Token(栅栏令牌)拒绝。恢复过程限制并发,把支付回调和计费放在高优资源舱,历史轨迹分批排空,避免所有过期任务同时抢占下游。若重启期间发布了新任务定义,旧实例仍按原版本恢复,新版本只用于新建任务或经过显式迁移的任务。验收不只看最终成功,还要检查已完成分片总数、结果摘要、外部单据唯一、旧世代拒绝、未知态年龄和积压净下降。检查点频率按重做成本、写放大和恢复目标权衡;不可逆动作前后必须有证据,纯计算分片可以更大。恢复完成后还要把检查点期望数、成功数、未知数、失败数和人工数做守恒核对,任何扫描异常都单列,避免漏处理对象被汇总成功掩盖。
- 追问1:旧 Lease(租约)未过期但实例确定崩溃,能否强制接管?
- 直答1:除非有权威撤销并产生更大世代,否则提前接管会扩大双执行窗口;通常等待到期更可证明。
- 追问2:恢复时可以直接使用新版本代码吗?
- 直答2:代码可以兼容运行,但业务语义必须按任务记录的定义版本解释,必要时做显式迁移和影子校验。
- 追问3:没有检查点的长任务怎么办?
- 直答3:把全范围视为待查证,按稳定业务键逐项核对后重做未发生对象;这会变慢,但不能假设内存进度。
- 线程池与异步编排
问题:下游故障触发重试风暴,怎样止血并恢复?
- 考点:错误分类、放大系数、预算、分舱与排空。
- 回答思路:先停止错误反馈,再恢复关键实时流和历史积压。
- 详细答案:重试治理先分类,再用预算、退避、抖动和隔离限制放大,最后按有效完成率逐档恢复。
- 进阶追问:重试预算应由谁统一管理?
- 进阶回答:任务执行器执行局部预算,平台按任务类型和下游汇总全局预算,避免多个实例各自合规却总体过载。
- 口述答案:我先确认是瞬时错误、确定性业务错误、结果未知还是系统性过载。参数和权限错误直接失败;结果未知按原业务键查证;连接抖动才在预算内重试;下游限流、连接池等待和高延迟说明过载,要降并发而不是加重试。止血时暂停目标任务类型的新历史领取,冻结无上限自动重试,为支付回调和计费保留独立资源舱;保留队列和任务证据,不清空、不批量改成功。重试采用指数退避、上限和随机抖动,同时设置任务级、任务类型级和下游级总预算,毒任务按错误指纹隔离。假设入口每秒 100 个任务、失败率 50%、每个失败立即重试三次,调用可放大到约每秒 187.5 次;若下游安全能力只有 120 次,这种重试会延长故障。把总重试预算限制为每秒 15 次后,调用上限约每秒 115 次,先让下游恢复净完成能力。恢复时小配额放开关键实时流,确认错误率、延迟和有效完成率稳定,再按限速排空历史任务;每次提速都检查数据库等待、下游高分位和最老任务年龄,反弹就退回上一档。最终验收是业务结果唯一、结果未知收敛、重试比回落、积压净下降和隔离任务有明确处置,不是队列数字被删除。复盘还要检查重试是否跨服务叠加:上游三次、Runner(执行器)三次、客户端再三次会形成乘法放大,应指定唯一重试责任层,其余层快速失败或只做查询。停止条件要预先固化:若连续两个观测窗口内下游限流率、连接等待或未知态重新上升,就撤回本档放量并保留现场,而不是为了追赶排空时间继续加压。
- 追问1:为什么指数退避仍可能同步冲击?
- 直答1:同批失败任务拥有相同退避时刻,仍会整齐醒来;需要随机抖动打散时间分布。
- 追问2:毒任务隔离后何时可以重放?
- 直答2:根因和输入版本已确认修复,先只读预览,再小批限速重放,并设置错误反弹停止条件。
- 追问3:熔断期间关键任务怎么办?
- 直答3:按风险选择查询、延迟或人工,关键流可有独立小配额,但不能绕过业务幂等和下游保护。
- 消息积压与线上排障
问题:消费者积压 6 万个任务,如何估算并执行恢复?
- 考点:净完成率、资源余量、实时与历史隔离、恢复门禁。
- 回答思路:用可复算公式给理想时间,再按安全余量修正。
- 详细答案:积压恢复以净有效完成率估时,以资源余量限速,以最老年龄和业务结果共同验收。
- 进阶追问:不同优先级积压怎样分配恢复能力?
- 进阶回答:先给关键实时流最低保留,再按风险和年龄给历史流加权配额,低优先任务不能无限饿死。
- 口述答案:我先确认积压定义是未获得业务终态的任务,而不是单纯消息数,并按支付回调、计费、轨迹和普通同步拆分最老年龄与风险。恢复时间用“积压量除以有效完成率减新到达率”估算,分母必须为正。E3(演练设计)假设积压 60000 个,新到达每秒 80 个,恢复后总有效完成每秒 140 个,理想净排空每秒 60 个,约
60000/60=1000 秒,即 16.7 分钟。但如果数据库和外部依赖需要 20% 安全余量,历史流只允许每秒 40 个,实际计划应按 25 分钟,并为实时流保留固定配额。执行时先修复共享瓶颈和毒任务,小配额恢复高优实时任务,再按任务类型、租户和分片限速回放历史积压;原业务 Idempotency Key(幂等键)不变,重复和乱序由状态机吸收。每次提高速率都观察有效完成、到达、最老年龄、线程连接等待、数据库延迟、下游限流、重试预算和旧世代冲突,任一恶化就回退。队列归零不代表完成,隔离、结果未知和人工清单都要计入未决。最终需要证明积压持续下降、关键任务年龄回到目标、外部业务记录唯一、历史分片全覆盖且实时流没有因排空再次超时。估算还要做敏感性分析:新到达每增加每秒 10 个,净排空能力就减少同样数量;接近零时恢复时间会陡增,因此必须持续用实测速率重算而不是固守初始承诺。每轮还要核对期望、成功、未知、失败、隔离与人工数量守恒,扫描缺口单列;只要总数对不上或关键实时流年龄反弹,就停止历史放量并重新定位瓶颈。 - 追问1:完成率等于消息确认率吗?
- 直答1:不等于;确认可能发生在业务失败或重复命中后,有效完成要由任务状态和业务结果共同裁决。
- 追问2:净完成率小于等于零时怎么办?
- 直答2:先降新到达、修共享瓶颈或增加有用容量,不能宣布排空时间,也不能靠更多消费者制造假吞吐。
- 追问3:为什么要看最老年龄?
- 直答3:总量下降可能只处理新任务而饿死旧任务,最老年龄能暴露公平性和长期未决风险。
- 容量排队与资源模型
问题:怎样设计 Runner(执行器)的暂停与恢复能力?
- 考点:控制粒度、在途查证、暂停版本、分批恢复。
- 回答思路:把暂停作为状态迁移和资源门禁,不当成简单开关。
- 详细答案:暂停先持久化控制意图,再在业务边界停新动作;恢复先校验事实与容量,再按优先级分批放量。
- 进阶追问:暂停指令自身丢失怎样发现?
- 进阶回答:以权威账本版本为准,执行者周期拉取或接收通知后回读;控制面核对已观察实例数和未响应实例清单。
- 口述答案:暂停至少支持全局、任务类型和单任务三种粒度,并记录暂停版本、原因、操作者、审批、开始时间和影响范围。控制面先把暂停意图写入权威账本,再停止目标范围的新领取;执行者观察到版本后不再启动新分片,在当前安全业务边界保存检查点。已经发出的外部计费、回调或轨迹请求不能取消为失败,而是按原 Idempotency Key(幂等键)查询并提交已确认结果;仍未知的保持未知态。暂停期间不删除 Lease(租约)、不清队列、不把执行中批量改回待运行,避免旧执行者和恢复执行者同时重做。恢复前校验任务定义与输入版本没有漂移、协调存储和业务权威源可用、旧 Lease(租约)已过期或被权威撤销、下游错误率与延迟回落、积压量可估算。放量先给关键实时任务小配额,再按任务类型和租户逐档恢复历史任务;每一档观察续租、Fencing Token(栅栏令牌)冲突、有效完成、最老年龄和资源余量。单任务人工暂停解除时还要核对原因是否消失、检查点是否可信和临时权限是否回收。恢复完成跨一个完整业务窗口验收,证明旧世代写入为零、外部结果唯一、未知态与人工清单收敛。这样暂停保护的是业务事实,恢复也有可审计门禁。控制面还要展示“暂停意图已落账、多少实例已观察、多少在途已到安全边界、多少外部动作仍未知”,避免一个绿色开关掩盖实际尚未停止的执行者。
- 追问1:暂停指令和任务完成同时发生怎么裁决?
- 直答1:按版本与前置状态条件提交;当前世代已完成且结果可确认时保留成功,暂停记录审计,不把终态倒退。
- 追问2:全局暂停是否应停止续租?
- 直答2:不应一刀切;在途查证和安全收尾可能仍需短期续租,策略应明确禁止新副作用并给出有界退出时间。
- 追问3:谁有权解除高风险暂停?
- 直答3:由预设角色和审批门禁控制,恢复原因、范围、版本与证据全部审计,不能由执行实例自行取消。
- 故障模型与恢复目标
问题:线上发现同一任务被两个实例执行,你如何排查?
- 考点:时间线、世代、业务重复与可逆止血。
- 回答思路:先证明是否为预期接管窗口,再验证业务副作用。
- 详细答案:排查先重建领取、续租、外部调用和提交四条时间线,再用世代与业务流水判断是正常接管还是重复事故。
- 进阶追问:日志时间不同步时怎样拼接时间线?
- 进阶回答:优先使用权威账本提交顺序、世代和业务事件因果,实例墙上时间只作辅助,并记录时钟偏差。
- 口述答案:我先限定任务类型、租户和时间范围,保存任务实例、分片、业务 Idempotency Key(幂等键)、两个持有者、Lease(租约)开始与到期、续租结果、世代、定义版本、检查点、外部单号和提交时间线。两个实例并发不一定是事故,旧实例失租后尚未停止、新实例按到期接管会形成预期双执行窗口;关键是资源端是否拒绝旧 Fencing Token(栅栏令牌),外部业务是否只产生一个结果。我会比较权威账本中的当前世代与两边提交条件,查询外部计费、回调或轨迹记录,并按业务键核对不可变流水。若只有一次旧世代拒绝且外部记录唯一,说明防线生效;若同键不同参数、两个外部单号或状态倒退,就冻结该任务类型自动重试,保留关键实时配额,逐笔建立差异清单。根因假设包括协调存储延迟、进程长暂停、续租线程饥饿、释放 Lease(租约)未比较持有者、资源端无栅栏条件、重试换业务键和消费确认丢失。修复后用故障注入重现:旧实例暂停超过到期,新实例接管提交,再恢复旧实例。恢复验收要看到旧世代写入持续为零、外部记录唯一、积压净下降和人工差异关闭,而不是只看两个进程都健康。为防止止血动作破坏证据,我会先导出目标任务与外部单据清单,再按任务类型暂停;若必须杀旧实例,也把进程、线程和请求上下文留档,并在恢复后逐笔核对未决范围。
- 追问1:可以先杀掉旧实例再查吗?
- 直答1:影响扩大时可以作为止血,但先尽量保留线程、续租和请求证据;杀进程不能替代业务对账。
- 追问2:两个实例世代相同意味着什么?
- 直答2:说明世代分配或领取原子性存在严重问题,或日志关联错误,应立即停止新领取并核对权威账本。
- 追问3:只有重复日志、没有重复业务记录要修吗?
- 直答3:仍要修复噪声和无效消耗,但严重级别低于业务重复;保留栅栏与幂等命中指标证明正确性防线有效。
- 协调服务线上排障
问题:Runner(执行器)可观测性和告警怎样设计?
- 考点:业务、调度、资源、审计四层信号与症状告警。
- 回答思路:从用户影响开始,再下钻 Lease(租约)和资源证据。
- 详细答案:可观测性以业务有效完成为顶层信号,以调度世代和资源等待解释原因,以审计链证明恢复动作。
- 进阶追问:高基数业务键会不会拖垮指标系统?
- 进阶回答:业务键进入结构化日志和链路,不作为无限指标标签;指标按任务类型、结果和失败域聚合后再下钻。
- 口述答案:我会先为每类任务定义业务成功:计费要有唯一费用结果,回调要有目标状态确认,轨迹要推进到允许状态,而不是仅看处理方法返回成功。业务层记录有效完成率、重复副作用、结果未知数量与最老年龄、人工差异;调度层记录领取、续租延迟、剩余安全窗口、接管、旧世代拒绝、状态分布、尝试次数和重试预算;容量层记录到达率、有效完成率、积压、最老任务年龄、线程池、连接池、数据库与下游高分位;审计层串联任务、分片、业务 Idempotency Key(幂等键)、持有者、世代、定义版本、前后状态、外部单号和 Trace(链路追踪)标识。告警优先面向症状:高优任务年龄越过目标、净完成率持续为负、旧世代实际写入非零、未知态增长或外部重复;单次重试、单次接管和短时续租抖动作为诊断信号,按窗口聚合,避免告警风暴。看板按任务类型和失败域分组,支持从业务键下钻到续租、外部调用与提交时间线。恢复门禁跨完整业务窗口,要求业务结果唯一、未知与人工队列收敛、积压下降、资源有余量和临时权限回收。真实阈值由历史基线、SLO(服务等级目标)和业务风险确定,缺监控证据时保持 E0(待核对)。告警规则还要有负责人、通知路由、抑制关系和可执行手册,并定期用失租、下游慢和积压注入验证;只创建图表而没有响应动作,不算完成可观测性建设。一次演练必须验证告警能关联到具体任务类型、业务键范围和处置手册,并记录从症状出现到止血完成的证据;若只能看到机器资源而无法确认业务恢复,观测链仍不合格。
- 追问1:接管率高但业务正常要告警吗?
- 直答1:可做预警或诊断,只有伴随任务年龄、世代冲突或业务风险时升级为症状告警。
- 追问2:日志量过大怎样控制?
- 直答2:关键状态迁移与人工动作全量审计,普通成功按结构化聚合或采样,但业务键和异常链路不能丢。
- 追问3:怎样证明告警真的可用?
- 直答3:故障注入续租延迟、旧世代提交、下游过载和积压,验证信号、路由、止血手册与恢复关闭条件完整触发。
- 指标告警与风暴治理
- 问题:协调存储不可用时,Runner(执行器)应该继续执行吗?
- 考点:可用性与正确性权衡、无法确认持有、降级边界。
- 回答思路:区分新领取、已有 Lease(租约)内收尾和外部高风险动作。
- 详细答案:协调存储失效时停止无法证明安全的新授权,已持有任务也只在确认窗口内做受限动作,失去确认后保守停写。
- 进阶追问:协调存储只读可用时能否续租?
- 进阶回答:不能;续租需要权威条件写。只读能力可用于取证,但不能延长授权或创建新世代。
- 口述答案:协调存储不可用时我会优先保护正确性。控制面停止新任务领取和所有无法权威分配世代的接管,避免多个实例各自认为自己是持有者。已有执行者若仍处于已确认的 Lease(租约)安全窗口,可以完成纯计算或可幂等、可查询的小步骤,并尽快保存本地证据;一旦续租超时导致无法确认仍持有,立即禁止新外部副作用和结果提交,只允许只读查询、上下文保全与有界收尾。支付计费、状态推进等高风险动作不能因为“业务着急”绕过 Fencing Token(栅栏令牌)和业务 Idempotency Key(幂等键);低风险、可重建的缓存刷新可以按预先批准的降级策略延迟或丢弃。协调存储恢复后,不是立刻全量抢占,而是先校验会话、权威时间、当前世代和过期 Lease(租约),对旧执行者可能发出的动作逐项查证,再以小配额接管。期间监控新领取为零、在途数量、剩余窗口、外部未知态和业务积压,人工操作也必须审计。若业务要求协调不可用时仍持续写入,就需要另一套有明确法定人数和世代保证的权威路径,而不是各实例本地选主。验收包括注入存储超时和网络分区,证明没有双领取成功、旧世代不能提交、恢复后外部业务唯一且积压可控。恢复演练还要覆盖存储部分可用和客户端重连,因为“少数实例可写、其余实例超时”比完全不可用更容易制造错误判断;门禁必须以权威提交结果而非客户端连接状态为准。
- 追问1:只读任务能否继续?
- 直答1:若没有持久副作用且结果可丢弃,可以按降级策略继续,但不能把其结果推进成权威业务状态。
- 追问2:本地缓存的 Lease(租约)到期时间可信么?
- 直答2:只能用于更保守地提前停止,不能用于延长授权或替代权威存储判断接管。
- 追问3:协调恢复后为何要先查旧动作?
- 直答3:旧执行者可能在失联前已完成外部调用,只是未提交任务结果,盲目接管会重复副作用。
- 注册发现与主节点选举
- 问题:外部系统既不支持幂等键也不能查询结果,任务如何恢复?
- 考点:不适用边界、风险分级、对账与人工裁决。
- 回答思路:明确自动恢复能力受限,不能用内部栅栏夸大保证。
- 详细答案:第三方缺少幂等和查询时,自动接管无法证明外部效果唯一,只能保留未知态并用对账与人工限制风险。
- 进阶追问:能否通过限制并发为一来获得恰好一次?
- 进阶回答:不能;单并发减少重叠调用,却消除不了请求成功而响应丢失后的重试歧义。
- 口述答案:这是端到端自动恢复的明确边界。内部 Lease(租约)和 Fencing Token(栅栏令牌)只能限制谁更新本地任务账本,不能撤销已经发往第三方的扣费、下单或通知。若外部既不接收稳定 Idempotency Key(幂等键),也不能按业务单号查询,超时后就无法区分“未执行”和“已执行但响应丢失”。我会先与业务划分风险:可重复且低成本的只读拉取可以有限重做;可能产生资金、订单、实物或用户通知的动作进入结果未知,停止自动重试。系统保存原请求参数摘要、发送时间、网络证据、可能的外部窗口和本地业务关联,通过后续账单、文件、回调或人工渠道对账;证据唯一后追加补偿或推进状态,证据冲突则人工裁决。若第三方允许改造,优先协商业务请求号、查询接口或异步回执;若不能改造,可在自有适配层建立一次性发出账本和严格人工重放门禁,但它仍不能证明第三方未执行。任务状态机要保留未知,告警看未知年龄和金额或对象风险,不能为了成功率把未知批量标失败。面试中我会直接说明,此场景无法实现严格自动恰好一次,只能通过风险隔离、保守查证、对账和人工把损失控制在边界内;任何自动重试方案都必须有业务批准和可接受重复成本。治理目标应推动第三方逐步提供请求号、查询或账单文件;在能力补齐前,自动化比例由可接受重复成本决定,不能由平台团队单方面扩大。
- 追问1:可以用更长 Lease(租约)避免这个问题吗?
- 直答1:不能;更长 Lease(租约)只减少接管频率,网络超时和响应丢失仍会产生外部未知态。
- 追问2:适配层记录“已发送”是否代表外部成功?
- 直答2:不代表,只能证明本地尝试发出;外部成功仍需回执、查询、账单或人工证据。
- 追问3:业务坚持自动重试怎么处理?
- 直答3:量化重复成本和最坏影响,设置低次数、长退避、金额或对象上限及人工抽样,并记录这是业务接受的风险。
- 项目案例与消息边界
- 问题:任务取消、暂停和完成并发时,状态机如何裁决?
- 考点:条件迁移、终态单调、不可逆副作用、版本竞争。
- 回答思路:以权威状态、世代和业务证据决定,不按消息到达顺序覆盖。
- 详细答案:并发命令由状态、版本、世代和业务结果共同条件裁决,终态保持单调,冲正另建可审计动作。
- 进阶追问:取消请求重复到达如何返回?
- 进阶回答:取消本身使用稳定命令键;同键同参数返回原裁决,同键冲突参数拒绝并告警。
- 口述答案:我会把取消、暂停和完成都建模为带版本的命令,而不是无条件更新状态。执行者提交完成时比较任务标识、当前 Lease(租约)世代、前置状态、暂停或取消版本、定义版本和结果摘要;控制面写暂停或取消也比较当前状态并记录原因。若外部动作已确认成功且当前世代完成提交先成立,后到取消不能把成功改成取消,只能按业务规则发起新的补偿、退款或撤销任务。若取消先在可逆阶段成功,迟到执行结果只进入审计,旧执行者不能复活任务;若请求已发出但结果未知,取消不能假设动作未发生,应进入取消待确认或人工状态,先查询外部结果。暂停通常阻止新分片,不否定已确认结果;执行者可在安全边界保存检查点,恢复时重新校验版本。终态成功、失败和取消不接受普通迟到事件倒退,合法冲正用新的业务动作和独立 Idempotency Key(幂等键)表示,保留原事实。测试要枚举完成先到、取消先到、暂停与续租交错、外部成功但本地未知、旧世代迟到等顺序,验证每个序列最多得到一个合法终态,业务副作用与任务状态一致。人工强制动作同样走审计和版本门禁,不能直接改库掩盖竞争。对批量任务还要逐分片裁决:已成功分片保持事实,未开始分片可取消,未知分片先查证,最终批次状态由各类数量守恒汇总,不能用一个批次标志覆盖局部差异。恢复放量前应抽取每类竞争序列回放,确认终态单调、外部副作用唯一且未知分片都有查证负责人;任何一类出现状态倒退,就撤销本次规则变更并冻结相关人工强制入口。
- 追问1:取消先成功但外部随后确认完成怎么办?
- 直答1:说明取消时存在未知动作,保留两侧事实并按业务政策补偿或人工裁决,不能覆盖其中一条记录。
- 追问2:暂停状态能否直接迁移到成功?
- 直答2:已在暂停前确认且由当前世代提交的结果可以成功;未确认部分必须查证,不能仅凭迟到回调推进。
- 追问3:为什么冲正要用新业务动作?
- 直答3:冲正是对已发生事实的反向业务,不应删除或改写原结果;新键和引用关系才能审计因果。
- 任务状态机详细节
- 问题:大促前如何为 Runner(执行器)做容量与故障演练?
- 考点:工作负载、单点瓶颈、故障域、恢复时间与退出条件。
- 回答思路:先取真实输入,阶梯压测后注入失租、重启、积压和下游故障。
- 详细答案:容量演练从真实工作负载建模,验证单故障域损失后的关键任务能力,并以业务错误作为硬退出条件。
- 进阶追问:压测环境下游额度与生产不同怎么办?
- 进阶回答:明确差异,用受控桩验证协议失败,用生产小流量灰度校准额度和延迟,结论分层而不外推。
- 口述答案:容量准备先收集各任务类型的峰值到达率、每任务分片数、高分位执行时间、外部调用次数、重试和重复比例、热点租户、线程连接占用、数据库写放大与下游额度,不能先拍执行实例数。用到达率乘高分位时间估算在途,再分别计算支付回调、计费、轨迹和普通同步的资源舱;容量门槛是在失去一个约定故障域后,关键任务仍满足年龄目标,历史任务可以延迟。压测采用接近生产的任务分布和阶梯流量,观察有效完成率、最老年龄、续租延迟、Fencing Token(栅栏令牌)冲突、重试比、线程连接等待和业务结果唯一。随后注入执行者长暂停超过 Lease(租约)、进程执行中重启、协调存储延迟、重复消息、下游超时未知、消费者积压与毒任务,验证旧世代拒绝、原 Idempotency Key(幂等键)查证、检查点恢复和分舱限流。E3(演练设计)若故障形成 60000 个积压,恢复净完成每秒 40 个,理论需要 25 分钟;演练还要验证提速时数据库与下游保留余量。退出条件包括任何重复计费或状态倒退、旧世代实际写入、净完成率非正、未知态持续增长或关键任务年龄超目标。真实峰值和效果必须从压测报告与监控取证,不能把演练输入说成生产成绩。演练结束还要处理故障期间生成的历史任务、未知请求和临时配置,并复测恢复门禁;只证明峰值能跑而没有证明故障后能收敛,不算通过。
- 追问1:为什么均匀任务分布不够?
- 直答1:热点租户、单分片和共享下游会形成局部串行瓶颈,总吞吐合格也可能让关键任务超龄。
- 追问2:演练中何时立即停止加压?
- 直答2:出现业务重复、状态倒退、旧世代写入、资源保护失效或净恢复非正时立即停止并保全证据。
- 追问3:扩容执行者前先看什么?
- 直答3:确认瓶颈确在执行计算且数据库、协调存储和外部额度有余量,否则扩容只放大等待与重试。
- 压测恢复与故障演练
- 问题:请用八段式完整讲述 Runner(执行器)调度项目,并说明证据边界。
- 考点:项目叙事、不变量、失败恢复、结果证据和复盘。
- 回答思路:严格按背景、约束、目标、方案、权衡、失败、结果证据、复盘组织。
- 详细答案:八段式把项目事实、业务约束、设计取舍、失败恢复和证据边界串成可验证的完整叙事。
- 进阶追问:八段式中哪一段最能区分资深候选人?
- 进阶回答:通常是失败、结果证据和复盘,因为它们要求说明未知态、恢复门禁、不适用边界和可核验事实。
- 口述答案:背景上,简历材料能证明常驻任务承载订单、计费、轨迹和回调重试,这是 E1(直接证据);具体调度框架、部署拓扑和参数没有源码证据,保持 E0(待核对)。约束是任务会重复、外部调用会超时未知、执行者会重启和失租,关键任务还会与历史补偿争资源。目标是同一 Lease(租约)世代只有当前持有者可提交,同一业务意图只产生一次效果,任何恢复动作可追踪。方案上把任务定义、实例和分片分离,固定逻辑分片,领取递增 Fencing Token(栅栏令牌),资源端条件拒绝旧世代;业务侧复用 Idempotency Key(幂等键),状态机保留结果未知、暂停、重试等待和检查点,任务类型与租户分舱。权衡是较长 Lease(租约)减少误接管却延长恢复,资源保留保护支付与计费却降低满载利用率。失败处理上,失租后旧世代只审计,执行中重启先查外部结果,重试按分类、预算、退避和随机抖动,积压按净完成率分批排空。结果证据只用任务账本、业务单据、旧世代拒绝、最老年龄、未知态和审计记录;真实提升比例缺证据时不声称。复盘会持续演练长暂停、协调存储抖动、重复消息和下游过载,并明确外部不可幂等、不可查询时自动恢复不适用。这样既能讲架构深度,也不把 E3(演练设计)包装成上线事实。被追问个人贡献时,我只定位到可证明的任务、代码、评审、发布或排障动作,团队结果与建议方案分开;这比给出无法核对的吞吐提升更可信。
- 追问1:你在这个项目中的个人贡献如何证明?
- 直答1:定位到实际负责的任务类型、代码、配置、评审、发布或排障记录,并区分个人动作与团队结果;当前未核对部分保持 E0(待核对)。
- 追问2:面试官要求真实效果数字时怎么回答?
- 直答2:给出应取的有效完成率、最老年龄、重复副作用和恢复时间口径及取证路径,不用演练数字冒充生产收益。
- 追问3:这段话术最核心的一句话是什么?
- 直答3:Lease(租约)控制谁能执行,Fencing Token(栅栏令牌)控制谁能提交,业务幂等控制最终效果是否重复。
- 可观测性与八段式话术
