共计 1797 个字符,预计需要花费 5 分钟才能阅读完成。
常见的限流算法有:计数器,漏桶、令牌桶。
计数器(工夫窗口)
原理:记录每个申请,判断在设定的限流工夫窗口内申请数是否大于限度数。 限流要留神防止边界问题,滑动工夫窗口的办法能很好解决这个问题。
利用 Redis 有序汇合实现,并用管道减速 。
思路:假如 $period 秒内,一个用户只能拜访 $maxCount 次。用户 ID 作为 key,毫秒工夫戳作为 score 和 value。
一个申请进入,
- 退出有序汇合——zadd
- 移除工夫窗口之前的行为记录,剩下的都是工夫窗口内的——zremrangebyscore
- 更新过期工夫——expire
- 获取窗口内的元素数量——zcard
- 判断窗口内元素数量是否大于最大申请限度数(maxCount),若大于等于则拒绝请求,若小于则承受申请。
function isActionAllowed($userId, $action, $period, $maxCount)
{$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = sprintf('hist:%s:%s', $userId, $action);
$now = msectime(); # 毫秒工夫戳
$pipe=$redis->multi(Redis::PIPELINE); // 应用管道晋升性能
$pipe->zadd($key, $now, $now); //value 和 score 都应用毫秒工夫戳
$pipe->zremrangebyscore($key, 0, $now - $period * 1000); // 移除工夫窗口之前的行为记录,剩下的都是工夫窗口内的
$pipe->zcard($key); // 获取窗口内的行为数量
$pipe->expire($key, $period + 1); // 多加一秒过期工夫
$replies = $pipe->exec();
return $replies[2] <= $maxCount;
}
// 执行
for ($i=0; $i<20; $i++){var_dump(isActionAllowed("110", "reply", 60, 5)); // 执行能够发现只有前 5 次是通过的
}
// 返回以后的毫秒工夫戳
function msectime() {list($msec, $sec) = explode(' ', microtime());
$msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
return $msectime;
}
漏斗算法
原理:漏桶 (Leaky Bucket) 算法思路很简略,水 (申请) 先进入到漏桶里,漏桶以肯定的速度出水 (接口有响应速率), 当水流入速度过大会间接溢出 (拜访频率超过接口响应速率), 而后就拒绝请求,能够看出漏桶算法能强行限度数据的传输速率。
利用 Redis-Cell 模块实现
Redis 4.0 提供了一个限流 Redis 模块,名称为 redis-cell,该模块提供漏斗算法,并提供原子的限流指令。
该模块只有一条指令 cl.throttle,上面看一下其参数和返回值
> cl.throttle tom:reply 14 30 60 1
1) (integer) 0 # 0 示意容许,1 示意回绝
2) (integer) 15 # 漏斗容量 capacity
3) (integer) 14 # 漏斗残余空间 left_quota
4) (integer) -1 # 如果回绝了,须要多长时间后再重试,单位秒
5) (integer) 2 # 多长时间后,漏斗齐全空进去,单位秒
令牌桶算法
原理:令牌桶算法 (Token Bucket) 和 Leaky Bucket 成果一样但方向相同的算法,更加容易了解。随着工夫流逝,零碎会按恒定 1/QPS 工夫距离 (如果 QPS=100, 则距离是 10ms) 往桶里退出 Token (设想和破绽漏水相同,有个水龙头在一直的加水), 如果桶曾经满了就不再加了。新申请来长期,会各自拿走一个 Token, 如果没有 Token 可拿了就阻塞或者拒绝服务。
利用 Redis String + 定时工作实现
利用定时工作一直减少令牌数,Redis->incr(),自增一,令牌桶满则不再减少;
来一个申请耗费一个令牌,Redis->decr(),自减一,当没有令牌时则拒绝请求。
一旦须要进步速率,则按需进步放入桶中的令牌的速率即可。
参考 https://learnku.com/articles/…