10.3 自动扩缩容与负载均衡:多副本下的请求分发
HPA/KEDA 扩缩容策略(CPU/QPS/队列/KV缓存)、前缀感知路由与缓存感知负载均衡、API Key 认证与安全加固
单副本服务不了真实流量。多副本之后出现两个新问题:副本数怎么跟着负载变(扩缩容)、请求怎么分到副本(负载均衡)。LLM 服务的特殊之处:副本不是无状态的——每个副本有自己的 KV 缓存,请求分错了地方,缓存全 miss。这一节解决这两个问题,外加一个绕不开的安全话题。
📑 目录
- 1. 扩缩容:HPA 与 KEDA
- 2. LLM 扩缩容的正确指标:为什么 CPU 不够
- 3. 负载均衡:从随机到缓存感知
- 4. 前缀感知路由:把同类请求导到同一副本
- 5. 安全加固:别让 API 裸奔
- 📝 总结
- 🎯 自我检验清单
- 📚 参考资料
1. 扩缩容:HPA 与 KEDA
1.1 HPA:K8s 内置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: vllm
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80 # CPU 利用率(vLLM 场景有坑,见第 2 节)
1.2 KEDA:指标驱动扩缩容
KEDA 支持任意 Prometheus 指标,是 LLM 服务的推荐方案:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: vllm-keda
spec:
scaleTargetRef: { name: vllm }
minReplicaCount: 1
maxReplicaCount: 10
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: | # 按队列长度扩缩容
vllm:num_requests_waiting / (vllm:num_requests_running + vllm:num_requests_waiting)
threshold: "0.5"
📌 关键点:HPA 与 KEDA 的差别是指标源:HPA 只认 K8s 内置指标(CPU/内存/自定义),KEDA 能直接吃 Prometheus 查询——LLM 场景的扩缩容信号(队列深度、KV 水位)都是 Prometheus 指标,KEDA 是正解。Production Stack 也内置了 KEDA 支持。
2. LLM 扩缩容的正确指标:为什么 CPU 不够
2.1 CPU 指标的失效原因
| 指标 | LLM 场景的问题 |
|---|---|
| CPU 利用率 | 算力在 GPU——CPU 满载前 GPU 早就饱和了;且长请求占着 GPU 但 CPU 空闲(等待中) |
| 内存 | 分配后基本不变,反映不了负载 |
| QPS | 有用,但区分不了”大请求”(长 prompt 长输出)与”小请求” |
2.2 LLM 场景的有效信号(按优先级)
| 信号 | PromQL | 含义 |
|---|---|---|
| 队列深度 | num_requests_waiting / (running + waiting) | 排队比 > 阈值 → 扩容(最直接) |
| KV 缓存水位 | kv_cache_usage_perc > 0.9 | 容量打满 → 扩容/降负载 |
| TTFT 分位数 | TTFT p95 超 SLO | 服务变慢 → 扩容(SLO 驱动,最稳) |
| 请求量 | rate(prompt_tokens[5m]) | 负载预测(配合 cron 扩缩容) |
2.3 扩缩容的两个陷阱
陷阱 1:模型加载 1-10 分钟 → 扩容来不及
对策:预留"预热副本"(minReplicas 覆盖日常高峰)+ 预下载(10.1 第 4 节)
KEDA 支持"按时刻表预扩容"(cron 触发器),午高峰前 10 分钟提前扩
陷阱 2:缩容太快 → 缓存全丢
对策:缩容冷却期拉长(KEDA cooldownPeriod,默认 300s)
销毁前优雅排空(terminationGracePeriodSeconds 覆盖正在跑的请求)
📌 关键点:LLM 扩缩容的本质是”提前量”博弈:扩容有 1-10 分钟加载延迟,缩容会丢 KV 缓存。所以生产策略是慢扩慢缩:KEDA 触发条件用”持续 N 分钟超过阈值”,cooldown 给足,配合 cron 预扩容覆盖已知高峰。
3. 负载均衡:从随机到缓存感知
3.1 普通 LB 的问题
普通 LB(round_robin / random / 最小连接):
请求均匀分散到所有副本
→ 相同 System Prompt 的请求落在不同副本 → 每个副本都 miss → 前缀缓存 0% 命中
→ 全量 prefill,TTFT 高企,吞吐打折
3.2 vLLM 官方 router:缓存感知负载均衡
vLLM 官方 router 项目(Production Stack 的默认路由)提供 5 种策略:
| 策略 | 特点 | 适用 |
|---|---|---|
round_robin | 顺序分发 | 通用、最均匀 |
random | 随机选择 | 简单场景 |
consistent_hash | 同一 session/用户 → 同一副本(会话亲和) | 多轮对话、KV 复用 |
power_of_two | 随机挑 2 个取负载小的 | 负载敏感场景 |
cache_aware | 按缓存命中优化(前缀感知) | 重复 prompt、few-shot 场景 |
3.3 一致性哈希(consistent_hash)的价值
多轮对话场景:同一用户连续请求命中同一副本 → 该副本保留其对话 KV 缓存 → 后续轮次跳过 prefill。
缺点:副本缩容 → 哈希环变化 → 缓存失效
对策:hash key 用"用户 ID 的稳定部分";缩容尽量整体替换而非逐个
💡 提示:负载均衡策略与业务形态强相关——对话服务用
consistent_hash,RAG(重复大前缀)用cache_aware,纯吞吐压测用round_robin。默认值没有最优,先测量请求的前缀分布再选。
4. 前缀感知路由:把同类请求导到同一副本
4.1 原理
前缀感知路由(prefix-aware routing):
按请求 prompt 的前缀哈希(如 64 token 前缀)路由
→ 相同前缀的请求 → 同一副本 → 该副本缓存直接命中
效果示例(Production Stack 官方教程):
第 1 个请求 → 副本 A(冷,全 prefill)
第 2 个请求(同前缀)→ 副本 A(缓存命中,仅 decode)← 日志可见 "cache hit"
4.2 什么时候有效
| 场景 | 前缀重复度 | 收益 |
|---|---|---|
| 聊天应用(固定 System Prompt) | 高 | 大 |
| RAG(共享文档前缀) | 中高 | 大 |
| 代码补全(文件头重复) | 中 | 中 |
| 随机 prompt 压测 | 低 | 无 |
4.3 与单副本前缀缓存的关系
单副本内:--enable-prefix-caching(v0.26 默认开)处理请求间共享前缀。
多副本:普通 LB 把同前缀请求打散 → 单副本缓存失效 → 前缀感知路由是”跨副本”层面的缓存协同。
两者是分层关系:路由层保证同前缀进同副本,副本内前缀缓存保证命中。缺一层都白搭。
📌 关键点:多副本 + 前缀缓存的完整链路 = 前缀感知路由 + 副本内 prefix caching。只有副本内缓存没有路由协同,缓存命中率被副本数稀释(N 副本,同前缀请求 1/N 概率同副本);只有路由没有副本内缓存,命中也无处可落。
5. 安全加固:别让 API 裸奔
5.1 现状与风险
vLLM 的 OpenAI 兼容服务默认无认证。直接暴露公网 = 开放算力(被薅羊毛)+ 数据泄露(prompt 全裸)+ 潜在 DoS(无限长请求)。
5.2 加固清单(按优先级)
| 层级 | 措施 | 说明 |
|---|---|---|
| 网关层 | API Key 校验 | 网关(Envoy/nginx/自研)校验 Authorization: Bearer <key> |
| 网关层 | TLS 终结 | HTTPS 终结在网关,后端内网 HTTP |
| 网络层 | 网络隔离 | 服务只在内网可达;公网只暴露网关 |
| vLLM 层 | --api-key | vLLM 内置 API Key 校验(简单场景可用) |
| 应用层 | 配额与限流 | 按 key 限 QPS/token 数(防止单 key 打爆) |
| 应用层 | 请求大小限制 | max_model_len 上限、max_tokens 上限 |
# vLLM 内置 API Key(最简方案)
vllm serve meta-llama/Llama-3.1-8B-Instruct --api-key sk-xxx
# 请求必须带
curl -H "Authorization: Bearer sk-xxx" http://localhost:8000/v1/chat/completions ...
# Production Stack 原生支持
servingEngineSpec:
vllmApiKey: sk-xxx # 或引用 Secret
# vllmApiKey: { secretKeyRef: { name: vllm-secret, key: apiKey } }
📌 关键点:安全的最低标准 = 网关 API Key + TLS + 内网隔离(三件套)。vLLM 内置
--api-key只挡”裸奔”,挡不住暴力破解与滥用——配额、限流、审计必须做在网关。
📝 总结
- 扩缩容:KEDA(Prometheus 指标驱动)优于 HPA(只认 CPU);信号优先级 = 队列深度 > KV 水位 > TTFT SLO > 请求量
- 两个陷阱:加载慢(cron 预扩容 + 预热副本)、缩容丢缓存(cooldown + 优雅排空)
- 负载均衡 5 策略:round_robin / random / consistent_hash / power_of_two / cache_aware,按业务形态选
- 前缀感知路由:与副本内前缀缓存分层协同,缺一不可
- 安全三件套:网关 API Key + TLS + 内网隔离;配额限流在网关
🎯 自我检验清单
- HPA 与 KEDA 的本质差别?LLM 场景为什么选 KEDA?
- 为什么 CPU 利用率不适合做 LLM 扩缩容信号?
- 扩缩容的两个陷阱与对策?
- 普通 LB 为什么会让前缀缓存失效?
- 5 种路由策略各自适用什么业务?
- 前缀感知路由与副本内缓存的层次关系?
- 安全加固清单的优先级排序?
-
--api-key能解决什么问题、不能解决什么?
📚 参考资料
- vLLM 官方 router 项目(负载均衡策略与 PD 路由支持)(https://github.com/vllm-project/router)
- Production Stack:Prefix Aware Routing 教程(https://docs.vllm.ai/projects/production-stack/en/latest/use_cases/prefix-aware-routing.html)
- KEDA 官方文档(https://keda.sh/docs/)
- Kubernetes HPA 文档(https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)