PHP 在内存不足错误后自动预置错误


PHP auto-prepend buggy after out of memory error

这可能更适合服务器故障,但我想我会先在这里问。

我们有一个文件,该文件使用自动预置附加到服务器上的每个 PHP 文件,其中包含一个名为 Bootstrap 的类,我们用于自动加载、环境检测等。一切正常。

但是,当在对

同一服务器上的另一个文件发出请求之前(即不到一秒甚至同时)出现"内存不足"错误时,会发生以下三种情况之一:

  1. 我们用于检查 if(class_exists('Bootstrap') 的代码,当我们第一次收到此错误时,我们用它来包装类定义,返回 true ,这意味着尽管这是自动预置文件,但该类已经被声明。

  2. 我们从自动预置文件中得到一个"无法重新声明类 Bootstrap"错误,这意味着class_exists('Bootstrap')返回了false但它仍然以某种方式被声明。

  3. 该文件
  4. 根本没有前缀,导致依赖于该文件的文件出现一次性致命错误。

当然,我们可以尝试修复内存不足问题,因为这些问题似乎会导致其他错误,但由于各种原因,它们在我们的设置中无法修复或很难修复。但这不是重点 - 在我看来,这是 PHP 中的一个错误,带有某种内存泄漏导致自动前置指令出现问题。

这比任何事情都令人好奇,因为这很少发生(在我们的高流量服务器上可能每周一次)。但我想知道 - 为什么会发生这种情况,我们能做些什么来解决这个问题?

我们正在与PHP 5.4.19一起运行FreeBSD 9.2

编辑:在过去的几个月里,我们在尝试解决此问题时注意到了一些事情:

  • 它似乎只发生在我们的安全服务器上。内存不足问题主要出在我们的安全服务器上(它们通常来自我们自己的员工试图下载太多数据),所以这可能只是一个巧合,但值得指出

  • 当我们遇到此问题时,get_declared_classes转储包含触发错误的页面上未使用的类。例如,$_SERVER 的输出显示该人正在 xyz.com,但其中一个声明的类仅用于 abc.com,这是内存不足问题通常的来源。

  • 所有这些都让我相信 PHP 在出现内存不足错误后没有进行正确的循环结束垃圾收集,这会导致 Bootstrap 类在下一页请求中全部或部分在内存中,如果它在错误后足够快。我对PHP垃圾收集不够熟悉,无法对此采取行动,但我认为这很可能是问题所在。

如果不修复内存不足问题,您可能无法"修复"问题。在不知道您正在使用的框架的情况下,我将列出想到的领域。

你说"他们通常来自我们自己的员工试图下载太多数据"。我会从那里开始,因为这可能是最大/最响亮的优化机会,我想到了几个想法。

  • 如果正在下载的数据是文件,也许您可以使用流将读取分块到恒定大小,这样内存就不会在大下载时被吞噬。

  • 您可以进行下载队列,限制吗?

  • 如果数据来自数据库,除了优化查询之外,您还可以对查询进行速率限制,减小结果集大小,理想情况下,将此类工作负载移动到具有镜像数据的专用环境中。

  • 确保您的代码负责任地发布文件指针和数据库连接,将其留给 PHP 拆除,可能会导致垃圾回收延迟,并在高流量情况下产生某种级联效应。

在内存限制方面其他唾手可得的成果

  • 您正在运行 PHP 5.4.19,如果您的软件允许,请考虑更新到更多怨恨的版本"PHP 5.4 自 2015 年以来没有打补丁",此外 PHP 7 还具有一系列性能改进。

  • 如果涉及客户端应用程序,请监视其 XHR 和整体网络活动,请查找过多的轮询和挂起连接。

  • 至于您的自动加载器,根据您的评论"当我们遇到此问题时,get_declared_classes的转储包含触发错误的页面上未使用的类"您可能需要检查实现,以确保它没有加载某种捆绑的类缓存,如果您使用的是 composer, 转储自动加载可能会有所帮助。

  • 会话,我看到一些应用程序基于 cookie 和会话加载文件,如果您有这样的设置,我会审核该逻辑并确保没有粘性会话加载不需要的资源。

从您的问题中可以清楚地看出您正在运行多租户服务器。如果没有适当的统计数据,很难更具体,但我认为很明显这个问题不是 PHP 问题,因为根据您的描述,它似乎有些孤立。

正确的调试和分析

我建议安装一个PHP分析器,即使是很短的时间,新的遗物也相当不错。您将能够确切地看到正在发生的事情,并拥有解决正确问题的数据。我认为他们有免费试用版,这应该会让你指向正确的方向......还有其他人,但他们的名字目前逃脱了我。

即使class_exists返回 false,如果存在同名接口,它也永远不会返回 true。但是,不能声明同名的接口和类。

尝试运行class_exists('Bootstrap') && interface_exists('Bootstrap')以确保不重新声明。

你看过__autoload函数吗?

我相信您可以通过在代码中创建一些类似的函数来解决此问题:

function __autoload($className)
{
    if ('file_exists($className . '.php')) 
        include_once($className . '.php');
    else 
        eval('class ' . $className . ' { function __call($method, $args) { return false; } }');
}

如果你有一个名为 Bootstrap 的文件.php其中声明了 Bootstrap 类,PHP 将自动加载文件,否则声明一个可以处理其中任何函数调用的 ghost 类,避免任何错误消息。请注意,对于幽灵功能,我使用了__call魔术方法。