2.3 降级与排队


文档摘要

2.3 降级与排队:过载时不硬扛,保住核心体验 限流是「提前按计划挡」,但再精密的限流也挡不住一切:一次未曾预料的爆款事件、一个下游模型的集体降速、一段网络分区,都能让系统进入「过载」状态。这一节要解决的,正是限流之后的「后手」问题。读者读完,应该能一句话说清楚:过载时优先降级而非硬扛,用优先级队列保住付费与核心用户体验,让系统在「少服务一点」和「全崩」之间,永远选前者。 2.3.1 过载时的两条路:硬扛 vs 优雅降级 系统过载时有两条截然不同的路。一条是「硬扛」:继续接收所有请求,让它们在内存里排队、让延迟一路飙升,直到某一层 OOM、连接表打满、连锁雪崩。另一条是「优雅降级」:主动承认「我现在能力有限」,关闭或替换掉非核心能力,用更便宜的方式满足用户,保住核心链路不挂。

2.3 降级与排队:过载时不硬扛,保住核心体验

限流是「提前按计划挡」,但再精密的限流也挡不住一切:一次未曾预料的爆款事件、一个下游模型的集体降速、一段网络分区,都能让系统进入「过载」状态。这一节要解决的,正是限流之后的「后手」问题。读者读完,应该能一句话说清楚:过载时优先降级而非硬扛,用优先级队列保住付费与核心用户体验,让系统在「少服务一点」和「全崩」之间,永远选前者。

2.3.1 过载时的两条路:硬扛 vs 优雅降级

系统过载时有两条截然不同的路。一条是「硬扛」:继续接收所有请求,让它们在内存里排队、让延迟一路飙升,直到某一层 OOM、连接表打满、连锁雪崩。另一条是「优雅降级」:主动承认「我现在能力有限」,关闭或替换掉非核心能力,用更便宜的方式满足用户,保住核心链路不挂。

我强烈建议选后者。 因为对话系统的过载往往有「自我强化」特性:延迟升高 → 用户不耐烦重发 → 请求更多 → 更慢。这种正反馈一旦形成,硬扛只会加速崩。降级则是「主动打破正反馈」——我少做点、做快点,先把核心体验保住。

```mermaid flowchart TD OV[系统过载] --> C{选择} C -->|硬扛| H[全量接收→排队堆积→OOM/雪崩] C -->|优雅降级| S[关闭非核心→降级答案→核心链路存活] H --> X[全站不可用] S --> Y[核心可用·体验降级] ```

2.3.2 降级的第一原则:先定义什么是「核心」

降级不是「随便砍功能」,而是「砍非核心、保核心」。所以降级的起点,是先给能力分级。以 AI 对话引擎为例,能力大致可分三档:

  • 核心(不可降级):接收用户输入、返回一条有效回答。哪怕这条回答来自最便宜的小模型、哪怕没有华丽格式,也必须能返。
  • 重要(可降级):多轮上下文记忆、检索增强(RAG)、工具调用。过载时可以暂时退化——比如只用最近 3 轮上下文、先不检索、先不调工具。
  • 增强(优先降级):个性化推荐、流式打字机效果、富媒体卡片、长上下文精排。这些「锦上添花」最先关。

经验法则:从增强到重要,逐级降级,永远不动核心。 这样用户感知是「回答变简单了」,而不是「系统坏了」。

2.3.3 降级的具体手段:缓存答案与轻量模型

当模型集群过载,最实用的两个降级手段是「缓存答案」和「轻量模型顶上」。

手段一:语义缓存兜底。 第 3 章会系统讲缓存,但这里要先点出它的降级价值:当大模型慢或不可用时,如果用户的请求和近期某次请求语义相近,直接返回那条缓存答案(带「答案可能不完全匹配」的提示)。这比让用户干等或报错好得多。缓存命中率每高一点,降级时的兜底能力就强一点。

手段二:小模型/蒸馏模型顶上。 准备一条「降级专用小模型链路」:平时它承接简单闲聊,过载时让它临时接管更多流量。小模型单卡并发高一个数量级、成本低得多,用它顶上能极大缓解大模型压力。代价是答案质量下降——但这是「可接受的质量降级」,总好过无答案。

手段三:静态兜底与排队提示。 极端情况下,返回一句「服务繁忙,已为你排到队列第 N 位,预计等待 X 秒」,并给出预估,比冷冰冰的 5xx 体验好太多。用户知道「系统在管我」,流失率会明显下降。

```mermaid flowchart TD R[请求] --> D{过载?} D -->|否| M[正常大模型链路] D -->|是| G{降级策略} G --> C[语义缓存兜底] G --> L[小模型/蒸馏模型顶上] G --> Q[排队提示+预估等待] C --> ANS[返回答案·质量降级] L --> ANS Q --> ANS ```

2.3.4 排队:用优先级队列保住关键用户

降级解决「怎么答得省」,排队解决「先答谁的」。当 capacity 实在有限,必须决定请求的处理顺序——这就是排队策略。

为什么用优先级队列而非 FIFO:先进先出看似公平,实则粗糙。在商业系统里,付费用户、VIP、核心 API 调用方,理应比匿名游客、刷量请求优先级更高。用单一 FIFO,等于让付费用户和最劣质流量「同等待遇」,既不合理也伤收入。

优先级怎么划:最常见是按「用户等级 / 租户 SLA / 请求价值」分档:

  • P0:付费核心租户、内部关键链路——优先处理,几乎不被排队。
  • P1:普通注册用户——正常排队。
  • P2:匿名、未鉴权、疑似刷量——最后处理,过载时最先被丢弃。

做法是:一个带优先级的队列,调度器总是先取高优先级请求去消耗有限的容量。当容量触底,低优先级请求直接进降级或拒绝,高优先级始终有路可走。

```mermaid flowchart TD IN[所有请求] --> PQ[优先级队列] PQ --> P0[P0 付费核心
优先处理] PQ --> P1[P1 普通用户
正常排队] PQ --> P2[P2 匿名/刷量
最后处理] P0 --> OUT[消耗有限容量] P1 --> OUT P2 -.过载.-> DROP[降级或丢弃] ```

2.3.5 排队的两大约束:别让队列成为无限缓冲

排队救急,但用错了比不排队更糟。两个必须守住的约束:

约束一:队列必须有上限,且必须计时。 无限队列 = 把「拒绝」推迟成「无限等待」,用户感知更差,内存也会被堆积请求吃光。队列要设最大长度,超出的低优先级请求直接降级/拒。同时每个请求在队列中要有 TTL(生存时间),超时未处理就移除并降级返回,绝不让孩子「排队排到天荒地老」。

约束二:给排队用户明确预期。 一旦决定让用户排队,就要回传「你在第几位、预计等几秒」。这种「可预期」能显著降低焦虑和流失。技术上可以在响应头或首帧里带 X-Queue-Position 和预估等待。没有预期的排队,用户只会反复刷新,反而制造更多请求(又回到硬扛的正反馈)。

90% 的人会在这里踩坑:把「排队」当成「万能缓冲」,设一个巨大队列,心想「先收着总能处理」。结果队列里堆了 200 万请求,平均等待 30 秒,用户全跑了,而系统还在「认真处理」早已没人等的请求。队列是缓冲,不是黑洞——它必须有出口、有上限、有超时。

2.3.6 熔断:过载的传播阻断器

最后补一个和降级、排队配合的关键机制——熔断(Circuit Breaker)。它和限流、降级是三兄弟。

原理:持续监测某个下游的健康度(错误率、延迟)。当错误率超过阈值,熔断器「跳闸」,后续请求不再打向该下游,而是直接走降级路径,给下游喘息时间。过一段冷却期后,放少量探活请求试探,恢复了再闭合。

为什么必须和降级联动:熔断跳闸后,请求不能「无家可归」,必须有降级答案接住——这正是 2.3.3 那一整套降级手段的价值。没有降级的熔断,等于「跳闸即报错」;有降级的熔断,等于「跳闸即优雅兜底」。

```mermaid flowchart LR REQ[请求] --> CB{熔断器} CB -->|闭合·健康| DOWN[下游] CB -->|打开·异常率高| DEG[降级路径] DOWN -->|错误率超阈| CB CB -->|冷却后探活| PROBE[少量试探] PROBE -->|恢复| CB ```

2.3.7 把降级、排队、熔断串成过载防护网

把 2.2 的限流和本节三件套合起来,一个完整的过载防护网是这样的:

```mermaid flowchart TD REQ[用户请求] --> RL[限流·分层挡] RL -->|通过| CB{熔断监测} CB -->|下游健康| MQ[优先级队列] CB -->|下游异常| DEG[降级答案] MQ --> CAP{容量够?} CAP -->|够| M[大模型处理] CAP -->|不够| DEG DEG --> ANS[返回·体验降级] M --> ANS ```

这张图是接入与限流层的终极全景:限流按计划挡、熔断按实况隔离、降级与排队按优先级兜底。三者协同,系统就能在「全崩」之前,先「少服务一点」,把核心体验守到最后。

2.3.9 降级开关的工程化:用配置中心实现秒级降级

降级策略绝不能写死在代码里。正确的做法是把所有降级开关做成可动态下发的配置:特性开关或配置中心里的一项项布尔值或数值,运维在监控发现过载时一键切换,无需发版。至少要把这几项做成热更新:

  • 「是否启用 RAG 检索增强」(过载时关闭)
  • 「是否启用小模型降级链路」(过载时开启)
  • 「多轮上下文保留轮数上限」(从 20 轮降到 3 轮)
  • 「流式打字机效果 / 富媒体卡片」(过载时关闭)

这样,一次降级就是「改几个配置、推送到全网」的事,从决策到生效以秒计。没有这层工程化,降级就只能是「紧急发版」,等你发完版,雪崩早已发生。

2.3.10 真实案例:一次晚高峰的降级演练

举个具体的演练场景,体会降级链路怎么接力。某对话产品晚高峰流量达到平峰的 3 倍,模型集群 P99 从 2 秒涨到 8 秒,连接池占用率触顶、背压开始频繁触发。监控告警后,运维依次做了四件事:

  1. 开启熔断:对延迟最高的模型实例组跳闸,流量切到降级路径;
  2. 小模型顶上:把闲聊与简单问答切到蒸馏小模型链路,大模型只接复杂请求;
  3. 关闭 RAG:检索增强暂时关闭,回复只基于近期上下文;
  4. 优先级排队:P0 付费租户优先处理,P2 匿名流量直接降级返回。

结果:核心链路可用率维持在 99.5%,付费用户体验基本无损,普通用户体验「变简单了」但全程无 5xx。这就是「少服务一点、而非全崩」的精髓。这类演练应当定期用混沌工程做,别等真故障才第一次跑。

2.3.11 降级的「恢复」同样重要:警惕恢复风暴

人们常忘一件事:过载解除后的「恢复」也能打挂系统。如果系统在过载时堆积了海量请求,一旦容量恢复就瞬间全量放行,相当于制造第二次洪峰——这叫恢复风暴。正确做法是渐进恢复(warm-up):容量回来后,先放 30% 流量、观察延迟与错误率稳定,再逐步提到 60%、100%。熔断器从「打开」到「闭合」也应经历「半开探活」阶段,先放少量请求试探下游是否真恢复。恢复要像限流一样被设计,而不是简单地「把闸一开」。

2.3.12 把降级体验提前告诉用户

降级不只是后端动作,也是一种「用户沟通」。当系统进入降级,应在 UI 上坦诚提示,比如「当前为高峰期,回答由轻量模型生成、可能更简洁」。这种透明反而提升信任、降低焦虑,比悄悄降级被用户发现「质量变差了」更得体。把降级当成一次「体面的妥协」而非「偷偷减配」,是产品成熟度的体现。

2.3.8 本节要点回顾

读完 2.3,请带走这些结论:

  1. 过载时选优雅降级,不选硬扛。硬扛会触发延迟→重发→更慢的正反馈雪崩。
  2. 降级先分级:核心不可降、重要可降、增强优先降。永远不动核心。
  3. 两个实用降级手段:语义缓存兜底、小模型顶上;极端时排队提示 + 预估等待。
  4. 排队用优先级队列:按用户等级/SLA 分档,保住付费与核心用户;队列必须有上限、有 TTL、给预期。
  5. 熔断是三兄弟之一:跳闸后必须由降级路径接住,否则熔断即报错。

至此,第 2 章三节全部讲完。下一章(第 3 章·上下文与缓存)我们会看到:接入层整形放行后的流量,如何被「缓存」这一最大杠杆进一步降本——它正是 2.3 里反复提到的「语义缓存兜底」的真正主场。


发布者: 作者: 转发
评论区 (0)
U