使用etag和文件缓存降低服务器数据库压力
程序员文章站
2022-03-30 14:08:39
...
使用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; } ?>
下一篇: JavaScript递归遍历和非递归遍历