版权声明:本文为博主原创文章,未经博主允许不得转载。 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 1
(1)verifycodesend:用户操作key
(2)15: 漏斗容量
(3)30:允许操作的数量
(4)60:多长时间间隔
(5)1:默认参数
返回值定义
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,单位秒)
我们可以直接使用这个模块,爽歪歪,不过要注意失败了重试,重试的时间在返回参数中定义了。