IP访问频率限制不能用数组循环插入多个限制条件原因分析及解决方案
14.IP频率限制不能用数组循环插入多个限制条件原因分析及解决方案:
define("RATE_LIMITING_ARR", array('3' => 3, '6' => 10));//缓存访问频率限制设置数组 foreach (RATE_LIMITING_ARR as $limit => $timeout) { $redis->access_limit($_SERVER['REMOTE_ADDR'], $limit, $timeout); } function access_limit($ip, $limit, $timeout) { $key = "rate.limiting:{$ip}"; $check = $this->increase($key); if ($check == 1) { $this->expire($key, $timeout); } else { $count = $this->get($key); if ($count > $limit) { header("location:../error.php?error_type=rate_limiting"); } } $count = $this->get($key); echo $limit . '秒内第 ' . $count . ' 次访问' . '<br>'; }
第一次进入页面显示: 3s刷新页面显示3s内第二次刷新页面
当前用户IP:127.0.0.1 |
当前用户IP:127.0.0.1 |
请求太频繁,请返回主页稍后重试! |
由上面的数据和实际情况可知,设置的时3秒3次,其实3秒内不到3次就已经超出限制了,测试大概不到两次就超出,
思考过后原因:数组中有两组数据,每次会进行两次循环调用函数.
第一次访问页面时:
-
第一次函数调用(‘3’=>3): 此时的$limit = 3,$check = 1,$timeout = 3,$count = 1,然后页面打印内容:3秒内第1次访问.
-
第二次函数调用(‘6’=>10): 此时的$limit = 6;$check = 2,else之后的$count =2,$count<$limit,没有超出,打印内容:10秒内第2次访问
此时也就形成了第一次访问页面显示的内容.
第二次访问页面时:
-
第一次调用函数(‘3’=>3):此时$limit又被设置为3,$check=3,else后判断$count=3,也没有超出,然后打印内容为:3秒内第3次访问.
-
第二次调用函数(‘6’=>10):此时$limit又被设置为6,$check=4,else后判断$count =4<$limit,也没有超出,然后打印内容:10内第4次访问.
第三次访问页面时:
如果在3秒内访问的第三次,就会溢出:
-
第一次调用函数(‘3’=>3):此时$limit又被设置为3,$check=5,else后判断$count=5>$limit=3,溢出.
如果不是在3秒内访问的第三次,可能是第4秒第5秒等等,但是此时$key已经过期了,$check又会等于1,所有参数都会重置和第一次访问页面一样,所以会循环之前的过程;
-
第一次调用函数(‘3’=>3):此时$limit=3,$check=1,$timout=3,$count=1
-
第二次调用函数(‘6’=>10):此时$limit=6,$check=2,$timeout=3,$count=2<6
上面的过程中
第几次访问页面 |
$limit |
$check |
$timeout |
备注 |
|
第一次 |
3->6 |
1->2 |
3 |
timeout只被设置一次 |
2<6 |
第二次 |
6→3->6 |
2->3->4 |
3 |
timeout没有变过 |
4<6 |
第三次 |
6->3 或6->3->6 |
4→5 或4->1 |
3 |
timeout还是3秒 |
5>3溢出 或继续循环 |
在重复以上过程中,key的过期时间会永远为3秒,经过页面显示过期时间也确实是这样,最重要的时我保持大概每秒刷新一次,到了第7次时没有触发10秒6次的限制,
由此可以下结论:利用数组循环输入限制参数时,后面的第二个第三个条件是得不到验证的,只会触发第一个限制条件,所以只能够设置多个key来实现多个限制条件.
解决方案:
define("RATE_LIMITING_ARR", array('3' => 3, '6' => 10));//缓存访问频率限制设置数组 $redis->access_limit($_SERVER['REMOTE_ADDR']); function access_limit($ip) { $limit_arr = []; $timeout_arr = []; foreach (RATE_LIMITING_ARR as $limit => $timeout) { $limit_arr[] = $limit; $timeout_arr[] = $timeout; } for ($i = 0; $i < count($limit_arr); $i++) { $key= "rate.limiting".$i.":{$ip}"; $check = $this->increase($key); if ($check == 1) { $this->expire($key, $timeout_arr[$i]); } else { if ($check > $limit_arr[$i]) { header("location:../error.php?error_type=rate_limiting"); } } echo $timeout_arr[$i] . '秒内第 ' . $check . ' 次访问' . '<br>'; echo "访问限制".$key."过期时间剩余:".$this->rest_survival_time($key)."<br>"; } }
验证一:
结论:3秒内连续两次访问页面,两个限制条件互不干扰,都时过去了2秒
验证二:
第一次访问页面: 第二次访问页面:
第三次: 第四次:
结论:每多余3秒的时间去访问时,3秒3次的限制会重置,而10秒6次的限制不会受到干扰.