4. 限流算法(一):固定窗口计数器算法原理、优缺点、适用场景
固定窗口计数器算法是限流领域最直观、最容易实现的算法之一。它将时间划分为固定的窗口(例如 1 秒、1 分钟),并在每个窗口内维护一个计数器,当请求到达时计数器加 1,若计数器超过预设阈值则拒绝后续请求,直到下一个窗口开始计数器重置。
4.1 算法原理
核心思想:以固定的时间窗口为粒度,统计窗口内的请求总量,超过阈值则触发限流。具体步骤如下:
- 定义窗口大小:设定一个固定的时间长度,如 1 秒、1 分钟或 1 小时。
- 初始化计数器:在每个窗口开始时,将计数器置为 0。
- 请求到达:判断当前请求所属的时间窗口。
- 计数判断:若当前窗口计数器 < 阈值,则允许请求通过,计数器加 1;否则拒绝请求。
- 窗口切换:当时间进入下一个窗口时,计数器重置为 0,重复上述过程。
关键参数:窗口大小(window_size)和 阈值(threshold)。例如:window_size = 1s, threshold = 100,表示每秒最多允许 100 个请求。
4.2 核心优缺点
| 维度 | 说明 | 影响 |
|---|---|---|
| 实现简单 | 仅需一个计数器 + 时间戳判断,无需复杂数据结构 | 开发成本低,适合快速落地 |
| 内存占用低 | 每个限流维度只需存储窗口起始时间和计数值 | 适合大规模分布式部署 |
| 临界突变问题 | 窗口切换瞬间可能出现两倍阈值的流量尖峰 | 严重时导致后端过载 |
| 精度粗糙 | 窗口内流量分布不均匀,无法平滑限流 | 不适合对突发敏感的场景 |
| 时间边界敏感 | 窗口对齐方式(自然时间 vs 滑动时间)影响结果 | 需要根据业务选择对齐策略 |
临界突变问题详解:假设阈值 100 请求/秒。若前 0.9 秒内无请求,最后 0.1 秒涌入 100 个请求;同时下一个窗口的前 0.1 秒又涌入 100 个请求。实际 0.2 秒内处理了 200 个请求,远超系统容量。这是固定窗口算法最致命的缺陷。
4.3 适用场景
- 对突发容忍度较高的场景:如非关键日志上报、批量数据同步、定时任务触发。
- 简单 API 限流:对外提供粗粒度 QPS 限制,允许短时突发。
- 资源受限环境:嵌入式设备、边缘节点,无法承载复杂算法。
- 快速原型验证:项目初期快速上线限流功能,后续再优化。
- 多级限流的前置过滤:作为第一道粗筛,配合滑动窗口或令牌桶做精细控制。
4.4 代码示例(Go 语言)
package main
import (
"sync"
"time"
)
type FixedWindowLimiter struct {
mu sync.Mutex
windowSize time.Duration
threshold int
count int
windowStart time.Time
}
func NewFixedWindowLimiter(windowSize time.Duration, threshold int) *FixedWindowLimiter {
return &FixedWindowLimiter{
windowSize: windowSize,
threshold: threshold,
windowStart: time.Now(),
}
}
func (l *FixedWindowLimiter) Allow() bool {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
// 如果当前时间已超出窗口范围,重置计数器
if now.Sub(l.windowStart) >= l.windowSize {
l.windowStart = now
l.count = 0
}
// 判断是否超过阈值
if l.count < l.threshold {
l.count++
return true
}
return false
}
4.5 金融压力测试中的注意事项
金融场景适配建议:固定窗口算法适合作为熔断前的第一道防线,用于限制非关键链路的请求量。对于核心交易链路,建议配合滑动窗口或令牌桶算法使用,避免临界突变导致交易系统过载。
- 窗口对齐策略:金融系统中建议使用自然时间窗口(如每分钟的 00 秒对齐),便于监控和审计。
- 阈值设置:建议设置为系统容量的 70%~80%,预留缓冲应对临界突变。
- 监控告警:对限流触发次数进行监控,若频繁触发说明阈值设置不合理或系统需要扩容。
- 降级配合:触发限流后应返回明确的错误码(如 429 Too Many Requests),并触发降级逻辑。
4.6 与其他算法的对比
| 对比维度 | 固定窗口 | 滑动窗口 | 令牌桶 | 漏桶 |
|---|---|---|---|---|
| 实现复杂度 | ★☆☆☆☆ | ★★★☆☆ | ★★☆☆☆ | ★★☆☆☆ |
| 临界突变 | 严重 | 无 | 无 | 无 |
| 突发处理 | 允许窗口内突发 | 允许窗口内突发 | 允许一定突发 | 平滑处理 |
| 内存开销 | 极低 | 中等 | 低 | 低 |
| 适用场景 | 粗粒度限流 | 精细限流 | 允许突发场景 | 严格平滑场景 |