当我们需要限制单个接口在一定时间内的请求次数,可以通过 Redis 的 zset 类型来实现一个简单的限流。
整体思路是:每一个行为到来时,都维护一次时间窗口,将窗口外的记录全部都清理掉,只保留窗口内的记录。zset 集合中的 score 值非常重要,Value 值没有特别的意义,只要保证唯一就可以了。
这种方式只适合简单的限流,如果限流的量很大,比如:60 秒内操作不能超过 100 万次,就不适合使用窗口限流,因为需要记录窗口内的所有行为,会消耗大量的存储空间。
<?php
/**
* 滑动窗口限流
*
* @param [type] $user_id 用户id
* @param [type] $action 行为
* @param [type] $period 滑动窗口宽度(秒)
* @param [type] $max_count 限制次数
* @return 返回布尔值,true 表示未限制 false 表示限制
*/
function sliding_window($user_id,$action,$period,$max_count)
{
$key = "hist:{$user_id}:{$action}";
$time = time();
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
// 记录行为
$redis->zadd($key,$time,md5(microtime()));
// 移除掉窗口外的
$redis->zremrangebyscore($key,0,$time - $period);
// 设置过期时间,防止冷用户持续占用内存
// 过期时间应该是时间窗口长度+1s
$redis->expire($key,$period + 1);
// 取窗口行为数
return $redis->zcard($key) <= $max_count;
}
sliding_window(1,'add',60,5);