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

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

Chris Yue No Comment
Posts

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

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

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

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

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

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

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

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

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

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

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

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

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

使用 APC(u) 创建访问频次限制服务 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

文章不错,我要帮站长分担建站费!
天使投赏人

发表评论

3 + = 12