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 getsiteurl()函数
Sep 05 PHP
简单的PHP留言本实例代码
May 09 PHP
php数组函数序列之end() - 移动数组内部指针到最后一个元素,并返回该元素的值
Oct 31 PHP
Drupal7连接多个数据库及常见问题解决
Mar 02 PHP
php smarty模板引擎的6个小技巧
Apr 24 PHP
php网站被挂木马后的修复方法总结
Nov 06 PHP
php把大写命名转换成下划线分割命名
Apr 27 PHP
php生成动态验证码gif图片
Oct 19 PHP
PHP下SSL加密解密、验证、签名方法(很简单)
Jun 28 PHP
PHP定时任务获取微信access_token的方法
Oct 10 PHP
yii2的restful api路由实例详解
May 14 PHP
PHP使用ajax的post方式下载excel文件简单示例
Aug 06 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
《魔兽争霸3》重制版究竟重制了什么?玩家:这么糊弄真的好吗?
2020/05/04 魔兽争霸
php mysql数据库操作分页类
2008/06/04 PHP
PHP学习之数组的定义和填充
2011/04/17 PHP
smarty简单分页的实现方法
2014/10/27 PHP
PHP实现的带超时功能get_headers函数
2015/02/10 PHP
PHP+MYSQL实现用户的增删改查
2015/03/24 PHP
PHP实现的数据对象映射模式详解
2019/03/20 PHP
JQuery触发事件例如click
2013/09/11 Javascript
angularjs客户端实现压缩图片文件并上传实例
2015/07/06 Javascript
js从外部获取图片的实现方法
2016/08/05 Javascript
基于JavaScript实现图片剪切效果
2017/03/07 Javascript
js实现自动图片轮播代码
2017/03/22 Javascript
JavaScript学习笔记之数组基本操作示例
2019/01/09 Javascript
Vue.js中的extend绑定节点并显示的方法
2019/06/20 Javascript
vue实现侧边栏导航效果
2019/10/21 Javascript
JavaScript实现简单的弹窗效果
2020/05/19 Javascript
js实现时间日期校验
2020/05/26 Javascript
[01:01:51]EG vs VG Supermajor小组赛B组 BO3 第二场 6.2
2018/06/03 DOTA
Python遍历目录并批量更换文件名和目录名的方法
2016/09/19 Python
浅谈编码,解码,乱码的问题
2016/12/30 Python
Python3调用微信企业号API发送文本消息代码示例
2017/11/10 Python
Python设计模式之工厂模式简单示例
2018/01/09 Python
对Python中实现两个数的值交换的集中方法详解
2019/01/11 Python
如何基于python实现不邻接植花
2020/05/01 Python
使用Python Tkinter实现剪刀石头布小游戏功能
2020/10/23 Python
CSS3教程(6):创建网站多列
2009/04/02 HTML / CSS
HTML5 Canvas图像模糊完美解决办法
2018/02/06 HTML / CSS
KIKO MILANO荷兰网上商店:意大利专业化妆品品牌
2017/05/12 全球购物
IGK Hair官网:喷雾、洗发水、护发素等
2020/11/03 全球购物
介绍一下javax.servlet.Servlet接口及其主要方法
2015/11/30 面试题
中药专业大学生医药工作求职信
2013/10/25 职场文书
洗车工岗位职责
2014/03/15 职场文书
乡镇党员群众路线教育实践活动对照检查材料思想汇报
2014/10/05 职场文书
百年校庆宣传标语口号
2015/12/26 职场文书
高一英语教学反思
2016/03/03 职场文书
使用python+pygame开发消消乐游戏附完整源码
2021/06/10 Python