news 2026/6/4 7:43:02

【Token限流计费系列】第3讲:大模型成“裸奔”的裸机!企业级多租户网关架构实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Token限流计费系列】第3讲:大模型成“裸奔”的裸机!企业级多租户网关架构实战

【Token限流计费系列】第3讲:大模型成“裸奔”的裸机!企业级多租户网关架构实战

前言

大模型平台如果只做简单 API 转发,就很容易出现租户身份不清、上下文串用、配额失控和资源争抢。尤其在私有化部署或多业务线共用模型池时,缺少企业级网关会让平台暴露在性能与数据安全双重风险下。

本文围绕企业级多租户网关实践,说明如何通过身份认证、配额控制、上下文隔离和动态路由构建可控的大模型访问入口。

一、底层原理

1.1 核心机制

做企业级网关,核心就三件事:认身份、管配额、保隔离。

认身份靠的是 API Key 和 JWT 令牌。

管配额得用令牌桶算法,防止单个租户把资源吃干抹净。

保隔离则是大模型场景的特殊要求。

每个租户的对话上下文(Context)必须严格分开。

A 公司的老板问“今年财报咋样”,不能把 B 公司的数据吐出来。

我们设计了这样一套流量处理链路:

sequenceDiagram participant Client as 租户客户端 participant Gateway as 网关接入层 participant Auth as 鉴权与限流中心 participant Router as 动态路由引擎 participant LLM as 大模型集群 Client->>Gateway: 发起请求 (携带 Tenant-ID) Gateway->>Auth: 校验身份与配额 Auth-->>Gateway: 放行或拒绝 Gateway->>Router: 根据租户策略路由 Router->>LLM: 转发请求 (隔离上下文) LLM-->>Router: 返回流式响应 Router-->>Gateway: 聚合数据 Gateway-->>Client: 返回最终结果

这套架构的优势在于“无感隔离”。

业务代码不需要知道租户是谁,网关在入口处就把标签打好了。

通过 ThreadLocal 将租户信息透传到整个调用链。

这样 downstream 的服务只管处理逻辑,不用操心安全问题。

1.2 与同类方案的对比

市面上有不少现成的网关,但直接拿来用往往水土不服。

我们对比了三种主流方案,看看差异在哪。

方案隔离能力扩展性适用场景
Spring Cloud Gateway弱 (需二次开发)高 (Java 生态)内部微服务治理
APISIX中 (插件化)中 (Lua 脚本)公网 API 管理
自研大模型网关强 (深度定制)高 (贴合业务)企业级 AI 中台

自研网关虽然初期投入大,但在“上下文隔离”和"Token 计费”上更精准。

毕竟大模型的 Token 就是真金白银,不能靠通用网关粗略估算。

二、快速上手

咱们先用 3 分钟写个最小可运行的“租户识别器”。

这个过滤器负责从 Header 里提取租户信息,并放入上下文。

package com.douli.gateway.filter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 租户识别过滤器 * 作用:在请求入口处提取租户标识,为后续隔离做准备 */ @Component public class TenantIdentificationFilter implements GlobalFilter, Ordered { // 定义 Header 中租户标识的键名 private static final String TENANT_ID_HEADER = "X-Tenant-Id"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取原始请求对象 ServerHttpRequest request = exchange.getRequest(); // 2. 从请求头中读取租户 ID // 注意:生产环境这里必须做非空校验,防止恶意请求 String tenantId = request.getHeaders().getFirst(TENANT_ID_HEADER); if (tenantId == null || tenantId.isEmpty()) { // 3. 如果没有租户 ID,直接拒绝,返回 401 // 这里可以封装统一的错误响应体 return exchange.getResponse().setComplete(); } // 4. 将租户信息“贴”到请求对象上,传递到下游 // 使用 mutate 构建新的请求,避免修改原始对象 ServerHttpRequest modifiedRequest = request.mutate() .header("X-Internal-Tenant", tenantId) .build(); // 5. 继续执行过滤器链 // 此时 downstream 服务可以通过获取 Header 拿到租户信息 return chain.filter(exchange.mutate().request(modifiedRequest).build()); } @Override public int getOrder() { // 设置优先级,确保在鉴权过滤器之后执行 return Ordered.HIGHEST_PRECEDENCE + 10; } }

这段代码看着简单,却是整个隔离体系的基石。

它保证了后续所有服务看到的请求,都带着“身份证”。

三、核心 API / 深水区

3.1 核心方法速查

在网关层,我们封装了几个核心工具类,方便业务方调用。

方法名功能描述适用场景
TenantContext.set()设置当前线程的租户上下文过滤器入口
TenantContext.get()获取当前租户 ID业务逻辑中
RateLimiter.check()检查租户剩余配额限流判断
ContextManager.clear()清理线程上下文防止内存泄漏

3.2 生产级配置

光有代码不行,配置得跟上。

特别是限流策略,不能一刀切。

我们要支持按租户配置不同的 QPS 阈值。

比如 VIP 客户给 1000 QPS,普通客户只给 100 QPS。

这需要对接 Redis 实现分布式计数。

# application.yml 配置示例 tenant-limit: enabled: true redis-host: 192.168.1.100 default-qps: 50 # 默认阈值 vip-qps: 500 # VIP 阈值 burst-size: 20 # 突发流量缓冲

异常处理也很关键。

大模型接口通常响应时间长,容易超时。

网关层必须设置独立的超时控制。

比如上游服务超时 30 秒,网关不能傻等,得主动断开。

3.3 高级定制

有些场景需要“动态路由”。

比如租户 A 用的是 GPT-4,租户 B 用的是开源的 Llama3。

网关得根据租户等级,把流量转发到不同的模型集群。

我们在路由配置里加了“模型标签”。

// 伪代码:根据租户等级选择模型集群 String modelCluster = tenantService.getModelCluster(tenantId); URI targetUri = UriComponentsBuilder.fromUriString(modelCluster).build().toUri();

这样就能实现“千人千面”的模型调度。

四、实战演练

光说不练假把式。

咱们模拟一个真实场景:两个租户同时发起高并发请求。

租户 A 是 VIP,租户 B 是普通用户。

我们编写一个测试类,模拟并发压测。

package com.douli.gateway.test; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; import java.time.Duration; /** * 多租户限流实战测试 * 模拟 10 个并发请求,观察限流效果 */ @SpringBootTest public class TenantRateLimitTest { @Autowired private TenantRateLimitService rateLimitService; @Test public void testConcurrentAccess() { // 1. 定义租户 A (VIP) 和 租户 B (普通) String tenantA = "vip_client_001"; String tenantB = "normal_client_002"; // 2. 模拟并发请求流 // 使用 Flux 创建 10 个任务,每隔 100 毫秒发送一个 Flux<String> requestStream = Flux.interval(Duration.ofMillis(100)) .take(10) .map(i -> i % 2 == 0 ? tenantA : tenantB); // 3. 验证限流结果 // 预期:租户 A 全部通过,租户 B 部分被限流 StepVerifier.create( requestStream.flatMap(tenantId -> rateLimitService.tryAcquire(tenantId) ) ) .expectSubscription() .expectComplete() .verify(); System.out.println("测试结束,请查看控制台日志中的限流统计"); } }

运行结果会显示,租户 B 的请求有部分返回了429 Too Many Requests

而租户 A 的请求则畅通无阻。

这就验证了隔离和限流策略生效了。

五、避坑指南与最佳实践

这几年踩过的坑,希望能帮你省点头发。

💡技巧:ThreadLocal 的清理
我们在TenantContext里用了ThreadLocal存储租户信息。

一定要在过滤器链的最后,或者finally块里调用remove()

否则线程池复用线程时,会把上一个请求的租户信息带过来。

这就成了“数据串味”的元凶。

⚠️警告:大模型响应慢
大模型生成文本需要时间,连接占用久。

网关的连接池不能设太小,否则容易耗尽。

建议配置独立的连接池,专门用于大模型路由。

同时开启“熔断”,当模型集群响应超过阈值,直接快速失败。

推荐:全链路追踪
多租户环境下,排查问题很难。

必须接入 SkyWalking 或 Zipkin。

Tenant-ID作为 Trace 标签传下去。

这样查日志时,直接搜租户 ID,就能看到该租户的所有调用链。

六、综合实战演示

最后,咱们把前面提到的逻辑串起来。

写一个完整的TenantGatewayService,包含鉴权、限流、路由。

package com.douli.gateway.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; /** * 企业级大模型网关核心服务 * 整合了鉴权、限流、上下文管理 */ @Service public class TenantGatewayService { private static final Logger log = LoggerFactory.getLogger(TenantGatewayService.class); // 模拟限流器 private final RateLimiter rateLimiter; // 模拟租户信息数据库 private final TenantRepository tenantRepo; public TenantGatewayService(RateLimiter rateLimiter, TenantRepository tenantRepo) { this.rateLimiter = rateLimiter; this.tenantRepo = tenantRepo; } /** * 处理大模型请求的核心入口 * @param request 封装了租户信息的请求对象 * @return 模型响应结果 */ public Mono<String> handleLLMRequest(LLMRequest request) { // 1. 提取租户 ID String tenantId = request.getTenantId(); // 2. 设置线程上下文,确保后续逻辑能获取到租户信息 // 注意:这里使用了 try-finally 保证清理 TenantContext.set(tenantId); try { // 3. 校验租户是否存在且可用 if (!tenantRepo.exists(tenantId)) { log.warn("非法租户尝试访问:{}", tenantId); return Mono.error(new SecurityException("租户不存在")); } // 4. 执行限流检查 // 如果超过配额,直接抛出异常,不再转发给大模型 if (!rateLimiter.tryAcquire(tenantId)) { log.warn("租户 {} 请求超过配额,已限流", tenantId); return Mono.error(new RateLimitException("请求过于频繁,请稍后重试")); } // 5. 执行真正的模型调用 // 这里模拟调用下游模型服务 return callModelProvider(request.getPrompt()) .doOnSuccess(response -> log.info("租户 {} 调用成功", tenantId)) .doOnError(e -> log.error("租户 {} 调用失败", tenantId, e)); } finally { // 6. 关键步骤:清理上下文,防止内存泄漏和数据污染 TenantContext.clear(); } } private Mono<String> callModelProvider(String prompt) { // 模拟异步调用大模型接口 return Mono.just("这是模型生成的回复内容..."); } }

这段代码涵盖了从入口到清理的全过程。

特别是try-finally块里的TenantContext.clear(),这是生产环境必须有的动作。

七、总结

企业级大模型网关,核心不是“通”,而是“稳”和“安”。

多租户隔离是底线,限流熔断是保障。

别为了追求功能大而全,忽略了上下文清理这种细节。

技术架构就像盖房子,地基打不牢,楼盖越高越危险。

把每个租户的流量管好,你的中台才能真正撑得起业务。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 7:42:57

百度网盘提取码智能获取工具终极指南:3秒破解资源下载密码

百度网盘提取码智能获取工具终极指南&#xff1a;3秒破解资源下载密码 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 还在为百度网盘分享链接的提取码而烦恼吗&#xff1f;baidupankey作为一款百度网盘提取码智能获取工具&am…

作者头像 李华
网站建设 2026/6/4 7:35:00

PHP图像处理与GD库实战

PHP图像处理与GD库实战PHP的GD库提供了图像处理功能。虽然不如图形处理专业软件强大&#xff0c;但处理常见的图片需求绰绰有余。GD库可以创建图片、处理已有图片、添加文字水印、生成缩略图等。先检查GD库是否安装。php// 检查GD库 if (!extension_loaded(gd)) { die("GD…

作者头像 李华
网站建设 2026/6/4 7:33:06

GLM-5开源代码模型如何重构程序员工作流

1. 项目概述&#xff1a;当一个开源大模型开始“写代码像人”&#xff0c;程序员的日常就变了“智谱GLM-5这次开源&#xff0c;让高级程序员也危险了……”——这句话在技术圈刷屏那天&#xff0c;我正蹲在客户现场调一个遗留系统的Java线程池死锁。手机弹出消息&#xff0c;没…

作者头像 李华
网站建设 2026/6/4 7:29:00

从收藏吃灰到高效执行:2026年度高内聚代码灵感仓储工具深度解析

很多独立开发者和科研团队都遇到过类似的尴尬&#xff1a;在 GitHub 上看到极其惊艳的开源项目或算法架构&#xff0c;随手点了 Star&#xff0c;可几个月后自己要用时&#xff0c;却怎么也找不到&#xff1b;或者在参与数学建模、科创比赛、编写毕业设计时&#xff0c;收集了一…

作者头像 李华
网站建设 2026/6/4 7:25:14

基于Arduino Uno自制赛车模拟器:从电位器到游戏控制器的完整指南

1. 项目概述与核心思路一直想给自己攒一套赛车模拟器&#xff0c;但市面上的成品要么价格劝退&#xff0c;要么功能固定缺乏折腾的乐趣。作为一个电子爱好者&#xff0c;我始终相信&#xff0c;最趁手的工具往往是自己做出来的。这次的项目&#xff0c;就是用一块几乎人手一块的…

作者头像 李华