走过了AI Agent应用开发的核心路径——从概念到范式,从能力到项目。Agent已经能思考、能动手、能记忆、能按格式输出、能按需加载技能。

但有一个问题始终悬而未决:能扛得住生产环境的流量吗?

一个Agent请求可能持续数秒到数十秒,输出长度从50到3000+ token不等。当10个、50个、100个用户同时使用时,线程池会不会耗尽?连接池会不会被打满?外部模型会不会限流?

这就像一道“法餐厅”的考题——传统Web应用像快餐店,每单几分钟搞定,翻台快;AI Agent像法餐厅,一单可能“吃”几十秒甚至几分钟,桌子(线程)被长期占用,翻台率极低。

今天,我们正式进入第三阶段“生产级落地与进阶篇”的第一天——并发控制与性能优化。

📌 本文是第三阶段“生产级落地与进阶篇”的第一篇。前两个阶段我们解决了“能不能做”的问题,现在要解决“能不能上线”的问题。今天先从并发控制与性能优化入手——让Agent从“实验室Demo”走向“生产级系统”。

一、AI应用的负载特征:为什么不能照搬传统Web方法?

根本差异

在传统Web服务中,我们通常用QPS + CPU + RT估算容量,方法虽然粗糙,但多数场景够用。但对接大模型后,系统负载模式发生了根本变化:

| 对比维度

|

传统Web应用

|

AI Agent应用

请求时长

|

几十毫秒到几百毫秒

|

数秒到数十秒

| |

输出长度

|

固定、可预测

|

高度不确定(50~3000+ token)

| |

资源瓶颈

|

CPU / 内存

|

RPM、TPM、连接池、线程占用

| |

SLA关注点

|

全量响应时间

|

首字延迟(TTFT)而非全量响应

|

💡 传统应用像“快餐店”——每单几分钟搞定,翻台快。AI Agent像“法餐厅”——一单可能吃几十秒甚至几分钟,桌子(线程)被长期占用,翻台率极低。

一次AI请求的资源消耗链路

以ChatClient调用为例,一次看似简单的prompt→answer背后,会同时消耗多类资源:

| 阶段

|

主要资源

|

风险点

Prompt组装

|

内存

|

历史消息过长导致内存上升

| |

HTTP建连与等待

|

CPU / Direct Memory

|

流式chunk过多时解析成本变高

| |

Tool调用

|

线程 / 下游连接

|

模型调用和业务查询叠加放大RT

| |

重试与超时

|

大响应体落日志、落缓存

|

导致膨胀

|

💡 Output Token比Input Token更关键——一个3000 token的长回答,往往比一个300 token的短回答多占用数倍的时间、连接和资源。

生产环境的核心指标

建议统一采用下面这组指标,而不是只盯QPS:

| 指标

|

含义

|

价值

Requests Per Minute

|

请求速率上限

|

评估系统吞吐能力

| |

Time Per Output Token

|

模型输出效率

|

定位生成瓶颈

| |

Active Requests

|

活跃请求数

|

评估在途负载

| |

连接等待数

|

连接池排队情况

|

判断连接池瓶颈

| |

429 Rate

|

下游限流比率

|

判断模型配额是否撞线

| |

Fallback Rate

|

降级比例

|

判断系统是否长期过载

|

二、线程池配置:别让“等模型”变成“等线程”

RunnableConfig:Agent执行的“控制面板”

RunnableConfig是Spring AI Alibaba中Agent执行的核心配置类,负责管理线程、checkpoint、元数据、上下文等关键执行参数:

| 配置项

|

作用

threadId

对话状态持久化的标识,用于多轮对话

| | checkPointId |

断点续跑的标识

| |

并行执行器

|

为并行节点指定Executor和聚合策略

| |

流式模式

|

指定流式输出的StreamMode

|

⚠️ threadId仅用于关联同一对话的上下文,无需每次执行都生成新的threadId——否则无法保留多轮对话状态。

自定义线程池配置

在Graph工作流中,高并发场景建议自定义执行器,控制线程数量:

<span leaf=""><span class="code-snippet__meta">@Configuration</span></span>

在RunnableConfig中使用自定义执行器

<span leaf=""><span class="code-snippet__meta">@Service</span></span>

线程池参数选择指南

| 参数

|

推荐值

|

说明

corePoolSize

CPU核心数 × 2

|

AI任务多为IO密集型(等待模型响应)

| | maxPoolSize |

corePoolSize × 2

|

应对突发流量

| | queueCapacity |

100-500

|

排队等待的任务数

| | keepAliveTime |

60秒

|

空闲线程回收时间

|

💡 生产环境建议使用CallerRunsPolicy + 监控告警——让调用方感知压力,同时不丢失任务,配合告警触发扩容。

三、超时控制:别让一个慢请求拖垮整个系统

Spring AI Alibaba的超时控制需要在多个层面配置:

第一层:HTTP客户端超时(全局)

<span leaf=""><span class="code-snippet__comment"># application.yml</span></span>
<span leaf=""><span class="code-snippet__variable">@Configuration</span></span>

第二层:工具执行超时(单工具级别)

<span leaf=""><span class="code-snippet__meta">@Component</span></span>

第三层:整体请求超时(调用层)

<span leaf=""><span class="code-snippet__meta">@Service</span></span>

四、ModelRetryInterceptor:智能重试,而非盲目重试

ModelRetryInterceptor是Spring AI Alibaba内置的重试拦截器,用于处理模型调用失败时的自动重试。

基本用法

<span leaf=""><span class="code-snippet__keyword">import</span>&nbsp;com.alibaba.cloud.ai.graph.agent.interceptor.modelretry.ModelRetryInterceptor;</span>

自定义配置

<span leaf=""><span class="code-snippet__type">ModelRetryInterceptor</span>&nbsp;<span class="code-snippet__variable">retryInterceptor</span>&nbsp;<span class="code-snippet__operator">=</span>&nbsp;ModelRetryInterceptor.builder()</span>

指数退避策略

重试延迟采用指数退避策略:

| 重试次数

|

延迟时间(默认配置)

第1次重试

|

等待 1000 ms

| |

第2次重试

|

等待 2000 ms

| |

第3次重试

|

等待 4000 ms

| |

|

不超过 maxDelay(30000ms)

|

自定义重试条件

<span leaf=""><span class="code-snippet__type">ModelRetryInterceptor</span>&nbsp;<span class="code-snippet__variable">retryInterceptor</span>&nbsp;<span class="code-snippet__operator">=</span>&nbsp;ModelRetryInterceptor.builder()</span>

⚠️ 重试不是万能的——重试也是调用,也会消耗Token和配额。maxAttempts设置过大可能导致“重试风暴”,放大系统压力。

五、熔断与降级:别让一个故障拖垮全部

在高并发场景下,需要引入熔断机制防止雪崩:

<span leaf=""><span class="code-snippet__meta">@Service</span></span>

六、缓存策略:让相同请求“秒回”

AI Agent调用具有以下特点,使得缓存极具价值:

| 特点

|

缓存的价值

调用成本高

|

每次调用消耗Token和费用

| |

响应时间长

|

数秒到数十秒的等待

| |

重复请求多

|

相似问题反复出现(如FAQ)

|

两级缓存架构

<span leaf="">用户请求</span>

缓存实现代码

<span leaf=""><span class="code-snippet__meta">@Configuration</span></span>
<span leaf=""><span class="code-snippet__meta">@Service</span></span>

💡 缓存Key的设计需要考虑个性化因素——如果回复与用户画像相关,需要在Key中包含userId;如果回复是通用的,可以只使用message本身。

七、生产环境避坑指南

| 问题

|

原因

|

解决方案

服务重启后记忆丢失

|

使用了MemorySaver

|

切换到RedisSaver

| |

线程池耗尽

|

默认线程池太小

|

自定义线程池,增大核心线程数

| |

请求超时

|

readTimeout太小(默认10-30秒)

|

调整到180-300秒

| |

重试风暴

|

maxAttempts设置过大

|

限制maxAttempts≤5,配合熔断

| |

缓存击穿

|

热点Key同时失效

|

使用布隆过滤器 + 互斥锁

| |

连接池打满

|

长连接占用过多

|

配置连接池大小 + 合理超时

|

💡 Spring AI Alibaba的最佳使用方式不是“在Controller里直接调Agent”,而是把它当成AI运行时内核,再围绕业务域构建应用层架构。

八、容量规划三步骤

AI应用的容量规划,本质上不是“一个Pod能抗多少QPS”,而是“在目标SLA、模型配额、业务Token分布和集群资源约束下,一个Pod能稳定承载多少有效Token Throughput”。

三步法:

  1. 压测摸底:通过JMeter等工具进行压测,找出系统瓶颈

  2. 指标建模:建立“并发数 → 响应时间 → 错误率”的关系模型

  3. 弹性扩容:基于指标设置HPA阈值

容量规划核心公式:

一个Pod的承载能力 ≈ min(模型配额 / 每请求Token数, 线程池大小 / 平均响应时间, 连接池大小 / 并发连接数)

九、读后挑战

任务:对之前的旅行规划Agent进行压力测试与优化

要求:

  1. 为Agent配置自定义线程池(核心/最大/队列合理设置)

  2. 配置ModelRetryInterceptor重试拦截器(最多重试3次,指数退避)

  3. 实现基于Redis的请求结果缓存(相同问题快速响应)

  4. 使用JMeter进行压力测试(并发用户数从10逐步增加到100)

  5. 记录并分析压测结果,找出瓶颈并优化

验收标准:

  • □ 

    线程池配置合理,能通过RunnableConfig.executor()正确注入

  • □ 

    重试拦截器配置正确,模型调用失败时自动重试

  • □ 

    Redis缓存生效,相同请求第二次响应时间显著降低

  • □ 

    JMeter压测报告完整(包含响应时间分布、错误率)

  • □ 

    根据压测结果完成了至少一项优化

十、本日核心收获

  1. AI应用负载特征与传统Web完全不同——请求时长从毫秒级变为秒级,输出长度高度不确定,资源瓶颈从CPU变为RPM/TPM/连接池

  2. 线程池是第一个瓶颈——AI任务是IO密集型,需要合理配置corePoolSize(CPU×2)和队列容量

  3. 超时控制分三层——HTTP客户端超时、工具执行超时、整体请求超时,层层设防

  4. ModelRetryInterceptor提供智能重试——指数退避 + 可自定义重试条件,避免“重试风暴”

  5. 缓存是降本增效的利器——两级缓存(Caffeine本地 + Redis分布式)让相同请求“秒回”

  6. 容量规划的本质是Token Throughput——不是QPS,而是在目标SLA下的有效Token吞吐量