用数组实现滑动窗口限流的核心是预分配固定长度计数数组,每元素代表一个时间片请求量,通过索引定位与原子操作保障并发安全;采用懒清理、单机内存存储,适合中等qps场景。

用数组实现滑动窗口限流,核心是预分配固定长度的计数数组,每个元素代表一个时间片的请求量,通过索引定位+原子操作完成并发安全的统计与清理。它轻量、无依赖、低延迟,适合单机中等QPS场景(如每秒几十到几百次请求),不依赖Redis等外部组件。
数组结构设计与时间片划分
关键不是“有多少秒”,而是“分多少片”。例如60秒窗口分12片,每片5秒;或30秒分30片,每片1秒。粒度越细,精度越高,但内存和计算开销略增。
- 窗口总长 windowSec(秒)和分片数 shards 需在初始化时确定
- 每片时长 = windowSec × 1e9 / shards(单位纳秒,适配 time.Now().UnixNano())
- 预分配 []int64 数组,长度为 shards,所有元素初始为 0
- 当前时间片索引 = (now.UnixNano() / shardDuration) % int64(shards),注意强制转 int64 防溢出
请求判断与原子更新逻辑
每次请求进来,不遍历全数组,只做三件事:算当前片索引、查过期片范围、累加当前片计数。过期数据采用「懒清理」——不主动删,只在读取时跳过或置零。
- 用 atomic.AddInt64(&slice[i], 1) 更新当前片,避免 map 并发 panic
- 统计窗口内总请求数时,遍历所有片,对每片检查其对应时间戳是否仍在窗口内:
某片时间戳 = index × shardDuration,对比 now.UnixNano() - windowSec×1e9 - 若某片时间已早于窗口起始时间,该片计数视为 0(或显式 atomic.StoreInt64 置零)
- 总和超过阈值即拒绝,否则放行
实战控制变量请求速率的关键细节
限流效果是否稳定,取决于三个变量的协同:窗口长度、分片数、阈值。它们不是孤立配置,而需按业务节奏反推。
- 窗口长度决定防护周期,如防秒级突刺用 1–5 秒,防分钟级爬虫用 60 秒
- 分片数影响平滑度,12–60 片较均衡;少于 10 片接近固定窗口,多于 100 片易因频繁索引计算引入抖动
- 阈值应设为「系统可持续处理的平均速率 × 窗口长度」,而非拍脑袋数字;例如服务稳态 QPS 为 30,则 60 秒窗口阈值建议设为 1800,再留 10%–20% 余量
- 务必设置请求标识(如用户ID + 接口路径),避免不同用户共用同一数组导致误限或绕过
容错与降级保障
数组限流跑在本地内存,天然无网络依赖,但需应对进程重启、多实例部署等现实问题。
- 进程重启后数组清零 → 若需跨重启一致性,必须搭配 Redis 做兜底,或接受短暂“重启后限流失效”
- 多实例部署时,各实例独立计数 → 此时数组方案退化为单机限流,无法替代分布式方案
- 可加开关支持自动降级:当检测到 CPU 负载 >90% 或 GC 频繁时,临时切到更粗粒度(如合并 2 片为 1 片)或直接放行
- 记录每片计数的 max 值和 last update 时间,用于监控异常毛刺(如某片突增 10 倍)










