php文件句柄,file_put_contents相关问题以及日志相关

fopen()

fopen(filename,mode,include_path,context)会返回一个文件句柄
文件句柄其实就是一个指针,指针就是指向文件中的某个位置mode参数决定了指针的位置。

fwrite()

fwrite(file,string,length)将字符串写入到文件句柄的指针处。

  • 参数file:必需。fopen()返回的文件句柄
  • 参数string:必需。写入文件的字符串
  • 参数length:可选。规定写入的最大字节数

注意:小于8k的字符串写入文件时不是一个字符一个字符的写入,而是所有字符串一次性全部写入文件。为什么小于8k的会这样?为什么是一次性写入?这些问题下面介绍。

fclose()

fclose(file)关闭文件句柄

  • file:fopen()返回的文件句柄

正常的写入文件代码如下

<?php
//打开文件句柄,并将资源绑定到一个流上
$status=fopen('./1.txt','a+');
fwrite($status,'88'); 
fclose($status);

flock()

flock(file,lock,block)为锁定资源或者释放资源。也称作文件锁

  • file:必需。fopen()返回的文件句柄
  • lock:必需。规定要使用哪种锁定类型
  • block:可选。若设置为 1 或 true,则当进行锁定时阻挡其他进程

lock参数选项如下

  • LOCK_SH:共享锁定,类似mysql的共享锁,可读不可写,是阻塞的
  • LOCK_EX:独占锁定,类似mysql的排他锁,不可读也不可写,是阻塞的,一般使用该类型
  • LOCK_UN:释放锁
  • LOCK_NB:如果不希望flock()在锁定时阻塞,则加上该选项

使用加锁后的普通代码如下

<?php
//打开文件句柄,并将资源绑定到一个流上
$status=fopen('./1.txt','a+');
$lock=flock($status,LOCK_EX);
if($lock){
    fwrite($status,'88');
    flock($status,LOCK_UN);
}
fclose($status);

file_put_contents()

file_put_contents()函数是把一个字符串写入到文件中。
与依次调用fopen(),fwrite(),fclose()功能一样。

格式为:file_put_contents(file,data,mode,context)

  • file:文件句柄。规定要写入的文件,文件如果不存在则会创建
  • data: 写入文件的内容,可以是字符串,数组(不能是多维数组)或者数据流
  • mode:规定如何打开/写入文件,值有下面几个选项
    • FILE_APPEND:将文件指针指向文件末尾,用于追加内容。与fopen()中的a模式相同。
    • LOCK_EX:写文件时加锁。与flock()中的LOCK_EX相同。
    • FILE_USE_INCLUDE_PATH:很少用。

追加内容

file_put_contents('./1.txt','ff',FILE_APPEND);

追加内容并加锁

file_put_contents('./1.txt','ff2',FILE_APPEND | LOCK_EX);

要知道:file_put_contents()和fwrite()一样都是:小于8k的字符串在写入文件时不是一个字符一个字符的写入,而是所有字符串于一次性全部写入文件
为什么是一次性写入?
因为每次写入文件都是一次io,io是阻塞耗时的,所以从性能各个方面肯定不会将每次字符都写入一次的,所以是一次性写入的,减少了io次数,相应的也就提高了性能。
为什么小于8k的会这样?
如果写入的内容很大很大,且一次性写入时消耗的性能也是很大的,所以可以分批次写入<=8k的内容,这样消耗可能更低。

使用file_put_contents()写入日志发生日志错乱

现象

高并发情况下,且日志很长大于8k的情况下,日志会发生错乱的现象。
我们可以注意到俩个关键字,高并发日志很长。写入内容大于8k的情况下,内容会分批次写入,也就保证不了原子性了,如果现在有并发情况,就可能在分批次写入这里发生日志错乱的情况

写入内容小于8k的情况下会一次性写入,这也就说明了保证了原子性,所以在高并发的情况下不会发生错乱的情况。

查看file_put_contents()源码

  • 脚本服务写入日志代码如下:
if ($this->isCli == true) {
    return file_put_contents($messageLogFile, $strLogMsg, FILE_APPEND);
}
  • 查看file_put_contents 的源码实现,最终写文件会执行到_php_stream_write_buffer 函数,里面有这样一处代码:

明确几个变量的含义:
count:需写入文件的字符串长度
stream->chunk_size :默认为8192 (8k)

从上面代码可以看出,当写入的字符串长度 大于8192时,则拆为多次<=8192的字符串,分批次写入,打个比方,如果写入的内容是23k,就分三次写入,第一次写入8k,第二次写入8k,第三次7k。

然后调用php_stdiop_write函数写入文件。什么意思呢?

  • php_stdiop_write函数实现如下:
static size_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
{
    php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;

    assert(data != NULL);

    if (data->fd >= 0) {
#ifdef PHP_WIN32
        int bytes_written;
        if (ZEND_SIZE_T_UINT_OVFL(count)) {
            count = UINT_MAX;
        }
        bytes_written = _write(data->fd, buf, (unsigned int)count);
#else
        int bytes_written = write(data->fd, buf, count);
#endif
        if (bytes_written < 0) return 0;
        return (size_t) bytes_written;
    } else {

#if HAVE_FLUSHIO
        if (!data->is_pipe && data->last_op == 'r') {
            zend_fseek(data->file, 0, SEEK_CUR);
        }
        data->last_op = 'w';
#endif

        return fwrite(buf, 1, count, data->file);
    }
}

php_stdiop_write 则调用的 write函数 写入文件;write函数是能保证一次写入的完整的

所以日志写串的原因也就能分析出来了,调用链接为:file_put_contents ->_php_stream_write_buffer ->php_stdiop_write(多次调用,每次最多写入8192字节) ->write(),是在 多次调用php_stdiop_write 函数时出的问题;第一次写完,紧接着在高并发的情况下,被其他进程的 write 函数追着写,此时就出现写串,也就是前面示例中日志;

总结:写入内容小于8k时是原子性操作,不用加锁,反之需要。这个8k的限制可以在php中修改的。

加锁代码

由于加锁是阻塞的,在并发时会影响性能,所以写入内容时最好判断下大小是否超过8k,代码如下

<?php

$str='xxx';
$strlen=strlen($str);
if($strlen > 8192){
    file_put_contents('./1.txt',$str,FILE_APPEND | LOCK_EX);
}else{
    file_put_contents('./1.txt',$str,FILE_APPEND);
}

file_put_contents()中的LOCK_EX和flock()的效果是一样的。

加锁后会有死锁的问题吗?

这个锁并没有设定过期时间,那么会不会有死锁的情况呢?比如在执行完加锁还没有到解锁的时候机器宕机,该文件会不会被锁死?
答案是:进程重启或者kill掉该进程后,系统会自动释放这个文件锁。在没重启或者没kill掉进程之前,该文件会被死锁

在多进程模式下,使用file_put_contents()会影响并发吗?

分俩种情况

  • 不加锁的情况
    首先file_put_contents()就是一个阻塞io,所以肯定会阻塞进程的,这点毋庸置疑。
    比如php-fpm一共有10个进程,执行file_put_contents()时会阻塞1s,那么此时最高的qps也就是10/s。只有进程空闲后才会继续处理别的请求。
  • 加锁的情况
$fp = fopen("/home/guoxinhua/php.log", "a+");
if (flock($fp, LOCK_EX)) {  //给日志文件加锁
   //do something
    fwrite($fp, "the huge string\n");
    flock($fp, LOCK_UN);    // 释放锁定
}

或者

file_put_contents("/home/guoxinhua/php.log",'111',FILE_APPEND | LOCK_EX);

比如php-fpm有10个进程,在写入数据时会阻塞1s,而且该文件还被加锁。
第一个请求在写入阻塞了1s,且该文件已加锁。第二个并发请求写入时需要等待第一个请求锁释放才能写入,一次类推,此时qps也就是1/s。

如果前一个请求没有释放文件锁就会导致后面的请求无法获得锁,卡死在获取锁的这一步。如果php-fpm一共10个进程,此时系统最多能处理10个请求,且这10个请求都是阻塞状态。说白了都在阻塞造成的问题,

所以在必须加锁的情况下,我们必须加上LOCK_NB,它可以避免阻塞,也就是说此时的qps也是10/s。

<?php
$fp = fopen('/tmp/lock.txt', 'r+');
if(!flock($fp, LOCK_EX | LOCK_NB)) {
    echo 'Unable to obtain lock';
    exit(-1);
}
fclose($fp);
?>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容