典型回答 滑动窗口限流是一种流量控制策略,用于控制在一定时间内允许执行的操作数量或请求频率。它的工作方式类似于一个滑动时间窗口,在窗口内允许的操作数量是固定的,窗口会随着时间的推移不断滑动。
✅什么是滑动窗口限流?
滑动窗口限流的主要优点是可以在时间内平滑地控制流量,而不是简单地设置固定的请求数或速率。这使得系统可以更灵活地应对突发流量或峰值流量,而不会因为固定速率的限制而浪费资源或降低系统性能。
利用Redis,我们就可以实现一个简单的滑动窗口限流的功能。因为滑动窗口和时间有关,所以很容易能想到要基于时间进行统计。
那么我们只需要在每一次有请求进来的时候,记录下请求的时间戳和请求的数据,然后在统计窗口内请求的数量时,只需要统计窗口内的被记录的数据量有多少条就行了。
在Redis中,我们可以基于ZSET来实现这个功能。假如我们限定login接口一分钟只能调用100次:
那么,我们就可以把login接口这个需要做限流的资源名作为key在redis中进行存储,然后value我们现在ZSET这种数据结构,把他的score设置为当前请求的时间戳,member的话建议用请求的详情的hash进行存储(或者UUID、MD5什么的),避免在并发时,时间戳一致出现score和memberv一样导致被zadd幂等的问题。
所以,我们实现滑动窗口限流的主要思想是:只保留在特定时间窗口内的请求记录,而丢弃窗口之外的记录。
主要步骤如下:
定义滑动窗口的时间范围,例如,窗口大小为60秒。 每次收到一个请求时,我们就定义出一个zset然后存储到redis中。 然后再通过ZREMRANGEBYSCORE命令来删除分值小于窗口起始时间戳(当前时间戳-60s)的数据。 最后,再使用ZCARD命令来获取有序集合中的成员数量,即在窗口内的请求量。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import redis.clients.jedis.Jedis; public class SlidingWindowRateLimiter { private Jedis jedis; private String key; private int limit; public boolean allowRequest(String key) { //当前时间戳 long currentTime = System.currentTimeMillis(); //窗口开始时间是当前时间减60s long windowStart = currentTime - 60 * 1000; //删除窗口开始时间之前的所有数据 jedis.zremrangeByScore(key, "-inf", String.valueOf(windowStart)); //计算总请求数 long currentRequests = jedis.zcard(key); //窗口足够则把当前请求加入 if (currentRequests < limit) { jedis.zadd(key, currentTime, String.valueOf(currentTime)); return true; } return false; } } 以上代码在高并发情况下,可能会存在原子性的问题,需要考虑加事务或者lua脚本:
...