滑动窗口算法是对“固定窗口算法”的一种优化,旨在解决窗口边界处的流量翻倍问题。
背景:固定窗口的缺陷
如果设置限流为“每秒 100 个请求”,使用固定窗口:
- 在
0.9s时涌入 100 个请求。 - 在
1.1s时又涌入 100 个请求。 - 站在固定窗口([0,1] 和 [1,2])的角度,都没有超速。但在
[0.9, 1.1]这短短 0.2s 内,系统处理了 200 个请求,瞬间压力翻倍。
滑动窗口原理
逻辑图解
时间轴 (1秒窗口)
|-----|-----|-----|-----|-----|
q1 q2 q3 q4 q5 (每个格子 200ms)
[ 当前 1s 统计窗口 ]
随着时间推移,窗口向右滑动一格:
|-----|-----|-----|-----|-----|
q2 q3 q4 q5 q6
[ 新的 1s 统计窗口 ]- 时间分片:将 1 秒的时间窗口划分为多个小格子(如 5 个格子,每个格子 200ms)。
- 窗口滑动:随着时间推移,整个窗口向前滑动,每次滑动一个格子的宽度。
- 动态计数:统计当前窗口内所有格子的请求总数。
Go 语言实现 (示例)
type SlidingWindow struct {
limit int // 限流阈值
windowSize time.Duration // 窗口大小
slotCount int // 格子数量
slots []int // 每个格子的计数
slotDur time.Duration // 每个格子的时长
mu sync.Mutex
}
func (w *SlidingWindow) Allow() bool {
w.mu.Lock()
defer w.mu.Unlock()
now := time.Now()
// 1. 计算当前时间落在哪一格
// (此处简化了滑动逻辑,实际生产中需使用循环数组并清理过期槽位)
currentIdx := (now.UnixNano() / int64(w.slotDur)) % int64(w.slotCount)
// 2. 统计当前窗口内所有格子的请求总数
sum := 0
for _, count := range w.slots {
sum += count
}
if sum < w.limit {
w.slots[currentIdx]++
return true
}
return false
}核心特点
- 精度高:格子划分得越细,滑动窗口的限流就越平滑。
- 解决临界问题:由于统计的是一个动态的时间区间,成功规避了固定窗口在边界处流量翻倍的风险。
应用场景
- Sentinel:阿里巴巴的开源限流组件 Sentinel 核心就采用了滑动窗口(LeapArray)。
- TCP 流量控制:TCP 协议中著名的滑动窗口也是基于类似的滑动窗口思想。