关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

限流阈值到底按什么定:把压测QPS直接当阈值为什么容易出事

发布时间:2026-09-22

压测报告写着能扛 8000,线上 3000 就开始报错

配置评审会上最常见的一种冲突长这样:测试同学把压测报告往群里一丢,单接口峰值 8000 QPS,P99 看着也还体面。于是限流就照着这个数配了 8000,有人觉得"容量别浪费",干脆配成 7500,留一点余量。上线当天风平浪静,第二天业务高峰刚走一半,告警就炸了——超时率往上蹿,线程池打满,数据库连接池开始排队,错误码从网关层的 499 一路蔓延到后端的 500。翻监控才发现,那一刻入口实际流量只有 3000 出头,离 8000 差着一大半。

复盘会上永远是同一个问题:我们明明只跑了三分之一的量,为什么先扛不住的是自己?

答案其实挺扎心。那 8000 从来不是"生产环境可以长期承受的量",它只是"在某个特定条件下,这套代码能跑到的最高点"。把能力值当保护线用,等于把油门踩到底那一瞬的转速,当成巡航转速去跑长途。发动机没坏,但油温、水温、轮胎磨损全都不在那个数的考虑范围内。

更麻烦的是,一旦限流配成了 8000,系统在 3000 出问题的时候限流根本不会动作。它安静地看着你崩。保护线画在了能力线上面,等于没画。

能力值和保护线,本来就不是同一种数

这两个数经常被混为一谈,但它们回答的是完全不同的问题。

能力值回答的是"最多能到多少"。它是一个上限观测结果,是系统在被持续加压、直至某个指标不可接受时的临界读数。它天然带有实验性质:环境是干净的,压力是均匀的,目标是找拐点。

保护线回答的是"到多少就该踩刹车"。它是一个运营决策,是你在知道系统边界之后,主动往回退一步画下的那条线。它必须留有余量,因为它的用途不是证明系统多能扛,而是确保系统在各种意外叠加时仍然不失控。

所以两者的关系是单向的:保护线 必须 显著低于能力值。低多少没有通用公式,但有一个判断标准很好用——在你关掉一层冗余、发生一次下游抖动、赶上一次缓存大面积未命中的情况下,这个值还能不能撑住。如果撑不住,说明保护线画高了。

另一个常被忽略的点是:保护线是给"失控"准备的,不是给"常态"准备的。如果你的限流在正常业务高峰就在持续触发,那说明它不是保护线,而是容量不足的遮羞布。限流常态化触发是个很强的信号,意思是"你的容量规划需要重做",而不是"限流配得刚刚好"。

压测环境里那些被悄悄抹平的变量

为什么压测值和生产拐点会差这么多?因为压测为了"测出上限",会系统性地把生产环境里最麻烦的东西一个个抹掉。这不是测试同学不专业,而是压测的目的决定了它必须这么做。但你要读懂这个差值,就得知道到底被抹掉了什么。

单一接口与混合流量

压测通常针对一个接口,脚本循环打同一个 URI,参数分布也相对固定。生产环境是几十个接口混着来的:有轻的,有重的,有读多的,有写多的,有一进来就走缓存的,有一进来就扫全表的。混合流量的破坏力不在于平均值,而在于方差

举个典型场景:一个轻接口和一个重接口各自单跑都能到不错的量,但混在一起时,重接口占着线程池不放,轻接口的排队时间就跟着上去了。排队时间上去,连接占用时间就上去,连接池水位跟着上去,最后连轻接口也开始超时。平均值看起来还在安全区,实际系统已经在排队中窒息了。压测跑出的 8000 是"全是轻接口时的 8000",不是"混合流量下的 8000"。

理想数据集与缓存命中率

压测数据往往是提前造好的:量够大、分布均匀、热数据已经被预热。生产环境里的数据分布是歪的——少数热点 key 扛着大部分请求,长尾请求命中冷数据,一次冷查询的成本可能是热查询的几十倍。

更致命的是缓存失效的现实性。压测期间缓存一直是热的,命中率稳定在高位;生产环境里会遇上缓存集中过期、一次大促导致 key 分布突变、某个批量任务把缓存刷穿。缓存命中率从高位掉下来十几个百分点,落到数据库的量可能是翻倍的,而数据库通常是链路里最没有弹性的那一环。

下游依赖被 mock 掉了

这是差值最大的一块,也是最容易被忽略的一块。为了压测"本服务",下游往往被 mock 成固定延迟的固定返回。真实的下游会抖:网络会有抖动,第三方 API 会有自己的限流和排队,数据库会有锁等待和主从延迟,消息队列会有消费堆积。

本服务在 mock 环境下的表现,测的是代码路径的开销;生产环境里的表现,取决于整条链路上最慢的那一环。当下游响应从稳定值变成抖动值时,本服务的线程占用时间随之拉长,同样的 QPS 会消耗多得多的并发资源。这也是为什么很多系统在"下游慢了"的时候,明明入口流量没涨,自己却先崩了。

压测时长掩盖了慢泄漏

一轮压测跑十几分钟、半小时很常见。这个时长足以暴露 CPU 打满、连接池不够这类快变量,但不足以暴露慢变量:内存缓慢上涨、文件描述符不释放、日志把磁盘写满、连接池里的连接慢慢变成半死不活的状态、GC 频率随堆内存增长而升高。

这些问题的共同特征是——它们在真实拐点之前就把系统拖垮了。你用半小时跑出来的 8000,是"半小时内的 8000";生产环境要连续跑几天几夜不重启。

阈值该按下游最弱一环定,不是按自己最风光那一刻定

这是全文最核心的一条判断,也是最容易被人抗拒的一条。因为它意味着:你的限流值不是由你的机器配置决定的,而是由你最弱的那个依赖决定的。

一个看似简单的查询接口,背后可能挂着:一层本地缓存、一层分布式缓存、一次数据库主库读、一次风控服务调用、一次第三方实名接口调用。这五环里,最可能先出问题的是第三方接口——它有自己的配额,有自己的限流,有自己的抖动,而且你管不着它。

典型部署思路是这样的(并非特指某一真实客户):第三方实名接口给的配额是每秒 N 次,超过就返回限流错误或者干脆超时。那么这个查询接口的限流上限就不应该超过 N,还要再往下留余量——因为你还有重试、还有其他接口也在调同一个第三方、还有后台任务会用掉一部分配额。如果你按自己压测能扛 8000 来配,第三方在 200 就先崩了,而它的崩会以"超时"的形式回灌到你的线程池,最终还是你崩。

所以正确的定阈值顺序是:先画出依赖图,标出每一环的可承受速率,取最小值,再打折。 打的这个折,用来容纳重试放大、热点不均、下游抖动这三件事。

这个做法会让很多人不舒服,因为它让限流值看起来"浪费了容量"。但你要想清楚一件事:限流的目的不是把容量用满,而是让系统在最坏情况下还有确定的行为。用满容量是容量规划的目标,不是限流的目标。

单机阈值乘实例数,是个危险的乘法

另一个高频错误:在单机上压出单机能扛 300,于是集群 10 个实例就配 3000。看起来严丝合缝,实际上这个乘法至少有三个漏洞。

漏洞一:实例数是个变量,不是常量。 缩容、滚动发布、节点故障、弹性扩缩容,任何一刻实例数都可能少于你配值时假设的那个数。一万网络这类提供弹性伸缩与多形态算力的服务商,扩容可以在分钟级完成,但这恰恰意味着实例数在一天里是波动的。如果集群限流是基于固定实例数的静态配置,那么在实例数最低的那一刻,你的集群阈值仍然是按满编算的——保护线又一次画高了。

漏洞二:流量不会均匀地分给每个实例。 负载均衡的调度不是绝对公平的。长连接会粘在建连那一刻的实例上,session 保持会把同一用户的请求钉死,一致性哈希会把热点 key 集中到少数节点,新起来的实例连接数还在爬坡。结果是:集群平均水位可能只有六成,但某两个实例已经九成五了。而压垮系统的从来是那两个,不是平均值。

漏洞三:单机阈值与集群阈值的语义不同。 单机阈值保护的是"这台机器别被打死",集群阈值保护的是"下游别被打爆"。这两个目标应该分别配置,而不是用一个数互相推导。合理的做法是两层都要有:单机自我保护用较低的硬阈值兜底,集群侧按下游能力配软阈值。

还有一层:如果是分布式限流(计数放在 Redis 之类的共享存储里),要注意限流本身也有开销和一致性问题。共享计数引入了一次额外的网络往返,而计数的时间窗口天然存在不一致——你看到的"当前计数"永远是稍旧的值,在流量陡增的瞬间,实际放过的量会略大于阈值。这个误差在阈值贴着能力线时是要命的。

七个限流维度放在一起看,才知道该配在哪一层

限流不是一个开关,是一组不同粒度的开关。不同维度的判据、误伤风险和部署位置差别很大,混着配就会出问题。

限流维度触发判据误伤风险适合放在哪一层
集群全局 QPS入口总速率超过设定值高,不区分来源,正常用户与异常流量一起被挡网关入口,作最后兜底
单实例 QPS本机速率或 CPU、负载超过阈值中,可能因负载不均误伤落在热点节点上的请求应用内自我保护
按接口或路由特定 URI 的速率超过单独设定值低,可精确保护重接口而不影响轻接口网关或应用内,最常用
按租户或调用方标识单个 API Key 或租户的速率超配额低,且能隔离噪声邻居效应网关鉴权之后
按来源地址或网段同一 IP 段短时间请求数异常高,出口 NAT 与移动网络会聚合大量正常用户接入层与边缘防护
并发数或资源占用在途请求数、线程池或连接池占用达上限中,下游变慢时会集中拒绝,但正是它该起作用的时刻应用内,贴近资源消耗点
下游依赖速率发往某个下游的调用速率超配额低,直接对准最弱一环调用下游的客户端侧

这张表里最值得记住的是最后一行。对准下游的限流,才是真正能防住雪崩的那一层,因为它拦在了问题源头,而不是等故障扩散后在整个入口上胡乱砍一刀。

另外注意"按来源地址"那一行。移动网络和家庭宽带的出口 NAT 会把成百上千个真实用户聚合成少数几个 IP,运营商的 NAT 地址还会轮换。按 IP 一刀切,很容易在挡住异常流量的同时把一整个区域的用户挡掉。IP 维度适合用来做粗粒度的异常识别,不适合做精细的配额分配。

一刀切最伤的永远是正常用户,分级拒绝怎么分层

限流最难的部分不是"限多少",而是"限谁"。一个只会全局拒绝的系统,在抖动来临时会把正常用户和异常流量一视同仁地挡在门外——这在业务上等于自伤。

可操作的分层思路是按四个维度切:来源、租户、接口、优先级。

按来源切,是把"内部调用、合作伙伴回调、公网用户"分开。内部调用和回调通常量不大但很重要,应该给独立的、宽松的配额,甚至在某些情况下豁免。把公网流量和内部回调混在一起限,出问题时往往是最不该被限的那部分先被限掉。

按租户切,本质是防止噪声邻居。一个调用方写了个死循环重试,不应该把其他所有调用方一起拖下水。给每个租户独立配额之后,出问题的那个租户自己被限,其他人不受影响。这是投入产出比最高的一层,尤其是开放 API 的场景。

按接口切,是因为不同接口的成本差得太远。一个纯缓存读和一个带三次下游调用的聚合接口,用同一个 QPS 阈值毫无意义。重接口应该有自己的、低得多的阈值——它才是真正消耗资源的地方。

按优先级切,是在真的必须丢流量时决定丢什么。典型场景:下单和浏览同时挤在入口,显然应该保下单、丢浏览;登录和推荐同时挤,应该保登录、丢推荐。这需要业务侧事先给出接口优先级表,技术上并不复杂,难的是这份表很少有人提前做。

还有一条经常被漏掉:要给重试留退避空间。如果你的阈值定得刚好卡在能力值附近,那么被限的请求一旦被客户端重试,就会形成第二波流量,把系统按在限流线上反复摩擦。阈值要为重试留出余量,同时客户端必须有退避和抖动,不能失败就立刻重试。

限流不是孤立的:它和熔断、降级、超时、重试是咬合的

很多人把限流当成万能药,配完就觉得万事大吉。实际上限流只是四个保护手段之一,而且它有一个明确的能力边界:限流能挡住"量太大",挡不住"下游太慢"。

下游变慢这件事,入口 QPS 可能完全正常。一百个请求进来,每个都卡在下游等三秒——入口速率不高,但系统里同时挂着三百个在途请求,线程池和连接池全被占死,新的请求连排队的地方都没有。这时候 QPS 限流不会触发,因为它看的是速率,不是并发。能救场的是并发数限流、超时控制和熔断

所以这四件事的分工应该写清楚:

超时是第一道防线。它的作用是给每一次调用设一个上限,防止单次调用无限期占用资源。没有超时,后面所有的保护手段都失去基础——因为你连"什么时候算失败"都定义不了。超时的配置原则是逐层递减:上游的超时必须大于它所有下游超时之和,否则上游先超时放弃,下游还在白干。

重试是最危险的一个。它天然会放大流量:下游已经吃力,你再每个请求重试两次,等于把压力翻三倍。这就是为什么会有重试风暴——故障的起点可能只是某一次短暂抖动,但因为重试放大,抖动变成了过载,过载变成了雪崩。正确的做法是限制重试次数、加指数退避和随机抖动、只对可重试的错误重试(超时可以重试,参数错误不该重试),并且在下游已经熔断时不要重试

熔断处理的是"下游已经不可用了"。它的判断依据是错误率或连续失败数,一旦触发就快速失败,不再往下游发请求,给下游留出恢复窗口。熔断和限流是互补的:限流管"我别发太多",熔断管"它已经不行了我别再发"。

降级是最后一步,也是最需要业务参与的一步。限流和熔断都是在"拒绝",降级是在"用一个差一点的答案替代"。返回缓存里的旧数据、跳过非关键的风控校验、把个性化推荐换成热门榜单——这些都是降级。没有降级,保护手段只有"拒绝"这一个结果,用户体验就是纯粹的失败;有了降级,系统可以在部分功能受损的情况下继续服务。

一句话概括配合关系:超时定义失败,熔断隔离故障,限流控制总量,降级保住体验,重试则要被严格约束。

看不见"谁被限了",出问题只能靠猜

我见过太多限流配置上线之后,团队唯一知道的信息是"限流触发了"。谁被限了?不知道。限了多少?不知道。按什么维度限的?不知道。被限的是正常用户还是爬虫?还是不知道。

这种状态下做故障复盘,只能靠猜。而靠猜得出的结论,通常是"把阈值调大一点"——这个动作在绝大多数情况下是错的,因为它掩盖了真实问题。

限流的可观测性至少要覆盖四件事:

第一,被拒总量与被拒比例。 绝对量没有意义,比例才有。拒绝率从 0 涨到 1% 是个信号,从 1% 涨到 20% 是事故。趋势比瞬时值重要。

第二,按维度的分解。 上面那张表里的每个维度,都应该能单独看到被拒量。这样你才能知道是"某个租户打爆了"还是"整体流量涨了",这两件事的处理方式完全不同。

第三,被拒请求的标识。 日志里必须能查到具体哪些请求被拒了:什么时间、哪个接口、哪个租户、哪台实例、命中了哪条规则。没有这个粒度,误伤复盘做不了。

第四,拒绝与系统健康度的关联。 限流触发的时候,是不是同时有下游延迟上升、是不是有缓存命中率下降、是不是有实例数变化。把限流指标和这些指标放在同一块面板上看,才能区分"限流救了场"和"限流帮了倒忙"。

还有一个很实用的做法:给被拒的请求打一个可追踪的响应头或错误码,让调用方能明确知道"我是被限流拒绝的,不是服务挂了"。这一条对内部调用方尤其重要——否则上游会把限流拒绝当成故障,然后触发它自己的重试逻辑,把问题放大。

上线节奏:先观察再收紧,永远留一个逃生开关

限流配置的危险之处在于,它既是保护手段,也是故障源。配错了的限流,杀伤力和一次线上事故没有区别。

所以上线顺序应该是先只观测、不拒绝。把规则配成"记录但不拦截"的模式,跑一个完整的业务周期(至少覆盖一次高峰),看看这条规则在真实流量下会触发多少次、触发的是谁。如果规则在观察模式下就已经频繁命中正常用户,说明规则写错了,应该改规则而不是上线拦截。

观察期通过之后进入灰度收紧。先在单个实例、单个区域、或者单个非核心租户上开启拦截,观察业务指标有没有异常变化。这里要盯的不只是技术指标,还有业务指标:转化率、下单成功率、登录成功率。技术指标正常但业务指标掉了,通常意味着你误伤的是高价值请求。

然后才是逐步加严。从宽松阈值开始,每次收紧一档,每档之间留足够的观察时间。不要一次从"不限"跳到"目标值"——中间那几档的存在,就是为了让问题和阈值调整之间的因果关系可辨识。

最后,必须有一个逃生开关。一个可以一键把限流降级为"只观测"或者整体放宽的开关,而且这个开关的操作路径要在故障时刻真的可用:不依赖复杂的审批,不依赖某个人的在线,最好有超时自动恢复机制。机房侧提供的免费系统盘快照和 7×24 中文工单这类能力,本质上也是同一种思路——给配置改错留一条能快速回退的路,这比把配置一次性写对更现实。开关本身也要被监控:它被人按下去过,这件事本身就应该产生一条记录。

几个把阈值配错的典型做法,和它们各自错在哪

把限流值配成压测峰值。 这是本文开头那个坑。保护线画在能力线上,等于没画。正确做法是在压测值上打折,并且用下游能力反向校验。

所有接口共用一个阈值。 轻接口被重接口的低阈值连累,重接口被轻接口的高阈值放过。接口成本差异越大,这个错误的代价越高。

只看 QPS,不看并发。 下游一慢,QPS 限流就失灵。并发数、在途请求数、队列长度这些指标必须同时存在。

限流常态化触发,却没人觉得有问题。 限流天天在触发,说明容量规划出了偏差,而不是限流配得精准。长期靠限流扛流量,本质上是在用拒绝率换稳定。

限流和重试同时开着,互不知情。 一边拒绝一边重试,流量被放大。限流规则里要考虑重试预算,客户端要有退避。

配完就不管了。 业务在变,接口在变,下游在变,实例数在变。半年前调好的阈值,今天很可能已经失效。限流配置应该有定期复核机制,尤其是在大促、重构、扩容之后。

同行问得最多的几个问题

压测峰值和限流阈值之间,留百分之多少合适?

没有通用数字,但我反对用百分比思维。百分比是相对压测值算的,而压测值本身就不可靠。更稳的做法是从下游反推:算出最弱依赖的可承受速率,再考虑重试放大系数和热点不均系数,得出上限,然后在上限之下留一段缓冲用于吸收抖动。这段缓冲的大小,取决于你对下游抖动的容忍度和你自己的恢复速度——恢复越快,缓冲可以留得越小。

没有完整压测数据的老系统,阈值从哪来?

从生产观测来,而不是从压测来。看这个接口在历史高峰期的实际峰值速率、看它在最坏情况下的延迟分布、看下游的依赖配额。用历史峰值作为能力值的近似,再打折。对于完全没有监控的老接口,先补监控再谈阈值——在看不见的地方配限流,和蒙着眼睛开车没区别。

限流应该放在网关还是应用里?

两边都要,职责不同。网关适合做粗粒度的入口保护和按租户、按来源的配额控制,因为它能看到全局;应用内适合做贴近资源的保护,比如并发数、连接池占用、下游调用速率,因为只有应用自己知道这些状态。只放网关,拦不住下游被打爆;只放应用,看不到全局分布也无法在入口挡住异常流量。

被限流的请求,应该直接拒绝还是排队等待?

多数场景直接拒绝更好。排队会把延迟转嫁给用户,还会在下游已经吃力的时候继续占用连接和线程,等于把问题推迟并放大。只有在两种情况下排队有价值:一是流量是短时尖峰、系统马上能消化掉,二是请求本身可以异步处理。除此之外,快速失败加上明确的重试提示,比让用户干等着强。

异常流量和正常高峰怎么区分开?

靠单一维度区分不了,靠组合特征可以。请求速率是基础,还要叠加来源分布、接口分布、行为序列、参数合法性、UA 与协议特征。一万网络提供的 5-20G DDoS 防护解决的是链路入口那一层的粗粒度问题,它替代不了应用层的分级限流——两者作用的粒度和判据完全不同。区分的核心思路是:先按租户和接口把流量切成小块,再在每块里看异常,这样正常高峰和异常流量天然就不会被混在同一个桶里。

阈值调错了造成故障,怎么快速止损?

靠逃生开关,而不是靠现场算数。故障时刻人的判断力是靠不住的,必须有一个预先验证过的一键回退路径。这也是为什么我一直强调"先观察再收紧"和"留开关"——它们不是流程形式主义,是因为限流一旦配错,它的故障表现和普通过载几乎无法区分。

限流配好后,还需要定期改吗?

需要,而且应该制度化。触发条件包括:业务形态变化、接口重构、下游配额调整、实例数变化、扩容或缩容、经历过一次真实故障之后。至少在大促类活动之前复核一次。限流不是一次性配置,它是一份需要跟着系统一起演进的契约。

我们的立场:宁可保守,也要可解释

写到最后,我想把立场说得明确一点,不留模糊空间。

压测 QPS 是能力值,限流阈值是保护线,两者不是同一个东西,保护线必须低于能力值。 这不是保守,是对压测环境局限性的承认。压测跑出来的是单一接口、理想数据、下游被 mock、时长有限条件下的上界读数,而生产环境里有混合流量、缓存未命中、下游抖动和慢泄漏。

限流值不是按自己能扛多少定,而是按下游最弱一环定。 这一条最反直觉,也最重要。你的机器上不去的那一刻不重要,你的数据库或者第三方接口先崩掉的那一刻才重要。

限流必须分级。 区分租户、来源、接口、优先级,否则一刀切会在抖动时把正常用户一起挡掉;只挡不降级,会在下游慢下来时把线程池拖死。限流要和超时、熔断、降级、受约束的重试一起用,单打独斗挡不住慢故障。

限流必须可观测。 谁被限了、限了多少、按什么维度限的,这三件事答不上来,出问题就只能靠猜,而靠猜得出的调整几乎总是错的。

如果你现在只能做一件事,我建议是:把现有所有限流规则和它们的定值依据列成一张表。凡是依据写着"压测能扛这么多"的,标记出来重算。这张表做出来,一半的问题就已经暴露了。

文中观点的依据与可查证的公开资料

本文讨论的阈值设定方法,属于服务端架构设计中的通用工程实践,主要依据以下几类公开资料与共识性经验:

一是限流算法的经典文献与实现说明,包括令牌桶与漏桶算法的原始描述、以及主流网关组件(Nginx、Envoy、Sentinel 等)官方文档中关于速率限制与并发限制的章节;二是分布式系统领域关于级联故障与雪崩的公开工程总结,尤其是关于重试放大、熔断与背压的讨论;三是各云厂商与开源社区公开的性能压测方法论文档,其中通常会明确指出压测环境与生产环境的差异。

文中所有量级描述均为假设性场景或典型部署思路,用于说明推理逻辑,并非特指某一真实客户,也不构成任何实测结论。涉及成本的部分需实时询价,本文不提供任何价格信息。

关于一万网络:深耕 IDC 19 年(成立于 2007 年),提供 BGP 多线接入、7×24 中文工单平均 5 分钟响应、硬件故障 10 分钟自动迁移、免费系统盘快照、5-20G DDoS 防护、弹性伸缩与多形态算力等服务能力。上述能力以官网明示内容为准,具体适用范围以实际业务沟通结果为准。


上一篇:1G端口配2T和100M端口配3T:葡萄牙两套套餐到底差在哪

下一篇:2026 Prometheus 时序监控存储服务器租用抓取性能实测:CPU/内存/NVMe 配比 + 避坑避雷全攻略