Redis 实现简单的限流

本文最后更新于:2020年2月11日 上午

利用 Redis 有序集合实现,并用管道加速。

假设 $period 秒内,一个用户只能访问 $maxCount 次。用户 ID 作为 key,毫秒时间戳作为 score 和 value。

一个请求进入,

  • 加入有序集合——zadd
  • 移除时间窗口之前的行为记录,剩下的都是时间窗口内的——zremrangebyscore
  • 更新过期时间——expire
  • 获取窗口内的元素数量——zcard
  • 判断窗口内元素数量是否大于最大请求限制数(maxCount),若大于等于则拒绝请求,若小于则接受请求。

代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
}

//返回当前的毫秒时间戳
function msectime() {
list($msec, $sec) = explode(' ', microtime());
$msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
return $msectime;
}

// 测试执行
for ($i=0; $i<20; $i++){
var_dump(isActionAllowed("110", "reply", 60, 5)); //执行可以发现只有前5次是通过的
}



本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!