当前位置: 首页 > news >正文

深入浅出限流算法(一):简单但有“坑”的固定窗口计数器

在现代分布式系统和 API 设计中,限流 (Rate Limiting) 是一个不可或缺的环节。它像一个尽职的门卫,保护着我们的服务资源,防止因突发流量或恶意攻击导致系统过载,同时也能确保资源的公平分配。

实现限流的算法多种多样,各有优劣。今天,我们就从最基础、最简单的一种开始聊起:计数器算法(固定窗口)。

什么是固定窗口计数器?

想象一下,一个热门景点入口,规定每分钟只能进入 100 位游客。工作人员手里有一个计数器和一个秒表。

  1. 窗口开始: 每分钟开始时(例如,10:00:00),工作人员将计数器清零。

  2. 请求到达: 每当有游客想进入(相当于一个 API 请求),工作人员就将计数器加 1。

  3. 检查限制: 在增加计数器后,工作人员检查计数器的数字是否超过了 100。

    • 如果未超过 100,游客被允许进入。
    • 如果已超过 100,则告知该游客:“本分钟名额已满,请稍后再试”(请求被拒绝)。
  4. 窗口结束与重置: 当这一分钟结束(到达 10:01:00),无论之前计数是多少,工作人员立即将计数器清零,准备下一个一分钟的计数。

这就是固定窗口计数器的核心思想:在一个固定的时间窗口内,维护一个计数器,记录该窗口接收到的请求数量。当请求到来时,计数器加一,如果超过设定的阈值,则拒绝请求。每个时间窗口结束后,计数器自动清零。

Java 实现(巧妙版)

下面是一个使用 Java 实现的固定窗口计数器。这个版本的巧妙之处在于,它避免了使用一个单独的定时任务来周期性地重置计数器,而是在每次请求到达时检查是否需要开启新的窗口。

import java.time.LocalTime; // 这个 import 在代码中实际未使用,可以移除
import java.util.concurrent.atomic.AtomicInteger;public class RateLimiterSimpleWindow {// 阈值 (例如,每秒允许的请求数 QPS)private static Integer QPS = 2;// 时间窗口大小(毫秒)private static long TIME_WINDOWS = 1000; // 1秒// 请求计数器 (使用 AtomicInteger 保证线程安全)private static AtomicInteger REQ_COUNT = new AtomicInteger(0);// 当前时间窗口的开始时间戳private static long START_TIME = System.currentTimeMillis();// 使用 synchronized 确保检查时间、重置计数器和更新开始时间这组操作的原子性public synchronized static boolean tryAcquire() {long now = System.currentTimeMillis();// 1. 检查当前时间是否已经超出了当前窗口if ((now - START_TIME) > TIME_WINDOWS) {// 如果是,说明需要开启新的时间窗口REQ_COUNT.set(0); // 重置计数器START_TIME = now; // 将窗口的开始时间更新为当前时间}// 2. 无论是否重置,都尝试为当前请求增加计数//    并检查增加后的计数值是否仍在允许的范围内return REQ_COUNT.incrementAndGet() <= QPS;}public static void main(String[] args) throws InterruptedException {// 简单的测试逻辑for (int i = 0; i < 10; i++) {Thread.sleep(250); // 每 250ms 发送一个请求long currentTimeMillis = System.currentTimeMillis();if (tryAcquire()) {System.out.println(currentTimeMillis + " 请求成功 -> ✅");} else {System.out.println(currentTimeMillis + " 请求失败 -> ❌");}}}
}

代码解读:

  • ​QPS​: 定义了每个时间窗口内允许的最大请求数(这里是 2)。

  • ​TIME_WINDOWS​: 定义了时间窗口的长度(这里是 1000 毫秒,即 1 秒)。

  • ​REQ_COUNT​: 使用 AtomicInteger​ 来存储当前窗口的请求计数。AtomicInteger​ 是线程安全的,允许多个线程并发地增加计数值而不会出错。

  • ​START_TIME​: 记录当前时间窗口的开始时间戳。

  • ​tryAcquire()​: 这是核心方法,用于尝试获取一个请求许可。

    • ​synchronized static​: 这个关键字非常重要。因为 REQ_COUNT​ 和 START_TIME​ 都是 static​ 的(所有线程共享),我们需要确保检查时间、重置计数器、更新开始时间这三个操作组合在一起是原子的。否则,可能多个线程同时判断窗口已过期,并多次重置计数器,导致逻辑错误。synchronized​ 保证了同一时刻只有一个线程能执行 tryAcquire​ 方法块内的代码。
    • 窗口检查与重置: if ((now - START_TIME) > TIME_WINDOWS)​ 判断当前时间 now​ 是否已经超过了窗口开始时间 START_TIME​ 加上窗口长度 TIME_WINDOWS​。如果是,说明旧窗口已结束,需要重置计数器 REQ_COUNT.set(0)​ 并将 START_TIME​ 更新为当前时间 now​,标志着新窗口的开始。
    • 计数与判断: REQ_COUNT.incrementAndGet()​ 原子地将计数器加 1,并返回增加后的值。然后将这个新值与 QPS​ 比较。如果小于等于 QPS​,则返回 true​(请求成功);否则返回 false​(请求被限流)。

优点

  1. 简单直观:算法逻辑和实现都非常简单,容易理解和部署。
  2. 内存占用低:只需要存储一个计数器和一个时间戳,资源消耗很小。

缺点:临界突变问题 (Edge Burst Problem)

这是固定窗口计数器算法最主要的缺陷。因为它只关心当前窗口的总量,并且在窗口边界处进行硬性切换,可能导致在两个窗口的交界处,实际通过的请求速率远超预期。

举个例子:

假设我们的 QPS 限制是 10(即每秒最多 10 个请求)。

  • 在第 1 秒的最后 100 毫秒(例如 0.9s 到 0.999s),一个客户端疯狂发送了 10 个请求,都被允许了,因为计数器从 0 增加到了 10。
  • 时间到达第 2 秒(1.0s),窗口立刻重置,计数器归零。
  • 在第 2 秒的前 100 毫秒(例如 1.0s 到 1.099s),该客户端又疯狂发送了 10 个请求,由于计数器刚清零,这些请求也都被允许了。

结果是什么? 在横跨两个窗口边界的短短 200 毫秒内,系统实际处理了 20 个请求!这相当于瞬时速率达到了 100 QPS (20 请求 / 0.2 秒),远远超过了我们设定的 10 QPS 的平均限制。这种在窗口边界可能发生的流量“突刺”就是临界突变问题。

什么时候用?

尽管有临界突变的问题,固定窗口计数器因其简单性,在某些场景下仍然可用:

  • 对限流精度要求不高的内部系统或工具。
  • 可以容忍短时流量突增的应用。
  • 作为更复杂限流算法的基础组件。

但对于需要严格、平滑地控制流量的场景(例如对外开放的 API),固定窗口计数器通常不是最佳选择。

总结

固定窗口计数器是最基础的限流算法,实现简单、资源消耗少。但它的主要缺点是临界突变问题,可能导致在窗口边界处的瞬时流量远超预期。


相关文章:

  • ORM、Hibernate 与 MyBatis 详解:选择合适的框架
  • 2025年渗透测试面试题总结-拷打题库24(题目+回答)
  • Ethan独立开发产品日报 | 2025-04-27
  • 系统架构-DSSAABSD
  • Django 缓存框架
  • 华为云空间安卓版存储扩展与文件管理体验测评
  • AI大模型学习十四、白嫖腾讯Cloud Studio AI环境 通过Ollama+Dify+DeepSeek构建生成式 AI 应用-接入DeepSeek大模型
  • 使用 ELK 实现全链路追踪:从零到一的实践指南
  • 阿里云服务器(ECS)基础指南:从入门到核心场景解析​
  • ubuntu新增磁盘挂载
  • Jackson 使用方法详解
  • 操作系统八股问——连载ing
  • 具身智能机器人的应用场景及最新进展
  • 解决MacOS端口被占用问题
  • 安卓基础(接口interface)
  • 高压场景首选:CKESC ROCK 120A-H CAN 电调技术解析与实测报告
  • 51c大模型~合集122
  • 第十六节:开放性问题-Vue与React Hooks对比
  • vue3:v-model的原理示例
  • ISO-C99标准 最小限定值
  • 看展览|建造上海:1949年以来的建筑、城市与文化
  • 亮剑浦江丨上海网信部门处罚一批医疗服务类互联网企业,三大类问题值得关注
  • 修订占比近30%收录25万条目,第三版《英汉大词典》来了
  • 俄军方:已完成库尔斯克地区全面控制行动
  • 航行警告!黄海南部进行实弹射击,禁止驶入
  • 仅退款正式成历史?仅退款究竟该不该有?