使用PHP实现多个用户同时写入同一文件

6
我有一个网站,可以同时有多个用户在同一时间写入同一个文件。以下是我的代码示例。
PHP 5.6.20
<?php
$root=realpath($_SERVER["DOCUMENT_ROOT"]);
$var=time()."|";
echo $var."<br/>";
$filename=$root."/Testing/Testing/test1.txt";

$myfile=fopen($filename,"a");
fwrite($myfile,$var);
fclose($myfile);

$myfile=fopen($filename,"r");
$contents = fread($myfile, filesize($filename));
echo $contents;
fclose($myfile);
?>

我了解到在PHP中,如果多个用户同时尝试写入同一个文件,就有可能导致数据损坏。当然,我不想编写可能会在长期运行中导致某些数据损坏的代码。
我尝试在浏览器上几乎同时运行以上代码,以模拟多个用户同时写入同一个文件,但是它没有产生任何错误或对正在写入的文件造成任何数据损坏,但我仍然不确定未来是否会发生数据损坏。
我读到可以使用flock来确保2个用户不能同时写入文件,但是由于我测试了以上代码并且没有发生数据损坏,所以我不确定是否应该使用flock更新我的代码,还是保持原样。
我的问题是:
1)以上代码是否有损坏正在写入的文件的风险?
2)如果是,使用flock会解决这个问题吗? 如果是,我应该如何在以上代码中实现flock?
编辑:
我知道可以通过使用数据库来解决这种不确定性,但对于此案例,最好使用纯文本,请不要建议我使用数据库。
提前感谢您的帮助。

这段代码可能会导致文件损坏。由于您正在将内容追加到文件中,因此可能还好。如果您将其用作日志文件,则不会有问题。使用flock将导致代码等待并导致使用它的人遇到延迟。 - ryantxr
你的意思是说,我在文件中添加数据就可以了吗?但是如果这样做不行怎么办?难道是因为使用"w"写入文件时出现问题吗?我也尝试过这种方法,但并没有发生数据损坏。 - John Colbir
1
你的用户为什么不能直接写入数据库呢?文件适合读取(通过包含等方式),但是你说得对,可能会在权限上产生争用。这甚至可能无法通过使用MySQL的旧MyISAM表类型来解决。但是如果你坚持使用InnoDB,那就没问题了。所以我想知道为什么你首选文件。很抱歉我没有解决你的问题。 - Lucas Krupinski
4个回答

2
如果两个脚本同时尝试写入同一文件,那么调用fopen()函数打开该文件并不会阻止其他脚本也打开该文件。这意味着你可能会发现一个脚本正在读取文件时另一个脚本正在写入文件,或者更糟糕的情况是两个脚本同时写入同一个文件。因此,最好使用flock()函数实现锁定。 您可以在http://www.hackingwithphp.com/8/11/0/locking-files-with-flock上获取更多帮助信息。 对于您的代码,您可以使用flock()函数。
<?php
$root=realpath($_SERVER["DOCUMENT_ROOT"]);
$var=time()."|";
echo $var."<br/>";
$filename=$root."/Testing/Testing/test1.txt";

$myfile=fopen($filename,"a");
if (flock($myfile, LOCK_EX)) {        
    fwrite($myfile,$var);
    flock($myfile, LOCK_UN); // unlock the file
} else {
    // flock() returned false, no lock obtained
    print "Could not lock $filename!\n";
}

fclose($myfile);

$myfile=fopen($filename,"r");
if (flock($myfile, LOCK_EX)) {        

    $contents = fread($myfile, filesize($filename));
    echo $contents;
    flock($myfile, LOCK_UN); // unlock the file
} else {
    // flock() returned false, no lock obtained
    print "Could not lock $filename!\n";
}
fclose($myfile);
?>

你为什么在读取文件时使用了 LOCK_EX 而不是使用 LOCK_SH,以便多个用户可以同时读取该文件,这样就不会导致文件的任何数据损坏。你认为呢? - John Colbir
是的,您可以在读取文件时使用LOCK_SH。但在写入时,您必须使用LOCK_EX,因为一次只能有一个进程写入。 - Vijay Rathore

1
我不太熟悉flock,但我的第一反应是锁定或排队机制。如果写入的数据不必被用户使用或查看,则工作队列是最好的选择。将数据写入基于redis或memcached的系统、sql或其他类型的队列系统,或者只需在目录中转储带有感兴趣内容的独特时间戳文件,工作线程可以按升序聚合到主文件中。
对于写入数据触发用户需要获取报告或结果、响应或其他反馈的用例,如果无法重新设计成具有最终一致性的异步堆栈,则锁定可能是一种方式。这也很难知道负载和并发用户数、服务器数量等。

1
这是许多编程语言中许多Web应用程序常见的理论问题。
答案是可以引起麻烦。然而,这是一个非常理论化的问题,如果您添加到文件中的内容不是很大,并且如果您没有重负载的话。今天的操作系统和文件系统都经过了优化(缓存、惰性写入等),因此很不可能发生,当您在使用完文件句柄后立即关闭它们时。
如果遇到错误(使用PHP检查访问权限/在其他语言中捕获异常前进行写入),您可以添加类似于缓冲区的内容,并在一段时间后再次尝试,或者将缓冲区写入临时文件并与另一个进程合并 - 您有几种选择。
是的,flock()是可用于这些目的的函数,但我认为那已经是过度设计了。

根据以下内容:“如果在使用文件句柄后立即关闭它们,这种情况非常不可能发生。”我只能告诉你,我也曾经认为这样的情况不会发生,直到有一次cron在我的服务器上同时运行了两次PHP脚本。尽管这是一个糟糕的配置任务,但这种情况确实发生过。 - Artur Poniedziałek
好的例子。但对于cronjob,编写锁定将变得容易;-) - iquellis
@iquellis,使用flock真的能保证我不会得到文件中的损坏数据吗?如果是的话,您能否在代码中添加flock并编辑您的答案? - John Colbir
1
我自己从未使用过flock()。只是在php.net上阅读了文档。然而,我见过很多原始和糟糕的代码,锁定某些在极高流量API上运行且不会引起问题的东西。最终每个人都是一条腿一条腿地穿裤子。 - iquellis

-1

你需要将保存机制从简单文件更改为带有事务的任何数据库。


1
我不想使用数据库,我想使用纯文本。 - John Colbir
3
我了解您的意图,但需要知道的是,您不能通过PHP脚本完全控制任何文件。虽然99%的技巧都能奏效,但无法掌控剩下的1%。这就是为什么在数据库中我们有事务机制的原因。 - Artur Poniedziałek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接