AI 网关架构:认证、限流、路由与审计统一管控

1 阅读33分钟

概述

系列定位:本文是“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 网关在传统网关的基础上增加了三个新维度:

  1. Token 感知:能够解析 LLM 请求和流式响应中的 Token 用量,并据此进行计费、限流和配额管理。
  2. 模型感知:能够根据用户身份、请求意图、模型健康状态等,动态选择最合适的模型(或模型实例)。
  3. 内容感知:能够审查进出内容的合规性,防御 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 桶”,存储每个用户(apiKeyuserId)在当前时间窗口(分钟/小时/天)内已消耗的 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实际调用的模型名称
promptTokensPrompt 消耗 Token 数
completionTokens生成消耗 Token 数
totalTokens总 Token 数
latency端到端延迟(ms)
statusCodeHTTP 状态码
promptPreviewPrompt 前 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 请求时面临两大挑战:

  1. 流式响应的 Token 累积:不能使用 Flux.collectList().block()。必须在 doOnNext 中逐步处理,在 doOnComplete 中触发最终动作。
  2. 请求体的重复读取:必须使用 DataBufferUtils.join 缓存 body,且小心处理 ByteBuf 的释放,避免内存泄漏。
  3. Redis 操作的非阻塞集成:使用 ReactiveRedisTemplate,所有调用返回 Mono/Flux,组合在响应式链中。

6.2 流式响应 Token 累积的完整代码解析

我们在 3.3 节中已给出核心实现,这里补充一个更健壮的版本,它通过 Fluxreduce 或使用 AtomicIntegerdoOnNext 中累计。更推荐的做法是编写一个自定义的 Subscriber 或使用 Flux.transform。但考虑到代码可读性,我们在 doOnComplete 中解析出最终 Token 数。

6.3 非阻塞 Redis 令牌桶的完整集成

在认证 Filter 中,我们需要同步检查用户配额是否超限。这可以在请求进入时通过 ReactiveRedisTemplateget 操作实现:

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

这些配置需要配合自定义的 ModelRoutingFilterTokenCountingFilter 等使用。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-viplb://model-free

总结:关键是解耦认证信息与路由决策,由 Filter 链传递元数据。

9.5 Prompt 注入攻击有哪些常见手段?网关如何防御?

问题:请列举 Prompt 注入的例子,并说明网关防护方案。

回答:常见手段包括直接注入(“忽略之前的指令”)、越狱(DAN)、间接注入(在文档中隐藏指令)。网关防御采用多层过滤:第一层正则/关键词匹配快速阻断已知模式;第二层使用本地轻量安全分类模型(如基于微调 BERT 的 ONNX 模型)检测有害内容;第三层调用专业内容审核 API(如 OpenAI Moderation)。任何一层触发即拒绝请求。

总结:防御要结合速度与深度,多层责任链是工业标准。

9.6 全链路审计日志需要包含哪些关键字段?为什么?

问题:设计一个 LLM 调用的审计日志结构,并说明各字段用途。

回答:核心字段:traceId(关联调用链)、userId/apiKey(成本归属)、timestampmodelpromptTokens/completionTokens/totalTokens(成本核算)、latency(性能监控)、statusCodepromptPreview/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 吗?

回答:这是一个生产常见问题。通常会采取防御策略:在 doOnCanceldoOnError 中,如果尚未收到最终 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 应用的系统化工程能力。