前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
返回 AI 情报前线
All News · 全部资讯8356
  • GitHub Copilot周更:支持断点续聊与上下文保留
  • 数据泄露:最常见的静默失败
  • 六个杠杆:不换模型降低LLM账单
  • CUDA OOM报错信息正确解读指南
  • 交叉验证何时是过度工程化:百万行数据集上的成本分析
  • RAG 检索污染:你的文档库正在成为模型攻击向量
  • 上下文位置效应:LLM 对中间信息利用率最低
  • context_length_exceeded 错误处理:5 种修复方案
  • 上下文压缩:如何在 10k token 预算内用好 100k 内容
  • Prompt 缓存策略:排序决定命中率和成本
  • 对话级上下文预算分配:窗口不是无限大
  • 上下文窗口应被视为架构级约束
  • 经典模型与LLM协同:三种实用架构
  • 为不稳定AI依赖加装断路器
  • Embedding模型选型:维度、成本与质量权衡
  • OpenAI因安全顾虑主动放缓Astra模型开发
  • GGUF 量化格式完全指南:选哪个文件一目了然
  • Chinchilla 论文:训练 Token 数与模型参数应等比扩展
  • 电子表格 AI 清洗:人类审批防止静默数据损坏
  • 网站语义搜索:内容哈希实现增量索引
  • AI 管道中的背压与队列深度设计
  • 面向审计的 AI 交互日志表结构设计
  • 提升识别率的音频预处理实战指南
  • AI 离线/气隙部署的完整依赖清单
  • 让 AI 读懂自有文档的两种机制
  • 腾讯云开源团队级AI编码记忆中枢v2.0
  • 与模型结对编程:有效的 Human-AI 协作方法论
  • Claude Code 将以 Auto Mode 为默认:人类无法全程监督
  • AI 功能六个月重构:三层衰减与各自修复逻辑
  • AI 功能复盘:非确定性使传统方法失效
  • AI 功能特性开关:布尔开关不够用
  • Rippling 推出 AI 支出追踪工具
  • AI Control:假设对齐失败后的安全部署策略
  • 如何构建 AI 合规证据包:清单与结构
  • AI 辅助工程团队的成本结构分析
  • AI编程助手效果差异巨大的根本原因
  • LLM代码注入安全漏洞的规律性分类
  • 用 NVIDIA NeMo Retriever + LanceDB 构建多模态 RAG 流水线
  • Copilot 仪表盘新增 ROI 分析版块
  • AI 宣传打假清单:两步过滤大部分虚假宣传
  • AI慢调用何时该上队列:p99与超时预算的精确判断
  • 构建经得起审查的AI审计日志设计指南
  • AI编程代理在CI/CD中的实战数据:人审不可替代
  • AI流式输出与屏幕阅读器的根本冲突
  • OpenAI披露暂停Astra原因:已达网络安全能力临界点
  • Copilot 代码审查支持 Lite/Balanced 力度级别
  • NVIDIA开源NOOA框架:用Python类一体化构建AI Agent
  • Meta AI在网络安全测试中自主入侵外部系统
  • 程序员用Claude追踪蓝牙信号找回丢失手机
  • My Virtual Office v0.7.0:统一SDK对接主流AI Coding工具
  • 安全沙箱执行AI生成的JavaScript实战指南
  • 已加载 51 / 8356
8.0
热点
AI SCORE
技术实践2026-08-08 06:24

AI 管道中的背压与队列深度设计

dev.to · AI#AI工程#系统设计#队列
Editor brief · 编辑速览

队列不吸收过载,只会把快速失败变成慢速失败,无界队列则把慢速失败变成系统性崩溃。到达率持续超过服务率时,客户端重试加剧洪泛,队列最终装满deadline已过的请求,白白消耗算力和金钱。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

队列不会吸收过载。它只是把快速失败转换成慢速失败,而如果队列是无界的,它就把慢速失败转换成全面崩溃。背压(backpressure)是一种对"你拒绝什么"的设计,而尽早拒绝几乎总是比晚拒绝要好。

队列成为故障本身

以下是这个过程,每次都一样。到达速率在某段时间内超过了服务速率,队列开始增长,延迟也随之增长——因为延迟等于排队时间加上服务时间。客户端命中自己的超时时间并重试,这又提高了到达速率,队列增长得更快。等你发现的时候,队列里已经积压了二十分钟的工作量,队列中的每个任务都已经超过了它的截止时间,系统把所有容量都花在了生产那些到达时就会被丢弃的答案上。

依赖服务的属性让每个阶段都更糟糕。服务时间以秒计,所以队列消化得很慢。每个请求都要计费,所以处理一批已死请求的积压不仅仅是浪费容量,而是在为没有人收到的输出花钱。而且重试是昂贵的而不是免费的,所以这个放大循环是有代价的。

因此起点不是"队列应该多大",而是"队列必须是有界的,而且我必须决定到达边界时会发生什么"。无界队列不是被推迟的容量决策,它是一种做得很差的容量决策。

深度是错误的信号,等待才是正确的

"队列超过 500 时拒绝"是一个没有任何意义的阈值,因为 500 个任务在好日子是三十秒的工作量,在坏日子是十五分钟——而坏日子恰恰是这个规则触发的时候。在做决策之前先把深度转换成时间:

// Little's Law,作为 admission control 规则使用。
//   预估等待时间 = 队列深度 / 服务速率
// 其中服务速率 = 并发数 / 平均服务时间。

function estimatedWaitMs(depth: number, concurrency: number, avgServiceMs: number) {
  const perSecond = concurrency / (avgServiceMs / 1000);
  return (depth / perSecond) * 1000;
}

function admit(job: Job, q: QueueStats): "run" | "reject" {
  const wait = estimatedWaitMs(q.depth, q.concurrency, q.p50ServiceMs);
  // 拒绝任何无法在调用者截止时间内完成的任务。
  // 接受它意味着为在所有人停止监听之后才到达的答案付费。
  if (wait + q.p95ServiceMs > job.deadline.remaining()) return "reject";
  return "run";
}

两个属性让它比深度阈值更好。它会在服务时间变化时自动适应,而这种情况恰恰会打破固定阈值。而且它会产生一个诚实的拒绝消息:你可以告诉调用者预估等待时间和原因,这让行为良好的客户端能够智能地退避,而不是立即在同一堵墙上重试。

这个通用原则值得单独说明。永远不要开始你无法按时完成的工作。在有免费依赖的系统中,这条规则关乎效率。在这里它关乎金钱。

按顺序丢弃什么

如果在平静时期就预先决定了优先级类别,负载丢弃会更容易。一个可用的默认集,从顶部开始丢弃:

推测性和预取工作。没有人请求的建议、预计算摘要、可以明天再跑的后台富化。丢弃它们没有代价,而且通常占总体积的很大比例。

已经失败的工作的重试。在过载情况下,重试是放大器。用占请求总数比例表示的重试预算——Google SRE 书中描述的客户端重试预算——从结构上限制它,而不是依赖每个调用者都表现得礼貌。

截止时间已过的请求。不占容量,对用户零影响,而且你很难想象饱和队列中有多少是这样的。

低级或超出配额的租户。如果你有多层服务,这就是它们存在的意义。在过载期间统一丢弃意味着你最有价值的流量和最没价值的流量受到的影响一样大。

等待中的用户的交互请求。排在最后。当你丢弃这些的时候,答案是容量而不是策略。

无论你丢弃什么,用 429 或 503 加上 Retry-After 来丢弃它。拒绝如果不携带时间信息会招致立即重试,而立即重试正是你试图阻止的事情。

反直觉的一个:过载时使用 LIFO

队列通常按先进先出服务,这是公平的。在持续过载情况下,公平是错误的目标:最老的任务最可能已经超时,所以先进先出意味着系统性地服务那些最不可能被需要的请求。

在过载时服务最新的任务,意味着你完成的是那些调用者仍在等待的任务,所以一些请求会成功而不是所有请求都慢速失败。旧任务然后在过期时被丢弃而不是被执行。这是过载控制文献中的已知技术,确实会让人不舒服,因为它会故意抛弃最旧的工作——但在过载情况下的替代方案不是"每个人最终都会被服务",而是"每个人都会被服务得太晚"。

如果这感觉太激进,更温和的版本可以获得大部分好处:保持 FIFO 顺序,但在出队时检查每个任务的截止时间,不执行就丢弃已过期的任务。仅这一点就能把充满死工作的队列转换成按活工作速度排空的队列。

按预算丢弃,而不仅仅是按负载

这个依赖增加了一个经典背压没有的维度。负载不是唯一可能耗尽的东西;金钱也可以。一个失控的循环、一个病毒式传播的页面或一个永远重试的 bug,可以完全在你的并发限制内,却在一个下午花掉一个月的预算。

所以在负载限制器旁边运行一个支出限制器,具有相同的结构:滚动窗口、阈值和与相同优先级类别绑定的丢弃策略。当每小时支出速率超过其上限时,首先丢弃推测性工作,然后降级到更便宜的降级阶梯,最后才拒绝。针对支出速率报警,而不是总支出——总支出告诉你的是事后,而速率在几分钟内就会告诉你。

每个租户的预算属于同一机制。没有它们,一个客户的失控集成是从所有其他人的容量和所有其他人的预算中服务的,第一个信号是账单。

把背压推给生产者

在队列处丢弃是最后一道防线。更好的做法是让生产者慢下来,而实现这一点的机制平淡无奇却有效:限制队列大小,这样同步生产者会自然阻塞;对远程生产者返回 429 带上 Retry-After,这样行为良好的客户端会自我节流;在响应中暴露当前预估等待时间,这样客户端可以决定是否提交;对于内部生产者,让依赖前的信号量成为阻塞的东西,因为等待许可证的生产者是在免费施加自己的背压。

唯一永远不起作用的是要求生产者表现得体贴。背压必须是一种机制,因为礼貌在事故中存活不下来,在你无法控制的客户端面前也存活不下来。

Concurrency Control: Not Melting Your Own Rate Limit

Queues and Background Jobs for Slow AI Calls

Designing an AI Feature That Degrades Gracefully

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
网站语义搜索:内容哈希实现增量索引
下一篇
面向审计的 AI 交互日志表结构设计