AI 应用中的高并发处理:从限流、异步到缓存与队列
做普通 Web 项目时,高并发主要解决的是数据库、接口和服务器的问题。
但 AI 应用有一个比较特殊的地方:一次请求背后往往不是一次简单的函数调用,而是一整条比较重的处理链路。
例如一个智能客服请求可能经历:
text
用户请求
↓
FastAPI
↓
身份认证
↓
查询会话历史
↓
查询长期记忆
↓
RAG 向量检索
↓
Rerank
↓
调用大模型
↓
工具调用
↓
再次调用大模型
↓
返回结果
如果同时来了 1000 个用户,真正需要解决的并不是"Python 能不能同时处理 1000 个请求",而是:
- 大模型 API 能不能承受?
- 数据库连接够不够?
- 向量数据库会不会成为瓶颈?
- 大量请求同时调用模型会不会触发限流?
- 一个 Agent 请求执行几十秒,会不会把 Web 服务线程全部占满?
- 相同的问题是不是被重复计算了很多次?
- 某一个下游服务挂掉以后,会不会把整个系统拖死?
所以 AI 应用的高并发,通常不是靠某一个技术解决,而是从请求入口到模型调用,再到数据存储,一层一层削减压力。
一、先搞清楚:AI 应用为什么比普通接口更容易遇到并发问题
传统接口可能是:
text
请求
↓
查询数据库
↓
返回
整个请求可能几十毫秒就结束。
而 AI 应用可能是:
text
请求
↓
RAG
↓
向量检索
↓
Rerank
↓
LLM
↓
Tool
↓
LLM
↓
返回
一次请求可能持续几秒甚至几十秒。
这意味着:
AI 应用真正需要控制的是"同时执行中的请求数量",而不只是 QPS。
例如:
text
普通接口:
100 QPS
每个请求 50ms
和:
text
AI 接口:
10 QPS
每个请求 20s
第二种情况反而可能更容易把系统拖垮。
因为假设每秒进入 10 个请求,每个请求平均执行 20 秒,那么系统中可能同时存在:
text
10 × 20 = 200
个正在执行的请求。
因此做 AI 应用时,经常需要同时关注:
text
QPS
并发请求数
平均响应时间
P95 / P99 延迟
Token 消耗
LLM 并发限制
数据库连接数
GPU 显存
二、第一步:先把同步调用改成异步
如果你的 AI 服务使用 FastAPI,第一步通常不是 Redis,也不是 Kafka,而是先检查代码是不是大量使用了同步阻塞调用。
例如:
python
@app.post("/chat")
def chat(request: ChatRequest):
result = llm.invoke(request.message)
return {"answer": result}
如果 llm.invoke() 本身是一个阻塞操作,那么在模型响应回来之前,这个请求就一直处于等待状态。
更合理的方式是使用异步接口:
python
@app.post("/chat")
async def chat(request: ChatRequest):
result = await llm.ainvoke(request.message)
return {"answer": result}
这样做的核心不是让"大模型变快"。
而是:
模型等待网络响应的时候,不要让 Web 服务白白占着执行资源。
三、异步并不等于高并发
这是一个非常容易误解的问题。
很多人看到:
python
async def
await
就认为系统已经可以处理高并发了。
实际上不是。
假设同时有 10,000 个用户请求:
text
FastAPI
↓
异步
↓
10,000 个请求
↓
全部调用 DeepSeek
一样会出问题。
因为瓶颈可能已经转移到了:
text
LLM API
数据库
Redis
向量数据库
网络
GPU
所以异步解决的是:
提高服务器等待 I/O 时的资源利用率。
而限流、队列、缓存等解决的是:
控制系统到底允许多少请求同时进入下游。
两者不是一回事。
四、第二步:限制大模型并发数
这是 AI 应用非常重要的一层。
假设你的模型服务允许:
text
最大并发:20
但是前端突然来了:
text
500 个请求
如果直接全部调用模型:
text
500 requests
↓
500 LLM calls
很容易出现:
text
429 Too Many Requests
Timeout
Connection Error
服务雪崩
更合理的方式是增加并发控制。
Python 中可以使用 asyncio.Semaphore:
python
import asyncio
llm_semaphore = asyncio.Semaphore(20)
async def call_llm(prompt):
async with llm_semaphore:
return await llm.ainvoke(prompt)
此时:
text
500 个请求
↓
并发控制
↓
最多 20 个同时调用 LLM
↓
其余请求等待
这样虽然不能让模型变快,但可以避免大量请求瞬间把模型服务打爆。
五、第三步:限流
如果系统允许任何用户无限请求,后面的所有优化都会失去意义。
最简单的情况:
text
一个用户
↓
疯狂请求
↓
1000 次 LLM 调用
↓
其他用户全部排队
所以 AI 应用一般需要做限流。
常见限流维度:
text
IP
用户
API Key
租户
接口
模型
Token
例如:
text
普通用户:
60 requests / minute
VIP 用户:
300 requests / minute
或者限制 Token:
text
用户每天最多消耗 1,000,000 tokens
六、常见限流算法
1. 固定窗口
例如:
text
1分钟最多100次
实现简单,但是存在临界问题。
例如:
text
12:00:59 → 100次
12:01:00 → 又100次
短时间内可能突然出现 200 次请求。
2. 滑动窗口
统计最近一段时间内的请求数量。
例如:
text
最近60秒
超过阈值就拒绝。
相比固定窗口更加平滑。
3. 令牌桶
实际系统中非常常见。
可以理解成:
text
桶
↓
不断产生令牌
↓
请求进来消耗一个令牌
↓
没有令牌就等待/拒绝
例如:
text
桶容量:100
生成速度:10 tokens/s
可以允许短时间的流量突发,同时限制长期平均请求速度。
Redis + Lua 经常用于实现分布式限流。
七、第四步:缓存是 AI 应用非常重要的一层
AI 应用中有大量重复请求。
例如:
text
用户A:什么是RAG?
用户B:RAG是什么?
用户C:RAG的作用是什么?
如果业务允许,可以通过缓存减少重复计算。
常见缓存内容:
text
LLM结果
Embedding结果
RAG检索结果
用户会话
Agent状态
热点知识
系统配置
八、LLM 结果缓存
例如:
python
cache_key = hash(prompt)
第一次:
text
用户问题
↓
Redis没有缓存
↓
调用LLM
↓
保存结果
↓
返回
第二次:
text
相同问题
↓
Redis命中
↓
直接返回
这样原本:
text
1000次LLM调用
可能变成:
text
100次LLM调用
900次缓存命中
对于成本比较高的模型,这个优化非常明显。
但 LLM 缓存不能简单地只使用用户问题作为 Key。
例如:
text
prompt
model
system_prompt
temperature
知识库版本
用户权限
发生变化,都可能导致结果不同。
因此实际项目中的 Cache Key 往往类似:
text
hash(
model +
system_prompt +
user_prompt +
temperature +
knowledge_version
)
九、Embedding 也可以缓存
RAG 系统中经常会出现:
text
用户问题
↓
Embedding
↓
向量检索
如果同一个问题重复出现,没有必要每次重新计算 Embedding。
可以:
text
query
↓
hash(query)
↓
Redis
↓
命中 → 直接拿 embedding
未命中 → 调用 embedding 模型
对于高频问答系统,这种缓存非常实用。
十、第五步:把耗时任务放到消息队列
并不是所有 AI 请求都适合让用户一直等待。
例如:
text
上传1000页PDF
↓
解析PDF
↓
OCR
↓
切分
↓
Embedding
↓
写入向量数据库
如果直接在 HTTP 请求里做:
text
POST /upload
↓
等待60秒
↓
返回
非常不合理。
应该改成:
text
用户上传
↓
API
↓
消息队列
↓
立即返回任务ID
↓
Worker后台执行
例如:
text
POST /document
↓
task_id=123
↓
Redis
↓
Worker
↓
PDF解析
↓
Embedding
↓
向量数据库
前端再通过:
text
GET /task/123
查询任务状态。
十一、常见消息队列
Python AI 项目常见选择:
text
Celery + Redis
Celery + RabbitMQ
RQ
Kafka
Redis Streams
不要看到 Kafka 就觉得一定要用。
如果只是:
text
PDF解析
文档Embedding
图片处理
批量生成题目
这种后台任务,很多时候:
text
Celery + Redis
就已经够用了。
Kafka 更适合:
text
大规模事件流
日志
数据管道
多个消费者
高吞吐场景
十二、为什么 Agent 特别适合使用任务队列
Agent 最大的问题之一就是:
一次请求可能执行很多步骤。
例如:
text
用户问题
↓
Agent
↓
搜索工具
↓
数据库
↓
代码执行
↓
再次调用LLM
↓
生成最终答案
如果这些操作全部占用一个 HTTP 请求:
text
请求持续30秒
并发量一大就很难受。
可以将一些不需要立即返回的任务异步化:
text
用户请求
↓
Agent
↓
创建任务
↓
Queue
↓
Worker
↓
执行工具
↓
保存状态
十三、第六步:数据库连接池
很多 AI 应用并不是模型先挂,而是数据库先挂。
例如:
text
1000个并发请求
↓
每个请求创建一个数据库连接
↓
1000个数据库连接
↓
数据库连接耗尽
所以不能每个请求都:
python
connect()
query()
close()
应该使用连接池。
例如:
text
Application
↓
Connection Pool
↓ ↓ ↓ ↓ ↓
DB Connections
↓
Database
数据库连接池的作用就是:
提前维护一定数量的连接,让请求复用,而不是每次重新创建。
常见配置:
text
pool_size
max_overflow
pool_timeout
pool_recycle
具体数值需要根据数据库和业务压测,而不是照抄一个固定值。
十四、第七步:RAG 的高并发优化
RAG 通常有:
text
Embedding
↓
Vector Search
↓
Rerank
↓
LLM
所以 RAG 本身就是一条多阶段链路。
可以针对每一层优化。
1. Embedding 缓存
text
query
↓
Redis
↓
命中 → 直接使用
2. 减少检索数量
不要:
text
top_k = 100
然后再让 Rerank 处理 100 个文档。
可以根据效果选择合理的:
text
Vector Search top_k = 20
↓
Rerank top_k = 5
↓
LLM
3. 缓存热点查询
例如:
text
"公司的报销制度是什么?"
每天可能有几百个人问。
可以缓存:
text
问题
↓
RAG结果
减少:
text
向量搜索
Rerank
LLM
的重复执行。
十五、第八步:不要让所有请求都直接打到一个模型
生产环境中可以做模型分层。
例如:
text
简单问题
↓
小模型
复杂问题
↓
大模型
需要推理
↓
Reasoning Model
需要工具
↓
Agent
例如:
text
┌→ 小模型
用户请求 → Router ┤
└→ 大模型
简单问题没必要全部调用最贵、最慢的模型。
这实际上也是一种成本和并发优化。
十六、第九步:模型服务本身需要并发优化
如果你不是调用第三方 API,而是自己部署模型,那么问题就完全不一样了。
例如:
text
FastAPI
↓
vLLM
↓
GPU
这时候真正的瓶颈可能是:
text
GPU显存
GPU计算能力
KV Cache
Batch Size
并发请求数
上下文长度
常见推理框架:
text
vLLM
SGLang
TensorRT-LLM
其中 vLLM / SGLang 在大模型推理服务中比较常见。
十七、Continuous Batching 是什么
普通推理可能是:
text
请求1
↓
模型
↓
完成
请求2
↓
模型
↓
完成
GPU利用率并不高。
Continuous Batching 可以动态把多个请求放到一起处理:
text
Request A ─┐
Request B ─┼→ GPU
Request C ─┘
当 A 完成之后:
text
Request D
可以继续进入批次,而不需要等整个 Batch 全部完成。
这对于高并发 LLM 推理非常重要。
十八、第十步:流式输出解决的是"用户等待",不是吞吐
AI 应用经常使用:
text
stream=True
然后:
text
用户提问
↓
模型开始生成
↓
第一个Token
↓
马上显示
↓
继续生成
用户感觉会明显更快。
但是需要注意:
流式输出主要降低的是首 Token 感知延迟,并不会直接提升模型的计算能力。
甚至流式连接会保持更长时间,所以并发特别高时,也需要考虑连接数量和资源占用。
因此:
text
流式输出 ≠ 高并发方案
它主要解决的是用户体验。
十九、第十一步:设置超时
高并发系统最怕一种情况:
text
下游服务卡住
↓
请求一直不结束
↓
连接一直占用
↓
越来越多请求堆积
↓
最终整个系统不可用
所以所有外部调用都应该设置 Timeout。
例如:
python
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(url, json=data)
模型调用、数据库、Redis、第三方 API 都应该考虑超时。
二十、第十二步:重试一定要有条件
例如:
text
LLM请求失败
↓
马上重试
↓
又失败
↓
马上重试
↓
所有请求一起重试
这可能造成更严重的问题。
这叫:
重试风暴。
正确做法通常是:
text
失败
↓
判断错误类型
↓
是否值得重试?
↓
指数退避
↓
再次请求
例如:
text
第一次:等待 1s
第二次:等待 2s
第三次:等待 4s
而像参数错误:
text
400 Bad Request
通常没必要一直重试。
二十一、第十三步:熔断和降级
假设:
text
DeepSeek API
突然不可用。
如果你的服务一直:
text
请求 → DeepSeek
请求 → DeepSeek
请求 → DeepSeek
最终整个系统都可能被拖垮。
可以做降级:
text
主模型不可用
↓
备用模型
↓
仍不可用
↓
返回缓存结果
↓
仍不可用
↓
友好提示
也可以根据请求类型降级:
text
复杂问题 → 大模型
大模型不可用
↓
小模型
小模型不可用
↓
固定知识库回答
二十二、第十四步:水平扩展
单台服务器最终一定存在上限。
例如:
text
Nginx
↓
┌──────┼──────┐
↓ ↓ ↓
API-1 API-2 API-3
↓ ↓ ↓
Redis
↓
Database
多个 API 实例共同处理请求。
这就是水平扩展。
常见部署方式:
text
Nginx
Docker
Kubernetes
云服务器负载均衡
但这里有一个重要问题:
API 服务最好设计成无状态。
不要把重要状态只保存在某一个 API 进程的内存里。
例如不要:
python
conversation_history = {}
然后把用户会话全部放进 Python 内存。
如果:
text
请求1 → API-1
请求2 → API-2
就可能出现状态不一致。
应该把状态放到:
text
Redis
MySQL
PostgreSQL
MongoDB
等共享存储中。
二十三、AI Agent 的并发架构可以怎么设计
如果是一个比较典型的 Agent 系统,可以考虑:
text
用户
↓
Nginx
↓
FastAPI API
↓
┌──────┴──────┐
↓ ↓
Redis DB
↓
Agent Service
↓
┌─────┴─────┐
↓ ↓
Cache Queue
↓
Worker
↓
┌──────┼──────┐
↓ ↓ ↓
RAG Tools LLM
↓ ↓ ↓
VectorDB API Model
各部分职责比较明确:
text
Nginx
→ 负载均衡、入口控制
FastAPI
→ 接收请求、鉴权、业务编排
Redis
→ 缓存、限流、短期状态
Queue
→ 削峰、异步任务
Worker
→ 执行耗时任务
VectorDB
→ RAG 检索
Database
→ 持久化业务数据
LLM
→ 推理
模型服务
→ 真正执行模型计算
二十四、真正生产环境不要一上来就上 Kafka + Kubernetes
这是做项目时非常容易犯的错误。
如果你的系统只有:
text
10~50并发
可能:
text
FastAPI
+ Redis
+ PostgreSQL
+ 向量数据库
+ LLM API
就已经够用了。
如果出现:
text
100~500并发
再考虑:
text
异步
+ 限流
+ 连接池
+ Redis缓存
+ Worker
+ 消息队列
+ 多实例部署
如果规模继续扩大:
text
1000+
再考虑:
text
负载均衡
Kubernetes
自动扩缩容
专业消息队列
独立模型服务
模型路由
监控告警
技术选型应该由实际瓶颈推动,而不是为了"看起来像生产环境"把所有组件都装一遍。
二十五、AI 应用高并发技术可以按问题来记
不要死记技术名称,直接按照"解决什么问题"来记。
| 问题 | 常用技术 |
|---|---|
| Web 请求阻塞 | asyncio、异步 API |
| 请求太多 | 限流、令牌桶、滑动窗口 |
| 相同请求重复计算 | Redis、LLM Cache |
| 大量耗时任务 | 消息队列、Worker |
| 数据库连接不足 | Connection Pool |
| 模型调用太多 | 并发控制、模型路由 |
| LLM 推理太慢 | vLLM、SGLang、Batching |
| 单机扛不住 | 多实例、负载均衡 |
| 下游服务挂掉 | 超时、重试、熔断、降级 |
| RAG 查询太慢 | Embedding Cache、检索优化 |
| Agent 执行时间太长 | 异步任务、任务队列 |
| 用户感觉等待太久 | Streaming |
| 服务故障无法发现 | 日志、指标、Tracing、告警 |
二十六、一个比较实用的 AI 应用高并发技术栈
如果现在让我从零搭一个中小型 AI 应用,我不会一开始就堆很多组件。
可以先从:
text
Nginx
↓
FastAPI 多实例
↓
┌──────────┼──────────┐
↓ ↓ ↓
Redis PostgreSQL VectorDB
↓
Worker
↓
LLM
开始。
其中:
text
FastAPI
→ API 服务
Redis
→ 缓存 + 限流 + Session + 队列辅助
PostgreSQL
→ 业务数据
VectorDB
→ RAG
Worker
→ PDF解析、Embedding、批量任务
LLM
→ 推理
然后通过压测观察:
text
CPU
内存
QPS
并发数
P50
P95
P99
LLM耗时
数据库耗时
Redis命中率
Token消耗
错误率
真正发现瓶颈之后,再针对瓶颈优化。
二十七、最后总结
AI 应用的高并发并没有一个所谓的"万能技术"。
真正的思路是:
text
先减少不必要的请求
↓
缓存
再限制请求进入速度
↓
限流
再控制下游并发
↓
Semaphore / Connection Pool
再把耗时任务异步化
↓
Queue + Worker
再提高单机处理能力
↓
Async + Batch
单机不够
↓
多实例 + Load Balancer
下游不稳定
↓
Timeout + Retry + Circuit Breaker + Fallback
模型成为瓶颈
↓
模型路由 + vLLM / SGLang + Batching
所以对于 AI 应用来说,高并发优化最重要的不是记住几十个组件,而是学会找到真正的瓶颈在哪里。
例如:
text
QPS高
→ 看限流
请求等待时间长
→ 看异步和下游耗时
LLM调用太多
→ 看缓存和模型路由
任务执行时间太长
→ 看消息队列
数据库报连接不足
→ 看连接池
GPU利用率低
→ 看Batching和推理框架
单机CPU/内存不足
→ 看水平扩展
第三方模型经常429
→ 看并发控制和限流
这才是 AI 应用高并发比较实际的处理思路。
不要先问"我要不要上 Kafka、Redis、Kubernetes",应该先问:现在系统的瓶颈到底在哪里。