今年有个项目让我印象很深:第一版推理服务就是"一张 A100 顶一个实例,前面挂个 Nginx 做轮询",听起来挺正常对吧?灰度放量那天下午,p95 首 token 延迟从 800ms 直接飙到 9 秒,监控告警还没响,工单电话先到了。后来复盘才发现,问题根本不在算力,而在调度——大模型推理的请求根本不像传统 Web 请求那样"短平快",负载均衡的策略、KV Cache 的复用、实例之间的互踢,每一项都能把集群拖垮。
这篇文章我想把从单卡推理到千卡负载均衡这条路上踩过的坑、算过的账、验证过的方案完整梳理一遍。写出来主要是给两类人看:一是手里已经有模型、正准备做推理服务化的工程师,二是已经在跑多卡集群但被延迟毛刺和扩容事故折磨的运维/架构同学。文里的估算逻辑、架构分层和调度策略,都是我实际部署中验证过的东西,有数据、有对比、有翻车记录,可以直接拿去参考。
1. 先算明白一张卡能扛什么:显存、带宽与算力的三角账
很多人上来就问"这张卡能不能跑 Qwen 27B",或者"K100 单卡跑量化版 27B 速度快不快"。但"能跑"和"能扛流量"是两码事。我在实测中见过有人用单卡 4-bit 量化跑通 27B 模型,generate 速度看着还行,结果一旦并发上来,延迟立刻崩。原因很简单:大模型推理不是只有权重那么点显存需求,KV Cache、并发槽位、prefill/decode 的资源争抢,这些才是真正的瓶颈。
1.1 速度到底怎么算:prefill 看算力,decode 看带宽
生成式推理分成两个阶段,资源瓶颈完全不一样:
- prefill(处理输入):把用户的 prompt 一次性算完,属于典型的计算密集型。每 token 的计算量可以用 2×N 来估算,N 是模型参数量。27B 模型每处理一个 token 大约要算 54 GFLOPs。单张 H100 的 FP16 稠密算力约 990 TFLOPS,按 60% 的实际利用率算,每秒能 prefill 约 11000 token,一个 2000 token 的长 prompt 大约 0.18 秒。如果是量化模型,prefill 速度还会被反量化开销拖累,但总体仍然"算力够用"。
- decode(逐 token 生成):每生成一个新 token 都要把整个权重读一遍做矩阵向量乘,瓶颈彻底变成显存带宽。H100 显存带宽约 3.35 TB/s,4-bit 量化后的 27B 权重约 13.5GB,理论极限是每秒 248 token;但实际还要算上 GQA、kernel 启动、多 batch 混跑的开销,单用户能拿到 30~60 token/s 已经是正常水平。A100 80G 带宽约 2TB/s,理论上限约 148 token/s,实测单请求大多在 20~40 token/s 这个区间。
所以判断"单卡快不快"的第一件事是搞清楚你测的是哪个阶段。很多人只报"生成速度",但没告诉你 prompt 有多长、并发是多少、是不是用了投机采样。我在压测时习惯分开记录 TTFT(首 token 延迟)和 TPOT(每 token 间隔),这两个数字混在一起,啥也看不出来。
1.2 显存账:权重只是门票,KV Cache 才是大头
权重能不能塞进显存只是第一关,真正的资源大头是 KV Cache。每个请求在处理过程中都需要保存历史 token 的 Key 和 Value,这套缓存的大小可以用下面这个公式粗算:
def estimate_kv_cache_bytes_per_token( num_layers: int, kv_heads: int, head_dim: int, bytes_per_element: int = 2, # fp16 ) -> float: elements_per_token = num_layers * kv_heads * head_dim * 2 # K和V各一份 return elements_per_token * bytes_per_element / 1024 # 单位KB # 以27B模型为例:64层、GQA 8个KV头、head_dim 128 per_token = estimate_kv_cache_bytes_per_token(64, 8, 128, 2) print(f"每token KV Cache: {per_token:.1f} KB") # 实际输出约 256 KB/token注意:这个例子是一个偏保守的 GQA 配置。如果模型用的是 MHA(每个注意力头都是独立 KV),这个数字要乘好几倍。按上面的保守估算,一个 2K context 的请求要吃掉约 512 MB?不对——256 KB × 2048 ≈ 512 MB?我重新算一下:256KB × 2048 = 524288 KB = 512 MB。是的,单个 2K context 的请求约 512MB KV Cache。8K context 就是 2GB。这一点非常反直觉:推理服务往往会同时维持几十上百个并发请求,每个请求的 context 还在不断边长,KV Cache 的累计占用很容易把显存吃穿,权重反而只占一小块地方。
更具体的账:一张 80G 的 H100,4-bit 27B 权重 13.5GB,剩下的约 66GB 理论上能放约 130 个 2K context 请求的 KV Cache(512MB × 128 个 ≈ 64GB)。但如果 context 涨到 8K,同样的 66GB 只能放约 33 个请求。这就是为什么有些单卡服务并发一高就 OOM——不是你显存不够,是每个请求的 KV Cache 比你想象的大得多。
1.3 单卡并发槽位的真相:为什么 20 路并发就卡
每张卡能同时服务的请求数受两个因素限制:KV Cache 总预算和 decode 阶段的总带宽。H100 单卡如果保持每请求 40 token/s 的生成速度,20 个并发意味着每秒要产出 800 token,每个 token 都要读一遍 13.5GB 权重,总读取量约 10.8TB/s,已经远超 3.35TB/s 的显存带宽。所以单卡 80G 显存看着很大,真正能稳定支撑的并发其实非常有限。
连续批处理模型(continuous batching)能把每个 batch 的 GPU 利用率抬高,但也只是把"带宽总量"这个硬上限的作用发挥到极致,不可能突破物理限制。我自己的经验:单张 H100 跑 4-bit 27B,在线业务如果要求每请求 30 token/s 以上,稳定并发基本在 4~8 路顶天了。所以判断"什么时候该上集群"其实很简单:用你业务高峰期的并发数,除以单卡能扛的并发数,结果大于 1,就得开始琢磨集群了。
2. 集群架构的分层逻辑:网关、路由、推理实例与 KV Cache 池
集群不是把一堆卡堆在一起就完事了。我见过不少团队直接把训练集群的那套思路搬过来做推理,结果发现完全不适用——训练任务以小时为单位,做一次资源申请、等到全部就位没问题;在线推理的延迟以毫秒计,请求不会等你的任务调度器慢慢排队。
2.1 第一阶段:多副本加负载均衡,大多数业务到此就够了
如果业务规模不大,最简单的架构就是"多实例副本 + 前置负载均衡"。每个推理实例都是独立进程,各自加载权重、各自维护 KV Cache,实例之间没有共享状态。请求进来由负载均衡层分发。
这个阶段真正要花心思的是网关层。我一开始以为网关就是 Nginx 转发,后来发现推理场景至少还要处理三件事:
- 排队与容量保护:实例有最大并发上限,超出的请求不能无限堆积,否则延迟会瀑布式恶化。网关必须对每个实例维护一个并发计数器,超过阈值就返回 503 或重试到其他实例。
- 超时分级:首 token 等待、总生成时长、流式空闲超时,三类超时用不同的阈值,不能一刀切。
- 优雅摘除:实例要升级或重启时,先从网关摘流、等存量请求结束、再做优雅停机。这一条做不好,扩容回滚时必出事故。
网关层还有一个容易忽略的细节:流式响应(SSE/流式 HTTP)下,网关如果不把连接和上游实例绑定好,用户会拿到"吐一半断流"的结果。长连接的超时设置、心跳机制,都要在压测里专门验证。
2.2 推理引擎层:连续批处理与 PagedAttention 是吞吐的地基
单实例内部用什么引擎,直接决定集群要多少张卡。现在主流推理引擎(vLLM、SGLang、TensorRT-LLM)都实现了连续批处理和 PagedAttention。就我的实测感受:
- PagedAttention 把 KV Cache 按页管理,显存利用率比静态分配高得多,内存碎片基本消失,OOM 概率大幅下降。
- 连续批处理让请求可以在 token 粒度上动态进出 batch,而不是等整个序列生成完。一个请求生成完一个词,立刻腾出算力给下一个请求,GPU 利用率能稳定在 70%~90%,比传统的静态 batching 高 30~50%。
选引擎的时候还得分清张量并行(TP)和流水线并行(PP)的适用场景。TP 把单卡装不下的模型切到多卡,卡间通信量很大,需要高速互联;PP 适合超长序列场景,把层切分成流水段,通信开销相对小但会牺牲设备利用率。千卡推理集群里,90% 的实例跑的是单卡能装下的模型(量化后),真正需要 TP 的通常是超大模型或极长 context 的专用场景。
2.3 进阶形态:PD 分离与 KV Cache 池,千卡架构的分水岭
当集群规模到几百卡以上,我强烈建议考虑 prefill 和 decode 分离(PD 分离)。原因在上面算过账:prefill 吃算力,decode 吃带宽。混跑时两者互相干扰,长 prompt 请求会抢占大量 GPU 算力做 prefill,同时把显存带宽占满,导致正在生成 token 的短请求集体变慢,表现为 p99 延迟剧烈抖动。
PD 分离的核心是把请求拆成两段:
- prefill 节点:负责读入整个 prompt、计算 KV Cache,算力密集型,适合用高算力卡,并行度可以开得很高。
- decode 节点:接收 prefill 阶段算好的 KV Cache,逐 token 生成,带宽密集型,卡上只需要保住 KV Cache 和权重即可。
两段之间要做 KV Cache 的跨节点传输。这一层设计得好,集群的利用率天花板会显著提高;设计不好,会出现"prefill 算完等 decode 接收"的死锁。工程上建议先用成熟的调度框架(如结合 Ray 或 K8s 的 PD 分离部署),把 prefill 和 decode 的实例配比、传输队列长度先用监控数据迭代出来,不要一上来就手工调参。
KV Cache 池化是另一个关键升级。实践中,大量请求存在公共前缀——系统提示词、文档、对话历史。如果在 prefill 阶段用 prefix cache 命中,可以直接跳过一大段重复计算。集群层面,则需要把相同前缀的请求路由到同一个有缓存前缀的节点上,这是"KV Cache 局部性",我在下一节细说。
2.4 一张表看清四层架构各自职责
| 层级 | 核心职责 | 常见误区 | 关键指标 |
|---|---|---|---|
| 网关层 | 路由、排队、超时、熔断、容量保护 | 只做转发不管排队 | QPS、排队深度、503率 |
| 路由层 | 按缓存局部性和实例负载做调度 | 只按轮询分发 | KV Cache命中率、剩余时间均方差 |
| 推理实例层 | 连续批处理、KV Cache管理、生成 | 忽视显存带宽瓶颈 | TTFT、TPOT、GPU利用率 |
| KV Cache层 | prefix cache、PD间KV传输 | 混跑长短请求 | 缓存命中率、传输时延 |
这张表是给架构评审用的,每层只有一两个核心指标能真正反映健康度,剩下的都是噪声。
3. 负载均衡策略的演进:为什么轮询在生成式推理上必然翻车
我见过很多团队的第一版负载均衡策略是 Nginx 轮询或者简单的 least-connection,压测一上就露馅。原因在于大模型推理请求根本不是"短事务"——一个请求可能运行几秒钟甚至几十秒,而且运行时资源占用是动态变化的。长 prompt 的 prefill 可能瞬间吃掉几 GB 显存和大量算力,短请求的 decode 则长期占用显存带宽。用传统负载均衡的思路去处理这种"长尾、异构、高相互影响"的流量,结果必然是部分实例过载、部分实例空转。
3.1 问题本质:请求不是均匀的短事务
假设集群有 4 个实例,网关用 least-connection 分发。第 1 个实例接到一个 8K prompt 的长文档总结请求,正在做 prefill,算力几乎占满;第 2 个实例上有一批正在逐 token 生成的短请求,显存带宽吃紧。这时新的请求进来,网关看连接数,可能选择发给第 1 个实例,结果第 1 个实例的 prefill 还没结束,新请求只能排队,TTFT 直接飙升。这不是调度算法"坏了",而是调度算法根本不了解实例内部正在干什么。
3.2 等开销负载均衡:让每个实例的预计剩余时间趋于相等
我在实际项目里最终采用的方案,可以归纳为"等开销负载均衡"。核心思想很简单:不是看谁连接数少,而是预估每个实例上"正在处理的请求预计还要跑多久 + 排队中的请求预计要跑多久",然后选择预计剩余时间最小的实例。用伪代码表达:
def choose_instance(request, instances): def estimated_finish_time(inst, req): # 正在运行的请求预计还需要多少时间 running = sum(r.remaining_latency() for r in inst.running_requests) # 排队中的请求预计需要多少时间(保守估计) queued = sum(q.estimated_latency() for q in inst.queue) # 新请求本身的预计处理时间 new_req = estimate_prefill(req.prompt_len) + estimate_decode(req.max_tokens) return running + queued + new_req return min(instances, key=lambda inst: estimated_finish_time(inst, request))关键在 estimate 函数怎么算。prefill 时长可以用 prompt 长度除以实例的 prefill 吞吐(每秒处理多少 token);decode 时长可以用 max_tokens 除以每 token 生成速度。这两个数字不需要很精确,能分出"这个实例明显更忙"就够了。我实测这么做之后,p95 TTFT 比轮询稳定了 3~4 倍,吞吐也有 20% 以上的提升。
等开销调度的本质是把"实例的忙碌程度"从离散的连接数升级为"时间维度上的负载估计"。传统负载均衡解决的是"谁闲着",等开销解决的是"谁最快能空出来"。
3.3 Cache-aware 路由:把局部性变成调度的一等公民
等开销调度的局限是它不知道实例里有哪些 KV Cache。一个实例可能非常空闲,但它缓存的前缀和你请求的前缀完全无关,把请求发过去意味着要重新做一遍 prefill。另一个实例稍微忙一点,但它缓存了某个长文档的前缀,你的大部分 prefill 计算可以直接复用缓存结果。
cache-aware 路由的做法是在调度打分函数里加入缓存命中因子:
def score(instance, request): load_score = instance.estimated_finish_time(request) cache_ratio = instance.prefix_cache_hit_ratio(request.prompt_prefix) # 缓存命中率越高,负载惩罚越小;α用于权衡 return load_score * (1 - alpha * cache_ratio)α 是个可调参数,我推荐从 0.3 起调,观察集群整体缓存命中率和延迟曲线的平衡。cache-aware 路由做得好,长 prompt 场景的 prefill 计算量能降 40%~70%,对 TTFT 的改善是立竿见影的。
但这里有个容易被忽视的副作用:如果调度策略过于强调缓存局部性,特定前缀的请求会被反复送到同一两个实例,造成热点。热点实例的 KV Cache 命中率很高,但排队时间也最长。所以局部性和负载均衡永远是矛盾的两端,需要不断用监控数据平衡。我的经验是:低负载时优先局部性,高负载时优先等开销,中间用 QoS 参数平滑过渡。
3.4 四种策略的压测对比与选型建议
| 策略 | 实现成本 | p95 TTFT | 缓存命中率 | 长尾抖动 | 适用规模 |
|---|---|---|---|---|---|
| 轮询 | 极低 | 差 | 低 | 严重 | 10卡以内 |
| least-connection | 低 | 中 | 低 | 中等 | 50卡以内 |
| 等开销调度 | 中 | 良 | 中 | 小 | 百卡级 |
| cache-aware + 等开销 | 较高 | 优 | 高 | 小 | 千卡级 |
选型建议直接给结论:50 卡以下可以先上 least-connection;一旦规模过百或者开始做 PD 分离,必须上等开销调度;如果业务有明显的长公共前缀特性(比如 RAG 系统用固定系统提示词),cache-aware 路由带来的收益会让你非常惊讶。
4. 千卡规模下的隐形灾害:互踢、羊群效应与故障雪崩
千卡集群和百卡集群的差别,不只是数量级的提升,而是会冒出一批小规模集群根本遇不到的"系统性灾害"。这些灾害几乎都源于"局部最优不等于全局最优"——单张卡跑得好好的,合在一起反而互相拖累。
4.1 冷启动互踢:新实例上线,为什么老实例的延迟反而变高
有一次扩容,我加了 8 个推理实例,结果存量实例的 p99 延迟反而涨了 30%。排查了好久才找到原因:新实例上线后是"冷"的——KV Cache 全空、显存里只有权重。网关把一部分流量切过去之后,新实例面对长 prompt 请求只能老老实实重新做 prefill,prefill 过程极其消耗算力,导致新实例处理缓慢。同时,新实例的慢请求不断积压,网关的健康检查连续失败后把新实例摘除,流量又弹回老实例,老实例瞬间过载,连锁反应。
这就是冷启动互踢。解决思路有两个方向:一是给新实例一段"预热期",在正式接流之前先灌入一批真实流量样本,把频繁访问的前缀缓存热身;二是对新实例的调度权重动态上调,从 0 开始逐步升高,让它慢慢吸收流量,而不是一上来就接受全量请求。我在生产环境两种方法都试过,预热期的效果更直接,但需要你提前攒一批压测流量脚本;权重渐进则适合无法预知业务前缀的场景。
4.2 故障放大链路:一次抖动如何演变成全集群雪崩
千卡规模的集群,单实例故障是常态,但真正怕的是故障被层层放大。最经典的链路是这样的:某台实例所在宿主机网络抖动 → 该实例上的在线请求变慢,排队越来越多 → 健康检查连续探测失败,网关将该实例摘除 → 该实例积压的请求全部断开、客户端自动重试 → 重试流量被均匀打向其他实例 → 其他实例瞬时过载,延迟上升 → 新一轮健康检查失败,更多实例被摘除……
整套崩塌过程可能只需要两三分钟。核心教训是:健康检查的判定阈值必须和业务延迟指标关联,而不是简单地"连接能否建立";同时网关侧必须做熔断——当某实例的 p99 延迟超过阈值时,不是把实例摘除,而是先让它"半开",只接收低优先级流量,避免重试风暴。
我还强烈建议在所有客户端 SDK 里做重试退避(exponential backoff)。很多重试风暴根本不来自用户,而是来自内部 SDK 的默认行为。一次 5 分钟的故障,可能被重试逻辑放大成 20 分钟的全集群抖动。
4.3 排队与过载保护:宁可拒绝,也不要让延迟失控
千卡集群的容量规划永远赶不上流量突刺,这种时候最关键的是"刹车系统"。我在网关层给每个实例配置了最大并发数和最大排队长度,超过上限直接返回 503 或者让客户端走降级链路。你的第一反应可能是"拒绝请求会影响用户体验",但数据不会骗人:当排队深度超过实例处理能力的 2 倍时,队列里的请求几乎全部超时,对用户来说,等 30 秒拿到一段错误信息,比立刻收到 503 再重试要恶劣得多。
用一张表说明我踩过的阈值经验:
| 参数 | 推荐初值 | 调整依据 | 过小的影响 | 过大的影响 |
|---|---|---|---|---|
| 实例最大并发 | 单卡带宽上限/单请求解码速度 | 实测decode吞吐 | 利用率低 | 延迟崩溃 |
| 实例最大排队 | 并发数的1~2倍 | 压测p95延迟拐点 | 频繁拒绝 | 超时堆积 |
| 网关全局并发 | 所有实例并发的80% | 容量余量 | 保护不足 | 雪崩 |
这个表的本质是给每个实例一个"即将过载"的红线,让它在崩掉之前先说"不"。
4.4 网络与拓扑:千卡集群真正难调的往往是通信
千卡 H100 级别的集群,典型配置是 InfiniBand 或 RoCE 高速网络 + 集中式存储 + K8s/GPU 调度平台。硬件连接需求并不复杂——按机架做 ToR 交换机、机架间再做 Spine 互联,重点在于网络隔离和 QoS。推理集群有个特点:大部分流量是"东西向"的短小控制流,但 PD 分离架构下 prefill 节点向 decode 节点传输 KV Cache 时会产生大量跨节点数据流,这种流量如果和训练任务的集合通信混在同一张网络上,分分钟把网络打爆。
建议不管规模多大,都要把推理集群的存储网络、KV Cache 传输网络、外部访问网络做逻辑隔离。跨节点 TP 或 KV 传输一定要走 RDMA,避免 TCP 协议栈在千卡规模下成为明显瓶颈。实测中,很多 GPU 利用率上不去不是算力问题,而是通信等待时间太长,GPU 一直在等数据。GPU 上的 Compute 和网络上的 Transfer 必须重叠起来,否则再强的卡也白搭。
5. 可观测性清单:监控、日志与 Trace 三位一体
千卡集群没有全局可观测性,就像蒙眼开车——你凭感觉觉得"还行",直到压死最后一根稻草才看到全貌。推理场景的监控体系和传统 Web 服务不一样,重点指标完全不同。
5.1 关键指标:TTFT、TPOT、排队深度、KV Cache 命中率
基础监控至少要有下面这些:
- TTFT(首 token 延迟):用户可感知的第一个指标,p50/p95/p99 都要看。长 prompt 场景下 TTFT 直接被 prefill 调度质量左右。
- TPOT 或 ITL(每 token 间隔):反映生成阶段是否稳定,持续上涨通常意味着带宽吃紧或排队恶化。
- 排队深度:网关和实例两个维度都要看,排队深度是预测延迟拐点的先行指标。
- GPU 利用率、显存占用、显存带宽利用率:带宽利用率比算力利用率更关键,推理场景瓶颈大多是带宽。
- KV Cache 命中率:prefix cache 是否生效,能直接解释 prefill 计算量的波动。
- 温度/功耗:千卡集群的散热和电力是硬约束,功耗曲线异常往往先于性能劣化出现。
对于 GPU 利用率,我要多说一句:推理场景只盯着算力利用率会误判。一张卡算力利用率 30% 但显存带宽利用率 90%,它其实是"满载"的,再加请求就会延迟崩溃。判断负载高低,优先看带宽利用率和排队深度。
5.2 Token 级日志与全链路 Trace
传统服务用 request ID 串日志就能定位问题,推理服务最好能记录到 token 粒度。我的做法是:网关生成全局 request ID,转发到推理实例时带上;推理实例每生成一个 token 输出一条带时间戳的日志,记录 token 序号、累积耗时、当前 KV Cache 大小。这样一旦某个请求出现"中间卡住"的情况,可以从 token 粒度精确复现是哪一秒开始变慢、当时实例在干什么。
再往上走就是全链路 Trace——网格从网关的排队开始,穿过负载均衡决策、实例的 GPU kernel 执行,再到 KV Cache 复用情况。你可能会觉得 token 级日志数据量太大,但实际上用采样方式(例如只保留 p99 请求和异常请求的 token 级日志)就能覆盖 95% 的排障场景。磁盘便宜,用户的困惑贵。
5.3 容量规划:靠数据而不是靠直觉扩容
有了监控数据,扩容决策就不再是拍脑袋。我通常用三个信号判定扩容时机:网关全局排队深度持续超过阈值的 30 分钟均值、p95 TTFT 超过业务 SLO 的 70% 且仍在上升、以及预计未来 1~2 小时内的配额估计(比如下班前的高峰)。数据驱动扩容最大的好处是"提前量"——等延迟打满再扩容,扩容的冷启动问题会让你先经历一轮劣化,不如在拐点之前动手。
6. 从 8 卡到 512 卡的演进路线与我的最后建议
最后把我实际走过的一条演进路线完整复盘一下,包含每个阶段对应的架构动作和容易踩的坑,给正在规划的同学一个参考。
6.1 分阶段的架构动作与踩坑记录
| 阶段 | 规模 | 架构动作 | 我踩过的坑 |
|---|---|---|---|
| 第一阶段 | 8~16卡 | 多副本+least-connection,统一网关 | 健康检查阈值只看连接,故障时雪崩 |
| 第二阶段 | 16~64卡 | 等开销调度上线,prefix cache 开启 | 冷启动没做预热,扩容引发生存量实例延迟抖 |
| 第三阶段 | 64~256卡 | PD 分离、KV Cache 池化,cache-aware 路由 | KV 跨节点传输走 TCP,网络成为瓶颈 |
| 第四阶段 | 256~512卡 | 网络隔离、token级可观测性、熔断半开 | 全局监控缺失,容量规划全靠经验 |
每个阶段过渡都伴随至少一次线上事故,这是正常的。架构升级和大版本重构一样,不可能平滑无感。关键是每次事故都要变成监控指标和调度策略的增量进步,而不是只修一个临时补丁。
6.2 如果重来,我会在第一天就做好的三件事
第一,压测脚本和监控面板在写第一个推理服务时就要搭好,而不是等集群到一百卡再补。没有压测基线,你后面做任何调度策略优化都无法量化收益。第二,网关层从第一天就预留"切换调度算法"的开关,能通过配置中心动态切换轮询、等开销、cache-aware 三种策略。我见过太多团队因为网关写死轮询,想换策略只能改代码重新发布,白白错过黄金调优窗口。第三,所有客户端 SDK 统一重试退避策略,这是在千卡集群里花最少的钱避免最大灾难的工程决策。
6.3 最后一个可落地的小技巧
分享一个很多人忽视的实践:给负载均衡策略加一个"金丝雀池"。任何调度算法的改动,先让 5% 的流量走新策略,和存量策略并行跑一两天,对比 TTFT 和吞吐。我在做 cache-aware 调参时就是靠金丝雀池避免了一次线上事故——新策略在某类流量上出现严重热点,金丝雀池数据立刻暴露了问题,存量流量完全没受影响。小规模集群可能觉得这是多余动作,但规模上来之后,这个习惯能救你很多次。