1.2 核心组件全景与分层架构


文档摘要

1.2 一个 AI 对话引擎后端,到底由哪些组件构成 在 1.1 我们校准了"百万 QPS"的口径,并画出了流量画像。这一节我们把镜头拉远,看全局:支撑百万级对话的后端,在逻辑上由哪几层组成,每层负责什么,层与层之间靠什么契约连接。 读者读完这节,应该能画出一张属于自己系统的"分层架构图",并说清楚:当流量从用户端进来,它依次经过了哪些处理阶段,每个阶段可能在哪里"塌方"。 1.2.1 总览:五层逻辑架构 我把一个生产级的 AI 对话引擎后端,抽象成五层。注意这是逻辑分层,不是物理部署——同一层在真实部署时可能横跨多个微服务、多个集群。 逐层说明: 接入层(第 2 章重点):负责把海量用户的 TCP/WebSocket 长连接接住,做协议升级、身份鉴权、最前端的限流与连接数管控。

1.2 一个 AI 对话引擎后端,到底由哪些组件构成

在 1.1 我们校准了"百万 QPS"的口径,并画出了流量画像。这一节我们把镜头拉远,看全局:支撑百万级对话的后端,在逻辑上由哪几层组成,每层负责什么,层与层之间靠什么契约连接。

读者读完这节,应该能画出一张属于自己系统的"分层架构图",并说清楚:当流量从用户端进来,它依次经过了哪些处理阶段,每个阶段可能在哪里"塌方"。

1.2.1 总览:五层逻辑架构

我把一个生产级的 AI 对话引擎后端,抽象成五层。注意这是逻辑分层,不是物理部署——同一层在真实部署时可能横跨多个微服务、多个集群。

```mermaid flowchart TB L0[接入层
长连接 / 协议 / 鉴权 / 限流] L1[网关与编排层
会话管理 / 路由决策 / 流式聚合] L2[上下文与缓存层
KV Cache / 历史压缩 / 语义缓存] L3[模型服务层
推理引擎 / 多模型接入 / 量化] L4[支撑与观测层
配置 / 监控 / 计费 / 安全]
U[用户] --> L0 L0 --> L1 L1 --> L2 L2 --> L3 L3 --> L1 L1 --> L0 L0 --> U L4 -.横切.-> L0 L4 -.横切.-> L1 L4 -.横切.-> L2 L4 -.横切.-> L3
</div> 逐层说明: - **接入层(第 2 章重点)**:负责把海量用户的 TCP/WebSocket 长连接接住,做协议升级、身份鉴权、最前端的限流与连接数管控。它的核心矛盾是"海量长连接"与"单机能持有的连接数上限"之间的斗争。 - **网关与编排层(第 4 章重点)**:会话状态、模型路由决策(该用哪个模型、是否要检索、是否要调用工具)、把多路子调用聚合成一个流式响应返回给用户。它的核心矛盾是"灵活编排"与"低延迟开销"之间的平衡。 - **上下文与缓存层(第 3 章重点)**:管理对话历史、做上下文压缩、提供语义缓存(相似问题命中缓存直接返回)。这是整个架构的"降本杠杆",也是百万量级下最容易被低估的一层。 - **模型服务层**:真正跑模型推理的地方,包括推理引擎(如 vLLM、TensorRT-LLM 等)、多模型接入、量化与批处理。核心矛盾是"吞吐"与"首字延迟"的此消彼长。 - **支撑与观测层**:横切所有层,提供配置中心、全链路监控、成本计量、内容安全等能力。没有它,百万系统就是一座黑箱,出了事只能盲猜。 ## 1.2.2 为什么是"分层"而不是"一个大服务" 有人会问:能不能就把所有逻辑塞进一个服务,靠堆机器解决?在百万量级下,答案是坚决不可以,原因有三: **一、各层的扩展维度不同。** 接入层是"连接数"维度扩展,模型层是"GPU 算力"维度扩展,缓存层是"内存/带宽"维度扩展。绑在一起,你就只能用"最大公约数的资源"去扩,浪费严重。 **二、各层的故障域不同。** 模型推理偶发超时、OOM,是高频事件;接入层应保持"即使模型挂了也不掉线"。分层后可以做熔断、降级、隔离,避免一个模型的热更新把整个接入层拖死。 **三、各层的演进速度不同。** 模型层可能每月换模型、换引擎;接入层相对稳定。分层让"换模型"这种高频变更,不会动到"接用户"这种生命线代码。 <div align="center"> ```mermaid flowchart LR Q[请求] --> CHK{分层检查} CHK -->|连接爆炸| A[接入层独立扩] CHK -->|缓存未命中| B[缓存层独立扩] CHK -->|模型慢| C[模型层独立扩] CHK -->|可观测| D[支撑层横切] A --> R[响应] B --> R C --> R

1.2.3 层间契约:它们靠什么"对话"

分层不是目的,层与层之间如何协作才是关键。这里有几个核心契约,请记住它们的形态:

  1. 接入层 → 网关层:通常用内部 RPC(如 gRPC)传递"已鉴权的会话上下文 + 标准化请求体"。关键是把"用户身份"和"限流配额"提前算好,下游不再重复鉴权。
  2. 网关层 → 缓存层:一个"带语义指纹的查询"。不是简单的 key-value,而是带着"用户 ID + 对话场景 + 归一化问题"的复合键,用于判断"这个问题之前是否回答过"。
  3. 网关层 → 模型层:一个"推理请求",包含 prompt、采样参数、最大 token、是否流式。返回的是"token 流"而非完整文本。
  4. 全层 → 支撑层:统一的 traceID、结构化日志、指标上报。这是排查"为什么这条对话慢了 3 秒"的唯一线索。

我的主张:契约先于实现。在动手写任何一层之前,先把这几组接口的请求/响应结构定下来,用 mock 把各层打通,再做真实模型接入。这样当模型层还没 ready 时,你可以先用假模型把接入、缓存、编排全部跑通——这是缩短整体工期的秘诀。

1.2.4 在百万量级下,哪些"小事"会变成"大事"

分层架构在千级 QPS 时人人都画得出来。真正区分"能不能扛住百万"的,是下面这些在量级跃迁后质变的问题:

在千级时无所谓 在百万级变成致命问题 出事表现
每次请求查一次数据库拿用户配置 数据库被打成瓶颈 全站配置读取超时,雪崩
上下文全量塞进 prompt 显存和 KV Cache 爆掉 长会话请求大规模失败
限流只做全局 QPS 单个大客户/热点把资源吃光 其他用户集体无响应
缓存 key 用原始问题字符串 命中率极低,缓存形同虚设 推理集群始终满负荷
模型调用同步等待 线程池被长请求占满 新请求排队直至超时

这张表是后面每一章的"问题清单"。第 2 章解决接入与限流,第 3 章解决上下文与缓存,第 4 章解决模型路由与编排——你会发现,每一章都在回应这张表里的一行。

1.2.5 一个落地建议:先画"故障推演图"

我建议你在确定分层后,立刻做一件反直觉但极有价值的事:画一张故障推演图,不是画"正常流程怎么走",而是画"每一层挂了会怎样"。

```mermaid flowchart TD FAIL1[接入层挂] --> IMP1[用户全掉线] FAIL2[缓存层挂] --> IMP2[推理量翻倍 可能击穿] FAIL3[模型层挂] --> IMP3[降级小模型/返回兜底] FAIL4[编排层挂] --> IMP4[请求无响应 需超时熔断] IMP2 --> MIT[分级降级策略] IMP3 --> MIT IMP4 --> MIT ```

这张图会逼你想清楚三件事:哪些层必须有降级方案(模型层)、哪些层挂了等于全挂需要多活(接入层)、哪些层挂了能靠兜底扛住(缓存层)。能画清故障推演图的团队,和只画了"正常流程图"的团队,在第一次线上事故时的存活率天差地别。

1.2.7 数据如何在层间流动:一次完整对话的端到端追踪

前面画的都是静态结构。为了让分层真正"活"起来,我们追踪一次最典型的多轮对话请求,看数据如何穿过五层。这会帮你理解每一层的"职责边界"到底划在哪里。

端到端流程(以"用户追问一个带上下文的问题"为例):

```mermaid sequenceDiagram participant U as 用户 participant A as 接入层 participant G as 网关编排层 participant C as 上下文缓存层 participant M as 模型服务层 U->>A: 发送追问(长连接) A->>A: 鉴权 + 配额校验 + 限流 A->>G: 标准化请求(带会话ID) G->>C: 取历史 + 语义缓存查询 C-->>G: 历史上下文 + 未命中 G->>G: 拼 prompt + 路由决策 G->>M: 推理请求(流式) M-->>G: token 流 G-->>A: 聚合流式响应 A-->>U: SSE/WS 推流 ```

注意几个关键设计点:

  • 鉴权与限流只在接入层做一次,下游各层拿到的是"已授信的请求",不再重复校验——这是把接入层变成"安全边界"而非"性能瓶颈"的关键。
  • 上下文缓存层是"读多写少"的热点,绝大多数请求在这里命中历史,只有路由决策后才真正触发昂贵的模型调用。
  • 网关编排层是唯一的"状态协调者",它持有这次对话的编排状态(要不要检索、用哪个模型、要不要工具调用),模型层只管"给 prompt 出 token",保持无状态以便横向扩展。

这套追踪也解释了为什么 1.1 说"瓶颈常不在 GPU":如果接入层限流没做好,大量非法/超额请求会穿透到网关层,把编排状态拖垮,模型层反而可能很闲——但用户侧已经全部超时了。

1.2.8 一个常见反模式:把编排逻辑写进模型层

我必须单独点名一个高频反模式,因为它会直接葬送分层带来的所有好处:为了"省一次 RPC",把路由决策、上下文拼接、工具调用这些编排逻辑,直接塞进模型服务的封装里。

短期看似乎少了一跳调用,但代价巨大:

  1. 模型服务从此"有状态且重",无法独立扩缩容,一次编排逻辑变更就要重启推理集群——而推理集群重启成本最高、风险最大。
  2. 编排策略(比如"什么情况上升大模型")被锁死在模型代码里,产品想调个策略要发版整个推理栈。
  3. 缓存、限流、降级等横切能力无法在编排层统一施加,被迫分散到各处。

我的硬主张:模型服务层必须是"纯推理"的无状态组件,只接收 prompt、返回 token 流。 任何"业务逻辑"都不该出现在这一层。守住这条线,你的架构在模型快速迭代的年代里才能岿然不动。

1.2.9 容量规划清单:五层各自要备多少

分层架构定下来后,一个非常落地的动作是:为每一层单独列一份容量规划清单,因为它们扩展的"维度"和"单位"完全不同。下面是一份可直接照填的模板:

  • 接入层:容量单位 = 可维持长连接数 + 每秒新建连接数。关注单机 fd 上限、内存、内核参数(如 somaxconntcp_max_syn_backlog)。横向扩展靠加节点 + 负载均衡,几乎线性。
  • 网关编排层:容量单位 = 每秒可编排请求数 + 编排状态内存占用。关注单实例能持有多少活跃会话状态、编排逻辑的计算开销。扩展靠无状态化 + 水平扩。
  • 上下文缓存层:容量单位 = 缓存条目数 × 平均条目大小 + 命中率。关注内存总量、淘汰策略、跨机房一致性。扩展靠分片集群。
  • 模型服务层:容量单位 = GPU 卡数 × 单卡并发 × 利用率。关注显存、KV Cache、批处理效率。扩展靠加卡 + 连续批处理,但受模型本身约束,非线性。
  • 支撑观测层:容量单位 = 每秒日志/指标吞吐量。关注采集链路不成为瓶颈(它本身不能拖慢主链路)。

关键提醒:这五层的扩展曲线不一样,尤其是模型层是非线性的(受显存墙、批处理收益递减影响)。做容量规划时,千万别用"接入层能线性扩,所以模型层也能"的错误类比。每一层都要单独压测、单独留余量。

1.2.10 本节要点回顾

1.2 给你留下这些可复述的结论:

  1. 五层逻辑架构:接入层、网关编排层、上下文缓存层、模型服务层、支撑观测层。这是逻辑分层,不是物理部署。
  2. 必须分层的三个理由:各层扩展维度不同、故障域不同、演进速度不同。揉成一个大服务,等于用最大公约数去扩,浪费且脆弱。
  3. 层间契约先于实现:先定接口形态,用 mock 打通各层,再接真实模型——能大幅缩短整体工期。
  4. 量级跃迁问题清单:数据库每请求查询、上下文全量塞 prompt、只做全局 QPS 限流、缓存 key 用原始字符串、模型调用同步等待——这五件事在千级时无所谓,百万级会致命。
  5. 守两条硬线:模型层必须纯推理无状态;五层扩展曲线不同,必须分层独立压测。

带着这份要点,下一节我们下到最前线,直面第一道关口:接入层与百万长连接。

1.2.11 快速自测:你的架构到第几层

读完本节,你可以用下面几个问题快速自检当前系统的成熟度。答不出或答"没有"的,就是下一阶段要补的:

  • 你的接入层,是否和推理逻辑彻底解耦、可以独立重启而不掉用户连接?(测接入层)
  • 你的对话历史与缓存,是否有独立的一层统一管理,还是散落在各个服务的内存里?(测缓存层)
  • 你的模型调用,是否是无状态纯推理,还是混入了路由、拼接、工具调用等业务逻辑?(测模型层纯净度)
  • 你的限流与降级,是否能在不发布模型层的情况下独立调整?(测解耦度)
  • 你的监控,是否能从一条慢对话反查到具体卡在哪一层的哪一口?(测观测层)

这五个问题对应五层。能全部答"是",说明你的分层是健康的;任一"否",都意味着某一层是"隐性单点",会在百万量级下成为第一个崩的点。

小结:逻辑上,AI 对话后端 = 接入层 + 网关编排层 + 上下文缓存层 + 模型服务层 + 支撑观测层。分层的核心收益是独立扩展、隔离故障、解耦演进。务必守住"模型层保持纯推理无状态"这条线,否则分层形同虚设;且五层扩展曲线不同,必须分层独立压测。下一节进入最前线的实战难题:如何接住百万长连接并限流。


发布者: 作者: 转发
评论区 (0)
U