我遇到了一个非常奇怪的问题,不确定如何进一步进行调试。我在一个NGINX + PHP5-FPM + APC的Amazon Ubuntu实例上安装了一个复杂的PHP框架网站。在尝试调试问题时,我将流程简化为以下几点:包含大量大类、创建主对象、启动会话、从memcached检索配置数组、从memcached检索XML文件、包含HTML模板、将输出发送到客户端。
然后我使用http_load
工具让该网站承受每秒20个请求的负载:http_load -timeout 10 -rate 20 -fetches 10000 ./urls.txt
接下来发生的事情非常奇怪。top
显示出一堆php5-fpm进程被生成,每个进程占用少量CPU资源,并且一切运行顺利,就像这样:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28440 www-data 20 0 67352 10m 5372 S 4.3 1.8 0:20.33 php5-fpm
28431 www-data 20 0 67608 10m 5304 S 3.3 1.8 0:16.77 php5-fpm
28444 www-data 20 0 67352 10m 5372 S 3.3 1.8 0:17.17 php5-fpm
28445 www-data 20 0 67352 10m 5372 S 3.0 1.8 0:16.83 php5-fpm
28422 www-data 20 0 67608 10m 5292 S 2.3 1.8 0:18.99 php5-fpm
28424 www-data 20 0 67352 10m 5368 S 2.0 1.8 0:16.59 php5-fpm
28438 www-data 20 0 67608 10m 5304 S 2.0 1.8 0:17.91 php5-fpm
28439 www-data 20 0 67608 10m 5304 S 2.0 1.8 0:23.34 php5-fpm
28423 www-data 20 0 67608 10m 5292 S 1.7 1.8 0:20.02 php5-fpm
28430 www-data 20 0 67608 10m 5300 S 1.7 1.8 0:15.77 php5-fpm
28433 www-data 20 0 67352 10m 5372 S 1.7 1.8 0:17.08 php5-fpm
28434 www-data 20 0 67608 10m 5292 S 1.7 1.8 0:18.56 php5-fpm
20648 memcache 20 0 51568 8192 708 S 1.3 1.3 2:51.06 memcached
28420 www-data 20 0 69876 13m 6300 S 1.3 2.3 0:20.89 php5-fpm
28421 www-data 20 0 67608 10m 5300 S 1.3 1.8 0:21.19 php5-fpm
28429 www-data 20 0 9524 2260 992 S 1.3 0.4 0:11.68 nginx
28435 www-data 20 0 67608 10m 5304 S 1.3 1.8 0:18.58 php5-fpm
28437 www-data 20 0 67352 10m 5372 S 1.3 1.8 0:17.87 php5-fpm
28441 www-data 20 0 67608 10m 5292 S 1.3 1.8 0:20.75 php5-fpm
然后过了一段时间,可能是一秒钟到几分钟不等,几个(通常为两个)php5-fpm进程突然占用了所有的CPU资源:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28436 www-data 20 0 67608 10m 5304 R 48.5 1.8 0:23.68 php5-fpm
28548 www-data 20 0 67608 10m 5276 R 45.2 1.7 0:07.62 php5-fpm
28434 www-data 20 0 67608 10m 5292 R 2.0 1.8 0:23.28 php5-fpm
28439 www-data 20 0 67608 10m 5304 R 2.0 1.8 0:26.63 php5-fpm
此时,一切都会卡住,所有新的HTTP请求都会超时。 如果我停止http_load工具,php5-fpm将在那里挂起多分钟。有趣的是,如果我执行php5-fpm stop
,php5-fpm进程将消失,但任何使用文件系统的命令都会执行出问题。例如,如果我尝试通过ssh下载文件,top
将显示以下内容,需要花费很多时间才能启动实际下载:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3298 sshd 20 0 7032 876 416 R 75.2 0.1 0:04.52 sshd
3297 sshd 20 0 7032 876 416 R 24.9 0.1 0:04.49 sshd
PHP错误日志通常会有以下内容:
[05-Dec-2012 20:31:39] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 58 total children
[05-Dec-2012 20:32:08] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 16 children, there are 0 idle, and 66 total children
Nginx错误日志中充斥着这些条目:
2012/12/05 20:31:36 [error] 4800#0: *5559 connect() to unix:/dev/shm/php-fpm-www.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: ..., server: ec2-....compute-1.amazonaws.com, request: "GET /usa/index.php?page=contact_us HTTP/1.0", upstream: "fastcgi://unix:/dev/shm/php-fpm-www.sock:", host: "ec2-....compute-1.amazonaws.com"
PHP-FPM慢日志没有显示任何有趣的内容,交换永远不会发生,我也无法收集到有关问题的其他有趣的事实。我已经经历了许多配置文件更改的迭代,最近的更改如下:
nginx.conf: http://pastebin.com/uaD56hJF
pool.d/www.conf: http://pastebin.com/mFeeUULC
===更新1===
站点的配置: http://pastebin.com/qvinVNhB
===更新2===
还发现dmesg
报告了像这样的错误
[6483131.164331] php5-fpm[28687]: segfault at b6ec8ff4 ip b78c3c32 sp bff551f0 error 4 in ld-2.13.so[b78b5000+1c000]
===更新3===
我们已经使用新的Amazon EC2微型实例,以排除可能的硬件问题。此外,我现在正在使用php-fastcgi来排除可能的fpm错误。其他区别很小,我想唯一改变的是Ubuntu->Debian。相同的问题仍然存在,只是现在服务器可以在max_execution_time秒后稍微恢复(然后再次飙升)。
我尝试创建一个独立的test.php文件,但我不确定它是否是相同的问题,但至少在top
中看起来是一样的。我创建了一个test.php并包含了一堆属于我们框架的库。这些库除了定义类或包含其他定义类的库之外,什么也不做。我用APC检查过,所有这些都能成功地被服务。然后我开始以每秒200个请求的速度对test.php进行压力测试,经过一段时间后,发生了同样的事情。除了现在我设法得到了一些错误,说"打开的文件太多"。它并不总是发生,有时候仅仅开始超时而没有输出错误,几个PHP进程会卡住消耗全部CPU。我只是简单地玩了一下,但我认为这里有一个相关性 - 通过控制包含库的数量或略微变化的请求/秒率,我可以控制CPU飙升的发生时间。我增加了相关的操作系统变量,但问题仍然存在,尽管需要更长的时间才会发生(还要注意,我已将限制设置为测试期间所做请求的总数N倍的值)。
fs.file-max = 70000
...
* soft nofile 10000
* hard nofile 30000
...
worker_rlimit_nofile 10000;
...
(reloaded all the configs and made sure the new system vars actually took affect)
所以到目前为止,我能想到的最好且唯一的解释是,即使APC应该从内存中读取文件,但在内部实现时,每当调用PHP include时仍然使用文件描述符。由于它们可能会延迟释放或在某个不幸的时刻有太多请求同时到达,系统会耗尽描述符,并且新到达的HTTP请求会快速堆积成一个巨大的队列。我会尝试进行测试。