php高并发状态下文件的读写

 

背景

1、对于PV不高或者说并发数不是很大的应用,不用考虑这些,一般的文件操作方法完全没有问题

2、如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

例如:一个在线聊天室(这里假定把聊天内容写入文件),在同一时刻,用户A和用户B都要操作数据保存文件,首先是A打开了文件,然后更新里面的数据,但这 里B也正好也打开了同一个文件,也准备更新里面的数据。当A把写好的文件保存时,这里其实B已经打开了文件。但当B再把文件保存回去时,这里已经造成了数 据的丢失,因为这里B用户完全不知道它所打开的文件在它对其进行更改时,A用户也更改了这个文件,所以最后B用户保存更改时,用户A的更新就被会丢失。

对于这样的问题,一般的解决方案

1、当一进程对文件进行操作时,首先对其它进行加锁

2、这里只有该进程有权对文件进行读取,其它进程如果现在读,是完全没有问题,但如果这时有进程试图想对其进行更新,会遭到操作拒绝,

3、先前对文件进行加锁的进程这时如果对文件的更新操作完毕,这就释放独占的标识,这时文件又恢复到了可更改的状态

4、接下来同理,如果那个进程在操作文件时,文件没有加锁,这时,它就可以放心大胆的对文件进行锁定,独自享用

所以一般的方案会是

1
2
3
4
5
6
7
8
$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);

但在PHP中,flock似乎工作的不是那么好!在多并发情况下,似乎是经常独占资源,不即时释放,或者是根本不释放,造成死锁,从而使服务器的cpu占用很高,甚至有时候会让服务器彻底死掉。好像在很多linux/unix系统中,都会有这样的情况发生。

所以使用flock之前,一定要慎重考虑。

那么就没有解决方案了吗?其实也不是这样的。如果flock()我们使用得当,完全可能解决死锁的问题。当然如果不考虑使用flock()函数,也同样会有很好的解决方案来解决我们的问题。

方案一:对文件进行加锁时,设置一个超时时间.

大致实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
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、将需要更新的文件copy一份到我们的临时文件目录,将文件最后修改时间保存到一个变量,并为这个临时文件取一个随机的,不容易重复的文件名

2、当对这个临时文件进行更新后,再检测原文件的最后更新时间和先前所保存的时间是否一致

3、如果最后一次修改时间一致,就将所修改的临时文件重命名到原文件,为了确保文件状态同步更新,所以需要清除一下文件状态

4、但是,如果最后一次修改时间和先前所保存的一致,这说明在这期间,原文件已经被修改过,这时,需要把临时文件删除,然后返回false,说明文件这时有其它进程在进行操作

大致实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$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、不需要排队。(影响快)方案三

在设计缓存系统时,一般我们不会采用方案三。因为方案三的分析程序和写入程序是不同步的,在写的时间,完全不考虑到时候分析的难度,只管写的行了。试想一 下,如我们在更新一个缓存时,如果也采用随机文件读写法,那么在读缓存时似乎会增加很多流程。但采取方案一、二就完全不一样,虽然写的时间需要等待(当获 取锁不成功时,会反复获取),

但读文件是很方便的。添加缓存的目的就是要减少数据读取瓶颈,从而提高系统性能。

原文地址:http://hqlong.com/2009/01/530.html

php高并发状态下文件的读写的更多相关文章

  1. 读/写锁的实现和应用(高并发状态下的map实现)

    程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁.在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源.但是如果有一个线程想去写这些共享资 ...

  2. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  3. 高并发压力下导致数据库bug

    环境信息:  linux 6.1 + oracle11.2.0.3 RAC     问题现象: 学校晚上6点选课,人数大概有3000,7点时,数据库报错如下(数据库到6点多还是可以连接的),数据库hu ...

  4. [转]高并发访问下避免对象缓存失效引发Dogpile效应

    避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并 ...

  5. Java高并发情况下的锁机制优化

    本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 1 public synchronized void syncMethod(){ 2 othercode ...

  6. 高并发情况下分布式全局ID

    1.高并发情况下,生成分布式全局id策略2.利用全球唯一UUID生成订单号优缺点3.基于数据库自增或者序列生成订单号4.数据库集群如何考虑数据库自增唯一性5.基于Redis生成生成全局id策略6.Tw ...

  7. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...

  8. 【高并发】高并发环境下如何优化Tomcat配置?看完我懂了!

    写在前面 Tomcat作为最常用的Java Web服务器,随着并发量越来越高,Tomcat的性能会急剧下降,那有没有什么方法来优化Tomcat在高并发环境下的性能呢? Tomcat运行模式 Tomca ...

  9. C++高并发场景下读多写少的解决方案

    C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...

随机推荐

  1. http://blog.csdn.net/iamshaofa/article/details/7877785/

    http://blog.csdn.net/iamshaofa/article/details/7877785/

  2. ASP.NET MVC5 + EF6 入门教程 (5) Model和Entity Framework

    一.创建Model MVC中的Model是用来给View提供显示数据的对象. 这里我们首先创建一个Model对象. 在解决方案资源管理器中右键点击Models文件夹,选择添加->类.添加一个名为 ...

  3. Storm DRPC实现机制分析

    DRPC是建立在Storm基本概念(Topology.Spout.Bolt.Stream等)之上的高层抽象,个人理解它的目标是在Storm 集群之上提供一种分布式的RPC框架,以便能够利用Storm快 ...

  4. Arima拟合函数,se出现NaN问题

    R语言Arima函数拟合模型,se(标准误)出现NaN的问题. 参考了网上的资料,虽然不太明白是什么意思,但是这样的模型不能用. 参考:http://stackoverflow.com/questio ...

  5. Android Service与Activity之间通信

    主要分为: 通过Binder对象 通过broadcast(广播)的形式 Activity调用bindService (Intent service, ServiceConnection conn, i ...

  6. ACM 独木舟上的旅行

    独木舟上的旅行 时间限制:3000 ms  |  内存限制:65535 KB 难度:2   描述 进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别.一条独木舟最多只能乘坐两个人,且乘客 ...

  7. BZOJ4552: [Tjoi2016&Heoi2016]排序

    Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这 ...

  8. App所需申请资料

    准备资料 企业五证 营业执照 税务登记证 组织机构代码证 银行开户许可证 法人身份证 新邮箱 申请一个新的邮箱地址,供申请以下材料使用 苹果证书申请 AppleID 申请邓氏编码需要有AppleID ...

  9. 简单打包 ipa 方式!

    应用的发布也分两种 一种是.打包成ipa上传到国内第3方软件市场,当用户的手机已经JailBreak时,双击下载的ipa文件就可以安装软件 (ipa同android的apk包一样,实质是一个压缩包) ...

  10. MongoDB数据库的简介及安装

    一.MongoDB数据库简介 简介 MongoDB是一个高性能,开源,无模式的,基于分布式文件存储的文档型数据库,由C++语言编写,其名称来源取自“humongous”,是一种开源的文档数据库──No ...