AI 后端架构设计与大模型服务集成实践:先收紧输入、状态与退出边界
AI 后端架构设计与大模型服务集成实践:先收紧输入、状态与退出边界
第一版接入大语言模型时,先把它当作一个延迟和失败模式都不太稳定的外部依赖。同步调用可能占满 Web 线程;一开始引入复杂的 Agent 编排和分布式向量库,也未必能解决眼前问题。本文用演示压测参数说明 MVP 该先补哪些链路,以及哪些能力可以暂缓。
一、 业务背景与问题边界
1. 模拟压测场景与性能瓶颈
在模拟压测场景中,假设并发用户数达到 200 QPS,平均 Prompt 长度为 1.5k tokens,LLM 首包响应时间(TTFT, Time to First Token)在 800ms ~ 1.5s 之间,完整生成耗时为 5s ~ 15s。
如果采用传统的同步阻塞 HTTP 客户端调用大模型服务:
- 线程池耗尽:Servlet 容器(如 Tomcat)的默认并发线程池(通常 200 线程)在数秒内会被未完成的长连接全部占满,导致非 AI 的普通业务接口发生拒绝服务。
- 连接超时与网络抖动:长连接容易因中间 Gateway 超时断开,缺乏断线续传与状态恢复机制。
- 上游 upstream 雪崩:当外部 LLM 服务发生 Rate Limit(HTTP 429)或服务降级(HTTP 503)时,缺乏缓冲队列会导致上游错误直接级联透传至前端。
2. 第一版的边界界定
针对上述场景,第一版 AI 后端架构的核心目标应定位为链路可控与故障隔离,而非复杂的智能逻辑。其核心边界界定如下:
- 包含:流式 SSE(Server-Sent Events)响应透传、基于响应式的非阻塞 I/O、多模型提供方(Provider)的静默降级、请求 Token 熔断与基础审计日志。
- 排除:复杂的自动多步 Reasoning 链、自建向量数据库检索(先采用轻量内存/文件索引过渡)、复杂的分布式 Agent 状态机。
二、 分层架构与核心链路设计
在第一版设计中,AI 后端需要作为业务系统与外部大模型服务之间的缓冲层(AI Gateway / Adapter)。架构分为网关接入层、业务编排层、模型适配层与基础监控层。
flowchart TD Client[客户端 App/Web] -->|SSE 请求| API_Gateway[API 网关] API_Gateway -->|鉴权 & 基础限流| Async_Controller[响应式 Controller] subgraph AI Backend Core [AI 后端核心层] Async_Controller -->|任务提交| Stream_Engine[流式处理引擎] Stream_Engine -->|Token 检查| Token_Bucket[Token 桶限流器] Stream_Engine -->|获取 Prompt| Prompt_Template[Prompt 模板管理器] Stream_Engine -->|路由选择| Provider_Router[模型路由适配器] end subgraph LLM Providers [大模型服务商] Provider_Router -->|主链路 (Primary)| Primary_LLM[主模型 API (如 DeepSeek/OpenAI)] Provider_Router -->|降级链路 (Fallback)| Backup_LLM[备用模型 API (如 基础 LLM)] end Stream_Engine -->|异步记录| Audit_Log[(审计与 Cost 日志)]核心处理链路说明:
- 客户端连接:使用 HTTP SSE 建立长连接,前端实时接收 Token 碎片。
- 流量控制:根据用户级别与应用配额,通过 Token 桶算法控制每分钟的最大 Token 消耗总量,而非仅限制 QPS。
- 适配路由:主模型调用失败(如超时 3 秒未首包或返回 5xx)时,路由适配器自动切换至备用模型服务。
三、 关键代码实现与技术细节
在 Java 生态中,采用 Spring WebFlux + Project Reactor 可以较好地解决长连接阻塞问题。以下展示第一版核心的流式代理服务(LLM Stream Service)关键代码实现。
package com.example.ai.gateway.service; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; import java.time.Duration; import java.util.Map; /** * AI 大模型流式服务适配器 (MVP 第一版实现) * 职责:处理流式响应转发、提供首包超时降级与重试机制 */ @Service public class LlmStreamAdapterService { private final WebClient primaryWebClient; private final WebClient backupWebClient; public LlmStreamAdapterService(WebClient.Builder webClientBuilder) { this.primaryWebClient = webClientBuilder.baseUrl("https://api.primary-provider.com/v1").build(); this.backupWebClient = webClientBuilder.baseUrl("https://api.backup-provider.com/v1").build(); } /** * 执行流式请求并处理故障降级 * * @param prompt 用户输入的 Prompt * @param apiKey 应用授权 API Key * @return Flux<String> 增量生成的文本流 */ public Flux<String> streamChatCompletion(String prompt, String apiKey) { Map<String, Object> requestBody = Map.of( "model", "deepseek-chat", "messages", new Object[]{Map.of("role", "user", "content", prompt)}, "stream", true ); return fetchStreamFromProvider(primaryWebClient, requestBody, apiKey) // 设置首包超时限制:若 3 秒内未收到任何 chunk,引发 TimeoutException .timeout(Duration.ofSeconds(3)) // 遇网络异常或超时,进行指数退避重试(最多 2 次) .retryWhen(Retry.backoff(2, Duration.ofMillis(500)) .filter(throwable -> !(throwable instanceof IllegalArgumentException))) // 若主链路完全失败,降级至备用 Provider .onErrorResume(throwable -> { // 记录错误日志 (模拟日志打印) System.err.println("主模型服务不可用,触发降级逻辑。原因: " + throwable.getMessage()); return fetchStreamFromProvider(backupWebClient, requestBody, apiKey); }); } private Flux<String> fetchStreamFromProvider(WebClient client, Map<String, Object> body, String apiKey) { return client.post() .uri("/chat/completions") .header("Authorization", "Bearer " + apiKey) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.TEXT_EVENT_STREAM) .bodyValue(body) .retrieve() .bodyToFlux(String.class) .filter(data -> !"[DONE]".equals(data.trim())); } }代码实现要点说明:
timeout(Duration.ofSeconds(3)):示例中限制相邻信号的等待时间,因而既会影响首包也会影响后续分片。若只需约束 TTFT,应单独设计首包计时与取消逻辑。onErrorResume:这里用于在主链路失败时尝试备用提供方。是否允许切换应取决于业务语义;例如需要严格一致性的任务,应向调用方明确返回失败,而不是悄悄更换模型。- 非阻塞数据流:整体使用
Flux<String>贯穿 Controller 到 WebClient,避免产生任何.block()阻塞调用。
四、 架构权衡(Trade-offs)
在 MVP 阶段的实际落地中,架构师必须进行明确的取舍,避免技术方案脱离业务阶段:
| 设计维度 | 方案 A (第一版选择) | 方案 B (过度设计) | 权衡理由 |
|---|---|---|---|
| 通信协议 | 标准 HTTP/SSE 流式透传 | 复杂 WebSocket 双向通信 | SSE 属于单向长连接,兼容 HTTP/1.1 与 HTTP/2,网关层配置简单,运维成本低。 |
| 状态存储 | Redis 存储 Session 上下文 (按 TTL 自动失效) | 完整关系型数据库 + 全量历史快照 | MVP 阶段重点验证核心交互,短期上下文在 Redis 中保存 24 小时即可满足需求。 |
| 模型路由 | 基于策略模式的静态优先级 + 熔断降级 | 强化学习/动态延迟测速自动路由 | 静态规则透明可控,易于排查问题;动态路由在流量小时易产生震荡。 |
| 知识增强 | 内存向量检索 (如 Faiss/Local Index) | 密集型分布式 Vector DB 集群 | 在数据量未超过数万条时,轻量索引构建快、零运维成本。 |
五、 故障证据链与可观测性验证
在模拟压测和演练环境中,AI 后端架构必须具备完整的故障证明链,以快速区分是“模型本身生成慢”还是“后端代理层延迟高”。
1. 关键指标日志结构化
演示环境中可记录以下三个时间戳,用来区分模型端和代理端的耗时:
t_recv: 收到客户端请求时间戳。t_first_byte: 从 LLM Provider 收到第一个 SSE Chunk 时间戳(决定 TTFT)。t_complete: 最后一个 SSE Chunk 接收完成时间戳(决定 Total Latency)。
日志输出样例:
{ "trace_id": "a1b2c3d4e5f6", "user_id": "usr_8829", "provider": "PrimaryProvider", "status": "SUCCESS", "ttft_ms": 642, "total_latency_ms": 4820, "prompt_tokens": 1200, "completion_tokens": 350, "fallback_triggered": false }2. 模拟演练推导
- 场景假设:主模型 API 网关突发网络丢包率 15%。
- 推导结果:响应式适配器在 3 秒超时限制下触发
onErrorResume,请求在 3.2 秒内完成向备份 Provider 的切换,客户端仅感知到首字输出延迟增加约 3 秒,连接未中断,系统成功避开了主链路的持续阻塞。
六、 收尾
第一版先验证三件事:流式请求不会拖住普通接口,超时后有明确结果,关键耗时能被看见。演示中的阈值和备用模型策略要在目标环境压测后再定,不必预先堆满复杂能力。
