redis限流

举报
林欣 发表于 2025/06/29 22:47:48 2025/06/29
【摘要】 Redis 是一个高性能的键值存储系统,常用于实现限流(Rate Limiting)功能,以控制请求的频率或并发量。以下是几种常见的 Redis 限流方案及其实现方式: 1. 固定窗口计数器(Fixed Window)原理:将时间划分为固定窗口(如每分钟),统计每个窗口内的请求次数,超过阈值则限流。优点:实现简单。缺点:窗口边界可能出现流量突增(例如,前一分钟最后1秒和后一分钟前1秒的请求可...

Redis 是一个高性能的键值存储系统,常用于实现限流(Rate Limiting)功能,以控制请求的频率或并发量。以下是几种常见的 Redis 限流方案及其实现方式:


1. 固定窗口计数器(Fixed Window)

原理:将时间划分为固定窗口(如每分钟),统计每个窗口内的请求次数,超过阈值则限流。
优点:实现简单。
缺点:窗口边界可能出现流量突增(例如,前一分钟最后1秒和后一分钟前1秒的请求可能集中)。

Redis 实现

# 使用 INCR 和 EXPIRE 实现
# 用户ID为 key,每次请求递增计数器
INCR user:123:rate_limit
# 设置过期时间为窗口大小(如60秒)
EXPIRE user:123:rate_limit 60
# 检查计数器是否超过阈值(如100次/分钟)
GET user:123:rate_limit

Lua 脚本优化(原子操作):

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

local current = redis.call("GET", key)
if current and tonumber(current) > limit then
    return 0  -- 限流
else
    redis.call("INCR", key)
    if tonumber(redis.call("TTL", key)) == -1 then
        redis.call("EXPIRE", key, expire_time)
    end
    return 1  -- 通过
end

2. 滑动窗口计数器(Sliding Window)

原理:记录每个请求的时间戳,动态计算最近一个窗口内的请求数。
优点:避免固定窗口的边界问题。
缺点:需要存储时间戳,占用更多内存。

Redis 实现

  • 使用 Sorted Set(ZSET)存储请求时间戳:
    # 添加请求时间戳(score为时间戳)
    ZADD user:123:requests <timestamp> <timestamp>
    # 移除窗口外的旧请求(如最近60秒)
    ZREMRANGEBYSCORE user:123:requests -inf <current_time - 60>
    # 获取当前窗口内的请求数
    ZCARD user:123:requests
    

Lua 脚本示例

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window_size = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 移除旧请求
redis.call("ZREMRANGEBYSCORE", key, 0, now - window_size)
-- 获取当前请求数
local count = redis.call("ZCARD", key)
if count >= limit then
    return 0  -- 限流
else
    -- 添加新请求
    redis.call("ZADD", key, now, now)
    -- 设置过期时间(避免冷启动时ZSET无限增长)
    redis.call("EXPIRE", key, window_size * 2)
    return 1  -- 通过
end

3. 令牌桶算法(Token Bucket)

原理:以固定速率生成令牌,请求需获取令牌才能通过。
优点:允许突发流量(桶中令牌可累积)。
缺点:实现稍复杂。

Redis 实现

  • 使用 String 存储令牌数量和最后更新时间:
    # 初始化桶:容量10,每秒生成2个令牌
    # key 格式:{bucket_name}:tokens, {bucket_name}:last_time
    SET user:123:tokens 10
    SET user:123:last_time <timestamp>
    

Lua 脚本

local key_tokens = KEYS[1]
local key_time = KEYS[2]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])  -- 每秒补充的令牌数
local requested = tonumber(ARGV[3])    -- 请求的令牌数
local now = tonumber(ARGV[4])

local last_time = tonumber(redis.call("GET", key_time) or now)
local tokens = tonumber(redis.call("GET", key_tokens) or capacity)

-- 计算补充的令牌数
local delta = math.max(0, now - last_time)
local refilled = delta * refill_rate
tokens = math.min(capacity, tokens + refilled)

-- 检查令牌是否足够
if tokens < requested then
    return 0  -- 限流
else
    tokens = tokens - requested
    redis.call("SET", key_tokens, tokens)
    redis.call("SET", key_time, now)
    return 1  -- 通过
end

4. 漏桶算法(Leaky Bucket)

原理:请求以固定速率处理,超出容量的请求被限流。
优点:平滑流量,适合严格限流场景。
缺点:突发流量会被直接拒绝。

Redis 实现

  • 类似令牌桶,但反向控制(请求消耗桶中的“水”)。

5. Redis + Lua 脚本(推荐)

由于限流需要原子操作,建议使用 Lua 脚本 封装逻辑,避免竞态条件。例如:

-- 固定窗口限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])

local current = redis.call("INCR", key)
if tonumber(current) == 1 then
    redis.call("EXPIRE", key, expire)
end

if tonumber(current) > limit then
    return 0
else
    return 1
end

6. 使用 Redis 模块

  • RedisCell:专为限流设计的模块,支持原子操作和令牌桶算法。
    示例:
    # 初始化限流规则(容量10,每秒补充5个令牌)
    CL.THROTTLE user:123 10 5 60 1
    # 返回:是否允许、剩余令牌、重置时间等
    

选择建议

  • 简单场景:固定窗口(INCR + EXPIRE)。
  • 精确控制:滑动窗口(ZSET)或令牌桶(Lua脚本)。
  • 高性能需求:使用 RedisCell 模块。

注意事项

  1. 分布式环境:Redis 天然支持分布式限流。
  2. 冷启动问题:为 ZSET 或令牌桶设置合理的过期时间。
  3. 性能优化:Lua 脚本减少网络开销。

通过合理选择算法和 Redis 数据结构,可以高效实现各种限流需求。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。