概述
系列定位:本文是“AI 应用核心框架与协议”系列的收官之作。在前十一篇中,我们依次建立了 LLM 通信协议、MCP 工具编排、语义缓存、可观测性等核心能力。本篇将所有这些底层能力封装在一道统一的“智能关卡”之后——AI 网关。它是整个 AI 应用架构的流量控制阀门,是所有 AI 能力对外暴露的唯一入口,是将技术能力转化为可控、可审计、可计费产品的最后一块拼图。
总结性引言:当你为团队构建的 AI Agent 开始从内部演示走向对外开放,或者当你的 AI 应用后端接入了多个不同的模型供应商(OpenAI、Claude、本地的 DeepSeek),你很快就会面临一系列“幸福的烦恼”:如何防止开发者的 Key 被盗用导致天价账单?如何在 OpenAI 宕机时自动把流量切到备用的 Claude?如何限制某个业务部门的 Token 消耗不超过月度预算?如何拦截用户的 Prompt 注入攻击?你不可能在每个微服务里都写一遍认证、限流、路由、安全、审计的代码。你需要一个网关——但不仅仅是一个转发 HTTP 请求的 Nginx 或 Spring Cloud Gateway,而是一个懂 AI 的网关。它理解每次请求背后的 Token 成本,它知道哪些问题是简单的(应该路由到便宜的模型),哪些是复杂的(需要最强的模型),它能嗅探到 Prompt 中的恶意指令并将其拦截在门外。本文正是要手把手带你用 Spring Cloud Gateway 构建这样一个企业级 AI 网关。我们将把你在微服务时代积累的网关经验,升级为 AI 时代的 Token 感知、模型感知和内容感知能力,让你的 AI 服务交付得既高效又安全。
核心要点:
- 从 QPS 到 Token 的思维升级:AI 网关的核心不再只是“每秒能处理多少请求”,而是“每次请求消耗了多少 Token”。
- 四大核心职能:模型路由(智能分流与降级)、Token 配额管理(计价与限流)、Prompt 安全(注入防御与内容审核)、全链路审计(成本归因与合规)。
- 响应式流式处理:攻克 WebFlux 网关在处理 LLM 流式响应时,实时累积 Token 和写入审计日志的难题。
- 统一管控架构:所有 AI 能力通过网关统一暴露,屏蔽底层多模型、多供应商的复杂性。
文章组织架构图:
flowchart TD
n1["1.从API网关到AI网关:架构升级的必要性"]
n2["2.核心一:模型路由、负载均衡与故障转移"]
n3["3.核心二:Token配额管理与精细化限流"]
n4["4.核心三:Prompt注入防御与内容安全"]
n5["5.核心四:全链路审计与不可变日志"]
n6["6.响应式编程与Spring Cloud Gateway集成细节"]
n7["7.贯穿案例:构建企业级AI统一接入平台"]
n8["8.与前后系列的衔接"]
n9["9.面试高频专题"]
n1 --> n2 --> n3 --> n4 --> n5 --> n6 --> n7 --> n8 --> n9
classDef nodeStyle fill:#f1f5f9,stroke:#334155,stroke-width:1.5px,color:#1e293b
class n1,n2,n3,n4,n5,n6,n7,n8,n9 nodeStyle
架构图说明:
- 图表主旨概括:本流程图展示了本文的逻辑递进结构,从概念差异到核心功能实现,再到集成细节与案例验证,最后以面试题巩固。
- 逐层/逐元素分解:1 确立 AI 网关的必要性与架构定位;2-5 是 AI 网关的四大核心功能,顺序由外向内(路由→配额→安全→审计);6 解决实现层面的技术难题;7 通过完整案例串联所有功能;8 衔接系列其他篇章;9 提炼高频考点。
- 设计原理映射:这反映了构建 AI 网关的工程顺序:先理解差异,再逐个攻克功能模块,最后整合与优化。
- 工程联系与关键结论:本文的叙述结构从理论到实践,再回归理论,确保读者既能深入代码细节,又能建立系统性架构思维。
1. 从 API 网关到 AI 网关:架构升级的必要性
传统 API 网关(如 Spring Cloud Gateway、Kong、APISIX)的核心职责是:基于 Path/Host 路由、基于 QPS 的限流、统一的认证授权、请求/响应日志。它们在微服务时代表现卓越,但在 AI 应用场景下暴露出三个根本性局限:
- 无法感知 Token 成本:LLM 调用成本与请求次数无关,而与 Token 消耗线性相关。一个生成 2000 Tokens 的请求,成本可能是一个 50 Tokens 请求的 40 倍,但传统网关只能看到两个 HTTP 请求。若仅按 QPS 限流,免费用户一次生成长篇小说就可能耗尽当日预算,而按 Token 限流则能精确控制成本。
- 无法按模型语义路由:传统路由依据 URL 路径或 Header,例如
/api/openai转到 OpenAI,/api/claude转到 Claude。但理想方式应是“简单问题路由到 GPT-3.5,复杂问题路由到 GPT-4o”。这需要网关能够理解请求的复杂度或携带的意图标签,并根据后端模型的能力和成本动态决策。 - 缺乏内容感知能力:传统网关不关心 HTTP Body 里的内容是否包含恶意指令或敏感信息。AI 网关则必须对 Prompt 进行注入检测,对 LLM 生成的回复进行合规审查,否则可能引发安全与合规风险。
因此,AI 网关在传统网关的基础上增加了三个新维度:
- Token 感知:能够解析 LLM 请求和流式响应中的 Token 用量,并据此进行计费、限流和配额管理。
- 模型感知:能够根据用户身份、请求意图、模型健康状态等,动态选择最合适的模型(或模型实例)。
- 内容感知:能够审查进出内容的合规性,防御 Prompt 注入攻击,并对敏感信息进行脱敏。
AI 网关的架构定位如下:
flowchart TD
classDef clientStyle fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#1e3a8a
classDef gatewaySub fill:#f0f4ff,stroke:#93a3d3,stroke-width:1.5px
classDef gatewayNode fill:#e0e8f0,stroke:#7f8fa4,stroke-width:1.5px,color:#1e293b
classDef llmNode fill:#ede9fe,stroke:#8b5cf6,stroke-width:1.5px,color:#4c1d95
classDef dataNode fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#92400e
Client["客户端/第三方系统"] --> GW
subgraph GW["AI 网关层"]
direction TB
AuthN["认证授权"] --> RateLimiter["Token配额限流"]
RateLimiter --> Router["模型智能路由"]
Router --> SecurityIn["输入安全审查"]
SecurityIn --> Cache["语义缓存"]
Cache --> BackendRouter["后端调用"]
BackendRouter --> SecurityOut["输出审核脱敏"]
SecurityOut --> Audit["全链路审计日志"]
end
BackendRouter -.-> LLM1["OpenAI GPT-4o"]
BackendRouter -.-> LLM2["Azure OpenAI"]
BackendRouter -.-> LLM3["本地vLLM/DeepSeek"]
Audit --> Kafka["Kafka"]
Kafka --> ClickHouse[("ClickHouse/ES")]
Cache --> Redis[("Redis Stack 语义缓存")]
RateLimiter --> Redis
class Client clientStyle
class GW gatewaySub
class AuthN,RateLimiter,Router,SecurityIn,Cache,BackendRouter,SecurityOut,Audit gatewayNode
class LLM1,LLM2,LLM3 llmNode
class Kafka,ClickHouse,Redis dataNode
AI 网关整体架构图说明:
- 图表主旨概括:该图展示了 AI 网关的完整过滤器链结构及其与后端多模型、基础设施(Redis、Kafka)的关系。
- 逐层/逐元素分解:客户端请求依次经过认证、Token 配额限流、模型路由、输入安全检查、语义缓存(可选)后,到达后端调用路由;LLM 的响应再经过输出审核脱敏,最终记录审计日志后返回客户端。Redis 同时服务于令牌桶限流与语义缓存,Kafka 和 ClickHouse 承载审计日志。
- 设计原理映射:这是一种典型的责任链(Filter Chain)模式,每个环节解决一类关注点。将缓存置于安全审查之后是为了避免缓存恶意请求的结果;将输出审核放在响应返回客户端之前,确保所有内容经过合规过滤。
- 工程联系与关键结论加粗:AI 网关是 AI 应用架构的“门禁系统”。它不仅承担着传统网关的路由和限流,更关键的是它实现了“Token 感知”、“模型感知”和“内容感知”。掌握 AI 网关的设计与实现,意味着你能够将任何 AI 能力安全地产品化,实现对成本、性能、安全的精细化管控。这是 AI 应用从“能用”走向“好用”、“敢用”的标志性一步。
2. 核心一:模型路由、负载均衡与故障转移
2.1 模型路由的需求场景
AI 网关的模型路由远比传统网关的 Path 路由复杂,常见的路由策略包括:
- 按用户等级路由:VIP 用户路由到低延迟、高并发的独占模型实例(例如专有部署的 GPT-4o 集群);普通用户路由到共享实例或更便宜的模型(如 GPT-4o-mini)。
- 按任务复杂度路由:通过预分类模型或启发式规则判断 Prompt 的复杂度,将简单任务(翻译、摘要)路由到轻量模型,复杂任务(逻辑推理、长文生成)路由到高性能模型。
- 灰度发布与金丝雀路由:当新模型或新的 Prompt 模板上线时,配置权重将 5% 的流量导入新版本,对比效果后再全量切换。
- 故障转移与断路器:当主模型(如 OpenAI API)返回 5xx 或超时率超过阈值时,通过 Resilience4j 断路器自动切换到备用模型(如 Azure OpenAI 或本地 vLLM)。
2.2 路由决策流程
我们设计一个复合路由决策引擎,按照优先级顺序决策:用户等级标签 > 任务复杂度标签 > 灰度权重 > 默认路由。故障转移在每次路由选择后由断路器保护。
flowchart TD
Start[请求进入路由Filter] --> CheckUserLabel{携带用户等级<br>Header?}
CheckUserLabel -->|是| RouteByLabel[按配置映射到<br>VIP/普通模型]
CheckUserLabel -->|否| CheckComplexity{携带复杂度<br>标签?}
CheckComplexity -->|是| RouteByComplexity[映射到对应模型<br>简单/复杂]
CheckComplexity -->|否| Grayscale{灰度权重<br>命中?}
Grayscale -->|是| CanaryModel[路由到金丝雀模型]
Grayscale -->|否| DefaultModel[路由到默认模型]
RouteByLabel --> CircuitBreaker[Resilience4j断路器]
RouteByComplexity --> CircuitBreaker
CanaryModel --> CircuitBreaker
DefaultModel --> CircuitBreaker
CircuitBreaker -->|断路器关闭| Forward[转发至选定模型]
CircuitBreaker -->|断路器打开| FallbackModel[降级到备用模型]
FallbackModel --> Forward
模型智能路由决策流程图说明:
- 图表主旨概括:该流程图描述了 AI 网关在收到请求后,如何根据用户等级、任务复杂度、灰度策略及故障状态,逐步决策最终路由目标。
- 逐层/逐元素分解:首先检查请求中是否显式标记了用户等级(由认证 Filter 注入),若有则直接按等级映射;否则检查是否由前置的复杂度评估 Filter 写入了复杂度标签;若无,则进入灰度权重判断;若以上均未命中,则使用默认模型。最终选定的模型实例都会经过 Resilience4j 断路器,若断路器因故障打开,则降级到备用模型。
- 设计原理映射:优先级设计体现了“显式意图优先,隐式推断次之,最后靠默认兜底”的原则。灰度和故障转移是两个独立的维度,灰度用于主动测试,故障转移用于被动防护。
- 工程联系与关键结论加粗:真正的智能路由不是简单的 if-else,而是通过可插拔的 Filter 组成的决策链。每个 Filter 都可以独立开发、测试和替换,这是 Spring Cloud Gateway Filter 链的核心优势。
2.3 Spring Cloud Gateway 实现:动态路由 Filter
我们实现一个 ModelRoutingGatewayFilterFactory(或者更灵活地,使用自定义 GatewayFilter),从 ServerWebExchange 的属性中读取由前置 Filter 注入的“目标模型标识”,动态修改路由后的 URI。
@Component
public class ModelRoutingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从 Exchange Attributes 获取前置分析阶段确定的模型 key
String targetModel = exchange.getAttribute("targetModel");
if (targetModel == null) {
targetModel = "default"; // 默认模型
}
// 在 Spring Cloud Gateway 中,通常通过 RouteToRequestUrlFilter 已经确定了请求 URI,
// 这里我们可以在该 Filter 之前执行,直接修改 URI 或添加负载均衡标识。
// 更佳实践:在 RouteLocator 中配置 lb://service-name,然后通过属性传递模型版本。
// 此处示意修改请求 Header,供后端负载均衡器识别。
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-Model-Name", targetModel)
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER - 1; // 在路由确定前执行
}
}
更符合 Spring Cloud Gateway 原生设计的方式是自定义 RoutePredicateFactory 或使用 Weight Route Predicate 进行灰度,并结合 LoadBalancer 实现多实例选择。但对于模型维度的路由,我们通常需要更复杂的逻辑,自定义 Filter 是最直接的手段。
2.4 集成 Resilience4j 断路器
Spring Cloud Gateway 提供了 spring-cloud-starter-circuitbreaker-reactor-resilience4j,可以轻松为路由配置断路器。在 application.yml 中:
spring:
cloud:
gateway:
routes:
- id: openai_route
uri: lb://openai-service
predicates:
- Path=/ai/v1/chat/**
filters:
- name: CircuitBreaker
args:
name: openaiCircuitBreaker
fallbackUri: forward:/fallback/model
resilience4j:
circuitbreaker:
instances:
openaiCircuitBreaker:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000ms
permittedNumberOfCallsInHalfOpenState: 3
timelimiter:
instances:
openaiCircuitBreaker:
timeoutDuration: 30s
当断路器打开时,请求会被转发到 Gateway 自身的 /fallback/model 端点,该端点可以是一个 Controller,内部再通过 WebClient 调用备用模型。这实现了自动降级。
3. 核心二:Token 配额管理与精细化限流
3.1 从 QPS 到 Token 限流的思维转变
传统限流算法(令牌桶、漏桶)一般以“请求次数”为维度。AI 场景下,成本由 Token 消耗决定,因此限流粒度必须细化到 Token 数量。我们需要实现一个“Token 桶”,存储每个用户(apiKey 或 userId)在当前时间窗口(分钟/小时/天)内已消耗的 Token 数,并在每次请求后根据实际消耗扣减。
流式响应给 Token 计数带来挑战:Gateway 是异步非阻塞的,不能等待整个响应结束再扣减(那样会阻塞线程)。我们必须在响应流经过网关时,逐块(SSE chunk)捕获并累计 Token 数量,并在流完成时一次性写入 Redis 扣除配额。
3.2 基于 Redis 的 Token 桶算法
Redis 是实现分布式限流的最佳选择。我们利用 INCRBY 命令累加 Token 消耗,并配合 EXPIRE 设置窗口过期时间。为了提高原子性和精度,通常使用 Lua 脚本:
-- lua/token_bucket.lua
-- KEYS[1]: 用户 Token 桶 Key,如 "token_quota:{apiKey}:minute"
-- ARGV[1]: 本次消耗 Token 增量
-- ARGV[2]: 窗口最大 Token 限制
-- ARGV[3]: 窗口过期时间(秒)
local current = redis.call('INCRBY', KEYS[1], ARGV[1])
if current == tonumber(ARGV[1]) then
-- 首次设置,添加过期时间
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
-- 检查是否超限
if tonumber(current) > tonumber(ARGV[2]) then
return 0 -- 超限
else
return 1 -- 允许
end
在 Java 端使用 ReactiveRedisTemplate 调用此脚本:
@Component
@RequiredArgsConstructor
public class TokenBucketRateLimiter {
private final ReactiveRedisTemplate<String, String> redisTemplate;
private static final String TOKEN_BUCKET_SCRIPT = """
local current = redis.call('INCRBY', KEYS[1], ARGV[1])
if current == tonumber(ARGV[1]) then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
if tonumber(current) > tonumber(ARGV[3]) then
return 0
end
return 1
""";
private final RedisScript<Long> script = RedisScript.of(TOKEN_BUCKET_SCRIPT, Long.class);
public Mono<Boolean> tryConsume(String apiKey, long tokens, long maxTokens, Duration window) {
String key = "token_quota:" + apiKey + ":" + window.toMinutes() + "m";
return redisTemplate.execute(script, List.of(key),
String.valueOf(tokens), String.valueOf(window.getSeconds()), String.valueOf(maxTokens))
.map(result -> result == 1L);
}
}
3.3 Token 计数的实现
我们需要一个 TokenCountingFilter,它作为响应式过滤器,拦截 LLM 的响应流并累计 Token。LangChain4j 1.0.0-alpha1 提供了 TokenCountEstimator 接口,也可以直接解析 OpenAI 格式的 SSE 数据。这里展示手工解析流式响应的实现:
@Component
public class TokenCountingFilter implements GlobalFilter, Ordered {
private final TokenBucketRateLimiter rateLimiter;
public TokenCountingFilter(TokenBucketRateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 继续调用链,处理响应
return chain.filter(exchange).then(Mono.defer(() -> {
// 获取原始响应对象
ServerHttpResponse response = exchange.getResponse();
// 仅处理 AI 请求的路径,可通过 Attribute 标记
Boolean isAiRequest = exchange.getAttribute("isAiRequest");
if (isAiRequest == null || !isAiRequest) return Mono.empty();
// 用装饰器包装响应,截获写出的数据
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
private final StringBuilder sseBuffer = new StringBuilder();
private int totalTokens = 0;
private String lastModel = "";
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Flux<? extends DataBuffer> flux = Flux.from(body);
return super.writeWith(flux.doOnNext(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String chunk = new String(content, StandardCharsets.UTF_8);
// 解析 SSE 数据
sseBuffer.append(chunk);
// 按行处理,寻找 "data:" 开头的行
while (sseBuffer.indexOf("\n") != -1) {
int endIndex = sseBuffer.indexOf("\n");
String line = sseBuffer.substring(0, endIndex);
sseBuffer.delete(0, endIndex + 1);
if (line.startsWith("data:")) {
String jsonData = line.substring(5).trim();
if ("[DONE]".equals(jsonData)) continue;
try {
// 简单解析 JSON 抽取 usage (用于最终 token 计数,通常在最后一条 chunk)
if (jsonData.contains("\"usage\"")) {
// 使用轻量 JSON 解析,这里仅示意
int tokenIdx = jsonData.indexOf("\"total_tokens\":");
if (tokenIdx != -1) {
int start = tokenIdx + 16;
int end = jsonData.indexOf(",", start);
if (end == -1) end = jsonData.indexOf("}", start);
totalTokens = Integer.parseInt(jsonData.substring(start, end).trim());
}
}
} catch (Exception ignored) {}
}
}
}).doOnComplete(() -> {
// 流结束,异步扣减配额
String apiKey = exchange.getAttribute("apiKey");
if (apiKey != null && totalTokens > 0) {
rateLimiter.tryConsume(apiKey, totalTokens, 100000, Duration.ofHours(1))
.subscribe();
}
// 同时写入审计日志(异步)
// auditLogger.log(exchange, totalTokens);
}));
}
};
// 将装饰后的响应设置回 exchange
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}));
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; // 在写响应之前
}
}
关键设计点:
- 使用
ServerHttpResponseDecorator拦截 write 操作,避免阻塞 Netty 线程。 - 通过
doOnNext累积每个数据块,解析 SSE 的data:行。 - 仅在
doOnComplete中获取最终的total_tokens并异步扣减配额,不影响响应返回。 - 与 Redis 的交互使用
subscribe()触发异步执行,不能使用block()。
3.4 超限处理与预算预警
当 Token 配额耗尽时,网关应拒绝请求并返回 429 Too Many Requests。我们可以在请求进入时预先检查剩余配额(通过 Redis GET 当前消耗量),若已超限则快速失败。预算预警可以通过在响应 Header 中添加 X-Budget-Remaining 实现,由 TokenBucketRateLimiter 计算后注入。
3.5 Token 配额实时结算与限流时序图
sequenceDiagram
participant Client
participant Gateway
participant Redis
participant LLM
Client->>Gateway: POST /v1/chat (apiKey, prompt)
Gateway->>Redis: GET token_quota:{apiKey}:current
Redis-->>Gateway: currentTokens
alt 配额已超限
Gateway-->>Client: 429 (配额用尽)
else 配额充足
Gateway->>LLM: 转发请求 (SSE)
LLM-->>Gateway: 流式响应 data chunks
Note over Gateway: 累积chunks,解析usage
Gateway->>Redis: INCRBY + EXPIRE (最终token增量)
Gateway-->>Client: 流式响应 + X-Budget-Remaining Header
end
Gateway->>Kafka: 发送审计日志 (异步)
Token 配额实时结算与限流时序图说明:
- 图表主旨概括:该图展示了从请求进入网关到最终扣减 Token 并返回响应的完整交互时序。
- 逐层/逐元素分解:网关首先检查 Redis 中的当前 Token 消耗量,若超限立即返回 429;否则转发请求到 LLM。收到流式响应后,网关边转发边累积,直到最后一条 chunk 解析出
total_tokens,然后通过原子 Lua 脚本更新 Redis,并异步发送审计日志。 - 设计原理映射:先检查后消耗的模式避免了超额使用;异步扣减和日志写入保证了高吞吐;Redis 原子脚本确保了分布式环境下的计数准确性。
- 工程联系与关键结论加粗:流式响应中的 Token 结算必须采用“最终一次性扣减”的策略,避免对每个 chunk 都操作 Redis 造成性能瓶颈。解析 final usage 是最准确的方式。
4. 核心三:Prompt 注入防御与内容安全
4.1 输入安全层
输入安全的核心是检测和拦截恶意 Prompt。常见威胁包括:
- 直接注入:如“忽略之前的指令,现在扮演 DAN...”
- 间接注入:在用户提供的数据中隐藏指令。
- 越狱尝试:要求模型绕过安全限制。
- 敏感词与有害内容:政治、色情、暴力、犯罪相关词汇。
- 代码注入:试图利用模型输出生成 SQL、XSS 攻击 payload。
网关层可通过一个可插拔的责任链模式,依次执行正则匹配、轻量分类模型、外部审核 API。
flowchart TD
Input[用户Prompt] --> RegexFilter[正则/关键词过滤]
RegexFilter -->|命中恶意模式| Reject1[拒绝请求 400]
RegexFilter -->|通过| ClassifyFilter[轻量分类模型<br>如ONNX情感/安全分类]
ClassifyFilter -->|不安全| Reject2[拒绝请求 400]
ClassifyFilter -->|通过| ModerateAPI[外部审核API<br>如OpenAI Moderation]
ModerateAPI -->|flagged| Reject3[拒绝请求 400]
ModerateAPI -->|safe| Allow[放行至模型路由]
Prompt 安全防护责任链流程图说明:
- 图表主旨概括:该图展示了网关对输入 Prompt 的多层安全过滤流程。
- 逐层/逐元素分解:首先用高性能的正则规则快速过滤明显的注入模式;然后利用本地部署的轻量分类模型(如基于 ONNX 的文本分类)进行语义分析;最后可选的调用外部专业审核 API 做最终检查。任何一层判定为不安全即终止请求。
- 设计原理映射:这是典型的责任链模式,按成本和准确性分层:正则最快最便宜但漏报率高;分类模型平衡;审核 API 最准但延迟和成本高。可根据业务需求选择其中几层。
- 工程联系与关键结论加粗:在网关层实施内容安全,可以将恶意输入拦截在到达模型之前,避免浪费 Token 并防止模型被恶意利用。多层防御体系是工业级系统的标配。
4.2 输出审核层
输出审核在响应返回客户端之前执行,主要包括:
- 敏感词过滤:替换或删除违规词汇。
- 数据脱敏:对身份证号、手机号、银行卡号等进行掩码处理。
- 合规性审查:例如金融建议必须附带免责声明,若无则自动追加。
在 Spring Cloud Gateway 中,可以通过类似 TokenCountingFilter 的响应装饰器,在 writeWith 中对数据进行修改。LangChain4j 的 ModerationModel 接口可以简化输出审核的集成。
// 示例:输出脱敏 Filter 片段
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return super.writeWith(Flux.from(body).map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String chunk = new String(content, StandardCharsets.UTF_8);
// 脱敏:隐藏手机号中间四位
String masked = chunk.replaceAll("(1[3-9]\\d)\\d{4}(\\d{4})", "$1****$2");
return response.bufferFactory().wrap(masked.getBytes(StandardCharsets.UTF_8));
}));
}
注意:对于流式响应,脱敏操作需要按数据块进行,可能造成脱敏不完整(比如手机号跨 chunk)。实际生产中应使用缓冲区累积足够内容后再处理,或者采用支持流式脱敏的库。
5. 核心四:全链路审计与不可变日志
5.1 审计日志设计
审计日志需记录每一次 AI 请求的完整上下文,字段设计如下:
| 字段 | 说明 |
|---|---|
| traceId | 分布式追踪 ID,关联 OpenTelemetry |
| userId | 最终用户 ID(业务层) |
| apiKey | 网关认证的 API Key 哈希 |
| timestamp | 请求时间戳 |
| model | 实际调用的模型名称 |
| promptTokens | Prompt 消耗 Token 数 |
| completionTokens | 生成消耗 Token 数 |
| totalTokens | 总 Token 数 |
| latency | 端到端延迟(ms) |
| statusCode | HTTP 状态码 |
| promptPreview | Prompt 前 200 字符(脱敏后) |
| responsePreview | 响应前 200 字符 |
| errorMessage | 错误信息(如有) |
5.2 采集与存储架构
网关通过 WebFilter 捕获请求体和响应体(需解决 Body 流只能读一次的问题),组装日志对象后异步发送到 Kafka。Kafka 消费者将数据批量写入 ClickHouse 或 Elasticsearch,供后续实时查询和离线分析。
flowchart LR
GW[AI Gateway] -->|异步发送| Kafka[(Kafka Topic)]
Kafka -->|消费| LogConsumer[审计日志消费者]
LogConsumer --> ClickHouse[(ClickHouse)]
LogConsumer --> ES[(Elasticsearch)]
ClickHouse --> Grafana[Grafana 成本/用量看板]
ES --> Kibana[Kibana 搜索与告警]
全链路审计日志采集与存储架构图说明:
- 图表主旨概括:该图描绘了审计日志从网关产生到最终可视化分析的数据流。
- 逐层/逐元素分解:网关异步将结构化日志推送到 Kafka,实现流量削峰填谷;消费者将日志写入 ClickHouse(适合聚合分析)和 Elasticsearch(适合全文检索);上层的 Grafana 和 Kibana 提供数据可视化和告警。
- 设计原理映射:异步写入避免日志记录拖慢网关响应;Kafka 作为缓冲层提高系统健壮性;ClickHouse 列式存储擅长 Token 成本计算,ES 擅长日志搜索。
- 工程联系与关键结论加粗:全链路审计是 AI 网关的企业级必备能力。它不仅是 FinOps 成本归因的基础,也是安全审计和故障排查的唯一入口。
5.3 请求体重复读的解决
Gateway 底层基于 Netty,ServerHttpRequest 的 Body 默认只能读取一次。我们需要使用 ServerHttpRequestDecorator 缓存请求体,以便安全 Filter 和审计 Filter 都能访问。
public class CachedBodyGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (exchange.getRequest().getHeaders().getContentType() != null &&
exchange.getRequest().getHeaders().getContentType().includes(MediaType.APPLICATION_JSON)) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
String cachedBody = new String(bytes, StandardCharsets.UTF_8);
// 将缓存内容放入 Attribute
exchange.getAttributes().put("cachedRequestBody", cachedBody);
// 构建新的可读请求
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
return chain.filter(exchange);
}
}
这段代码将请求体缓存为字节数组,并注入到 Exchange 属性中供后续 Filter 使用,同时提供了一个新的可读 Body。这是实现请求体多次读取的标准模式。
6. 响应式编程与 Spring Cloud Gateway 集成细节
6.1 WebFlux 响应式模型下的挑战
Spring Cloud Gateway 构建在 WebFlux 之上,要求所有操作必须非阻塞。处理 AI 请求时面临两大挑战:
- 流式响应的 Token 累积:不能使用
Flux.collectList().block()。必须在doOnNext中逐步处理,在doOnComplete中触发最终动作。 - 请求体的重复读取:必须使用
DataBufferUtils.join缓存 body,且小心处理 ByteBuf 的释放,避免内存泄漏。 - Redis 操作的非阻塞集成:使用
ReactiveRedisTemplate,所有调用返回Mono/Flux,组合在响应式链中。
6.2 流式响应 Token 累积的完整代码解析
我们在 3.3 节中已给出核心实现,这里补充一个更健壮的版本,它通过 Flux 的 reduce 或使用 AtomicInteger 在 doOnNext 中累计。更推荐的做法是编写一个自定义的 Subscriber 或使用 Flux.transform。但考虑到代码可读性,我们在 doOnComplete 中解析出最终 Token 数。
6.3 非阻塞 Redis 令牌桶的完整集成
在认证 Filter 中,我们需要同步检查用户配额是否超限。这可以在请求进入时通过 ReactiveRedisTemplate 的 get 操作实现:
public Mono<Boolean> isOverLimit(String apiKey, long maxTokens, Duration window) {
String key = "token_quota:" + apiKey + ":current_" + window.toMinutes();
return redisTemplate.opsForValue().get(key)
.defaultIfEmpty("0")
.map(val -> Long.parseLong(val) >= maxTokens);
}
如果返回 true,则直接在 Filter 中返回 Mono.error 或设置 429 响应,而不再继续链调用。这种预先检查可以快速失败。
6.4 Spring Cloud Gateway 配置总览
以下是整合了路由、断路器、限流等的 application.yml 片段:
spring:
cloud:
gateway:
routes:
- id: ai_chat
uri: lb://ai-backend
predicates:
- Path=/ai/v1/chat
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
- name: CircuitBreaker
args:
name: aiCircuitBreaker
fallbackUri: forward:/fallback/ai
- RemoveRequestHeader=Authorization # 防止内部 Key 泄露
metadata:
model-group: chat
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000
timelimiter:
configs:
default:
timeoutDuration: 30s
这些配置需要配合自定义的 ModelRoutingFilter、TokenCountingFilter 等使用。Spring Cloud Gateway 的 RequestRateLimiter 仍然可以作为 QPS 限流的第一道防线,Token 限流则在更细粒度层控制。
7. 贯穿案例:构建企业级 AI 统一接入平台
7.1 需求背景
某 SaaS 公司向外部开发者提供 AI 能力 API。开发者注册后获得 apiKey,每月有固定的 Token 额度。公司内部使用 OpenAI 和本地部署的 DeepSeek 模型,希望根据请求复杂度和用户套餐动态路由,并严格控制成本和安全。
7.2 架构设计
整体网络拓扑如下:
flowchart TB
classDef clientStyle fill:#d1fae5,stroke:#10b981,stroke-width:1.5px,color:#064e3b
classDef gatewaySub fill:#f0f4ff,stroke:#93a3d3,stroke-width:1.5px
classDef gatewayNode fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#1e3a8a
classDef serviceNode fill:#ede9fe,stroke:#8b5cf6,stroke-width:1.5px,color:#4c1d95
classDef dataNode fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#92400e
Dev["外部开发者"] --> Internet["Internet"]
Internet --> LB["负载均衡器"]
LB --> GW["AI Gateway (Spring Cloud Gateway)"]
subgraph GatewayCluster["网关集群"]
GW1["实例1"]
GW2["实例2"]
end
GW --> AuthService["认证服务"]
GW --> Redis[("Redis 7.x")]
GW --> Kafka[("Kafka")]
GW --> Router["路由层"]
Router --> OpenAI["OpenAI API"]
Router --> DeepSeek["本地 DeepSeek vLLM"]
Kafka --> ClickHouse[("ClickHouse")]
ClickHouse --> Grafana["Grafana Dashboard"]
class Dev,Internet,LB clientStyle
class GatewayCluster gatewaySub
class GW,GW1,GW2,AuthService,Router gatewayNode
class OpenAI,DeepSeek serviceNode
class Redis,Kafka,ClickHouse,Grafana dataNode
贯穿案例的企业 AI 接入平台拓扑图说明:
- 图表主旨概括:该图展示了网关在生产环境中的部署架构,包括冗余、外部依赖和监控。
- 逐层/逐元素分解:开发者通过公网访问负载均衡器,流量分发到网关集群。网关调用独立的认证服务校验
apiKey,并依赖 Redis 进行 Token 限流和缓存,通过 Kafka 异步发送审计日志。后端模型包括外部 API 和自建模型,ClickHouse 存储日志供可视化。 - 设计原理映射:网关无状态,便于水平扩展;认证分离实现关注点隔离;Kafka 解耦日志处理,避免 ClickHouse 写入压力影响关键路径。
- 工程联系与关键结论加粗:AI 网关必须作为无状态服务部署,所有状态外置到 Redis 和 Kafka,这是实现高可用和弹性伸缩的基础。
7.3 端到端请求流程序列图
sequenceDiagram
participant Dev as 开发者
participant GW as AI Gateway
participant Auth as 认证服务
participant Redis
participant Route as 路由Filter
participant LLM_Simple as DeepSeek (简单)
participant LLM_Complex as GPT-4o (复杂)
participant Kafka
Dev->>GW: POST /v1/chat + apiKey
GW->>Auth: 校验 apiKey
Auth-->>GW: 用户信息(等级、套餐)
GW->>Redis: 检查 Token 配额
Redis-->>GW: 剩余配额充足
GW->>GW: 复杂度分析 Filter 判定为 "简单"
GW->>Route: targetModel=deepseek-simple
Route->>LLM_Simple: 转发请求
LLM_Simple-->>GW: SSE 流式响应
Note over GW: 逐块转发,累积usage
GW->>Redis: 扣减 Token (lua)
GW-->>Dev: SSE 响应 + X-Budget-Remaining
GW->>Kafka: 异步发送审计日志
贯穿案例请求流程序列图说明:
- 图表主旨概括:该序列图详细描述了外部开发者调用 AI API 的全链路处理过程。
- 逐层/逐元素分解:请求经过认证、配额检查、复杂度分析、路由选择,最终转发到合适的后端模型。响应以流式返回,网关实时累积 Token 并结算。每一步都与基础设施交互,体现了网关的协调作用。
- 设计原理映射:复杂度分析 Filter 可根据简单的规则(如字符串长度、是否包含“分析”关键词)或调用一个小模型来判断,决定使用低成本模型还是高能力模型。
- 工程联系与关键结论加粗:通过网关的统一编排,后台的多模型异构性对客户端完全透明。开发者只需调用一个接口,而业务方可以精细控制成本和体验。
8. 与前后系列的衔接
本文作为系列收尾,承担着将前序所有技术能力整合为统一入口的角色:
- 与第4篇《LLM 通信协议》:AI 网关是介于客户端和 REST/SSE/gRPC 推理服务之间的透明代理,它需要理解协议细节才能解析 Token 用量和路由。
- 与第5-8篇《MCP 协议深度解析》:网关可以作为 MCP Host 的代理,对外暴露 MCP 接口,对内路由到不同的 MCP Server,从而实现工具调用的统一管控。
- 与第9篇《AI 应用的可观测性》:网关层是分布式追踪的起点,负责生成并传播
TraceId。本文的全链路审计与 OpenTelemetry 的 Span 互补,形成业务级和系统级的双重可观测性。 - 与第11篇《语义缓存与重复请求消除》:语义缓存可以直接集成在网关层,作为全局 Filter,在请求到达后端 LLM 之前拦截并返回缓存结果,极大节省成本。本文的架构图中已将语义缓存作为可选模块。
- 与系列五第15篇《AI FinOps》:网关层的 Token 配额管理是 FinOps 计费与预算控制的基石。网关记录的 Token 消耗数据将直接输入成本归因模型,实现按用户、部门、项目的精确账单。
通过这种衔接,读者可以清晰地看到:整个 AI 应用架构以网关为入口,以可观测性为脉络,以缓存、存储、协议为支撑,最终达成安全、高效、可审计的企业级交付。
9. 面试高频专题
以下问题结合本文核心内容,模拟一线大厂面试场景。每题均按“问题→分析→回答→总结”四段式结构阐述。
9.1 AI 网关和传统 API 网关有什么区别?为什么不能用 Nginx 直接代理 LLM 请求?
问题:请对比 AI 网关与 Spring Cloud Gateway / Nginx 这类传统 API 网关的差异。
分析:考察候选人对 AI 网关本质的理解,是否意识到 Token 成本、模型路由和内容安全等新需求。
回答:传统 API 网关主要处理 HTTP 路由、QPS 限流、认证和基础日志。AI 网关则额外要求:① Token 感知——能解析 LLM 交互中的 Token 消耗并据此计费限流;② 模型感知——根据用户等级、任务复杂度、灰度策略动态路由到不同模型;③ 内容感知——检测 Prompt 注入、敏感内容并审核 LLM 输出。Nginx 虽然能通过 ngx_http_js_module 编写脚本,但缺乏对 LLM 协议(SSE 流、Token 解析)的原生支持,且扩展复杂、难以实现异步非阻塞的 Token 结算和动态路由。Spring Cloud Gateway 基于 WebFlux,配合自定义 Filter 和 ReactiveRedisTemplate 能自然实现这些能力。
总结:AI 网关不是传统网关的替代品,而是其增强版。关键是抽象层次的提升:从“请求”维度上升到“Token 和内容”维度。
9.2 如何设计一个基于 Token 消耗的限流系统?如何解决流式响应中实时计费的难题?
问题:请描述 Token 限流的方案,并说明在网关层如何处理 SSE 流式响应的计费问题。
分析:考察分布式限流算法和响应式编程的实际经验。
回答:采用基于 Redis 的令牌桶变种——Token 桶。每个用户拥有以时间窗口为单位的计数器(key 包含窗口标识),每次请求完成后通过 Lua 脚本原子增加消耗的 Token 数并检查是否超限。流式响应处理:在 Gateway Filter 中,使用 ServerHttpResponseDecorator 截获写出的 DataBuffer,通过 doOnNext 累积 SSE chunk,解析最后的 usage.total_tokens。在 doOnComplete 中异步调用 Redis 扣减。不能阻塞 Netty 线程,因此用 subscribe() 触发。
总结:关键在于非阻塞累积和最终一次性结算,避免每个 chunk 都操作 Redis。
9.3 LLM 模型故障时,如何通过网关实现自动降级?
问题:如果 OpenAI API 宕机,你的网关如何将请求切到备用模型?
回答:利用 Resilience4j 断路器。在 Spring Cloud Gateway 路由配置中指定 CircuitBreaker 过滤器,设置失败率阈值。当断路器打开,网关将请求转发到预先定义的 fallback URI(如 /fallback/model)。该端点内使用 WebClient 调用备用模型(如 Azure OpenAI 或本地模型)。同时可以结合 LoadBalancer 对健康端点进行探测,实现主动切换。
总结:断路器是保证 AI 服务高可用的标准组件,网关只需做好集成配置。
9.4 如何实现按用户等级路由到不同质量的模型?
问题:VIP 用户用 GPT-4,免费用户用 GPT-3.5,如何在网关实现?
回答:通过认证 Filter 从 apiKey 解析用户等级,存入 ServerWebExchange 属性。自定义 ModelRoutingFilter 读取该属性,并根据配置映射关系,修改转发请求的 Host header 或直接替换 URI。如果后端使用服务发现,可以动态选择 lb://model-vip 或 lb://model-free。
总结:关键是解耦认证信息与路由决策,由 Filter 链传递元数据。
9.5 Prompt 注入攻击有哪些常见手段?网关如何防御?
问题:请列举 Prompt 注入的例子,并说明网关防护方案。
回答:常见手段包括直接注入(“忽略之前的指令”)、越狱(DAN)、间接注入(在文档中隐藏指令)。网关防御采用多层过滤:第一层正则/关键词匹配快速阻断已知模式;第二层使用本地轻量安全分类模型(如基于微调 BERT 的 ONNX 模型)检测有害内容;第三层调用专业内容审核 API(如 OpenAI Moderation)。任何一层触发即拒绝请求。
总结:防御要结合速度与深度,多层责任链是工业标准。
9.6 全链路审计日志需要包含哪些关键字段?为什么?
问题:设计一个 LLM 调用的审计日志结构,并说明各字段用途。
回答:核心字段:traceId(关联调用链)、userId/apiKey(成本归属)、timestamp、model、promptTokens/completionTokens/totalTokens(成本核算)、latency(性能监控)、statusCode、promptPreview/responsePreview(安全审计)。这些字段支撑成本分账、故障排查、安全合规三大目标。
总结:日志不仅是记录,更是公司运营的基础数据。
9.7 如何处理请求体和响应体的多次读取问题?
问题:在 Gateway 中,安全 Filter 和审计 Filter 都需要读取 Body,怎么做?
回答:使用 ServerHttpRequestDecorator 缓存请求体为字节数组,并重写 getBody() 返回新的 Flux。缓存内容放入 exchange.getAttributes() 供其他 Filter 使用。响应体则通过 ServerHttpResponseDecorator 拦截 writeWith 实现。
总结:这是 WebFlux 网关的常见模式,必须注意内存管理和 Buffer 释放。
9.8 如何实现金丝雀发布新模型?
问题:上线新版 Prompt 模板或新模型版本,希望 5% 流量先试用,怎么实现?
回答:结合 Spring Cloud Gateway 的 Weight 路由谓词和自定义 Filter。配置两条路由,一条指向新模型,权重 5%,一条指向旧模型,权重 95%。在 Filter 中可进一步添加特定用户的 Header 进行流量染色。通过可观测系统对比两组请求的效果和成本。
总结:灰度发布是 AI 网关的标准能力,Spring Cloud Gateway 原生支持权重路由。
9.9 网关中的 Token 计数如何保证准确性?如果流中断怎么办?
问题:如果 SSE 连接中断,最后的 usage chunk 未收到,网关还会扣减 Token 吗?
回答:这是一个生产常见问题。通常会采取防御策略:在 doOnCancel 或 doOnError 中,如果尚未收到最终 usage,则根据已接收的内容长度估算 Token(如使用 LangChain4j 的 TokenCountEstimator)并部分扣减,或在日志中标记为不完整。更严格的方式是在 LLM 侧要求返回 X-Usage Header 作为非流式的备选。
总结:健壮的计费系统需要处理异常情况,估算和补救机制不可缺少。
9.10 网关如何处理不同 LLM 供应商的协议差异?
问题:OpenAI 的 Chat Completion 和 Anthropic 的 Messages API 格式不同,网关怎么统一?
回答:网关可以定义统一的内部 API 协议(如 OpenAI 格式),通过适配器模式在转发前转换请求,响应时再转回统一格式。LangChain4j 的 ChatLanguageModel 已经抽象了不同供应商,网关可以集成它作为统一的调用层。
总结:协议适配是网关对上游系统屏蔽复杂性的重要手段。
9.11 为什么 AI 网关的限流必须基于 Token 而不是 QPS?
问题:一个 QPS 限流可以限制请求次数,为什么还不够?
回答:因为单次请求的 Token 消耗差异巨大。例如生成一篇 2000 字的文章消耗约 3000 Tokens,而一个简单问答只需 50 Tokens。若只限制 QPS,一个免费用户可通过单次请求消耗大量配额,造成成本不可控。Token 限流直接与经济成本挂钩,粒度更细。
总结:AI 的计费模型决定限流模型,这是设计思维的转变。
9.12 阐述网关中 Token 配额预算预警的实现方式。
问题:如何在用户月度 Token 消耗达到 80% 时通知用户?
回答:网关在每次扣减后检查当前总消耗量(可维护一个日/月累计 key),若超过阈值,在响应 Header 中注入 X-Budget-Remaining: 20% 或 X-Budget-Exceeded: true。更进一步的,可以触发邮件或 webhook 通知,由网关异步调用消息服务。
总结:预警机制让成本管理从被动限制变为主动告知,提升用户体验。
9.13 网关在高并发下如何保证 Redis 令牌桶的性能?
问题:如果 QPS 很高,大量 INCRBY 操作会打垮 Redis 吗?
回答:Redis 单机可支撑 10 万+ QPS,通常足够。还可以通过本地预取一部分配额减少 Redis 调用(本地小令牌桶),或使用 Redis Cluster 分片。但 Lua 脚本的执行是原子且快速的,是标准方案。
总结:做好容量评估,必要时可以采用两级限流(本地 + 远程)。
9.14 (系统设计题)设计一个面向开放平台的 AI 网关,支持 API Key 认证与配额、按套餐路由、安全过滤和审计。
问题:请为一家提供 AI API 的开放平台设计网关架构,需满足:①API Key 认证和 Token 配额管理;②根据用户购买套餐路由到不同模型;③Prompt 注入防御和敏感词过滤;④完整的 LLM 调用审计日志。画出架构图和请求处理时序图,并给出关键 Filter 代码设计。
分析:综合考察网关设计能力,需要整合前面所有知识点。
回答:
架构图:
flowchart LR
classDef devStyle fill:#d1fae5,stroke:#10b981,stroke-width:1.5px,color:#064e3b
classDef gwSub fill:#f0f4ff,stroke:#93a3d3,stroke-width:1.5px
classDef gwNode fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#1e3a8a
classDef llmNode fill:#ede9fe,stroke:#8b5cf6,stroke-width:1.5px,color:#4c1d95
classDef dataNode fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#92400e
Dev["开发者"] --> Internet["Internet"]
Internet --> GW["AI Gateway"]
subgraph GWSub["网关Filter链"]
direction LR
F1["认证Filter"] --> F2["配额检查"]
F2 --> F3["输入安全"]
F3 --> F4["套餐路由"]
F4 --> F5["后端调用"]
F5 --> F6["输出安全"]
F6 --> F7["审计日志"]
end
F1 --> AuthDB[("认证数据库")]
F2 --> Redis[("Redis")]
F4 --> ModelRegistry["模型注册中心"]
F5 --> LLM1["GPT-4o"]
F5 --> LLM2["GPT-4o-mini"]
F7 --> Kafka[("Kafka")]
Kafka --> ClickHouse[("ClickHouse")]
class Dev,Internet devStyle
class GWSub gwSub
class F1,F2,F3,F4,F5,F6,F7,ModelRegistry gwNode
class LLM1,LLM2 llmNode
class AuthDB,Redis,Kafka,ClickHouse dataNode
请求处理时序图:
sequenceDiagram
participant Client
participant AuthF
participant QuotaF
participant SecurityF
participant RoutingF
participant LLM
participant AuditF
Client->>AuthF: 请求+apiKey
AuthF->>AuthDB: 验证Key,获取套餐
AuthDB-->>AuthF: 套餐: premium
AuthF->>QuotaF: 用户信息
QuotaF->>Redis: 查询剩余Token
Redis-->>QuotaF: 30000 tokens left
QuotaF->>SecurityF: 请求放行
SecurityF->>SecurityF: 正则+分类检测,通过
SecurityF->>RoutingF: 放行,携带套餐属性
RoutingF->>RoutingF: 根据premium路由到GPT-4o
RoutingF->>LLM: 转发请求
LLM-->>RoutingF: 流式响应
RoutingF-->>AuditF: 响应流
AuditF->>Redis: 扣减Token
AuditF->>Kafka: 发送日志
AuditF-->>Client: 流式响应+Header
关键 Filter 代码设计:
QuotaFilter:检查 Redis 剩余 Token,超限则返回 429。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String apiKey = exchange.getAttribute("apiKey");
return rateLimiter.isOverLimit(apiKey, monthlyMaxToken)
.flatMap(isOver -> {
if (isOver) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
});
}
SecurityInputFilter:执行多层检查。
RoutingFilter:根据 plan 属性选择对应模型 URI。
技术选型权衡:Spring Cloud Gateway 作为开发框架,因其响应式架构和丰富的 Filter 机制非常适合;Redis 用于分布式计数,Kafka 解耦日志,ClickHouse 存储分析数据。此设计能支撑日均亿级 Token 吞吐。
量化分析:单网关实例可处理约 2000 SSE 并发连接,Token 结算延迟<5ms(Redis Lua 脚本),审计日志端到端延迟<1s。
总结:该系统设计展示了 AI 网关的完整能力,是进入大厂 AI 基础设施岗位的必备知识。
10. AI 网关设计核心要素速查表
| 维度 | 核心问题 | 解决方案 |
|---|---|---|
| 认证授权 | 如何安全识别调用方? | API Key + JWT,通过 Gateway Filter 校验并注入用户上下文 |
| Token 限流 | 如何按 Token 消耗控制成本? | Redis Token 桶 + Lua 脚本,流式响应最终结算 |
| 模型路由 | 如何智能选择模型? | 自定义 Filter 根据用户等级、复杂度、灰度权重决策,结合 Resilience4j 降级 |
| 内容安全 | 如何防御 Prompt 注入? | 多层责任链:正则→分类模型→审核 API |
| 输出审查 | 如何确保合规? | 响应装饰器脱敏、追加免责声明 |
| 审计日志 | 如何追溯每次调用? | 异步 Kafka 写入 ClickHouse/ES,记录 TraceId 关联全链路 |
| 流式处理 | 如何处理 SSE 不阻塞? | WebFlux 装饰器,doOnNext 累积,doOnComplete 异步扣减 |
| 请求体重读 | 多 Filter 读取 Body? | ServerHttpRequestDecorator 缓存字节数组 |
| 高可用 | 网关自身和模型故障? | 网关集群部署,Redis 哨兵,模型断路器降级 |
延伸阅读:Spring Cloud Gateway 官方文档、Resilience4j 官方文档、Redis 官方令牌桶实现参考、OpenAI API 安全最佳实践、LangChain4j 文档。
至此,我们完成了 AI 应用核心框架与协议系列的收官之旅。从通信协议到 MCP 编排,从缓存到可观测性,最终由 AI 网关将所有能力以安全、可控、可计费的方式呈现给世界。你已经具备了构建企业级 AI 应用的系统化工程能力。