Redis实现限流功能的几种方法总结

 更新时间:2026年05月22日 08:43:30   作者:洛水水  
在实际业务场景中,限流是保护系统的重要手段:在一段时间(period)内,限定某个行为(action)的最大次数(max_count),本文介绍如何基于 Redis 实现多种限流方案,需要的朋友可以参考下

一、问题背景

在实际业务场景中,限流是保护系统的重要手段:在一段时间(period)内,限定某个行为(action)的最大次数(max_count)。本文介绍如何基于 Redis 实现多种限流方案。

二、限流类型总览

限流类型核心思想优点缺点
固定窗口限流时间窗口固定,到期自动清零实现简单存在窗口边界突击流量问题
滑动窗口限流窗口随时间滑动,统计窗口内请求数精确解决边界问题实现稍复杂
漏斗限流容量固定,速率固定精确控制容量和速率需要 Redis 模块支持
令牌桶限流令牌以固定速率放入桶中支持突发流量实现复杂

三、固定窗口限流

3.1 什么是固定窗口限流

将时间划分为固定的窗口,例如每 5 分钟为一个窗口:

|---5min---|---5min---|---5min---|---5min---|
20:00      20:05      20:10      20:15

在每个窗口内独立计数,窗口到期后计数清零。

3.2 Redis 实现

-- 固定窗口限流实现
local key = "***" .. user_id .. ":" .. action
local limit = 10  -- 最大次数
local period = 10  -- 时间窗口(秒)

-- 方式1:INCR + EXPIRE(存在问题)
redis.call('INCR', key)
redis.call('EXPIRE', key, period)

-- 方式2:SET + INCR(正确实现,解决竞态条件)
-- 使用 SET + EXPIRE 原子操作,避免窗口切换时丢失数据
redis.call('SET', key, 0, 'EX', period, 'NX')
local count = redis.call('INCR', key)

return count <= limit

关键点:使用 SET + EXPIRE 代替单独 EXPIRE,避免 INCR 和 EXPIRE 之间进程崩溃导致数据丢失。

可通过 Pipeline 保证两个命令同时发送:

# Python 示例
pipe = redis.pipeline()
pipe.set(key, 0, ex=period, nx=True)
pipe.incr(key)
res = pipe.execute()
return res[1] <= limit

3.3 固定窗口的局限性

假设 5 分钟内限定 10 次请求:

20:04-20:05 发生 9 次请求
20:05-20:06 发生 9 次请求

在 20:04-20:06 这 2 分钟内,实际发生了 18 次请求,远超每 5 分钟 10 次的限制。

问题根源:固定窗口的边界不连续,在边界处可能发生突发流量。

四、滑动窗口限流

4.1 核心思想

滑动窗口的核心是窗口随时间连续滑动,而非固定边界:

传统固定窗口:      |-----5min-----|-----5min-----|
                   20:00         20:05         20:10

滑动窗口:
                   现在时刻的窗口持续向前滑动
                   |----5min-----|----5min-----|
                   20:01         20:06

4.2 Redis 实现(ZSET)

local function is_action_allowed(red, user_id, action, period, max_count)
    local key = "***" .. user_id .. ":" .. action
    local now = redis.call('TIME')  -- 获取当前时间戳(毫秒)

    -- 1. 记录当前行为(score 和 member 都用时间戳)
    red:zadd(key, now, now)

    -- 2. 移除窗口之前的行为记录
    red:zremrangebyscore(key, 0, now - period * 1000)

    -- 3. 获取窗口内的行为数量
    local count = red:zcard(key)

    -- 4. 设置过期时间,避免冷用户持续占用内存
    red:expire(key, period + 1)

    return count <= max_count
end

流程图:

时间轴:[--窗口period--|---未来---]
                       ↑now
                       
ZSET 存储:score=时间戳, member=时间戳
ZREMRANGEBYSCORE:删除 score < now-period 的旧记录
ZCARD:统计剩余元素数量,即窗口内请求数

4.3 为什么用 ZSET 而非 LIST

数据结构适用场景
ZSET支持按时间范围删除,适合滑动窗口
LIST只能按索引删除,无法按时间范围清理

五、漏斗限流(Redis-Cell)

5.1 什么是漏斗限流

漏斗限流的核心是容量固定 + 速率固定,能精确控制元素的容量和速率:

漏斗模型:
       [入口] -> (容量固定) -> [出口]
         ↓
      速率恒定
  • 漏斗容量:最多能容纳多少请求
  • 漏斗速率:单位时间内能处理多少请求

5.2 Redis-Cell 模块安装

Redis-Cell 是 Redis 的第三方模块,采用 Rust 编写,需要单独安装:

# 下载并编译
git clone https://github.com/brandur/redis-cell
cd redis-cell
cargo build --release
cp target/release/libredis_cell.so /path/to/modules/
# 启动 Redis 加载模块
redis-server --loadmodule /path/to/modules/libredis_cell.so

5.3 CL.THROTTLE 命令详解

CL.THROTTLE key capacity operations seconds [quota]

参数说明:

参数含义示例
key漏斗容器名称user:123:login
capacity漏斗容量(最大容纳请求数)10
operations单位时间内的操作次数5
seconds单位时间(秒)60
quota单次行为消耗的令牌数(可选,默认1)1

示例:每 60 秒最多 5 次请求,漏斗容量 10

CL.THROTTLE user:123:login 10 5 60

返回结果:

1) (integer) 0  # 是否被限流(0=允许,1=拒绝)
2) (integer) 7  # 漏斗剩余容量
3) (integer) 7  # 如果被拒绝,还需要等多久(秒)
4) (integer) -1  # 预留字段
5) (integer) 60  # 下次请求的间隔时间

5.4 流速计算

流速 = operations / seconds = 5 / 60 ≈ 0.083 请求/秒

这意味着每秒只能处理约 0.083 个请求,即约 12 秒处理 1 个请求。

六、令牌桶限流

6.1 核心思想

令牌桶的核心是令牌以固定速率放入桶中

令牌桶:
         -> [桶容量] -> 请求消耗令牌 -> 通过
         ↑
      固定速率放入令牌
  • 桶容量:最大令牌数
  • 令牌添加速率:每秒添加多少令牌
  • 请求消耗:每个请求消耗 1 个令牌

6.2 特点

特点说明
支持突发流量桶满时可一次性处理多个请求
令牌非即时补充需要等待令牌生成

6.3 与漏斗限流的区别

对比维度漏斗限流令牌桶限流
速率匀速匀速(令牌补充)
突发能力不支持支持(桶满时)
实现难度较简单较复杂

七、四种限流方案对比

维度固定窗口滑动窗口漏斗限流令牌桶
实现复杂度
边界突击
突发流量支持不支持不支持不支持支持
精度控制
额外依赖Redis-Cell

八、面试追问 FAQ

问题回答要点
Q: 为什么固定窗口需要 SET + INCR 组合?单独 INCR + EXPIRE 在进程崩溃时可能丢失数据,SET+EXPIRE 原子操作保证一致性
Q: 滑动窗口为什么要设置过期时间为 period+1?避免窗口边界附近过期导致数据丢失,确保跨窗口的请求仍被统计
Q: 漏斗限流和令牌桶限流各适用于什么场景?漏斗:需要精确控制速率的 API 限流;令牌桶:允许突发流量的场景(如秒杀)
Q: Redis-Cell 是原子操作吗?是,CL.THROTTLE 整个命令是原子的,无需担心并发问题
Q: 滑动窗口的 ZSET 会不会无限增长?不会,每次请求都会清理窗口外的旧数据,且有 expire 保证清理

总结

限流方案实现难度精度突发流量推荐场景
固定窗口不支持简单场景
滑动窗口不支持需要精确控制
漏斗限流不支持API 限流
令牌桶支持秒杀/抢购

核心结论: 根据业务场景选择合适的限流方案,简单场景用固定窗口,精确控制用滑动窗口或漏斗限流,需要突发能力用令牌桶。

以上就是Redis实现限流功能的几种方法总结的详细内容,更多关于Redis实现限流功能的资料请关注脚本之家其它相关文章!

相关文章

  • Govern Service 基于 Redis 的服务治理平台安装过程详解

    Govern Service 基于 Redis 的服务治理平台安装过程详解

    Govern Service 是一个轻量级、低成本的服务注册、服务发现、 配置服务 SDK,通过使用现有基础设施中的 Redis 不用给运维部署带来额外的成本与负担,接下来通过本文给大家分享Govern Service 基于 Redis 的服务治理平台的相关知识,感兴趣的朋友一起看看吧
    2021-05-05
  • Redis线程模型的原理分析

    Redis线程模型的原理分析

    Redis是一个高性能的数据存储框架,在高并发的系统设计中,Redis也是一个比较关键的组件,是我们提升系统性能的一大利器,本文详细的介绍了Redis线程模型,感兴趣的可以了解一
    2021-11-11
  • 利用Redis统计网站在线活跃用户的方法

    利用Redis统计网站在线活跃用户的方法

    Redis支持对String类型的value进行基于二进制位的置位操作。通过将一个用户的id对应value上的一位,通过对活跃用户对应的位进行置位,就能够用一个value记录所有活跃用户的信息。下面这篇文章主要介绍了利用Redis统计网站在线活跃用户的方法,需要的朋友可以参考。
    2017-01-01
  • 浅谈Redis中的缓存更新策略

    浅谈Redis中的缓存更新策略

    这篇文章主要介绍了浅谈Redis中的缓存更新策略,CacheAsidePatter是我们比较常用的缓存更新策略,其由缓存调用者在更新数据库时,在业务逻辑中设置缓存更新,需要的朋友可以参考下
    2023-08-08
  • Redis与自定义注解实现重复方式

    Redis与自定义注解实现重复方式

    文章介绍了如何使用Redis和自定义注解`SubmitLock`来防止重复提交,首先,创建了一个注解`SubmitLock`,然后在需要防止重复提交的方法上使用该注解,最后,通过一个controller测试验证了该功能的有效性
    2025-12-12
  • redis如何取hash的值

    redis如何取hash的值

    这篇文章主要介绍了redis如何取hash的值问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • Redis是单线程的吗

    Redis是单线程的吗

    Redis使用单线程的原因就是多线程并不能有效提升Redis的性能,相反可能还会降低性能,所以自然而然使用单线程,本文给大家详细介绍了Redis为什么是单线程的,感兴趣的朋友跟随小编一起看看吧
    2023-06-06
  • Window server中安装Redis的超详细教程

    Window server中安装Redis的超详细教程

    这篇文章主要介绍了Window server中安装Redis的教程,本文通过图文实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-11-11
  • Redis实现延迟队列的项目示例

    Redis实现延迟队列的项目示例

    延迟队列是Redis的一个重要应用场景,本文主要介绍了Redis实现延迟队列的项目示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • 详解redis脚本命令执行问题(redis.call)

    详解redis脚本命令执行问题(redis.call)

    这篇文章主要介绍了redis脚本命令执行问题(redis.call),分别介绍了redis-cli命令行中执行及linux命令行中执行问题,本文给大家介绍的非常详细,需要的朋友参考下吧
    2022-03-03

最新评论