AI时代的系统架构演进:从传统Web到大模型驱动系统
一、传统 Web 系统结构
一个典型的 Web 架构链路如下:
Client → CDN → Load Balancer → API Gateway → Application Server → Cache → Database各层核心作用
| 层级 | 作用 | 典型技术 |
|---|---|---|
| CDN | 静态资源缓存,将内容分发到离用户最近的边缘节点,降低首屏延迟 | Cloudflare、AWS CloudFront |
| Load Balancer | 将流量分发到多个后端实例,避免单点故障,提升可用性 | Nginx、HAProxy、AWS ALB |
| API Gateway | 统一入口,负责鉴权、限流、路由转发、协议转换 | Kong、APISIX、Spring Gateway |
| Application | 承载核心业务逻辑,处理请求并返回响应 | Express、NestJS、Spring Boot |
| Cache | 热点数据缓存,拦截重复查询,减少数据库压力 | Redis、Memcached |
| Database | 数据持久化,保证 ACID 特性 | PostgreSQL、MySQL、MongoDB |
核心优化方向
- 延迟优化:CDN 缓存静态资源、Redis 缓存热点数据,让请求在到达数据库之前就被拦截返回
- 吞吐优化:通过 Load Balancer 实现横向扩展,增加实例数量即可线性提升处理能力
- 可用性:多实例部署 + 健康检查 + 自动摘除故障节点,任何单点宕机不影响整体服务
- 一致性:数据库事务保证数据正确性,缓存失效策略保证数据最终一致
传统 Web 的核心假设是:请求是短暂的。客户端发一个 HTTP 请求,服务端在毫秒级返回响应,连接随即释放。整个架构都围绕"如何在单位时间内处理更多短请求(QPS)"来设计。
二、AI 时代的大模型系统架构
AI 系统的本质变化:
从"短请求-响应" → “长连接 + 长任务 + 异步执行”
传统 Web 里一个请求 50ms 就结束了,但大模型的一次推理可能要 5 秒甚至 60 秒。如果还用同步模型,一个请求就会占住一个线程/连接几十秒,系统很快就会被拖垮。因此 AI 系统必须从根本上改变架构模型。
新一代架构模型
Client ↓ Connection Layer(WebSocket / SSE,负责"挂着"等结果) ↓ API / Orchestrator(接收请求,创建任务) ↓ Queue(任务排队,削峰填谷) ↓ Worker Pool(从队列取任务,控制并发) ↓ Model Layer(LLM / 图像 / 视频模型,真正干活的地方) ↓ Pub/Sub(结果产出后,广播通知) ↓ Connection Layer(找到对应用户的连接,推送结果) ↓ Client这个架构的精髓在于:请求的发起和结果的返回走的是两条独立的路径。客户端发请求后不会傻等,而是通过长连接"挂"在那里,等 Worker 处理完后通过 Pub/Sub 把结果"推"回来。
三、架构核心变化(本质)
| 维度 | 传统 Web | AI 系统 | 为什么变了 |
|---|---|---|---|
| 请求模型 | 同步(请求-响应) | 异步(请求-排队-回调) | 模型推理耗时太长,同步会阻塞所有资源 |
| 生命周期 | 毫秒级(50~200ms) | 秒/分钟级(1~60s+) | LLM 的 token 生成是逐个的,本质上就慢 |
| 通信方式 | 短连接(HTTP) | 长连接(WebSocket/SSE) | 需要持续接收流式输出,短连接做不到 |
| 核心瓶颈 | QPS(每秒请求数) | 连接数 + 任务调度效率 | 瓶颈从"处理速度"转移到"等待管理" |
一句话概括这个本质变化:传统 Web 优化的是"多快能处理完一个请求",AI 系统优化的是"同时能挂住多少个等待中的任务"。
四、关键瓶颈点分析
1. 长连接(WebSocket / SSE)
每个长连接都会占用服务端资源,不像短连接用完就释放:
- 文件描述符(FD)耗尽:Linux 默认每个进程只能打开 1024 个 FD,每个连接占一个 FD。如果不调整
ulimit,1024 个用户同时在线就会把服务打挂 - 内存占用:每个连接需要维护读写缓冲区、状态信息,大约占 30~80KB。1 万连接就是 300MB~800MB 的内存开销
- 单机连接上限:即使调大 FD 限制,受限于内存和 CPU 调度能力,单机通常在 1万~5万 连接就是极限。超过这个数就必须横向扩展
2. 模型调用瓶颈
模型调用是整个链路中最贵、最慢、最受限的环节:
- 高延迟:GPT-4 级别的模型,一次完整响应通常需要 3~30 秒,复杂推理甚至超过 60 秒。这不是网络延迟,而是模型本身的计算时间
- 强限流(Rate Limit):API 提供商会限制每分钟的请求数和 token 数。比如 OpenAI 的 GPT-4 可能限制 500 RPM(每分钟请求数),超了直接返回 429 错误
- 高成本:GPT-4 的输入 $30/M tokens,输出 $60/M tokens。一个带上下文的对话一次可能消耗数千 token,成本是传统 API 调用的成百上千倍
3. 任务堆积(Queue Backlog)
当用户请求的速度(生产速度)超过 Worker 处理的速度(消费速度)时,队列会不断膨胀。这会导致:
- 用户等待时间从秒级退化到分钟级(延迟爆炸)
- 队列占用的内存/磁盘持续增长
- 更早的任务排在前面,后来的用户体验急剧恶化
这就是经典的背压(Backpressure)问题:下游处理不过来,上游还在不停塞数据进来。
4. 流式输出压力
大模型的响应是逐 token 生成的,前端需要实时展示"打字机效果"。这带来独特的压力:
- 高频 flush:如果每生成一个 token 就 flush 一次网络缓冲区,假设每秒生成 30 个 token,就是每秒 30 次网络写入
- TCP 小包过多:每次 flush 发送的数据可能只有几个字节,产生大量 TCP 小包,触发 Nagle 算法延迟或产生网络开销
- CPU 序列化压力:每次 flush 都需要将数据序列化(JSON 编码)、写入缓冲区、触发系统调用,高并发下 CPU 占用显著
5. 上下文成本(隐性瓶颈)
这是最容易被忽视但影响最大的瓶颈:
- token 增长:多轮对话中,每轮都要把之前的对话历史作为上下文传给模型。10 轮对话后,上下文可能从 500 token 膨胀到 5000+ token
- 成本线性增长:上下文越长,每次调用的费用越高。而且输入 token 也计费,相当于"重复为历史对话买单"
- 延迟线性增长:模型处理更长的输入需要更多时间,用户会明显感觉到"对话越久,回复越慢"
应对策略包括:上下文摘要压缩、滑动窗口截断、关键信息提取后丢弃原文等。
五、瓶颈解决方案(核心)
1. 长连接(WS / SSE)解决方案
核心思想:连接层与执行层必须解耦。
传统架构中,处理请求的服务器同时负责维护连接。但在 AI 场景下,一个任务可能执行 30 秒,如果执行层还要负责维护连接,资源利用率极低。所以必须把"谁跟用户保持连接"和"谁去调模型干活"拆成两个独立的服务。
架构设计:
Connection Server(多节点,只负责维持连接) ↕ Pub/Sub(Redis / NATS,消息桥梁) Worker(任意节点,只负责执行任务)关键技术点:
多连接节点(横向扩展):单个 Connection Server 大约能维持 1 万个长连接。需要支持 10 万用户同时在线?部署 10 台 Connection Server,前面用 Load Balancer 分流即可。每台只做连接维护,不做业务逻辑,所以非常轻量。
Pub/Sub 消息分发:Worker 处理完任务后,不需要知道用户连接在哪台 Connection Server 上。它只需要把结果publish到以userId为 key 的频道。所有 Connection Server 都subscribe了这个频道,持有该用户连接的那台服务器收到消息后推送给客户端。这就是"找人"的过程。
无状态连接层:Connection Server 不存储任何业务状态,不绑定任务执行。它只做两件事:接收客户端连接、将 Pub/Sub 的消息推送给对应连接。这意味着任何一台 Connection Server 挂了,用户重连到另一台即可,不会丢失任务进度(因为任务状态在 Worker 和 Queue 那边)。
心跳机制:长连接如果长时间没有数据传输,中间的网络设备(NAT、防火墙、代理)可能会主动断开。所以需要定期发送ping帧保活。通常每 30 秒一次心跳就够了。
连接控制:限制单个用户的最大连接数(通常 1~3 个),防止恶意用户或 bug 导致的连接泄漏耗尽服务端资源。
2. 模型调用解决方案
核心:队列 + Worker Pool(限并发调用模型)
用户请求 → Queue(排队等候)→ Worker Pool(取出任务,控制最大并发数为 N)→ 模型 API并发控制:根据模型 API 的 Rate Limit 设定 Worker Pool 的最大并发数。比如 OpenAI 限制 500 RPM,你的 Worker Pool 就不应该超过 ~8 个并发(500/60≈8)。超出的请求在队列里等,而不是直接打到模型 API 被 429 拒绝。
Backpressure(背压):设定队列最大长度。当队列已满(比如超过 1000 个待处理任务),直接拒绝新请求并返回友好提示(“系统繁忙,请稍后重试”),而不是无限堆积导致所有人的延迟都变得不可接受。这是一个有损但可控的策略——宁可拒绝少数人,也不能让所有人都卡住。
连接复用:Worker 调用模型 API 时,使用 HTTP Keep-Alive 和连接池复用 TCP 连接。每次调用都新建 TCP 连接的话,TLS 握手就要消耗 50~100ms,高频调用下这是不可忽视的开销。
3. 流式输出优化
错误做法:每生成一个 token 就立刻 flush 到客户端。假设每秒 30 token,就是每秒 30 次网络写入,大量 TCP 小包,效率极低。
正确做法:批量 flush。设定一个时间窗口(比如 50ms)或 token 数量阈值(比如每 5 个 token),攒够了再一次性 flush。用户感知的延迟只增加了 50ms(人类感知不到),但网络效率提升了一个数量级。
// 伪代码示意 buffer = [] timer = setInterval(50ms, () => { if (buffer.length > 0) { flush(buffer) buffer = [] } }) onToken(token) { buffer.push(token) if (buffer.length >= 5) { flush(buffer) buffer = [] } }六、WebSocket vs SSE(重点对比)
核心区别
| 维度 | WebSocket | SSE |
|---|---|---|
| 通信方向 | 双向(客户端和服务端都能主动发消息) | 单向(只有服务端能推送给客户端) |
| 协议 | 独立的ws://协议,通过 HTTP Upgrade 握手建立 | 基于标准 HTTP,使用text/event-stream内容类型 |
| 复杂度 | 高(需要处理握手、帧解析、心跳、重连) | 低(就是一个不关闭的 HTTP 响应,浏览器原生支持) |
| 自动重连 | 无(需要自己实现重连逻辑) | 有(EventSourceAPI 自带断线重连,还能通过Last-Event-ID恢复) |
| 适用场景 | 需要双向通信的强交互场景 | 服务端单向推送的场景 |
AI 场景推荐
SSE(默认推荐)适用于大多数 AI 对话场景:ChatGPT 类对话、流式文本输出、单向数据流。原因很简单——AI 对话本质上就是"客户端发一个问题,服务端流式返回答案",这是一个单向推送模型,SSE 完全够用,而且实现成本远低于 WebSocket。
WebSocket适用于需要客户端频繁主动发消息的场景:实时打断生成(用户说"停,换个方向")、多 Agent 协作实时同步、高频双向交互(如协同编辑 + AI 辅助)。
关于"打断生成"
这是选择 WebSocket 还是 SSE 时最常被讨论的功能:
- WebSocket 方案:在同一条连接上直接发送
{ type: "cancel", taskId: "xxx" }消息,服务端立即收到并中止生成。延迟极低,体验流畅 - SSE 方案:SSE 连接是单向的,客户端无法在上面发消息。需要额外发一个
POST /api/cancelHTTP 请求来通知服务端。功能上完全可行,但多了一次 HTTP 往返,且实现上需要保证 cancel 请求能路由到正确的 Worker
结论:如果你的产品只需要基础的对话流式输出,用 SSE。如果需要打断、实时控制等复杂交互,用 WebSocket。
七、SSE 高并发瓶颈与优化
虽然 SSE 实现简单,但在高并发下同样面临挑战:
瓶颈分析
连接数限制:SSE 本质上还是一个不关闭的 TCP 连接,同样受 FD、内存的约束。浏览器对同一域名的 HTTP/1.1 连接数还有 6 个的限制(HTTP/2 下多路复用可以缓解)。
HTTP 长连接占用:SSE 连接会一直占住 Web 服务器的一个 worker/线程。如果用 Apache 这种每连接一线程的模型,几百个 SSE 连接就能把服务器打满。必须用 Nginx、Node.js 这种事件驱动模型。
代理缓冲问题:Nginx 等反向代理默认会缓冲响应体,等攒够一定量再转发给客户端。这会导致流式数据被"卡住",用户看不到实时输出。必须显式关闭缓冲。
flush 频率:和前面讨论的一样,过于频繁的 flush 产生大量小包。
重连风暴:网络抖动时,如果所有客户端同时尝试重连,会产生瞬时的连接洪峰,可能直接压垮服务端。
优化方案
关闭代理缓冲(Nginx 配置):
proxy_buffering off; X-Accel-Buffering: no;这两行配置确保 Nginx 不缓冲 SSE 响应,数据到了就立即转发。
批量 flush:前面已经讨论过,每 50ms 或每 N 个 token flush 一次,而不是逐 token flush。
心跳保活:SSE 协议支持注释行作为心跳,格式是: ping\n\n。定期发送(每 15~30 秒)防止中间设备因超时断开连接。
重连退避(Exponential Backoff):客户端重连时不要立即重试,而是按指数退避:1s → 2s → 4s → 8s,加上随机抖动(jitter)。这样即使大量客户端同时断开,重连请求也会被分散到一个时间窗口内,避免雪崩。
连接分层:将 Connection Layer 独立部署为专门的服务,和业务逻辑服务分开。这样 Connection 服务可以选用最适合长连接的技术栈(如 Node.js),而业务服务可以用最适合业务逻辑的技术栈(如 Java/Python)。
八、不同层的技术选型(语言 & 框架)
Connection Layer(连接层)
| 技术 | 适用场景 | 理由 |
|---|---|---|
| Node.js(推荐) | 大多数场景 | 事件驱动、单线程非阻塞 IO,天然适合高并发连接。单进程轻松维持上万连接,配合ws或socket.io库生态成熟 |
| Go | 需要更高性能 | goroutine 模型天然支持高并发,内存占用比 Node.js 更低,适合连接数极大(10万+)的场景 |
| Rust | 极限场景 | 零成本抽象 + 异步运行时(tokio),单机可以推到百万级连接,但开发成本高,通常只在基础设施层使用 |
连接层的核心特征是IO 密集而非计算密集——它不做复杂计算,只是维持大量连接并转发数据。所以事件驱动模型(Node.js)或协程模型(Go)是最佳选择。
Orchestrator(调度层)
| 技术 | 适用场景 | 理由 |
|---|---|---|
| Node.js(NestJS) | 快速迭代 | TypeScript 全栈统一,NestJS 提供了依赖注入、模块化等企业级能力 |
| Java(Spring) | 大型团队 | Spring 生态成熟,适合需要复杂事务管理、微服务治理的场景 |
调度层的职责是:接收请求 → 验证参数 → 创建任务 → 写入队列 → 返回 taskId。逻辑相对简单,选型主要看团队技术栈。
Worker Layer(执行层)
| 技术 | 适用场景 | 理由 |
|---|---|---|
| Python(首选) | AI 相关任务 | AI 生态的绝对主力:LangChain、LlamaIndex、HuggingFace Transformers、向量数据库客户端等几乎都是 Python 优先。RAG、Embedding、Agent 编排等任务用 Python 开发效率最高 |
| Node.js | 简单的 API 转发 | 如果 Worker 只是调用外部模型 API 然后转发结果,Node.js 也能胜任 |
Model Layer(模型层)
- 外部 API:OpenAI、Claude、Gemini 等。优势是零运维,按量计费;劣势是受 Rate Limit 约束,且数据离开了你的控制范围
- 本地部署:需要 GPU 服务器,使用 vLLM、TGI 等推理框架。优势是完全可控,无 Rate Limit;劣势是成本高,需要 GPU 运维能力
中间件选型
| 类型 | 推荐技术 | 说明 |
|---|---|---|
| Cache / PubSub | Redis | 兼具缓存和发布订阅能力,单机十万级 QPS,AI 系统的标配 |
| Queue | BullMQ(Node.js 生态)/Kafka(大规模) | BullMQ 基于 Redis,轻量好用;Kafka 适合日均千万级消息的大规模场景 |
| 网关 | Nginx | 反向代理、负载均衡、SSL 终止,配置灵活且性能优异 |
九、最终架构总结
连接层负责"挂着",Worker 负责"干活",队列负责"排队",Pub/Sub 负责"找人"
这四个角色的分工就是 AI 系统架构的精髓。每一层都可以独立扩展:连接多了加 Connection Server,任务多了加 Worker,流量波动大就加长队列缓冲。
本质变化
| 维度 | 传统系统 | AI 系统 |
|---|---|---|
| 驱动模型 | 请求驱动(来一个处理一个) | 任务驱动(来一个排一个,异步处理) |
| 执行模式 | 同步(请求线程等到处理完) | 异步(请求线程立即释放,结果异步推送) |
| 优化焦点 | QPS(单位时间处理更多请求) | 连接管理 + 任务调度(挂住更多人,调度更多任务) |
架构核心原则
解耦连接、任务、执行资源,让系统可以独立扩展
这是一切设计决策的根基。当你面对任何架构选择时,问自己:这个决策是在让系统更解耦,还是在引入耦合?解耦意味着每一层可以按自己的节奏扩展和演进,耦合意味着牵一发而动全身。
十、最终认知升级(重点)
当你评估一个 AI 系统的承载能力时,不要再问:
“QPS 能扛多少?”
要问:
- 能扛多少并发连接?→ 决定了同时在线用户数的上限
- 能处理多少并发任务?→ 决定了 Worker Pool 的吞吐能力
- 队列是否可控?→ 队列长度是否有上限?是否有背压机制?延迟是否在可接受范围内?
- 模型资源是否被合理利用?→ Rate Limit 是否被充分利用但不超限?是否有连接复用和缓存命中?