2、常见限流算法详解(上):计数器算法原理、滑动窗口算法原理、两种算法的优缺点对比

在微服务架构中,API Gateway 的限流策略直接决定了后端服务的稳定性与资源利用率。本章将深入剖析两种最基础的限流算法——计数器算法与滑动窗口算法,从原理、实现到优缺点进行系统性对比,为后续参数调优打下坚实的理论基础。

2.1 计数器算法原理

计数器算法(Counter Algorithm)是最简单直观的限流实现方式,其核心思想是:在一个固定的时间窗口内,统计请求总数,一旦超过预设阈值则拒绝后续请求。

2.1.1 算法流程

  1. 定义时间窗口:设定一个固定的时间长度,例如 1 秒、1 分钟或 1 小时。
  2. 初始化计数器:在每个时间窗口开始时,将计数器重置为 0。
  3. 请求到达:每收到一个请求,计数器加 1。
  4. 阈值判断:如果计数器值 ≤ 阈值,则放行请求;否则直接拒绝。
  5. 窗口切换:当前时间窗口结束后,自动进入下一个窗口,计数器归零。
典型应用场景:Nginx 的 limit_req_zone 模块、Redis 的 INCR + EXPIRE 实现、单机内存计数器。

2.1.2 核心缺陷:临界突变问题

计数器算法存在一个严重的 “临界突变” 问题:由于时间窗口是固定的,请求可以在窗口边界处集中爆发,导致实际流量远超阈值。例如:

  • 设定阈值:100 请求/秒
  • 第 1 秒的最后 10ms:涌入 100 个请求
  • 第 2 秒的最初 10ms:再次涌入 100 个请求
  • 实际在 20ms 内通过了 200 个请求,相当于 10,000 请求/秒的瞬时流量

这种突发流量足以击穿后端服务,而计数器算法对此毫无感知。

计数器算法原理与临界突变问题 时间窗口 1(0~1s) 计数器:100/100(已满) 时间窗口 2(1~2s) 计数器:100/100(已满) 边界 最后 10ms:100 请求 最初 10ms:100 请求 20ms 内通过 200 请求 → 瞬时 10,000 QPS 后端服务被击穿 ⚠ 计数器算法无法感知窗口边界处的请求集中爆发,导致限流失效

2.2 滑动窗口算法原理

滑动窗口算法(Sliding Window Algorithm)是对计数器算法的改进,通过 将固定窗口细分为多个小格子(slot),并随着时间推移动态滑动窗口,从而平滑流量检测,消除临界突变问题。

2.2.1 核心机制

  • 窗口划分:将整个时间窗口(如 1 秒)划分为 N 个等长的小格子(如 10 个格子,每个 100ms)。
  • 滑动前进:窗口以格子为单位向前滑动,每次滑动丢弃最旧的格子数据,加入最新的格子数据。
  • 实时统计:当前窗口的总请求数 = 所有格子计数之和,与阈值比较决定是否限流。
实践建议:格子数量越多,滑动窗口的精度越高,但内存消耗也越大。生产环境中通常将 1 秒划分为 10~20 个格子,在精度与资源之间取得平衡。

2.2.2 算法步骤

  1. 初始化:创建长度为 N 的环形数组或队列,每个元素初始值为 0。
  2. 请求到达:计算当前时间所属的格子索引,将该格子计数加 1。
  3. 窗口滑动:检查当前时间是否已进入下一个格子范围,若是则滑动窗口:将最旧格子计数归零,并更新窗口起始位置。
  4. 阈值判断:累加当前窗口内所有格子计数,若总和 ≤ 阈值则放行,否则拒绝。
滑动窗口算法原理(1 秒窗口 / 10 个格子) 0~100ms 15 100~200ms 12 200~300ms 8 300~400ms 20 400~500ms 5 500~600ms 18 600~700ms 10 700~800ms 7 800~900ms 14 900~1000ms 9 当前窗口(已滑过的格子) 当前窗口(活跃格子) 时间轴 → 滑动方向 当前窗口总请求数 = 15+12+8+20+5+18+10+7+14+9 = 118 若阈值 = 100,则拒绝后续请求;若阈值 = 120,则继续放行 💡 滑动窗口通过细粒度格子 + 动态滑动,将流量检测精度从“秒级”提升到“百毫秒级” 格子数越多,精度越高,但内存开销也线性增长

2.3 两种算法优缺点对比

为了帮助你在实际项目中做出正确的技术选型,下面从多个维度对两种算法进行系统性对比。

对比维度 计数器算法 滑动窗口算法
实现复杂度 极低,仅需一个计数器 + 时间戳 中等,需要环形数组/队列 + 格子管理
内存开销 O(1),仅存储一个整型变量 O(N),N 为格子数(通常 10~20)
流量检测精度 低,仅能检测整个窗口的总量 高,可检测到格子级别的流量变化
临界突变防御 无,窗口边界处可爆发 2 倍流量 强,窗口滑动平滑,无突变窗口
突发流量容忍 窗口内任意时刻均可突发至阈值 突发流量被分散到多个格子,更平滑
适用场景 低精度要求、资源受限、简单限流 高精度要求、生产环境、微服务网关
典型实现 Redis INCR+EXPIRE、Nginx limit_req Sentinel 滑动窗口、Guava RateLimiter
选型警告:在微服务 API Gateway 场景中,强烈建议使用滑动窗口算法。计数器算法虽然简单,但其临界突变问题在高并发下极易导致后端服务雪崩。如果必须使用计数器算法,请配合“预热”或“排队”机制进行补偿。

2.4 实战选型建议

基于上述对比,给出以下选型决策树:

  • 场景一:单机限流,QPS < 1000,可接受秒级波动
    • 推荐:计数器算法
    • 理由:实现简单,资源开销极低,性能损耗可忽略
  • 场景二:分布式限流,QPS > 1000,要求毫秒级精度
    • 推荐:滑动窗口算法(格子数 10~20)
    • 理由:消除临界突变,流量曲线平滑,保护后端服务
  • 场景三:API Gateway 全局限流,多维度(IP、用户、接口)
    • 推荐:滑动窗口算法 + Redis 分片存储
    • 理由:支持大规模分布式计数,精度与扩展性兼顾
性能数据参考:在 4C8G 的 API Gateway 节点上,滑动窗口算法(20 格子)的单机吞吐量可达 50,000 QPS,额外延迟 < 0.5ms,完全满足生产环境要求。