漏斗限流

漏斗限流

Redis 漏斗限流(Rate Limiter)是一种常用的限流技术,用于控制对某个资源或服务的访问频率,以防止服务被过度使用或遭受滥用。漏斗限流算法通过模拟水流从一个漏斗中流出,来限制数据的传输速率。在 Redis 中,可以通过使用 Redis 的原子操作和一些数据结构来实现漏斗限流,例如使用 Redis 的 INCRINCRBYEXPIRESETEX 等命令,或者使用 Redis 的 Lua 脚本来实现更复杂的逻辑。

漏斗限流的基本原理

  • 固定容量和速率:
    • 漏斗有一个固定的容量(capacity),表示在单位时间内可以处理的最大请求数。
    • 漏斗还有一个固定的泄漏速率(rate),表示每秒可以从漏斗中泄漏(即允许处理的)请求数。
  • 时间窗口:
    • 通常,漏斗限流是基于一个固定时间窗口(例如1秒)来计算和执行限制的。

实现方式

使用 Redis 命令实现

存储请求数

使用一个 Redis 键来存储当前时间窗口内的请求数。例如,使用 INCR 命令增加请求数。

INCR key:requests:user_id

设置时间窗口

使用 EXPIRE 命令设置时间窗口的过期时间,例如每秒重置一次。

EXPIRE key:requests:user_id 1

检查请求数

在执行请求前,先检查当前时间窗口内的请求数是否超过了容量限制。

GET key:requests:user_id

如果请求数大于等于容量,则拒绝请求;否则,继续处理。

使用 Lua 脚本实现更复杂的逻辑

对于更复杂的漏斗限流逻辑(如动态调整速率),可以使用 Redis 的 Lua 脚本来实现。Lua 脚本可以在服务器端原子地执行多个命令,避免了多命令执行中的竞态条件。

--[[
漏斗限流算法核心逻辑:
1. KEYS[1] 限流器唯一标识
2. ARGV[1] rate 每秒允许的请求数
3. ARGV[2] capacity 漏斗总容量
--]]
local key = KEYS[1]  -- 限流器存储 key,例如: user_123_api_limit
local rate = tonumber(ARGV[1])  -- 漏嘴流速(每秒允许请求数)
local capacity = tonumber(ARGV[2])  -- 漏斗总容量(最大突发请求数)

-- 获取当前漏斗状态
local current = tonumber(redis.call('get', key) or "0")  -- 当前剩余容量
local timestamp = tonumber(redis.call('get', key .. ':timestamp') or "0")  -- 上次更新时间戳
local now = tonumber(redis.call('time')[1])  -- 当前 Redis 服务器时间(秒)

-- 时间窗口检测(每秒重置一次漏斗)
if now > timestamp + 1 then
    -- 距离上次请求超过 1 秒,重置漏斗
    current = capacity  -- 恢复漏斗最大容量
    timestamp = now  -- 记录新时间窗口起点
    
    -- 打印调试信息(生产环境需移除)
    -- redis.log(redis.LOG_NOTICE, "漏斗重置 current:"..current.." timestamp:"..timestamp)
end

-- 计算剩余容量(核心算法)
if current > 0 then
    -- 漏斗有剩余容量时:
    local allowed = 1  -- 允许本次请求
    current = current - 1  -- 消耗 1 个容量单位
    
    -- 更新存储(带1秒过期时间)
    redis.call('setex', key, 1, current)  -- 存储剩余容量
    redis.call('setex', key .. ':timestamp', 1, timestamp)  -- 存储时间窗口起点
    
    -- 返回允许请求,并返回剩余容量
    return {1, current}  -- 第一个值1表示允许,第二个值返回剩余容量
else
    -- 漏斗容量已耗尽
    -- 返回拒绝请求,并返回剩余容量
    return {0, 0}
end

总结

Redis 的漏斗限流通过结合原子操作和过期策略,可以有效地限制对资源的访问速率。无论是使用简单的 Redis 命令还是通过 Lua 脚本实现更复杂的逻辑,都可以根据实际需求选择合适的方法来达到限流的目的。

最后更新于