11.3

View in English

11.3 排队论

概述与动机

排队论是关于等待队列的数学研究。在软件工程中,它是支撑着大量实践的一门不显眼的理论。客户服务的响应速度、看板(kanban)规划(一种通过限制在制品(work in progress)来改善流动性的拉动式方法)、进程间消息队列、持续部署流水线:这些全都是队列,而且全都遵循相同的规律。理解这些规律能让团队推理交付前置时间(lead time)、吞吐量(throughput)、容量,以及在接近极限运行系统的真实代价,而不是在生产环境中被它们打个措手不及。本章之所以位于”流动”部分,是因为排队论正是流动的形式化基础:它解释了工作为什么要等待,以及什么才能真正减少等待。

动机在于:人们对队列的直觉往往是错的,而且错得代价高昂。人们以为一台利用率 90% 的服务器”离出问题还差 10%“,而实际上,随着利用率接近 100%,等待时间会呈非线性方式爆炸式增长。他们以为增加在制品(WIP)能加快交付,实际上它会拉长交付前置时间。他们围绕平均值来规划容量,然后被变异性打垮。一点排队论知识,就能用为数不多的几条稳健关系取代这些代价高昂的直觉,其中最重要的是利特尔法则(Little’s Law),它同样适用于客户队列、任务看板和 CI/CD 流水线。

对大型团队、企业和政府而言,排队论是关于容量与流动的共同语言,它能把原本各说各话的不同角色连接起来。产品经理关心从想法到客户的交付前置时间。SRE 关心服务器利用率和延迟。DevOps 团队关心部署频率。支持团队负责人关心响应时间。这些其实都是队列指标,用同一个框架(到达率、服务率、利用率、等待时间)来表达它们,能让组织依据数学而非轶事来规划容量、设定切合实际的 SLO(服务水平目标)并论证投入的合理性。

关键原则

  • 凡是有等待的地方就有队列: 包括工单、任务、消息和部署。
  • 利特尔法则是锚点: 系统中的项目数 = 到达率 × 系统内停留时间(κ = λτ)。
  • 利用率和等待时间是非线性的: 最后 15% 的容量成本最高。
  • 变异性是流动的大敌: 平均值会掩盖痛点;方差才会产生队列。
  • 减少在制品能缩短交付前置时间: 目标是流动,而不是忙碌。
  • 度量整个流动过程: 到达、服务、成功、失败、跳过和等待,都要度量。
  • 一个流程就是一系列队列的队列: 对各阶段建模,然后优化那个构成约束的阶段。

建议

学习核心记号并始终如一地使用它们

少数几个量就能描述任何一个队列。将它们标准化(惯例上使用希腊字母)能消除团队间的歧义:

  • λ(lambda),到达率: 新项目进入的速度。
  • μ(mu),服务率: 项目被处理的速度。由于”服务率”这个说法常有歧义,通常值得把吞吐量明确拆分为总速率(χ)、成功率(α)、失败率(β)和跳过率(σ),其中 χ = α + β + σ。
  • ρ(rho),利用率 / 流量强度 = λ / μ: 单一最重要的汇总指标。ρ < 1 意味着队列会消耗殆尽;ρ ≥ 1 意味着队列会无限增长。
  • 时间: 交付前置时间(τ,从开始到完成)、工作时间(φ,实际处理时间)、等待时间(ω,待处理)和步骤时间(θ,完成之间的间隔)。
  • ε(epsilon),错误率: 失败数 ÷ 总数。

在软件中,明确地为失败和跳过命名很重要:一个被放弃的项目(一位放弃的客户、一个被丢弃的购物车、一张被拒绝的工单)在未被服务的情况下离开了队列,而假装它”已被服务”会污染你的指标。要把望而却步(balking,决定不加入队列)、中途放弃(reneging,等待后放弃)和跳槽(jockeying,切换队列)作为一等的结果来跟踪。

以利特尔法则为规划的锚点

利特尔法则指出,一个稳定系统中长期平均的项目数,等于平均到达率乘以每个项目在系统中停留的平均时间:κ = λτ(经典表述为 L = λW)。它的普适性令人惊叹(它不需要对到达分布或服务顺序做任何假设),这使它成为流动规划的主力工具。将其变形后,它告诉你交付前置时间 = 在制品 ÷ 吞吐量。这正是看板和精益方法的数学基础:如果你想缩短交付前置时间,又无法提高吞吐量,那么唯一的杠杆就是降低在制品。它还能提供快速的合理性检查。如果有 40 张工单处于打开状态,你每天关闭 8 张,那么平均每张工单大约要花 5 天,无论每个人感觉自己有多忙。它唯一的前提条件是稳定性:到达速度不能持续超过离开速度(ρ < 1),否则队列以及该法则的假设都会失效。

尊重利用率的非线性特性

排队论最重要的运维启示是:随着利用率接近 100%,响应时间是急剧上升,而不是逐渐上升的。Bob Wescott 的《排队论的七个洞见》生动地概括了这一实际后果:

  1. 服务中心越慢,你应当规划的峰值利用率就应越低。
  2. 用尽任何资源的最后 15% 都非常困难。
  3. 你运行得越接近边缘,出错的代价就越高。
  4. 响应时间的增长受限于能有多少项目在等待。
  5. 这些都是平均值,而非最大值:要为长尾做规划。
  6. 警惕跨多个服务中心时的人类否认效应。
  7. 要以最有利的角度展示微小的改进。

设计上的启示是:要有意识地预留余量。 对延迟敏感的系统,将目标利用率定在 70% 到 80% 之间并不是浪费;而是在购买可预测的响应时间。这直接指导着容量规划和 SLO(第 3.5 章和第 9.1 章)。

把流程建模为一系列队列的队列

真实的工作会流经若干阶段,一个多阶段流程,本质上就是一个队列,其项目在每一步又排在各自的队列中。要这样建模:整个流程的到达率就是第一阶段的到达率;整个流程的成功率就是最后一个阶段的成功率;整个流程的错误数和跳过数就是各阶段之和。两种常见的形态会反复出现:

  • 漏斗,即项目数在每个阶段都会减少(招聘:外联 → 面试 → 录用;采购:浏览 → 加入购物车 → 支付;交付:集成 → 用户验收测试 → 生产环境)。要优化最重要的那个阶段:最大化漏斗顶部的进入量,最小化漏斗中段的跳过率(购物车放弃),或最小化最后阶段的错误率(糟糕的生产环境上线)。
  • 双菱形的探索与交付流程(探索 → 定义 → 开发 → 交付),本书”流动”部分直接讨论了这一模式(第 11.1 章)。

找到并缓解构成约束的阶段(瓶颈),才是流动改进真正见效的地方;优化非约束环节只会转移队列的位置。

把队列指标与团队已经在使用的关键绩效指标关联起来

排队论中的各项量能干净利落地映射到本书其他章节所讨论的交付和可靠性指标上,这正是这套理论之所以实用而非学院派的原因:

  • 交付前置时间(Dτ),即”从概念到客户”,是一种交付前置时间(τ)度量,也是一项 DORA(DevOps 研究与评估)指标(第 11.2 章)。
  • 部署频率(Dμ)是一种服务率度量。
  • 变更失败率(Dε)是一种错误率。
  • 恢复时间(Rτ)是一种恢复前置时间,即 MTTR(第 9.3 章)。

要区分几种不同的 MTTR(平均响应时间、平均修复时间、平均恢复时间和平均解决时间),因为它们度量的是事故队列的不同环节,而人们经常把它们混为一谈。用队列的术语来夯实 SLI/SLO/SLA(第 9.1 章),能让目标保持诚实且可比较。

权衡:优点与缺点

决策优点缺点
以高利用率运行系统单位硬件/成本更低延迟出现非线性爆炸;对突发峰值脆弱
预留充裕余量延迟可预测;对变异性有韧性稳态成本更高;看起来”利用不足”
限制在制品(看板)交付前置时间更短;减少上下文切换感觉更慢;需要纪律来坚守限制
正式的队列建模容量决策有量化依据;意外更少有学习曲线;模型会简化混乱的现实
只靠经验法则快速,无需数学恰恰在代价最高(接近容量极限)的地方出错

反复出现的权衡是效率与可预测性之间的取舍:提高利用率能省钱,直到它突然不再省钱为止,而在那一刻,延迟、失败和救火的代价会远远超过所省下的钱。排队论的贡献在于告诉你那个悬崖究竟在哪里,从而使这种权衡成为一个主动选择,而不是一次意外。

与团队讨论的问题

  1. 你为每一个对延迟敏感的系统设定的明确利用率目标是多少,是谁签署批准的? 余量是对可预测延迟的一次主动购买,因此它应该是一项明文规定的策略,而不是恰好来了多少负载就变成什么样的意外结果。由于响应时间呈非线性上升,运行在 85% 的利用率就可能已经意味着长尾延迟升高,然而财务部门却把余量视为浪费,推动利用率不断上升。带上具体数字:当前利用率、实测的延迟曲线,以及你上一次延迟事故的代价,然后展示每个服务的悬崖究竟在哪里。对于有季节性高峰(申报季、注册窗口期)的企业和政府系统,要把目标设定在应对峰值的悬崖之外,而不是应对平均值。如果没有人负责利用率目标,延迟事故就会不断”莫名其妙”地出现。

  2. 你的系统中哪些队列是无界的,在系统不堪重负时没有背压来卸载负载? 一个无界队列不会优雅地失败;它会退化为崩溃,因为到达速度持续超过离开速度(ρ ≥ 1)就意味着队列会无限增长。清点你的消息队列、线程池和请求缓冲区,并追问当到达率超过服务率时每一个会发生什么:它会卸载负载、施加背压,还是直接崩溃?这一点在企业规模下尤其重要,因为一个饱和的下游服务可能会在各服务间级联蔓延。带上一次负载测试结果,或一次队列积压的历史事故,检查系统当时是拒绝了多余的工作,还是试图把它全部扛下来。解决方法是设置有界队列,配合从利特尔法则推导出的明确背压机制和超时设置,使过载表现为卸载而非崩塌。

  3. 你是否把从创意到生产的流程建模为一系列队列的队列,你的改进是否瞄准了真正的约束? 一个多阶段流程是一个项目在每个阶段都排队的队列,优化除构成约束的阶段以外的任何东西,都只是把队列转移了位置。绘制出你的交付漏斗(从集成到用户验收测试再到生产环境,或从探索到定义到开发再到交付),并测量每个阶段的到达率、服务率、等待率和跳过率,找出工作真正堆积的地方。团队常常会优化自己最了解的那个阶段,而不是瓶颈所在的阶段,这样投入了精力却没有推动任何进展。带上按阶段划分的等待时间数据,而不是凭直觉判断,因为瓶颈往往是一种等待状态(评审、审批、环境可用性),而不是一种工作状态。一旦你知道了约束所在,就把力气用在那里,不要动非约束环节。

  4. 你是在用利特尔法则来设定在制品限制,还是在通过增加容量来治疗本应靠更多纪律就能解决的交付前置时间问题? 利特尔法则指出,交付前置时间等于在制品除以吞吐量,因此如果你无法提高吞吐量,缩短交付前置时间的唯一杠杆就是降低在制品,而这除了自律之外不需要任何成本。相互竞争的现实压力是真实存在的:限制在制品感觉更慢、更闲,而承受压力的管理者宁愿招聘或购买硬件,也不愿告诉团队少启动、多完成。带上硬核数字:每个阶段当前打开的项目数和完成率,据此计算出隐含的平均交付前置时间,再把它与人们的主观认知相比较;两者的差距通常很大,也令人尴尬。在大型企业或机构中,一项以”解决交付前置时间问题”为由提出的招聘或采购申请,应当先用这套算术来检验,因为一次会提高在制品的人员扩张,反而可能拉长它本应缩短的那个交付前置时间。

  5. 你是围绕平均值来规划容量,还是已经量化了真正制造出你队列的那种变异性? 队列是由方差而非均值形成的,因此两个平均负载相同的系统,如果一个的到达是突发式的、或服务时间是长尾分布的,其表现可能截然不同。这里的张力在于,平均值容易收集,汇报起来也令人心安,而方差和长尾则更难测量,在状态汇报中也不受欢迎。带上分布数据,而不是均值:到达的突发程度、第 95 和第 99 百分位的服务和等待时间,以及把工作集中成峰值的批量大小。对于有可预见激增(申报季、发薪周期、注册窗口期、季末负载)的企业和政府系统,要依据高峰期的方差,而非年度平均值,来规划缓冲区和利用率目标,因为一个按年度平均值设计的系统,恰恰会在公众瞩目的时刻失效。

  6. 你的哪些队列在悄悄把放弃和拒绝算作已被服务,这掩盖了哪些未被满足的需求? 一个望而却步、中途放弃或被拒绝的项目在未被处理的情况下离开了队列,而把它记录为”已服务”会同时污染你的吞吐量、错误率和容量规划。相互竞争的考量是,“已接听的电话”或”已关闭的工单”在仪表盘上看起来比”放弃的来电者”更好看,因此那个诚实的数字,正是没有人愿意主动揭示的那一个。带上跳过率(σ)、望而却步和中途放弃的计数,以及提供的负载与被服务负载之间的差值,让真实需求变得可见。这一点在政府服务提供中尤为重要,那些放弃电话排队或福利申请的公民,代表的是未被满足的义务,而非已解决的案件,把他们报告为已处理,既会歪曲绩效,也会低估公众理应获得的实际容量。

行业视角

创业公司。 你没有时间做正式的队列建模,也不需要。先着手于两个最廉价的收获:把利特尔法则应用到你的待办事项列表上,看看你的在制品实际暗示着怎样的交付前置时间;观察你的看板,找出工作堆积的那个阶段,而不是在一个可能根本不存在的瓶颈上招人。在任何对延迟敏感的路径上,通过留出余量而非精细调优,让利用率远离悬崖,因为在增长高峰期发生一次故障,代价远远超过一点闲置容量。

小型企业。 由于没有排队论专家,要购买现成的指标,而不是自己构建模型。选择一个已经能报告到达率、等待时间和放弃率的帮助台、消息代理或托管平台,直接读取这些数字,而不是自己推导。把这个决策框定为观察两种症状:随着业务繁忙而呈非线性上升的等待时间,以及在被服务之前就放弃的客户,因为流失一位客户正是队列成本对小企业伤害最大的地方。

企业。 工作的核心是把队列思维变成跨众多团队的共同学科:一套统一认可的记号(λ、μ、ρ、交付前置时间)、一致的在制品和利用率余量策略,以及背压标准,使一个饱和的下游服务不会在各服务间级联蔓延。依据排队分析而非猜测来设定 SLO 和容量,并把你的队列当作一个有基线和评审机制的组合来管理,这样就不会有单个团队孤立地过热运行。把这套分析嵌入容量治理和审计流程,使余量目标成为一项有据可查、有人负责的决策。

政府。 采购、透明度和公众问责塑造着每一项容量决策。依据高峰期的方差(申报季、注册窗口期),而非年度平均值,来确定联络中心和面向公民系统的规模,并配备人手,使需求激增时利用率远离悬崖。把望而却步和中途放弃当作未被满足的公众需求来跟踪,而不是把它们藏在”已接听电话数”之中,并用利特尔法则对等待时间的估算来论证容量支出的合理性,这能为审计人员和民选官员提供一个站得住脚、有数学依据的理由,而不是一则轶事。

示例

创业公司。 一个五人的 SaaS 团队被支持工单积压淹没,认为自己需要再招一名客服。在花这笔钱之前,他们应用了利特尔法则:60 张打开的工单、每天关闭 12 张,意味着平均一张工单要等待约 5 天,这与愤怒的邮件相吻合。观察看板后,他们发现工单堆积是因为等待工程团队处理,而不是等待支持团队处理,于是他们限制了在制品数量,把缺陷报告直接路由进冲刺,而不是让它们排队等待。交付前置时间在没有新招人的情况下降到两天以内,他们把省下的预算用在了真正的瓶颈上。

企业。 一个支付平台在为其授权服务确定规模时,测得 λ ≈ 每秒 850 个请求,每个节点的 μ ≈ 每秒 200 个。粗略计算大约需要 5 个节点(ρ = 0.85),但团队知道 ρ = 0.85 已经意味着长尾延迟显著升高,因此他们把供给量提升到 ρ ≈ 0.65,并用利特尔法则来预测在途请求数,从而设定队列深度和超时时间。以往那些”莫名其妙”出现的高峰季事故不再出现,因为团队不再在曲线的陡峭部分运行。

政府。 一个税务机构的联络中心把申报季的支持工作建模为一个队列:到达高峰(λ)、坐席容量(μ),以及关键的、长时间等待后放弃的公民所构成的跳过率(σ)。通过跟踪望而却步和中途放弃,而不仅仅是”已接听电话数”,领导层看到了真实的未满足需求,配备人手以在高峰期让利用率远离悬崖,并用利特尔法则对等待时间的估算来论证额外容量支出的合理性,这为公共支出提供了一个站得住脚、有数学依据的理由,而不是一则轶事。

商业论证:动机、投资回报率与总拥有成本

排队论的价值体现在避免两种代价高昂的错误:过度配置(为你并不需要的闲置容量买单),以及危害远大于前者的在悬崖边配置不足(在那里,负载的小幅增加就会导致延迟大幅上升、违反 SLA、客户流失和紧急支出)。由于在接近 100% 利用率运行的代价是非线性的,“再多加一点负载”所省下的钱很少,而下行风险却是灾难性的,而这种不对称,恰恰正是一点数学知识能把它转变为一个主动决策的地方。这种回报体现为避免的服务中断、达标的 SLA、原本会流失却被留住的客户,以及更平静的待命轮值。

在总拥有成本方面,这套框架的采用成本很低(它是知识,而不是工具),它能改善大型组织在系统整个生命周期中所做的几乎每一项容量、延迟和流动决策。利特尔法则和在制品限制能在不花一分钱的情况下缩短交付前置时间(纯粹的流程收益),而利用率纪律则是用一笔适度、可预测的稳态成本,换取消除代价高昂、不可预测的故障。要向领导层论证这一点,就把最近一次延迟事故转化为利用率曲线,展示一个余量目标本可以如何避免它,并用利特尔法则把降低在制品直接与更快的交付联系起来。

反模式与陷阱

  • 围绕平均值规划容量: 忽视方差,而方差才是真正制造出队列的原因。
  • 过热运行: 在对延迟敏感的系统上以 90% 以上的利用率为目标,然后被长尾延迟震惊。
  • 把跳过算作服务: 把放弃的客户或被拒绝的工单当作已处理,污染指标。
  • 堆积在制品: 把忙碌误认为吞吐量,拉长交付前置时间。
  • 优化非瓶颈: 改进并非约束的阶段,把队列转移到别处。
  • 混淆各种 MTTR: 一边报告”恢复”,一边度量的其实是”修复”,反之亦然。
  • 无界队列: 没有背压机制,导致过载系统退化为崩溃,而不是卸载负载。
  • 把平均值当作最大值: 按均值设计,却被长尾事件呼叫。

成熟度模型

  • 第 1 级,启动(Initiate): 队列(工单、任务、消息、部署)无人管理,只能被动应对;容量靠猜测;利用率任凭负载落到哪里就是哪里;延迟问题会让团队措手不及,只能事后救火。
  • 第 2 级,发展(Develop): 少数团队收集基本指标(吞吐量、平均等待时间),但把它们当作平均值来解读,应用也不一致;一些团队限制在制品或留出余量,另一些则过热运行;没有共享的记号体系,因此实践无法在团队间传播。
  • 第 3 级,标准化(Standardize): 一套通用记号(λ、μ、ρ、交付前置时间)在全组织范围内被记录并强制执行;在制品限制和利用率余量目标为每个对延迟敏感的系统有意识地设定;几种不同的 MTTR 被明确区分;带有背压机制的有界队列成为各服务的默认做法。
  • 第 4 级,管理(Manage): 各队列被度量并对照基线加以控制:到达率、服务率、利用率、长尾延迟(p95/p99)和交付前置时间被跟踪至既定目标和 SLO;队列深度、超时和余量由利特尔法则推导而来,而非凭猜测;望而却步、中途放弃和跳过率被计数,使提供的负载与被服务的负载得以区分;容量决策基于这些证据而非感觉来评审。
  • 第 5 级,编排(Orchestrate): 流动被持续建模为一系列队列的队列;识别并缓解瓶颈是一项持续性的实践;容量、SLO 和背压机制随需求和方差的变化而自适应;队列指标直接与 DORA 和业务关键绩效指标挂钩,组织会随着负载和风险状况的变化,在整个流动过程中重新平衡容量。

讨论思路

  1. 你对延迟敏感的系统实际运行在什么利用率上,它们的悬崖在哪里?
  2. 把利特尔法则应用到你当前的待办事项列表上:你的在制品 ÷ 吞吐量暗示着怎样的交付前置时间,它与现实相符吗?
  3. 你的哪些队列在悄悄把”跳过”(放弃、拒绝)算作已被服务?
  4. 在哪些地方,降低在制品会比增加容量更廉价地缩短交付前置时间?
  5. 你从创意到生产的流程中,哪个阶段才是真正的瓶颈,你的改进是否瞄准了那里?
  6. 你的仪表盘展示的是平均值吗,而真正伤害你的其实是长尾?

关键要点

  • 客户队列、看板、消息队列和部署流水线,全都是受同一套规律支配的队列。
  • 利特尔法则(κ = λτ)是流动规划的锚点:交付前置时间 = 在制品 ÷ 吞吐量。
  • 利用率和等待时间是非线性的:要预留余量;最后 15% 的成本最高。
  • 跟踪完整的画面:到达、服务、成功、失败和跳过,以及等待;不要让放弃被掩盖。
  • 把流程建模为一系列队列的队列,并修复瓶颈,而不是那些看似忙碌的琐事。
  • 队列指标直接映射到 DORA/流动和 SLI/SLO 指标(第 11.1、11.2、9.1 章),为整个组织提供一套关于容量与流动的共同语言。

参考文献与延伸阅读

  • Bob Wescott, Seven Insights into Queueing Theory(以及 The Every Computer Performance Book)。
  • John D. C. Little, “A Proof for the Queuing Formula L = λW”(1961):利特尔法则。
  • Nicole Forsgren, Jez Humble, Gene Kim, Accelerate:与队列关键绩效指标一致的、基于流动的 DORA 指标。
  • Donald Reinertsen, The Principles of Product Development Flow:队列、批量大小和在制品经济学。
  • Daniel Vacanti, Actionable Agile Metrics for Predictability:利特尔法则在看板中的应用。
  • Joel Parker Henderson, Queueing Theory:记号、关键绩效指标与队列的队列(github.com/joelparkerhenderson/queueing-theory)。
  • Dan Slimmon, “The most important thing to understand about queues”(2016)。
  • 维基百科:“排队论”、“M/M/1 队列”、“利特尔法则”、“马尔可夫链”。