AI Agent 并发与可靠性:100 人同时用不崩的工程设计

HTMLPAGE 团队
27 分钟阅读

AI Agent 上线后最先崩的往往不是模型,而是限流、超时、重试、并发写入与上下文隔离。本文给出一套从单机 Demo 到可承载并发的工程化方案:队列与限流、幂等与重试、会话隔离、降级与熔断、可观测性与告警,让你的 Agent 在高峰期仍然可用、可控、可复盘。

#AI Agent #并发 #可靠性 #限流 #可观测性

当你把 Agent 从“自己用”变成“多人用”,故障通常会以一种非常不体面的方式出现:

  • 同一时间 50 个请求打到模型/工具,开始 429(rate limit)或超时。
  • 你加了重试,结果把对方 API 打挂,自己的队列也被挤爆。
  • 多个并发请求写同一个会话状态,记忆被覆盖,回答开始串台。

这篇文章不讲“多买服务器”,只讲能落地的工程解法:把 Agent 当成线上系统,围绕失败设计

延伸阅读(与本篇强相关):


零、先看一个最常见的线上事故:429 之后的重试风暴

并发系统最容易被低估的一类故障,不是“请求太多”本身,而是请求太多以后你做了错误恢复,结果把系统从“有点慢”推成“全面失控”。

0.1 事故现象

一个客服 Agent 在工作日上午 10 点迎来流量高峰:

  • 同时在线用户从 20 增长到 130
  • 每个请求平均会触发 1 次模型调用 + 2 次工具调用
  • 上游模型供应商开始返回 429

这时系统启动了“简单重试”:每次失败后 1 秒重试,最多 3 次。

结果 2 分钟后出现了三个连锁现象:

  1. 原始请求还没处理完,重试请求已经堆满队列
  2. 工具服务被短时间内重复打爆,开始出现 5xx
  3. 同一会话的后续问题被排在旧请求后面,用户感知成“卡死”

0.2 根因定位

这类事故的根因通常不止一个,而是多层叠加:

  • 入口层:没有按用户、会话、任务类型做分级限流
  • 重试层:固定间隔重试,没有指数退避和抖动
  • 队列层:轻任务和重任务混在一个队列里,无法隔离拥塞
  • 依赖层:模型与工具的失败没有触发熔断,系统持续向坏依赖施压
  • 状态层:同会话并发请求没有串行,等待时间拉长后更容易出现状态竞争

0.3 修复策略

真正有效的修复一般是组合拳,而不是只改一个参数:

  1. 会话级串行:同一 conversation_id 默认排队,先止住状态竞争
  2. 指数退避 + 抖动:把 1s/1s/1s 改成退避重试,避免同一时刻回潮
  3. 重任务拆队列:长任务与轻任务分层,保证基础可用性
  4. 熔断坏依赖:连续失败达到阈值后短时拒绝调用,只放探活请求
  5. 明确降级:上游不可用时返回“稍后继续”或“先给人工步骤”,而不是无穷等待

0.4 回归测试怎么做

要证明你真的修好,至少做这几类注入测试:

  1. 人工注入 429,验证重试不会在 1 秒内集体回潮
  2. 压测轻任务与重任务混跑,验证轻任务仍能稳定返回
  3. 模拟某个工具持续 5xx,验证熔断会在阈值后打开
  4. 同一会话并发提交 3 个请求,验证状态不串台

0.5 一组可靠性指标口径

这篇文章后面会反复用到这些指标,你应该尽早固定定义:

指标说明常见问题信号目标方向
p95_e2e_latency端到端 95 分位时延高峰时明显拉长稳定受控
tool_failure_rate工具调用失败比例某依赖抖动时快速升高可回落
queue_wait_time请求在队列中的等待时间高峰拥塞、任务混跑对轻任务可控
degrade_rate被降级处理的比例依赖异常时显著上升可观测、可解释

你不需要一开始就有完美基线,但必须能把“失败发生在哪里”说清楚。


一、先把“并发”说清楚:你在同时扛哪些东西?

在 Agent 系统里,并发不是一个维度,而是至少四个并发面:

  1. 用户并发:同时多少人发请求。
  2. 步骤并发:一个请求内部可能并发调用多个工具/检索。
  3. 外部依赖并发:模型供应商、向量库、业务 API、文件系统、数据库。
  4. 状态并发:同一会话多请求同时读写状态(最容易串台)。

你做可靠性设计的目标不是“永不失败”,而是:

  • 失败可控(不扩散)
  • 恢复可预期(可重试且幂等)
  • 体验可接受(降级而不是崩溃)
  • 过程可观测(能定位、能回放、能复盘)

二、第一层:入口限流与排队(别让系统一上来就炸)

2.1 不要把所有请求直接打到模型

模型/工具通常是最贵、最慢、最容易被限流的依赖。你需要“入口治理”:

  • 全局限流:保护供应商配额
  • 用户级限流:防止单个用户打爆系统
  • 会话级串行:同一会话默认串行处理,避免状态竞争

2.2 一个可落地的三段式队列

你可以按成本和优先级拆三层:

  1. 快速路径(Fast path):能在 1~2 秒内完成的(纯检索/缓存命中/无需 LLM)。
  2. 标准路径(Normal):一次 LLM + 少量工具调用。
  3. 重任务路径(Heavy):多步规划、长文生成、多工具并发。

核心思想:别让重任务挤占轻任务的可用性

2.3 一张文字版并发流程图:从入口到恢复的主链路

如果你要把可靠性架构讲清楚,下面这张文字版流程图足够当技术说明的主干:

REQUEST
  -> AUTH / RATE_LIMIT
  -> CLASSIFY_WORKLOAD
    -> FAST_QUEUE | NORMAL_QUEUE | HEAVY_QUEUE
  -> SESSION_SERIALIZE
  -> EXECUTE_AGENT
    -> MODEL_CALL
    -> TOOL_CALL
    -> STATE_WRITE
  -> VALIDATE
    -> IF FAIL: RETRY_OR_DEGRADE
  -> OBSERVE
    -> METRICS
    -> TRACE
    -> ALERT

这张图的重点不是“画得好看”,而是让你清楚每个故障应该归到哪一层处理。


三、第二层:超时、重试与幂等(重试不是“再来一次”)

3.1 超时要分层,不要只设一个大 timeout

  • 模型调用:例如 30s(带 streaming 可更长,但必须有心跳)
  • 检索/向量库:例如 2~5s
  • 业务 API:例如 3~10s(看 SLA)

超时的意义是:把“坏请求”尽快变成可控失败,释放资源。

3.2 重试的正确姿势:指数退避 + 抖动 + 预算

重试至少要满足:

  • 指数退避:$1s, 2s, 4s...$
  • 抖动(jitter):避免所有请求同一时刻重试
  • 重试预算:每个请求最多重试 N 次,或总重试时长不超过阈值

3.3 幂等是底线:否则重试会制造“重复副作用”

凡是可能产生副作用的工具调用(下单、发邮件、写数据库),必须有幂等键:

  • idempotency_key = user_id + conversation_id + step_id + request_id

当你在面试里讲可靠性,幂等通常是“工程味”的关键加分点。

这部分如果你想进一步细化到工具网关和写操作治理,可以直接接着看:


四、第三层:会话隔离与并发一致性(串台是致命体验)

4.1 同一会话并发的两种安全策略

策略 A:会话级排队(推荐起步)

  • 同一 conversation_id 的请求排队串行执行
  • 代价:同一会话的并发吞吐低
  • 收益:最简单、最稳定、不串台

策略 B:乐观锁 + 合并(高阶)

  • 状态带 version
  • 写入时检查版本;冲突则合并或回滚重放
  • 代价:复杂
  • 收益:可以并发,但要解决“状态合并语义”

如果你还没有成熟的状态机,不建议直接上 B。

4.2 把“状态”当成数据库记录,而不是 prompt 文本

你应该能回答:

  • 状态存在哪里(DB/Redis)
  • 状态 schema 是什么(字段级)
  • 每轮请求更新哪些字段
  • 如何回放一条会话的状态演进

配合结构化输出,你可以把“状态更新”做成可校验协议:


五、第四层:降级、熔断与体验兜底(让系统“难用但可用”)

当外部依赖不稳定时,你要有明确降级路径:

5.1 模型不可用的降级

  • 返回缓存的上一次成功答案(带“可能过期”提示)
  • 只给“下一步需要你补充的信息”(而不是硬编答案)
  • 切换到更便宜/更快的模型(质量下降但能用)

5.2 工具不可用的降级

  • 返回“我无法完成步骤 X,因为工具 Y 超时”,并给替代方案
  • 把任务切换为“生成操作清单/手工步骤”

5.3 熔断:防止坏依赖拖死全系统

当某个工具连续失败时:

  • 短时间内直接拒绝调用(熔断打开)
  • 只允许少量探活请求
  • 恢复后逐步放量

5.4 一张“失败类型 -> 处理动作”矩阵

很多团队说自己做了可靠性治理,但真正高峰出问题时,值班同学并不知道“这个错该重试、该降级,还是该直接拒绝”。

你可以先把最常见的失败做成矩阵:

失败类型常见原因默认处理动作是否允许自动重试备注
429 限流上游配额打满指数退避 + 排队 + 必要时降级是,但要有预算必须加抖动
5xx 服务错误依赖抖动或故障短重试,连续失败触发熔断是,次数有限熔断后放探活
超时网络慢、依赖卡死取消执行并记录时延视操作类型而定写操作更谨慎
空结果检索不到/工具返回空追问用户或切换备选路径不要盲重试
状态冲突同会话并发写入版本校验失败后重放或排队否,优先串行化容易串台
高风险写失败幂等/权限/业务边界不满足直接拒绝并给出明确原因不要自动补偿乱写

把这张表写进 runbook,比抽象说“系统具备高可用设计”更有实际价值。


六、可观测性:没有 tracing,你永远在猜

Agent 的观测不只要日志,还需要“可回放”。推荐最小采集:

  • trace_id:贯穿一次用户请求
  • conversation_id / user_id
  • step_id / tool_name / tool_latency_ms / tool_error
  • model / prompt_tokens / completion_tokens / cost
  • retry_count / timeout_count / circuit_breaker_state
  • citations_count(如果有 RAG 引用)

6.1 先固定指标口径,再谈优化

如果没有统一口径,所谓“系统变稳定了”就只是感受。建议你最少先做下面这组仪表盘:

指标如何采集建议样本口径目标方向
p95_e2e_latency在 API 入口和最终输出处打点全量线上请求高峰期可控
tool_failure_rate每次工具调用记录 success/failure按工具维度拆分快速识别坏依赖
queue_wait_time请求入队到出队时间差按 fast/normal/heavy 分层统计轻任务优先
retry_per_request每次请求累计重试次数按失败类型拆分避免重试风暴
degrade_rate降级返回占总请求比例高峰与异常窗口重点观察可解释而不是失控

如果你要构建一个最小评估集,建议先收 20 到 50 条高峰时段失败样本,逐个回放,确认每类错误都能归因到具体处理层。

然后把失败分成可行动的类别:

  • 入口限流导致的拒绝(容量问题)
  • 外部依赖 429/5xx(配额/供应商问题)
  • 工具超时(SLA 或网络)
  • 状态冲突(并发一致性)
  • 输出校验失败(协议/Prompt/Schema)

这也是“项目含金量”的核心来源:你能用数据讲清楚系统怎么变好。


七、一个最小可上线架构(你可以直接画在面试白板上)

你可以用这套骨架讲清楚“并发与可靠性”闭环:

  1. API Gateway:认证 + 限流 + 路由
  2. Job Queue:按会话/优先级排队
  3. Agent Worker:执行 planning + tool calling
  4. State Store:会话状态 + 摘要 + 版本
  5. Evidence Store:向量库/知识库(RAG)
  6. Observability:trace + metrics + replay

如果你在选题 5 做好了记忆与状态,这里就是自然延伸。

而当你的系统从单 Agent 进入协作式编排,并发治理会进一步升级到任务仲裁、预算分配和补偿回滚:


八、落地清单:从“能跑”到“能扛”

按这个顺序做,成本最低,收益最大:

  1. 会话级串行(排队)+ 用户级限流
  2. 分层超时 + 指数退避重试 + 幂等键
  3. 工具熔断 + 降级路径
  4. 状态 schema + 版本化写入
  5. tracing + 关键指标 + 失败分类

如果你想把这篇直接转成团队执行清单,可以再补一层发布前检查:

  • 是否已经区分 fast / normal / heavy 三类任务
  • 是否为同会话请求设置了串行策略或版本控制
  • 是否对 429 / 5xx / timeout 分别定义了处理动作
  • 是否对副作用操作启用了幂等键
  • 是否有熔断阈值与恢复条件
  • 是否能在一次失败请求里看到完整 trace、重试次数和降级结果

下一步如果你想继续把系统做“更复杂”,可以进入多 Agent 编排或安全权限:

  • 多 Agent 协作架构(选题 7)
  • Agent 安全与权限控制(选题 9)