AI 后端队列背压:请求堆住时,系统要会说不
AI 后端队列背压:请求堆住时,系统要会说不
AI 后端最怕一种状态:请求不断进来,模型服务已经处理不过来,队列越堆越长,用户还在等待,最终超时、重试、雪崩一起发生。很多系统不是被单个请求打垮,而是被没有边界的排队拖垮。
背压的核心是让系统在压力下会说不。不是所有请求都必须进入队列,不是所有任务都值得继续等。基础设施要保护核心路径,而不是把所有压力吞进去。
一、队列长度不是唯一指标
AI 请求成本差异很大。一个短标题生成和一个长文档总结,不能只按请求数排队。更合理的是按预计 token、任务优先级和超时时间估算队列压力。
flowchart TD A[请求进入] --> B[估算成本] B --> C{队列是否可接收} C -->|可接收| D[进入队列] C -->|不可接收| E[快速失败/降级] D --> F[Worker 处理]如果队列等待时间已经超过用户可接受范围,继续接收只是在制造无意义等待。
二、入队前做预算判断
可以在网关层估算任务成本,并按租户和任务设置并发上限。
func canEnqueue(q QueueState, req InferenceJob) bool { if q.EstimatedWaitMs > req.MaxWaitMs { return false } if q.PendingTokens+req.EstimatedTokens > q.TokenBudget { return false } if q.TenantRunning[req.TenantID] >= req.TenantLimit { return false } return true }这段逻辑不复杂,但能挡住很多雪崩。队列不是垃圾桶,它应该有容量和规则。
三、降级要提前设计
拒绝请求不是唯一动作。可以切小模型、缩短输出、关闭高成本功能、把离线任务延后,或者返回“稍后再试”。关键是这些策略要提前写好。
backpressure_policy: interactive_chat: action: use_smaller_model long_summary: action: delay_job batch_generation: action: reject_with_retry_after不同任务的降级方式不同。在线用户要尽快得到可理解反馈,离线任务可以等待,批量任务可以限速。
四、重试要避免放大事故
请求失败后客户端如果立即重试,会把压力放大。服务端应返回Retry-After,客户端使用退避。内部 worker 重试也要有上限。
背压和重试必须一起设计。只做重试不做背压,会让系统在故障时更快崩;只做背压不控制客户端,也会被重试流量淹没。
背压还要被用户和上游服务看见。返回错误时不要只给500,而是明确这是容量保护,并带上可重试时间。内部调用可以用结构化错误,外部接口可以返回429或业务错误码。
{ "code": "QUEUE_OVERLOADED", "message": "当前生成任务较多,请稍后重试", "retry_after_seconds": 30, "degraded": false }可观测性也要跟上:入队拒绝数、降级次数、队列等待 p95、重试来源、客户端是否遵守退避。没有这些指标,背压策略是否有效只能靠猜。
五、总结
AI 后端队列背压的目标,是让系统在压力下保持秩序。按成本估算队列压力,入队前做预算判断,提前设计降级,重试使用退避。
基础设施不是永远接住所有请求,而是在该说不的时候说得清楚、说得及时。
