注册一亩三分地论坛,查看更多干货!
您需要 登录 才可以下载或查看附件。没有帐号?注册账号 
x
刷一地论坛经常看到大家在讨论高并发流量治理时,张口闭口就是 Guava 的 RateLimiter、分布式令牌桶、漏桶。说实话,这些东西应付一下面试八股文挺好,要是真不加思索地扔进几千个 Pod 的复杂微服务底座里,大概率大促当晚你就得起来擦屁股。
今天咱们不扯理想化的公式,从流量配额的本质出发,聊聊为什么大规模工业界都在从静态 QPS 限流走向自适应并发控制。
【先说结论:令牌桶其实没错】
在正式剖析痛点前,先定个调子:令牌桶没有错。
令牌桶解决的是流量配额(Rate Limiting)和业务边界问题,在以下场景中它是绝对的主力:
- 用户公平性控制
- 租户隔离(Multi-tenancy)
- API 防刷与恶意爬虫防护
- 业务层面的流量预算控制(Budgeting)
但它并不直接回答另一个更底层、更物理的工程问题:"当前这台机器,此刻到底还能扛多少请求?"
当系统进入资源瓶颈区间时,真正决定服务是否会雪崩的,往往不是 QPS 本身,而是当前正在处理的请求数(Concurrency)、响应时间(RT)以及底层的资源利用率。
【真实工业界的 Corner Case】
教科书总假设流量是完美的随机均匀分布。但在生产环境中,静态阈值往往会被下面两个经典场景背刺。
1. 静态阈值的“刻舟求剑”与 Little's Law
我们在配置中心给某个接口配了。下午 3 点,由于各种不可控的客观物理原因,单机处理延迟开始恶化。
其实这里隐藏着一个经常被忽略的底层事实:对于一个在线服务来说,机器承受的实际压力往往更接近并发数,而不是 QPS。
根据经典的 利特尔法则(Little's Law):
这也是为什么很多团队会发现一个极其诡异的现象:某个接口上线半年一直稳定运行,某天下游数据库突然变慢了 30%,明明外部输入的 QPS 完全没变,结果自己服务的线程池却先满了,随后 RT 飙升,最终整个服务陷入雪崩。
问题从来不是流量变大了,而是因为 RT 变长,单位请求在系统里停留得更久了。如果 QPS 保持 500 不变,但 RT 从 50ms 上涨到 100ms,那么系统中的在途请求数(持有的线程和内存资源)会直接翻倍。
从这个角度看,QPS 其实只是外部观察指标,RT 反映的是系统内部状态,而 Concurrency 才是连接二者的桥梁。 这也是为什么很多成熟系统最终控制的是并发数而不是 QPS。因为 QPS 只是输入流量,而 Concurrency 才是真正占用线程、连接、内存和 CPU 资源的对象。QPS 并不等于系统容量。
2. 新 Pod 扩容时的“冷启动脆断”
在大促或者突发流量把现有集群逼到绝路时,HPA 终于憋出招来,紧急扩容了 50 个 Pod。按照静态均摊逻辑,每个新 Pod 分到了满额的 2000 QPS 指标。
然而,一个处于完全冷状态的实例,由于本地缓存空白(Cache Miss)、连接池尚未建立、下游连接未预热、以及 JVM 的分层编译(Tiered Compilation)此时尚未完成充分优化(JIT 优化尚未收敛),其真实的实际处理能力往往远低于稳定运行状态。
当 Readiness Probe 刚探测通过后,上游负载均衡可能在很短时间内开始向该实例分配正常流量。如果直接灌进来满额的静态均摊流量,会导致这个新 Pod 瞬间 CPU 爆表、RT 飙升,进而引发健康检查超时被直接干死。扩一个死一个,形成骨牌雪崩。
【走向自适应并发控制】
这也是为什么 Netflix 的 Concurrency Limits、Envoy 的 Adaptive Concurrency Filter、以及越来越多现代 Service Mesh 和 API Gateway,虽然实现细节不同,但都在向基于实时反馈的并发控制演进。
它们的核心思想和 TCP 的拥塞控制(Congestion Control)非常接近:
- 不断探测系统当前的极限
- 出现排队迹象和延迟变长时快速收缩并发窗口
- 窗口恢复后缓慢扩张
很多现代系统甚至不再把 CPU 作为主要控制信号。因为 CPU 打满未必代表系统已经拥塞,而排队和 RT 恶化往往能更早地反映瓶颈。因此,更常见的做法是将 In-flight Requests(在途请求数)、Queue Length(队列长度)和 RT 变化趋势作为核心的反馈信号,而将 CPU、Memory 和 Load Average 退化为辅助的兜底保护指标。
核心思想示意(AIMD)这种动态窗口最朴素的实现,其实非常类似 TCP 里的 AIMD(Additive Increase Multiplicative Decrease) 算法。
- 系统健康(RT 正常):并发窗口缓慢递增()
- 系统开始拥塞(RT 变长):并发窗口快速收缩()
这种策略最大的优点是稳定:扩张很慢,避免流量突增瞬间冲垮系统;收缩快,避免雪崩进一步扩散。
为了说明核心思想,这里放一段极简的逻辑示意代码(实际生产环境一般会使用更复杂的 AIMD、Vegas 或者 Netflix Gradient2 算法,下面代码只是为了剥离细节看本质):
【深入浅出:Vegas 与 Gradient2 思想与工程痛点】
当然,工业界的顶尖生产底座并不会真的像上面 AIMD 的示意代码那么粗暴。
以 Netflix Limits 或 Envoy 过滤器背后的算法演进为例,其核心逻辑都是在实时观察和对比:当前短窗口的平均 RT (R_current) vs 历史长周期无排队时的基准 RT (R_min) 二者之间的差值。
为了方便说明思想,在排除了 RTT EWMA 采样、长周期均值窗口以及分歧检测(Divergence Detection)等工程细节后,我们可以将其近似理解为如下的梯度调节公式:
- Limit_new = Limit_current * (R_min / R_current) + QueueSize
复制代码- 当系统没有排队时:接近,梯度接近 1,并发窗口保持稳定,并配合一个微小的(常数,比如 1~3)允许系统缓慢向外探测更高的边界。
- 当系统开始出现排队时:开始变大,变成一个小于 1 的系数(比如 0.85),并发窗口立刻平滑地向内收缩。
由于直接观察 RT 的变化趋势,这种方法往往能比 CPU 利用率更早、更精准地发现系统正接近饱和状态。这与 TCP Vegas 的核心思想非常接近:利用延迟变化预测拥塞,而不是等到系统已经发生资源耗尽、线程池打满甚至雪崩之后再被动处理。
真正踩过坑才知道的工程细节:RT 选型的两难
然而,实际生产中,RT 基准值的选择本身就是一个巨大的难题。
采样 RT 时,我们到底看平均值、P50 还是 P99?
- 使用平均值:极易受到长尾请求的严重污染,导致窗口无意义地频繁收缩。
- 使用 P99:虽然代表了极端长尾,但可能导致窗口过度保守,系统白白空闲。
- 使用 P50:又容易漏掉真实的拥塞苗头。
因此,在工业界的真实治理底座里,通常需要引入 EWMA(指数加权移动平均),配合长短周期基线对比来降低噪声。同时,当硬件环境、下游依赖或者网络拓扑长期发生变化时,历史基准 RT(R_min)可能逐渐失真,从而导致窗口判断出现偏差,因此很多实现都会对基准值进行动态更新或老化处理。
总结
梳理完这条演进线,我们就能清晰地看清现代分布式流量治理的全局拓扑。
- 令牌桶解决的是流量配额问题(关心“谁能进来”,聚焦于公平性、隔离与预算控制)。
- 自适应并发控制解决的是容量边界问题(关心“系统还能承受多少”,聚焦于单机物理边界的动态自愈)。
两者并不是替代关系,而是现代流量治理体系中相互补充的两层保护机制。
[外部流量引入]
|
v
+------------------------------------------+
| 外层防护:Rate Limiting (令牌桶/配额管理) | --> 过滤租户、防刷、控预算
+------------------------------------------+
|
v
+------------------------------------------+
| 内层内核:Adaptive Concurrency Control | --> 盯紧 In-flight、RT 反馈与 Vegas 思想
| (Vegas / Gradient / Gradient2) | 动态收缩/扩张并发窗口,保护单机物理边缘
+------------------------------------------+
|
v
[业务线程池处理]
放弃对静态阈值的盲目迷信,转而通过在途请求数(Concurrency)去和系统的硬件负载及延迟做实时对齐,对于大规模分布式系统而言,这往往是流量治理走向精细化的重要一步。
大伙儿组里在物理网关或者 Mesh 边车里,目前有真正把 Gradient2 这种自适应并发算法推向大规模生产的吗?大家在实操中遇到过哪些调参的坑(比如长短周期滑动窗口的参数设计,或者规避瞬时抖动干扰)?欢迎在回帖里聊聊你们组真实的流量治理故事。
利益相关提示:给别人加米不会扣除自己的积分/米,看完觉得有点收获的老铁,顺手赏两颗米鼓励一下硬核技术分享,拜谢! |