在 driver
为 local
时,Storage::append()
在高并发下,会存在丢失数据问题,文件被覆写,而非尾部添加,如果明确是本地文件操作,像日志写入,建议使用 Illuminate\Filesystem\Filesystem
或者php原生方法file_put_content()
,通过 storage_path()
app_path()
public_path()
等方法也能达到类似效果。
源码 分析:
-
Storage::append()
在driver
为local
时,调用的是vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php
提供的方法,那么其中定义的append()
是这样的:
/**
* Append to a file.
*
* @param string $path
* @param string $data
* @param string $separator
* @return int
*/
public function append($path, $data, $separator = PHP_EOL)
{
if ($this->exists($path)) {
return $this->put($path, $this->get($path).$separator.$data);
}
return $this->put($path, $data);
}
可以明显的看出这里的 append
方法是将文件读出来拼接再写入的,那么当文件非常大时,io 消耗和内存消耗肯定会存在问题
- 那么看看
Filesystem
的append
/**
* Append to a file.
*
* @param string $path
* @param string $data
* @return int
*/
public function append($path, $data)
{
return file_put_contents($path, $data, FILE_APPEND);
}
可以看到这里是调用的 php
原生方法 file_put_contents()
,那么为什么这个原生方法不会有问题?
-
file_put_contents()
的实现
php
官方文档在介绍 file_put_contents()
时是这样描述的:
可以看到,它也并没有调用 php 在应对高并发时的应对函数 flock()
那么当使用 FILE_APPEND
模式时,php
是如何操作的,我们注意到在描述 fwirte()
时,特别标注了 可安全用于二进制对象
那么它的含义是什么?
看到这里,也明白了 fwrite
是能保证在追加模式下的多进程调用都写入到文件尾部
总结:php_put_contents
在使用追加模式时,其实是fwrite()
函数在追加模式下能保证文件安全的都实现内容追加到尾部,php
的 fwrite
调用的是系统的 write()
并使用参数 O_APPEND