思维导图
前言
本文将深入探讨常见的限流算法及其适用场景,并详细解析基于 QPS 的限流方案。从如何设置合理的限流阈值,到请求被限流后的处理策略。
常见的限流算法
漏桶
- 核心原理
请求以任意速率进桶,以恒定速率
出桶。若桶满则丢弃或排队等待 - 适用场景
需要严格平滑流量
的场景,如支付系统、金融交易 - 优点
实现简单,适合流量整形。输出的流量绝对平滑 - 缺点
无法应对突发流量 - 工具支持
Sentinel 限流行为RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
,使用示例详见 PaceFlowDemo
令牌桶
- 核心原理
以固定速率向桶中添加令牌,请求前需要获取令牌。若桶中存在令牌则处理,否则限流 - 适用场景
允许突发流量,但服务不会被流量压垮。如秒杀场景 - 优点
能处理突发流量,用户体验相对漏桶好 - 缺点
突发流量可能会导致瞬时资源占用过高
相比于漏桶实现更复杂 - 工具支持
Google Guava
的 RateLimiter
Sentinel 限流行为RuleConstant.CONTROL_BEHAVIOR_WARM_UP
,使用示例详见 WarmUpFlowDemo
固定窗口
- 核心原理
将时间划分为固定长度的窗口(如1分钟),统计窗口内的请求数。若请求数超过阈值,则后续时间窗口内的请求均会被拒绝 - 适用场景
对限流精度要求不高的简单场景 - 优点
实现简单,内存占用低 - 缺点
临界值问题:在窗口切换时,可能存在2倍流量(如1:59 和 2:00 的请求可能分属2个窗口)
滑动窗口
- 核心原理
将时间划分为更细粒度的子窗口,统计当前时间点向前滑动的时间窗口内总请求数 - 适用场景
高精准的限流。如 API 接口 - 优点
精准限流
可动态调整窗口大小,适应流量波形 - 缺点
实现复杂;内存和计算成本较高 - 工具支持
详见 Sentinel LeapArray 实现
基于 QPS 限流
基于 QPS 限流是业内最常用的限流方案,常应用于接口限流。限流的关键在于回答2个问题。
- 限流阈值如何设置?
- 限流后如何处理?
下面将对这 2 个问题进行深入探讨
限流阈值如何设置
阈值的设置一般有以下4种方式:压测、服务性能数据、借鉴已有功能、手动计算预估接口响应时间。
服务性能数据
需要服务接入可观测,例如 Promethues。根据业务高峰期的QPS,以及服务其他性能数据,例如 CPU、内存等,冗余 20% ~ 30%
的量。
不过业务高峰期的 QPS 是一个动态值,会随着业务发展的变化而变化。当变化较大时,需要及时调整阈值。
全链路压测
这里的压测指的是全链路压测,而非针对单接口的压测。
基于全链路压测,得到的接口 QPS 阈值会更加准确。
不管是全链路压测、还是单接口压测随着 QPS 不断增加我们都能得到以下 3 个值
A点: 性能最好,响应时间最快
B点: 吞吐量最高
C点: 并发最高,资源利用率最高,但是系统处于崩溃临界点
一般而言,我们会在 [B,C] 之间选择一个阈值,作为限流阈值。
借鉴
如果该接口无法做压测,或者需求急时间紧,那么可以考虑借鉴类似相关已有接口的限流阈值。
例如 A 接口的调用场景与 B 接口一致,那么就可以用 B 接口的限流阈值来确定 A 接口的限流阈值。
手动计算
如果是一个全新的业务那怎么办?
这个时候可以考虑手动计算。
例如,一个查询接口,只会查询数据库,且数据库查询平均响应时间为 10ms,再增加 10ms 作为 CPU 计算时间,且实例的 CPU 为 4核,那么该接口的 QPS 可以简单计算为: 1000ms / 20ms * 4 = 200
限流后的处理策略
服务端角度
- 直接拒绝
例如,返回限流特定状态码 - 排队等待
例如,允许等待 20ms,如果 20ms 内未能被处理,则返回限流特定状态码。如 Sentinel 的限流行为RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
- 冷启动
让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间。如 Sentinel 的限流行为RuleConstant.CONTROL_BEHAVIOR_WARM_UP
- 同步转异步
可以同步转异步的场景并不多。常见于数据上报类型的接口,客户端不关注服务端的返回。
客户端角度
客户端要根据服务端返回的特定状态码提示用户,提升用户体验。