Runner(执行器)调度、租约、栅栏、隔离与恢复方案
案例定位:本文是 Runner(执行器)调度与恢复的端到端候选方案,不是生产复盘。旧根与既有并发、MQ(消息队列)、Zookeeper(分布式协调服务)和可观测性材料能够证明任务状态、分片抢占、退避重试、线程隔离与人工补偿是可讲方向,标为 E2(已有材料映射);本文的租约、心跳、Fencing Token(栅栏令牌)、量级、阈值、容量、恢复时长和成本均为 E3(演练证据);真实源码、表结构、调度算法、部署规模、运行指标、事故与收益保持 E0(待核对)。本次正文、图源及同名真实渲染图片标为 E1(直接证据),但不能反推生产已经采用本方案。

正式图源见 case-runner-scheduling-recovery.puml。方案合同来自 52/00 案例索引,并回链 Java(编程语言)线程池与恢复、MQ(消息队列)可靠性、Zookeeper(分布式协调服务)选主与栅栏和 SLO(服务等级目标)事故闭环。
正式图解读:参与者覆盖任务管理端、调度控制器、任务库、MQ(消息队列)、新旧 Runner(执行器)、副作用服务与审计工作台。前提是任务定义、触发实例、执行尝试和业务结果分别持久化,租约使用权威时间裁决,Fencing Token(栅栏令牌)在真正资源端原子校验。正常路径是注册、生成唯一触发、分片领取、获得租约与令牌、心跳、幂等执行、结果提交和审计;失败路径覆盖失租、重复投递、旧执行者迟到写、重试风暴、消费者积压、执行中重启与人工接管。结论是调度唯一只减少重复尝试,业务幂等才约束重复副作用,而锁或租约本身不能远程阻止旧持有者继续写。
1. 任务注册、触发语义与事实边界
1.1 把“定时跑一下”改写为可版本化、可追溯、可停止的任务合同
任务注册至少固化任务标识、任务类型、租户或业务域、参数摘要、计划版本、触发规则、时区、错过策略、优先级、执行截止时间、最大在途数、幂等键模板、重试预算、资源组、暂停状态与责任人。注册定义回答“应该何时产生什么执行意图”,触发实例回答“某个计划时刻是否已经被创建”,执行尝试回答“谁在本轮处理”,业务结果回答“副作用是否真正生效”;四者不能压成一行状态。周期计划、一次性补偿、事件触发与人工补跑使用同一执行合同,但拥有不同触发身份。修改计划要生成新版本,旧触发保留原版本,禁止无痕改写历史。
| 对象 | 稳定身份 | 权威字段 | 成功证据 | E0(待核对)来源 |
|---|---|---|---|---|
| 任务定义 | 任务标识 | 类型、参数、计划版本、资源组 | 版本可查询且审批完整 | 注册接口与配置 |
| 触发实例 | 任务标识加计划时刻加版本 | 预计时间、实际时间、错过策略 | 唯一触发键存在 | 触发表与唯一索引 |
| 执行尝试 | 触发标识加执行代次 | 持有者、租约、令牌、错误类 | 尝试终态可追溯 | 执行日志与任务表 |
| 业务结果 | 业务幂等键加动作 | 状态、结果摘要、副作用编号 | 业务状态机唯一生效 | 下游接口与流水 |
| 人工处置 | 异常单号 | 操作者、原因、审批、前后摘要 | 处置后再次对账 | 工单与审计记录 |
sequenceDiagram
participant O as 任务管理端
participant R as 注册服务
participant D as 任务数据库
participant S as 调度控制器
O->>R: 提交定义、计划、资源组和重试预算
R->>D: 创建不可变计划版本
alt 同任务同版本摘要一致
D-->>R: 返回既有版本
else 版本复用但摘要冲突
D-->>R: 拒绝并记录审计
else 新计划版本
D-->>R: 保存定义并允许调度
end
S->>D: 按计划版本生成唯一触发实例
D-->>S: 返回新增、幂等命中或暂停拒绝图解读:节点分别拥有管理意图、注册校验、任务事实与触发资格;箭头表示先版本化注册,再产生触发。前提是计划时区和摘要稳定;正常路径只创建一个版本和一个触发,失败路径拒绝版本复用冲突或暂停任务;结论是“内存里有一个定时器”既不能证明任务已注册,也不能支撑重启后的缺口恢复。
数据演绎 1:注册与触发身份守恒
E3(演练证据):任务 JOB-7 原计划版本 12 在 10:00 触发,管理端因超时重复提交 6 次,其中 5 次摘要相同、1 次参数不同。注册唯一约束应得到 1 个版本、4 次幂等命中和 1 次冲突拒绝。调度控制器重复扫描 3 次,唯一触发键 JOB-7@10:00@12 只产生 1 个触发实例。状态从“已启用定义”到“待执行触发”;观测信号是版本冲突、触发冲突和错过数量;结论是注册去重、触发去重与业务副作用去重是三条不同防线。
热门面试题
- 问题:任务定义、触发实例和执行尝试为什么要拆开?
- 考点:身份分层、重启恢复、审计。
- 回答思路:分别回答应该做、何时做和谁在做。
- 详细答案:任务定义描述长期计划,触发实例代表某个计划时刻的执行意图,执行尝试记录一次具体领取。若混成一行,重试会覆盖原错误,修改计划会改写历史,重启后也无法区分漏触发还是执行失败。拆开后可按触发键补扫、按执行代次接管,并按业务结果验收。
- 进阶追问:一次性人工补跑属于新触发还是新任务?
- 进阶回答:通常复用任务定义并创建带原因、操作者和独立触发身份的新实例;参数语义变化较大时创建新定义版本,不能伪装成原计划重试。
- 问题:为什么计划修改必须生成新版本?
- 考点:历史可解释性和在途兼容。
- 回答思路:说明旧触发必须按旧语义完成或终止。
- 详细答案:时区、参数、分片和错过策略变化都会改变执行集合。若原地覆盖,在途任务重启后可能读取新参数,审计也无法解释为何同一触发产生不同结果。新版本让旧实例固定旧快照,新触发使用新规则,并允许按版本暂停、回滚和对账。
- 进阶追问:旧版本何时可以删除?
- 进阶回答:所有关联触发、执行、补偿和审计超过规定保留期且可证明无恢复需求后才能归档;不能因新版本启用就立即删除。
- 问题:没有真实源码时如何讲 Runner(执行器)项目?
- 考点:E0(待核对)至 E3(演练证据)的事实边界。
- 回答思路:先说现有材料能证明什么,再给候选设计与取证清单。
- 详细答案:可以把任务状态、抢占、退避、线程隔离和人工补偿讲成 E2(已有材料映射),把本文租约、令牌和容量数字明确说成 E3(演练证据)。真实任务量、表字段、线程池、事故和收益保持 E0(待核对),并列出注册表、配置、执行日志、副作用流水和暂停恢复记录作为升级证据。
- 进阶追问:图已经渲染能否证明生产采用该架构?
- 进阶回答:不能。渲染只能证明文档资产可复现,生产结论仍需代码、配置、部署和运行证据。
2. 触发计算、唯一计划键、分片与错过补扫
2.1 调度器只生成唯一执行意图,不承诺业务只发生一次
调度控制器读取启用的计划版本,以“任务标识、计划时刻、计划版本、必要业务分区”生成唯一计划键。周期计算必须明确时区、夏令时、起止边界、补偿窗口和错过策略:跳过、只补最近一次、补全窗口或转人工。大量任务按稳定哈希或时间桶分片扫描,分片只决定谁计算触发,不改变触发唯一键。主节点切换或扫描重启后,从已确认水位前留出重叠窗口补扫;重复扫描依靠数据库唯一约束返回原触发。MQ(消息队列)只负责唤醒执行,不是触发事实源,消息丢失可由待执行扫描补发,消息重复只形成领取竞争。
| 控制点 | 推荐设计 | 防止的问题 | 不能替代 |
|---|---|---|---|
| 计划时区 | 注册时固定时区和版本 | 跨区与夏令时歧义 | 业务截止时间确认 |
| 唯一计划键 | 任务、时刻、版本、分区 | 重复扫描与主从切换 | 业务幂等键 |
| 扫描水位 | 已确认水位加重叠窗口 | 重启漏扫 | 触发唯一约束 |
| 任务分片 | 稳定映射加分片代次 | 扫描热点与重复负责 | 执行租约 |
| 错过策略 | 跳过、最近、补全、人工 | 停机后洪峰或静默缺口 | 容量与风险审批 |
sequenceDiagram
participant C as 调度控制器
participant D as 任务数据库
participant Q as MQ(消息队列)
participant W as Runner(执行器)
C->>D: 读取计划版本和已确认水位
C->>D: 重叠补扫并插入唯一计划键
alt 唯一键首次出现
D-->>C: 新触发待执行
C->>Q: 发布触发标识
else 已由旧控制器创建
D-->>C: 冲突后返回原触发
end
Q->>W: 至少一次唤醒
W->>D: 读取当前触发并竞争租约图解读:控制器负责计算,数据库负责唯一触发,MQ(消息队列)负责唤醒,Runner(执行器)负责竞争执行。前提是计划键和水位持久化;正常路径创建并投递,失败路径通过重叠补扫吸收漏消息和切换重复;结论是 MQ(消息队列)确认、调度主节点唯一和业务副作用唯一不能相互替代。
数据演绎 2:分片扫描与重叠补扫
E3(演练证据):共有 24,000 个启用任务,每 10 秒扫描一次,分为 16 个逻辑分片,单片平均 24,000 / 16 = 1,500 个任务。控制器在 10:00:25 重启,最后确认水位为 10:00:20,恢复时从 10:00:10 重叠补扫到当前,共覆盖 3 个时间桶。若窗口内应产生 3,600 个触发,其中 3,450 个已存在,则唯一约束新增 150 个、冲突返回 3,450 个。状态从“存在缺口风险”变为“差集补齐”;观测是每片扫描耗时、触发冲突、最老漏触发年龄和补扫新增数;结论是重叠扫描宁可产生可解释冲突,也不能依赖不可靠内存水位跳过历史。
热门面试题
- 问题:调度主节点唯一为什么仍可能重复触发?
- 考点:长暂停、响应丢失、补扫重叠。
- 回答思路:把当前计划者资格和触发持久化分开。
- 详细答案:旧主节点可能在会话失效前已经插入触发但未收到响应,新主节点接管后又补扫同一时刻;同一主节点也可能因数据库响应丢失重试。主节点唯一只能减少并发计算,最终必须由唯一计划键吸收同一触发的重复创建。
- 进阶追问:只用内存时间轮能否保证不漏?
- 进阶回答:不能,重启和发布会丢失内存进度;至少要持久化定义、触发和水位,并能从权威计划重算缺口。
- 问题:停机后应该补全所有错过的周期吗?
- 考点:错过策略与失败成本。
- 回答思路:按可叠加性、时效和副作用分类。
- 详细答案:报表快照可能只补最近一次,逐分钟统计可按窗口补全,库存释放和支付对账不能静默跳过,而过期通知可能无业务价值。策略要随任务版本固化,并计算恢复流量;高风险或超大缺口超过预算时转人工审批。
- 进阶追问:补扫为何要留重叠窗口?
- 进阶回答:最后水位可能已写但触发提交或消息发布尚未闭合;向前重叠配合唯一键,可以用少量重复查询换取不漏触发。
- 问题:分片键选择错误会有什么后果?
- 考点:热点、迁移和顺序边界。
- 回答思路:比较任务数量均衡与实际执行成本均衡。
- 详细答案:只按任务数均分可能把所有长任务或同一第三方渠道放到一个分片,扫描和调用都形成热点;随意改分片数又会让在途任务重复负责。应以稳定业务键、历史成本和下游配额评估映射,迁移时推进分片代次并保留唯一触发防线。
- 进阶追问:分片代次等于执行令牌吗?
- 进阶回答:不等于。分片代次决定谁扫描一组任务,执行令牌决定谁能提交某次触发,两者作用域不同。
3. 任务状态机、数据模型与业务不变量
3.1 允许重复尝试,但每个状态与副作用都必须能被权威事实解释
触发实例建议经历待执行、执行中、待重试、暂停中、已暂停、补偿中、已成功、已失败、已取消和人工处理中。执行尝试单独保存执行代次、持有者、租约、Fencing Token(栅栏令牌)、检查点、错误分类和开始结束时间;业务结果单独保存幂等键、动作、参数摘要、外部请求号与终态。核心不变量是:一个唯一计划键最多一个触发实例;同一时刻一个触发最多一个当前租约;执行资格过期后旧令牌不得提交;同一业务幂等键最多一个有效副作用;状态只能按合法边迁移;所有自动终态都能由尝试、结果和审计复算。
| 不变量 | 原子约束 | 在线防线 | 最终复核 |
|---|---|---|---|
| 触发唯一 | 唯一计划键 | 冲突返回原实例 | 计划差集 |
| 当前执行资格唯一 | 租约条件更新加递增令牌 | 抢占影响行数 | 当前令牌核对 |
| 旧写不可生效 | 资源端令牌条件 | 低令牌拒绝 | 旧写拒绝审计 |
| 业务副作用唯一 | 幂等键加动作唯一 | 状态机与唯一约束 | 业务流水对账 |
| 状态可解释 | 前置状态加版本条件 | 非法迁移拒绝 | 尝试、结果、审计守恒 |
stateDiagram-v2
[*] --> 待执行
待执行 --> 执行中: 领取租约与令牌
执行中 --> 待重试: 可恢复失败且预算未尽
待重试 --> 待执行: 到达下次执行时间
执行中 --> 暂停中: 管理请求或全局闸门
暂停中 --> 已暂停: 检查点已提交
已暂停 --> 待执行: 审批恢复并生成新代次
执行中 --> 补偿中: 主步骤已产生需逆转副作用
补偿中 --> 已成功: 业务目标已由补偿收敛
执行中 --> 已成功: 结果幂等提交
执行中 --> 已失败: 永久错误或预算耗尽
执行中 --> 人工处理中: 结果未知或不可逆图解读:状态节点表达允许动作,箭头表达带版本与令牌条件的合法迁移。前提是执行尝试和业务结果不被状态字段覆盖;正常路径到成功,失败路径按可恢复、需补偿、永久错误和未知结果分流;结论是“执行线程结束”不等于任务成功,“任务成功”也必须由业务结果证明。
数据演绎 3:任务、尝试与业务结果守恒
E3(演练证据):某批次 1,000 个触发,终态为 760 个成功、80 个失败、70 个暂停、40 个人工处理中,另有 30 个待重试和 20 个执行中,满足 760 + 80 + 70 + 40 + 30 + 20 = 1,000。执行尝试共 1,180 次,说明有 180 次重复或重试;业务结果表只有 760 个成功结果,其中 14 个尝试命中既有幂等结果。状态变化不能把 1,180 次尝试说成 1,180 个业务完成;观测信号是尝试放大、幂等命中、非法迁移和未知年龄;结论是调度层与业务层要分别守恒。
热门面试题
- 问题:为什么任务状态和执行尝试不能放在同一行反复覆盖?
- 考点:历史证据、重复尝试和接管。
- 回答思路:用第一次失败、第二次接管、第三次人工处置说明。
- 详细答案:覆盖会丢失持有者、令牌、错误、耗时和检查点,无法判断重试是否同一错误,也无法证明旧执行者是否迟到。任务行保存当前聚合状态,尝试表保存每一代执行事实,结果表保存业务副作用,三者通过标识关联。
- 进阶追问:尝试表会不会太大?
- 进阶回答:按审计期分区归档,保留关键摘要和外部关联;容量问题不能通过删除当前恢复证据解决。
- 问题:执行尝试重复一定算故障吗?
- 考点:至少一次执行与业务效果。
- 回答思路:接受重复计算,拒绝重复有效副作用。
- 详细答案:确认丢失、租约接管和进程重启都可能产生重复尝试,这是可恢复调度的正常边界。只要旧令牌被拒绝、幂等结果唯一且状态可解释,重复尝试不等于业务故障;但重复率异常升高仍要排查续租、超时和稳定性。
- 进阶追问:哪些任务可直接丢弃重复结果?
- 进阶回答:可重建投影可按版本拒绝旧结果;库存释放、支付补偿和外部通知必须查询原结果并保留审计,不能无证据丢弃。
- 问题:状态机为什么还需要数据库唯一约束?
- 考点:并发穿透与多层防线。
- 回答思路:状态约束合法顺序,唯一约束裁决同一身份。
- 详细答案:两个执行者可能同时读到可执行状态,应用层判断都通过;条件更新决定当前状态胜者,唯一约束阻止同一计划键或业务动作被并发插入。状态机、版本和唯一约束分别防非法迁移、并发覆盖和重复身份,不能互相替代。
- 进阶追问:唯一冲突后直接报错可以吗?
- 进阶回答:应读取既有记录,参数摘要相同则返回幂等结果,不同则标记冲突并告警,不能统一当系统异常重试。
4. 总体架构、调度唯一与业务幂等边界
4.1 控制面决定谁可以尝试,业务权威层决定什么结果可以生效
方案分为注册与管理面、触发控制面、执行数据面、业务副作用面和审计恢复面。控制面可用主节点或数据库分片扫描减少重复计划,数据面通过 MQ(消息队列)或待执行表获得唤醒,Runner(执行器)按资源组竞争租约,副作用服务以业务幂等键、状态机和 Fencing Token(栅栏令牌)裁决,恢复面扫描失租、未知、积压与差异。调度唯一性只承诺某时刻尽量由一个计划者或持有者推进;业务幂等性承诺即使消息、尝试或调用重复,同一业务动作也只产生一个可解释结果。前者优化效率,后者守住正确性。
| 层次 | 决定的问题 | 关键机制 | 失效后的现象 | 最终责任 |
|---|---|---|---|---|
| 注册管理面 | 应该运行什么 | 版本、审批、暂停 | 错计划或无审计变更 | 任务定义 |
| 触发控制面 | 何时产生意图 | 唯一计划键、水位、分片 | 重复或漏触发 | 触发实例 |
| 执行数据面 | 谁在当前尝试 | 租约、心跳、令牌 | 新旧执行者并存 | 执行尝试 |
| 业务副作用面 | 哪个结果生效 | 幂等键、状态机、资源端栅栏 | 重复写或旧写 | 业务权威记录 |
| 审计恢复面 | 最终是否闭环 | 对账、补偿、人工接管 | 技术恢复但业务未恢复 | 差异与工单 |
sequenceDiagram
participant S as 调度控制器
participant D as 任务数据库
participant Q as MQ(消息队列)
participant R as Runner(执行器)
participant B as 业务副作用服务
participant A as 审计恢复器
S->>D: 以唯一计划键创建触发
D-->>Q: 发布触发标识
Q->>R: 重复也可能发生的唤醒
R->>D: 条件领取租约与令牌
R->>B: 携带业务幂等键和令牌提交
B-->>R: 新结果、既有结果或旧令牌拒绝
R->>D: 按当前令牌提交任务终态
A->>D: 扫描未知、失租和差异
A->>B: 查单、补偿或创建人工异常单图解读:触发、传输、执行、业务和恢复各有权威边界;箭头允许触发和消息重复,但副作用端只接受合法业务身份与当前令牌。前提是业务服务参与裁决;正常路径一次闭环,失败路径由审计恢复器查询和补偿;结论是只在调度器本地加锁无法保证跨进程、跨存储和第三方副作用正确。
数据演绎 4:调度唯一与业务幂等分层
E3(演练证据):同一触发因控制器重扫出现 4 次创建尝试,唯一计划键收敛为 1 行;MQ(消息队列)因确认丢失投递 3 次,两个 Runner(执行器)竞争后产生 2 次执行尝试;第一次外部调用已成功但响应丢失,第二次沿用业务幂等键查询并返回原结果。调度冲突率为 (4 - 1) / 4 = 75%,尝试放大为 2 / 1 = 2,有效业务结果仍为 1。状态从待执行经两次尝试到已成功;观测必须同时展示计划冲突、租约冲突、幂等命中和业务结果;结论是“有重复执行日志”不等于“发生重复业务效果”。
热门面试题
- 问题:调度唯一性和业务幂等性有什么区别?
- 考点:资格与结果的责任分离。
- 回答思路:一个回答谁尝试,一个回答重复后结果是否唯一。
- 详细答案:调度唯一性通过选主、分片、租约和唯一触发减少同时执行者,但网络分区、长暂停和响应丢失仍会产生重复尝试。业务幂等性用稳定业务键、状态机、唯一约束和查单让重复尝试返回同一结果。关键任务两者都要,不能用前者冒充后者。
- 进阶追问:只有幂等是否可以不要租约?
- 进阶回答:正确性可能仍守住,但会浪费资源、放大第三方请求和锁竞争;租约用于效率、接管和可观测所有权,仍有价值。
- 问题:为什么分布式锁不能阻止旧持有者写?
- 考点:协调状态与进程执行权分离。
- 回答思路:用会话过期后旧进程恢复说明。
- 详细答案:锁服务只能撤销协调资格,无法远程杀死旧线程、关闭数据库连接或撤回已经发出的请求。旧进程在长暂停后恢复,仍可能凭本地状态继续写。真正阻断必须在数据库、对象指针或消费者等资源端原子比较 Fencing Token(栅栏令牌)和业务状态。
- 进阶追问:缩短租约能解决吗?
- 进阶回答:不能,只会更快产生新持有者并增加误接管;旧写仍需资源端栅栏,租约长度只在接管速度和误过期之间取舍。
- 问题:MQ(消息队列)做到只投递一次是否就不需要业务幂等?
- 考点:传输语义与业务提交窗口。
- 回答思路:说明生产、消费和业务事务之间仍有未知窗口。
- 详细答案:即使代理层减少重复,消费者也可能业务提交成功后在确认前崩溃,或者外部调用成功但本地回写失败。传输系统看不到完整业务事务,因此仍要用幂等键、状态机、查单和对账处理重复与未知结果。
- 进阶追问:消费确认应放在业务处理前还是后?
- 进阶回答:通常在业务结果持久化后确认,接受重复投递并由幂等吸收;先确认会在进程崩溃时形成静默丢失。
5. 抢占、租约、心跳与执行中重启
5.1 租约定义有限期资格,心跳只能续资格,不能证明任务已经完成
领取使用数据库权威时间执行条件更新:触发处于待执行或待重试、下次时间已到、暂停闸门关闭、现有租约为空或过期,才写入持有者、租约截止、递增执行代次和 Fencing Token(栅栏令牌)。心跳间隔必须小于租约,并在当前持有者、令牌和执行中状态都匹配时续租;连续失败达到阈值后,Runner(执行器)立即关闭本地提交闸门,在安全点保存检查点并停止新副作用。执行中重启时不把内存线程当事实:新实例先加载未过期租约、过期尝试和检查点,未过期的他人任务等待或查询,过期任务以新令牌接管,外部结果未知则先查单。
| 阶段 | 条件 | 允许动作 | 失败动作 | 证明完成所需证据 |
|---|---|---|---|---|
| 领取 | 状态可执行且租约空或过期 | 写持有者、截止与新令牌 | 冲突后读取当前持有者 | 只证明获得资格 |
| 心跳 | 持有者、令牌、状态匹配 | 延长截止并记录进度 | 关闭提交闸门 | 只证明资格尚有效 |
| 检查点 | 当前令牌有效 | 保存已完成步骤和摘要 | 保留旧检查点不覆盖 | 可从稳定边界恢复 |
| 重启恢复 | 原租约过期或主动移交 | 新令牌接管 | 未过期不强抢 | 新尝试与旧尝试可关联 |
| 完成提交 | 当前令牌与业务结果有效 | 条件转成功 | 零行后查当前状态 | 业务结果与任务终态一致 |
sequenceDiagram
participant A as 旧 Runner(执行器)A
participant D as 任务数据库
participant B as 新 Runner(执行器)B
participant X as 副作用服务
A->>D: 领取任务,获得令牌 41
A->>D: 心跳续租并提交检查点 8
A--xD: 进程暂停,连续续租失败
D-->>B: 租约过期可接管
B->>D: 领取并获得令牌 42
B->>X: 携带业务键和令牌 42 执行
X-->>B: 返回唯一业务结果
A->>X: 恢复后携带令牌 41 迟到提交
X-->>A: 拒绝旧令牌并要求停止图解读:任务数据库裁决租约,副作用服务裁决结果;箭头展示旧 Runner(执行器)失租、新实例接管和旧实例迟到写。前提是心跳与提交都校验同一令牌;正常路径由 42 完成,失败路径拒绝 41;结论是检测到失租后本地停止很重要,但资源端栅栏才是旧进程不污染事实的最后防线。
数据演绎 5:租约、心跳与接管窗口
E3(演练证据):租约为 60 秒,每 15 秒心跳,连续 2 次续租失败关闭提交闸门。A 在第 22 秒暂停,第 60 秒租约过期,B 在第 63 秒获得令牌 42;最坏接管等待约 63 - 22 = 41 秒。A 第 82 秒恢复,已超过租约 22 秒,任何令牌 41 的检查点和业务写都应被拒绝。若正常心跳延迟高分位为 8 秒,则 15 秒间隔仍有余量;若全停顿常达 70 秒,单纯拉长租约只会推迟恢复,应拆短步骤并强化栅栏。观测信号为续租延迟、失租停机时延、接管耗时和旧令牌拒绝数。
热门面试题
- 问题:租约应该设多长?
- 考点:误接管与恢复时间取舍。
- 回答思路:从心跳延迟、长暂停、步骤边界和业务恢复目标推导。
- 详细答案:租约要覆盖正常网络抖动、调度延迟和短暂停顿,并显著大于心跳间隔;同时不能超过业务可接受接管时间。应按续租延迟和暂停高分位做故障实验,长任务通过检查点拆分,不用极长租约掩盖不可恢复步骤。
- 进阶追问:能用 Runner(执行器)本机时间判断过期吗?
- 进阶回答:不应由各客户端独立裁决,时钟漂移会产生不同结论;使用任务数据库或协调服务的权威时间与原子条件更新。
- 问题:心跳成功能否证明任务健康?
- 考点:进程活性与业务进度分离。
- 回答思路:心跳只证明当前线程还能续租。
- 详细答案:任务可能在死循环、下游阻塞或反复处理同一检查点,但心跳线程仍正常。心跳应附带最后检查点和进度时间,另监控无进展年龄、外部在途和业务结果;是否完成仍由状态机和业务权威记录裁决。
- 进阶追问:心跳线程应该与业务线程共池吗?
- 进阶回答:不宜完全共池,否则业务池耗尽会让所有任务同时误失租;应有小型独立控制池和严格上限,同时防止控制池脱离业务进度无限续租。
- 问题:执行中重启如何避免既漏做又重复做?
- 考点:检查点、未知结果、幂等接管。
- 回答思路:先恢复事实,再决定从哪里继续。
- 详细答案:重启后扫描执行中任务,未过期租约不直接抢占;过期后以新令牌接管,读取最后稳定检查点。已发出的外部动作按业务键查单,明确成功就补本地状态,明确未执行才沿用同键重试,仍未知转人工。重复读取可以接受,重复有效副作用不可以。
- 进阶追问:检查点应多频繁提交?
- 进阶回答:按重做成本、提交开销和一致性边界权衡,必须落在已完成且可幂等复用的步骤之后,不能先推进再执行。
6. Fencing Token(栅栏令牌)、迟到写与外部副作用
6.1 锁只能撤销资格,资源端单调代次才能拒绝旧执行者
Fencing Token(栅栏令牌)是同一任务或受保护资源每次成功授权时递增的代次,不是随机持有者标识。任务库在领取事务内生成新令牌,Runner(执行器)将它随检查点、结果、消息和可控外部写传递;资源端在同一原子操作中验证当前状态与令牌,并保存已接受最大代次或要求恰好匹配当前代次。随机请求号解决重试去重,持有者标识防误释放,Fencing Token(栅栏令牌)解决新旧授权顺序,业务状态机解决动作是否合法。第三方接口不支持令牌时,不能宣称已经栅栏,只能用稳定请求号、查单、补偿、对账和人工接管降低风险。
| 目标资源 | 栅栏落点 | 原子条件 | 旧写证据 | 仍需机制 |
|---|---|---|---|---|
| MySQL(关系型数据库)任务结果 | 结果行或资源版本 | 当前令牌匹配且状态合法 | 影响行数为零 | 幂等键、事务、对账 |
| MQ(消息队列)消费结果 | 业务消费者 | 消息令牌不低于当前代次 | 旧消息隔离计数 | 消费幂等、死信 |
| 对象存储产物 | 数据库正式指针 | 指针发布校验令牌 | 旧对象不被引用 | 内容摘要、孤儿清理 |
| 第三方物流 | 通常无法校验令牌 | 稳定业务请求号 | 查到既有外部结果 | 查单、补偿、人工 |
| 支付或库存动作 | 业务状态与流水 | 幂等键、状态、令牌共同条件 | 旧动作拒绝或原结果返回 | 金额/数量不变量与对账 |
sequenceDiagram
participant A as 旧执行者令牌 77
participant B as 新执行者令牌 78
participant D as 权威资源
participant T as 第三方系统
B->>D: 状态合法且令牌 78 的条件写
D-->>B: 接受并保存最大令牌 78
A->>D: 长暂停恢复后提交令牌 77
D-->>A: 拒绝陈旧代次
A->>T: 旧请求可能早已发出
T-->>A: 响应未知或迟到
B->>T: 按稳定业务号查询原结果
T-->>B: 返回成功、明确失败或仍未知图解读:权威资源能直接拒绝旧令牌,第三方系统则可能只支持业务号查询。前提是令牌在资源端原子校验;正常路径由 78 推进,失败路径拒绝 77 并查证外部未知结果;结论是栅栏解决本地可控写,不能撤回已经到达不支持令牌的第三方请求。
数据演绎 6:旧执行者迟到写
E3(演练证据):A 以令牌 77 调用库存释放动作 RES-900:release,请求在网络中延迟;租约过期后 B 以令牌 78 查到尚无本地结果,先向可查询下游查单。若下游返回 A 已释放,B 只补齐结果;若明确未执行,B 沿用同一幂等键提交;若仍未知,转人工。随后 A 的本地回写到达 MySQL(关系型数据库),条件 current_token = 77 不成立而影响零行。最终有效释放数为 1,旧写拒绝 1 次;观测是令牌差、幂等命中和未知年龄;结论是锁不能阻止旧持有者写,令牌也不能替代第三方查单。
热门面试题
- 问题:Fencing Token(栅栏令牌)与幂等键有什么区别?
- 考点:新旧顺序与重复身份。
- 回答思路:令牌回答谁更新,幂等键回答是不是同一动作。
- 详细答案:令牌必须在资源维度单调递增,用来拒绝旧授权迟到写;幂等键只需稳定唯一,用来让同一业务动作重复提交返回原结果。同一新令牌也可能重试,因此仍需幂等;不同业务键的旧执行者也可能迟到,因此仍需令牌。
- 进阶追问:随机锁值可以当栅栏令牌吗?
- 进阶回答:不能,随机值只有唯一性没有可比较的新旧关系;它适合防误删自己的锁,不适合资源端判断哪个授权更新。
- 问题:令牌为什么必须在真正资源端校验?
- 考点:检查与写入竞态。
- 回答思路:应用先查再无条件写仍会被并发穿透。
- 详细答案:旧执行者可以在应用校验通过后暂停,新执行者随后推进代次并写入,旧执行者恢复再执行无条件写就会覆盖新事实。资源端必须把状态检查、令牌比较、业务写和最大代次推进放在同一事务或原子条件中。
- 进阶追问:对象存储无法比较令牌怎么办?
- 进阶回答:各代次上传不可变临时对象,把正式对象指针放在可条件更新的数据库中发布;旧对象只成为可审计、可清理的孤儿。
- 问题:第三方不支持令牌如何控制旧请求?
- 考点:能力边界与未知结果。
- 回答思路:承认无法强栅栏,再用稳定业务号收敛。
- 详细答案:调用前持久化业务请求号、参数摘要和当前令牌,第三方若支持幂等就复用该号;超时后先查询,明确未执行才重试,仍未知则冻结冲突动作并人工。内部结果提交仍校验令牌,最终通过第三方账单或状态与本地流水对账。
- 进阶追问:可以换新请求号重试吗?
- 进阶回答:高风险副作用通常不能,换号会让第三方无法识别同一意图并产生重复;只有明确证明旧请求未生效且业务允许时才新建动作。
7. 资源隔离、并发控制与下游保护
7.1 按失败成本建立故障域,让慢任务、重试和恢复流量不能吃光核心容量
资源隔离至少覆盖任务类型、租户、优先级、线程池、队列、数据库连接、下游并发许可、CPU(中央处理器)、内存和临时磁盘。支付对账、库存补偿、跨境物流同步、普通报表和清理任务的时效与副作用不同,不能共用一个无界线程池。线程数由服务时间、下游配额和在途内存共同限制;队列长度由可接受等待时间反推;每个资源组保留最低服务份额和最大占用,避免高优先级长期饿死低优先级。恢复任务使用独立配额,并由实时任务与历史积压按比例共享下游,任何扩容先验证下游安全水位。
| 资源组 | 优先级与时效 | 隔离边界 | 过载动作 | 恢复验收 |
|---|---|---|---|---|
| 库存与资金补偿 | 高,短等待 | 独立线程、连接和下游许可 | 拒绝新低风险任务,保留事实 | 差异与最老年龄下降 |
| 物流外部同步 | 中,受渠道配额限制 | 按渠道信号量和队列 | 熔断、查单、延迟重试 | 成功吞吐与配额稳定 |
| 报表与投影 | 低,可重建 | 独立工作池和存储额度 | 暂停或合并 | 补数完整且不挤核心 |
| 历史恢复 | 动态风险分级 | 独立回放令牌 | 阶梯放量和快速回退 | 净消化为正 |
| 人工任务 | 高风险、低并发 | 审批队列和双人复核 | 禁止自动扩并发 | 每笔证据闭环 |
flowchart TD
A[待执行触发] --> B{任务风险与资源组}
B -->|库存和资金| C[核心保底池]
B -->|物流渠道| D[渠道隔离池]
B -->|报表和投影| E[低优先级池]
B -->|历史恢复| F[受控回放池]
C --> G[数据库与业务状态机]
D --> H[第三方配额闸门]
E --> I[可暂停下游]
F --> J[实时与历史比例控制]
G --> K{任一资源越过安全水位}
H --> K
I --> K
J --> K
K -->|是| L[停领、降并发、保留检查点]
K -->|否| M[继续并观测业务成功吞吐]图解读:风险分类先于线程分配,各池最终受数据库、第三方和恢复比例约束。前提是资源组有独立计量;正常路径按有效业务吞吐运行,失败路径停领和保存检查点;结论是“拆线程池”只是隔离起点,共享数据库、连接和第三方配额仍需硬边界。
数据演绎 7:并发取最小资源约束
E3(演练证据):物流任务平均服务时间 2 秒,渠道稳定配额 50 次/秒,理论在途上限为 50 × 2 = 100。数据库连接池只为该资源组保留 60 个连接,单任务持有 1 个连接;单任务工作集 18 兆字节,动态内存预算 720 兆字节,内存上限为 720 / 18 = 40。因此并发初值取 min(100, 60, 40) = 40,而不是按渠道配额开 100 个线程。若故障时服务时间升到 8 秒,40 个在途只能完成约 40 / 8 = 5 次/秒,应限流和熔断,不把线程扩到 400。观测看业务成功吞吐、连接等待、内存工作集和渠道错误率。
热门面试题
- 问题:不同任务类型为什么要隔离线程池?
- 考点:故障域和优先级。
- 回答思路:慢依赖、长任务和重试会占满共享工作者。
- 详细答案:物流超时、报表大查询或重试洪峰若进入同一池,会耗尽线程和队列,使库存补偿与支付对账无法运行。独立池、队列和配额让故障停留在任务域,并能按业务风险单独暂停、扩缩和恢复。
- 进阶追问:都拆池后是否完全隔离?
- 进阶回答:不完全,数据库、节点 CPU(中央处理器)、内存和第三方仍可能共享;还需连接配额、资源限制和全局水位控制。
- 问题:线程池并发为什么不能只按 CPU(中央处理器)核数配置?
- 考点:多资源瓶颈和外部配额。
- 回答思路:取线程、连接、内存和下游约束的最小值。
- 详细答案:Runner(执行器)任务通常包含数据库和外部调用,CPU(中央处理器)空闲不代表连接、缓冲或渠道有余量。并发应由服务时间、下游稳定吞吐、连接池、单任务工作集和故障余量共同推导,再用阶梯压测找拐点。
- 进阶追问:使用虚拟线程是否可以取消并发限制?
- 进阶回答:不能。它只降低阻塞线程成本,数据库连接、内存、锁和第三方配额仍是硬上限,必须保留信号量和背压。
- 问题:高优先级任务会不会饿死低优先级?
- 考点:公平性与最长等待。
- 回答思路:保底份额、年龄提升和最大占用并用。
- 详细答案:绝对优先级在持续高峰下会永久挤压报表、清理和投影,最终又产生存储或审计风险。每组设置最低服务份额,等待超过阈值逐步提升优先级,同时限制单租户和单类型最大占用;事故期暂停必须有恢复账本。
- 进阶追问:资金任务是否永远最高优先级?
- 进阶回答:资金正确性优先,但仍要按截止时间、未知风险和人工能力排序;无限并发的“最高优先级”会反过来压垮渠道和账务。
8. 业务幂等、重试预算、补偿与重复执行
8.1 重试只重新驱动可恢复步骤,补偿用新事实收敛,不删除原失败
业务幂等键应由业务对象、动作和业务版本组成,而不是每次执行随机生成。处理前先读取或原子创建幂等记录,参数摘要相同则返回既有结果,摘要冲突则拒绝。错误分为可立即重试的瞬时本地冲突、需退避的依赖超时、需先查单的结果未知、不可重试的参数权限错误、需补偿的部分副作用和需人工的不可逆或证据冲突。重试预算同时限制次数、总时长、最大年龄、并发和下游调用量;补偿创建引用原动作的新记录,拥有独立幂等键、状态机和审计,补偿失败不能递归无限补偿。
| 错误类型 | 自动重试 | 预算策略 | 最终去向 | 典型动作 |
|---|---|---|---|---|
| 乐观冲突 | 是 | 少量快速重读 | 重新竞争或返回原结果 | 读取当前状态 |
| 网络超时 | 有条件 | 退避、抖动、总时长 | 查单后重试或人工 | 复用业务号 |
| 下游过载 | 暂停即时重试 | 熔断、并发与调用预算 | 延迟队列 | 保护下游 |
| 参数或权限错误 | 否 | 零次自动重试 | 失败或人工修正 | 保留原输入摘要 |
| 部分副作用 | 不重做原步骤 | 独立补偿预算 | 补偿中或人工 | 新增冲正事实 |
| 结果未知 | 不猜测 | 查询次数与最大年龄 | 明确终态或人工 | 查单、对账 |
sequenceDiagram
participant R as Runner(执行器)
participant I as 幂等结果库
participant X as 外部系统
participant C as 补偿器
participant H as 人工工作台
R->>I: 以业务键和参数摘要登记动作
alt 已有相同结果
I-->>R: 返回既有业务结果
else 首次动作
R->>X: 携带稳定业务号调用
alt 明确成功或失败
X-->>R: 返回确定结果
R->>I: 条件提交结果
else 响应超时
R->>X: 按原业务号查单
X-->>R: 成功、失败或仍未知
end
end
R->>C: 部分副作用创建补偿单
C-->>H: 超预算或证据冲突转人工图解读:幂等结果库先识别业务身份,外部系统处理确定或未知结果,补偿器和人工台承接不可自动闭环场景。前提是重试沿用稳定业务号;正常路径返回一个结果,失败路径先查单、再补偿或人工;结论是重试不是循环调用,补偿也不是删除原事实。
数据演绎 8:重试预算与风暴放大
E3(演练证据):正常每秒 2,000 个任务,下游失败率 50%,若每个失败立即重试 5 次,粗略调用量为 2,000 × (1 + 0.5 + 0.5² + 0.5³ + 0.5⁴ + 0.5⁵) = 3,937.5 次/秒;若完全失败则升为 2,000 × 6 = 12,000 次/秒。改为最多 3 次、10 秒/30 秒/120 秒退避、总年龄 10 分钟,并在失败率越线时熔断,瞬时调用接近新任务保底加少量探测。状态从执行中转待重试或人工处理中;观测区分原始任务率、重试率、成功吞吐和预算耗尽;结论是重试预算的目标是恢复成功,不是提高尝试次数。
热门面试题
- 问题:什么错误可以自动重试?
- 考点:错误分类与可恢复性。
- 回答思路:同时判断错误是否短暂、动作是否幂等、结果是否已知。
- 详细答案:短暂网络错误、乐观冲突和明确未执行的依赖失败可在预算内重试;参数、权限和非法状态不应重试;外部超时属于未知结果,要先查单;已产生部分副作用的场景应进入补偿,不是重做整条链路。
- 进阶追问:超时为什么不是普通失败?
- 进阶回答:超时只证明本地没收到响应,对方可能已提交;立即换请求号重试会产生重复副作用。
- 问题:补偿和回滚有什么区别?
- 考点:跨事务的可逆边界。
- 回答思路:本地事务可原子回滚,外部已提交只能用新动作修正。
- 详细答案:回滚撤销同一未提交事务,补偿在原事实已经提交后新增反向或修正事实,例如释放库存、冲正账务或关闭重复外部单。补偿必须引用原动作、独立幂等并保留前后状态,不能覆盖历史假装原动作未发生。
- 进阶追问:补偿失败怎么办?
- 进阶回答:按补偿自身错误分类有限重试,超预算后冻结相关业务并人工;不能让“补偿的补偿”无限递归。
- 问题:如何防止重复执行产生重复业务效果?
- 考点:多层幂等和结果查询。
- 回答思路:从触发、领取、业务和对账四层回答。
- 详细答案:唯一计划键吸收重复触发,租约和令牌约束当前提交者,业务幂等键与状态机保证同一动作只生效一次,外部超时按稳定请求号查单,最终对账发现遗漏或重复。任何单层都不能覆盖全部失败窗口。
- 进阶追问:幂等记录写成功但业务事务失败怎么办?
- 进阶回答:幂等记录与本地业务结果应同事务提交,或使用明确的处理中状态和恢复器;不能提前写“成功”占位后失去业务事实。
9. 消费者积压、重试风暴、容量与渐进恢复
9.1 用有效成功吞吐和最老年龄判断可恢复性,不用拉取数制造繁荣
积压必须同时看待执行数量、最老任务年龄、到达率、有效业务成功率、重试流量、执行中数量和下游利用率。MQ(消息队列)位移差只说明传输层尚未确认,消息也可能已被预取到 Runner(执行器)内存或线程池,形成不可见在途。恢复时间使用 积压 / (有效成功吞吐 - 新增到达率),净速率小于等于零表示当前配置不可恢复。扩容前先确认分片可并行、下游有余量、幂等与令牌有效;否则增加消费者只会扩大连接、锁等待、超时和重试。恢复按风险、租户和年龄分层,实时任务保底,历史积压阶梯放量,任何业务错误率或下游水位反弹立即回退。
| 指标 | 含义 | 常见误判 | 恢复决策 |
|---|---|---|---|
| 待执行数量 | 尚未领取的显性工作 | 不含预取和执行中 | 与在途合并观察 |
| 最老任务年龄 | 最差业务等待 | 毒任务可能占头部 | 按风险与分位分层 |
| 有效成功吞吐 | 真正完成业务的速率 | 拉取或线程完成不等于成功 | 用于净恢复公式 |
| 重试放大比 | 尝试数除以原始触发数 | 聚合值掩盖错误类型 | 熔断高放大错误 |
| 下游安全水位 | 稳定成功前的容量拐点 | 配置配额不等于稳定能力 | 决定最大恢复并发 |
| 预计恢复时间 | 当前积压除以净消化 | 忽略新流量和失败 | 滚动更新并设置停止线 |
flowchart TD
A[消费者积压增长] --> B[拆分原始、重试、在途和最老年龄]
B --> C{有效成功吞吐大于新增到达率吗}
C -->|否| D{下游与分片有余量吗}
D -->|是| E[小步扩 Runner(执行器)或批量能力]
D -->|否| F[入口限流、暂停低优先级、熔断重试]
C -->|是| G[计算预计恢复时间]
E --> H[观察成功吞吐与下游水位]
F --> H
G --> H
H -->|指标稳定| I[10%、25%、50%、100% 放量]
H -->|错误或延迟反弹| F图解读:先拆清工作构成,再判断净恢复和下游余量;正常路径滚动计算并渐进放量,失败路径限流、暂停和熔断。前提是成功口径落到业务结果;结论是积压归零只是技术现象,历史任务的状态、补偿和人工差异全部闭环才算恢复。
数据演绎 9:消费者积压与恢复时间
E3(演练证据):入口每秒 600 个新触发,当前有效成功吞吐 400 个/秒,20 分钟新增积压为 (600 - 400) × 1,200 = 240,000。下游压测安全水位为 900 个/秒,保留 100 个/秒故障余量后把 Runner(执行器)成功吞吐提升到 800 个/秒,净消化为 800 - 600 = 200 个/秒,理论恢复需 240,000 / 200 = 1,200 秒,即 20 分钟。若盲目开到 1,200 个/秒导致 30% 失败和重试,有效成功仅 840 个/秒且调用量更高,恢复与下游风险都变差。观测看最老年龄、净消化、重试比和下游高分位延迟。
热门面试题
- 问题:消费者积压时第一动作是扩容吗?
- 考点:瓶颈定位与下游保护。
- 回答思路:先查到达、成功吞吐、分片和下游拐点。
- 详细答案:不一定。若数据库、第三方或单热点分片已饱和,扩容会增加竞争、超时和重试。先隔离毒任务、暂停即时重试、保护核心任务并保全证据;只有分片可并行且下游有余量时才小步扩容,并用有效成功吞吐验证。
- 进阶追问:队列数量下降为何业务仍然慢?
- 进阶回答:消息可能已预取到本地队列、线程或未确认集合;要联合看执行中、外部在途和业务完成年龄,必要时降低预取。
- 问题:如何计算积压恢复时间?
- 考点:净消化速率和动态预测。
- 回答思路:用业务成功吞吐减新增到达率。
- 详细答案:当前积压除以滚动窗口内的有效业务成功吞吐减新增率;净值小于等于零直接判定不可恢复。还要按分片和任务风险分别预测,加入重试、暂停和扩容预热,并持续用实际下降速度修正。
- 进阶追问:为什么不能用消息拉取速率?
- 进阶回答:拉取后可能等待、失败或重试,甚至先确认后丢业务;只有权威结果成功才真正减少待完成工作。
- 问题:恢复为何要渐进放量?
- 考点:历史洪峰与实时流量叠加。
- 回答思路:刚恢复的下游最脆弱,先探测再扩大。
- 详细答案:积压回放与实时任务同时竞争数据库、连接和第三方配额,全量放开容易再次超时并触发重试风暴。按 10%、25%、50%、100% 阶梯观察成功吞吐、错误、最老年龄和下游利用率,任一反弹立即回退。
- 进阶追问:旧任务会不会饿死新任务?
- 进阶回答:用独立队列或配额给实时流量保底,历史按风险和年龄分层;高风险旧任务仍可因截止时间获得更高优先级。
10. 暂停、恢复、取消、发布重启与检查点
10.1 暂停是持久化控制协议,恢复是新代次接续,不是把旧线程叫醒
暂停分任务级、任务类型级、租户级、资源组级和全局级。控制面先原子关闭触发与领取闸门,再向运行任务写暂停请求;Runner(执行器)在批次、事务或外部调用返回后的安全点检查请求,提交检查点,释放可释放资源,并把状态转为已暂停。无法中断的外部调用保持结果未知,不能标已暂停后立即重做。恢复前核对暂停期间的外部回调、租约、检查点和业务结果,生成新执行代次与新令牌,从最后稳定边界继续。发布重启遵循停止新领取、排空、检查点、截止强停、接管扫描和渐进恢复;强杀只是进程动作,任务安全由持久化状态、幂等与栅栏证明。
| 控制动作 | 先停止什么 | 在途处理 | 恢复方式 | 禁止做法 |
|---|---|---|---|---|
| 任务暂停 | 单触发领取 | 安全点提交检查点 | 审批后新代次继续 | 直接改回待执行 |
| 类型暂停 | 某任务类型新触发与领取 | 已运行按风险排空 | 按类型小流量放开 | 丢弃历史触发 |
| 全局暂停 | 所有非保底触发与领取 | 保留资金等必要任务配额 | 逐资源组恢复 | 只停 MQ(消息队列)消费 |
| 发布重启 | 当前实例新领取 | 等待截止或记录未完成清单 | 新实例接管过期租约 | 把线程退出当任务结束 |
| 取消 | 后续步骤与发布 | 已发生副作用查证或补偿 | 进入取消终态 | 覆盖已成功事实 |
sequenceDiagram
participant O as 运维控制面
participant D as 任务数据库
participant A as 旧 Runner(执行器)
participant B as 新 Runner(执行器)
participant X as 外部系统
O->>D: 关闭资源组触发与领取闸门
D-->>A: 写入暂停或排空请求
A->>X: 等待当前外部调用返回或查明未知
A->>D: 提交检查点并释放租约
alt 截止前安全退出
A-->>O: 返回未完成清单为空
else 超过截止强制重启
A--xO: 进程退出,保留未完成清单
end
O->>D: 审批恢复并开启小流量
B->>D: 以新代次接管并读取检查点
B->>X: 先查原业务号,再继续未完成步骤图解读:控制面持久化闸门,旧实例在安全点交接,新实例以新代次恢复。前提是外部调用和检查点可查询;正常路径排空,失败路径强制重启后仍依靠清单与租约接管;结论是暂停不是线程睡眠,恢复也不能复用旧执行资格。
数据演绎 10:执行中重启与恢复
E3(演练证据):发布前有 120 个执行中任务,其中 90 个在 60 秒排空期内完成,20 个提交检查点后转已暂停,10 个处于外部结果未知。排空率为 90 / 120 = 75%,可安全交接率为 (90 + 20) / 120 = 91.67%。重启后新实例不直接重跑 10 个未知任务,而是按稳定业务号查单,得到 7 个已成功、2 个明确失败、1 个仍未知;只对 2 个明确失败任务在预算内重试,1 个转人工。状态变化和业务结果闭合为 120 个,观测看排空耗时、检查点年龄、未知任务和接管重复命中。
热门面试题
- 问题:暂停与取消有什么区别?
- 考点:可恢复控制与业务终止。
- 回答思路:暂停保留继续资格,取消终止原意图并处理副作用。
- 详细答案:暂停在安全点保存检查点,后续可用新代次继续;取消表示原业务意图不再继续,已发生副作用要查证、补偿或保留不可逆终态。把暂停当取消会误补偿,把取消当暂停会让任务被错误复活。
- 进阶追问:已成功任务能取消吗?
- 进阶回答:不能覆盖成功事实,只能创建新的反向业务动作,例如退款、释放或撤销,并独立审计。
- 问题:发布重启为什么要先停领再排空?
- 考点:边界收敛与未完成清单。
- 回答思路:先让在途集合不再增长,才能评估交接。
- 详细答案:若边排空边领取,永远无法形成稳定未完成集合,强停后也不知道哪些任务需要接管。停领后等待短任务完成,长任务提交检查点,未知外部调用列清单,超过截止再强停,恢复器才能按事实逐项处理。
- 进阶追问:所有任务都必须等待完成吗?
- 进阶回答:不必。可检查点任务在安全点交接,幂等短任务可接受重做,高风险不可逆调用必须先查明或进入人工,按任务合同分类。
- 问题:为什么恢复要生成新执行代次?
- 考点:旧线程隔离和审计。
- 回答思路:恢复不是延长旧授权,而是新的所有权裁决。
- 详细答案:旧实例可能仍在运行或迟到返回,复用原代次无法区分新旧提交。新代次配新 Fencing Token(栅栏令牌),资源端拒绝旧结果;检查点只作为恢复输入,不继承旧资格,审计也能还原交接时间线。
- 进阶追问:短暂暂停后原会话仍有效可以复用吗?
- 进阶回答:只有未失租且重新验证当前持有者、令牌和状态都匹配时可继续;一旦会话或租约过期,必须新代次接管。
11. 可观测性、线上排查、审计与人工接管
11.1 技术信号用于定位,业务审计用于裁决,人工操作也必须进入状态机
可观测性以任务标识、触发标识、执行代次、Fencing Token(栅栏令牌)、业务幂等键和外部请求号贯通。指标至少覆盖触发延迟、待执行和执行中数量、最老年龄、租约冲突、续租延迟、失租停机时延、旧令牌拒绝、重试放大、幂等命中、未知结果、补偿与人工积压;日志保存状态前后、错误分类和参数摘要,不打印敏感原文;链路用于定位等待,但不能替代业务结果。人工接管必须创建异常单,冻结自动任务,展示原始证据、允许动作和风险,执行双人复核或最小权限操作,写前后摘要,再由独立对账验证。
| 证据域 | 关键字段或指标 | 能证明什么 | 不能证明什么 | 保全动作 |
|---|---|---|---|---|
| 调度控制面 | 计划版本、水位、触发冲突 | 是否生成意图 | 业务是否完成 | 固化计划与触发快照 |
| 执行数据面 | 持有者、租约、令牌、检查点 | 谁曾尝试和推进到哪 | 第三方最终结果 | 保存尝试和线程证据 |
| 业务权威层 | 幂等键、状态、流水、结果摘要 | 哪个副作用有效 | 计划是否漏触发 | 只读快照与对账 |
| 外部系统 | 请求号、回执、账单 | 外部是否执行 | 本地是否补齐 | 保存原文和签名摘要 |
| 人工工作台 | 操作者、审批、原因、前后值 | 谁做了什么修复 | 修复是否长期正确 | 再次对账和观察窗 |
sequenceDiagram
participant M as 监控告警
participant I as 事故指挥
participant D as 任务与业务数据库
participant X as 外部系统
participant H as 人工工作台
M->>I: 最老年龄、失租与旧令牌拒绝异常
I->>D: 冻结高风险任务并保存状态快照
I->>D: 核对触发、尝试、令牌、幂等结果
I->>X: 按稳定业务号查询外部终态
alt 可自动确定
X-->>D: 补齐成功、失败或补偿状态
else 证据冲突或不可逆
I->>H: 创建异常单并限定允许动作
H->>D: 审批后提交带审计的修正事实
end
D-->>I: 对账差异、积压和观察窗验证图解读:监控发现趋势,事故指挥冻结风险,数据库与外部系统提供权威证据,人工台处理无法自动裁决的异常。前提是所有标识可关联;正常路径自动补齐,失败路径经审批修正;结论是告警恢复、线程恢复或队列清零都不能替代业务对账与历史异常闭环。
数据演绎 11:事故证据与人工接管
E3(演练证据):某次失租事故发现 86 个受影响任务:任务库中 50 个已由新令牌完成,18 个旧令牌写被拒绝,12 个外部结果未知,6 个参数冲突。对 12 个未知任务查单得到 8 个成功、3 个明确失败、1 个仍未知;3 个失败进入有限重试,1 个与 6 个参数冲突共 7 个转人工。自动闭环率为 (50 + 18 + 8 + 3) / 86 = 91.86%,人工占比为 7 / 86 = 8.14%。恢复完成前必须让 7 个异常单都有终态并再次对账;观测是未知年龄、人工最老年龄和差异数,而不是只看服务存活。
热门面试题
- 问题:Runner(执行器)事故排查顺序是什么?
- 考点:止血、证据、根因与恢复分离。
- 回答思路:先影响面和高风险暂停,再查调度、租约、业务与外部证据。
- 详细答案:先按任务类型、租户和时间窗界定影响,暂停高风险新触发与即时重试,保存计划、水位、任务、尝试、线程、消息和下游指标;再核对租约、令牌、唯一键和状态机,按业务号查外部结果。分类补齐、重试、补偿或人工后,小流量恢复并用对账验证。
- 进阶追问:发现重复任务能直接判定协调服务脑裂吗?
- 进阶回答:不能,响应丢失、消息重复、长暂停和应用重试都可能造成重复尝试;要分别核对协调任期、令牌拒绝和业务唯一结果。
- 问题:人工接管怎样避免成为新的越权写入口?
- 考点:最小权限、职责分离和不可抵赖审计。
- 回答思路:异常单限定对象、动作、审批和有效期。
- 详细答案:人工不能直接执行任意数据库语句。工作台展示受控证据与候选动作,按风险要求审批或双人复核,命令携带异常单、业务键和当前版本,写入新的修正事实;记录操作者、原因、前后摘要和结果,最后由独立对账确认。
- 进阶追问:紧急情况下可以绕过审批吗?
- 进阶回答:只能使用预先定义、时效受限的紧急授权,自动通知和事后复核不可缺失;资金与库存核心动作仍应保留双重控制。
- 问题:哪些指标最能发现旧执行者问题?
- 考点:所有权与业务影响关联。
- 回答思路:令牌拒绝、失租停机、重复尝试和业务差异联合观察。
- 详细答案:旧令牌拒绝数证明资源端挡住了陈旧写,失租到停机时延反映本地响应,重复尝试与幂等命中反映放大,状态冲突和业务差异反映是否已有污染。单看会话过期或服务存活无法判断业务结果。
- 进阶追问:令牌拒绝突然上升一定是坏事吗?
- 进阶回答:切换后短期上升可能说明栅栏正在生效;若持续不降或伴随业务差异,则说明旧实例未隔离、会话抖动或任务代次设计有误。
12. 安全、成本、迁移演进与项目话术
12.1 从影子触发到单主提交,每一步都有停止线、回退路和事实验收
安全上,任务参数采用白名单与模式校验,敏感凭据由运行身份按需获取,租户和任务类型限制可执行资源,注册、暂停、补跑、补偿与人工修正全部审计。成本按每个正确完成的业务结果核算计算、存储、消息、外部调用、重试和人工,而不是追求最大触发吞吐。迁移从读取旧计划并影子计算触发开始,比较差集;再让新系统只写影子触发、不执行;随后选择低风险任务单主执行,旧系统保留只读与回退;最后逐类型切换、处理在途、归档旧调度。每阶段以漏触发、重复有效副作用、旧令牌拒绝、最老年龄、下游水位和人工差异为停止线。
| 阶段 | 新系统职责 | 旧系统职责 | 验收门槛 | 回退动作 |
|---|---|---|---|---|
| 影子计算 | 读取定义并计算触发差集 | 继续唯一执行 | 计划集合与时区一致 | 停影子读取 |
| 影子触发 | 写隔离触发与容量指标 | 继续生产触发 | 无漏算且无外部副作用 | 清理影子数据 |
| 低风险单主 | 执行报表或投影 | 对该类型只读 | 幂等、租约、栅栏故障实验通过 | 关闭新领取,旧路重开 |
| 分类型切换 | 承担更多资源组 | 保留查询和补偿入口 | 积压、成本和差异在界内 | 按类型回切 |
| 收口归档 | 新系统唯一生成和执行 | 只读历史 | 在途清零、审计完整 | 保留可恢复快照 |
flowchart LR
A[旧计划与执行事实] --> B[新系统影子计算]
B --> C{触发差集和时区一致}
C -->|否| D[修正规则并停止扩大]
C -->|是| E[影子触发但不执行]
E --> F[低风险任务单主]
F --> G{漏触发、重复效果、积压和下游均达标}
G -->|否| H[关闭新领取并按类型回退]
G -->|是| I[逐资源组切换]
I --> J[在途、人工差异和审计收口]图解读:迁移先比较计划,再验证触发,最后才承担副作用;正常路径逐类型扩大,失败路径随时停止并回旧路。前提是新旧系统不能同时成为同一任务类型的写主责;结论是双写调度不是保险,而是重复副作用风险,迁移应保持单主提交和可重放验证。
数据演绎 12:灰度、成本与回退水位
E3(演练证据):首批选择 1,000 个低风险任务的 10%,即 100 个任务;每个任务正常 1 次调用,灰度发生 8 次重复尝试但幂等命中,外部有效结果仍为 100,重复效果为 0。单位正确结果成本假设为计算 0.018 元、消息和存储 0.006 元、外部调用 0.020 元、重试摊销 0.004 元、人工摊销 0.002 元,合计 0.018 + 0.006 + 0.020 + 0.004 + 0.002 = 0.050 元。若旧令牌拒绝持续超过基线、最老年龄超过目标或人工差异出现 1 个即停止扩大并回退;这些都是演练阈值,不是生产承诺。
热门面试题
- 问题:新旧调度系统迁移为什么不能长期双写执行?
- 考点:单主提交和重复副作用。
- 回答思路:影子可以双算,业务写必须单主。
- 详细答案:两个系统同时生成并执行同一计划,会让触发身份、租约和重试责任分裂,第三方不支持令牌时尤其危险。可以影子计算和写隔离结果做差异,但每个任务类型在任何阶段只能有一个生产提交主责,切换用闸门、版本和在途清单完成。
- 进阶追问:回退时在途任务怎么办?
- 进阶回答:先停新系统领取,等待或记录在途,旧系统只接管租约过期且未完成的触发,沿用业务幂等键并查询未知结果,不能全量重跑。
- 问题:Runner(执行器)成本应该如何衡量?
- 考点:单位正确结果和失败成本。
- 回答思路:把计算、存储、调用、重试和人工都计入分母。
- 详细答案:以正确完成且通过业务验收的结果为分母,计入空闲故障余量、线程与实例、任务和审计存储、MQ(消息队列)、第三方调用、重试、补偿与人工。只看单次触发价格会鼓励删除审计或无限重试,把风险转给业务。
- 进阶追问:降低幂等保留期能省成本吗?
- 进阶回答:只有证明最大重试、消息保留、外部回调和人工恢复窗口都更短时才可缩减;否则会把迟到重复变成真实副作用。
- 问题:怎样用三分钟讲清 Runner(执行器)项目?
- 考点:事实边界、架构主线和事故闭环。
- 回答思路:按需求量级、不变量、架构、失败恢复和验证收束。
- 详细答案:先说明 E2(已有材料映射)只证明项目方向,真实实现待核对;再讲任务先落库、唯一计划键触发、分片扫描、租约领取、心跳续租和资源隔离。重点强调调度唯一不等于业务幂等,资源端 Fencing Token(栅栏令牌)拒绝旧执行者,外部动作用稳定业务号查单。最后用积压公式、暂停恢复、人工接管和业务对账验收。
- 进阶追问:最值得主动说出的反例是什么?
- 进阶回答:分布式锁不能远程阻止旧持有者写;若资源端不校验令牌,锁过期后仍可能双写,必须补业务幂等、查单与对账。
12.2 十二字段方案收束
需求明确任务来源、时区、截止、暂停、重试和人工责任;量级从触发率、服务时间、失败率、重试放大、检查点和保留期推导;不变量守住唯一计划键、当前租约、旧令牌拒绝和业务副作用唯一;架构分注册管理、触发控制、执行数据、业务副作用与审计恢复五面;模型覆盖定义、触发、尝试、检查点、业务结果和异常单;正常路径按注册、触发、领取、心跳、幂等执行和结果提交推进;失败路径覆盖失租、重复、迟到写、风暴、积压、暂停、重启与人工;容量以有效成功吞吐、最老年龄和下游安全水位约束;安全覆盖身份、参数、凭据、租户、审批和审计;成本按正确结果计量;迁移采用影子计算、低风险单主和按类型回退;最终以触发差集、任务守恒、业务流水、外部结果和人工异常清零共同验收。
13. 综合题库与长口述训练
综合题 01:请完整设计一套 Runner(执行器)调度与失败恢复系统
问题:请完整设计一套 Runner(执行器)调度与失败恢复系统。
口述答案:我会先声明证据边界:既有材料只能把任务状态、分片抢占、退避、线程隔离和人工补偿作为 E2(已有材料映射),租约、令牌和下述数字属于 E3(演练证据),真实表结构、部署、吞吐与事故仍是 E0(待核对)。需求先明确任务来源、时区、截止时间、错过策略、优先级、暂停恢复、外部副作用和人工责任。模型拆成任务定义、触发实例、执行尝试、检查点、业务结果和异常单;定义按版本不可变,触发以任务、计划时刻、版本和业务分区组成唯一计划键。控制面按分片扫描并用重叠窗口补漏,MQ(消息队列)只唤醒,任务库才是事实源。Runner(执行器)通过条件更新领取有限租约和单调 Fencing Token(栅栏令牌),心跳只续当前资格,连续失败就关闭本地提交闸门。业务提交必须同时携带令牌和业务幂等键:资源端拒绝旧令牌,同一动作重复时返回既有结果;第三方不支持令牌则复用稳定请求号,超时先查单。不同任务按失败成本分线程、连接、队列和下游配额,重试按错误分类、次数、总时长、年龄和并发预算;部分副作用用新补偿事实收敛。积压用有效成功吞吐减新增率计算恢复,按比例放量。发布先停领、排空、检查点和未完成清单,重启后新代次接管。验收同时看触发差集、任务守恒、旧令牌拒绝、业务结果、未知年龄、人工差异和对账,不能用主节点唯一、消息确认或线程结束冒充业务成功。
- 追问 1:最核心的两条不变量是什么? 直答 1:唯一计划键守触发身份,业务幂等键加资源端令牌守有效副作用。
- 追问 2:MQ(消息队列)丢唤醒怎么办? 直答 2:待执行扫描按任务库补发,消息不是触发事实源。
- 追问 3:外部接口不支持令牌怎么办? 直答 3:复用稳定业务号查单、补偿和对账,并明确剩余风险。
- 追问 4:何时允许人工接管? 直答 4:结果长期未知、不可逆或证据冲突且自动预算耗尽时,以异常单和审批接管。
综合题 02:任务注册、计划版本、触发实例和执行尝试如何建模
问题:任务注册、计划版本、触发实例和执行尝试如何建模?
口述答案:我不会用一行任务记录同时表示长期计划、某次到点和某个工作线程。任务定义保存业务类型、租户、参数摘要、时区、触发规则、错过策略、优先级、截止时间、资源组、幂等模板、重试预算、暂停状态和责任人;任何影响执行集合的修改都创建新版本。触发实例代表某个计划时刻的执行意图,以任务标识、计划时刻、计划版本和必要业务分区形成唯一计划键,记录预计触发、实际创建、来源和错过处置。执行尝试记录每次领取的持有者、租约截止、执行代次、Fencing Token(栅栏令牌)、检查点、错误分类、开始结束和退出原因。业务结果再以业务对象、动作和业务版本形成幂等键,保存外部请求号、参数摘要、状态与结果。这样计划修改不会改写在途语义,扫描重试只命中原触发,租约接管保留旧尝试,业务重复又能返回原结果。注册入口对同任务同版本做摘要校验,同摘要返回既有版本,不同摘要拒绝;触发插入依赖数据库唯一约束,不用应用层先查后插。删除旧版本前必须确认关联触发、补偿和审计都超过保留期。复核时分别计算定义版本数、应触发与实触发差集、尝试放大和业务结果守恒,避免把 10 次尝试统计成 10 个完成。这个模型还让任务级、类型级和全局暂停有明确落点,恢复时新代次读取旧检查点但不继承旧资格。
数据库索引还要同时支撑按计划窗口补漏、按租约截止接管和按业务键查结果;上线前用跨版本修改、重复扫描与人工补跑验证三类记录不会互相覆盖,并核对归档后仍可追责。
- 追问 1:人工补跑属于新尝试吗? 直答 1:若原触发仍可恢复是新尝试;若业务要新增一次执行意图则创建带原因的新触发。
- 追问 2:计划参数小改可以原地覆盖吗? 直答 2:只要可能改变执行集合、结果或恢复语义,就必须新版本。
- 追问 3:为什么业务结果还要独立建模? 直答 3:任务成功是调度聚合,真正副作用需要自己的幂等身份、状态机和审计。
延伸阅读:需求约束与量级澄清、MQ(消息队列)可靠性语义。
综合题 03:为什么调度唯一不等于业务幂等
问题:为什么调度唯一不等于业务幂等?
口述答案:调度唯一回答的是“当前尽量由谁产生计划或推进尝试”,业务幂等回答的是“即使同一动作被执行多次,最终有几个有效业务结果”。选主、分片、唯一触发和租约能减少重复,但不能覆盖所有失败窗口:旧主节点可能在会话过期前已经插入触发但丢了响应,新主补扫时再次创建;消息确认丢失会重复投递;Runner(执行器)业务提交成功后在写任务状态前崩溃,新执行者会接管;外部请求成功但响应超时,调用方也无法判断。唯一计划键让重复扫描收敛为一个触发,租约让一个时刻尽量只有当前持有者,Fencing Token(栅栏令牌)让资源端拒绝旧授权;但同一当前持有者也可能重试,第三方也可能不支持令牌,所以还要用业务对象、动作和业务版本形成稳定幂等键,以唯一约束和状态机返回既有结果。超时必须按原业务号查单,不能换号重发;跨系统最终用流水和对账验证。反过来,只做业务幂等而不做调度唯一,理论上可守住结果,却会浪费计算、占满连接、冲击第三方并放大日志和人工,因此两者分别承担效率和正确性。面试时我会明确说“允许重复尝试,不允许重复有效副作用”,并展示计划冲突、租约冲突、幂等命中和业务结果四组指标,而不是看到重复日志就直接判断重复扣款或重复发货。
故障演练要分别破坏触发写回、消息确认、任务状态提交和外部响应,确认唯一键、令牌、业务幂等与查单各自接住对应窗口,而不是只做正常路径压测;各层指标也要能独立归因。
- 追问 1:有数据库唯一键是否还需要状态机? 直答 1:需要,唯一键防重复身份,状态机防同一对象发生不合法的不同动作。
- 追问 2:只有幂等是否可以不用选主? 直答 2:小规模可以简化控制面,但要接受更多竞争,并由容量证据证明不会压垮下游。
- 追问 3:重复尝试率高要告警吗? 直答 3:要,它可能不破坏结果,却会提示续租、确认、超时或实例稳定性异常。
综合题 04:Runner(执行器)失租后如何停止、接管并验证
问题:Runner(执行器)失租后如何停止、接管并验证?
口述答案:失租不是把任务直接判失败,而是旧执行资格失效。领取时任务库用权威时间写持有者、租约截止、执行代次和单调 Fencing Token(栅栏令牌);心跳只有在持有者、令牌和执行中状态都匹配时才能续租。Runner(执行器)发现连续续租失败后,先原子关闭本地提交闸门,不再领取、不再开始新外部副作用,在批次或事务安全点保存检查点;已经发出的请求记录为确定结果或未知结果,不能假设已取消。租约到期后,新实例通过条件更新获得更高令牌,读取最后稳定检查点和原尝试清单。对本地可控资源,所有检查点、结果和状态提交都校验新令牌,旧实例恢复后的低令牌写影响零行;对第三方,接管者按原业务号查单,查到成功只补本地,明确未执行才沿用同号重试,仍未知转人工。接管不能从当前时间直接继续,还要按计划水位补扫失租窗口并用唯一计划键吸收重复。恢复先小流量,观察失租停机时延、接管耗时、旧令牌拒绝、幂等命中、最老年龄和下游利用率。验证不仅看新实例成功,还要主动恢复旧实例让它提交旧令牌,确认资源端明确拒绝,再对触发、业务流水和外部结果做差集。租约长度根据心跳延迟与可接受恢复目标推导,不能靠无限拉长租约掩盖长停顿。
事故关闭前还要确认旧实例已从流量与领取范围隔离,未知任务都有确定结果或人工责任人,失租窗口的补扫差集为零,并保留恢复旧进程的拒写证据和处置时间线。
- 追问 1:连接断开是否立即永久失租? 直答 1:先进入资格不确定并停新副作用;服务端或任务库确认过期后才永久失去旧资格。
- 追问 2:旧线程可以继续本地计算吗? 直答 2:可做可丢弃计算,但提交前必须重新验证;不可逆写应立即停止。
- 追问 3:新实例何时可以接管? 直答 3:权威租约已过期且条件领取成功后,不能凭本机时钟或监控猜测。
- 追问 4:如何证明失租方案有效? 直答 4:故障注入暂停旧实例、让新实例接管、恢复旧实例并验证旧令牌被资源端拒绝。
延伸阅读:会话过期与旧进程恢复、Runner(执行器)调度案例。
综合题 05:锁为什么不能阻止旧持有者写,Fencing Token(栅栏令牌)怎样补齐
问题:锁为什么不能阻止旧持有者写,Fencing Token(栅栏令牌)怎样补齐?
口述答案:分布式锁或选主服务只能维护协调层资格,无法远程杀死旧进程、关闭它已经建立的数据库连接,也无法撤回已经发出的网络请求。典型窗口是 A 持锁后发生长时间全停顿,服务端让会话过期并删除临时节点,B 合法取得新锁并开始写;A 恢复时本地线程仍保留旧上下文,如果直接无条件写,两个持有者都可能产生副作用。Fencing Token(栅栏令牌)把每次授权映射为同一资源内单调递增代次,领取时由权威存储生成,执行者随检查点和业务写传递。数据库或真正资源端在同一原子操作中比较当前状态和令牌,只接受当前或更高合法代次,并推进最大值,低代次迟到写被拒绝。随机锁值只能识别“是不是我的锁”,没有新旧大小关系;幂等键识别“是不是同一业务动作”,也不能代替授权顺序,因此关键写通常同时校验业务键、状态和令牌。对象存储不能比较时,上传按代次生成不可变对象,把正式指针放在数据库条件发布。第三方接口若不支持令牌,必须承认不能强栅栏,调用前持久化稳定业务号,超时先查单,仍未知就人工和对账。验收要故意让 A 迟到,确认旧令牌拒绝计数增加且业务有效结果仍为一个;只看 B 成功日志不能证明安全。
令牌的作用域、生成事务、持久化位置、比较规则和溢出处理必须写入设计合同;还要枚举任务状态、业务表、检查点、对象发布和补偿五类写路径,确认没有旁路绕过资源端条件。故障实验至少覆盖旧实例已算出结果、数据库连接仍存活和第三方请求已经发出三个迟到窗口。
- 追问 1:随机锁令牌能否直接比较大小? 直答 1:不能,随机值只提供唯一性,不提供可靠授权顺序。
- 追问 2:令牌校验放在应用层可以吗? 直答 2:不够,检查后到写入前仍有竞态,必须在资源端原子校验。
- 追问 3:令牌需要全局递增吗? 直答 3:只需在同一受保护资源或所有权域内可比较,全局序列通常没有收益。
- 追问 4:第三方无栅栏是否意味着方案不可用? 直答 4:不一定,但要用幂等、查单、补偿和人工明确降低而非消除风险。
综合题 06:重复执行外部物流推送时如何做到业务结果唯一
问题:重复执行外部物流推送时如何做到业务结果唯一?
口述答案:我先接受物流推送可能重复尝试:触发补扫、消息重复、租约接管、发布重启和外部响应丢失都可能让同一动作再次进入 Runner(执行器)。第一层以任务、计划时刻和版本组成唯一计划键,防止同一计划无限创建;第二层领取租约并获得 Fencing Token(栅栏令牌),本地任务结果只接受当前代次;第三层业务幂等键使用运单号、事件类型和事件版本,参数摘要还包括目标渠道和必要业务字段。同键同摘要直接读取既有推送结果,同键不同摘要拒绝并告警,不能把错误复用当幂等。调用前先持久化请求号、参数摘要和执行代次,再向第三方传稳定业务号;明确成功就保存外部回执,明确失败按分类处理,超时或断连进入未知态。未知态先按业务号、客户单号或第三方可查字段查单,查到已受理只补本地,明确不存在才沿用原号有限重试,仍无法判断则冻结冲突动作并转人工。若第三方完全不支持幂等也不支持查询,自动重试必须更保守,限制业务规模并把剩余风险写入接入合同。任务成功提交还要校验当前令牌与业务结果,旧 Runner(执行器)的迟到回写被拒绝。最终对账比较内部推送动作、外部订单或事件、业务状态和异常单,重复展示可重建,重复外部副作用则按业务政策取消、补偿或人工,不能随机删除一条记录。
回归测试还要排列确认丢失、回调先到、查单超时和旧实例恢复四种顺序,核对每种顺序都只留下一个有效外部结果,并且异常单能定位到原请求号。
- 追问 1:为什么幂等键不能只用 taskId(任务标识)? 直答 1:任务可能包含多个业务动作或重跑新版本,应按业务对象、动作和版本定义。
- 追问 2:外部成功但本地失败怎么办? 直答 2:按稳定业务号查到原结果,补齐本地状态,不再次创建外部动作。
- 追问 3:同键参数不同如何处理? 直答 3:拒绝并记录摘要冲突,防止调用方错误复用返回不相关结果。
综合题 07:任务执行一半服务重启如何从检查点恢复
问题:任务执行一半服务重启如何从检查点恢复?
口述答案:重启前先把进程生命周期和任务生命周期分开。正常发布先关闭本实例新领取,让在途集合停止增长;短任务在排空窗口完成,长任务在已完成且可幂等复用的步骤后提交检查点,记录步骤号、业务游标、累计摘要、外部请求号和当前 Fencing Token(栅栏令牌)。无法中断的外部调用进入未完成清单,不能在进程退出前直接标失败。超过截止可强制重启,但这只终止进程,不代表业务已安全结束。新实例启动后扫描执行中任务:未过期的他人租约不抢,过期后以新执行代次和更高令牌接管;检查点只作为恢复输入,不继承旧资格。对检查点之前的步骤,通过结果表和业务幂等键确认已完成;检查点之后若外部请求已经发出,先按稳定业务号查单,成功就补本地,明确未执行才重试,仍未知转人工。恢复提交时资源端拒绝旧令牌,因此旧实例迟到返回也不能覆盖新状态。对于批量任务,宁可从上一个已稳定检查点重复读取一小批,由批次幂等吸收,也不能先推进检查点造成永久漏项。完成后核对计划触发、步骤结果、外部回执、任务终态和补偿,观察重做量、接管耗时、幂等命中和未知年龄。若代码版本改变检查点语义,升级必须提供兼容读取或让旧版本排空,不能让新代码猜测旧游标。
发布门禁应实际测量排空率、强停清单规模、接管后的重做量和首个业务周期差异;检查点协议必须带结构版本与校验摘要,防止新代码误读旧游标后跳项。
- 追问 1:检查点越频繁越好吗? 直答 1:不是,要在重做成本与持久化开销间权衡,并落在真正稳定的业务边界。
- 追问 2:为什么检查点不能先写后执行? 直答 2:进程在两步之间退出会永久跳过未执行步骤;先执行再记点最多产生可吸收重复。
- 追问 3:强杀前必须等所有任务吗? 直答 3:不必,但必须形成可查询的检查点、未知调用和未完成清单。
- 追问 4:升级后检查点不兼容怎么办? 直答 4:保留旧执行器排空或提供显式迁移器,不能静默按新语义续跑。
延伸阅读:线程池关闭与任务恢复、异步导出断点恢复。
综合题 08:如何识别并治理重试风暴
问题:如何识别并治理重试风暴?
口述答案:重试风暴的特征不是错误多,而是同一原始工作在短时间产生大量尝试,依赖越慢、超时越多、重试越快,实际成功吞吐反而下降。排查先拆原始触发率、重试率、同业务键尝试次数、错误类型、退避间隔、执行中数量、连接等待和下游高分位延迟;若依赖错误上升后重试流量倍增、最老年龄继续增长,就是正反馈。止血先暂停即时重试和自动死信回灌,按任务风险保留新任务或权威事实入口,打开熔断并把并发降到下游安全水位;参数、权限和非法状态等毒任务立即隔离,不参与自动重试。策略按错误分类:短暂冲突少量快速重试,网络超时先查单,依赖过载使用指数退避和随机抖动,永久错误直接失败或人工。预算同时限制最大次数、总时长、最大任务年龄、单租户并发和单位时间下游调用量,预算耗尽进入死信、补偿或人工,而不是清零次数继续循环。恢复先半开探测,再把历史重试与实时任务按配额分开,以有效业务成功吞吐、错误率和最老年龄决定 10%、25%、50%、100% 放量。复盘要找出为何错误被判可重试、为何退避没有生效、谁绕过熔断,以及幂等记录是否在风暴下仍唯一。最终业务对账证明没有因重试造成重复库存、资金或外部单,队列下降本身不够。
所有预算参数都应随错误分类和下游能力版本化,变更记录依据、审批和回滚阈值;演练中还要证明熔断打开后新任务不会从补扫、人工回灌或旁路调用形成第二条重试通道。
- 追问 1:暂停重试会丢任务吗? 直答 1:只要任务持久化、状态可查且有恢复账本,暂停只是延后可见时间,不等于删除。
- 追问 2:为什么必须加随机抖动? 直答 2:避免大量任务按同一退避时间同时醒来形成周期性洪峰。
- 追问 3:死信可以自动全量回灌吗? 直答 3:不能,先按错误聚类验证修复和幂等,再小批限速回灌。
综合题 09:消费者积压时如何估算恢复并保护下游
问题:消费者积压时如何估算恢复并保护下游?
口述答案:我先把“消费者积压”拆成待投递、已预取未确认、本地线程池排队、执行中、待重试和业务未完成六类,因为代理节点数量下降不等于业务完成。指标至少看总量、最老任务年龄、到达率、有效业务成功吞吐、增长斜率、重试放大、热点分片和下游利用率。恢复公式是当前积压除以“有效成功吞吐减新增到达率”;净值小于等于零说明当前能力不可恢复,不能报一个虚假完成时间。扩容前判断分片或业务键是否可并行、下游数据库和第三方是否有稳定余量、连接与内存是否足够、幂等和 Fencing Token(栅栏令牌)是否能承受接管。若下游已在延迟和错误拐点,增加 Runner(执行器)只会提高超时和重试,应先入口限流、暂停低优先级与可重建任务、熔断慢依赖、隔离毒任务,并给库存补偿和支付对账保底配额。若有余量,按小步增加消费者、批量或分片,动作后用业务成功而非拉取数验证净消化。历史恢复与实时流量拆队列或比例,例如实时保底、历史按风险和年龄分层;高优先级也要有最大占用,低优先级保留最低份额。恢复按阶梯放量,持续滚动修正预计时间,错误率、下游高分位或积压年龄反弹立即回退。最终还要核对任务状态、业务流水、死信和人工差异,不能在队列清零时提前关闭事故。
容量报告还要列出公式输入、单位、采样时间、敏感性和单故障域后的剩余能力,并用每个放量阶段的实际下降斜率修正预测,避免静态均值掩盖热点。
- 追问 1:为什么最老年龄比总条数更重要? 直答 1:它直接反映最差业务等待,也能暴露毒任务或热点长期不动。
- 追问 2:净恢复率为零怎么办? 直答 2:限入口、降失败、提升有效能力或改变业务承诺,当前配置无法自然恢复。
- 追问 3:扩容后错误率上升怎么办? 直答 3:冻结继续扩容,回到最近稳定并发,检查下游、连接、热点和再均衡。
- 追问 4:队列清零是否可以恢复全部流量? 直答 4:还要验证在途、死信、未知、人工差异和下游观察窗,不能只看代理层。
延伸阅读:MQ(消息队列)积压恢复、稳定性事故与容量。
综合题 10:暂停、恢复和取消如何避免状态竞态
- 问题:暂停、恢复和取消如何避免状态竞态?
口述答案:三者必须是持久化状态机动作,不是进程内布尔值。暂停表示暂时停止推进并保留继续资格,取消表示原业务意图终止且要处置已发生副作用,恢复表示通过新执行代次重新获得资格。控制面先按任务、类型、租户、资源组或全局范围原子关闭触发和领取闸门,再给执行中任务写暂停请求;Runner(执行器)在批次、事务或外部调用返回后的安全点检查,提交检查点并条件转暂停中或已暂停。无法中断的外部调用保持未知,不能为了让面板好看直接标已暂停。取消与完成通过前置状态和版本竞争唯一胜者:完成已提交后,迟到取消不能覆盖成功,只能创建新的反向业务动作;取消先胜出后,旧执行者即使返回也会因状态和 Fencing Token(栅栏令牌)不匹配被拒绝。恢复前核对暂停期间的回调、业务结果、租约和检查点,审批后生成新执行代次与新令牌,从最后稳定边界继续;旧线程不能被“唤醒”后复用原资格。全局暂停仍要给必要的资金核对、租约清理和审计保留最小控制容量,否则系统连恢复动作都无法运行。每个控制动作记录操作者、原因、范围、版本、开始结束、受影响任务和回退条件。验收排列暂停与完成、取消与外部成功、恢复与旧返回等竞态,证明终态唯一、业务结果可解释、触发没有静默丢失,恢复后积压按安全水位下降。
操作台必须显示每个暂停范围的配置版本、生效实例数、剩余在途和最老未知年龄;控制命令超时应保持未确认,不能让值班人员误判已经全量生效。
- 追问 1:暂停后租约要立即释放吗? 直答 1:到安全点并提交检查点后释放;外部未知尚未记录时不能假装已安全交接。
- 追问 2:完成后还能取消吗? 直答 2:不能改写成功,只能创建退款、释放、撤销等新的业务动作。
- 追问 3:全局暂停是否停止全部线程? 直答 3:不是,保留控制、审计和必要高风险闭环容量,停止的是受控触发与推进。
延伸阅读:Runner(执行器)停止与恢复、任务协调与恢复。
综合题 11:不同任务如何做资源隔离并避免核心任务饥饿
- 问题:不同任务如何做资源隔离并避免核心任务饥饿?
口述答案:我先按失败成本和时效划分资源组,而不是按代码包名拆线程池。库存释放与支付对账属于高风险、短等待任务,跨境物流同步受第三方渠道配额约束,报表与投影可延迟可重建,历史恢复会在故障后形成洪峰,人工任务低并发但高权限。每组分别设置队列、线程、数据库连接、下游并发许可、内存预算、单租户配额和暂停开关;共享 CPU(中央处理器)、节点内存和数据库仍要有全局高低水位,避免“线程分开但连接共抢”。并发不是按 CPU(中央处理器)核数拍脑袋,而是取下游稳定吞吐乘服务时间、连接数、单任务工作集和故障余量的最小值。队列容量由允许等待时间和到达率反推,无界队列只会把拒绝变成延迟与内存事故。优先级采用核心任务保底份额、单组最大占用、低优先级最低份额和等待年龄提升,不能让高优先级永久吞满所有资源;恢复流量另设令牌,和实时任务按比例共享下游。过载时先停报表、投影和即时重试,核心任务若也超过安全水位则明确拒绝新业务或延迟承诺,不能静默丢任务。扩容前确认分片可并行和下游有余量,扩容后以业务成功吞吐、连接等待、下游高分位和重试比验证。故障演练同时注入慢物流、报表洪峰和资金补偿,证明慢任务只在自身故障域积压,核心任务仍能在目标内完成,恢复后低优先级也能按账本补齐。
资源配置和隔离策略必须版本化,发布后能按资源组快速回退;演练还要验证共享数据库达到高水位时,各组会按预算收缩,而不是经公共连接池互相拖垮。
- 追问 1:拆线程池是否已经完成隔离? 直答 1:没有,还要隔离连接、下游许可、内存和队列,并设置全局水位。
- 追问 2:高优先级为何还要最大占用? 直答 2:防止持续高峰饿死清理和审计,最终形成新的稳定性风险。
- 追问 3:虚拟线程能否替代资源隔离? 直答 3:不能,它降低线程成本,不增加数据库、内存或第三方容量。
- 追问 4:如何证明隔离有效? 直答 4:在一个资源组过载时验证其他组的业务成功、延迟和资源水位仍达标。
综合题 12:分片、抢占和租户公平性如何协作
- 问题:分片、抢占和租户公平性如何协作?
口述答案:分片解决扫描和执行并行度,抢占解决某个触发当前由谁尝试,公平性解决多租户和多类型如何分享有限资源,三者不能混成一个哈希。调度扫描可按任务稳定键映射逻辑分片,分片数量与物理实例解耦,成员变化时只迁移部分分片并推进分片代次;唯一计划键仍独立存在,因此新旧扫描者重叠不会创建两个触发。执行阶段 Runner(执行器)对具体触发做条件领取,只有状态可执行、下次时间到、暂停闸门关闭且租约为空或过期时才能获得新执行代次与 Fencing Token(栅栏令牌)。分片所有权只代表谁扫描一组任务,不能直接当作某个任务的执行令牌。公平调度在资源组内先按租户配额和最大在途过滤,再结合优先级、截止时间、等待年龄与历史消耗选择;大租户不能占满全部槽位,小租户也不能因绝对优先级永久饥饿。任务成本差异大时,不能只按任务数均衡,应使用历史服务时间、外部调用数或成本权重估算份额,热点渠道还要单独受下游配额限制。实例失效后,分片迁移先恢复扫描资格,触发任务仍需逐个抢占;旧实例的分片代次和任务令牌都应被相应资源端拒绝。扩分片或改映射时保持一段重叠读取,依赖唯一触发吸收重复,并监控每片扫描耗时、触发冲突、租户等待分位、抢占冲突和下游利用率。验收要模拟大租户洪峰、热点分片和实例失联,证明没有漏触发、没有重复有效副作用,且小租户最长等待有界。
- 追问 1:分片代次能否作为任务令牌? 直答 1:不能直接替代,前者作用于扫描分片,后者作用于具体执行或资源。
- 追问 2:一致性映射是否保证负载均衡? 直答 2:只减少迁移,不保证任务成本与热点均衡,还要按历史成本修正。
- 追问 3:租户配额固定是否公平? 直答 3:不一定,应同时考虑合同、业务风险、等待年龄和空闲份额借用。
延伸阅读:分布式锁与任务所有权、MQ(消息队列)容量与积压。
综合题 13:部分副作用成功后如何补偿并转人工
- 问题:部分副作用成功后如何补偿并转人工?
口述答案:我不会把部分成功统一标失败后重跑整条任务,因为前半段可能已经产生不可重复副作用。执行尝试要按步骤记录输入摘要、业务幂等键、外部请求号、结果和检查点;发生异常后先分类每个步骤是明确成功、明确失败还是结果未知。明确成功的动作不重做,明确失败且可恢复的步骤在预算内沿用同一业务号重试,未知结果先查单或等回调。若业务目标已经无法按原路径完成,就创建引用原任务、原动作和原业务结果的补偿单,例如释放预占、撤销外部单、冲正账务或重建投影;补偿是新事实,有独立幂等键、状态机、执行代次、Fencing Token(栅栏令牌)和重试预算,不能覆盖原成功记录。可逆性必须由业务定义:消息或投影可重建,未出库仓单可能可取消,已出库、已扣款或已通知客户的动作往往需要退货、退款、说明或人工。补偿也可能结果未知,仍按稳定号查证,不允许“补偿失败再生成补偿的补偿”无限递归。超出总时长、金额或数量过大、证据冲突、第三方不可查、状态已不可逆时,冻结相关自动任务并创建异常单;人工台只提供受控候选动作,按风险审批或双人复核,写入前后摘要。处置结束后独立对账任务核对原动作、补偿、外部结果和业务终态,只有差异清零且观察窗稳定才关闭异常。指标分开统计自动补偿成功、预算耗尽、人工最老年龄和再次差异,不能用任务状态成功掩盖补偿链未闭合。
- 追问 1:补偿等于数据库回滚吗? 直答 1:不等于,补偿是在原事实已提交后新增反向或修正业务事实。
- 追问 2:哪些错误应直接人工? 直答 2:不可逆、证据冲突、第三方不可查或风险超过自动权限的错误。
- 追问 3:补偿成功后原任务改成功吗? 直答 3:按业务目标定义聚合终态,但原失败和补偿事实都必须保留。
- 追问 4:如何防人工重复处置? 直答 4:异常单、动作键、当前版本和审批条件共同幂等,完成后再次对账。
延伸阅读:分布式补偿与消息一致性、支付资金对账恢复。
综合题 14:线上出现旧令牌拒绝与重复任务时如何排查
- 问题:线上出现旧令牌拒绝与重复任务时如何排查?
口述答案:我先不把重复任务直接定性为协调服务脑裂,也不把旧令牌拒绝直接定性为数据污染。第一步按任务类型、租户、计划窗口和业务风险圈定影响,暂停高风险新触发、即时重试与自动死信回灌,保留必要审计和查单能力。第二步固化证据:任务定义与计划版本、扫描水位、触发唯一冲突、消息投递与确认、执行尝试、持有者、租约、心跳、Fencing Token(栅栏令牌)、检查点、线程状态、发布批次、数据库条件更新、业务幂等结果和外部请求号。第三步建立时间线,判断重复来自重叠补扫、响应丢失、消息重复、长全停顿、网络分区、发布重启还是应用层错误重试;协调集群有唯一 Leader(领导者)也不排除旧进程恢复。第四步核对资源端:旧令牌写是否影响零行,同一业务键是否只有一个有效结果,第三方是否出现重复外部单。旧令牌拒绝短期上升可能证明栅栏正在生效;若拒绝后仍有业务差异,说明某条写路径没有校验令牌或幂等粒度错误。对外部未知结果按稳定业务号查单,明确成功补本地,明确失败有限重试,仍未知人工。止血后先隔离异常实例、修复续租或写入路径,再从最后确认水位重算触发差集,小比例恢复低风险任务;观察选举或租约抖动、失租停机时延、拒绝数、最老年龄、重复有效结果和下游水位。最终用业务流水、外部结果和异常单对账关闭事故,不能只因告警下降或队列归零宣布恢复。
- 追问 1:重复任务是否等于重复业务效果? 直答 1:不等于,要核对幂等结果、状态机和外部副作用。
- 追问 2:旧令牌拒绝上升为何可能是好信号? 直答 2:切换窗口内说明资源端挡住了迟到旧写,但应在隔离后回落。
- 追问 3:事故时可以先重启所有实例吗? 直答 3:不应,先保全时间线和多数派、任务及外部证据,再逐故障域处置。
延伸阅读:协调服务事故证据链、DevOps(开发运维一体化)事故闭环。
综合题 15:调度控制器停机后如何补漏而不制造洪峰
- 问题:调度控制器停机后如何补漏而不制造洪峰?
口述答案:恢复前先从任务定义和计划版本重算“本应产生哪些触发”,不能只从当前时间继续。控制器持久化最后确认水位,但水位写入与触发、消息发布之间可能有失败窗口,所以恢复扫描要从水位前的安全重叠窗口开始;每个候选以任务标识、计划时刻、版本和业务分区形成唯一计划键,已存在就读取原触发,缺失才新增。停机窗口的错过策略按任务合同执行:可重建报表可能只补最近一次,逐窗口统计可补全,库存释放和支付对账不能静默跳过,过期通知可能直接取消;高风险或超大窗口需要人工批准。先计算补漏规模、任务服务时间、重试比例和下游配额,得到恢复所需成功吞吐;若实时新增加补漏超过安全水位,按任务风险和年龄分批,给实时任务保底,历史恢复用独立令牌。消息只负责唤醒,新增触发先落任务库再发布;发布失败由待执行扫描补发,避免为了补漏反复直接调用执行器。若使用主节点,新主取得更高控制任期后才推进水位,旧主迟到推进被 Fencing Token(栅栏令牌)拒绝;分片迁移也保留重叠读取。恢复从 10% 低风险任务开始,观察触发新增、唯一冲突、最老漏触发年龄、有效成功吞吐、重试比和下游高分位,再逐级扩大。最后用“计划集合减触发集合”查漏,用“触发集合减业务结果”查未完成,并确认每个跳过或取消都有策略版本和审计,不能以调度线程重新运行作为恢复证据。
- 追问 1:为什么不能从最后水位直接继续? 直答 1:水位与触发提交可能未原子闭合,直接继续会留下静默缺口。
- 追问 2:补漏是否全部立即执行? 直答 2:不是,按错过策略、风险、时效和下游容量分层。
- 追问 3:消息发布失败会漏任务吗? 直答 3:触发已落库时可由待执行扫描补发,消息不是权威事实。
- 追问 4:如何证明补漏完成? 直答 4:重算计划差集、触发差集和业务未完成清单,逐项闭合。
延伸阅读:主节点选举与恢复放量、容量与积压恢复。
综合题 16:消息处理成功但确认丢失时怎样处理重复投递
- 问题:消息处理成功但确认丢失时怎样处理重复投递?
口述答案:我会把消息确认和业务完成分开。MQ(消息队列)把触发标识至少一次交给 Runner(执行器),消费者先从任务库读取当前状态、暂停闸门和租约,而不是仅凭消息内容执行。领取通过条件更新产生执行代次与 Fencing Token(栅栏令牌);业务处理以业务对象、动作和版本组成幂等键,本地业务状态、幂等结果和必要 Outbox(发件箱)在同一事务提交。只有业务结果达到可恢复确认边界后才确认消息,因此“业务已提交、确认丢失”会导致再次投递,但新消费者看到任务已成功或幂等结果已存在,就返回原结果并确认,不重复副作用。若任务状态回写失败但业务已成功,重复执行不能只看任务仍是执行中;要先查幂等结果或外部业务号,补齐任务状态。外部调用成功但响应丢失时,仍按原业务号查单,不能换号重试。相反,先确认再处理会在进程崩溃时造成业务未完成却消息消失,只能依赖任务库扫描补救,因此不应作为默认边界。预取和本地线程池要有界,避免消息从代理层搬到不可见内存;消费超时要大于正常处理或使用续租机制,防止长任务反复投递。指标同时看重复投递、租约冲突、幂等命中、业务成功、确认延迟和未确认数量。验收注入业务提交后断开确认、确认响应丢失和消费者重启,证明多次投递只有一个有效业务结果,任务状态最终补齐,并能从审计还原每次尝试。
- 追问 1:能否先确认再异步处理? 直答 1:会把崩溃窗口变成静默丢失,除非任务已可靠落库且有独立扫描恢复。
- 追问 2:业务成功但任务状态失败怎么办? 直答 2:读取幂等或外部结果补齐状态,不重新执行副作用。
- 追问 3:重复消息需要报错吗? 直答 3:同参数重复返回原结果即可,异常升高再告警排查确认和稳定性。
延伸阅读:消费确认与重复投递、Runner(执行器)消息案例。
综合题 17:租约过期为什么要使用权威时间而不是客户端时钟
- 问题:租约过期为什么要使用权威时间而不是客户端时钟?
口述答案:租约的核心是所有参与者对“当前资格何时失效”有同一个可原子裁决的答案。若 Runner(执行器)A、B 各用本机墙上时钟判断,时钟漂移、校时回拨、虚拟机暂停和网络延迟会让 A 认为租约未过期、B 认为已经过期;即使时间同步通常准确,也不能把概率当正确性。更安全的做法是在任务数据库或协调服务端用权威时间执行条件更新:领取要求现有租约为空或服务端判断已到期,续租要求持有者、Fencing Token(栅栏令牌)和状态匹配,并由同一端计算新截止。客户端只把截止用于观测和提前心跳,不单方面宣布自己仍有资格。心跳间隔小于租约,并根据续租延迟高分位、短暂停顿和接管目标留余量;连续续租失败时本地先关闭提交闸门,不能等本机倒计时。服务端时间也不是全部答案:数据库不可用时无法续租,旧进程仍可能运行,所以业务资源端还要校验令牌;权威时间解决过期裁决,Fencing Token(栅栏令牌)解决迟到写,业务幂等解决同代次重复。排障时记录服务端领取与续租时间、客户端发送接收时间、发布和全停顿,区分网络慢、数据库慢与本机暂停。故障演练主动制造客户端时钟偏移、长全停顿和数据库响应延迟,期望只有权威存储成功条件更新的一方获得新资格,旧方恢复后所有提交被拒绝。若跨区域数据库时间语义不稳定,应把租约放在单一一致性域,不能让多个区域各自判断同一资源过期。
- 追问 1:时钟同步很准还需要权威时间吗? 直答 1:需要,正确性不能依赖偶然小漂移,领取与过期必须同点原子裁决。
- 追问 2:数据库不可用时能否沿用旧租约? 直答 2:不应开始新副作用,资格不可验证时进入暂停并等待恢复或接管。
- 追问 3:单调时钟能解决跨实例比较吗? 直答 3:只能测本进程间隔,不能给不同主机提供共同过期事实。
综合题 18:Zookeeper(分布式协调服务)选主与数据库租约如何选
- 问题:Zookeeper(分布式协调服务)选主与数据库租约如何选?
口述答案:我不会先按产品偏好选,而是看控制面规模、现有运维能力、任务事实位置、选举频率和失败模型。Zookeeper(分布式协调服务)适合多个同构实例中选择低频主节点或维护分片成员,临时节点与成熟客户端配方能处理会话和竞争;但获得 Leader(领导者)回调只代表协调资格,连接丢失要暂停,会话过期后旧进程仍可能写,因此必须再取得业务任期并在资源端校验 Fencing Token(栅栏令牌)。数据库租约适合任务和状态本来就在关系库、领取规模可由索引与分片承受的场景,条件更新可在同一事务中写持有者、截止和令牌,恢复与审计直观;代价是扫描、热点和续租会增加数据库负载。两者也可以组合:Zookeeper(分布式协调服务)只选出触发控制主节点,具体任务仍由数据库唯一计划键、租约和业务幂等裁决。不能让选主直接承担每个高频任务锁,也不能因数据库租约简单就忽略服务端时间、索引和故障余量。选择前做 E3(演练证据)实验:模拟网络分区、会话过期、长全停顿、数据库慢、主节点切换和任务洪峰,比较选举或领取延迟、误接管、数据库写放大、旧令牌拒绝和恢复时间。若团队没有可靠协调集群且任务量中等,数据库方案可能更可维护;若已有成熟协调平台且需要独立控制面,可采用选主,但业务正确性仍落在任务库和副作用端。真实生产组件与版本没有 E1(直接证据)时,只把两者作为候选,不宣称已经使用。
- 追问 1:选主能否替代任务唯一键? 直答 1:不能,主切换、响应丢失和同任期重试仍会重复触发。
- 追问 2:数据库租约是否就是数据库锁? 直答 2:它通常是带过期、持有者和令牌的持久化状态,不应长时间持有事务锁。
- 追问 3:两者组合会不会过度设计? 直答 3:只有独立控制面和高频任务裁决确有不同需求时组合,否则优先更简单的单方案。
- 追问 4:最终安全边界在哪里? 直答 4:业务幂等、状态机和资源端令牌校验,不在选举产品本身。
综合题 19:新旧 Runner(执行器)如何灰度迁移、回退并核算成本
- 问题:新旧 Runner(执行器)如何灰度迁移、回退并核算成本?
口述答案:迁移原则是双算可以、同一任务类型双主执行不可以。第一阶段新系统只读旧计划并影子计算触发集合,按任务、计划时刻、版本和时区比较差集,不接触业务副作用;第二阶段写隔离的影子触发,验证扫描、分片、容量和错过策略;第三阶段选择可重建、低风险任务单主切换,新系统生成和执行,旧系统对该类型关闭触发但保留查询与紧急回退。切换前冻结计划版本,记录水位和在途清单,确认业务幂等键、Fencing Token(栅栏令牌)、暂停与故障实验通过;切换后按 10%、25%、50%、100% 扩大类型或租户。回退不是两边一起开:先关闭新系统领取,等待短任务、保存长任务检查点、查明外部未知,再让旧系统只接管租约过期且未完成的触发,沿用业务键,不能全量重跑。停止线包括漏触发、重复有效副作用、持续旧令牌拒绝、最老年龄、下游高分位、人工差异和审计缺口。成本以正确完成并通过业务验收的结果为分母,计入实例与故障余量、数据库扫描和续租、MQ(消息队列)、任务与审计存储、第三方调用、重试、补偿和人工;不能靠缩短幂等保留、删除审计或放大下游风险得到虚假低成本。每阶段保存新旧差集、资源和单位成本,收益没有原始账单与运行记录时保持 E0(待核对)。最终收口要求旧在途清零、历史可查询、人工异常关闭、回退快照可验证,再归档旧调度,而不是一切流就立即删除旧数据。
- 追问 1:为何不能长期双主执行? 直答 1:会分裂触发、租约与重试责任,并让不支持令牌的下游产生重复副作用。
- 追问 2:回退为何不能全量重跑? 直答 2:已有任务可能成功或未知,应按租约、幂等结果和外部查单逐项接管。
- 追问 3:单位成本分母是什么? 直答 3:正确完成且通过业务验收的结果,不是触发次数或线程完成数。
延伸阅读:架构演进与技术债、POC(概念验证)风险验收。
综合题 20:请用三分钟串讲 Runner(执行器)项目并守住事实边界
- 问题:请用三分钟串讲 Runner(执行器)项目并守住事实边界?
口述答案:我会先说事实边界:现有材料能支持 Runner(执行器)任务状态、分片抢占、退避重试、线程隔离和人工补偿这个项目方向,属于 E2(已有材料映射);租约、Fencing Token(栅栏令牌)、容量数字和恢复门槛是 E3(演练证据);真实源码、表结构、部署规模、事故和收益仍是 E0(待核对)。业务目标不是“定时器能跑”,而是任务不静默丢失、重复尝试不产生重复有效副作用、执行中重启可恢复、状态可查且高风险异常可人工接管。设计上先把定义、触发、尝试和业务结果拆开,计划修改生成版本,任务加计划时刻和版本形成唯一计划键;控制器分片扫描并重叠补漏,MQ(消息队列)只唤醒。Runner(执行器)用条件更新抢占有限租约和递增令牌,独立心跳续租,失租立即关闭提交闸门,新实例从检查点接管。我要主动强调:调度唯一不等于业务幂等,锁不能远程阻止旧持有者写;数据库和可控资源必须原子校验令牌,业务动作还要稳定幂等键与状态机,第三方超时沿用请求号先查单。任务按库存资金、物流、报表和恢复拆资源组,重试按错误分类并限制次数、时长、年龄和并发,部分成功创建补偿单。积压用有效成功吞吐减新增率计算,按比例恢复;发布先停领、排空、检查点和未知清单。线上以触发差集、最老年龄、旧令牌拒绝、幂等命中、业务差异和人工积压验收。若面试官追问真实效果,我会说明需要任务表、配置、监控、流水和复盘才能升级,而不把演练数字说成上线成果。
- 追问 1:项目最重要的架构取舍是什么? 直答 1:接受至少一次尝试,以业务幂等和栅栏换取可恢复性,而不虚构恰好一次。
- 追问 2:最重要的反例是什么? 直答 2:租约过期后旧线程仍能写,锁服务无法替代资源端令牌校验。
- 追问 3:如何证明没有漏任务? 直答 3:按计划版本重算应触发集合,与触发和业务结果做差集。
- 追问 4:如何证明没有重复效果? 直答 4:核对业务幂等结果、状态机、外部请求号和对账流水只有一个有效终态。
延伸阅读:Runner(执行器)项目案例、架构治理与可观测性决策。
14. 复习与验收清单
- 能区分任务定义、触发实例、执行尝试、检查点、业务结果和异常单。
- 能说明唯一计划键只保证触发身份,业务幂等键才约束重复副作用。
- 能画出任务注册、分片扫描、租约领取、心跳、失租和接管路径。
- 能解释锁不能远程阻止旧持有者写,以及 Fencing Token(栅栏令牌)必须在资源端原子校验。
- 能演绎旧令牌迟到写、重复执行、重试风暴和消费者积压。
- 能按错误类型给出重试、查单、补偿、死信和人工接管边界。
- 能设计任务级、类型级、租户级、资源组级和全局级暂停恢复。
- 能处理执行中发布重启,形成检查点、未知调用和未完成清单。
- 能用有效成功吞吐和新增率计算净恢复时间,并保护下游安全水位。
- 能给库存资金、物流、报表和历史恢复建立独立资源与公平份额。
- 能从计划、租约、令牌、业务结果、外部回执和人工工单建立事故证据链。
- 能按 E0(待核对)、E1(直接证据)、E2(已有材料映射)、E3(演练证据)控制项目陈述强度。
- 能真实渲染 12 张 Mermaid(图表语法)和正式 PlantUML(统一建模语言)图,并目视检查同名 PNG(便携式网络图形)。
- 能确认 12 个知识小节各有 3 道六字段题,20 道综合题均有 3 至 5 组追问直答与真实相对链接。
