本文目录导读:

在PHP项目中处理多进程写入同一文件,需要考虑数据完整性和性能,以下是几种常见的解决方案:
使用文件锁(flock)
最基础的解决方案,使用flock函数实现进程间互斥:
<?php
function writeToFile($file, $data) {
$fp = fopen($file, 'a');
if (flock($fp, LOCK_EX)) { // 获取独占锁
fwrite($fp, $data . PHP_EOL);
fflush($fp); // 强制写入
flock($fp, LOCK_UN); // 释放锁
}
fclose($fp);
}
优点:简单易用,PHP原生支持
缺点:性能较低,不适合高并发场景
使用临时文件 + 重命名
原子操作,避免文件锁的性能问题:
<?php
function atomicWrite($targetFile, $data) {
$tmpFile = tempnam(dirname($targetFile), 'tmp_');
file_put_contents($tmpFile, $data . PHP_EOL, FILE_APPEND);
// 原子重命名
if (rename($tmpFile, $targetFile)) {
return true;
}
unlink($tmpFile);
return false;
}
注意:这种方式会覆盖原文件,适合记录日志等场景
使用数据库(推荐方案)
使用Redis、MySQL等数据库处理并发写入:
<?php
// 使用Redis队列
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 生产者:写入队列
$redis->lPush('log_queue', json_encode($data));
// 消费者:单进程从队列中取出写入文件
function processQueue() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while ($data = $redis->brPop('log_queue', 5)) {
file_put_contents('log.txt', $data[1] . PHP_EOL, FILE_APPEND);
}
}
使用消息队列
专业消息队列系统处理高并发:
<?php
// 使用RabbitMQ
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'user', 'pass');
$channel = $connection->channel();
$channel->queue_declare('file_write_queue', false, true, false, false);
// 发送消息
$msg = new AMQPMessage(json_encode(['file' => 'log.txt', 'data' => $data]));
$channel->basic_publish($msg, '', 'file_write_queue');
使用PHP的pcntl扩展
使用进程信号量进行同步:
<?php
// 创建信号量
$sem = sem_get(1, 1); // 信号量ID,初始值
function semWrite($file, $data) {
global $sem;
sem_acquire($sem); // 获取信号量
file_put_contents($file, $data . PHP_EOL, FILE_APPEND);
sem_release($sem); // 释放信号量
}
使用文件日志库
成熟的日志库通常已经处理了并发问题:
<?php
// 使用Monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('app.log', Logger::WARNING));
// 多进程写入
$log->warning('This is a warning log');
最佳实践建议
简单场景:使用文件锁
<?php
class SafeFileWriter {
private $file;
private $fp;
public function __construct($file) {
$this->file = $file;
}
public function write($data) {
$this->fp = fopen($this->file, 'a');
// 尝试获取锁,最多等待3秒
if (flock($this->fp, LOCK_EX | LOCK_NB)) {
fwrite($this->fp, $data . PHP_EOL);
flock($this->fp, LOCK_UN);
fclose($this->fp);
return true;
}
// 锁获取失败,可以选择重试或记录错误
fclose($this->fp);
return false;
}
public function __destruct() {
if ($this->fp) {
fclose($this->fp);
}
}
}
高并发场景:使用Redis队列
<?php
// 配置
$config = [
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'queue_key' => 'file_write_queue'
],
'file' => '/var/log/app.log'
];
// 多进程生产者
function producer($data) {
global $config;
$redis = new Redis();
$redis->connect($config['redis']['host'], $config['redis']['port']);
$redis->lPush($config['redis']['queue_key'], json_encode([
'time' => time(),
'pid' => getmypid(),
'data' => $data
]));
}
// 单进程消费者(cron job或daemon)
function consumer() {
global $config;
$redis = new Redis();
$redis->connect($config['redis']['host'], $config['redis']['port']);
while (true) {
$item = $redis->brPop($config['redis']['queue_key'], 1);
if ($item) {
$data = json_decode($item[1], true);
file_put_contents($config['file'],
date('Y-m-d H:i:s', $data['time']) . " [PID:{$data['pid']}] {$data['data']}\n",
FILE_APPEND | LOCK_EX
);
}
}
}
注意事项
- 性能平衡:文件锁会阻塞,不适合高并发;队列方案性能好但需要额外组件
- 数据完整性:考虑写入部分数据时进程崩溃的情况
- 日志轮转:大文件需要考虑日志切分
- 错误处理:实现重试机制和错误记录
- 资源清理:确保文件句柄正确关闭
选择方案时要根据实际场景评估:
- 写入频率
- 数据量大小
- 对实时性的要求
- 可用基础设施
推荐优先使用成熟的日志库或消息队列方案,避免自己实现复杂的并发控制。