利用memcached实现一个简单的频率限制功能

之前开放了获取出口IP功能的php,但是发现部分IP过来的请求达到了每秒10次以上,耗费服务器资源啊。

加上阿里云免费使用128MB存储的OCS(Memcached),故简单弄了个频率限制:


<?php
/*
 * 利用Memcached,实现一个简单的频率限制组件
 * */
class ActLimit{
	private $ocs_memcached_addr;
	private $ocs_username;
	private $ocs_pwd;
	private $ocs_port;
	private $mmc;

	public function __construct($mem_addr,$uname,$upwd,$port=11211){
		$this->ocs_memcached_addr = $mem_addr;
		$this->ocs_username = $uname;
		$this->ocs_pwd = $upwd;
		$this->ocs_port = $port;

		if (!class_exists('Memcached')) {
			die('limit needs Memcached extension.');
		}

		$this->mmc = new Memcached();
		$this->mmc->setOption(Memcached::OPT_COMPRESSION, false);
		$this->mmc->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
		$this->mmc->addServer($this->ocs_memcached_addr , $this->ocs_port);
		$this->mmc->setSaslAuthData($this->ocs_username,$this->ocs_pwd);
		if (!$this->mmc->set('ActLimit',1,10)){
			die('memcached connected failed,errno:'.$this->mmc->getResultCode().',errmsg:'.$this->mmc->getResultMessage());
		}
	}

	public  function __destruct(){
		$this->mmc->quit();
	}

	// 操作次数限制函数: 限制 uid 在 period 秒内能操作 action 最多 max_count 次.
	// 如果超过限制, 返回 false.
	public function check_limit($uid, $action, $max_count, $period){
		$now = time();
		$expire = intval($now / $period) * $period + $period;
		$ttl = $expire - $now;
		$count = $max_count;
		$key = "act_limit|$uid|$action";
		if($this->mmc->get($key)===false){
			$this->mmc->set($key,1,$ttl);//if key is not exists, inc opt can occur error.
			$count=1;
		}else{
			$this->mmc->increment($key);//memcached increment is not support return inc value.
			$count = $this->mmc->get($key);//so need to get value by key.
		}
		if($count === false || $count > $max_count){
			return false;
		}
		return true;
	}
	
	public function limit_reach_exit(){
		header("HTTP/1.0 301 Moved Permanently");
		header("Limit-Server: limit reached at time ".time());
		die();
	}
	
	public function app_access_limit($uid,$action='get_ip'){ 
		$max_count=3;//1min per 3 counts
		$period=60;//seconds
		
		if(false===$this->check_limit($uid, $action, $max_count, $period)){
			$this->limit_reach_exit();
		}
		return true;
	}
	
	public function visit_limit($uid) {
		$action='emlog_access';
		$max_count=5;//1min per 5 counts
		$period=60;//seconds
		if(false===$this->check_limit($uid, $action, $max_count, $period)){
			$this->limit_reach_exit();
		}
		return true;
	}
	
}//endclass


$ocs_memcached_addr = 'xxx.m.cnhzalicm10pub001.ocs.aliyuncs.com';
$ocs_username = 'xxx';
$ocs_pwd = 'xxx';

global $act_limit;
$act_limit = new ActLimit($ocs_memcached_addr,$ocs_username,$ocs_pwd);


?>

使用方法很简单了,可以使用check_limit,或者简单封装的方法

比如以访问IP为key,限制每分钟最多请求三次。

$act_limit->app_access_limit($uid=$_SERVER['REMOTE_ADDR'],'get_ip');

| 0个评论