Apache mpm-worker + mod_fcgid + php5_cgi 部分且偶尔出现故障

Apache mpm-worker + mod_fcgid + php5_cgi 部分且偶尔出现故障

最近,由于内存问题,我已将 Apache mpm-prefork(PHP 模块)更改为 mpm-worker(PHP-FPM)。我正在运行一个相当大的 PHP 应用程序,每个 prefork 进程需要大约 20-30M 的内存。

总体来说,服务器运行稳定,速度快。不过,有时页面无法访问一些用户几分钟。

工作假设 1(=粗略想法)是其中一个进程(通常是 2 个,有时多达 5 个或 6 个)挂起,并且分配给该进程的每个客户端(例如 50% 的客户端)都会收到一条错误消息。

工作假设 2 是 MaxRequestsPerProcess 造成的。在 500 次调用之后,进程尝试关闭,mod_fcgid 不会正常终止,而当进程等待终止时,更多的客户端被分配给该进程(并被拒绝)。但我真的无法想象 Apache 会这么愚蠢。

我的问题是:错误日志中除了一些

[warn] mod_fcgid: process ???? graceful kill fail, sending SIGKILL

我不知道该从哪里追踪问题。它偶尔出现,我还没有设法引起它。服务器性能(CPU/RAM)不会成为问题,因为最近几周总体负载一直处于较低水平。

谢谢您的提示。您对我的假设有什么意见吗(但这并没有帮助我找到解决方案,我尝试禁用 MaxRequestsPerProcess,但不知道是否有帮助)?如果您能提供一些关于如何追踪此问题的想法,我将不胜感激。

Apache 配置

    <Directory /var/www/html>
           ...

            # PHP FCGI
            <FilesMatch \.php$>
                    SetHandler fcgid-script
            </FilesMatch>
            Options +ExecCGI
    </Directory>

    <IfModule mod_fcgid.c>
            FcgidWrapper /var/www/php-fcgi-starter .php
            # Allow request up to 33 MB
            FcgidMaxRequestLen 34603008
            FcgidIOTimeout 300
            FcgidBusyTimeout 3600
            # Set 1200 (>1000) for PHP_FCGI_MAX_REQUESTS to avoid problems
            FcgidMaxRequestsPerProcess 1000
    </IfModule>

Apache 模块配置

<IfModule mod_fcgid.c>
  AddHandler    fcgid-script .fcgi
  FcgidConnectTimeout 20
  FcgidBusyTimeout 7200

  DefaultMinClassProcessCount 0
  IdleTimeout 600
  IdleScanInterval 60
  MaxProcessCount 20

  MaxRequestsPerProcess 500
  PHP_Fix_Pathinfo_Enable 1
</IfModule>

注意:超时设置为 2 小时,因为很少情况下,应用程序可能需要一些时间来运行(例如,执行数据库优化的夜间 cronjob)。

启动脚本

#!/bin/sh
PHP_FCGI_MAX_REQUESTS=1200
export PHP_FCGI_MAX_REQUESTS

export PHPRC="/etc/php5/cgi"
exec /usr/bin/php5-cgi

#PHP_FCGI_CHILDREN=10
#export PHP_FCGI_CHILDREN

软件包版本

  • 系统:Ubuntu 12.04.2 LTS
  • apache2-mpm-worker:2.2.22-1ubuntu1.4
  • libapache2-mod-fcgid:1:2.3.6-1.1
  • php5-通用:5.3.10-1ubuntu3.7

答案1

我认为每个进程 20-30MB 已经很小了。这都是相对的,但例如大多数 CMS 应用程序至少需要 100MB。此外,如果这很重要的话,您的最大上传大小将受到最大进程大小的限制。

当您的服务器不可用时,很可能是因为 php 工作进程都很忙,但这只是直接原因。某些东西正在减慢您的服务器速度,以至于至少在一段时间内,php 进程无法跟上传入的请求。很难判断是什么减慢了您的服务器速度,但“正常终止失败”让我认为要终止的进程很可能正在磁盘上等待。

发生这种情况时您是否登录过?系统是否响应?

在 top 中,查看进程状态,并查找正在等待 IO 的“D”进程。这些进程很多吗?顶部摘要中的“wa”是进程等待 IO 的总时间。(它表示百分比,但这可能是一个处理器时间的百分比)。iotop、atop 和 vmstat 等工具也可用于查看哪些进程受磁盘限制,以及磁盘限制整体性能的程度。

您对工作进程无法接受新请求时发生的情况的理解是错误的。新请求将不会分配给它。

在终止 worker 之前处理 1000 个请求太高了。我建议将其降至 10 到 50 之间。

答案2

我认为你对假设 1 的看法是正确的。mc0e 的建议非常可靠,因此我主要对其进行了补充。

您看到的日志消息表明,个别进程在预分叉MPM 可以为您提供比工人。我以前在生产环境中见过这种情况,这意味着你的代码存在问题。

由于每个子进程的最大请求数较高,并且进程挂起,这为内存膨胀埋下了伏笔。文档中特别提到了以下事实:非零值有助于防止内存泄漏,但如果将该值设置得太高,好处就会消失。让您的进程挂在此之上只会进一步增加整体内存占用。

这给你留下了两个直接的启示:

  • 就像 mc0e 建议的那样,低了MaxRequestsPerChild很多。这有助于防止单个进程存活时间过长而积累大量内存泄漏……但正如他所说,20-30M 可能不是什么大问题。
  • 找到你的错误。你正在寻找内存泄漏和执行死锁(mc0e 建议的资源争用,但也看看当网络资源无法访问或无响应时你的代码会做什么)。lsof在你的大型进程上运行可能根据代码正在执行的操作提供提示(即文件句柄泄漏,达到最大文件句柄上限可能与进程死锁有关),但除此之外,您正在查看代码调试。

相关内容