【问题标题】:fopen file locking in PHP (reader/writer type of situation)PHP 中的 fopen 文件锁定(读取器/写入器类型的情况)
【发布时间】:2010-09-21 10:21:49
【问题描述】:

我有一个场景,一个 PHP 进程每秒大约 3 次写入一个文件,然后几个 PHP 进程正在读取这个文件。

这个文件本质上是一个缓存。我们的网站有一个非常坚持的轮询,对于不断变化的数据,我们不希望每个访问者每次轮询时都访问数据库,所以我们有一个每秒读取数据库 3 次的 cron 进程,处理数据,并将其转储到轮询客户端可以读取的文件中。

我遇到的问题是,有时打开文件进行写入需要很长时间,有时甚至长达 2-3 秒。我假设发生这种情况是因为它被读取(或某些东西)锁定,但我没有任何确凿的方法来证明这一点,另外,根据我从文档中了解到的情况, PHP 不应该锁定任何东西。 这种情况每 2-5 分钟发生一次,因此很常见。

在代码中,我没有进行任何类型的锁定,而且我几乎不在乎该文件的信息是否损坏、读取失败或数据是否更改读到一半。 但是,我确实在意,如果写入它需要 2 秒,本质上,因为必须每秒发生三次的过程现在跳过了几个节拍。

我正在用这段代码编写文件:

$handle = fopen(DIR_PUBLIC . 'filename.txt', "w");
fwrite($handle, $data);
fclose($handle);

我正在直接阅读它:

file_get_contents('filename.txt')

(它没有作为静态文件直接提供给客户端,我收到了一个常规的 PHP 请求,它读取文件并用它做一些基本的事情)

该文件大约 11kb,因此读/写不需要太多时间。不到 1 毫秒。

这是发生问题时的典型日志条目:

  Open File:    2657.27 ms
  Write:    0.05984 ms
  Close:    0.03886 ms

不确定它是否相关,但读取发生在常规 Web 请求中,通过 apache,但写入是由 Linux 的 cron 执行的常规“命令行”PHP 执行,它不通过 Apache。

您有什么想法会导致打开文件出现如此大的延迟吗?
关于我可以在哪里寻找帮助我查明实际原因的任何指示?

或者,您能想到我可以做些什么来避免这种情况吗?例如,我希望能够为 fopen 设置 50 毫秒的超时时间,如果它没有打开文件,它就会向前跳过,让下一次运行 cron 来处理它。

再次重申,我的首要任务是让 cron 每秒跳动三次,其他一切都是次要的,因此非常欢迎任何想法、建议和任何事情。

谢谢!
丹尼尔

【问题讨论】:

  • 这取决于文件系统如何实现锁以及 PHP 是否尊重它们。如果您以读取模式打开文件,则不应创建锁定。但是,您可以将“w”(写入)替换为“a”(仅附加)
  • 会改变模式以追加避免锁定吗?
  • 是否写入一个不同名称的文件,然后重命名为公共缓存文件名?
  • 马克,我想尝试一下,但我担心删除现在需要我很长时间才能打开的文件会同样糟糕。它在我的尝试清单上

标签: php locking delay fopen


【解决方案1】:

我能想到 3 个可能的问题:

  • 在您不知情的情况下,通过较低的 php 系统调用读取/写入文件时,文件会被锁定。这应该阻止文件最多 1/3 秒。你会得到更长的时间。
  • fs 缓存启动 fsync() 并且整个系统阻止对磁盘的读/写操作,直到完成。作为修复,您可以尝试安装更多 RAM 或升级内核或使用更快的硬盘
  • 您的“缓存”解决方案不是分布式的,它会每秒多次击中整个系统中性能最差的硬件...这意味着您无法通过简单地添加更多机器来进一步扩展它,只能通过增加硬盘速度。你应该看看 memcache 或 APC,甚至是共享内存 http://www.php.net/manual/en/function.shm-put-var.php

我能想到的解决方案:

  • 将该文件放入 ramdisk http://www.cyberciti.biz/faq/howto-create-linux-ram-disk-filesystem/ 。这应该是避免频繁访问磁盘的最简单方法,无需进行其他重大更改。
  • 使用内存缓存。它在本地使用时速度非常快,扩展性很好,被“大”玩家使用。 http://www.php.net/manual/en/book.memcache.php
  • 使用共享内存。它专为您在这里尝试做的事情而设计......拥有“共享内存区”......
  • 更改 cron 调度程序以减少更新,可能会实现某种事件系统,因此它只会在必要时更新缓存,而不是按时间更新
  • 使“写入”脚本写入 3 个不同的文件,并让“阅读器”随机读取其中一个文件。这可能允许在更多文件中“分配”锁定,并且可能会减少写入某个文件时锁定某个文件的机会......但我怀疑它会带来任何明显的好处。

【讨论】:

  • Quamis,非常感谢您的回复。一些 cmets:我不是 Linux 专家,但如果我正确读取 htop 的输出,它似乎使用了 25% 的 RAM。我知道这个缓存系统很糟糕。如果这是 ASP.Net 而不是 PHP,我会不假思索地将该数据存储在内存中。但这对于开始使用 memcache 甚至拥有多个服务器来说真的很小。我知道如果网站继续增长,这将会出现可扩展性问题,但就目前而言,转向更“严肃”的缓存系统并没有多大意义。
  • 我不知道 PHP 共享内存,我认为 PHP 不可能。这看起来确实很有趣……我要研究一下。老实说,我没有使用 memcached 的经验,考虑到我的系统管理员专业知识,我想避免安装新系统。如果必须,我们会这样做,但就目前而言,我宁愿采取更简单的方法。非常感谢!!
  • 一个问题... PHP 的共享内存是否可以跨 cron 进程和 Apache PHP 进程工作?我有一个运行 5 分钟并结束(并由 cron 重新启动)写入的 cron 进程,然后 Apache PHP 进程响应读取此共享内存的 HTTP 请求。这会正常工作吗?
  • @Daniel:是的,它应该跨进程工作,这就是实现它的原因:)
  • 我最终选择了 ramdisk 解决方案,它对代码的影响最小,而且它的工作方式就像一个魅力。非常感谢!!
【解决方案2】:

如果您想保证持续的低打开时间,您应该使用真正快速的解决方案。也许您的操作系统正在执行磁盘同步、数据库文件提交或其他您无法解决的事情。

我建议使用 memcached、redis 甚至 mongoDB 来完成这些任务。你甚至可以编写自己的缓存守护进程,甚至在 php 中(不过这完全没有必要,而且可能很棘手)。

如果你绝对,肯定你只能通过这个文件缓存来解决这个任务,并且你在 Linux 下,尝试使用不同的磁盘 I/O 调度程序,比如截止日期,或者(cfq 并且将 PHP 进程优先级降低到-3 / -4)。

【讨论】:

  • 说实话,我知道有更好的方法来做到这一点。我继承了一个很大的代码库来做到这一点,并且没有太多的预算(或真的需要)来重新设计整个事情。我知道这是一种糟糕的方法,但考虑到我们的流量水平,它应该足够好至少在很长一段时间内。所以我试图在不重写大量代码的情况下解决这个问题。如果我做不到,那么我将采用更复杂的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
相关资源
最近更新 更多