5

我需要打开一个日志文件进行写入。麻烦的是,很多事情可能会同时发生,我不想要冲突。每次写入将是一行,通常大约 150 字节(并且总是小于 1K),并且不严格要求按时间顺序排列。

我想要的是尝试flock(),如果失败,请继续尝试几秒钟。如果多次尝试后仍无法建立锁,则放弃。

$fh=fopen($logfile, "a");

if (flock($fh, LOCK_EX|LOCK_NB)) {
  $locked=TRUE;
} else {
  $locked=FALSE;
  // Retry lock every 0.1 seconds for 3 seconds...
  $x=0; while($x++ < 30) {
    usleep(100000);
    if (flock($fh, LOCK_EX|LOCK_NB)) {
      $locked=TRUE;
      break;
    }
  }
}

if ($locked) {
  if (fwrite($fh, strftime("[%Y-%m-%d %T] ") . $logdata . "\n")) {
    print "Success.\n";
  } else {
    print "Fail.\n";
  }
  flock($fh, LOCK_UN)
} else {
  print "Lock failed.\n";
}

我有两个问题,一个是一般的,一个是具体的。首先,除了以不同的方式(do...while等)实现相同的解决方案之外,是否有更好的通用策略来处理此类问题,仅在 PHP 中运行?其次,有没有更好的方法在 PHP 中实现这一点?(是的,我把它们分开是因为我对策略部分真的很感兴趣。)

我考虑过的一种替代方法是使用syslog(),但 PHP 代码可能需要在系统级管理(即向 /etc/syslog.conf 中添加内容)可能无法作为选项提供的平台上运行。

更新:根据randy的建议添加|LOCK_NB到上面的代码中。

4

1 回答 1

4

在我在 PHP 制作日志(在 linux 下!)方面的长期经验中,我从未遇到过冲突问题(即使有数百个同时和并发写入)。如此简单的跳过锁管理:

$fh=fopen($logfile, "a");
if (fwrite($fh, strftime("[%Y-%m-%d %T] ") . $logdata . "\n")) {
    print "Success.\n";
  } else {
    print "Fail.\n";
  }
fclose($fh);

关于这个策略,文件日志记录(有或没有锁)不是最好的解决方案,因为每个带有“ a ”的 fopen 都意味着一个 seek 系统调用来将光标设置在文件的末尾。保持文件打开的syslog可以避免这种开销。

当然,对于“大”文件,开销变得很重要(在性能上),一个简单的解决方案是创建名称中包含日期(或日期时间)的日志文件。

添加

apache 软件包包含一个测试程序:ab,允许同时进行查询,您可以测试我的论文,以通过 10 到 1000 个线程完成的 1000000 个查询来测试我的服务器。

添加 - 在评论之后

不,这不是不可能完成的任务。

我从http://php.net/manual/en/function.fwrite.php找到了一个注释

如果句柄在追加模式下被 fopen() 处理,则 fwrite() 是原子的(除非字符串的大小超过文件系统的块大小,在某些平台上,并且只要文件位于本地文件系统上)。也就是说,在调用 fwrite() 之前不需要flock() 一个资源;所有数据都将不间断地写入。

要知道一个块有多大(通常是 4k):

dumpe2fs /dev/sd_your_disk_partition |less -i

写入的“原子性”是实现阻止其他“代理”写入(当您在“ps ax”中看到进程处于“D”状态时),但是PHP流工具可以解决这个问题,请参见:*stream_set_blocking*。这种方法可能会引入部分写入,因此您必须验证记录的完整性。

在任何情况下,fwrite(网络或文件)都容易受到阻塞/失败的影响,无论使用flock。恕我直言,羊群只介绍开销。

关于您的原始问题和您的目标(尝试在高度规避风险的环境中实施公司政策),即使是 fwrite 也可能有问题,我只能想象一个简单的解决方案:使用数据库

  • 大部分复杂性都来自 PHP 并用 C 编写!
  • 完全控制操作流程
  • 高并发级别
于 2012-07-01T19:36:18.210 回答