php多用户读写文件冲突的解决办法


Posted in PHP onNovember 06, 2013

一般的方案会是:

$fp = fopen("/tmp/lock.txt", "w+");
if (flock($fp, LOCK_EX)) {
    fwrite($fp, "Write something heren");
    flock($fp, LOCK_UN);
} else {
    echo "Couldn't lock the file !";
}
fclose($fp);

但在PHP中,flock似乎工作的不是那么好!在多并发情况下,似乎是经常独占资源,不即时释放,或者是根本不释放,造成死锁,从而使服务器的cpu占用很高,甚至有时候会让服务器彻底死掉。好像在很多linux/unix系统中,都会有这样的情况发生。
所以使用flock之前,一定要慎重考虑。
那么就没有解决方案了吗?其实也不是这样的。如果flock()我们使用得当,完全可能解决死锁的问题。当然如果不考虑使用flock()函数,也同样会有很好的解决方案来解决我们的问题。
经过我个人的搜集和总结,大致归纳了解决方案有如下几种。
方案一:对文件进行加锁时,设置一个超时时间.
大致实现如下:
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) < 1000));
 if ($canWrite) {
   fwrite($fp, $dataToSave);
 }
 fclose($fp);
}
 

超时设置为1ms,如果这里时间内没有获得锁,就反复获得,直接获得到对文件操作权为止,当然。如果超时限制已到,就必需马上退出,让出锁让其它进程来进行操作。
方案二:不使用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.rename();重命名一个文件或一个目录,该函数其实更像linux里的mv。更新文件或者目录的路径或名字很方便。
但当我在window测试上面代码时,如果新文件名已经存在,会给出一个notice,说当前文件已经存在。但在linux下工作的很好。
2.clearstatcache();清除文件的状态.php将缓存所有文件属性信息,以提供更高的性能,但有时,多进程在对文件进行删除或者更新操作时,php没来得及更新缓存里的文件属性,容易导致访问到最后更新时间不是真实的数据。所以这里需要使用该函数对已保存的缓存进行清除。
方案三:对操作的文件进行随机读写,以降低并发的可能性。
在对用户访问日志进行记录时,这种方案似乎被采用的比较多。
先前需要定义一个随机空间,空间越大,并发的的可能性就越小,这里假设随机读写空间为[1-500],那么我们的日志文件的分布就为log1~到log500不等。每一次用户访问,都将数据随机写到log1~log500之间的任一文件。
在同一时刻,有2个进程进行记录日志,A进程可能是更新的log32文件,而B进程呢?则此时更新的可能就为log399.要知道,如果要让B进程也操作log32,概率基本上为1/500,差不多约等于零。
在需要对访问日志进行分析时,这里我们只需要先将这些日志合并,再进行分析即可。
使用这种方案来记录日志的一个好处时,进程操作排队的可能性比较小,可以使进程很迅速的完成每一次操作。
方案四:将所有要操作的进程放入一个队列中。然后专门放一个服务完成文件操作。
队列中的每一个排除的进程相当于第一个具体的操作,所以第一次我们的服务只需要从队列中取得相当于具体操作事项就可以了,如果这里还有大量的文件操作进程,没关系,排到我们的队列后面即可,只要愿意排,队列的多长都没关系。
对于以前几种方案,各有各的好处!大致可能归纳为两类:
1、需要排队(影响慢)比如方案一、二、四
2、不需要排队。(影响快)方案三
在设计缓存系统时,一般我们不会采用方案三。因为方案三的分析程序和写入程序是不同步的,在写的时间,完全不考虑到时候分析的难度,只管写的行了。试想一下,如我们在更新一个缓存时,如果也采用随机文件读写法,那么在读缓存时似乎会增加很多流程。但采取方案一、二就完全不一样,虽然写的时间需要等待(当获取锁不成功时,会反复获取),但读文件是很方便的。添加缓存的目的就是要减少数据读取瓶颈,从而提高系统性能。

PHP 相关文章推荐
php下实现伪 url 的超简单方法[转]
Sep 24 PHP
PHP防注入安全代码
Apr 09 PHP
Php 构造函数construct的前下划线是双的_
Dec 08 PHP
批量获取memcache值并按key的顺序返回的实现代码
Jun 14 PHP
php利用腾讯ip分享计划获取地理位置示例分享
Jan 20 PHP
PHP语法自动检查的Vim插件
Aug 11 PHP
10个实用的PHP正则表达式汇总
Oct 23 PHP
php通过array_unshift函数添加多个变量到数组前端的方法
Mar 18 PHP
PHP封装的Twitter访问类实例
Jul 18 PHP
php函数传值的引用传递注意事项分析
Jun 25 PHP
PHP实现活动人选抽奖功能
Apr 19 PHP
浅谈laravel orm 中的一对多关系 hasMany
Oct 21 PHP
php生成图形(Libchart)实例
Nov 06 #PHP
php ZipArchive压缩函数详解实例
Nov 06 #PHP
php根据分类合并数组的方法实例详解
Nov 06 #PHP
php foreach循环中使用引用的问题
Nov 06 #PHP
php用正则表达式匹配中文实例详解
Nov 06 #PHP
php引用传值实例详解学习
Nov 06 #PHP
php二维数组排序详解
Nov 06 #PHP
You might like
php mysql数据库操作类
2008/06/04 PHP
php 过滤器实现代码
2010/08/09 PHP
关于js与php互相传值的介绍
2013/06/25 PHP
php利用单例模式实现日志处理类库
2014/02/10 PHP
学习php设计模式 php实现桥梁模式(bridge)
2015/12/07 PHP
PHP使用PDO创建MySQL数据库、表及插入多条数据操作示例
2019/05/30 PHP
比Jquery的document.ready更快的方法
2010/04/28 Javascript
各情景下元素宽高的获取实现代码
2011/09/13 Javascript
jQuery对JSON数据进行排序输出的方法
2015/06/24 Javascript
JavaScript基于ajax编辑信息用法实例
2015/07/15 Javascript
javascript数据类型验证方法
2015/12/31 Javascript
javascript获取网页各种高宽及位置的方法总结
2016/07/27 Javascript
基于jQuery的AJAX和JSON实现纯html数据模板
2016/08/09 Javascript
详解vue-router 2.0 常用基础知识点之router-link
2017/05/10 Javascript
JS 设置Cookie 有效期 检测cookie
2017/06/15 Javascript
详解vue中引入stylus及报错解决方法
2017/09/22 Javascript
详解webpack性能优化——DLL
2017/10/20 Javascript
Vuex 单状态库与多模块状态库详解
2018/12/11 Javascript
JS forEach跳出循环2种实现方法
2020/06/24 Javascript
JSON stringify方法原理及实例解析
2020/10/23 Javascript
Python pymongo模块用法示例
2018/03/31 Python
Python 脚本的三种执行方式小结
2019/12/21 Python
如何更改 pandas dataframe 中两列的位置
2019/12/27 Python
python使用pandas抽样训练数据中某个类别实例
2020/02/28 Python
将keras的h5模型转换为tensorflow的pb模型操作
2020/05/25 Python
如何写python的配置文件
2020/06/07 Python
python opencv图像处理(素描、怀旧、光照、流年、滤镜 原理及实现)
2020/12/10 Python
印尼极简主义和实惠的在线家具店:Fabelio
2019/03/27 全球购物
会计专业自我评价
2014/02/12 职场文书
政府门卫岗位职责
2014/04/29 职场文书
2014年冬季防火方案
2014/05/21 职场文书
民主评议党员自我评价材料
2014/09/18 职场文书
好好学习保证书
2015/02/26 职场文书
幼儿园教师心得体会范文
2016/01/21 职场文书
2020年基层司法所建设情况调研报告
2019/11/30 职场文书
Redis Cluster 字段模糊匹配及删除
2021/05/27 Redis