尽管增加了 PHP 内存限制并确认没有 RLimitMEM,Wordpress 站点仍重复出现 PHP 内存不足错误

jpt*_*tme 0 php5 wordpress centos5 apache-2.2

几乎每 4 分钟,我就会在 php 错误日志中看到以下内存不足错误:

01-Jul-2014 21:50:03 UTC] PHP Fatal error:  Allowed memory size of 268435456 bytes
exhausted (tried to allocate 72 bytes) in /home/[sitename]/public_html/wp-includes
/wp-db.php on line 1938
Run Code Online (Sandbox Code Playgroud)

该错误消息似乎支持php.ini 设置memory_limit = 256M 被PHP 认为是正确的。但是,我在 wordpress 中使用了几个内存监控插件,他们都报告说该站点在稳定状态下使用了大约 35MB 的 RAM,并且在 OOME 发生之前它似乎永远不会增长。记忆之前被设置在较低的水平,并在没有解决症状的情况下反复增加。几乎总是恰好 4 分钟。有时正好是 3 分钟,或 3 分 30 秒,等等。我安装了一个 wordpress cron 插件,看看是否有计划以 4 分钟的间隔运行,但似乎没有。

我检查了 httpd.conf 文件并确认没有 RLimitMEM 设置。我还通过 apachectl -V 确认我正在查看正确的 httpd.conf 文件。Top 表示系统有半 GB 的可用 RAM。我发现访问日志中的条目和 php 错误日志中的 OOME 之间没有相关性。

该服务器托管了相当多的站点。我不管理服务器,但我一直在帮助解决相关站点上的一些问题。

对于如何继续解决此问题的任何建议,我将不胜感激。

jpt*_*tme 5

我终于想通了这一点。

问题是站点上安装的两个插件之间存在冲突(特别是两个插件的配置方式)。 iThemes 安全插件 ( http://ithemes.com/security ) 配置为定期进行站点备份。进行 DB 备份的代码会转储数据库中的每个表,并假设每个表的内容都适合整个内存。与此无关,网站上安装了另一个名为 Redirection 的插件(http://urbangiraffe.com/plugins/redirection/) 用于维护重定向。这个插件有配置选项来记录重定向以及 404 响应。不幸的是,这些日志被设置为永不过期,并且针对我们站点的僵尸网络流量的 b/c 累积了近 90,000 个重定向日志和 30,000 404 个日志。由于 iThemes Security 插件在尝试创建备份时尝试将整个表加载到内存中,因此 wp_redirection_logs 表消耗了来自 php 的所有可用内存并导致进程崩溃。我怀疑 iThemes Security 尝试在每个可用机会重新运行失败的备份,导致每 3-4 分钟出现一次错误。

我通过将重定向日志设置更改为在 5 天后使重定向条目过期并且根本不记录 404 错误来解决此问题。然后我不得不反复刷新日志页面以删除过期的条目。不再出现内存不足错误。

[编辑] 我从其他 wordpress 人员那里听说 iThemse Security 插件在各种 wordpress 表上执行 SELECT * 时遇到类似错误。以下是我如何调试此问题的说明,以防您的问题相似但不完全相同:

我在正常修复后解决错误的方法(增加 php 内存并确保实际工作,确认我的内存使用量随着时间的推移稳定且低)是向 wp-db.php 添加一些调试日志语句,所以我可以看到发生错误时发生了什么。这是我为帮助缩小问题范围所做的代码更改(请务必在尝试之前备份 wp-db.php,以便您可以轻松恢复设置,以防在编辑文件时出现问题):

function get_results( $query = null, $output = OBJECT ) {
    $this->func_call = "\$db->get_results(\"$query\", $output)";

    if ( $query )
        $this->query( $query );
    else
        return null;

    $new_array = array();
    if ( $output == OBJECT ) {
        // Return an integer-keyed array of row objects
        return $this->last_result;
    } elseif ( $output == OBJECT_K ) {
        // Return an array of row objects with keys from column 1
        // (Duplicates are discarded)
        foreach ( $this->last_result as $row ) {
            $var_by_ref = get_object_vars( $row );
            $key = array_shift( $var_by_ref );
            if ( ! isset( $new_array[ $key ] ) )
                $new_array[ $key ] = $row;
        }
        return $new_array;
    } elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
        // Return an integer-keyed array of...
        if ( $this->last_result ) {
            $emited = false;
            foreach( (array) $this->last_result as $row ) {
                if ( $output == ARRAY_N ) {
                    // ...integer-keyed row arrays
                    if (!$emitted) {
                        error_log("Current Mem: " . memory_get_usage() . ", eak mem: " . memory_get_peak_usage());
                        error_log($query);
                        $emitted = true;
                    }
                    $new_array[] = array_values( get_object_vars( $row ) );
                } else {
                    // ...column name-keyed row arrays
                    $new_array[] = get_object_vars( $row );
                }
            }
        }
        return $new_array;
    } elseif ( strtoupper( $output ) === OBJECT ) {
        // Back compat for OBJECT being previously case insensitive.
        return $this->last_result;
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

这是添加的 6 行代码;第一个将 $emitted 变量分配给 false 以跟踪我们是否已经为此请求记录,然后将 if 子句的 5 行实际记录。

这样做是在我们开始将查询结果读入内存之前打印出 php 消耗的当前和峰值内存,并打印出执行的查询。在我们开始读取结果之前,内存会让您了解是否有合理的可用内存。如果可用内存接近您的限制(在几 MB 内),那么问题可能出在其他地方,您实际上没有可用的空间来运行合理大小的查询。如果像我一样,在运行查询之前您有大量空闲内存,那么请查看在内存不足之前运行的查询是什么(我的日志条目都在 php 错误日志中,但如果您的日志被拆分了跨和 iThemes 日志和 php 错误日志,基于时间戳在两者之间关联。

就我而言,它是一个 SELECT * FROM wp_redirection_logs;。该表已经失去控制 b/c 重定向插件在我的站点上配置错误,永远不会使日志条目过期。通过阅读 iThemes 安全插件的代码,很明显备份操作会在数据库中以 wp_ 前缀(或其他前缀,如果您的站点配置为使用不同的前缀)开头的每个表上执行 SELECT * FROM 查询站点或其他东西。)iThemes Security 的其他领域(如 404 错误日志)似乎也对可能超过可用内存的表发出 SELECT * 查询。一旦你找到了查询是什么,你就可以开始推理你的错误的原因,也许像我一样修剪不必要的数据库内容来解决这个问题。

如果您完成这些步骤并返回报告,我很乐意提供建议。