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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: php高并发状态下文件的读写   背景 1、对于PV不高或者说并发数不是很大的应用,不用考虑这些,一般的文件操作方法完全没有问题 2、如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失 例如:一个在线聊天室(这里假定把聊天内容写入文件),在同一时刻,用户A和用户B都要操作数据保存文件,首先是A打开了文件,然后更新里面的数据,但这 里B也正好也打开了同一个文件,也准备更新里面的数据。

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
4月前
|
PHP
php常见问题,php.ini文件不存在或者找不到,mb_strlen()函数未定义系列问题,dll模块找不到的解决
本文介绍了解决PHP常见问题的步骤,包括定位和创建`php.ini`文件,以及解决`mb_strlen()`函数未定义和DLL模块加载错误的具体方法。
php常见问题,php.ini文件不存在或者找不到,mb_strlen()函数未定义系列问题,dll模块找不到的解决
|
7月前
|
存储 运维 Serverless
函数计算产品使用问题之在YAML文件中配置了环境变量,但在PHP代码中无法读取到这些环境变量,是什么原因
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
3月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
94 4
|
4月前
|
前端开发 PHP
php学习笔记-php文件表单上传-day06
本文介绍了PHP文件上传处理流程、预定义变量`$_FILES`的使用、文件上传状态代码以及文件上传实现函数。同时,通过一个文件上传的小例子,演示了文件上传表单的创建、文件上传表单处理的PHP页面编写以及运行测试输出。
php学习笔记-php文件表单上传-day06
|
4月前
|
缓存 监控 算法
分析慢日志文件来优化 PHP 脚本的性能
分析慢日志文件来优化 PHP 脚本的性能
|
4月前
进入靶场,出现一张照片,右击查看源代码,发现有一个注释的source.php文件
这段代码实现了一个网站上弹出的促销海报动画效果,包含一个关闭按钮。当促销海报弹出时,会在三秒后开始抖动一两下。海报使用固定定位居中显示,带有阴影和圆角,关闭按钮位于右上角。可以通过修改时间参数调整弹出时间。
32 0
|
5月前
|
存储 安全 数据库连接
php.ini 文件的用途是什么?
【8月更文挑战第29天】
104 1
|
5月前
|
PHP
PHP遍历文件并同步上传到服务器
在进行网站迁移时,由于原网站的图片文件过多,采用打包下载再上传的方式耗时过长,且尝试使用FTP工具从旧服务器传输至新服务器时失败。为解决此问题,特使用PHP编写了一款工具,该工具能扫描指定目录下的所有`.webp`图像文件,并将其上传至新的服务器,极大地提高了迁移效率。
119 16
|
5月前
|
Java 应用服务中间件 PHP
PHP——调用java文件中的方法
PHP——调用java文件中的方法
67 0
PHP——调用java文件中的方法
|
6月前
|
API PHP UED
​一个PHP文件实现联系表单自动发送邮件
使用PHP和AOKSend服务,可以创建一个联系表单,收集用户信息并自动发送邮件。HTML表单包含姓名、邮箱和消息字段。PHP文件`send_mail.php`处理表单提交,通过AOKSend的SMTP设置(如主机、端口、API密钥)使用PHPMailer发送邮件到指定地址。代码中还包括安全措施,如使用`htmlspecialchars`防止XSS攻击。这种方法增强了网站的用户沟通体验,并依赖AOKSend的稳定性和API进行高效邮件发送。