Redis第17讲——Redis zset结构实现滑动窗口限流

一、什么是滑动窗口限流

滑动窗口限流是一种流量控制策略,用于控制在一定时间内允许执行的操作数量或请求频率。它的工作方式类似于一个滑动时间窗口,对每个时间窗口的请求数量进行计数,并根据预先设置的限流策略来限制或调节流量,通常包括以下几个要素:

  • 时间窗口:限流的时间段,例如每秒、每分钟或每小时,窗口可以固定,也可以动态调整。

  • 滑动窗口:在整个事件窗口内滑动的小窗口,用于计算当前时间段内的请求数量。

  • 计数器:用于记录当前窗口内的请求数量数量。

  • 限流策略:预先设定的限流规则,例如允许的最大请求数量,以及如何处理超出限制的请求(例如拒绝、延迟处理)等。

ps:目前主流的限流算法有令牌(Gateway、Ratelimiter)、漏桶(Nginx)、滑动窗口(Sentinel)。

二、为什么需要限流

  • 防止滥用:通过限制每个用户或IP地址的请求频率,可以防止恶意用户或攻击者对系统进行滥用,例如DOS攻击。

  • 防止过载:当大量请求同时到达系统时,如果系统无法合理地处理这些请求,可能会导致系统资源被耗尽,甚至崩溃。

三、工作原理

假设我们时间窗口设为每分钟,那么红色窗口内的时间段就是滑动窗口,如下图:

当时间窗口移动时,需要把上一个时间段中的请求数量给减掉,当有新请求或操作进来时,系统会检查窗口内的计数是否已满,如果没满,请求允许执行,反之触发限流策略,比如被拒绝或者进入等待队列。

就拿上图举个例子,当前时间为12:00,计数器为200个,也就是每分钟允许的请求数量为200个,当12点零59秒第201个请求进来了,那么会直接触发限流策略。假设到了12点一分零1秒,系统会把12点至12点零一分的请求数量给移除。

四、Redis实现滑动窗口限流

在Redis中,我们可以用ZSET来实现这个功能。

key可以为请求的资源名,例如根据ip限制访问资源流量,那么key就可以设置为资源名+ip地址,score为当前请求的时间戳,member建议用请求详情的hash进行存储(或者UUID、MD5),避免在并发时,时间戳一致出现score和member一样导致的幂等问题。

代码如下:

public class LimitWindows {
   //计数器
   private final static long limit = 100;
   public boolean allowRequest(Jedis jedis,String key){
      //当前时间
      long currentTime = System.currentTimeMillis();
      //窗口开始时间:当前时间-60s
      long windowStart = System.currentTimeMillis() - 60*1000;
      //删除窗口开始时间之前的所有数据
      jedis.zremrangeByScore(key,"-inf",String.valueOf(windowStart));
      //计算总请求数
      long current = jedis.zcard(key);
      //判断
      if(current<limit){
         //窗口足够则把当前请求加入
         jedis.zadd(key,currentTime,String.valueOf(currentTime));
         return true;
      }
      return false;
   }
}

以上高并发下可能会出现原子性问题,那么我们可以考虑用LUA脚本实现:

public class LimitWindows {
   //计数器
   private final static long limit = 100;
   public boolean allowRequest(Jedis jedis,String key){
      //获取当前时间戳
      long currentTime = System.currentTimeMillis();
      String luaScript = "local window_start_time = ARGV[1] -ARGV[3]*1000 " +
              " redis.call('ZREMRANGEBYSCORE',KEYS[1],'-inf',window_start_time) " +
              " local now_request = redis.call('ZCARD',KEYS[1]) " +
              " if now_request < tonumber(ARGV[2]) then " +
              " redis.call('ZADD',KEYS[1],ARGV[1],ARGV[1]) " +
              "     return 1 " +
              "else " +
              "      return 0 " +
              " end ";
      Object result = jedis.eval(luaScript, 1, key, String.valueOf(currentTime), String.valueOf(limit), String.valueOf(60));
      return (long) result == 1;
   }
}

ps:"-inf"在reids中表示负无穷,"+inf"表示正无穷

End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。 

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis可以通过使用有序集合(zset)来实现滑动窗口限流算法。滑动窗口限流算法是一种常见的限流算法,它通过滑动时间窗口来计数请求并进行限制。具体实现方法如下: 1. 创建一个有序集合(zset),用于存储请求的时间戳和对应的请求标识。 2. 每次收到一个请求时,将当前时间戳和请求标识作为元素添加到有序集合中。 3. 删除有序集合中时间戳小于当前时间减去限流时间窗口的元素,以保持窗口内的请求记录。 4. 统计有序集合中请求标识的数量,如果数量超过限流阈值,则拒绝该请求。 利用Redis的有序集合特性,我们可以方便地获取某个时间段内的请求数量,并且由于有序集合的自动排序特性,可以快速进行删除操作。 下面是一个示例代码,演示了如何在Redis实现滑动窗口限流: ```java import redis.clients.jedis.Jedis; import java.util.Set; public class RedisSlidingWindow { private Jedis jedis; private String key; public RedisSlidingWindow(Jedis jedis, String key) { this.jedis = jedis; this.key = key; } public boolean isActionAllowed(int windowSize, int limit) { long currentTime = System.currentTimeMillis(); long windowStart = currentTime - windowSize * 1000; // 删除窗口之外的记录 jedis.zremrangeByScore(key, 0, windowStart); // 统计窗口内的请求数量 long count = jedis.zcard(key); // 添加当前请求记录 jedis.zadd(key, currentTime, String.valueOf(currentTime)); // 设置过期时间,防止集合无限增长 jedis.expire(key, windowSize + 1); // 判断请求数是否超过限制 return count < limit; } public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); RedisSlidingWindow slidingWindow = new RedisSlidingWindow(jedis, "sliding_window"); for (int i = 1; i <= 15; i++) { boolean actionAllowed = slidingWindow.isActionAllowed(60, 5); System.out.println("第" + i + "次操作" + (actionAllowed ? "成功" : "失败")); } jedis.close(); } } ``` 以上代码使用Jedis库连接Redis,并实现了一个`RedisSlidingWindow`类,其中`isActionAllowed`方法用于判断当前请求是否允许通过。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值