☰
AI服务集体宕机后,Agent工作流的高可用逃生通道实战
2026/10/10 17:33:27 网站建设 项目流程

1. 事故复盘:我那天经历了什么

1.1 日常 AI 基建:Claude Code / Codex / Grok 各司其职

我属于那种把 AI 当成“数字同事”的人,工作流里同时挂着三套主流服务:Claude Code 跑在终端里负责主力编码,Codex CLI 处理跨仓库重构这类批量任务,Grok bot 则用来做文案润色和快速问答。这三个工具各司其职,正常情况下配合得相当顺滑,但有一天它们突然集体翻车,我精心搭建的 Agent 工作流差点就瘫痪了。

Claude Code 是我最依赖的一个。安装很简单,一行npm install -g @anthropic-ai/claude-code就搞定,配好 API key 后直接在项目目录里启动会话。它真正厉害的地方不只是代码生成,而是能把整个仓库的上下文拉起来理解,然后一步步改代码、跑测试、修 bug。我习惯每天早上开工先开一个 Claude Code 会话,把前一天没跑完的需求继续往前推。用了几个月下来,它几乎成了我肌肉记忆的一部分。

Codex CLI 是我的“批处理专员”。它擅长的是那种“给你一个任务,你自己去读文件、改代码、跑检查”的无人值守模式。我不会拿它做探索式开发,但跨模块重命名、统一日志格式、升级依赖这种事,交给它效率极高。安装用的是官方脚本,配置里要指定 API 端点和模型名。我通常会给它喂一个需求 markdown,让它拆成十几个子任务逐条执行。这种批量能力让你很难割舍。

Grok 则相对轻量,我主要用它写提交说明、PR 描述,或者让它在两个模型回答不一致时投个票。它最大的价值是“语气更接近人”,生成的东西不会太像说明书。比如有一次我让三个模型分别解释同一个报错,最后 Grok 的表述最直白,直接说“这个错误是类型不匹配导致的,建议去第 37 行看看”。三家服务有各自的账号和 API key,我原以为这种“分布式”布局已经足够稳,后来才知道这种想法有多天真。

1.2 故障爆发:429、连接重置、网页转圈一起袭来

那天下午两点左右,我在做一个“升级日志系统”的专项任务。Claude Code 刚跑完第一轮仓库扫描,正准备把结果传给 Codex CLI 时,终端忽然被一堆红色报错刷屏。最典型的是:

Error: claude-code: Request failed with status 429 Retrying in 15s...

刚开始我以为是订阅套餐的并发上限被撞了,心想等几秒就好。可过了十分钟,429 没消失,反而 Codex CLI 那边开始报更严重的错:codex endpoint /responses连接被重置,偶尔返回一个 200 状态码却带着空响应体。这种“假成功”最坑——我的编排脚本只认状态码,空响应也会被当成正常结果继续往下走,然后生成一堆基于幻觉的后续动作,等到人工发现时已经浪费了很多时间。

与此同时,Grok bot 在网页端也开始转圈,最后弹出一句“服务暂时不可用”。我关掉网页试 API,同样超时。那一刻我才意识到,不是我的账号出了问题,而是“AI 服务大宕机”:多家主流模型服务商在差不多同一时间段集体翻车。这种感觉就像你平时依赖的三条高速路同时封路,导航再怎么规划也都是死路。

1.3 连锁反应:我的 Agent 工作流差点整体瘫痪

我当时正在跑一条相当典型的 Agent 流水线,环节大概长这样:

  1. Claude Code 扫描所有仓库,找出所有旧日志 API 调用点;
  2. Codex CLI 根据扫描结果生成替换补丁并逐个应用;
  3. 每应用完一个补丁,Grok 负责润色提交说明;
  4. 自动化脚本执行测试并推送异常检测。

看起来很清晰,但故障一出现就全卡住了。Claude Code 在第一步就停了,后面环节全在等待,任务队列越积越长;两个依赖 Claude 输出的子 Agent 在等待超时后直接报错退出,连带着整条流水线标记失败。我想手动接管,却发现每个环节都是自动触发的,根本没有“暂停键”,只能看着任务一个个失败。

这次事故让我彻底想通一件事:表面上我有三家服务互为备份,但因为它们都是云端 API,底层基础设施、网络链路、甚至故障频段都可能重叠,真出大事的时候,所谓“多模型协作”和“单点依赖”没有本质区别。后来我不止一次拿这件事提醒团队,与其追求“用最多的 AI 工具”,不如先把“断了一个怎么办”想清楚。

2. 我的 Agent 工作流架构:表面上多元,骨子里单点

2.1 工作流的逻辑分层与依赖关系

我搭的这套 Agent 工作流,从逻辑上分成四层:

  • 入口层:一个常驻的 Python 编排脚本,监听 Git 仓库事件和需求文档变化,有新任务就自动拆解入队;
  • 规划层:用 LLM 把任务拆成子任务,并决定每个子任务的执行模型,这一层同样依赖云端 API;
  • 执行层:Claude Code 和 Codex CLI 在本地终端跑,通过 API 调用云端模型完成代码修改和命令执行;
  • 交付层:生成 commit、PR 描述和变更日志,用 Grok 做润色,或者直接用本地模板。

每一层都有明确的分工,看起来没有任何问题。但仔细看依赖关系就会发现,每一层都卡在“云端 API 必须可用”这个前提上。规划层要调 LLM 做任务拆分,执行层要调 LLM 做代码生成,交付层还要调 LLM 做文案润色。三层全走云端,任何一层断了,整个流水线都会停转。更无语的是,规划层和交付层其实都是很小的任务,完全可以用本地小模型完成,但我当时为了省事全走了云端,把风险全部放在了一起。

2.2 选择这三家的原因,以及忽略的隐患

选 Claude Code 是因为它长上下文理解和 agentic coding 的稳定度确实是我用过最好的;选 Codex CLI 是看中它在批量代码重构上的执行力,以及和现有工程流程的匹配度;选 Grok 则是纯粹喜欢它输出内容的语气,比自己写 PR 描述生动得多。工具本身都很好,问题出在我的架构设计。

但我忽略了三件非常实际的事。

  • 三家服务商虽然品牌不同,但它们的高可用机房可能部署在同一个区域,区域性的网络抖动完全可能把它们同时带走;
  • 三家的限流策略各不相同,但高峰期的“限流风暴”是同步出现的——当整体请求量暴涨,所有服务商都会在差不多同一时间开始拒绝新增请求;
  • 我的编排脚本没有任何降级逻辑,所有失败都只会固定等待后重试,重试失败就带着整条流水线一起死掉。

这几个隐患平时根本不显眼,直到“集体宕机”才一次性暴露。我后来反思,真正的“高可用”不是你有几个模型订阅,而是你的系统能不能在任意一个模型不可用时继续运转。你把三家都买了,不等于你就有了三个选择,你只是把同一场事故买了三份。

2.3 容易忽略的隐形依赖:DNS、网络链路与账户配额

除了模型服务商本身,我的工作流还依赖很多“看不见”的公共设施。比如本地到 API 的网络链路、系统 DNS 解析、API 网关的限流头信息,以及账号本身的配额状态。这些因素平时都不起眼,但一旦出问题,表现起来和“服务宕机”一模一样。

事故当天我第一件事是检查本地网络:用curl -I到一些常用站点,延迟和丢包都正常,说明问题不在我这端。接着我查了各家 API 控制台,发现自己的配额并没有耗尽,但请求依然被拒。这才锁定问题出在服务端侧。这段排查其实花了我不少时间,因为普通的网络检查根本查不出“云服务商内部故障”这类问题。

还要提一笔账户配额。很多时候“大宕机”并不是全线瘫痪,而是限流风暴。由于某个时段请求暴涨,服务商为了保障核心用户,开始对普通订阅做激进限流;你越重试,越触发限流,最终进入恶性循环。所以排查时,一定要把“配额不足”和“服务端故障”分开看,两者处理方式完全不同。前者是省钱和规划问题,后者是容灾和降级问题,混在一起处理只会越搞越乱。

3. 从报错到定位:我的排障实录

3.1 把报错信息当线索,而不是噪音

面对一堆报错时,最忌讳的是看到什么就重启什么。我会先把报错归类,按照下面的表快速判断问题层面:

错误类型典型症状可能原因
429Request failed with status 429配额不足、限流策略触发
5xx500 / 502 / 503服务端异常或网关故障
连接层Connection reset / timeout网络链路、CDN、基础设施
假成功空响应但状态码 200服务端返回异常,需要校验内容

这张表的价值在于,它把“用户可控”和“用户不可控”的报错分开来了。429 和 5xx 虽然是服务端返回的,但前者还掺杂着配额因素,后者基本就是服务端问题;连接层错误则可能来自本地的网络链路。先分类,再决定是重试还是停机,能省下大量无效操作。

那天我一开始看到 429 就以为是配额问题,结果 Codex CLI 那边跟着出现连接层错误,这基本就排除了“只限流”的可能。再去翻三个官方状态页,发现都在不同时段出现了异常,同时第三方监测平台也有大量用户反馈,基本可以断定是行业级事故。这时候再看自己终端的报错,每条都像诊断报告,不再是无意义的噪音。

3.2 排障顺序:先本地后云端,先验证再假设

我整理了一个固化下来的排查流程,现在每次出问题都照着走,效率提升不少:

  1. 先用curl -I直接打 API 端点,看 HTTP 状态码和响应时间,确认本地链路是否正常;
  2. 检查本地 CLI 工具的配置,比如环境变量、API key 是否过期,以及本地是否有非必要的中转规则;
  3. 打开各家官方状态页和官方账号,看有没有发布故障公告;
  4. 用第三方监测平台做交叉验证,判断影响范围;
  5. 确认是远端故障后,立即停掉自动重试,启动降级方案。

如果前两步都是正常的,基本可以断定是远端问题。这时候千万别继续重试,重试只会让限流更严重,正确做法是暂停相关任务,改走备用通道。我记得那天我还在犹豫要不要继续等,后来发现等下去毫无意义,果断把主流程切到了人工模式,至少保住了最核心的仓库扫描结果。

3.3 复盘时发现的定时炸弹:睡死重试与级联失败

排查过程中我发现了工作流里的两个致命设计缺陷。

第一个是“睡死重试”。我的旧编排脚本遇到 429 后固定等待 15 秒再重试,一旦遇到持续限流,几百个子任务就会同时卡在重试循环里。这不仅拖垮了我自己的任务队列,还给 API 侧增加了很多无效压力。后来我把重试改成了指数退避,并且加上最大重试次数,超过阈值就熔断。这个改动虽然不起眼,但效果立竿见影,至少不会再出现“任务把自己卡死”的傻事。

第二个是“级联失败”。旧工作流里,一个子任务失败后,它下游的所有任务都会跟着失败,整条主流程归零。这个设计在“所有服务都正常”时看不出问题,可一旦上游进入降级状态,下游全得陪葬。后来我给每个子任务加了执行状态机,支持跳过、重试、标记人工处理,才算堵住这个坑。其实这些概念做后端的人天天讲,但真正做到 Agent 工作流里的很少,大家都默认 API 永远可用。

4. 高可用改造:给 Agent 工作流加逃生通道

4.1 多供应商冗余:把写死的模型改成动态路由

我做的第一件事,是把执行层从“写死调用某一家”改成动态路由。核心思路是:为每个模型封装统一的接口,让路由层根据健康状态和优先级自动选择供应商。这样做的目的不是追求“最强模型”,而是追求“什么时候都有的用”。

MODEL_TIERS = [ {"name": "claude", "client": claude_client, "priority": 10}, {"name": "codex", "client": codex_client, "priority": 9}, {"name": "grok", "client": grok_client, "priority": 8}, {"name": "local", "client": ollama_client, "priority": 5}, ] def execute(task): for tier in MODEL_TIERS: if not is_healthy(tier["name"]): continue try: result = tier["client"].run(task) return validate_result(result) except ServiceUnavailable: mark_unhealthy(tier["name"], cooldown=120) continue raise AllServicesDown()

这里有一个细节:每个任务不会盲目选择优先级最高的模型,而是从任务的preferred_models列表里选。比如某个任务明确依赖 Claude 特有的 artifacts 能力,那就把 claude 设为唯一首选,其他模型作为兜底;如果某个任务只是普通的代码格式化,则让本地模型优先,节省 API 配额。我还给路由层加了一个健康状态缓存,每个服务连续失败两次就会被标记为不健康,冷却两分钟后再探测,避免每次都去试错。

4.2 本地模型兜底:云端全挂时的最后防线

当所有云端供应商都不可用时,本地模型是唯一的逃生通道。我在工作流里接入了 Ollama,跑一个 7B 级别的代码模型。它写复杂逻辑的能力确实不如云端大模型,但做基础补全、格式化、生成提交说明完全够用。安装和配置都不复杂,官方镜像拉下来就能在本地起服务,接口风格也和 OpenAI 兼容,改几行 base_url 就行。

具体做法是:路由层检测到所有云端服务都不健康时,自动把执行层切换到本地 Ollama,同时把任务标记成“降级模式”。降级模式下,我只允许它处理低风险任务——比如生成 commit message、修 lint 错误、跑静态检查;涉及核心业务逻辑改动的任务则进入待人工审批队列,绝不会让本地模型直接改代码。这道限制看着保守,但非常有必要,因为本地小模型的“幻觉率”相比云端大模型还是要高一些,让它碰核心逻辑只会帮倒忙。

这一步很重要,它的目的不是追求效果,而是保证工作流不断线,给云端恢复争取时间。实测下来,那天三家服务修好后,我的任务队列里只积压了少量降级任务,整体损失比之前预计的小得多。

4.3 超时、退避、熔断与降级:把“集体宕机”变成“局部抖动”

我封装了一个调用 API 的健壮性中间件,关键参数调整如下:

参数原值修改后作用
单次请求超时120s45s快速失败,避免线程阻塞
第一次重试延迟15s5s缩短等待,快速探测恢复
最大重试次数无限3次防止死循环
重试退避公式固定等待指数退避base * 2^attempt减轻服务端压力
熔断阈值无连续失败5次熔断2分钟保护配额和系统资源

熔断器的逻辑有点像家里停电后的总闸:一旦检测到连续失败,直接切断对该服务的调用,而不是硬着头皮反复试。2 分钟后放一个探测请求,成功才恢复服务。这个改造带来的最大变化是,即便某个模型还在故障,也不会继续消耗我的配额,整体的“故障半径”被控制住了。

另外,我把“超时”也分成了连接超时和读超时两层。连接超时设成 10 秒,读超时设成 45 秒,避免某些服务“已经建连但一直不返回响应”把线程拖死。这套参数在不同网络环境下可以调,但整体思路就是快速失败、快速切换,哪怕是误判,也比一直挂起强。

4.4 断点续跑:让任务队列不因一次故障清零

断点续跑也是这次改造的重头戏。我给每个子任务加了一个幂等状态,用 SQLite 记录执行状态,取值包括pending、running、completed、failed、manual。工作流中断后,编排脚本重启时会读取这个状态库,跳过已经completed的任务,只重跑pending和failed的任务。

举个例子:20 个仓库补丁任务跑到第 7 个时宕机了,以前只能清空队列从头跑,现在重启后脚本直接从第 8 个继续。这个能力在日常开发里价值巨大,因为断网、断电、系统重启都难免发生,只要任务能断点续跑,就不会白白浪费时间。

写状态库的时候要注意一个细节:必须在每个子任务真正完成后才更新状态,而不是在任务开始前就标记。否则任务执行一半崩了,状态却显示完成,重启后就会跳过它,造成数据丢失。我的做法是在任务函数末尾统一调用mark_completed(task_id),执行过程中不管怎么异常都不会误标。“先做事,后记账”这个原则,做任务系统的人应该都很熟悉,但真的很容易忽略。

5. 日常避坑与监控告警经验

5.1 为 AI 服务加监控:比官方状态页快一步

官方状态页往往有延迟,而且只反映“是否挂了”,不一定反映“是否可用”。我后来自己写了一个简单的健康检查脚本,每两分钟请求一次各家 API 的真实推理接口,记录响应码和时延,一旦连续三次非 200,就通过群机器人推告警到手机。这个脚本本身没什么技术含量,关键是它把“人工盯着状态页”变成了“系统主动通知你”。

这里有一个小技巧:不要只探测像/models这种轻量管理接口,最好真正发一个小成本的推理请求,比如max_tokens=1的最简 completion。因为某些服务可能“状态页正常但推理服务挂了”,只有真实请求才能暴露问题。虽然这种探测会带来一点点额外费用,但相比一次事故造成的损失,这点钱完全可以忽略。

另外,健康检查的数据也可以直接喂给路由层。每次探测成功就把结果写入一个本地 JSON 文件,路由层读取后就能知道哪些服务当前是健康的,而不是每次任务都要先试错一遍。这个联动改造之后,我的 Agent 工作流在故障期间的切换速度明显快了。

5.2 配额预警:别等到 429 才后悔

另一个经验是把配额管理前置。我每个月会预估各家服务的使用量,并在 API 控制台设好额度告警。比如某家服务当日消耗量达到 80%,就自动停掉非关键任务,只保留核心任务。这样既避免月底超支,也大幅降低触发限流的概率。很多人的 429 其实不是服务商挂了,就是自己把配额用光了,然后在最不该出问题的时候卡住。

同时,我在路由配置里把任务分成不同优先级,核心任务走付费和稳定性更高的服务,非核心任务走更便宜的备用模型。这不仅能省钱,还能避免“所有任务都挤在同一份配额里,大家一起撞线”的尴尬。比如批量文档润色我全丢给 Grok 或者本地模型,Claude 的配额都留给代码改造。实践证明,这种分流比单纯提高订阅等级划算得多。

5.3 常见问题速查表

我把这段时间遇到的高频问题整理成了一张速查表,也分享给了团队同学,大家反馈说比翻官方文档快得多。尤其是那种“看着像宕机,其实是自己配置问题”的情况,表格里基本都有对应解法。

症状最可能原因解决思路
Claude 突然 429账户额度或区域限流查用量控制台,切换备选模型
Codex CLI 报 Connection reset服务端网关故障停止自动重试,查看状态页
Grok 网页端转圈官方服务异常等待恢复,或改用 API 调用
空响应却显示成功服务端返回了畸形 body增加响应校验,空内容判为失败
编排脚本定时卡住超时过长或重试死循环缩短超时,启用熔断机制
本地模型参与后质量下降模型能力不足限定为低风险任务,核心逻辑人工处理

表格里最值得说的是“空响应却显示成功”这一行。很多 agent 框架只校验 HTTP 状态码,不校验内容,这导致服务端返回空 body 时也会被判为成功。我后来在所有解析响应的地方统一加了内容校验,只要content为空就抛异常,这才堵住这个坑。

5.4 把宕机当常态:Agent 工作流的设计原则

最后说点非技术的事。这次事故彻底改变了我设计系统的心态,从“默认 AI 服务永远可用”变成了“默认 AI 服务随时可能挂掉”。任何一个新的 Agent 工作流上线前,我都会问自己三个问题:

  • 如果所有云端模型同时挂掉,我还能手动完成任务吗?
  • 如果某个模型连续失败,工作流能自动降级到可用模型吗?
  • 任务中断后重启,能不能从断点继续,而不是从头再来?

这三个问题的答案,我会写进架构评审 checklist 里。把逃生通道当成第一需求,而不是事后补救,才是这次“AI 服务大宕机”给我上的最重要一课。技术的细节可以慢慢调,但如果整个系统压根没有“逃生”的概念,那平时跑得再顺,也可能在某一天突然归零。

这次“AI 服务大宕机”最后持续了几个小时,三家服务才陆续恢复。我虽然没有损失关键数据,但那种看着工作流瘫痪却无从下手的感觉,真的不想再体验第二遍。后来我在每一次架构评审里都把“逃生通道”列为必查项:本地模型兜底、多供应商路由、熔断降级、断点续跑,缺一样都不允许上线。如果你也在跑类似的 Agent 工作流,趁着服务都正常,赶紧把这些能力补上。等真出事了再想对策,就像着火之后才找灭火器,不是每次都这么幸运的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询