3. 限流算法基础:固定窗口算法原理与Go实现

固定窗口算法(Fixed Window Algorithm)是限流领域最直观、最容易实现的算法之一。它将时间划分为固定长度的窗口(如1秒、1分钟),在每个窗口内维护一个计数器,当请求到达时计数器加1;若计数器超过预设阈值,则拒绝后续请求,直到下一个窗口开始计数器重置。

3.1 算法核心原理

固定窗口算法的运行机制可以概括为以下三个步骤:

  1. 时间窗口划分:将时间轴按固定长度(如1秒)切分为连续的窗口,每个窗口拥有独立的计数器。
  2. 请求计数:每个请求到达时,判断其所属的当前窗口,并将该窗口的计数器加1。
  3. 阈值判断与拒绝:若当前窗口计数器超过预设的阈值(如100 QPS),则拒绝该请求;否则正常放行。
关键参数:窗口大小(windowSize)和阈值(limit)。窗口大小决定了计数重置的频率,阈值决定了单位时间内允许的最大请求量。

3.2 固定窗口算法的Go实现

以下是一个线程安全的固定窗口限流器实现,使用 sync.Mutex 保证并发安全:

package fixedwindow

import (
    "sync"
    "time"
)

// FixedWindowLimiter 固定窗口限流器
type FixedWindowLimiter struct {
    mu         sync.Mutex
    limit      int           // 窗口内最大请求数
    windowSize time.Duration // 窗口大小
    counter    int           // 当前窗口计数
    lastTime   time.Time     // 上次窗口开始时间
}

// NewFixedWindowLimiter 创建固定窗口限流器
func NewFixedWindowLimiter(limit int, windowSize time.Duration) *FixedWindowLimiter {
    return &FixedWindowLimiter{
        limit:      limit,
        windowSize: windowSize,
        lastTime:   time.Now(),
    }
}

// Allow 判断当前请求是否允许通过
func (l *FixedWindowLimiter) Allow() bool {
    l.mu.Lock()
    defer l.mu.Unlock()

    now := time.Now()
    // 如果当前时间已超过窗口结束时间,重置计数器
    if now.Sub(l.lastTime) >= l.windowSize {
        l.counter = 0
        l.lastTime = now
    }

    // 判断是否超过阈值
    if l.counter >= l.limit {
        return false
    }

    l.counter++
    return true
}
使用示例: limiter := NewFixedWindowLimiter(100, time.Second) 创建一个每秒最多允许100个请求的限流器。

3.3 固定窗口算法的临界突变问题

固定窗口算法虽然简单,但存在一个严重的缺陷——临界突变问题(Critical Burst Problem)。该问题发生在窗口切换的边界处,导致实际流量可能达到阈值的两倍。

3.3.1 问题现象

假设限流阈值为100 QPS,窗口大小为1秒。考虑以下场景:

  • 窗口A(0s~1s)的最后100ms内,突然涌入100个请求,此时窗口A的计数器达到阈值,后续请求被拒绝。
  • 紧接着窗口B(1s~2s)开始,计数器重置为0。在窗口B的前100ms内,又涌入100个请求。
  • 实际上,在200ms的时间跨度内(0.9s~1.1s),系统承受了200个请求,远超100 QPS的阈值。

下图直观展示了临界突变问题的产生过程:

固定窗口临界突变问题示意图 0s 1s 2s 窗口A(0s~1s) 窗口B(1s~2s) 临界爆发区(200ms内200请求) 临界突变问题说明 • 窗口A末尾100ms:100个请求(计数器满) • 窗口B开头100ms:100个请求(计数器重置) • 实际200ms内通过200个请求,超出阈值100%

3.3.2 问题根源分析

临界突变问题的根本原因在于:固定窗口算法只关心窗口内的总计数,而不关心请求在窗口内的分布情况。窗口边界处的计数器重置,使得相邻窗口的请求可以在极短时间内集中到达,绕过限流保护。

对比维度 正常场景 临界突变场景
请求分布 均匀分布在1秒内 集中在窗口边界两侧
实际QPS ≤ 100 可达200
限流效果 有效保护 限流失效
对系统影响 稳定 可能引发雪崩
实际危害:在微服务网关、API限流等场景中,临界突变问题可能导致后端服务在短时间内承受双倍流量冲击,引发连接池耗尽、响应超时甚至服务雪崩。该问题在流量突增的秒杀、抢购场景下尤为致命。

3.4 固定窗口算法的适用场景

尽管存在临界突变问题,固定窗口算法在以下场景中仍然适用:

  • 对突发流量不敏感:如日志收集、批量任务调度等允许短时波动的场景。
  • 粗粒度限流:分钟级或小时级的限流,窗口边界处的突发影响相对较小。
  • 作为兜底策略:与其他限流算法(如滑动窗口、令牌桶)配合使用,提供基础保护。

3.5 本章小结

固定窗口算法是限流领域的入门级算法,实现简单、性能优异,但其临界突变问题限制了在高精度限流场景中的应用。理解该问题的本质——窗口边界处的计数器重置导致流量聚集——是后续学习滑动窗口算法和令牌桶算法的重要基础。在实际项目中,建议根据业务对突发流量的容忍度,选择是否使用固定窗口算法或升级为更平滑的限流方案。