• 技术文章 >php教程 >PHP源码

    使用etag和文件缓存降低服务器数据库压力

    PHP中文网PHP中文网2016-05-23 16:38:50原创591

    使用php5.3+,使用了一些自定义的内容,不过都一看便知
    比如常量ROOT、DIR_CACHE等
    核心使用的有
    diehere(输出json字符串,并die),err_a(组合错误信息),makedir(连续创建目录)
    其余的都根据实际使用的情况来

    终于debug完成了……新增one_key方法,一键完成输出,完美……
    departments初次查询170ms
    之后仅16ms,越复杂效果越好啊

    高复杂测试get_users_complex.php
    初次108.0 KB 985ms
    第二次16ms,哈哈哈,
    清空etag(未清空data)读取,接收数据125ms

    1. [代码]DEF.inc.php

    	define('ROOT',dirname(__FILE__));
    
    	define('CLS_SCACHER','/inc/SCACHER.cls.php');
    	define('CLS_ECACHER','/inc/ECACHER.cls.php');
    
    	define('DIR_CACHE','/cache/');	//用于缓存判断的目录
    
    
    function run_sql($sql){
    	static $db;
    	if(!$db){
    		$db=getdb();
    	}
    	return mysql_query($sql,$db);
    }
    
    function getdb(){
    	static $mydb;
    	if(!$mydb){
    		$mydb=dbconnection();
    	}
    	return $mydb;
    }
    
    function dbconnection(&$var=0){
    	
    	if($var==0||!is_array($var)){$var=array();}
    	if(!isset($var['dbhost']) || !is_string($var['dbhost'])){	$var['dbhost']=constant('DBHOST');}
    	if(!isset($var['dbuser']) || !is_string($var['dbuser'])){	$var['dbuser']=constant('DBUSER');}
    	if(!isset($var['dbpsw']) || !is_string($var['dbpsw'])){$var['dbpsw']=constant('DBPSW');}
    	$db=mysql_connect($var['dbhost'],$var['dbuser'],$var['dbpsw']) or die();
    	if(!$db){return 0;}
    	mysql_select_db(constant('DBNAME'),$db) or die();//echo('db enter here');
    	mysql_query("SET NAMES 'UTF8'");
    	return $db;
    }
    
    function PR($v){
    	if(isset($v)){
    		echo('<pre>');
    		print_r($v);
    		echo('</pre>');
    	}
    }
    
    function rs_2_array($rs){
    //this is a function used to make the code clear and less
    //i am tired to code same code to get the arry result
    //thought it is not much
    //redlz2500@2008-06-24
    	$t=array();
    	try {
    		while($row=mysql_fetch_array($rs,MYSQL_ASSOC)){
    			$t[]=$row;
    		}
    		return $t;
    	}catch (Exception $e) {
    		
    	}
    	return $t;
    }
    
    /*
     * 功能:连续建目录
     * $dir 目录字符串
     */
    function makedir($dir,$mode = '0777') {
    	//notice: the $dir will not set the code style &
    	//as maybe call by $str.$str1
    	//the var can not be reference
    	if(!isset($dir)){return 0;}
    	//echo('<br>**********intomakedir*************<br>'.$dir);
    	$dir = str_replace( "\\", "/", $dir );
    	$mdir = "";
    	foreach( explode( "/", $dir ) as $val ) {
    		$mdir .= $val."/";
    		if( $val == ".." || $val == "." ) continue;
    		if( ! file_exists( $mdir ) ) {
    			if(!@mkdir( $mdir, $mode )){
    				echo "创建目录 [".$mdir."]失败.";
    				exit;
    		  	}
    	 	}
    	}
    	return true;
    }

    2. [代码]CLS_SCACHER

    <?php
    /*
    //超级简单的文件缓存类,用于ECACHE的数据缓存支持
    //使用ROOT.DIR_CACHE作为基本目录,下面再是path划分小目录,category和name组合为缓存文件的名称
    //不包括时间有效期,若需使用时间有效判定,使用CLS_CACHER类
    //属性配置调用scacher方法
    //set($v)直接设置$v的值到缓存
    //get()和del()无参数,含义自明
    //主要用于CLS_ECACHER的底层支持
    //redlz2500@20151022
    */
    	
    	class scacher{
    		protected $fullpath='';
    		protected $path='';	//在ROOT.DIR_CACHE目录下的,左右无目录分隔符
    		protected $category='default';	//缓存下的分类
    		protected $name='mycache';	//文件标识名称
    		
    		public function __construct($opt){
    			$this->scacher($opt);
    		}
    		
    		public function scacher($opt=[]){
    			$flag=false;
    			if($opt['category'] && is_string($opt['category']) && ($this->category!=$opt['category']) ){
    				$this->category=$opt['category'];
    				$flag=true;
    			}
    			if($opt['name'] && is_string($opt['name']) && ($this->name!=$opt['name'])){
    				$this->name=$opt['name'];
    				$flag=true;
    			}
    			if($opt['path'] && is_string($opt['path']) && ($this->pat!=$opt['path'])){
    				$this->path=$opt['path'];
    				$flag=true;
    			}
    			if($flag){
    				if($this->path){
    					$this->fullpath=ROOT.DIR_CACHE.$this->path.'/';
    				}else{
    					$this->fullpath=ROOT.DIR_CACHE;
    				}
    				if(!file_exists($this->fullpath)){
    					makedir($this->fullpath);
    					if(!file_exists($this->fullpath)){
    						throw new Exception('errSCACHER配置失败 当前调用参数:'.$this->category.'.'.$this->name);
    					}
    				}
    			}
    		}
    		
    		public function set($v){
    			$fp=fopen($this->fullpath . $this->category .'.'. $this->name,'w');
    			if (!fwrite($fp,$v)) {
    				return ['success'=>false,'error'=>err_a('errSCACHER_1','数据写入失败,请稍后重试。<br/>重试无效请联系管理员。<br/>当前调用参数:'.$this->category.'.'.$this->name)];
    			}  
    			@fclose($fp);
    			return ['success'=>true];
    		}
    		public function get(){
    			$f=$this->fullpath . $this->category .'.'. $this->name;
    			if(file_exists($f)){
    				$res=@file_get_contents($f);
    				if(!$res){
    					$res='';
    				}
    				return ['success'=>true,'data'=>$res];
    			}else{
    				return ['success'=>false,'data'=>'','error'=>err_a('errSCACHER_3','未找到缓存。<br/>当前调用参数:'.$this->category.'.'.$this->name)];
    			}
    		}
    		public function del(){
    			$f=$this->fullpath . $this->category .'.'. $this->name;
    			if(file_exists($f)){
    				@unlink($f);
    				if(file_exists($f)){
    					return ['success'=>false,'error'=>err_a('errSCACHER_2','数据处理异常,请稍后重试。<br/>重试无效请联系管理员。<br/>当前调用参数:'.$this->category.'.'.$this->name)];
    				}
    			}else{
    				return ['success'=>true];
    			}
    		}
    	}
    ?>

    3. [代码]CLS_ECACHER

    <?php
    /*
    //基于CLS_CACHER的缓存机制,包括etag参数以及其余的数据,主要用于单个的json数据缓存
    //主要目的为在服务器端给json方式做缓存,模式如下:
    //核心的detail缓存由后台互动生成(也可以由前台生成,方法摆在这里自己组合)
    //1、查询端query.php
    //	调用etag_chk,相同则 发送304header(默认允许)
    //		不同则调用data_get方法,取出缓存,如果取出缓存失败,则前台处理,不重新生成缓存(也可以生成,但是需重新包括缓存生成方法)
    //2、数据生成页面trigger.php
    //	触发数据重新生成机制 ,生成新的缓存,并更新etag信息,这样做在触发频繁的情况下可能引起大量无必要的数据库操作,
    //可在此时修改触发方式,或者触发的时候仅清空数据,但是并不重新生成缓存,而在前台实际调用的时候才执行缓存生成操作
    //A、或者是在查询段负责生成数据,触发端负责清空缓存
    //	ecacher重设参数
    //	mode_etag mode_data在两种模式下切换,内部方法
    //	etag_chk	检查浏览器是否一致,一致的话 发送304(默认允许)
    //	etag_create	生成新的etag并缓存
    //	data_get	获取缓存的data
    //	data_create	调用外部定义的方法以及参数生成缓存并重设etag,注意,虽然重设了etag,但是并不会重新发送200
    //	clear	清空数据,传入数组
    //第一次生成数据的时候可能不正确,未处理	已经解决redlz2500@20151022
    //v1.1新增one_key方法
    //v1.2增加catch-control输出。某个页面一直无法输出304,检查服务器返回catch-control:no-catch……查不出原因,直接重写了……
    //v1.3增加force参数,用于强制输出catch-control控制,默认false,为true强制输出自己的catch-control,以避免和php自己的session_cache_limiter冲突
    //redlz2500@20151022
    */
    
    	define('DEF_ECACHE_PERFECT','0001');	//浏览器发送了匹配的etag,完美,返回304
    	define('DEF_ECACHE_BROWSER_NULL','0010');	//浏览器未发送etag
    	define('DEF_ECACHE_ETAG_NULL','0100');	//本地的etag记录为空(可能是数据真空期)
    	define('DEF_ECACHE_ETAG_CREATED','1000');//etag成功生成
    
    	require_once(ROOT.CLS_SCACHER);//使用scacher类
    	
    	class ecacher{
    		protected $path='';
    		protected $category='default';	//当前类别的分类
    		protected $name='myname';		//模板名称
    		//以上三个是scacher类的定义,方式与ecacher相同,缓存位置由ecacher来控制
    		protected $force_cache=false;
    		protected $auto_send_etag_header=true;	//是否自动发送header信息
    
    		protected $create_fn='';	//没有数据的时候生成数据的回调函数,返回数据由data_create处理,仅支持字符串
    		protected $create_par;		//生成数据的时候需要传送的参数,按参数先后顺序组合为array传送,不是数组则自动将其转换为数组
    		
    		protected $scacher;
    		
    		public function __construct($opt){
    			$this->scacher=new scacher([]);		//scacher实例,路径由scacher来控制
    			$this->ecacher($opt);
    		}
    		
    		public function __destruct(){
    			
    		}
    		
    		function ecacher($opt){
    			if(is_array($opt)){
    				if($opt['force_cache']){
    					$this->force_cache=true;
    				}else{
    					if(isset($opt['force_cache'])){
    						$this->force_cache=false;
    					}
    				}
    				if($opt['path'] && is_string($opt['path'])){
    					$this->path=$opt['path'];
    				}
    				if($opt['category'] && is_string($opt['category'])){
    					$this->category=$opt['category'];
    				}
    				if($opt['name'] && is_string($opt['name'])){
    					$this->name=$opt['name'];
    				}
    				if(isset($opt['auto_send_etag_header'])){
    					$this->auto_send_etag_header=$opt['auto_send_etag_header'];
    				}
    				if($opt['create_fn'] && is_string($opt['create_fn'])){
    					$this->create_fn=$opt['create_fn'];
    				}
    				if($opt['create_par']){
    					if(is_array($opt['create_par'])){
    						$this->create_par=$opt['create_par'];
    					}else{
    						$this->create_par=[$opt['create_par']];
    					}
    				}else{
    					$this->create_par=[];
    				}
    				$this->scacher->scacher($opt);//更新的数据写入(好吧,其实并没有什么卵用)(好吧,可以提前判断缓存路径有没有效)
    			}
    		}
    		
    		private function mode_etag(){
    			$this->scacher->scacher(['name'=>$this->name.'.etag']);
    		}
    		private function mode_data(){
    			$this->scacher->scacher(['name'=>$this->name.'.']);
    		}
    		public function etag_chk(){
    			$this->mode_etag();//设置etag模式
    			$etag=$this->scacher->get();
    			echo_debug('test etag');
    			echo_debug($etag);
    			if($etag['success']){
    				$etag=$etag['data'];
    			}else{
    				return $etag;
    			}
    			$s_etag=$_SERVER['HTTP_IF_NONE_MATCH'];
    			echo_debug('etag from browse');
    			echo_debug($s_etag);
    			if($etag){
    				if($s_etag==$etag){
    					if($this->auto_send_etag_header){
    						if($this->force_cache){
    							header('Cache-Control: max-age=0');
    							header('Expires: '.gmdate('D, d M Y H:i:s', time() + SERVER_TIME_SHIFT + 10 ) . ' GMT' );
    						}
    						header('Etag:'.$etag,true,304);
    						die();//必须die,否则还会继续执行下去。
    					}else{
    						return [	'etag'=>$etag,	'statue'=>DEF_ECACHE_PERFECT	];
    					}
    				}else{
    					if($this->auto_send_etag_header){
    						if($this->force_cache){
    							header('Cache-Control: max-age=0');
    							header('Expires: '.gmdate('D, d M Y H:i:s', time() + SERVER_TIME_SHIFT + 10 ) . ' GMT' );
    						}
    						header('Etag:'.$etag);
    					}
    					return [	'etag'=>$etag,	'statue'=>DEF_ECACHE_BROWSER_NULL	];
    				}
    			}else{
    				return [
    					'etag'=>'',
    					'statue'=>DEF_ECACHE_ETAG_NULL
    				];
    			}
    		}
    		
    		public function etag_create($auto=false){
    			$etag=md5($this->category.':'.$this->name.':'.time().':'.ranstr());
    			$this->mode_etag();
    			$this->scacher->set($etag);
    			if($auto){
    				if($this->force_cache){
    					header('Cache-Control: max-age=0');
    					header('Expires: '.gmdate('D, d M Y H:i:s', time() + SERVER_TIME_SHIFT + 10 ) . ' GMT' );
    				}
    				header('Etag:'.$etag);
    			}
    			echo_debug('etag create finish:'.$etag);
    			return [
    				'success'=>true,
    				'etag'=>$etag,
    				'status'=>DEF_ECACHE_ETAG_CREATED
    			];
    		}
    		
    		public function data_get(){
    			//PR('begin get data');BR();
    			$this->mode_data();
    			$data=$this->scacher->get();
    			if($data['success']){
    				echo_debug('orgin data is:');
    				echo_debug($data['data']);
    				$data['data']=unserialize($data['data']);
    			}else{
    				echo_debug('not success:');
    				echo_debug($data);
    				$data['data']='';
    			}
    			echo_debug();
    			echo_debug('the data is:');
    			echo_debug($data);
    			return $data;
    		}
    		public function data_create($auto_etag=false){
    			if(!$this->create_fn){
    				throw new Exception('<ECACHER>未传递数据生成函数<br/>当前参数:'.$this->category.'.'.$this->name);		//这样的错误时不允许的,因此直接抛出错误
    				die();
    			}
    			$data=call_user_func_array($this->create_fn,$this->create_par);
    			//生成数据的处理
    			if($data===false){
    				throw new Exception('<ECACHER>生成数据失败<br/>当前参数:'.$this->category.'.'.$this->name);		//无法,只有不返回false了
    				die();
    			}
    			//PR($data);
    			$s_data=serialize($data);
    			$this->mode_data();
    			$res=$this->scacher->set($s_data);
    			if(!$res['success']){	return $res;	}
    			if($auto_etag){
    				$res=$this->etag_create();
    				if(!$res['success']){	return $res;	}
    			}
    			return ['success'=>true,'data'=>$data];
    		}
    		
    		public function clear($p=['etag','data']){
    			if(in_array('both',$p)){
    				$p=['etag','data'];
    			}
    			if(in_array('etag',$p)){
    				$this->mode_data();
    				$res=$this->scacher->del();
    				if(!$res['success']){	return $res;	}
    			}
    			if(in_array('etag',$p)){
    				$this->mode_etag();
    				$res=$this->scacher->del();
    				if(!$res['success']){	return $res;	}
    			}
    			return ['success'=>true];
    		}
    		
    		public function one_key(){
    			$r=$this->etag_chk();
    			if(!$r['etag']){
    				echo_debug('the etag is null,should be rebuild');
    				echo_debug($r);
    				$this->etag_create('auto');
    			}
    			$res=$this->data_get();
    			if($res['success']){
    				//PR($res);
    				if($res['data']){
    					diehere($res);
    				}
    			}
    			echo_debug('recreate data');
    			
    			$data=$this->data_create();
    			diehere($data);
    		}
    	}
    ?>

    4. [代码]get_departments.php

    <?php
    //实在受不了每次的数据的读取咯,所以按照以下的方式进行处理:
    //对于部门,因为内容不算很多,120多个的样子,有效部门90个的样子,因此将其一次性进行处理,使用这个东西来创造,使用缓存机制
    //如果数据没有变化的,就读取缓存,如果有变化的,就发送数据
    //redlz2500@20151022
    	define('IN_SERVER',1);
    	require('../../../DEF.inc.php');
    	require(ROOT.CLS_ECACHER);
    	
    	//define('ECHO_DEBUG',0);
    	//define('ECHO_DEBUG',1);
    	
    	$e=new ecacher([
    		'create_fn'=>'get_departments',
    		'path'=>'json','category'=>'common','name'=>'department'
    	]);
    	
    	$e->one_key();
    	
    	die();
    function get_departments(){
    	$sql='select `depid` as `id`,`name`,`father`,`departcode` as `code` from `department` where `father` !=0';
    	
    	$rs=run_sql($sql);
    	$data=[];
    	require_once(ROOT.INC_MAIL);
    	while($row=mysql_geta($rs)){
    		$address=get_dep_mail_address($row['id']);
    		$fullname=explode('.',$address);
    		$fullname=array_reverse($fullname);
    		$fullname=implode('.',$fullname);
    		$row['fullname']=$fullname;
    		$data[]=$row;
    	}
    	return $data;
    }
    ?>

    5. [代码]get_users.php

    <?php
    //本来想一次性全部读取,想到数量还是有点儿大,还是按照部门来读取好了
    //redlz2500@20151022
    	define('IN_SERVER',1);
    	require('../../../DEF.inc.php');
    	require(ROOT.CLS_ECACHER);
    	
    	$par=$_POST;
    	$par=$_GET;
    	$res['success']=false;
    	if(!$par['depid']){
    		$res['error']=err_a('errU038','参数缺失');
    		diehere($res);
    	}
    	if(!isDecimalNumber($par['depid'])){
    		$res['error']=err_a('errU039','参数错误');
    		diehere($res);
    	}
    	$e=new ecacher([
    		'create_fn'=>'get_users',
    		'create_par'=>$par['depid'],
    		'path'=>'json','category'=>'common','name'=>'users_in_'.$par['depid']]);
    	
    	$e->one_key();
    	
    function get_users($depid){
    	$sql='select `uid`,`name`,`login`,`depid` from `user` where `register` = 1 and `depid` = '.$depid;
    	$rs=run_sql($sql);
    	$rs=rs_2_array($rs);
    	return $rs;
    }
    
    ?>

    6. [代码]get_users_complex.php

    <?php
    //本来想一次性全部读取,想到数量还是有点儿大,还是按照部门来读取好了
    //redlz2500@20151022
    	define('IN_SERVER',1);
    	require('../../../DEF.inc.php');
    	require(ROOT.CLS_ECACHER);
    	
    	$res['success']=false;
    
    	$e=new ecacher([
    		'create_fn'=>'get_users',
    		'create_par'=>$par['depid'],
    		'path'=>'json','category'=>'common','name'=>'users_all']);
    	
    	$e->one_key();
    	
    function get_users(){
    	$sql='select `uid`,`name`,`login`,`depid` from `user` where `register` = 1 ';
    	$rs=run_sql($sql);
    	require_once(ROOT.INC_MAIL);
    	$data=[];
    	while($row=mysql_geta($rs)){
    		$addr=_get_user_mail_address($row['login']);
    		$row['addr']=$addr;
    		$data[]=$row;
    	}
    	return $data;
    }
    
    ?>
    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:php
    上一篇:php 阿拉伯数字转中文汉字 下一篇:PHP读取网卡MAC地址
    Web大前端开发直播班

    相关文章推荐

    • PHP总结我的简单静态页生成 过程,• 整理php操作memcache缓存基础方法• php面向对象之工厂模式示例• PHP常用用的六个处理正则表达式函数• php 广告点击统计代码

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网