redis+lua+固定窗口实现分布式限流

用key的过期时间替代固定窗口的时间戳

lua 复制代码
-- KEYS[1]: 限流的key
-- ARGV[1]: 限流窗口大小(秒)
-- ARGV[2]: 限流阈值

local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])

-- 尝试获取当前计数
local current = redis.call("GET", key)

if current == false then
    -- key不存在,初始化计数器并设置过期时间
    redis.call("SET", key, 1, "EX", window)
    return 1
else
    -- key存在,检查是否超过限制
    if tonumber(current) < limit then
        redis.call("INCR", key)
        return 1
    else
        return 0
    end
end

java客户端

java 复制代码
public class FixedWindowRateLimiterWithTTL {
    private Jedis jedis;
    private String key;
    private int window; // 窗口大小(秒)
    private int limit;  // 限流阈值
    
    private static final String LUA_SCRIPT =
        "local key = KEYS[1]\n" +
        "local window = tonumber(ARGV[1])\n" +
        "local limit = tonumber(ARGV[2])\n" +
        "local current = redis.call(\"GET\", key)\n" +
        "if current == false then\n" +
        "    redis.call(\"SET\", key, 1, \"EX\", window)\n" +
        "    return 1\n" +
        "else\n" +
        "    if tonumber(current) < limit then\n" +
        "        redis.call(\"INCR\", key)\n" +
        "        return 1\n" +
        "    else\n" +
        "        return 0\n" +
        "    end\n" +
        "end";
    
    public FixedWindowRateLimiterWithTTL(Jedis jedis, String key, int window, int limit) {
        this.jedis = jedis;
        this.key = key;
        this.window = window;
        this.limit = limit;
    }
    
    public boolean allowRequest() {
        Object result = jedis.eval(LUA_SCRIPT, 
            Collections.singletonList(key),
            Arrays.asList(
                String.valueOf(window),
                String.valueOf(limit)
            ));
        return "1".equals(result.toString());
    }
}

使用

java 复制代码
public static void main(String[] args) {
    Jedis jedis = new Jedis("localhost");
    // 创建一个每分钟最多100次请求的限流器
    FixedWindowRateLimiterWithTTL limiter = 
        new FixedWindowRateLimiterWithTTL(jedis, "api:limit:user1", 60, 100);
    
    for (int i = 0; i < 120; i++) {
        if (limiter.allowRequest()) {
            System.out.println("处理请求 " + i);
        } else {
            System.out.println("限流请求 " + i);
        }
    }
    
    jedis.close();
}

优点:实现简单

缺点:

固定窗口算法无法解决临界问题

Redis的过期机制是惰性删除+定期删除,可能导致key实际过期时间与预期有微小差异

重启导致的窗口重置
在超高并发下会成为单点瓶颈

相关推荐
JavaPub-rodert3 小时前
Redis 和 MySQL 如何保证数据一致性?从业务方案到底层原理完整讲解
数据库·redis·mysql
数智启示录5 小时前
Apache Kafka Consumer 扩到 40 个仍不提速:Partition 上限锁死有效并行度 【Kafka合集】
数据库·经验分享·分布式·缓存·面试·kafka·apache
数智启示录5 小时前
Apache Kafka 幂等 Producer 的边界:Exactly-Once 到了 MySQL 为什么失效 【Kafka合集】
数据库·经验分享·分布式·mysql·面试·kafka·apache
数智启示录6 小时前
Apache Kafka 不只是消息队列:日志与 Offset 分离如何让事件可重放 【Kafka合集】
经验分享·分布式·面试·kafka·apache
数智启示录6 小时前
Apache Kafka 同 Key 仍会乱序:从分区 Offset 到业务可见的四道边界 【Kafka 合集】
大数据·数据库·经验分享·分布式·面试·kafka·apache
BYSJMG6 小时前
大数据毕业设计选题推荐:基于大数据的全球气温历史演变分析与可视化,用Hadoop+Spark处理
大数据·hadoop·分布式·信息可视化·spark·kmeans·课程设计
爱吃苹果的梨叔6 小时前
AI 算力监控中心怎么建?分布式坐席 + 大屏联动 + 过程回放
人工智能·分布式·python
無a伟6 小时前
RabbitMQ :与AMQP的关系、核心组件、交换机类型
分布式·rabbitmq
程序员阿明7 小时前
spring boot4+springAI 2加redis多轮对话存储
spring boot·redis·后端
jyOverQ11 小时前
RabbitMQ 消息积压怎么办?Prefetch、消费者并发与扩容
分布式·后端·rabbitmq