当在 PHP 7.4 中使用 file_put_contents() 时,读取文件内容为空的排查分析
1、查看 Illuminate\Filesystem\Filesystem 中的 put() 方法实现
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 重新保存,您最好确保正确执行此操作,否则您的文件将随机擦除自身。
<?php $myfile=fopen('test.txt','rt'); flock($myfile,LOCK_SH); $read=file_get_contents('test.txt'); fclose($myfile); ?>
3、决定在测试环境中模拟出使用 file_put_contents() 同时写入同一路径文件的情况,然后观察是否会出现读取文件内容为空的情况。
require.php
<?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
<?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() 时,文件内容为空的排查分析。 […]