生产 7:sgl-router 架构与路由策略
谁该读这一篇? 要在 SGLang 副本前置 router、做 cache-aware 路由 / 灰度 / 健康检查的工程师。 前置阅读:
05-distributed/02-data-parallelism.md。 耗时: 30 分钟 学完能: 1. 启动并配 sgl-router; 2. 选对路由策略(cache-aware / shortest-queue / round-robin); 3. 解释 Cache-aware 路由怎么近似匹配 prefix; 4. 加灰度发布; 5. 排查 router 偏向某副本 / 不均衡的问题。
1. Router 是什么
Client ─▶ Router ──┬─▶ SGLang Worker 1
├─▶ SGLang Worker 2
└─▶ SGLang Worker N
职责:
- 接收客户端 HTTP 请求。
- 选一个最合适的 worker 转发。
- 健康检查、故障切换。
- 监控统计。
源码:sglang/rust/sgl_router/ 或独立的 sgl-router/ 目录。Rust 实现,因为它要承受高 QPS。
2. 启动
安装:
pip install sgl-router # 或者 cargo install
启动:
sglang-router \
--worker-urls http://worker1:30001 http://worker2:30001 \
--policy cache_aware \
--port 30000 \
--host 0.0.0.0
K8s 部署见 02-kubernetes.md §4。
3. 路由策略
| 策略 | 选择标准 | 适合 |
|---|---|---|
random |
随机 | 测试 |
round_robin |
轮询 | 均匀流量 |
shortest_queue |
队列最短 | 负载敏感 |
cache_aware |
命中 prefix 最长 | SGLang 招牌(推荐) |
consistent_hash |
按 session / user 一致性 hash | 多轮对话 sticky |
切换:
--policy cache_aware
--policy shortest_queue
4. Cache-aware 路由实现
挑战:router 不持有 RadixCache 副本(在 worker 里),怎么知道哪个 worker 命中?
实现思路:
1. 每个 worker 周期性把自己的 RadixCache 摘要(hash 集合 / Bloom filter)上报给 router。
2. Router 收到请求时,把 prompt 分块算 hash,查每个 worker 的摘要,找命中最多的。
3. 多 worker 命中相同 prefix 时,选 shortest_queue 兜底。
源码细节(Rust):
struct WorkerCacheView {
bloom: BloomFilter, // 或 prefix tree summary
last_update: Instant,
}
impl Router {
fn pick(&self, prompt_hash_chunks: &[u64]) -> WorkerId {
let mut best = None;
let mut best_hits = 0;
for worker in self.workers.iter() {
let hits = prompt_hash_chunks.iter()
.filter(|h| worker.bloom.contains(h))
.count();
if hits > best_hits {
best_hits = hits;
best = Some(worker.id);
}
}
best.unwrap_or(self.shortest_queue())
}
}
不是精确匹配(Bloom filter 有 false positive),但实际效果接近精确。
5. 灰度发布
部分流量打到新版本:
sglang-router \
--worker-urls http://stable:30001 http://canary:30001 \
--worker-weights "0.9,0.1" \
--policy round_robin
10% 流量打 canary,观察 canary 的 metrics。
SGLang Router 也支持基于 header 的精确路由(如带 X-Canary: true 走 canary)。
6. 健康检查
Router 周期性发健康 ping:
GET /health worker → 200 if alive
GET /health_generate worker → 实际跑一个请求验证
worker fail:
- 暂时移出 backend pool。
- 周期性 retry 重新加入。
- 触发告警。
7. 故障切换
worker crash 中途:
- 当前 in-flight 请求 fail(客户端看到 5xx)。
- 后续请求自动避开。
- 客户端应配重试(带 jitter)。
注意:router 不做请求 replay(有状态请求不能简单 replay)。
8. 性能数字
Rust 实现的 router 单 pod 能扛 50k+ QPS(远超后端 worker 容量)。 通常一个 router pod 服务多个 worker pod 副本即可。
router 单 pod → 10 worker pod → 10 × 100 QPS = 1000 QPS aggregate
9. 监控指标
sglang_router:requests_total
sglang_router:request_duration_seconds
sglang_router:worker_health{worker="..."}
sglang_router:routing_decisions{policy="...", worker="..."}
sglang_router:cache_hit_estimated # router 算的命中估计
健康范围:
- 所有 worker 均衡(routing_decisions 分布均匀)。
- worker_health 全 1。
- cache_hit_estimated 比单 worker 高(因为 router 选了最优)。
10. 常见问题
| 现象 | 排查 |
|---|---|
| 流量倾斜某副本 | Cache-aware 策略 + 业务 prompt 集中;考虑加 sticky session 或换 policy |
| 健康检查 false negative | start-period 给足,区分 /health vs /health_generate |
| 灰度 canary 没流量 | header 配置错;权重为 0 |
| router 自己 CPU 100% | 单 router pod 流量过大;多副本 |
| 跨节点延迟高 | router 和 worker 同 AZ |
11. 加自定义路由策略
Rust router 是开源的,可以自己加策略:
// 例如:选最便宜的 worker
fn pick_cheapest(workers: &[Worker]) -> WorkerId {
workers.iter()
.min_by_key(|w| w.cost_per_token)
.unwrap().id
}
打包成 plugin,启动时 --policy custom:cheapest。
12. 与 Service Mesh 的关系
K8s 里有 Istio / Linkerd 做 traffic management,能不能不用 sgl-router?
- 通用 service mesh 不知道"cache-aware"——它做不到 SGLang router 的 cache 优化。
- 但可以共存:Istio 做 mTLS / 限流,sgl-router 做 cache-aware。
- 或:Istio + Gateway API Inference Extension(vLLM 生态在推)。
详见 vllm-learning/08-production-deployment/03-gateway-and-service-mesh.md。
13. 小结
- sgl-router 是 SGLang 推荐的多副本前置,Rust 实现。
- Cache-aware 是核心创新:用 Bloom filter 近似匹配 prefix。
- 支持灰度、健康检查、故障切换。
- 单 router pod 50k+ QPS,性能不是瓶颈。
- 自定义策略可插件化。
14. 自检
- Cache-aware 路由的实现思路?为什么不精确?
答案
思路:每 worker 周期上报 RadixCache 摘要(Bloom filter 或 prefix hash 集合)给 router;router 收请求时把 prompt chunk hash,查每 worker 的摘要,选命中最多的。 不精确的原因: (a) **Bloom filter 有 false positive**:worker 不一定真有这个 prefix,可能 hash 碰撞。 (b) **更新有延迟**:摘要 1-5s 更新一次,router 看到的可能是几秒前的 cache 状态。 (c) **驱逐未同步**:worker 刚 evict 了某节点,摘要还没更新。 后果:偶尔路由到 cache miss 的 worker,但远比 round-robin 准。生产实测命中率从 round-robin 的 25% 提升到 cache-aware 的 65%+。- Router 自己存 RadixCache 副本吗?
答案
**不存**。RadixCache 持有真实 KV 张量在 GPU 上,跨 router-worker 共享技术上能做(CUDA IPC + 跨节点 RDMA)但代价巨大。 Router 只持有 **cache 摘要**(Bloom filter / 小型 hash 集合),用于近似匹配做路由决策;几 MB 数据,CPU 上跑。 想真正共享 KV 要走 **disagg KV transfer**(P/D 分离架构),那是另一个量级的复杂度,目前小范围生产。- 灰度发布在 router 怎么配?
答案
方式 1 - **权重路由**: ```bash sglang-router \ --worker-urls http://stable:30001 http://canary:30001 \ --worker-weights "0.95,0.05" # 5% 流量打 canary ``` 配 round-robin 或 random 策略均匀分流。 方式 2 - **Header 路由**(精确控制): - 按 `X-Canary: true` header 路由到 canary。 - 业务侧选定 1% 用户带这个 header。 - 适合"灰度某些 internal user"或"小流量长时间观察"。 方式 3 - **DNS / Service Mesh**:router 之外用 Istio VirtualService 控权重;好处是 router 不需要重启。 监控 canary 24 小时 SLO 指标,没退化再扩到 50% → 100%。- Router 健康检查 false negative 怎么避免?
答案
False negative = worker 健康但被 router 标记 unhealthy → 流量被异常拒绝。 常见原因和对策: (a) **超时太短**:worker 处理慢请求时 healthcheck 卡住;用 `/health` 而不是 `/health_generate`(前者轻量)。 (b) **start-period 不够**:worker 还在 capture CUDA Graph 时被探测;给 5-10 分钟 grace period。 (c) **失败阈值低**:网络偶尔丢包就标 unhealthy;阈值 3-5 次连续失败。 (d) **healthcheck 频率太高**:每秒 ping 把 worker 压垮(罕见但有);5-15s 一次足够。 (e) **服务依赖**:worker 依赖外部组件(如 weight server)暂时不可用 → healthcheck 失败;分离 liveness(容器健康)和 readiness(业务就绪)。 实践:start-period 长 + 失败阈值 3 + 间隔 10s + `/health` 轻量探针 + readiness 用 `/health_generate`。- 流量倾斜某副本时考虑换什么策略?
答案
倾斜的根因决定策略: (1) **业务 prompt 集中**:业务 80% 流量都问类似问题,cache-aware 把它们集中到同副本——这是**好事**(cache 命中最大化);但负载不均时可以叠加 **shortest_queue 兜底**:cache_aware + shortest_queue,命中相同时按队列分流。 (2) **某 worker 偶尔慢**:硬件差异、邻居 noisy(共享节点);切到 `shortest_queue` 让慢 worker 自动少收流量。 (3) **新 worker 上线 cache 空**:cache-aware 把它跳过——这其实合理(让老 worker 继续命中),新 worker 慢慢被 round-robin 喂热;不要主动调策略。 (4) **某 worker 一直 0 流量**:可能 health check 失效;先排查 healthcheck false negative。 原则:cache-aware 自带"倾斜"性,要的不是绝对均匀,是 cache 命中最大化 + worst case 兜底。15. 下一步
08-multi-tenant.md— 多业务隔离。09-versioning.md— 模型版本与路由。05-distributed/02-data-parallelism.md— DP 拓扑。
上游源码:
sglang/rust/sgl_router/或sgl-router/仓库。