Home  >  Article  >  Backend Development  >  使用APC创建访问频次限制服务

使用APC创建访问频次限制服务

WBOY
WBOYOriginal
2016-06-20 12:40:441000browse

我们经常会遇到这样的需求:红包在5分钟内不能领取两次,在1分钟内不能超过两次抽奖,或者某个接口一个IP一天只能访问50次,这种需求就是访问频次限制。

使用PHP的小伙伴应该对APC都不太陌生,APC不但可以用来缓存opcode,而且也可以用做shared memory。利用APC的第二种特性,我们可以用来做类似NGINX的limit_req模块的访问频次限制功能。

访问限制的关键在于需要知道每一次访问的请求时间,然后查看从当前时间往前推的时间段内,有多少次重复的访问。利用APC可以设置ttl的特性,我们可以很简单的做到这点:

apc_store($uniqid, 1, $period); // $period为限制的时间,以“1分钟只能抽两次奖”这个需求为例,$period = 60

其中uniqid变量为一个被限制访问的对象的唯一标识,比如用户的ID,手机设备号,IP等。

上面的代码还有些问题:如果允许在$period时间段内可以访问一次以上,那么$key是不能不变的,否则后一次访问会把前一次的访问设置的$key的过期时间替换掉,也无法判断在$period时间段内,到底有多少次访问。

既然$key不能被替换,那么思路只能是每次访问都要生成不一样的$key,这通过 microtime和 mt_rand两个函数还是很容易做到的:

$key = sprintf('%s.%s%s', %uniqid, $microtime(true), mt_rand(0, 99999));app_store($key, 1, $period);

从PHP函数手册上看,apc_*函数并没有提供可以通过key的前缀来获取所有的key的方法,如果没有这样的方法,上面的思路又被卡死,不过且慢,APC还提供了一个类,叫做APCIterator。解决我们需求的最重要一块拼图就是它了。

从 官方文档来看,这个类构造函数的$search参数,就提供了我们所需要的功能:通过正则的方式来匹配key:

$iter = new APCIterator('user', sprintf('/^%s\./', preg_quote($uniqid)));

从文档里我们可以看到APCIterator里有一个 getTotalCount方法,大家不要被这个方法的名字骗了,从测试的结果来看,这个方法返回的数,居然是包含了已经过期的key的,再加上PHP官方文档对这个方法的描述几乎可以说是毫无用处,这个方法是非常的坑。

这里多说一句:APCIterator还有一个方法叫做 getHitsCount,这里的Hits不是指$search是否能匹配,而是指当前的脚本使用apc_fetch时是否命中。其实方法还是挺好用的,就是文档啥都不写,还得自己试验自己猜,坑死个人。

我们直接将这个Iterator使用foreach遍历一下,可以发现遍历的结果居然是对的(为啥要说居然……),所有有效期内的key都有,不在有效期的key都没有,完全是我们想要的效果。对于“1分钟内每个人只能抽奖两次”这样的需求,我们终于有了解法:

if (iterator_count($iter) >= $count) { // 例子里$count = 2    header('status: 429 Too Many Requests'); die();}

当然,我们还有可以改进的空间,比如说,不同的时间段长度+不同的访问次数应该属于不同的限制策略,所以$key里面应该需要体现时间段长度和访问次数:

$key = sprintf('%s.%s.%s', $uniqid, $period, $count);

代码有些零散,但思路就是这样的。我把代码综合一下变成一个函数,大家更看得明白些吧:

function ratelimit($uniqid, $period, $count){    // ...省去参数检查的代码    $key = sprintf('%s.%s.%s', 

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn