当在 PHP 7.4 中使用 file_put_contents() 时,读取文件内容为空的排查分析
1、查看 Illuminate\Filesystem\Filesystem 中的 put() 方法实现
1 2 3 4 | public function put( $path , $contents , $lock = false) { return file_put_contents ( $path , $contents , $lock ? LOCK_EX : 0); } |
2、参考:即在并发场景中,如果您不将其包装如下,则 file_get_contents 可能会返回空 https://www.php.net/manual/zh/function.file-put-contents.php#112831 。重要的是要理解 LOCK_EX 不会阻止读取文件,除非您还使用 PHP ‘flock’ 函数显式获取读锁(共享锁定)。即在并发场景中,如果您不将其包装如下,则 file_get_contents 可能会返回空。如果您有代码对文件执行 file_get_contents,更改字符串,然后使用 file_put_contents 重新保存,您最好确保正确执行此操作,否则您的文件将随机擦除自身。
1 2 3 4 5 6 | <?php $myfile = fopen ( 'test.txt' , 'rt' ); flock ( $myfile ,LOCK_SH); $read = file_get_contents ( 'test.txt' ); fclose( $myfile ); ?> |
3、决定在测试环境中模拟出使用 file_put_contents() 同时写入同一路径文件的情况,然后观察是否会出现读取文件内容为空的情况。
require.php
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 | <?php $i = rand(); $path = 'return.php' ; $contents = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ]; if ( file_exists ( $path )) { $read = file_get_contents ( $path ); if ( empty ( $read )) { file_put_contents ( 'empty-' . rand() . '.php' , $read , LOCK_EX); } } $code = '<?php' ; $code .= "\n\n" ; $contents = array_merge ( $contents , [ $i ]); $code .= 'return ' . var_export( $contents , true) . ';' ; file_put_contents ( $path , $code , LOCK_EX); ?> |
4、参考:使用阿里云 性能测试 PTS 模拟并发 http 请求 。 查看程序运行结果,确定生成了 2万多 个文件,分别为:return.php、以及大量以 empty- 开头的文件。如图1
5、由此得出结论,如果您有代码对文件内容使用 file_put_contents 进行保存,虽然已经添加 LOCK_EX,但是在高并发场景中,其在读取文件内容时,是有很大的概率读取到文件内容为空的情况的。
6、决定在读取文件内容前,先使用 PHP ‘flock’ 函数显式获取读锁(共享锁定)。最终代码实现如下
require.php
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 | <?php $i = rand(); $path = 'return.php' ; $contents = [ 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ]; if ( file_exists ( $path )) { $myfile = fopen ( $path , 'r' ); flock ( $myfile , LOCK_SH); $read = file_get_contents ( $path ); fclose( $myfile ); if ( empty ( $read )) { file_put_contents ( 'empty-' . rand() . '.php' , $read , LOCK_EX); } } $code = '<?php' ; $code .= "\n\n" ; $contents = array_merge ( $contents , [ $i ]); $code .= 'return ' . var_export( $contents , true) . ';' ; file_put_contents ( $path , $code , LOCK_EX); ?> |
7、参考:使用阿里云 性能测试 PTS 模拟并发 http 请求 。 重新并发测试后,再次查看程序运行结果。已经不存在 empty- 开头的文件,说明 使用 PHP ‘flock’ 函数显式获取读锁(共享锁定)有效。不过在程序运行期间,文件 return.php 的内容仍然是有为空的时候,只不过,此时,没有直接读取文件的内容罢了,而是等待中。如视频1
8、不过,查看测试报告,加了读锁之后,性能有所下降。加读锁前后的性能对比如下。加锁前的测试报告:平均RT(ms):11,TPS(平均/峰值):832/903。加锁后的测试报告:平均RT(ms):12,TPS(平均/峰值):825/872。名称解释:平均RT(ms)RT业务响应时间(Response Time),平均RT是所有API的RT平均值,单位为ms,愈小愈好。TPS(平均/峰值)TPS系统每秒处理事务数 Transaction Per Second),包括TPS的平均值和峰值:平均:表示压测周期内,该场景TPS的平均值。峰值:表示压测周期内,该场景的最高TPS,愈大愈好。如图2、图3
9、因此,是否加读锁,需要自行衡量了。如果不加读锁,建议在读取文件内容时,需要判断是否为空,如果为空,需要想办法额外处理。
1 条回复
[…] 12、具体排查分析流程可参考:当在 PHP 7.4 中使用 file_put_contents() 时,文件内容为空的排查分析。 […]