顺风车运维研发团队黄涛现象在线脚本机写单条日志时间过长,会出现交叉打印现象:交叉的日志非常有规律,单条日志过长被截断,建议优化/*/ruleanalysis.php:68这里日志写入的字符串长度为:int(25909)原因分析脚本服务写入日志代码为如下:if($this->isCli==true){returnfile_put_contents($messageLogFile,$strLogMsg,FILE_APPEND);//调用file_put_contents方法写入时,为什么写的超长字符串是交叉写入的?//跟进file_put_contents函数的实现?}查看file_put_contents的源码实现,最终文件会执行到_php_stream_write_buffer函数,有这么一段代码:明确几个变量的含义:count:要写入文件的字符串长度stream->chunk_size:默认为8192(8k)从上面代码可以看出,当写入的字符串长度大于8192时,会拆分成多个<=8192的字符串,然后调用php_stdiop_write函数写入文件.php_stdiop_write函数实现如下:断言(数据!=NULL);if(data->fd>=0){#ifdefPHP_WIN32intbytes_written;如果(ZEND_SIZE_T_UINT_OVFL(count)){count=UINT_MAX;}bytes_written=_write(数据->fd,buf,(unsignedint)count);#elseintbytes_written=write(data->fd,buf,count);#endif如果(bytes_written<0)返回0;返回(size_t)bytes_written;}else{#ifHAVE_FLUSHIOif(!data->is_pipe&&data->last_op=='r'){zend_fseek(data->file,0,SEEK_CUR);}data->last_op='w';#endifreturnfwrite(buf,1,count,data->file);}}php_stdiop_write调用write函数写入文件;写函数可以保证完整的写因此,可以分析出日志写字符串的原因。调用链接为:file_put_contents->_php_stream_write_buffer->php_stdiop_write(多次调用,每次最多写入8192字节)->write(),这是多次调用php_stdiop_write函数时出现的问题;第一次写入后,在高并发的情况下被其他进程的写入函数追赶。这时候出现了一个write字符串,就是前面例子中的log;为了证明这一点,我针对错误码/**/ruleanalysis.php:68写了如下伪代码:publicfunctionrun(){$this->mysqlConnect();$sql="select*from***where***=1";$pidRet=$this->db->run($sql);Utils\Logger::notice('tiger_project_info:'.json_encode($pidRet));die;}vim~/*****/logger.phpif($this->isCli==true){var_dump(substr($strLogMsg,16084,400));//把附近的字符串放在8192的倍数的位置16384=8192*2returnfile_put_contents($messageLogFile,$strLogMsg,FILE_APPEND);}执行代码,看看打印日志字符串的地方是否是8192的倍数,结果如下:截断位置非常接近8192的倍数;但是因为定位的时间不是当时的时间点,Partial变化的时候数据库是存在的,所以有一个偏移量,那么我们也可以验证我们之前的猜测,就是file_put_contents调用write时发生的交叉打印多次发挥作用。问题解决:1.修改日志文件的代码。把这么大的日志写到文件里合理吗?2.需要写一个巨大的日志又不想被交叉打印,加上LOCK_EX标志;file_put_contents函数相关知识点Q&A1.file_put_contents(filename,msg,FILE_APPEND)追加到最后?FILE_APPEND文件添加的形式,这个是怎么实现的?先标记mode[0]='a'然后转换成:O_CREAT|O_APPEND调用open函数,fd=open(realpath,open_flags,0666);通过C底层函数保证,写入默认追加写入;2.LOCK_EX实现中的file_put_contents(filename,msg,FILE_APPEND|LOCK_EX)?影响?file_put_contents在调用_php_stream_write_buffer前加锁php_stream_supports_lock(stream)->flock()获取文件锁并调用_php_stream_write_buffer->multiplewrite();写入后释放文件锁php_stream_close(stream)->close(data->fd);//直接关闭总结:LOCK_EX保证一个巨大字符串的完整性,不会被写入;3、多进程,file_put_contents()数据是否被覆盖?写入调用路径:file_put_contents->_php_stream_write_buffer->php_stdiop_write(多次调用,每次最多写入8192字节)->write()file_put_contents($messageLogFile,$strLogMsg,FILE_APPEND);O_APPEND模式下的write函数,偏移到文件末尾并写入文件是原子的,不存在被覆盖的情况;4.以O_APPEND方式打开文件,然后使用lseek定位到文件头,然后调用write?它写在文件的末尾吗?或者写在文件末尾,参考文章:https://blog.csdn.net/dog250/...write函数生成代码如下:+staticinlineloff_tfile_pos_read_lock(structfile*file){+if(file->f_mode&FMODE_LSEEK)+mutex_lock(&file->f_pos_lock);returnfile->f_pos;}+staticinlinevoidfile_pos_write_unlock(structfile*file,loff_tpos){file->f_pos=pos;+if(file->f_mode&FMODE_LSEEK)+mutex_unlock(&file->f_pos_lock);}修改sys_write系统调整:file=fget_light(fd,&fput_needed);if(file){-loff_tpos=file_pos_read(file);+loff_tpos=file_pos_read_lock(文件);ret=vfs_write(文件,buf,计数,&pos);-file_pos_write(文件,位置);+file_pos_write_unlock(文件,位置);fput_light(file,fput_needed);}5、进程内多次file_put_contents,open和close只有一次还是多次?open调整路径:file_put_contents->php_stream_open_wrapper_ex->php_plain_files_stream_opener->php_stream_fopen_rel->fd=open(realpath,open06_6);close调整路径:file_put_contents->php_stream_close->php_stdiop_close->ret=close(data->fd);open和close每次都会执行
