Redis如何实现限流算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/William0318/article/details/89374979

本文将介绍如何利用Redis实现限流算法,希望您有所收获。

应用场景

UGC(用户发布内容)场景,限制用户某个时间内的操作多少次,如果操作过于频繁,请求失败。最常见的就是登陆手机验证码,如果验证码发送过于频繁,会让用户稍后再试,这样的需求怎么实现呢?

限流算法

可以维护一个时间窗口,在窗口内查询已经操作的次数,如果大于阈值就拒绝请求,如果小于阈值,请求验证通过。利用Redis的zset结构实现,也可以利用经典漏斗算法实现,下面一一介绍。

Redis简单限流实现

思路:利用zset结构实现,key代表userid_action(某个用户的某个动作),value(userid_action_time), score操作发生的时间, zremrangeByScore可以根据score移除不在时间窗口的元素,计算元素个数与阈值比较。

public function isActionAllowed($userId, $action, $period, $allowCnt){
      $key = $userId."_".$action;
      $value = md5($key);
      $now = time();
      $this->redis->zadd($key, $value, $time);
      $this->redis->zremrangeByScore($key, 0, $now - $period);
      $actionCnt = $this->redis->zcard(key);
      $this->redis->expire(key, $period + 1);
      return $actionCnt <= $allowCnt;
}

缺点也是很明显,数量用户非常大,存储空间占用非常大。

Redis漏斗限流用法

漏斗限流算法来源于生活中真实的漏斗,漏斗有个漏水速率,漏斗有个进水速率,如果进水速率小于等于漏水速率,那这个漏斗永远满不了,反之则溢出。漏斗的剩余空间就代表着当前行为可以持续进行的数量,漏嘴的流水速率代表着系统允许该行为的最大频率。我们自己实现一个漏斗算法

class Funnel {
    private $capacity; //容量
    private $leakRate; //漏水速率
    private $leftSpace; //剩余空间
    private $startTime;//开始漏水时间

    public Funnel($capacity, $leakRate) {
      $this.capacity = $capacity;
      $this.leakRate = $leakRate; // 5个/s
      $this.leftSpace = $capacity;
      $this.startTime = time();
    }
   //腾出漏斗空间
   public void makeSpace() {
      $nowTime = time();
      $leakTime = $nowTime - $this.startTime;
      $leakCnt = $leakTime * $this.leakRate;
      $this.leftSpace += $leakCnt;
      $this.startTime = $nowTime;
      if ($this.leftSpace > $this.capacity) {
        $this.leftSpace = $this.capacity;
      }
    }
    //漏水操作
    public boolean watering($quota) {
      makeSpace();
      //$quota 漏水最小单位
      if ($this.leftSpace >= $quota) {
        $this.leftSpace -= $quota;
        return true;
      }
      return false;
    }
  }
}
class FunnelLimiter{
  private $funnels = array();
  public boolean isActionAllowed($userId, $action, $capacity, $leakRate) {
    $key = $userId."-".$actionKey;
    if(isset($funnels[$key])){
        $funnel = $funnels[$key];
    }else{
      $funnel = new Funnel($capacity, $leakRate);
      $funnels[$key] = $funnel;
}
    return $funnel.watering(1); 
  }
}

Redis-cell

Redis 4.0 提供了一个限流 Redis 模块redis-cell, 只有一个命令cl.throttle

cl.throttle verifycodesend  15  30 60 11)verifycodesend:用户操作key
(215: 漏斗容量
(330:允许操作的数量
(460:多长时间间隔
(51:默认参数           
返回值定义
cl.throttle verifycodesend 15 30 60
1) (integer) 0   # 0 表示允许,1表示拒绝
2) (integer) 15  # 漏斗容量capacity
3) (integer) 14  # 漏斗剩余空间left_quota
4) (integer) -1  # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2   # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

我们可以直接使用这个模块,爽歪歪,不过要注意失败了重试,重试的时间在返回参数中定义了。

猜你喜欢

转载自blog.csdn.net/William0318/article/details/89374979