前面几篇把网络、运行环境和任务恢复串了起来。这一篇继续看系统上线之后:用户说“分析一直没有完成”,应该从哪里查?任务越来越多,增加 Worker 就够了吗?发布一个新版本,会不会让昨天等待人工确认的任务无法继续?
仍然以同一条流程为例:用户上传 Excel,API 创建任务,Worker 调用模型,沙箱执行分析代码,报告存入 OSS,前端接收执行事件。线上维护的目标,是让这条流程的状态、耗时、资源和结果都有依据可查,并且能够控制变更带来的影响。
本文由 AI 辅助整理,资料核对于 2026 年 11 月。容量数字是假设条件下的推导,检查项是工程建议;未对生产环境做压测或故障演练。
用户说“很慢”,先知道时间花在哪里
我会先把观测分成三类。它们记录的对象不同,组合起来才容易还原问题。
| 类型 | 观察什么 | Excel 分析场景 |
|---|---|---|
| 日志 Logs | 某个时刻发生的具体事件 | 文件解析失败,异常类型和步骤是什么 |
| 指标 Metrics | 一段时间内的数量和分布 | 任务完成率、排队时长、内存峰值 |
| 链路 Traces | 一次调用在多个组件间的经过 | API、Worker、模型和工具分别用了多久 |
OpenTelemetry 的 Signals 文档介绍了这些观测信号。它提供采集与关联的基础,哪些事件对自己的业务有意义,仍然需要应用定义。
例如用户觉得慢,可能是任务尚未被 Worker 领取,也可能是模型首个 Token 返回慢,或者 Excel 解析已经完成、上传报告时卡住了。只有一个“总耗时”很难区分这些情况。
可以分别记录排队、模型调用、工具执行、产物上传和端到端耗时,观察平均值以外的分位数。告警也应有明确对象,例如“已接收的任务长时间没有推进”“最旧待执行任务持续变老”,并附上查询路径和处理负责人。
让日志、任务和调用链对应起来
业务任务使用稳定的 run_id,步骤使用 step_id,每次尝试保留 attempt_id;Trace 使用 trace_id 串联调用。异步任务要在消息中传递必要的关联信息,Worker 不能每次都生成一个完全无法关联的上下文。
日志中可以保留任务 ID、步骤、错误分类和版本;文件正文、模型密钥和用户敏感信息则需要按用途脱敏、限制访问和保留期限。
这些 ID 适合用于定位具体执行,指标标签更适合使用模型、工具类型、结果类别等有限集合。如果把每个 run_id 都做成指标标签,组合数量会持续增长。OpenTelemetry 的基数说明解释了这类属性对内存和聚合的影响。
运行成功,还要检查结果是否有用
报告成功写入 OSS,只能说明流程完成了。报告是否漏了工作表、统计口径是否正确、引用的数据是否来自当前文件,需要另外评测。
我会同时记录执行完成率和结果质量。前者用于判断服务是否稳定,后者用于比较模型、Prompt、工具或检索方案的变化。发布时保留一组固定输入及评判规则,才能知道“运行更快了”是否伴随着质量下降。
扩容前,先算清楚正在等待什么
假设系统长期平均每秒接收 2 个任务,每个任务从进入到结束平均停留 30 秒,并且系统处于稳定状态,那么平均在途任务数约为:
2 个/秒 × 30 秒 = 60 个任务
这里统计的是系统内的平均任务数量,包括等待和执行。它提供容量分析的起点;实际需要多少 Pod,还取决于单实例并发、内存、CPU 和外部服务能力。流量突发或队列持续增长时,需要重新分析,不能把这个平均值当成容量上限。
Agent 等待模型时 CPU 可能很低,因此我会结合队列等待时间、在执行任务数和资源利用率来判断扩容。K8s HPA支持资源、自定义和外部指标,但对应的指标采集与适配需要实际接好。
还要一起检查下游预算。假设每个 Worker 实例的数据库连接池上限为 20,扩到 50 个实例后,潜在连接需求就是 1000,再加上 API、管理任务和发布时并存的新旧实例。数据库也许会先到达上限。
队列可以吸收短时峰值,同时应有等待期限、容量边界和租户配额。下游接近上限时,通过限制领取速度、暂停部分消费或明确拒绝新任务来形成背压。无边界地接收任务,只会把压力转换成更长的等待。
模型限流和成本需要独立控制
增加 Worker 会增加本地执行能力,上游模型的账号配额仍按供应商规则执行。一次 Agent 任务还可能包含多轮模型调用和并行工具,任务数与模型请求数也不能直接画等号。
以 Claude 的限流文档为例,请求速率、输入 Token、输出 Token 和消费额度属于不同限制;返回 429 时,也要继续检查错误信息和响应头。
对于暂时的速率限制,可以按 Retry-After 等信息等待,并设置退避、抖动和总重试预算。对于消费额度耗尽,持续重试通常无法解决。配置错误、权限失败、外部操作结果不确定,也应进入各自的处理分支。
重试预算最好覆盖整条调用链。SDK 重试、Worker 重试、Agent 自己再次调用工具,如果各自独立放大,实际请求数可能远高于界面看到的次数。
成本记录可以按 run_id 汇总模型用量、重试消耗、沙箱运行时间和文件存储。再为任务设置最大步骤、总时长、Token 预算和工具调用次数。到达边界后,保存已有结果并明确结束或转人工处理。
沙箱回收也要有可观察的状态。任务取消之后,应确认子进程和浏览器是否退出、实例是否释放、产物是否已经上传;回收失败需要后台清理与告警。等待人工时是否保留沙箱,则要结合恢复成本、等待时长和平台计费规则选择。
发布时,程序版本和任务版本一起考虑
一条可追溯的发布流程,可以包含这些步骤:
- 记录代码提交,执行必要测试与固定样本的 Agent 回归评测。
- 构建镜像并推送 ACR,记录确定的镜像标识。
- 配置运行环境、网络和受控凭据。
- 部署新版本,验证启动、工具访问和流式事件。
- 逐步接入任务,观察完成率、延迟、质量和成本变化。
- 保留可回退版本,并确认回退后的任务状态仍可读取。
每个任务最好能关联到代码、模型、Prompt、工具和状态格式的版本。这样才能回答:这份报告是在什么条件下生成的?同样的输入,为什么新旧版本行为不同?
回滚前,先检查数据是否兼容
假设新版本把检查点中的 file_path 改成了 artifact_id,发布后老任务怎样恢复?如果回退镜像,旧代码是否又读不懂新状态?
可以先增加兼容字段,让新旧版本都能读取;完成存量迁移后再删除旧字段。对于变化较大的执行逻辑,也可以让存量任务继续绑定旧版 Worker,明确设置退役期限。
这需要与数据库结构变更一起设计。K8s Deployment 的回滚恢复的是 Pod 模板部分,数据库内容、外部工具效果和业务状态需要自己的恢复方案。
权限跟着任务走,凭据跟着工作负载走
API、Worker 和沙箱承担不同职责,权限也应分别配置。例如 API 可以创建任务,Worker 可以读本次输入并写入结果,分析代码只获得当前任务需要的文件和工具能力。
阿里云函数角色和 IAM 最佳实践都可以作为理解工作负载身份的入口:使用角色和临时凭据,并将操作权限限定到必要资源。
云账号权限之外,工具服务还要检查业务授权。用户提交一个 run_id、文件 ID 或下载地址时,服务端应核验它属于哪个租户、当前用户有什么权限。数据库查询、缓存键、对象路径和恢复接口,都要保持同一套租户边界。
备份需要以恢复结果验收
副本可以帮助服务在实例故障后继续运行,备份则提供历史恢复点。错误删除可能被同步到副本,所以需要单独保留能够恢复的数据。
这里先认识两个目标:RPO 是允许丢失的数据时间窗口,RTO 是允许服务中断到恢复的时间。 它们决定备份频率、日志保留和恢复流程的要求。
PostgreSQL 的备份文档区分了 SQL 导出、文件系统备份和持续归档等方式。选择方案时,还要核对检查点引用的 OSS 文件能否同步恢复,避免数据库里保留了记录,文件却已过期删除。
恢复演练应使用隔离环境:恢复一批真实结构的任务记录与产物,验证权限、文件引用和状态读取,并记录耗时。恢复出的历史任务先保持受控状态,核对外部操作记录后再允许执行,避免旧队列或旧检查点再次触发已完成的动作。
第一版上线前,实际走一遍这些场景
| 演练 | 需要留下的证据 |
|---|---|
| 一次正常 Excel 分析 | 用 run_id 找到完整步骤、耗时、模型用量和产物 |
| 临时模型限流 | 任务有界等待,重试有上限,其他任务仍能推进 |
| 模型额度不足 | 能识别原因并停止无效重试,告警指向正确配置 |
| 发布时存在在途任务 | 新旧任务都能完成,检查点能够正确读取 |
| 用户取消正在分析的任务 | 后台停止推进,沙箱最终被回收,已产生效果有记录 |
| 使用另一租户的任务或文件 ID | 查询、恢复与下载入口都拒绝越权访问 |
| 从备份恢复任务及文件 | 引用完整,恢复时间可记录,外部动作不会直接重放 |