对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失。
例如:一个在线聊天室(这里假定把聊天内容写入文件),在同一时刻,用户A和用户B都要操作数据保存文件,首先是A打开了文件,然后更新里面的数据,但这里B也正好也打开了同一个文件,也准备更新里面的数据。当A把写好的文件保存时,这里其实B已经打开了文件。但当B再把文件保存回去时,这里已经造成了数据的丢失,因为这里B用户完全不知道它所打开的文件在它对其进行更改时,A用户也更改了这个文件,所以最后B用户保存更改时,用户A的更新就被会丢失。
对于这样的问题,一般的解决方案时当一进程对文件进行操作时,首先对其它进行加锁,意味着这里只有该进程有权对文件进行读取,其它进程如果现在读,是完全没有问题,但如果这时有进程试图想对其进行更新,会遭到操作拒绝,先前对文件进行加锁的进程这时如果对文件的更新操作完毕,这就释放独占的标识,这时文件又恢复到了可更改的状态。接下来同理,如果那个进程在操作文件时,文件没有加锁,这时,它就可以放心大胆的对文件进行锁定,独自享用。
一般的方案会是:
代码如下:
$fp=fopen('/tmp/lock.txt','w+');
if (flock($fp,LOCK_EX)){
fwrite($fp,"Write something here\n");
flock($fp,LOCK_UN);
}else{
echo 'Couldn\'t lock the file !';
}
fclose($fp);
代码如下:
if($fp=fopen($fileName,'a')){
$startTime=microtime();
do{
$canWrite=flock($fp,LOCK_EX);
if(!$canWrite){
usleep(round(rand(0,100)*1000));
}
}while((!$canWrite)&&((microtime()-$startTime) if($canWrite){
fwrite($fp,$dataToSave);
}
fclose($fp);
}
方案二:不使用flock函数,借用临时文件来解决读写冲突的问题。大致原理如下:
(1)将需要更新的文件考虑一份到我们的临时文件目录,将文件最后修改时间保存到一个变量,并为这个临时文件取一个随机的,不容易重复的文件名。
(2)当对这个临时文件进行更新后,再检测原文件的最后更新时间和先前所保存的时间是否一致。
(3)如果最后一次修改时间一致,就将所修改的临时文件重命名到原文件,为了确保文件状态同步更新,所以需要清除一下文件状态。
(4)但是,如果最后一次修改时间和先前所保存的一致,这说明在这期间,原文件已经被修改过,这时,需要把临时文件删除,然后返回false,说明文件这时有其它进程在进行操作。
实现代码如下:
代码如下:
$dir_fileopen='tmp';
function randomid(){
return time().substr(md5(microtime()),0,rand(5,12));
}
function cfopen($filename,$mode){
global $dir_fileopen;
clearstatcache();
do{
$id=md5(randomid(rand(),TRUE));
$tempfilename=$dir_fileopen.'/'.$id.md5($filename);
} while(file_exists($tempfilename));
if(file_exists($filename)){
$newfile=false;
copy($filename,$tempfilename);
}else{
$newfile=true;
}
$fp=fopen($tempfilename,$mode);
return $fp?array($fp,$filename,$id,@filemtime($filename)):false;
}
function cfwrite($fp,$string){
return fwrite($fp[0],$string);
}
function cfclose($fp,$debug='off'){
global $dir_fileopen;
$success=fclose($fp[0]);
clearstatcache();
$tempfilename=$dir_fileopen.'/'.$fp[2].md5($fp[1]);
if((@filemtime($fp[1])==$fp[3])||($fp[4]==true&&!file_exists($fp[1]))||$fp[5]==true){
rename($tempfilename,$fp[1]);
}else{
unlink($tempfilename);
//说明有其它进程 在操作目标文件,当前进程被拒绝
$success=false;
}
return $success;
}
$fp=cfopen('lock.txt','a+');
cfwrite($fp,"welcome to beijing.\n");
fclose($fp,'on');
方案三:对操作的文件进行随机读写,以降低并发的可能性。
在对用户访问日志进行记录时,这种方案似乎被采用的比较多。先前需要定义一个随机空间,空间越大,并发的的可能性就越小,这里假设随机读写空间为[1-500],那么我们的日志文件的分布就为log1~到log500不等。每一次用户访问,都将数据随机写到log1~log500之间的任一文件。在同一时刻,有2个进程进行记录日志,A进程可能是更新的log32文件,而B进程呢?则此时更新的可能就为log399.要知道,如果要让B进程也操作log32,概率基本上为1/500,差不多约等于零。在需要对访问日志进行分析时,这里我们只需要先将这些日志合并,再进行分析即可。使用这种方案来记录日志的一个好处时,进程操作排队的可能性比较小,可以使进程很迅速的完成每一次操作。
方案四:将所有要操作的进程放入一个队列中。然后专门放一个服务完成文件操作。队列中的每一个排除的进程相当于第一个具体的操作,所以第一次我们的服务只需要从队列中取得相当于具体操作事项就可以了,如果这里还有大量的文件操作进程,没关系,排到我们的队列后面即可,只要愿意排,队列的多长都没关系。
对于以前几种方案,各有各的好处!大致可能归纳为两类:
(1)需要排队(影响慢)比如方案一、二、四
(2)不需要排队。(影响快)方案三
在设计缓存系统时,一般我们不会采用方案三。因为方案三的分析程序和写入程序是不同步的,在写的时间,完全不考虑到时候分析的难度,只管写的行了。试想一下,如我们在更新一个缓存时,如果也采用随机文件读写法,那么在读缓存时似乎会增加很多流程。但采取方案一、二就完全不一样,虽然写的时间需要等待(当获取锁不成功时,会反复获取),但读文件是很方便的。添加缓存的目的就是要减少数据读取瓶颈,从而提高系统性能。
从上为个人经验和一些资料的总结,有什么不对的地方,或者没有谈到的地方,欢迎各位同行指正。