最近,由于内存问题,我从 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 但还不知道它是否有帮助)?我将非常感谢一些如何追踪这个问题的想法。
阿帕奇配置
<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>
阿帕奇模块配置
<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-common: 5.3.10-1ubuntu3.7
我认为每个进程 20-30MB 非常小。这都是相对的,但例如大多数 CMS 应用程序至少需要 100MB。此外,如果重要的话,您的最大上传大小将受到最大进程大小的限制。
当您的服务器不可用时,很可能 php 工作进程都很忙,但这只是一个近因。某些事情正在减慢您的服务器速度,至少在一段时间内,php 进程无法跟上传入的请求。是什么让你的服务器变慢很难判断,但“优雅的终止失败”让我认为要被终止的进程可能正在磁盘上等待。
发生这种情况时您是否已登录?系统感觉反应灵敏吗?
在顶部,查看进程状态,并查找正在等待 IO 的“D”状态。这些有很多吗?顶部摘要中的“wa”是进程在等待 IO 上花费的总时间。(它表示百分比,但这可能是一个处理器时间的百分比)。iotop、atop 和 vmstat 等工具也可能有助于了解哪些进程是磁盘绑定的,以及磁盘限制整体性能的程度。
您对当工作进程无法接受新请求时会发生什么的理解是不正确的。新请求不会分配给它。
杀死工作人员之前的 1000 个请求很高。我建议将其降低到 10 到 50 之间。
我认为你在假设 1 的正确轨道上。mc0e 的建议非常可靠,所以我主要是添加它。
您看到的这些日志消息表明各个进程在prefork MPM 下被锁定,这为您提供了比worker更好的进程隔离。我以前在生产环境中看到过这种情况,这意味着您有一些行为不端的代码。
在每个孩子的最高请求和挂起进程之间,这为内存膨胀奠定了基础。该文档特别涵盖了一个事实,即非零值有助于防止内存泄漏,但是如果您将该值设置得太高,则会失去好处。让你的进程挂在上面只会进一步增加整体内存占用。
这给你留下了两个直接的收获:
MaxRequestsPerChild
所暗示的那样,大幅下降。这有助于防止各个进程的生存时间足够长以累积大量内存泄漏……但正如他所说,20-30M 可能没什么大不了的。lsof
在大型进程上运行可能会根据代码正在执行的操作提供提示(即文件句柄泄漏,并且达到最大文件句柄上限可能与进程死锁有关),否则您正在查看代码调试。