在高并发场景下,PHP 直接对同一文件进行读写操作时,极易出现文件内容丢失、数据错乱等问题。究其原因,是多个进程同时操作文件时缺乏原子性控制,因此需要通过文件锁机制来规避这类风险,核心思路是借助 flock 函数对文件加锁,确保同一时间只有一个进程能读写文件。
接下来要分享的这个文件操作函数,源自知名无数据库轻量级云存储系统 —— 可道云(KodCloud)的核心代码(原版位于 /app/function/file.function.php 第 729 行附近)。可道云全程依赖文件存储配置数据,因此该函数经过了海量场景的验证,其安全性、稳定性和普适性均有保障,可放心集成到各类 PHP 项目中。
原版函数存在一个小局限:当目标文件不存在时,会直接返回 false 导致写入失败。我对这一逻辑做了优化 —— 若写入的目标文件不存在,函数会自动创建文件并完成内容写入,进一步提升了函数的易用性和场景适配性。
可道云源码下载地址:
https://pan.quark.cn/s/8b02f9ebfbda
以下是完整的优化后代码:
<?php
/**
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/
/**
* 安全读取文件,避免并发下读取数据为空
*
* @param $file 要读取的文件路径
* @param $timeout 读取超时时间
* @return 读取到的文件内容 | false - 读取失败
*/
function file_read_safe($file, $timeout = 5) {
if (!$file || !file_exists($file)) return false;
$fp = @fopen($file, 'r');
if (!$fp) return false;
$startTime = microtime(true);
// 在指定时间内完成对文件的独占锁定
do {
$locked = flock($fp, LOCK_EX | LOCK_NB);
if (!$locked) {
usleep(mt_rand(1, 50) * 1000); // 随机等待1~50ms再试
}
}
while ((!$locked) && ((microtime(true) - $startTime) < $timeout));
if ($locked && filesize($file) >= 0) {
$result = @fread($fp, filesize($file));
flock($fp, LOCK_UN);
fclose($fp);
if (filesize($file) == 0) {
return '';
}
return $result;
} else {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
}
/**
* 安全写文件,避免并发下写入数据为空
*
* @param $file 要写入的文件路径
* @param $buffer 要写入的文件二进制流(文件内容)
* @param $timeout 写入超时时间
* @return 写入的字符数 | false - 写入失败
*/
function file_write_safe($file, $buffer, $timeout = 5) {
clearstatcache();
if (strlen($file) == 0 || !$file) return false;
// 文件不存在则创建
if (!file_exists($file)) {
@file_put_contents($file, '');
}
if(!is_writeable($file)) return false; // 不可写
// 在指定时间内完成对文件的独占锁定
$fp = fopen($file, 'r+');
$startTime = microtime(true);
do {
$locked = flock($fp, LOCK_EX);
if (!$locked) {
usleep(mt_rand(1, 50) * 1000); // 随机等待1~50ms再试
}
}
while ((!$locked) && ((microtime(true) - $startTime) < $timeout));
if ($locked) {
$tempFile = $file.'.temp';
$result = file_put_contents($tempFile, $buffer, LOCK_EX);
if (!$result || !file_exists($tempFile)) {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
@unlink($tempFile);
ftruncate($fp, 0);
rewind($fp);
$result = fwrite($fp, $buffer);
flock($fp, LOCK_UN);
fclose($fp);
clearstatcache();
return $result;
} else {
flock($fp, LOCK_UN);
fclose($fp);
return false;
}
}