3. 限流算法基础:计数器、滑动窗口、漏桶、令牌桶的原理与适用场景对比
限流是保障系统稳定性的第一道防线。在熔断降级自愈体系中,限流算法决定了“何时触发保护”以及“保护力度如何”。不同的算法在精度、内存开销、突发处理能力上差异显著。本节从原理出发,对比四种核心算法的适用场景。
3.1 计数器算法(固定窗口)
计数器算法是最直观的限流实现。它将时间划分为固定的窗口(如 1 秒),每个窗口内维护一个计数器,请求到达时计数器加 1;当计数器超过阈值时,拒绝后续请求;窗口结束时重置计数器。
核心特征:实现简单,内存占用极低(仅需一个计数器 + 时间戳),但存在“窗口临界突变”问题——在窗口切换的瞬间,可能允许两倍于阈值的突发流量通过。
| 维度 | 说明 |
|---|---|
| 精度 | 低,仅按整窗口统计,无法感知窗口内的流量分布 |
| 内存开销 | 极低,仅需 1 个计数器 + 1 个时间戳 |
| 突发处理 | 差,窗口切换时允许 2 倍突发 |
| 适用场景 | 对精度要求不高、流量平稳的内部系统;或作为粗粒度前置过滤 |
3.2 滑动窗口算法
滑动窗口将固定窗口进一步细分为多个小格子(如 1 秒窗口拆成 10 个 100ms 的格子)。每个格子独立计数,窗口整体统计最近 N 个格子的累计值。随着时间推移,窗口“滑动”,丢弃过期格子,加入新格子。
精度提升:格子越细,精度越接近真实流量曲线。例如 10 个格子可将临界突变从 2 倍阈值降低到约 1.1 倍。代价是内存开销随格子数线性增长。
| 维度 | 说明 |
|---|---|
| 精度 | 高,格子越细精度越高,可有效抑制临界突变 |
| 内存开销 | 中等,与格子数成正比(通常 10~20 个格子) |
| 突发处理 | 较好,窗口内平滑统计,但仍允许窗口内的短时突发 |
| 适用场景 | 大多数通用限流场景,如 API 网关、Web 应用限流 |
3.3 漏桶算法
漏桶算法将请求视为水滴,以固定速率从桶底“漏出”处理。桶本身有容量上限,当桶满时新请求被丢弃。漏桶强制输出速率恒定,无论输入流量如何波动。
关键特性:漏桶的输出速率是平滑的,适合需要严格限制出口速率的场景(如数据库写入、下游慢服务保护)。但无法应对突发流量——即使桶内有容量,输出速率也固定不变。
| 维度 | 说明 |
|---|---|
| 精度 | 高,输出速率严格恒定 |
| 内存开销 | 低,仅需维护水位和上次漏水时间戳 |
| 突发处理 | 不支持突发,输出速率固定,无法利用空闲资源 |
| 适用场景 | 下游服务脆弱、需要严格限速的场景;流量整形(Traffic Shaping) |
3.4 令牌桶算法
令牌桶以固定速率向桶中放入令牌,请求到达时需获取一个令牌才能通过。桶有最大令牌数限制。与漏桶不同,令牌桶允许一定程度的突发——只要桶中有累积的令牌,请求可以一次性全部取出。
核心优势:令牌桶既能限制平均速率(通过令牌生成速率),又能允许短时突发(通过桶容量)。这是业界最广泛使用的限流算法,Guava RateLimiter、Nginx limit_req 均基于令牌桶或其变种。
| 维度 | 说明 |
|---|---|
| 精度 | 高,平均速率精确,突发可控 |
| 内存开销 | 低,仅需维护令牌数和上次填充时间戳 |
| 突发处理 | 优秀,桶容量允许短时突发,适合互联网流量特征 |
| 适用场景 | 通用限流首选;API 网关、微服务限流、流量突发场景 |
3.5 四种算法对比总结
| 算法 | 精度 | 内存开销 | 突发支持 | 输出平滑度 | 典型实现 |
|---|---|---|---|---|---|
| 计数器 | 低 | 极低 | 差(临界 2x) | 差 | 本地变量 + 定时器 |
| 滑动窗口 | 高(可调) | 中 | 较好 | 中 | Redis Sorted Set / 环形数组 |
| 漏桶 | 高 | 低 | 不支持 | 严格平滑 | 队列 + 定时漏出 |
| 令牌桶 | 高 | 低 | 优秀 | 中(允许突发) | Guava RateLimiter / Nginx |
选型建议:在熔断降级自愈体系中,推荐以令牌桶作为主限流算法,配合滑动窗口做精细化监控统计。漏桶用于下游脆弱服务的出口限速,计数器仅作为极端轻量级的兜底方案。