java单机滑动窗口限流器

下面使用 ConcurrentHashMap + ConcurrentLinkedDeque 实现一个滑动窗口限流器 ,限制在 10 秒内最多 N 次请求(例如 5 次)。这种方案只依赖本地内存,适合单机限流场景。


实现原理

  • 为每个用户(或 IP)维护一个 双端队列,存储每次请求的时间戳(毫秒)。

  • 每次请求时,先清理队列中 超过 10 秒 的旧时间戳,再判断队列大小是否小于阈值:

    • 若小于,则添加当前时间戳,返回 允许
    • 否则,返回 限流
  • 使用 ConcurrentHashMap 保证对用户 Key 的线程安全,使用 synchronized 对每个队列做同步,避免并发修改。


代码实现

java

arduino 复制代码
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;

/**
 * 基于 Map 的滑动窗口限流器(10秒限频)
 */
public class LocalSlidingWindowRateLimiter {
    // 存储每个用户的请求时间戳队列
    private final Map<String, ConcurrentLinkedDeque<Long>> userQueueMap = new ConcurrentHashMap<>();
    private final int windowSeconds;   // 窗口大小(秒)
    private final int maxRequests;     // 窗口内最大请求数

    public LocalSlidingWindowRateLimiter(int windowSeconds, int maxRequests) {
        this.windowSeconds = windowSeconds;
        this.maxRequests = maxRequests;
    }

    /**
     * 检查是否允许请求
     * @param key 限流标识(如 userId、ip)
     * @return true 允许,false 被限流
     */
    public boolean allowRequest(String key) {
        long now = System.currentTimeMillis();
        long windowStart = now - TimeUnit.SECONDS.toMillis(windowSeconds);

        // 获取或创建该用户的队列
        ConcurrentLinkedDeque<Long> deque = userQueueMap.computeIfAbsent(key,
                k -> new ConcurrentLinkedDeque<>());

        // 对队列加锁,保证清理和添加的原子性
        synchronized (deque) {
            // 1. 清理过期记录(窗口外的旧时间戳)
            while (!deque.isEmpty() && deque.peekFirst() < windowStart) {
                deque.pollFirst();
            }

            // 2. 判断是否超过阈值
            if (deque.size() < maxRequests) {
                // 允许请求,记录当前时间戳
                deque.addLast(now);
                return true;
            } else {
                return false;
            }
        }
    }

    // 可选:定期清理无数据的用户 Key,避免内存泄漏(可以用 ScheduledExecutorService 定时清理)
    public void cleanEmptyKeys() {
        userQueueMap.entrySet().removeIf(entry -> entry.getValue().isEmpty());
    }
}

使用示例

java

java 复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 限流规则:10秒内最多5次请求
        LocalSlidingWindowRateLimiter limiter = new LocalSlidingWindowRateLimiter(10, 5);
        String userId = "user_123";

        for (int i = 0; i < 10; i++) {
            boolean allowed = limiter.allowRequest(userId);
            System.out.println("请求 " + (i + 1) + ":" + (allowed ? "通过" : "限流"));
            Thread.sleep(1000); // 模拟间隔 1 秒
        }
    }
}

输出示例(第 6 次请求被限流,因为 10 秒内已有 5 次):

text

erlang 复制代码
请求 1:通过
请求 2:通过
请求 3:通过
请求 4:通过
请求 5:通过
请求 6:限流
请求 7:限流
...

注意事项

  1. 内存清理 :长期不活动的用户 Key 会残留队列对象,建议定时调用 cleanEmptyKeys() 清理。
  2. 并发安全 :每个队列使用 synchronized 锁保证原子操作,锁粒度小,性能较高。
  3. 时间精度:使用毫秒级时间戳,滑动窗口边界判断精确。
  4. 适用场景:单机应用、对分布式一致性要求不高的限流场景。
相关推荐
长栎16 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode20 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812225 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode25 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战27 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn1 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构