最近,由于内存问题,我已将 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
在你的大型进程上运行可能根据代码正在执行的操作提供提示(即文件句柄泄漏,达到最大文件句柄上限可能与进程死锁有关),但除此之外,您正在查看代码调试。