先看事故:请求超时,系统却把同一篇简报发布了两次

周予经营一项面向 63 名付费客户的行业情报服务。每周一,她从 12 个固定来源整理 9 项变化,生成网站简报,检查 17 个引用链接,再给客户发邮件。最初的自动化只有“定时触发 → 抓取 → 生成 → 发布 → 发信”。前 7 周一切正常,第 8 周却出现了一次“请求结果未知”的事故。

时间 自动化看到的情况 外部实际情况
09:12:03 发出创建文章请求 内容管理系统已创建文章 A
09:12:33 30 秒未收到响应,标记失败 响应丢失,文章 A 仍公开
09:13:03 无条件重试创建 内容管理系统又创建文章 B
09:13:08 收到文章 B 的网页地址,继续发邮件 网站存在两个同标题版本
09:31 客户报告链接内容不同 才发现重复发布与版本分叉
09:12:03 至 09:13:08 双轨时间线:自动化把状态未知误判成失败并重新提交,内容管理系统因此创建文章 A 和文章 B
响应丢失如何造成重复发布。按行对照自动化掌握的状态与内容管理系统中的实际状态:重复来自“没有确认就等于没有执行”的错误判断,而不是 30 秒本身。时间与文章 A、B 来自虚构事故,不能由此推断所有创建接口都支持查询或安全幂等重试。

问题不是“超时时间太短”,而是系统把“调用方没收到确认”误写成“外部动作没发生”。网络断开、执行器崩溃、人工双击、定时任务重叠和供应商延迟都可能制造这种不确定状态。

重建后,每期简报先建立持久任务和操作记录;内容管理系统超时后先按业务键查询,不直接重建;内容审批绑定具体草稿;邮件发送另有受众哈希值和第二道审批。48 个故障用例全部收敛后,系统连续运行 30 期:28 期完成,1 期因来源缺失而阻塞,1 期在发信前回退网站并完成补偿;3 次内容管理系统响应丢失和 2 次邮件响应丢失都没有造成重复外部动作。

本文为虚构教学案例。数字用于展示可靠性设计,不代表任何内容管理系统、邮件平台或行业表现;涉及付款、法律承诺、健康、安全、身份和不可逆客户权益时,应由有经验的工程、安全、隐私或法律人员设计与审阅。

先画边界和不变量:状态机要保护业务承诺,不是给流程多加颜色

周予把动作分成读取、内部写入、可撤回外部写入和不可完全撤回动作。来源抓取失败可以重试;保存快照需防重复;网站版本通常可回退;邮件一旦送达不能真正撤回,只能停止后续发送或更正。不同副作用决定不同状态与恢复策略。

不变量 必须始终成立 证明位置
I01 一期、语言、内容类型只有一个任务 任务键唯一约束
I02 发布内容必须与获批版本一致 审批与发布回执
I03 一项发布意图至多一个内容管理系统对象 操作键与外部查询
I04 未公开验证不能进入邮件审批 状态转换守卫
I05 邮件受众与正文变化后旧审批失效 受众与内容哈希值
I06 成功必须有远端可查询回执 文章与活动回执
I07 状态未知绝不等于失败或成功 操作状态枚举
I08 补偿不删历史事实 仅追加事件/收据
读取来源、保存内部产物、发布网站版本和发送客户邮件四类动作,分别对应从有限重试到独立审批、远端回执和补偿的保护要求
副作用边界决定保护强度。从左到右读取四类动作,再沿下方看调用前、调用中和调用后的控制;越难撤回,越不能只靠重试。分类基于本案例的内容管理系统与邮件能力,实际可撤回性仍要按目标平台、缓存、受众和合同核实。

不变量先于状态名。若流程可以从“有草稿”直接跳到“发信成功”,无论看板多漂亮都不可靠。状态机的工作是阻止非法承诺,并保存恢复所需证据。

每条不变量都要有破坏测试。临时移除发布守卫,让“等待内容审批”直接提交内容管理系统,I02 测试必须失败;把发送的受众哈希值改一个字符,旧批准必须被拒;在操作表中预先放入第二条相同操作键的记录,唯一约束必须阻断。只在文档里写“不应该重复”,却没有执行层控制,事故发生时仍会失守。

不变量之间也有顺序:I02 和 I03 保护发布发生前与发生中,I06 约束“完成”声明,I08 保护恢复记录。周予把每个外部动作的先决条件、影响和后置条件并排写清:调用前有批准,调用中有操作记录,调用后有远端回执;缺任何一段都不能推进。

每次运行先建立任务、步骤和操作三类持久记录

任务描述整期目标,步骤记录内部阶段,操作专门记录一次可能产生外部副作用的意图。浏览器窗口、聊天记录和平台通知都不能替代这三类持久状态。

记录 关键字段 本例
任务 任务编号、任务键、状态、状态版本 简报-2026-W30、版本 17
输入 来源快照、缺失、截止 12/12、07:55
产物 路径、哈希值、结构版本 草稿 D-8f3c
步骤 名称、尝试、开始/结束、错误 检查链接尝试 2
操作 操作键、意图哈希值、远端编号、状态 发布…、18427、已确认
批准 范围、产物哈希值、执行者、过期 内容 D-8f3c、周予
租约 负责人、租约截止时间、心跳 工作节点 02、09:15
事件 序列、发件人/收件人、原因、证据 41、发布→已发布

任务状态是业务事实来源;步骤日志解释运行细节;操作解决“请求发出后到底发生了什么”。把三者塞进一个最近错误字符串,会丢掉并发、重试与对账所需信息。

这些记录有不同的保留策略。任务、批准、外部操作和补偿证据随客户交付记录保存;体量很大的调试日志按较短周期清理;来源内容按授权处理。如果原文不能长期保留,可在授权范围内保存不可逆哈希值,用来证明当时使用的是哪个版本。运行表不直接复制 9 项事实正文或 63 个邮件地址,避免为了观察系统而扩大敏感数据面。

写状态使用一个命令处理器:读取任务与版本,验证事件在当前状态合法,核对所需回执,在同一本地事务中追加事件并更新状态/版本。若事件保存成功但状态更新失败,事务整体回滚;若外部系统不在同一事务内,则由操作记录跨越这个边界。这样“记录说发布过但任务仍在草稿”可以被对账发现。

状态转换必须列出事件、前置条件、保存证据和非法路径

主路径为 已创建 → 收集 → 起草 → 检查 → 等待内容审批 → 内容已批准 → 发布 → 已发布 → 等待发送审批 → 发送 → 成功。等待重试、已阻塞、补偿和已补偿是分支;状态不是由页面按钮随意覆盖,而由守卫验证后追加事件。

当前 事件 下一状态 守卫/证据
已创建 执行器已领取 收集 租约有效
收集 来源已冻结 起草 12项或有批准缺失说明
起草 产物已生成 检查 草稿校验和
检查 硬检查已通过 等待内容审批 9 事实、17 链接回执
等待内容审批 内容已批准 内容已批准 参与者 + 哈希值 + 过期时间
内容已批准 发布已开始 发布 操作键唯一
发布 远端已确认 已发布 文章编号与公开页探测
已发布 已请求发送审批 等待发送审批 统一资源定位符、受众哈希值
等待发送审批 发送已批准 发送 独立批准且未过期
发送 活动已确认 成功 活动编号与状态查询
任一执行态 无法恢复/需人工处理 已阻塞 原因 + 负责人 + 后续操作
已发布前后 已决定补偿 补偿 事件与计划
等待内容审批经批准事件、参与者和哈希值守卫进入内容已批准状态,并保存批准回执、哈希值、时间和状态版本;三条越级路径被禁止
合法转换不是手工改状态。上方从当前状态、事件与守卫读到下一状态及证据;下方三条红线说明哪些越级会丢失业务事实。图中只展开内容批准这一条转换,发布、发送、阻塞和补偿仍须使用各自守卫,不能照抄一个通用按钮。

禁止路径包括“等待内容审批 → 已发布”“内容已批准 → 发送”“发布超时 → 已创建”和“已阻塞 → 成功”。恢复不是手工把状态改绿,而是补足证据后触发一条合法事件。

已阻塞也不是终点垃圾桶。记录阻塞原因枚举、人工负责人、所需证据、恢复事件和过期时间:缺来源恢复到收集,权限修复后重新执行原步骤,内容冲突回检查,审批过期则重新收集/检查而不是从已批准继续。不同原因共用一个“重试”按钮会绕过原守卫。

状态图还写终端语义:成功、已补偿和保护性关闭都不可由执行器继续;需要新意图时建立新任务版本并用取代连接。历史任务不改写为“最终成功”,否则无法解释当时是否已对客户产生影响。

用版本号、租约和心跳防止两个执行器同时推进同一任务

第 8 周事故也可能由两个定时器同时领取任务造成。任务行保存状态版本;更新时要求“当前版本必须为 16”,只有一个执行器能将它写成 17,另一个收到冲突后重新读取。领取时还建立短租约,执行器每 30 秒报告一次心跳,租约过期后才能被另一个执行器接管。

并发场景 错误做法 控制
两个调度器同刻触发 各建一条任务 任务键唯一约束
两个执行器同时领取 看见排队就都开始 版本比较更新与租约
执行器崩溃 任务永久运行中 租约到期 + 清理者
旧执行器恢复 继续写新状态 租约令牌/隔离版本拒绝
人工与执行器同时改 最后写入优先 状态版本冲突
心跳延迟 立刻双执行 宽限期+远端操作对账

租约表示临时执行权,不表示外部动作没有发生。新执行器接管发布状态时,第一步读取操作并查询内容管理系统,而不是因租约换人就重发请求。

隔离令牌随每次租约递增,并传给支持条件写入的内部工具。执行器 01 拿到令牌 7 后暂停,工作节点 02 接管并获得令牌 8;即使执行器 01 稍后恢复,它的内部写入也会因令牌过旧而被拒。无法接收令牌的外部内容管理系统,则依靠操作键、远端查询和本地操作记录保护,不能把租约误当成跨系统锁。

清理程序不会在租约刚过期时同时启动多个接管。它先用原子比较更新把负责人从旧令牌切到新令牌,保存恢复来源和最后一次心跳,再根据任务状态选择恢复入口。起草阶段可以重新生成;发布和发送阶段只能先对账。同样是“执行器失联”,所在状态不同,恢复动作也不同。

幂等键标识业务意图,不是给每次网络请求生成一个新通用唯一标识符

本例的任务键可以读作“周报:2026 年第 30 周:中文”。发布操作还要加入内容哈希值;邮件操作还要加入公开网页地址、受众哈希值和正文哈希值。同一意图的重试必须复用同一个操作键。参数变了却沿用旧键,应返回“意图不一致”;重试时生成新键,服务端就无法知道它们属于同一次业务动作。

操作 操作键组成 同键允许 必须新键
快照 来源 + 截断 同一抓取重试 截止改变
发布 任务键 + 草稿哈希值 超时后查询/重试同内容 获批内容变化
公开更新 文章编号与草稿哈希值 同版本重复提交 新网站版本
发送 任务键+网页地址+受众哈希值+正文哈希值 状态查询/安全重试 受众或正文变化
补偿 事件编号与操作 失败后继续同一补偿 补偿决定改变

RFC 9110 将幂等方法解释为:重复发送相同请求,服务端的预期效果应与只发送一次相同;它也提醒,若无法确认原请求未生效,就不要自动重试非幂等请求。业务接口仍要按自己的意图设计,HTTP 方法名本身不能替代唯一约束、参数比较和结果保存。

键还有作用域与寿命。第 30 周的中文简报与英文简报是两个任务键;同一期重新发布新内容,因为草稿哈希值不同,也构成新的发布意图;但单纯的网络重试必须沿用旧键。外部服务若只保留键 24 小时,48 小时后到达的延迟请求可能不再被去重。因此本地操作记录要长期保存远端编号,超出服务窗口后禁止自动重提。键的过期策略是接口合同的一部分。

同键参数冲突不能“以第一次为准”静默成功。若第一次请求D-8f3c,第二次误带D-91aa,系统返回意图不一致并同时保存两组哈希值供调查;否则调用方可能误以为新内容已发布。随机通用唯一标识符只保证碰撞少,不表达同一次业务意图,重跑时生成新通用唯一标识符反而破坏幂等。

完整走一遍:创建已经成功但响应丢失,怎样收敛到文章 18427

第 30 周的内容 D-8f3c 获批后,系统先把操作状态写为“已准备”,再发送带操作键的创建请求。30 秒后连接断开,操作改为“状态未知”而不是“失败”,任务也停留在发布阶段。恢复执行器按操作键查询内容管理系统,找到文章 18427;确认外部键、草稿哈希值和任务编号一致后,才保存远端编号并探测公开页面。

可复制模板
已准备 → 请求已发送 → 状态未知
  → 按操作键
  → 找到文章 18427
  → 核对意图哈希值 + 内容哈希值
  → 已确认 → 公开页探测 → 已发布
对账结果 处理
找到 1 条且哈希值一致 回填远端编号,继续探测
找到 1 条但哈希值不同 冲突,已阻塞,不覆盖
找到 2 条 记录事件,冻结发布并人工选择
未找到但平台仍可能处理 等待 2 分钟再查,不立即重建
明确未找到且请求可安全复用键 在预算内重试同意图
查询接口不可用 保持状态未知并告警
D-8f3c 的内容管理系统操作从已准备、请求已发、状态未知进入按操作键查询和意图核对,最终确认远端文章 18427;冲突与暂未找到分别冻结或等待
状态未知如何收敛到文章 18427。先沿上方读取同一操作意图的六个状态,再用下方三种查询结果决定回填、冻结或等待;任何分支都不直接生成新键重建。18427 与 D-8f3c 是虚构标识,安全重试仍取决于目标内容管理系统的查询、一致性窗口和幂等合同。

状态未知必须被当成正式状态。只有外部证据,或语义明确的幂等重试,才能消除它;不能靠“通常 30 秒没回就是失败”来猜。

时间线继续到邮件:09:14 的前两次公开探测都读到旧缓存,第 3 次才在 09:14:42 看到文章 18427 和内容 D-8f3c,任务由此进入“已发布”。09:18,周予批准发送;09:19:05,邮件平台接受活动,但响应再次丢失。系统没有新建活动,而是用本地保存的活动创建键查询,并在 09:20:11 找到活动 930。受众哈希值与正文哈希值一致后,才保存远端编号。两次“状态未知”遵循同一原则,但查询字段、等待窗口和完成证据并不相同。

对账回执要保存查询时间、查询参数摘要、返回对象数、选定的远端编号、意图比较结果和操作者。若外部搜索采用最终一致性,第一次“未找到”不能当作“绝对不存在”,还要按照平台已知的可见延迟等待。既没有可靠查询、创建动作又不具备幂等语义的系统,不适合自动重试高影响写入,应停在人工操作。

错误分类先决定动作:瞬态、永久、业务、状态未知和人工不是一回事

类别 例子 自动重试 下一步
瞬态 域名解析失败、429、短暂 5xx 有限 退避与抖动
永久输入 字段结构错误、统一资源定位符非法 已阻塞,修输入
鉴权/权限 令牌过期、403 已阻塞,更新授权
业务 9 项事实中 2 项无来源 人工删主张或补资料
未知副作用 提交后超时 不盲重试 查询与对账
人工决策 同级来源冲突 等待或阻塞
不变量违背 同操作键出现两个对象 记录事件并冻结

同一个 HTTP 500,在只读抓取中可能只是瞬态错误;在创建请求发出后响应丢失时,却代表副作用未知。错误分类必须绑定具体操作,不能只按状态码全局处理。

适配器负责把平台原始错误映射为内部类别,并保存原始代码供审计。例如,内容管理系统返回 409 时,如果同时返回同一外部键对应的对象,它就是待核对候选;如果标识符已被别的任务占用,则属于业务冲突。邮件平台返回 429 时,可以按照响应中的建议等待时间重试;但如果邮件已被接受,只是后续查询遇到 429,发送操作仍然是状态未知,不能重新发信。映射表要按工具版本测试,不能让大模型临场阅读错误文字,再决定高影响动作。

每个类别有负责人:瞬态由执行器,输入由内容负责人,认证由账户负责人,业务由周予,不变量违背由事件负责人。最近错误写错误类别、工具、操作、尝试、原始记录和下一步动作;一句“调用失败,请重试”既无法路由责任,也无法判断能否安全重试。

重试策略要有范围、间隔、抖动、上限和总预算

周予为每个工具分别写策略,而不是统一规定“失败重试 3 次”。来源抓取在 20 秒调用超时后最多尝试 3 次,等待约 30 秒、2 分钟和 10 分钟,并加入小幅随机抖动;遇到 429,优先遵循服务端给出的等待提示。AI 草稿的结构错误只修一次;事实检查失败不靠重新生成碰运气;内容管理系统或邮件处于未知状态时,先查询。

步骤 最大尝试次数 预算 重试前 已耗尽
源获取 3 15 分钟 分类、缓存、服务端等待提示 缺失或被阻止
AI 草稿结构 2 5 分钟 返回结构错误 已阻塞
链接读取 2 3 分钟 区分超时与 404 状态未知或不通过
内容管理系统创建 2 个提交上限 8 分钟 必须先完成操作查询 被阻止且状态未知
公开探测 6 3分钟 只读检查 保留旧版
邮件创建或发送 不盲目重建 10 分钟 活动查询 被阻止且状态未知
补偿 3 20 分钟 查当前远端状态 人工事件
来源、草稿结构、内容管理系统创建和邮件发送使用不同重试语义;开发工具包、应用和工作流三层各重试三次时,一次意图最多放大为 27 次调用
重试先按副作用分类,再设置一个总预算。上表先判断每类动作能否重试和耗尽后去哪;下方乘法说明三层隐藏重试会把一次意图放大到 27 次调用。次数与分钟数是本案例配置,不是通用参数,实际策略还应服从任务截止日期与服务端提示。

重试层不能叠加失控:开发工具包 3 次 × 应用 3 次 × 工作流 3 次,会把一次业务意图放大成最多 27 次外部调用。配置时要盘点所有重试层,只保留一个可观察的总预算。无限重试既会拖延任务,也可能加重依赖方的故障。

预算按任务累计,而不只按单个步骤。12 个来源若各自重试 3 次,极端情况下会产生 36 次请求,并把任务拖过 10:30。因此,收集阶段设置 15 分钟总截止时间和 3 个并发上限。第一个来源原计划等待 10 分钟,也可能被任务截止时间截断;此时策略返回“预算耗尽”,而不是继续跑完整个重试序列。可靠性设计必须服从业务时效。

抖动用于避免多个运行同刻重试,不代表等待随意。回执保存策略版本、基础延迟、服务器提示、实际延迟和尝试。若7天重试率升至基线2倍,先查依赖和负载,不通过提高最大尝试次数把告警压下去。电路开启时新请求快速失败并进入可见被阻止/重试等待,给下游恢复空间。

超时至少有调用、步骤、任务、租约和审批五种时钟

超时/截止日期 本例 到期含义 处理
呼叫超时 获取 20 秒、内容管理系统 30 秒 本次等待结束 分类,不推断副作用
步骤截止日期 收集 15 分钟 阶段超预算 保存进度,被阻止/重试等待
任务截止日期 周一10:30 本期时效不足 不再自动发送
租约到期 90 秒无心跳 执行器可能失联 新工作节点隔离接管
内容审批 24 小时 来源或草稿可能过期 回到收集或检查
发送审批 2 小时 网页地址或受众可能变化 重新验证再审
调用、步骤、任务、租约和审批五类时钟分别控制等待、阶段预算、业务许可、执行权和批准有效性,均不能直接证明远端动作失败或取消
到期改变的是不同权利与知识。逐列读取五类时钟:调用停止等待,步骤和任务截断预算与许可,租约转移执行权,审批使旧授权失效;下方明确它们都不能替代远端查询。时长来自虚构周报,实际阈值需按服务延迟、人工窗口和业务截止设置。

超时不是远端动作已经取消的证据。客户端停止等待后,内容管理系统或邮件平台可能仍在继续处理;因此,调用超时只改变本地掌握的信息。任务截止时间则代表业务决定:超过 10:30,宁可人工通知或延期,也不能为了“完成自动化”而发送未经核对的内容。

截止日期沿调用链向下传,内部步骤不能各自拿满默认超时。09:58开始公开探测时,距离任务截止日期只剩32分钟;发送复核至少保留10分钟人工窗口,系统可在10:20停止自动路径。审批计时使用可靠服务器时间并记录时区,不能用浏览器本地时钟决定过期。

超时后还要区分“请求取消”和“已经取消”。纯内部渲染可以取消并清理候选项;对外部发布发送取消请求,本身也只是一项新操作,必须查询是否生效。用户点击“取消”表示改变了意图,不代表远端已经恢复原状;界面应分别显示“已请求取消”“已确认取消”或“状态未知”。

审批必须绑定对象、证据、范围和有效期,不能只存一个“已批准”

内容审批包展示 12 个来源快照、9 项事实及其位置、17 个链接、警告、草稿哈希值和计划执行的外部动作。批准记录写明执行者、授权范围“发布”、哈希值 D-8f3c、检查回执 C-441 和过期时间。正文任何字节或关键元数据发生变化,旧审批都不能再用于发布。

批准 绑定 不授权
内容发布 草稿哈希值、检查、目标内容管理系统与别名 发邮件
发送 公开网页地址、正文哈希值、63 人受众哈希值 改受众或正文
已接受的警告 指定警告编号与理由 接受其他警告
补偿 事件、远端对象、动作 删除审计记录或重发邮件

发布后第二道发送审批先访问公开统一资源定位符,确认页面哈希值、标题、17个链接与预期,再显示63名受众的分组和抑制结果。把“发布并通知”合成一个按钮会让网页错误在人工看到之前传播。

审批可以撤销。周予若在发布前发现来源更正,就记录“审批已撤销”事件;守卫要确认审批尚未撤销,且产物仍与获批版本一致。若发布已经执行,撤销不能假装请求从未发生,任务应转入状态未知或补偿,并先查询远端。批准界面因此要显示操作是否已经开始,以及此刻撤销可能产生的实际效果。

代理审批、批量批准和永久批准不用于首版。周予若无法在24小时内审,任务过期;系统不会因“这类周报以前都批”自动延续许可。审批时间是重要运行成本,最近 30视图分别统计等待内容与发送复核,避免把人工瓶颈误诊为模型慢。

来源、草稿和检查产物都要冻结,批准后重新生成必须重新审

12 个来源分别保存网页地址、抓取时间、内容哈希值和许可范围;抓取失败时,不能让 AI 凭记忆补事实。事实表中的 9 项声明全部有来源位置,17 个链接的检查回执区分 200、重定向、超时和已阻塞。草稿、事实表、链接报告和渲染页面共同组成审批内容包。

包项 版本证据 硬性放行门槛
来源快照 12 个哈希值与截止时间 缺关键来源则阻塞
事实表 9 组声明与证据 缺少证据支持的主张为 0
草稿 内容哈希值 与批准一致
链接 17 个最终网页地址与检查时间 损坏为 0;状态未知转人工
渲染 公开预览哈希值 标题、正文和引用存在
发布说明 任务、操作和审批编号 可追到一次运行

AI若在内容管理系统写入前“顺手润色”一个段落,哈希值变化就回到检查和等待内容审批。批准保护的是具体产物,不是对主题的泛化许可。

计算哈希值前先规范化明确可忽略的元数据,例如构建时间不进入内容哈希值,但标题、正文、引用、链接和语言都进入;否则每次导出产生新哈希值导致无意义重审,或反过来漏掉重要变化。哈希规则本身有版本,批准记录“哈希规则版本=2”,升级算法时不能直接比较旧值。

来源在审批后变化也会使包过期。系统不持续联网偷偷替换12个快照,而是保存截止;若监控发现关键来源更新,生成源变更警告,由周予决定是否回收集。已批准事实不会因网页后来改变而被历史重写。

外部写入先记录意图,再执行,再保存远端编号和验证回执

发布与发送都使用意图日志。先在本地事务中创建已准备的操作并关联任务;执行器领取后才调用外部平台;响应成功先保存远端编号,再推进任务。若进程在外部成功和本地保存之间崩溃,恢复可按操作键查询,而不是丢失关联。

操作状态 说明 可否再执行
已准备 意图已持久化,尚未调用 可由单一租约执行
执行中 请求已发,结果未定 不并发重发
状态未知 连接/执行器丢失 只查/对账
已确认 远端编号与意图匹配 返回原结果
已拒绝 远端明确未开始且输入错误 修输入后新意图
已补偿 原副作用已有补偿 不伪装未发生

外部平台即使原生支持幂等键,本地仍要保存自己的操作记录。Stripe 当前接口文档说明,其幂等请求会按键保存首次结果,后续相同键返回相同结果,并对参数不一致作出约束;这是特定产品的语义。使用任何平台,都应核对键的保留期、参数比较、并发和错误结果规则,不能假设所有接口都一样。

“已准备”的操作与任务转换在同一个本地事务中提交,目标类似事务性出站队列:不允许任务已经进入发布,却找不到待执行意图。执行器按操作编号领取,并标记为执行中。外部结果无法与本地数据库原子提交,所以仍要保留状态未知和对账路径;出站队列能减少丢请求,却不能消灭跨系统的不确定性。

操作不可变意图与可变执行分开:操作键、意图哈希值、目标固定;尝试、状态、远端编号和最近观察更新并留事件。人工不得在同操作里换标识符或受众来“修复失败”,因为那已是新意图。仪表盘可据此区分同一意图的多次尝试与两个不同业务动作。

邮件是无法回退点:状态查询、受众抑制和更正都要单独设计

发送操作键包含任务、公开网页地址、邮件正文哈希值和受众哈希值。创建活动后先保存活动编号;真正发送前,再检查抑制名单和 63 人计数。响应超时时按活动编号或操作键查询,绝不另建活动。提供商“已接受”不等于邮件“已送达”,回执要分别记录已接受、已送达、退信和投诉。

时间点 可撤回能力 事故动作
活动草稿 可删除候选 修正后新哈希值和新审批
已接受未开始 视平台能力尝试取消 查询确认,不承诺成功
部分送达 不可整体撤回 停余量、确定受影响人
全部送达 不可撤回 更正/解释,由本人批准
退信 不盲目改用其他地址重发 核对客户记录与授权
邮件活动从草稿、平台接受、部分送达、全部送达到退信时,可撤回能力逐步下降;W30 回执为接受 63、送达 62、退信 1、活动只创建一次
邮件越往后,恢复越接近停止与更正。从左到右看可撤回能力递减,再用下方回执区分接受、送达和阅读;退信只进入人工客户维护,不让智能体猜地址。63、62、1 是虚构运行结果,实际提供商状态和取消能力必须逐项核实。

第 30 周最终回执为已接受 63、已送达 62、已退信 1;成功只表示计划动作已经完成并留下状态记录,不等于 63 人都阅读了邮件。退信进入人工客户维护队列,不触发智能体自行猜测邮箱。

受众哈希值由排序后的收件人编号和抑制名单版本生成,不把完整地址写进批准记录。发送前重新计算:若有人退订,哈希值变化会使旧审批失效;即使受众人数减少,也不能自动沿用许可,因为授权范围已经改变。重新审批页要突出新增、移除及其原因;任何新增收件人都需人工确认。

邮件状态轮询也有上限,不能让执行器一直等待所有供应商的最终状态。平台接受活动后,任务可以记录为“发送结果待确认”并释放租约,再由定时任务轮询;30 分钟后仍有待定项,本例会改为“等待观察”并通知人工查询,而不是提前记为成功。业务状态名必须与对客户的承诺一致,不能为了让完成率好看而提前成功。

回滚、补偿、对账和转发修复是四种不同恢复

代码部署错误可把应用产物回到上一版,称回滚;网站内容可把当前版本指回已批准旧版,但已产生的统一资源定位符访问/缓存仍需处理;邮件只能补偿;状态未知先对账;数据迁移若不可安全回退则转发修复。一个“撤销”按钮无法覆盖这些语义。

副作用 首选恢复 所需证据
内部草稿 丢弃候选项 来源未改哈希值
内容管理系统新版本 指回上一正确版本并验证 文章与版本编号
重复文章 冻结、选择规范版本、重定向或下架 两个远端编号与访问记录
邮件错误 停止剩余并修正 活动与收件人状态
客户数据写错 补偿事件或转发修复 前后及负责人
未知发布 查询/对账 操作键 + 远程搜索

Microsoft Azure 架构中心的补偿事务模式强调补偿是业务特定、可能失败、需记录进度并可恢复,而且不一定把系统恢复成初始状态。本文借用这一分布式恢复思想;63人邮件案例仍需按实际服务能力和客户沟通责任处理。

补偿计划逐项记录原操作、当前远端状态、补偿动作、先决条件、负责人和验证方法。例如,在把文章 18427 回退到版本 v29 前,先确认当前仍是错误的 v30;若他人已发布 v31,直接指回 v29 会覆盖新工作,必须转人工或采用向前修复。补偿不能无条件执行“反操作”。

补偿自身使用独立幂等键,也必须能够暂停后恢复。步骤 1 关闭自动发送,步骤 2 切回网站,步骤 3 验证公开页面哈希值,步骤 4 处理缓存和重定向,步骤 5 决定如何沟通;若步骤 3 失败,状态继续保持“补偿中”,已完成的步骤不重复产生副作用。恢复完成后记为“已补偿”,原发布事件仍然保留。

事件记录、告警和三个日常视图让一个人看得懂自动化现在在做什么

事件日志只追加、不改写:序列、任务、操作、发件人与收件人、执行者、时间、原因和证据位置。日志不写完整客户内容或令牌。指标从事件和操作记录中计算,不能用聊天摘要猜运行历史。

视图 显示 立即关注
今日操作 待复核、已阻塞、补偿 负责人、下一步和过期时间
卡住或未知 租约过期、操作未知、步骤超时 外部对账,不重跑
最后 30 运行 完成、恢复、重试、人工时长、成本 漂移与重复副作用
告警 阈值 自动安全动作
重复远端对象 第 1 次 冻结任务并禁用写入
未批准哈希值发布 第 1 次 回退候选并记录事件
未知操作超过 8 分钟 1 次 通知负责人并保持不重发
重试次数上升 达到 7 日基线的 2 倍 降速并打开熔断器
审批已过期 到期 禁止推进
任务已过 10:30 1 禁止自动发信

成功率不是唯一指标;“失败可见、状态不乱、外部副作用不重复、能在预算内恢复”更接近可靠性的工作定义。

指标分母必须明确。重试率按工具调用计算,还是按任务计算,会得出不同结论;本例同时记录 19/360 次来源调用和受影响的运行数。未知时长从“请求已发送”算到“已确认”或“已锁定”;审批等待从进入等待状态算到批准或过期;平均恢复时间从事件确认算到所有不变量恢复,不能把告警前的潜伏时间丢掉。

告警必须带着处置所需的信息。重复远端对象消息要包含任务、操作键、两个远端编号、哈希值、最近一次批准和冻结结果;只发一句“检测到异常”,会迫使周予在压力下重新调查。每月演练一次未知发布,每季度演练一次补偿;若运行手册离开作者的记忆就无法执行,应视为失败。

用 48 个故障场景验收状态转换和恢复,不只跑一次正常演示

系列 案例数 示例 必须结果
正常与状态守卫 8 合法主路径、非法跳转 仅合法事件生效
重试与错误 10 域名解析、429、结构错误、403 按类别处理
幂等与状态未知 10 内容管理系统或邮件响应丢失、延迟请求 0 个重复副作用
并发与租约 8 双触发、执行器崩溃、旧租约恢复 单一推进
审批版本 6 哈希值或受众变化、审批过期 旧批准失效
补偿 6 网站回退、重复对象、更正失败 可恢复或人工事件
合计 48 所有不变量保持
六类共 48 个故障场景同时断言任务状态、事件序列、操作记录和远端对象数;内容管理系统响应未知路径最终只允许一次创建调用
最终成功不足以证明恢复正确。上方是六类故障覆盖,中间四块是每例同时检查的证据,底部给出内容管理系统状态未知时的预期事件序列和创建次数。48 是虚构的非生产故障集,不证明风险已经穷尽;修复后仍要全量回归,并让两个执行器交错运行。

测试同时检查任务状态、事件、操作记录和模拟远端对象数。只断言最终成功,会漏掉“先发了两次再删一条”这种严重的过程错误。故障注入在非生产环境执行;涉及邮件时使用沙盒收件组,不能拿 63 名真实客户做演练。

48 个案例还要冻结预期事件序列。例如,内容管理系统状态未知的案例应依次出现“发布已开始 → 请求已发送 → 状态未知 → 开始对账 → 远端已确认 → 已发布”,且创建调用次数必须为 1。若实现直接从状态未知跳到已发布,缺少查询证据,即使远端对象数最终为 1 也算失败。并发案例让两个执行器交错执行,用来验证旧隔离令牌不能再写状态。

修复一项失败后,要重跑全部 48 项,并保留三类非故障基线:正常运行不应因可靠性控制而明显变慢;人工审批不能在超时后被自动视为批准;监控故障不应导致客户内容写入调试日志。恢复功能本身也可能制造新风险,不能只测试事故分支。

30 期运行账本:恢复成功不等于没有问题

30 期共计划 360 次来源读取。期间出现 19 次瞬态错误:6 次域名解析失败、8 次超时、5 次速率限制;18 次在预算内恢复,1 次关键来源持续缺失,任务阻塞且没有发布。6 次 AI 结构错误均在一次修正内通过;3 次事实证据失败全部转人工,没有盲目重新生成。

结果 运行/操作 结果
首路径完成 24 期 无重试或人工异常
恢复后完成 4 期 内容管理系统未知 3 次,另有其他瞬态组合
已阻塞 1 期 缺关键来源,0 次发布、0 封邮件
已补偿 1 运行 网站预览错误,发信前回上一正确版本
邮件状态未知 2 次操作 查询找到原活动,0 次重建
重复文章或活动 0 不变量保持
合计 30 期 28 期成功、1 期阻塞、1 期补偿
30 期分为 24 期首路径完成、4 期恢复后完成、1 期阻塞、1 期补偿;3 次内容管理系统和 2 次邮件状态未知均查询到原对象,重复创建为零
账本把正常、恢复、阻塞和补偿分开。先看上方 30 期构成,再看下方未知操作、来源读取和不能推出的结论;保护性阻塞没有被藏进模糊完成率。记录来自虚构小样本,只证明几类预期故障留下证据并收敛,不能证明未来无故障或直接宣称效率收益。

网站已补偿的那一期没有计入成功,也没有发送邮件;修正来源后创建新的任务版本,而不是改写历史状态。30 期只是小规模案例证据,不能证明未来没有故障;它能证明的是,设计中预期的几类故障可以留下证据并收敛。

28 期成功中,24 期没有异常,4 期至少经历一次可恢复事件。因此,“完成率 28/30”不能说明恢复设计是否有效;还要分别检查 5 个未知操作是否都在预算内完成对账、重复计数是否为 0、未批准发布是否为 0,以及人工接管时是否拿到了完整证据。已阻塞与已补偿说明保护机制生效,不能全都藏进一个模糊的失败数里。

运行账本还记录人工时间:内容审批中位数7分钟、发送审批3分钟、异常对账最长11分钟。自动化节省未在本文计算,因为没有重建人工基线;可靠性文章不把“30期跑完”顺便包装成效率收益。

把事故运行手册写成查询—冻结—判断—恢复—验证—沟通,而不是“重启试试”

步骤 操作 停止/证据
1 识别 任务、操作、远端编号和当前状态 找不到编号不执行写操作
2 冻结 停调度器、撰写人和自动重试 记录冻结时间
3 对账 查内容管理系统或邮件活动的实际对象与状态 保持状态未知直到有证据
4 决定 继续、回滚、补偿、转发修复 负责人批准高影响动作
5 执行 使用独立补偿操作键 保存每步回执
6 验证 不变量、公开页、受众、哈希值 未通过不宣布恢复
7 沟通 受影响人、事实、下一步 不推测未确认原因
8 学习 故障案例、工具或策略变更 全部 48 个案例回归
重复文章事故依次经过识别、冻结、对账、判断、恢复、验证、沟通和学习;人工选择文章 18427 为规范化对象并满足哈希值、链接、邮件和回执关闭条件
事故处置不是重启,而是一条可验证证据链。从上方八个动作读处置顺序,再用下方案例看两条同操作键文章如何冻结、选定 18427、补偿并满足关闭条件。16 分钟仅是虚构演练的技术处置时间;真实沟通、缓存传播与补偿失败可能更久。

演练场景是内容管理系统出现两条使用同一操作键的对象。系统自动冻结,不删除其中任何一条;人工比较哈希值和访问记录,选择 18427 作为规范版本,将另一条下架并记录重定向与缓存处理,同时确认邮件尚未发送。运行手册在 16 分钟内完成技术处置,客户沟通另由周予决定。若补偿步骤本身失败,状态继续保持“补偿中”并告警,不能手工改成正常。

演练结束后再做一次证据盘点:两个远端对象仍可由事件追溯;规范页面的哈希值与获批的 D-8f3c 一致;重复网页地址不再公开返回冲突内容;调度器和撰写程序只在负责人确认后恢复;下一期使用新的任务键。少一项都不能关闭事件。“用户现在看不见错误”不代表内部一致性已经恢复。

复制这份最小可靠运行包,从一个会写外部系统的步骤开始改

最小包包含任务结构、状态转换表、不变量、操作与幂等表、错误与重试策略、审批方式、超时表、补偿矩阵、事件日志、仪表盘和故障集。先选择现有自动化中最危险的一项外部写入,不必一次重建全部流程。

本案例参考了五类公开资料:RFC 9110 对 HTTP 幂等语义及非幂等请求自动重试边界的说明;AWS Builders' Library 对客户端请求标识、同一意图重试和延迟请求等接口设计问题的讨论;Microsoft 对有限重试、退避、抖动以及避免层叠和无限重试的建议;Azure 补偿事务模式对业务特定补偿、进度记录、幂等补偿和人工介入的说明;Stripe 幂等请求文档提供的一种具体接口实现。产品细节和键的保留期会变化,实际使用必须以目标服务的最新文档和测试结果为准。

今天先选一个会写入外部系统的动作,补齐五个答案:业务意图键是什么;请求超时后怎样查询实际结果;哪类错误可以重试、总预算是多少;审批绑定哪个具体哈希值;已经产生副作用后怎样补偿。任何一项只能回答“平台应该会处理”,就先停在草稿或人工批准,不让自动化直接写入生产系统。