AI 服务网关架构:大模型调用链路中的限流、路由与降级设计

一、大模型调用链的脆弱性:从单点故障到雪崩效应

大模型服务接入企业系统后,调用链路比传统微服务更加脆弱。原因有三:其一,大模型推理延迟高且波动大,P99 延迟可能达到数十秒,远超常规 RPC 调用;其二,Token 计费模式下,单次请求成本可达数元,无限制调用会快速消耗预算;其三,多模型供应商的 SLA 参差不齐,OpenAI、Anthropic、国内模型的可用性和响应速度差异显著。

当某个模型供应商出现故障或延迟飙升时,如果没有网关层的保护,所有上游请求会堆积在故障节点,线程池耗尽后引发级联失败。更隐蔽的风险是成本失控——一个未设限的内部服务可能在数小时内消耗掉整月预算。AI 服务网关正是解决这些问题的核心基础设施。

二、AI 网关的分层架构:从请求入口到模型路由

AI 服务网关在传统 API 网关基础上,增加了模型路由、Token 限流和语义降级三个核心层。

flowchart TB
    A[客户端请求] --> B[认证与鉴权层]
    B --> C[Token 限流层]
    C --> D[模型路由层]
    D --> E1[OpenAI Provider]
    D --> E2[Anthropic Provider]
    D --> E3[国产模型 Provider]
    E1 --> F[响应聚合与降级]
    E2 --> F
    E3 --> F
    F --> G[监控与计费]

    C -->|超限| H[降级策略]
    H --> H1[缓存响应]
    H --> H2[轻量模型替换]
    H --> H3[排队等待]

    style D fill:#e1f5fe
    style C fill:#fff3e0
    style H fill:#fce4ec

模型路由层是 AI 网关区别于传统网关的关键。它根据请求的模型标识、上下文长度和成本预算,将请求分发到最合适的供应商。路由策略包括:优先级路由(优先使用成本最低的供应商)、延迟路由(优先使用响应最快的供应商)、权重路由(按比例分配流量用于 A/B 测试)。

三、生产级代码实现:Spring Cloud Gateway + AI 路由

3.1 基于 Token 的限流过滤器

@Component
public class TokenRateLimitFilter implements GlobalFilter, Ordered {

    private final RedisTemplate<String, String> redisTemplate;

    // 每个租户每分钟的 Token 预算上限
    private static final long TOKEN_BUDGET_PER_MINUTE = 50000L;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String tenantId = exchange.getRequest().getHeaders().getFirst("X-Tenant-Id");
        if (tenantId == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        String key = "token_budget:" + tenantId;
        // 获取当前租户已消耗的 Token 数
        String consumed = redisTemplate.opsForValue().get(key);
        long currentConsumed = consumed != null ? Long.parseLong(consumed) : 0L;

        if (currentConsumed >= TOKEN_BUDGET_PER_MINUTE) {
            // 超出预算,触发降级
            return handleRateLimitExceeded(exchange, tenantId);
        }

        // 预扣减:估算本次请求的 Token 消耗
        long estimatedTokens = estimateRequestTokens(exchange.getRequest());
        redisTemplate.opsForValue().increment(key, estimatedTokens);
        // 首次设置时添加过期时间
        redisTemplate.expire(key, 60, TimeUnit.SECONDS);

        // 将估算值传递给下游,用于响应后精确扣减
        exchange.getAttributes().put("estimatedTokens", estimatedTokens);

        return chain.filter(exchange);
    }

    /**
     * 根据请求体估算 Token 消耗
     * 简单策略:输入字符数 / 4 + 预估输出 Token
     */
    private long estimateRequestTokens(ServerHttpRequest request) {
        String contentLength = request.getHeaders().getFirst("Content-Length");
        long bodySize = contentLength != null ? Long.parseLong(contentLength) : 0L;
        // 粗略估算:1 个中文字约 2 Token,预留输出空间
        return (bodySize / 2) + 1024;
    }

    private Mono<Void> handleRateLimitExceeded(ServerWebExchange exchange,
                                                String tenantId) {
        // 尝试降级到轻量模型
        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
            .header("X-Model-Downgrade", "true")
            .header("X-Target-Model", "gpt-3.5-turbo")
            .build();

        log.warn("租户 {} Token 预算超限,降级到轻量模型", tenantId);
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    @Override
    public int getOrder() {
        return -1; // 优先级最高,最先执行
    }
}

3.2 多模型路由策略

@Component
public class ModelRoutePredicateFactory extends AbstractRoutePredicateFactory<ModelRoutePredicateFactory.Config> {

    private final ModelHealthIndicator healthIndicator;

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String targetModel = exchange.getRequest().getHeaders()
                .getFirst("X-Target-Model");

            // 如果指定了目标模型,直接路由
            if (targetModel != null) {
                return config.getModelId().equals(targetModel);
            }

            // 未指定时,根据健康状态和延迟动态选择
            ModelHealth health = healthIndicator.getHealth(config.getModelId());
            if (!health.isAvailable()) {
                return false;
            }
            // 延迟超过阈值时降低路由权重
            return health.getP95Latency() < config.getMaxLatencyMs();
        };
    }

    @Data
    public static class Config {
        private String modelId;
        private long maxLatencyMs = 10000L;
    }
}

3.3 语义降级与缓存策略

@Service
public class SemanticFallbackService {

    private final CacheManager cacheManager;
    private final EmbeddingService embeddingService;

    /**
     * 语义缓存:对相似请求返回缓存结果
     * 适用于 FAQ 类场景,降低模型调用频率
     */
    public Mono<String> getSemanticCache(String query, double similarityThreshold) {
        float[] queryEmbedding = embeddingService.embed(query);

        Cache cache = cacheManager.getCache("semantic_cache");
        // 遍历缓存,计算余弦相似度
        return Mono.fromCallable(() -> {
            Map<String, float[]> cachedEmbeddings = getAllCachedEmbeddings(cache);
            String bestMatch = null;
            double bestScore = 0.0;

            for (Map.Entry<String, float[]> entry : cachedEmbeddings.entrySet()) {
                double similarity = cosineSimilarity(queryEmbedding, entry.getValue());
                if (similarity > bestScore) {
                    bestScore = similarity;
                    bestMatch = entry.getKey();
                }
            }

            if (bestScore >= similarityThreshold && bestMatch != null) {
                log.info("语义缓存命中: query={}, similarity={}", query, bestScore);
                return getCachedResponse(cache, bestMatch);
            }
            return null;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    private double cosineSimilarity(float[] a, float[] b) {
        double dot = 0, normA = 0, normB = 0;
        for (int i = 0; i < a.length; i++) {
            dot += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8);
    }
}

四、AI 网关的架构妥协:延迟、一致性与成本三角

限流精度与延迟的矛盾:Token 限流依赖预估算,而精确 Token 数只能在模型响应后获取。预估算偏高会浪费预算配额,偏低则无法有效限流。生产中建议采用"预扣减 + 异步精算"模式——请求前按估算值扣减,响应后按实际值回补差额,Redis 中维护两套计数器。

语义缓存的命中率陷阱:语义缓存依赖 Embedding 相似度匹配,但相似度阈值设置极难。阈值过低(如 0.85),大量不相关请求命中缓存,返回错误答案;阈值过高(如 0.98),命中率极低,缓存形同虚设。建议仅在 FAQ、知识库问答等高重复度场景启用语义缓存,对话类场景禁用。

多模型路由的一致性风险:同一会话中切换模型供应商,可能导致上下文理解不一致。不同模型对相同 Prompt 的响应风格、格式和推理能力差异显著。建议在会话维度绑定模型,仅在会话初始化时执行路由决策,会话内不切换。

降级策略的用户体验代价:从 GPT-4 降级到 GPT-3.5 时,响应质量显著下降,但用户并不感知降级发生。生产中必须在响应头中标记降级状态(如 X-Downgraded: true),并在前端展示降级提示,否则会引发用户对产品质量的质疑。

五、总结

AI 服务网关的核心价值在于将大模型调用的不确定性——延迟波动、成本失控、供应商故障——收敛到可控范围内。本文方案的三个关键机制为:Token 预算限流(防止成本失控)、多模型动态路由(保障可用性)、语义降级缓存(降低调用频率)。落地时需重点配置四个参数:Token 预算上限(建议按租户日预算的 80% 设置)、限流窗口(建议 1 分钟滑动窗口)、语义缓存阈值(建议 0.92 起,根据业务调优)、路由健康检查间隔(建议 10 秒)。网关上线前必须进行供应商故障演练,验证降级链路的完整性。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐