衆所周知,在高併發的狀態下,直接使用 PHP 讀寫同一個文件時,可能會導致文件內容丟失,於是乎就需要額外的代碼來解決這個問題。大致的思路是先使用 flock
函數對原文件進行鎖死,再來讀寫。
下面的這個函數是從大名鼎鼎的可道雲的代碼中找到的。可道雲相信大家都不會陌生,它是一個無數據庫的程序,因此配置存儲全都是靠這個函數完成的,所以這段代碼的安全性和普適性絕對毋庸置疑,可以放心的用於項目中(注意儘量保留原作者的版權信息就行了)。
代碼的原版位於可道雲的 /app/function/file.function.php
第 729 行左右。原版代碼的邏輯是 寫文件時如果原文件不存在,則直接返回 false
,我把這一部分稍微修改了一下,改成了 如果目標文件不存在,則創建文件並寫入。
不多說了,全部的代碼如下:
<?php
/**
* @link http://kodcloud.com/
* @author warlee | e-mail:[email protected]
* @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;
}
}