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

打印 上一主题 下一主题

主题 857|帖子 857|积分 2571

一、什么是滑动窗口限流

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

  • 时间窗口:限流的时间段,例如每秒、每分钟或每小时,窗口可以固定,也可以动态调解。
  • 滑动窗口:在整个事件窗口内滑动的小窗口,用于计算当前时间段内的请求数量。
  • 计数器:用于记录当前窗口内的请求数量数量。
  • 限流策略:预先设定的限流规则,例如允许的最大请求数量,以及如何处置惩罚超出限制的请求(例如拒绝、耽误处置惩罚)等。
  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一样导致的幂等题目。
代码如下:
  1. public class LimitWindows {
  2.   //计数器
  3.   private final static long limit = 100;
  4.   public boolean allowRequest(Jedis jedis,String key){
  5.      //当前时间
  6.      long currentTime = System.currentTimeMillis();
  7.      //窗口开始时间:当前时间-60s
  8.      long windowStart = System.currentTimeMillis() - 60*1000;
  9.      //删除窗口开始时间之前的所有数据
  10.      jedis.zremrangeByScore(key,"-inf",String.valueOf(windowStart));
  11.      //计算总请求数
  12.      long current = jedis.zcard(key);
  13.      //判断
  14.      if(current<limit){
  15.         //窗口足够则把当前请求加入
  16.         jedis.zadd(key,currentTime,String.valueOf(currentTime));
  17.         return true;
  18.      }
  19.      return false;
  20.   }
  21. }
复制代码
以上高并发下可能会出现原子性题目,那么我们可以思量用LUA脚本实现:
  1. public class LimitWindows {
  2.   //计数器
  3.   private final static long limit = 100;
  4.   public boolean allowRequest(Jedis jedis,String key){
  5.      //获取当前时间戳
  6.      long currentTime = System.currentTimeMillis();
  7.      String luaScript = "local window_start_time = ARGV[1] -ARGV[3]*1000 " +
  8.              " redis.call('ZREMRANGEBYSCORE',KEYS[1],'-inf',window_start_time) " +
  9.              " local now_request = redis.call('ZCARD',KEYS[1]) " +
  10.              " if now_request < tonumber(ARGV[2]) then " +
  11.              " redis.call('ZADD',KEYS[1],ARGV[1],ARGV[1]) " +
  12.              "     return 1 " +
  13.              "else " +
  14.              "      return 0 " +
  15.              " end ";
  16.      Object result = jedis.eval(luaScript, 1, key, String.valueOf(currentTime), String.valueOf(limit), String.valueOf(60));
  17.      return (long) result == 1;
  18.   }
  19. }
复制代码
ps:"-inf"在reids中表示负无穷,"+inf"表示正无穷
End:希望对各人有所帮助,如果有纰漏或者更好的想法,请您肯定不要吝啬你的赐教
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

花瓣小跑

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表