当前位置:首页 > 问答 > 正文

用Redis怎么搞计数限流,感觉挺实用也不难理解

你说得对,用Redis做计数限流确实非常实用,而且核心思想不难理解,咱们就把它想象成一个简单的“令牌桶”或者“访问次数记录本”,只不过这个本子放在了速度极快的Redis里。

核心思想:简单粗暴的计数

想象一下,你要给某个API接口限流,比如一分钟内最多允许访问100次,没有Redis的时候,你可能会把计数存在程序的内存里,但如果你有多台服务器,这个计数就不准了,因为每台服务器都有自己的小本本,没法统一。

这时候Redis就派上用场了,我们可以把Redis当成一个唯一的、速度超快的中央计数器,所有的服务器在接到API请求时,都去问这个中央计数器:“喂,现在还能不能访问?” Redis负责统一记录和判断。

最基本的实现方式:固定窗口计数法

这是最直观的方法,就像把时间切成一个个固定的窗口(比如1分钟一个窗口)。

  1. 设计Key:我们需要一个唯一的键(Key)来标识这个限流规则,我们可以把接口名和当前的时间窗口绑定在一起,对于接口 /api/v1/user,它的Key可以是 rate_limit:/api/v1/user:202310261355(2023年10月26日13点55分这一分钟),这样,每一分钟都会有一个全新的Key。
  2. 操作过程
    • 第一步:计数加一,每当一个请求过来,我们就对这个Key执行一个 INCR 命令。INCR 是Redis里一个非常简单的命令,就是把某个Key的值增加1,如果这个Key不存在,Redis会先把它创建出来,值设为0,然后再加1,所以返回值是1。
    • 第二步:设置过期时间,当我们第一次创建这个Key(也就是INCR返回1)的时候,我们需要给这个Key设置一个过期时间(TTL),比如60秒,这样,一分钟过后,这个Key就会自动被Redis删除,下一分钟的计数又从零开始,设置过期时间用的是 EXPIRE 命令。
    • 第三步:判断是否超限INCR 命令会返回增加后的值,我们拿到这个返回值,比如是 current_count,然后我们和设定的阈值(比如100)比较。current_count > 100,就说明这一分钟内的访问量已经超过100次了,这时候就应该拒绝这个请求(比如返回HTTP 429 Too Many Requests),如果小于等于100,就正常处理。

这个方法的好处是超级简单,只用到了Redis两个基本命令INCREXPIRE,性能非常高。

但有个明显的缺点:窗口临界问题

固定窗口法有个硬伤,想象一下,限流是每分钟100次。

  • 在13:59:30这一秒开始,突然来了50个请求。
  • 紧接着,在14:00:01这一秒(也就是下一分钟的开始),又瞬间来了50个请求。

虽然这两个时间点都属于不同的“一分钟窗口”,各自窗口内的请求数(50)都没有超限,但从用户的角度看,在短短两秒内(13:59:30到14:00:01),系统实际接收了100个请求,这就在时间窗口的边界处形成了一个流量尖峰,可能还是会压垮系统。

更平滑的解决方案:滑动窗口计数法

为了解决边界问题,人们想到了滑动窗口,它不像固定窗口那样“一刀切”地切换时间,而是像一个窗口一样在时间轴上滑动,看的是一段连续的时间。

实现滑动窗口有多种方式,一种比较常见且仍然简单的是使用Redis的有序集合(Sorted Set)。

  1. 设计Key:这次我们不需要把时间写在Key里了,一个接口就用一个Key,rate_limit_sliding:/api/v1/user
  2. 操作过程
    • 第一步:记录时间戳,当一个请求到来时,我们获取当前的时间戳(精确到毫秒最好),比如是 1698301234500,这个时间戳将作为有序集合里的“分数”(Score)。
    • 第二步:清理过期请求,我们定义时间窗口的大小,比如一分钟(60000毫秒),当前时间戳减去60000毫秒之前的所有请求记录都是过期的,我们用 ZREMRANGEBYSCORE 命令,删除有序集合中分数小于(当前时间戳 - 60000毫秒)的所有成员,这一步保证了我们集合里只保留最近一分钟的请求记录。
    • 第三步:判断当前请求数,使用 ZCARD 命令,获取当前有序集合的成员数量,这个数量就是最近一分钟内的请求总数。
    • 第四步:判断是否超限,如果请求总数已经大于等于阈值(100),就拒绝请求。
    • 第五步:添加当前请求,如果还没超限,就用 ZADD 命令,将当前请求的时间戳(作为分数)和一个唯一值(比如可以用UUID,或者直接用时间戳也行)作为成员,添加到有序集合中。
    • 第六步:设置整体过期时间:为了不让这个有序集合无限变大,我们还可以给它设置一个过期时间,比如比窗口时间长一点(70秒),让它能自动清理。

滑动窗口这种方式解决了临界突变的问题,因为它统计的是任何一个时间点开始往前推一分钟内的请求总量,流量控制更加精确和平滑,代价是比固定窗口法稍微复杂一点,用了更多的Redis命令。

实际应用中的技巧和选择

在实际项目中,你可能会直接使用一些现成的库,比如Redis官方的RedisCell模块(它实现了更高级的漏桶算法),或者在Spring Cloud Gateway、Nginx等网关层面直接配置限流规则。

但理解这两种基本的Redis实现方式非常有价值,因为它揭示了限流的核心,选择哪种方式取决于你的业务场景:

  • 如果对限流的精确性要求不是极高,可以接受短暂的流量毛刺,那么固定窗口的简单性和高性能是很有吸引力的。
  • 如果需要对流量进行更平滑、更精确的控制,避免临界问题,那么滑动窗口是更好的选择。

希望这个直接的解释能让你对“用Redis搞计数限流”有一个清晰实用的认识。

用Redis怎么搞计数限流,感觉挺实用也不难理解