3. Rate Limiting 基础:限流定义、限流目的、常见限流算法
3.1 限流定义与核心目的
限流(Rate Limiting) 是一种通过控制单位时间内允许通过的请求数量,来保护系统不被突发流量冲垮的防护手段。在尾延迟压测场景中,限流是熔断的前置防线——它确保系统在进入过载状态前,主动拒绝超出容量的请求,从而维持已接受请求的服务质量。
核心目的:
- 防止资源耗尽: 避免 CPU、内存、连接池、数据库连接等资源被无限请求耗尽。
- 保障公平性: 确保不同租户或用户之间不会因为某一方的突发流量而影响整体可用性。
- 平滑流量尖峰: 将突发的高峰流量整形为系统可处理的平稳速率。
- 降低尾延迟: 通过主动拒绝超量请求,避免请求排队堆积导致 P99/P999 延迟急剧上升。
- 配合熔断策略: 限流是熔断的前置信号,当限流触发频率过高时,可联动熔断进行更激进的保护。
3.2 常见限流算法对比
| 算法 | 核心原理 | 突发处理能力 | 平滑度 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|---|
| 计数器(固定窗口) | 在固定时间窗口内累计请求数,超过阈值则拒绝 | 差(窗口边界易突发) | 低 | 极低 | 简单 API 限流、测试环境 |
| 滑动窗口计数器 | 将时间窗口细分为多个小格,滑动统计请求数 | 中等 | 中 | 低 | 生产环境基础限流 |
| 漏桶(Leaky Bucket) | 请求先进入桶中,以固定速率漏出;桶满则丢弃 | 差(强制平滑) | 高 | 中 | 流量整形、数据库连接池 |
| 令牌桶(Token Bucket) | 以固定速率生成令牌,请求需获取令牌才能通过 | 好(允许一定突发) | 高 | 中 | 微服务网关、API 网关 |
3.3 令牌桶算法(Token Bucket)
令牌桶是生产环境中最常用的限流算法,它允许一定程度的突发流量,同时保证长期平均速率可控。算法核心参数:capacity(桶容量)、rate(令牌生成速率)。
压测实践建议: 在尾延迟压测中,令牌桶的
capacity 应设置为系统能承受的瞬时突发上限,rate 应设置为系统长期稳定处理的 QPS。例如:capacity = 200,rate = 100/s,允许 2 秒的突发缓冲。
3.4 漏桶算法(Leaky Bucket)
漏桶算法强制请求以恒定速率流出,无论流入速率如何。它适合需要严格平滑流量的场景,但无法应对突发流量。
3.5 计数器算法(固定窗口)
计数器算法是最简单的限流实现,但存在明显的窗口边界突发问题:在窗口切换瞬间,可能允许两倍于阈值的请求通过。
// 固定窗口计数器伪代码
class FixedWindowCounter {
private long windowStart = System.currentTimeMillis();
private int count = 0;
private final int limit = 100; // 窗口内最大请求数
private final long windowSize = 1000; // 窗口大小(毫秒)
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
if (now - windowStart > windowSize) {
// 重置窗口
windowStart = now;
count = 0;
}
if (count < limit) {
count++;
return true;
}
return false;
}
}
避坑提醒: 固定窗口计数器在压测中容易产生"双倍突发"现象。例如:窗口大小为 1 秒,阈值为 100,在第 999ms 时通过 100 个请求,第 1000ms 时窗口重置,又可通过 100 个请求——实际 2ms 内通过了 200 个请求。生产环境建议使用滑动窗口或令牌桶替代。
3.6 算法选型决策树
在尾延迟压测场景中,算法选型需结合系统特性与压测目标:
- 需要允许突发流量? → 令牌桶(推荐)
- 需要严格平滑输出? → 漏桶
- 实现简单、对精度要求不高? → 滑动窗口计数器
- 压测目标为熔断协同测试? → 令牌桶(便于观察限流→熔断的过渡行为)
- 资源极度受限(如嵌入式)? → 固定窗口计数器(需配合抖动预防)