redis限流
【摘要】 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 模块。
注意事项
- 分布式环境:Redis 天然支持分布式限流。
- 冷启动问题:为 ZSET 或令牌桶设置合理的过期时间。
- 性能优化:Lua 脚本减少网络开销。
通过合理选择算法和 Redis 数据结构,可以高效实现各种限流需求。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)