Redis+Lua限流的四种算法

在这里插入图片描述

1. 固定窗口(Fixed Window)

原理:
  • 固定窗口算法将时间划分为固定的时间段(窗口),比如 1 秒、1 分钟等。在每个时间段内,允许最多一定数量的请求。如果请求超出配额,则拒绝。
优点:
  • 实现简单,能够快速处理请求限流。
缺点:
  • 在窗口边界处可能出现流量突增的情况(称为“边界效应”),比如两个窗口交界处可能短时间内允许通过的请求数量翻倍。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 最大允许请求数
-- ARGV[2]: 窗口时间(以秒为单位)

local current = redis.call('GET', KEYS[1])

if current and tonumber(current) >= tonumber(ARGV[1]) then
    return 0  -- 返回0表示超出限流
else
    current = redis.call('INCR', KEYS[1])
    if tonumber(current) == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间
    end
    return 1  -- 返回1表示未超限
end
Java模拟限流:
package com.strap.common.redis.demo;

import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;


/**
 * 固定窗口lua限流Demo
 *
 * @author strap
 */
public class FixedWindowExample {
   
   

    private static final String LIMIT_SCRIPT =
            "-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +
                    "-- ARGV[1]: 最大允许请求数\n" +
                    "-- ARGV[2]: 窗口时间(以秒为单位)\n" +
                    "\n" +
                    "local current = redis.call('GET', KEYS[1])\n" +
                    "\n" +
                    "if current and tonumber(current) >= tonumber(ARGV[1]) then\n" +
                    "    return 0  -- 返回0表示超出限流\n" +
                    "else\n" +
                    "    current = redis.call('INCR', KEYS[1])\n" +
                    "    if tonumber(current) == 1 then\n" +
                    "        redis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间\n" +
                    "    end\n" +
                    "    return 1  -- 返回1表示未超限\n" +
                    "end";

    @SneakyThrows
    public static void main(String[] args) {
   
   
        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
   
   
            for (int i = 0; i < 100; i++) {
   
   
                Thread.sleep(100);
                Object o = jedis.eval(LIMIT_SCRIPT, 1, "FixedWindowExample", "10", "5");
                if (Long.valueOf(1).equals(o)) {
   
   
                    System.out.println(i + "=============================放行");
                } else {
   
   
                    System.out.println(i + "拦截=============================");
                }
            }
        }
    }

}


2. 滑动窗口(Sliding Window)

原理:
  • 滑动窗口改进了固定窗口的“边界效应”问题,它通过更细粒度的时间单位来平滑地控制请求。滑动窗口可以在较短的时间窗口内动态调整请求计数,防止瞬时流量激增。
优点:
  • 能平滑地限制请求,减少流量的突增问题。
缺点:
  • 相对固定窗口来说,滑动窗口实现复杂度更高。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 时间窗口(秒)
-- ARGV[2]: 最大允许请求数
-- ARGV[3]: 当前时间戳(毫秒)

-- 移除窗口外的请求
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)

local count = redis.call('ZCARD', KEYS[1])

### 使用 RedisLua 实现限流算法 #### 固定窗口限流 固定窗口限流是一种简单的限流策略,在指定的时间窗口内允许一定数量的请求通过。一旦超过这个数量,则拒绝后续请求直到下一个时间窗口开始。 ```lua local key = KEYS[1] local limit = tonumber(ARGV[1]) local current_time = tonumber(ARGV[2]) -- 获取当前计数器值,如果不存在则初始化为0 local counter = redis.call("GET", key) if not counter then counter = 0 else counter = tonumber(counter) end -- 如果计数值小于限制,则增加计数并设置过期时间 if counter < limit then local new_counter = counter + 1 redis.call("SET", key, new_counter) redis.call("EXPIRE", key, 60) -- 设置60秒过期时间 return true else return false endif ``` 此代码片段展示了如何利用 Redis 的 `GET` 和 `SET` 命令以及 Lua 脚本来实现基于固定窗口的限流逻辑[^1]。 #### 令牌桶算法 令牌桶算法更加灵活,它模拟了一个可以容纳有限数量令牌的桶。系统按照固定的速率向桶里添加令牌,当有新请求到来时会尝试从中取出一个令牌;如果没有足够的令牌可用就拒绝服务。 ```lua local function get_current_timestamp() return tonumber(redis.call('TIME')[1]) end local bucket_key = KEYS[1] local rate_per_second = tonumber(ARGV[1]) -- 每秒钟产生的令牌数目 local capacity = tonumber(ARGV[2]) -- 桶的最大容量 local now = get_current_timestamp() -- 尝试获取存储的状态信息 local state = cjson.decode(redis.call("GET", bucket_key)) if not state or type(state) ~= 'table' then state = {tokens=capacity, last_update=now} end -- 计算自上次更新以来应该补充多少个token local elapsed_seconds = now - state.last_update local tokens_to_add = math.min(capacity, elapsed_seconds * rate_per_second) state.tokens = math.min(capacity, state.tokens + tokens_to_add) state.last_update = now -- 处理本次请求所需的token量 local required_tokens = tonumber(ARGV[3]) if state.tokens >= required_tokens then state.tokens = state.tokens - required_tokens redis.call("SETEX", bucket_key, 86400, cjson.encode(state)) -- 存储一天有效期的数据 return "ALLOWED" else return "DENIED" end ``` 这段 Lua 脚本实现了完整的令牌桶机制,并且借助于 Redis 提供的持久化功能来保存桶的状态信息[^2]。 为了确保高并发环境下的安全性,上述两种方法都依赖于 Redis Lua 脚本提供的原子性执行特性,从而避免了竞争条件的发生。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值