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初学者头痛的十四个问题
Jul 12 PHP
PHP中Session的概念
Oct 09 PHP
php自动跳转中英文页面
Jul 29 PHP
php下将图片以二进制存入mysql数据库中并显示的实现代码
May 27 PHP
php Rename 更改文件、文件夹名称
May 24 PHP
PHP 杂谈《重构-改善既有代码的设计》之四 简化条件表达式
Apr 09 PHP
php实现在服务器上创建目录的方法
Mar 16 PHP
PHP+MySql+jQuery实现的&quot;顶&quot;和&quot;踩&quot;投票功能
May 21 PHP
thinkphp验证码的实现(form、ajax实现验证)
Jul 28 PHP
详谈php静态方法及普通方法的区别
Oct 04 PHP
动态表单验证的操作方法和TP框架里面的ajax表单验证
Jul 19 PHP
Yii框架小部件(Widgets)用法实例详解
May 15 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程序之die调试法 快速解决错误
2009/09/17 PHP
PHP面向对象之旅:深入理解static变量与方法
2014/01/06 PHP
带密匙的php加密解密示例分享
2014/01/29 PHP
用 Composer构建自己的 PHP 框架之使用 ORM
2014/10/30 PHP
prototype 1.5相关知识及他人笔记
2006/12/16 Javascript
Autocomplete Textbox Example javascript实现自动完成成功
2007/08/17 Javascript
一些实用的jQuery代码片段收集
2011/07/12 Javascript
node.js操作mongodb学习小结
2015/04/25 Javascript
详解JavaScript的Polymer框架中的通知交互
2015/07/29 Javascript
javascript中利用柯里化函数实现bind方法
2016/04/29 Javascript
Javascript实现通过选择周数显示开始日和结束日的实现代码
2016/05/30 Javascript
JS添加或修改控件的样式(Class)实现方法
2016/10/15 Javascript
bootstrap日期插件daterangepicker使用详解
2017/10/19 Javascript
vue.js将时间戳转化为日期格式的实现代码
2018/06/05 Javascript
详解Python验证码识别
2016/01/25 Python
windows下ipython的安装与使用详解
2016/10/20 Python
浅析使用Python操作文件
2017/07/31 Python
浅析Python中return和finally共同挖的坑
2017/08/18 Python
利用python如何处理百万条数据(适用java新手)
2018/06/06 Python
Django中日期处理注意事项与自定义时间格式转换详解
2018/08/06 Python
numpy向空的二维数组中添加元素的方法
2018/11/01 Python
Python基于locals返回作用域字典
2020/10/17 Python
如何在scrapy中集成selenium爬取网页的方法
2020/11/18 Python
python爬虫基础之urllib的使用
2020/12/31 Python
pycharm 关闭search everywhere的解决操作
2021/01/15 Python
【HTML5】Canvas绘制简单图片教程
2016/05/13 HTML / CSS
巴西家用小家电购物网站:Polishop
2016/08/07 全球购物
介绍java中初始化块的使用
2012/09/11 面试题
EJB3.1都有哪些改进
2012/11/17 面试题
服装公司总经理岗位职责
2013/11/30 职场文书
事业单位辞职信范文
2014/01/19 职场文书
《最后的姿势》教学反思
2014/02/27 职场文书
办理房产过户的委托书
2014/09/14 职场文书
2014年机关后勤工作总结
2014/12/16 职场文书
少先队辅导员事迹材料
2014/12/24 职场文书
新郎父亲婚礼致辞
2015/07/27 职场文书