高併發下多進程同時讀寫同一檔案 (如日誌、計數器、快取),若無同步機制會導致資料遺失、內容損毀。
function write_with_lock($file, $data, $timeoutMs = 100) {
$fp = fopen($file, 'a+');
if (!$fp) return false;
$start = microtime(true);
while (!flock($fp, LOCK_EX)) {
if ((microtime(true) - $start) * 1000 > $timeoutMs) {
fclose($fp);
return false; // 逾時
}
usleep(mt_rand(100, 500)); // 隨機退讓
}
fwrite($fp, $data . "\n");
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
注意:NFS/網路檔案系統 flock() 不可靠;Windows 下 LOCK_EX 行為與 Linux 不同。
function atomic_write($file, $data) {
$tmp = $file . '.tmp.' . bin2hex(random_bytes(8));
$fp = fopen($tmp, 'w');
if (!$fp) return false;
fwrite($fp, $data);
fclose($fp);
// rename 在同一檔案系統上是原子操作
if (!rename($tmp, $file)) {
@unlink($tmp);
return false;
}
return true;
}
// 讀取時無需鎖,直接讀 (可能讀到舊版本,但不會壞檔)
$data = file_get_contents($file);
原理:寫入暫存檔 → rename() 原子覆蓋。讀者永遠看到完整舊版或完整新版,不會看到半寫入內容。
function sharded_write($baseDir, $data, $shards = 100) {
$shard = mt_rand(1, $shards);
$file = "$baseDir/log_$shard.txt";
return file_put_contents($file, $data . "\n", FILE_APPEND | LOCK_EX);
}
// 讀取/分析時合併所有分片
function merge_shards($baseDir, $shards = 100) {
$all = '';
for ($i=1; $i<=$shards; $i++) {
$f = "$baseDir/log_$i.txt";
if (is_file($f)) $all .= file_get_contents($f);
}
return $all;
}
優點:寫入無排隊、極高吞吐。缺點:讀取/分析需合併、即時性稍弱。
// 生產者:推入 Redis List / RabbitMQ / Kafka
$redis->rpush('log_queue', json_encode($logData));
// 消費者:單一進程序列化寫入
while (true) {
$item = $redis->lpop('log_queue');
if (!$item) { sleep(1); continue; }
$data = json_decode($item, true);
file_put_contents('/var/log/app.log', format($data) . "\n", FILE_APPEND);
}
優點:完全解耦、可水平擴展、支援重試/死信佇列/監控。缺點:架構複雜度提高、引入外部依賴。
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 單機、低併發、簡單邏輯 | flock() | 零依賴、程式碼少 |
| 單機、需資料一致性、中等併發 | 臨時檔 + rename | 無鎖、ACID、效能佳 |
| 高併發寫入、日誌/統計、可延遲讀取 | 隨機分片 | 寫入 O(1)、無鎖競爭 |
| 分散式、多服務、需持久/重試/順序 | Message Queue | 架構級解耦、可觀測性 |
若需跨伺服器即時同步檔案,可用 inotifywait 監聽變更觸發 rsync,或使用 lsyncd (Lua 腳本封裝 inotify+rsync)。