3. 常见限流算法(上):固定窗口计数器算法
固定窗口计数器算法(Fixed Window Counter)是所有限流算法中最直观、最容易实现的一种。它将时间划分为固定长度的窗口(例如 1 秒、1 分钟),在每个窗口内维护一个计数器,当请求到达时计数器加 1;如果计数器超过预设阈值,则拒绝后续请求;窗口结束时计数器重置。
3.1 算法原理
固定窗口算法的核心思想可以概括为三个步骤:
- 划分时间窗口:将时间轴均匀切分为固定长度的窗口(如 1 秒)。
- 计数累加:每个请求到达时,当前窗口的计数器加 1。
- 阈值判断:若计数器 ≤ 阈值,请求通过;否则请求被拒绝或排队。
窗口切换时,计数器立即归零,开始新一轮计数。
核心公式: 当前窗口请求数 ≤ 阈值 → 允许;否则 → 拒绝。
3.2 代码实现(Go 语言示例)
以下是一个基于 Go 语言的固定窗口计数器限流器实现,使用 sync.Mutex 保证并发安全:
package limiter
import (
"sync"
"time"
)
// FixedWindowLimiter 固定窗口限流器
type FixedWindowLimiter struct {
mu sync.Mutex
limit int // 窗口内最大请求数
window time.Duration // 窗口大小
count int // 当前窗口计数
windowStart time.Time // 当前窗口起始时间
}
// NewFixedWindowLimiter 创建固定窗口限流器
func NewFixedWindowLimiter(limit int, window time.Duration) *FixedWindowLimiter {
return &FixedWindowLimiter{
limit: limit,
window: window,
windowStart: time.Now(),
}
}
// Allow 判断请求是否允许通过
func (l *FixedWindowLimiter) Allow() bool {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
// 如果当前时间已超出窗口范围,重置计数器
if now.Sub(l.windowStart) >= l.window {
l.count = 0
l.windowStart = now
}
// 判断是否超过阈值
if l.count < l.limit {
l.count++
return true
}
return false
}
3.3 优缺点分析
| 维度 | 说明 |
|---|---|
| 优点 |
|
| 缺点 |
|
⚠️ 临界突变问题详解: 假设限流阈值为 100 次/秒。如果在第 1 秒的最后 10ms 内通过了 100 个请求,紧接着第 2 秒的前 10ms 内又通过了 100 个请求,那么这 20ms 内实际通过了 200 个请求,是阈值的 2 倍。这是固定窗口算法最致命的缺陷。
3.4 适用场景
- 对突发流量容忍度较高的 API:如非关键性的日志上报、统计埋点。
- 粗粒度限流:分钟级或小时级的配额控制,临界突变影响较小。
- 资源受限环境:嵌入式设备、边缘节点,需要极简实现。
- 配合其他算法使用:作为前置快速过滤,再配合滑动窗口或令牌桶做精细控制。
💡 实践建议: 在生产环境中,如果使用固定窗口算法,建议将窗口粒度缩小(如 100ms 窗口)或配合随机丢弃策略来缓解临界突变问题。对于模型服务 API,通常推荐使用滑动窗口或令牌桶算法以获得更平滑的限流效果。