讲师中心 微信公众号
AI工具推荐 视频效率加速

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

千芳同学_3365

千芳同学_3365

发布时间:2016-05-23 16:38:50

|

1739人浏览过

|

来源于php中文网

原创

使用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;
}

?>

                   

PHP
PHP

编写健壮的PHP代码,规避类型转换陷阱、数组怪癖及常见安全漏洞。

下载

                   

热门AI工具

更多
UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

0

2026.09.23

Buffalo框架路由与请求处理实操指南
Buffalo框架路由与请求处理实操指南

本专题讲解Buffalo框架路由与请求处理机制,涵盖路由注册与分组、资源路由、Handler编写规范、Context上下文方法、参数绑定、中间件编写挂载、Session与Cookie读写、Flash消息及错误页面定制方法。

0

2026.09.23

Buffalo框架零基础入门教程
Buffalo框架零基础入门教程

本专题整理Buffalo框架入门内容,涵盖Go环境准备、buffalo CLI安装、新项目生成、目录结构说明、dev热加载启动、数据库连接配置与常见报错排查,帮助新手按约定优于配置的思路跑通第一个Buffalo框架应用。

0

2026.09.23

Conan创建软件包配方指南
Conan创建软件包配方指南

本专题介绍通过conanfile.py创建软件包的方法,讲解包名、版本、依赖和构建设置等基础信息,以及source、build、package、package_info等常用方法的作用及编写思路。

0

2026.09.22

Conan二进制包配置指南
Conan二进制包配置指南

本专题介绍Conan根据操作系统、编译器、架构和构建类型生成二进制包的方法,讲解Profile、Settings、Options及Package ID的作用,帮助管理不同平台和编译环境下的包版本。

0

2026.09.22

Conan私有仓库搭建教程
Conan私有仓库搭建教程

本专题系统的讲解Conan私有仓库的搭建流程,涵盖仓库服务部署、存储目录配置、用户认证、权限划分和远程地址添加,并介绍内部C++依赖包的上传、下载及版本维护方法。

0

2026.09.22

loomy官网入口地址合集
loomy官网入口地址合集

本专题汇总了 Loomy 桌面 AI 助理的官方入口地址合集及使用指南。提供 macOS 与 Windows 客户端下载 。Loomy 是讯飞推出的桌面级 AI 工作搭子,支持文件整理、数据分析、网页操作及通过飞书/钉钉远程操控电脑,助你高效完成本地办公任务 。

0

2026.09.22

NumPy常见函数使用方法
NumPy常见函数使用方法

本专题整理 NumPy 常见函数使用方法相关教程,覆盖函数大全、参数用法、数组运算、统计聚合、排序处理、where 条件筛选、linspace 创建数列等常用场景,帮助读者快速掌握 NumPy 函数调用思路和实际数据处理技巧。

0

2026.09.22

NumPy性能优化版本更新与常见报错排查
NumPy性能优化版本更新与常见报错排查

本专题整理 NumPy 性能优化、版本更新与常见报错排查相关教程,覆盖向量化计算、广播性能、内存布局、NumPy 2.0 升级、版本兼容冲突、安装导入报错、dtype 溢出、矩阵运算异常和 broadcasting 报错修复,帮助读者系统掌握 NumPy 性能调优与问题定位方法。

20

2026.09.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn