大模型多租户隔离架构资源配额与推理调度的工程实践一、共享集群的吵闹邻居大模型服务的多租户困境企业内部将大模型服务作为平台能力共享给多个业务线不同业务线的调用模式差异巨大搜索团队在高峰期每秒发送数百次请求而内部工具团队可能每分钟只有几次调用。如果不做资源隔离高流量租户可能耗尽 GPU 推理资源导致低流量租户的请求排队超时。多租户隔离的核心需求是每个租户有独立的资源配额QPS 上限、Token 预算、并发数限制同时保证集群资源的整体利用率。静态配额分配简单但浪费资源动态调度灵活但实现复杂。工程上需要在隔离性和资源利用率之间找到平衡点。二、多租户隔离的架构与调度机制多租户隔离架构分为三层接入层的租户识别与配额校验、调度层的请求排队与优先级排序、推理层的资源分配与隔离执行。flowchart TD A[租户 A 请求] -- B[API 网关: 租户识别] A2[租户 B 请求] -- B A3[租户 C 请求] -- B B -- C[配额校验: QPS/Token/并发数] C --|配额充足| D[请求进入调度队列] C --|配额不足| E[返回 429 Too Many Requests] D -- F[优先级调度器] F -- G[租户 A 队列: 优先级 P1] F -- H[租户 B 队列: 优先级 P2] F -- I[租户 C 队列: 优先级 P3] G H I -- J[推理资源池: GPU 实例组] J -- K[结果返回 配额扣减]调度器采用加权公平队列WFQ算法每个租户的队列有权重调度器按权重比例从各队列中取请求保证租户间的资源分配公平性。当某个租户的队列空闲时其配额可以临时让渡给其他租户提高资源利用率。三、生产级多租户隔离的代码实现3.1 租户配额管理器Service Slf4j public class TenantQuotaManager { private final RedisTemplateString, String redisTemplate; /** * 校验租户配额QPS Token 预算 并发数 * 三维度独立校验任一维度超限即拒绝 */ public QuotaCheckResult checkQuota(String tenantId, int estimatedTokens) { TenantQuota quota getTenantQuota(tenantId); // 1. QPS 校验滑动窗口计数 boolean qpsOk checkQpsQuota(tenantId, quota.getMaxQps()); if (!qpsOk) { return QuotaCheckResult.rejected(QPS 超限, QPS_LIMIT); } // 2. Token 预算校验日累计 Token 消耗 boolean tokenOk checkTokenBudget(tenantId, estimatedTokens, quota.getDailyTokenBudget()); if (!tokenOk) { return QuotaCheckResult.rejected(Token 日预算超限, TOKEN_LIMIT); } // 3. 并发数校验当前在途请求数 boolean concurrencyOk checkConcurrency(tenantId, quota.getMaxConcurrency()); if (!concurrencyOk) { return QuotaCheckResult.rejected(并发数超限, CONCURRENCY_LIMIT); } return QuotaCheckResult.allowed(); } /** * QPS 校验基于 Redis 滑动窗口 */ private boolean checkQpsQuota(String tenantId, int maxQps) { String key quota:qps: tenantId; long windowMs 1000; // 1 秒窗口 Long count redisTemplate.opsForValue().increment(key); if (count ! null count 1) { redisTemplate.expire(key, Duration.ofMillis(windowMs)); } return count null || count maxQps; } /** * Token 预算校验日累计消耗 * 使用 Redis INCRBY 原子累加 */ private boolean checkTokenBudget(String tenantId, int estimatedTokens, long dailyBudget) { String key quota:tokens: tenantId : LocalDate.now(); Long consumed redisTemplate.opsForValue().increment(key, estimatedTokens); if (consumed ! null consumed estimatedTokens) { // 首次写入设置次日过期 redisTemplate.expire(key, Duration.ofDays(2)); } return consumed null || consumed dailyBudget; } /** * 并发数校验原子增减 */ private boolean checkConcurrency(String tenantId, int maxConcurrency) { String key quota:concurrency: tenantId; Long current redisTemplate.opsForValue().increment(key); return current null || current maxConcurrency; } public void releaseConcurrency(String tenantId) { String key quota:concurrency: tenantId; redisTemplate.opsForValue().decrement(key); } }3.2 加权公平队列调度器/** * 加权公平队列调度器 * 按租户权重比例从各队列中取请求保证公平性 */ Service public class WeightedFairQueueScheduler { private final MapString, TenantQueue tenantQueues new ConcurrentHashMap(); private final ExecutorService inferenceExecutor; /** * 提交推理请求到租户队列 */ public CompletableFutureInferenceResult submit(String tenantId, InferenceRequest request, int priority) { TenantQueue queue tenantQueues.computeIfAbsent(tenantId, id - new TenantQueue(id, getTenantWeight(id))); return queue.enqueue(request, priority); } /** * 调度循环按权重比例从各队列取请求 * 使用 Deficit Round Robin (DRR) 算法实现公平调度 */ PostConstruct public void startScheduling() { ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::scheduleRound, 0, 10, TimeUnit.MILLISECONDS); } private void scheduleRound() { for (TenantQueue queue : tenantQueues.values()) { // 每轮增加配额 权重 × 量子每次处理的请求数量单位 int quantum queue.getWeight() * 1; queue.addDeficit(quantum); while (queue.getDeficit() 0 !queue.isEmpty()) { InferenceRequest request queue.dequeue(); if (request ! null) { inferenceExecutor.submit(() - executeInference(request)); queue.addDeficit(-1); } else { break; } } } } private void executeInference(InferenceRequest request) { try { InferenceResult result doInference(request); request.getFuture().complete(result); } catch (Exception e) { request.getFuture().completeExceptionally(e); } } }3.3 租户配额动态调整/** * 租户配额动态调整基于历史使用量和业务优先级 * 每日定时评估支持手动覆盖 */ Service public class TenantQuotaAdjuster { /** * 根据历史使用量动态调整配额 * 核心逻辑使用率持续超过 80% 的租户可申请提额 * 使用率持续低于 20% 的租户配额可回收 */ Scheduled(cron 0 0 2 * * ?) // 每日凌晨 2 点执行 public void adjustQuotas() { ListTenantUsage usages collectDailyUsage(); for (TenantUsage usage : usages) { double qpsUsageRate (double) usage.getActualQps() / usage.getQuotaQps(); double tokenUsageRate (double) usage.getActualTokens() / usage.getQuotaTokens(); TenantQuota newQuota calculateNewQuota(usage, qpsUsageRate, tokenUsageRate); applyQuota(usage.getTenantId(), newQuota); } } private TenantQuota calculateNewQuota(TenantUsage usage, double qpsRate, double tokenRate) { TenantQuota current usage.getCurrentQuota(); // QPS 配额调整使用率 80% 时提升 20% 20% 时降低 30% int newQps current.getMaxQps(); if (qpsRate 0.8) { newQps (int) (current.getMaxQps() * 1.2); } else if (qpsRate 0.2) { newQps Math.max(10, (int) (current.getMaxQps() * 0.7)); } // Token 预算调整同理 long newTokenBudget current.getDailyTokenBudget(); if (tokenRate 0.8) { newTokenBudget (long) (current.getDailyTokenBudget() * 1.2); } else if (tokenRate 0.2) { newTokenBudget Math.max(100000L, (long) (current.getDailyTokenBudget() * 0.7)); } return TenantQuota.builder() .maxQps(newQps) .dailyTokenBudget(newTokenBudget) .maxConcurrency(current.getMaxConcurrency()) .build(); } }四、多租户隔离的边界分析与架构权衡配额粒度与资源利用率的矛盾。配额粒度越细如按 API 端点限流隔离性越好但资源碎片化越严重。粗粒度配额如按租户总量限流资源利用率高但可能出现某个 API 端点被其他端点挤占的情况。建议采用两级配额租户级总量 API 级预留。公平调度的延迟代价。DRR 算法保证了公平性但引入了调度延迟。当高优先级租户的请求在队列中等待时调度器仍需按权重为低优先级租户分配时间片。对于延迟敏感场景可以引入优先级抢占机制高优先级请求可以直接插队。配额超售与回收。为提高资源利用率可以允许配额超售如总配额超过集群容量的 120%前提是所有租户不会同时达到峰值。超售比例需要基于历史数据评估过高可能导致资源争抢。适用边界多租户隔离架构适合大模型服务平台的场景。对于单租户独占部署不需要复杂的隔离机制简单的 QPS 限流即可。五、总结大模型多租户隔离架构通过接入层配额校验、调度层加权公平队列和推理层资源分配实现了租户间的资源隔离与公平调度。落地时需关注配额粒度与资源利用率的平衡、公平调度的延迟代价、以及配额超售的风险控制。建议从静态配额开始逐步引入动态调整和优先级抢占机制。
大模型多租户隔离架构:资源配额与推理调度的工程实践
发布时间:2026/6/11 11:33:01
大模型多租户隔离架构资源配额与推理调度的工程实践一、共享集群的吵闹邻居大模型服务的多租户困境企业内部将大模型服务作为平台能力共享给多个业务线不同业务线的调用模式差异巨大搜索团队在高峰期每秒发送数百次请求而内部工具团队可能每分钟只有几次调用。如果不做资源隔离高流量租户可能耗尽 GPU 推理资源导致低流量租户的请求排队超时。多租户隔离的核心需求是每个租户有独立的资源配额QPS 上限、Token 预算、并发数限制同时保证集群资源的整体利用率。静态配额分配简单但浪费资源动态调度灵活但实现复杂。工程上需要在隔离性和资源利用率之间找到平衡点。二、多租户隔离的架构与调度机制多租户隔离架构分为三层接入层的租户识别与配额校验、调度层的请求排队与优先级排序、推理层的资源分配与隔离执行。flowchart TD A[租户 A 请求] -- B[API 网关: 租户识别] A2[租户 B 请求] -- B A3[租户 C 请求] -- B B -- C[配额校验: QPS/Token/并发数] C --|配额充足| D[请求进入调度队列] C --|配额不足| E[返回 429 Too Many Requests] D -- F[优先级调度器] F -- G[租户 A 队列: 优先级 P1] F -- H[租户 B 队列: 优先级 P2] F -- I[租户 C 队列: 优先级 P3] G H I -- J[推理资源池: GPU 实例组] J -- K[结果返回 配额扣减]调度器采用加权公平队列WFQ算法每个租户的队列有权重调度器按权重比例从各队列中取请求保证租户间的资源分配公平性。当某个租户的队列空闲时其配额可以临时让渡给其他租户提高资源利用率。三、生产级多租户隔离的代码实现3.1 租户配额管理器Service Slf4j public class TenantQuotaManager { private final RedisTemplateString, String redisTemplate; /** * 校验租户配额QPS Token 预算 并发数 * 三维度独立校验任一维度超限即拒绝 */ public QuotaCheckResult checkQuota(String tenantId, int estimatedTokens) { TenantQuota quota getTenantQuota(tenantId); // 1. QPS 校验滑动窗口计数 boolean qpsOk checkQpsQuota(tenantId, quota.getMaxQps()); if (!qpsOk) { return QuotaCheckResult.rejected(QPS 超限, QPS_LIMIT); } // 2. Token 预算校验日累计 Token 消耗 boolean tokenOk checkTokenBudget(tenantId, estimatedTokens, quota.getDailyTokenBudget()); if (!tokenOk) { return QuotaCheckResult.rejected(Token 日预算超限, TOKEN_LIMIT); } // 3. 并发数校验当前在途请求数 boolean concurrencyOk checkConcurrency(tenantId, quota.getMaxConcurrency()); if (!concurrencyOk) { return QuotaCheckResult.rejected(并发数超限, CONCURRENCY_LIMIT); } return QuotaCheckResult.allowed(); } /** * QPS 校验基于 Redis 滑动窗口 */ private boolean checkQpsQuota(String tenantId, int maxQps) { String key quota:qps: tenantId; long windowMs 1000; // 1 秒窗口 Long count redisTemplate.opsForValue().increment(key); if (count ! null count 1) { redisTemplate.expire(key, Duration.ofMillis(windowMs)); } return count null || count maxQps; } /** * Token 预算校验日累计消耗 * 使用 Redis INCRBY 原子累加 */ private boolean checkTokenBudget(String tenantId, int estimatedTokens, long dailyBudget) { String key quota:tokens: tenantId : LocalDate.now(); Long consumed redisTemplate.opsForValue().increment(key, estimatedTokens); if (consumed ! null consumed estimatedTokens) { // 首次写入设置次日过期 redisTemplate.expire(key, Duration.ofDays(2)); } return consumed null || consumed dailyBudget; } /** * 并发数校验原子增减 */ private boolean checkConcurrency(String tenantId, int maxConcurrency) { String key quota:concurrency: tenantId; Long current redisTemplate.opsForValue().increment(key); return current null || current maxConcurrency; } public void releaseConcurrency(String tenantId) { String key quota:concurrency: tenantId; redisTemplate.opsForValue().decrement(key); } }3.2 加权公平队列调度器/** * 加权公平队列调度器 * 按租户权重比例从各队列中取请求保证公平性 */ Service public class WeightedFairQueueScheduler { private final MapString, TenantQueue tenantQueues new ConcurrentHashMap(); private final ExecutorService inferenceExecutor; /** * 提交推理请求到租户队列 */ public CompletableFutureInferenceResult submit(String tenantId, InferenceRequest request, int priority) { TenantQueue queue tenantQueues.computeIfAbsent(tenantId, id - new TenantQueue(id, getTenantWeight(id))); return queue.enqueue(request, priority); } /** * 调度循环按权重比例从各队列取请求 * 使用 Deficit Round Robin (DRR) 算法实现公平调度 */ PostConstruct public void startScheduling() { ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::scheduleRound, 0, 10, TimeUnit.MILLISECONDS); } private void scheduleRound() { for (TenantQueue queue : tenantQueues.values()) { // 每轮增加配额 权重 × 量子每次处理的请求数量单位 int quantum queue.getWeight() * 1; queue.addDeficit(quantum); while (queue.getDeficit() 0 !queue.isEmpty()) { InferenceRequest request queue.dequeue(); if (request ! null) { inferenceExecutor.submit(() - executeInference(request)); queue.addDeficit(-1); } else { break; } } } } private void executeInference(InferenceRequest request) { try { InferenceResult result doInference(request); request.getFuture().complete(result); } catch (Exception e) { request.getFuture().completeExceptionally(e); } } }3.3 租户配额动态调整/** * 租户配额动态调整基于历史使用量和业务优先级 * 每日定时评估支持手动覆盖 */ Service public class TenantQuotaAdjuster { /** * 根据历史使用量动态调整配额 * 核心逻辑使用率持续超过 80% 的租户可申请提额 * 使用率持续低于 20% 的租户配额可回收 */ Scheduled(cron 0 0 2 * * ?) // 每日凌晨 2 点执行 public void adjustQuotas() { ListTenantUsage usages collectDailyUsage(); for (TenantUsage usage : usages) { double qpsUsageRate (double) usage.getActualQps() / usage.getQuotaQps(); double tokenUsageRate (double) usage.getActualTokens() / usage.getQuotaTokens(); TenantQuota newQuota calculateNewQuota(usage, qpsUsageRate, tokenUsageRate); applyQuota(usage.getTenantId(), newQuota); } } private TenantQuota calculateNewQuota(TenantUsage usage, double qpsRate, double tokenRate) { TenantQuota current usage.getCurrentQuota(); // QPS 配额调整使用率 80% 时提升 20% 20% 时降低 30% int newQps current.getMaxQps(); if (qpsRate 0.8) { newQps (int) (current.getMaxQps() * 1.2); } else if (qpsRate 0.2) { newQps Math.max(10, (int) (current.getMaxQps() * 0.7)); } // Token 预算调整同理 long newTokenBudget current.getDailyTokenBudget(); if (tokenRate 0.8) { newTokenBudget (long) (current.getDailyTokenBudget() * 1.2); } else if (tokenRate 0.2) { newTokenBudget Math.max(100000L, (long) (current.getDailyTokenBudget() * 0.7)); } return TenantQuota.builder() .maxQps(newQps) .dailyTokenBudget(newTokenBudget) .maxConcurrency(current.getMaxConcurrency()) .build(); } }四、多租户隔离的边界分析与架构权衡配额粒度与资源利用率的矛盾。配额粒度越细如按 API 端点限流隔离性越好但资源碎片化越严重。粗粒度配额如按租户总量限流资源利用率高但可能出现某个 API 端点被其他端点挤占的情况。建议采用两级配额租户级总量 API 级预留。公平调度的延迟代价。DRR 算法保证了公平性但引入了调度延迟。当高优先级租户的请求在队列中等待时调度器仍需按权重为低优先级租户分配时间片。对于延迟敏感场景可以引入优先级抢占机制高优先级请求可以直接插队。配额超售与回收。为提高资源利用率可以允许配额超售如总配额超过集群容量的 120%前提是所有租户不会同时达到峰值。超售比例需要基于历史数据评估过高可能导致资源争抢。适用边界多租户隔离架构适合大模型服务平台的场景。对于单租户独占部署不需要复杂的隔离机制简单的 QPS 限流即可。五、总结大模型多租户隔离架构通过接入层配额校验、调度层加权公平队列和推理层资源分配实现了租户间的资源隔离与公平调度。落地时需关注配额粒度与资源利用率的平衡、公平调度的延迟代价、以及配额超售的风险控制。建议从静态配额开始逐步引入动态调整和优先级抢占机制。