大模型推理网关——从负载均衡到故障注入的完整设计
前言在上一篇文章中我们实现了AI课程问答助手它能让用户通过SSE流式调用大模型。但这个方案有一个隐含的问题API密钥直接暴露在后端代码中单点调用没有容灾。一旦大模型API限流、宕机或者某个KEY耗尽额度整个问答功能就挂了。而且如果一个KEY被多人共享很快就会被限流。我在蚂蚁集团AI Coding笔试中遇到了类似的问题——设计一个面向大模型推理场景的HTTP网关支持智能路由、负载均衡、健康检查、限流和容灾。本文完整复盘这个网关的设计和实现。本文核心问题为什么大模型API需要一个网关不能直接调吗负载均衡的策略怎么选轮询 vs 权重 vs 最少连接Token限流是怎么实现的滑动窗口和令牌桶有什么区别API密钥怎么管理多KEY场景下如何分配请求健康检查怎么设计怎么判断后端API是否健康故障注入压测验证了什么怎么保证系统的稳定性WebFlux响应式编程在这里起到什么作用如果生产环境部署还需要加什么读完本文你将掌握大模型API网关的完整设计思路以及高并发场景下的负载均衡与容灾策略。一、为什么需要网关——从单KEY到多KEY的困境疑问大模型API不就是发个HTTP请求吗加个网关是不是过度设计回答一个KEY调用大模型API确实不需要网关。但当你面临多KEY管理、限流、容灾时网关就成为了必需品。1.1 无网关时的典型问题单KEY直连 客户端A ──→ API KEY #1 ──→ 大模型API 客户端B ──→ API KEY #1 ──→ 大模型API ← 同一个KEY被多个客户端共享 客户端C ──→ API KEY #1 ──→ 大模型API 两个核心痛点 ❌ 单点故障KEY被限流或过期所有客户端全挂 ❌ 限流不透明谁也不知道当前还可以发多少请求什么时候会被限流一旦KEY达到RPM或者TPM上限所有客户端都会收到429错误。没有一个地方能统一看到所有KEY的使用情况和剩余配额排查也需要在各个客户端和服务端之间来回跳。1.2 加网关后的架构网关 客户端A ──┐ ┌──→ API KEY #1 ──→ 大模型API 客户端B ──┤ ──→ 统一网关 ──→ ├──→ API KEY #2 ──→ 大模型API 客户端C ──┘ └──→ API KEY #3 ──→ 大模型API 三个问题的解决 ✅ 负载均衡请求分发到不同KEY单一KEY不会被打满 ✅ 健康检查某个KEY失效自动踢出请求不会打到故障节点 ✅ 统一限流网关层做限流请求频率对所有KEY统一可见网关成为所有API调用的唯一入口客户端不需要知道有几个KEY、每个KEY还剩多少配额。KEY的增减对客户端透明任何一个KEY失效都不会影响整体服务。1.3 网关的职责职责做什么不用网关的后果负载均衡把请求分到多个API KEY某个KEY被打满其他KEY闲着健康检查自动检测并摘除失效KEY请求被发到已过期的KEY返回错误限流控制保护后端API不被超额调用触发限流导致所有请求被拒鉴权安全前端不暴露真实API KEYKEY泄露风险高账单可能失控监控告警实时统计每个KEY的成功率和延迟无法及时发现问题排查靠猜二、整体架构设计疑问网关的内部结构是什么样的请求进来后经过了哪些处理回答网关内部采用责任链模式请求依次经过鉴权→限流→路由→负载均衡→调用。┌───────────────────────────┐ │ 大模型推理网关 │ │ │ 客户端请求 ──→ │ ┌─────────────────────┐ │ │ │ 1. 鉴权层 │ │ │ │ Token/APIKey验证 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 2. 限流层 │ │ │ │ 滑动窗口/令牌桶 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 3. 路由层 │ │ │ │ 根据模型选择后端 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 4. 负载均衡层 │ │ │ │ 权重轮询/最少连接 │ │ │ └──────────┬──────────┘ │ │ ↓ │ │ ┌─────────────────────┐ │ │ │ 5. 健康检查 │ │ │ │ 定期探活/自动摘除 │ │ │ └──────────┬──────────┘ │ │ ↓ │ └─────────────┼──────────────┘ ↓ 大模型API集群 ┌───────────────────┐ │ KEY1 KEY2 KEY3 │ └───────────────────┘各层职责鉴权层验证客户端是否有权限调用。前端只需传网关颁发的轻量Token不需要知道真实API KEY限流层控制每个客户端、每个时间窗口内的请求次数。滑动窗口计算精确可控路由层根据请求模型名如GPT-3.5、GPT-4选择对应的后端KEY池。不同模型有独立的配额负载均衡层在同一模型的多个KEY中分发请求权重轮询或最少连接数决定最终走向健康检查层后台定时探活所有KEY发现故障自动摘除恢复后自动加回三、负载均衡策略——请求该发给哪个KEY疑问多个API KEY之间怎么分配请求三种负载均衡策略各自适合什么场景回答核心策略有三种——轮询、权重、最少连接。大模型API场景下权重轮询最实用。3.1 三种策略对比策略做法适用场景大模型API场景普通轮询按顺序分配每个KEY轮流处理所有KEY性能完全相同额度相同的KEY权重轮询按权重比例分配KEY配额不同✅ 最常用最少连接数发给当前处理请求最少的KEY每个请求耗时差异大不适用3.2 为什么大模型API场景选权重轮询大模型API的限流是基于RPM每分钟请求数和TPM每分钟Token数不同KEY的配额通常不一样。权重轮询可以按配额比例分配请求——额度大的KEY设更高权重额度小的KEY设更低权重最大化利用总的配额池。最少连接数在这个场景中用处有限大模型API的响应时间主要取决于模型生成速度和输入长度不同请求的耗时差异可达数倍。但连接数不反映实际负载——一个等待长回答的连接和一个短回答的连接对KEY的消耗完全不同。3.3 权重轮询实现publicclassWeightedRoundRobinLoadBalancer{privatefinalListApiKeyEndpointendpoints;privatefinalAtomicIntegerpositionnewAtomicInteger(0);// 每个ENDPOINT按权重展开成多个槽位// 如 KEY1(权重3) KEY2(权重1) → 槽位KEY1, KEY1, KEY1, KEY2privatefinalListApiKeyEndpointweightedList;publicWeightedRoundRobinLoadBalancer(ListApiKeyEndpointendpoints){this.endpointsendpoints;this.weightedListendpoints.stream().flatMap(ep-IntStream.range(0,ep.getWeight()).mapToObj(i-ep)).collect(Collectors.toList());}publicApiKeyEndpointnext(){// 轮询取槽位中的下一个权重高的KEY自然出现更多次intindexposition.getAndUpdate(i-(i1)%weightedList.size());returnweightedList.get(index);}}四、Token限流——保护后端API不被超额调用疑问限流是怎么做的为什么需要限流回答限流是保护后端大模型API不被意外超额调用的防线。大模型API通常是按KEY做RPM和TPM限流——超出了就直接拒绝。网关在请求到达后端之前先做一层限流可以提前拦截超额请求避免后端拒绝后仍需消耗请求配额。4.1 两种主流算法对比算法做法优势劣势固定窗口统计每分钟内的请求数实现简单边界突发会放过超额的瞬时流量限流精度不够滑动窗口统计最近60秒内的请求数精确平滑无边界效应内存消耗略高需要维护时间窗口内的所有请求时间戳令牌桶按固定速率放入令牌请求需获取令牌允许突发在令牌存量内处理流量弹性好突发过大时堆积的请求积压反而影响整体稳定性4.2 为什么大模型API网关选滑动窗口大模型API的特点请求本身成本高且后端API本身已经做了限流保护。网关侧的限流主要目的是防止某个客户端或KEY意外超配而不是应对突发流量网关本身不需要弹性突发能力。所以选择滑动窗口——精度高、误判少能精确反映最近一个完整窗口周期内的请求密度。4.3 滑动窗口实现publicclassSlidingWindowRateLimiter{// 窗口大小60秒privatefinallongwindowSizeInMillis60_000;// 最大请求数100次/分钟privatefinalintmaxRequests100;// 存储每个请求的时间戳privatefinalDequeLongrequestTimestampsnewConcurrentLinkedDeque();publicsynchronizedbooleanallowRequest(){longnowSystem.currentTimeMillis();// 移除窗口外的旧时间戳while(!requestTimestamps.isEmpty()now-requestTimestamps.peekFirst()windowSizeInMillis){requestTimestamps.pollFirst();}// 检查当前窗口内是否还有配额if(requestTimestamps.size()maxRequests){requestTimestamps.offerLast(now);returntrue;// 允许通过}returnfalse;// 限流}}4.4 分层限流设计客户端级限流每个客户端每分钟最多N次 ↓ 超过 → 返回429 请求过于频繁 API KEY级限流每个KEY每分钟最多M次 ↓ 超过 → 切换KEY或排队等待 后端API级限流大模型服务商的原生限流 ↓ 超过 → 触发降级策略每层限流之间独立计算。客户端级防止单个用户占用所有配额API KEY级防止单个KEY被打爆后端API级是所有保护都失效后的最后兜底。五、健康检查——自动发现和摘除故障KEY疑问怎么知道一个API KEY失效了发现后怎么处理回答健康检查的核心是定期探活 自动摘除。用两种探活方式组合——主动探测为主被动标记为辅助。5.1 两种探活方式方式怎么做优势劣势主动探活定时发送测试请求故障发现快能在请求到达前同时检查所有KEY消耗少量配额当探活成本被动标记根据实际请求结果判断无额外成本反映真实调用质量只有当请求发生时才能检测到故障5.2 故障判定和恢复策略publicclassHealthChecker{// 每个KEY的状态连续失败次数privatefinalMapString,AtomicIntegerfailureCountnewConcurrentHashMap();// 连续失败阈值privatestaticfinalintFAILURE_THRESHOLD3;// 定期主动探活Scheduled(fixedRate30_000)// 每30秒publicvoidcheckHealth(){for(ApiKeyEndpointendpoint:endpoints){try{// 发送一个轻量探测请求booleanhealthyprobe(endpoint);if(healthy){// 恢复重新加入负载均衡endpoint.setHealthy(true);failureCount.get(endpoint.getKey()).set(0);}else{// 连续失败达到阈值 → 摘除intfailuresfailureCount.get(endpoint.getKey()).incrementAndGet();if(failuresFAILURE_THRESHOLD){endpoint.setHealthy(false);alertService.send(API KEY失效: endpoint.getKey());}}}catch(Exceptione){// 探活本身网络异常也计为失败handleProbeFailure(endpoint);}}}}关键设计细节不是失败一次就摘除——网络抖动可能造成偶发失败。连续3次失败才摘除同时发送告警通知。恢复后自动加回健康池整个流程无需人工介入。探活请求用极短的Prompt如回复OK消耗Token极少几乎不影响KEY的可用配额。六、故障注入压测——主动制造故障验证稳定性疑问压测不只是用正常流量打吗故障注入是什么回答故障注入是主动制造故障场景验证系统在异常情况下的行为。正常流量压测测的是系统能扛多少故障注入测的是系统在坏了之后能不能自愈。6.1 四种压测场景设计场景模拟什么验证什么正常流量常规请求持续进入系统基本稳定性基线性能数据热点前缀大量请求集中在某个模型负载均衡是否均匀分发过载请求量超过正常峰值限流是否生效能否保护后端API故障注入主动让某个KEY返回错误健康检查能否自动摘除和恢复6.2 故障注入实现RestControllerRequestMapping(/admin/fault-injection)publicclassFaultInjectionController{// 故障注入开关privatefinalMapString,FaultConfigfaultConfigsnewConcurrentHashMap();// 注入故障让某个KEY开始返回错误PostMapping(/inject)publicStringinjectFault(RequestParamStringkey,RequestParamFaultTypetype){faultConfigs.put(key,newFaultConfig(type,true));return故障已注入: key → type;}// 取消故障PostMapping(/clear)publicStringclearFault(RequestParamStringkey){faultConfigs.remove(key);return故障已清除: key;}}// 在负载均衡器中嵌入故障注入逻辑publicApiKeyEndpointnext(){ApiKeyEndpointendpointweightedList.get(index);// 检查是否被注入了故障FaultConfigfaultfaultConfigs.get(endpoint.getKey());if(fault!nullfault.isActive()){// 模拟故障直接让这个KEY看起来不可用endpoint.setHealthy(false);}returnendpoint;}6.3 故障注入压测的验证闭环1. 正常状态下所有KEY健康请求均匀分发 2. 注入故障KEY #2 开始返回500错误 3. 验证健康检查在30秒内将KEY #2标记为不健康 4. 验证后续请求自动跳过KEY #2 5. 清除故障KEY #2 恢复正常 6. 验证健康检查检测到恢复将KEY #2重新加入负载均衡 7. 验证请求再次均匀分发到所有KEY一个完整的自愈周期被验证通过。压测数据会记录故障注入时刻和系统恢复时刻的时间差作为SLA指标的一部分。七、WebFlux响应式编程——为什么不用传统的Tomcat疑问网关为什么要用WebFlux用Spring MVC不行吗回答网关的核心工作就是转发请求——大量时间花在等待后端响应上而不是CPU计算。WebFlux的非阻塞I/O模型在这种场景下天然优于传统的每请求一线程模型。7.1 网关场景的特点网关本身不处理业务逻辑——不查数据库、不做复杂计算、不调用多个服务做聚合。它只是收到请求→检查鉴权→选择KEY→转发→等待后端响应→返回给客户端。工作线程几乎所有时间都在等待大模型API的响应——生成500字的回答通常需要2-3秒。这2-3秒内线程只是阻塞着等待网络数据CPU完全空闲。7.2 传统Tomcat vs WebFlux传统Tomcat每请求一线程 200个并发请求 → 200个工作线程 每个线程等待大模型API 2秒阻塞等待 → 200个线程全在等待线程池可能被打满 → 新请求只能排队等线程释放 WebFlux非阻塞I/O 200个并发请求 → 可能只需要几个EventLoop线程 每个请求注册一个回调线程继续处理其他请求 大模型API返回数据时 → 触发回调 → 返回客户端 → 线程数固定不会因为等待而膨胀7.3 WebFlux实现RestControllerRequestMapping(/api/v1)publicclassLLMGatewayController{AutowiredprivateLLMGatewayServicegatewayService;PostMapping(/chat/completions)publicMonoResponseEntityStringchatCompletion(RequestBodyChatRequestrequest){returngatewayService.route(request.getModel())// 异步选择KEY.flatMap(endpoint-gatewayService.callLLM(endpoint,request)// 异步调用).map(response-ResponseEntity.ok(response)).timeout(Duration.ofSeconds(30))// 超时控制.onErrorReturn(ResponseEntity.status(502).body({\error\:\所有API KEY不可用请稍后再试\}));}}八、生产环境还需要什么疑问这个网关现在是Demo级别生产环境部署还需要完善什么回答五个方面的升级——配置管理、可观测性、安全防护、灰度路由和统一成本统计。升级项Demo方案生产方案配置存储内存/配置文件配置中心(Nacos)动态刷新限流存储单机内存DequeRedis集中存储日志与监控控制台日志ELK Prometheus Grafana安全防护无SQL注入检测、请求体限制、TLS加密KEY配额可视化无每个KEY的使用率统计和配额预警面板灰度路由无按比例将请求分流到不同模型或KEY池成本统计无按KEY和模型维度的Token消耗统计和账单面板总结网关的作用多KEY负载均衡、统一限流、健康检查、鉴权隔离——让大模型API从单点变成服务负载均衡选权重轮询大模型API KEY配额不同按权分布最合理。加权轮询以配额比例为权重将请求按比例分给高配额和低配额KEY限流选滑动窗口大模型API请求成本高需要精确控制防止超额。网关不依赖令牌桶的弹性突发能力精度优先于弹性健康检查的核心是自动自愈连续3次失败摘除、恢复后自动加回、全程告警通知。人工无需介入故障注入验证自愈闭环注入故障→节点摘除→请求转移→故障恢复→节点加回→请求恢复。全流程压测通过才算通过WebFlux解决网关的根本痛点网关线程大量时间在等待后端响应非阻塞I/O避免线程膨胀EventLoop线程复用效率远超传统每请求一线程模型生产化需补齐监控和配置限流集中化、指标可视化、安全防护、动态配置成为上线前必须完成的配套专栏预告本文是“AI应用实战”专栏的第二篇。从第一篇的RAG课程问答助手到本篇的推理网关两个项目覆盖了AI应用开发的两个核心方向——业务集成怎么把AI嵌入到真实产品中和基础设施怎么让AI调用稳定可靠。