概要

概要

我遇到了 FastCGI (mod_fastcgi) 问题。这种情况偶尔会发生,但不会导致服务器完全崩溃,只是出现 500 个错误。以下是一些问题。首先,我使用 APC,因此 PHP 可以控制其自己的进程,而不是 FastCGI。此外,我将 webroot 设置为:

/var/www/html

以及里面的fcgi-bin:

/var/www/html/fcgi-bin

首先这里是 apache error_log:

[Fri Jan 07 10:22:39 2011] [error] [client 50.16.222.82] (4)Interrupted system call: FastCGI: comm with server "/var/www/html/fcgi-bin/php.fcgi" aborted: select() failed, referer: http://www.domain.com/

我还对“fcgi-pm”进程运行了 strace。以下是该进程崩溃时跟踪的片段:

21725 gettimeofday({1294420603, 14360}, NULL) = 0
21725 read(14, "C /var/www/html/fcgi-bin/php.fcgi - - 6503 38*", 16384) = 46
21725 alarm(131)                        = 0
21725 select(15, [14], NULL, NULL, NULL) = 1 (in [14])
21725 alarm(0)                          = 131
21725 gettimeofday({1294420603, 96595}, NULL) = 0
21725 read(14, "C /var/www/html/fcgi-bin/php.fcgi - - 6154 23*C /var/www/html/fcgi-bin/php.fcgi - - 6483 28*", 16384) = 92
21725 alarm(131)                        = 0
21725 select(15, [14], NULL, NULL, NULL) = 1 (in [14])
21725 alarm(0)                          = 131
21725 gettimeofday({1294420603, 270744}, NULL) = 0
21725 read(14, "C /var/www/html/fcgi-bin/php.fcgi - - 5741 38*", 16384) = 46
21725 alarm(131)                        = 0
21725 select(15, [14], NULL, NULL, NULL) = 1 (in [14])
21725 alarm(0)                          = 131
21725 gettimeofday({1294420603, 311502}, NULL) = 0
21725 read(14, "C /var/www/html/fcgi-bin/php.fcgi - - 6064 32*", 16384) = 46
21725 alarm(131)                        = 0
21725 select(15, [14], NULL, NULL, NULL) = 1 (in [14])
21725 alarm(0)                          = 131
21725 gettimeofday({1294420603, 365598}, NULL) = 0
21725 read(14, "C /var/www/html/fcgi-bin/php.fcgi - - 6179 33*C /var/www/html/fcgi-bin/php.fcgi - - 5906 59*", 16384) = 92
21725 alarm(131)                        = 0
21725 select(15, [14], NULL, NULL, NULL) = 1 (in [14])
21725 alarm(0)                          = 131
21725 gettimeofday({1294420603, 454405}, NULL) = 0

我注意到,无论如何,“select()”似乎都保持不变,但是 read() 在失败时将其返回值从 46 更改为其他数字。有人见过这样的情况吗?这可能是某种文件锁定吗?

谢谢,本

答案1

概要

我在 Apache 中观察到了同样的行为;看来这个问题并不只存在于 lighttpd 中。

就我而言,症状完全相同;Apache 访问日志中充斥着间歇性的 500 响应代码,而 PHP 的错误日志中没有相应的条目(并且 PHP 错误报告配置为最详细)。

我在 Apache 邮件列表中详细描述了这个问题(在列表档案中搜索主题“access.log 中出现间歇性 500 响应,而 error.log 中没有相应的条目”)。

根本原因

1100110 的答案暗示了根本原因,但我将直接来自 Apache 的附加文档以及消除问题的建议。

以下是 Apache 对此事的官方声明:

https://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html

特殊 PHP 注意事项

默认情况下,PHP FastCGI 进程在处理 500 个请求后退出,并且它们可能在该模块已连接到应用程序并发送下一个请求后退出。发生这种情况时,将记录错误并向客户端返回 500 内部服务器错误。可以通过将 PHP_FCGI_MAX_REQUESTS 设置为 0 来禁用此 PHP 行为,但如果 PHP 应用程序泄漏资源,这可能会带来问题。或者,可以将 PHP_FCGI_MAX_REQUESTS 设置为比默认值高得多的值,以减少此问题的频率。可以将 FcgidMaxRequestsPerProcess 设置为小于或等于 PHP_FCGI_MAX_REQUESTS 的值以解决问题。

PHP 子进程管理 (PHP_FCGI_CHILDREN) 应始终通过 mod_fcgid 禁用,该功能每次只会将一个请求路由到它所生成的应用程序进程;因此,PHP 创建的任何子进程都无法有效使用。(此外,PHP 子进程可能无法正常终止。)默认情况下,如果环境变量设置 PHP_FCGI_CHILDREN=0,PHP 子进程管理将被禁用。

流行的 PHP APC 操作码缓存无法在 PHP FastCGI 进程之间共享缓存,除非 PHP 管理子进程。因此,缓存的有效性受到 mod_fcgid 的限制;并发的 PHP 请求将使用不同的操作码缓存。

我们明白了。

可能的解决方案

选项1

一种解决方案是将 PHP_FCGI_MAX_REQUESTS 设置为零,但采取此措施可能会导致内存泄漏失控。

我所查阅过的各种文档都没有明确指出通过 Fast-CGI 运行的 PHP 是否存在固有的内存泄漏(因此有这种内置的“进程回收”行为)或者这种风险是否仅限于编写不当的“失控”脚本。

无论如何,将 PHP_FCGI_MAX_REQUESTS 设置为零都存在固有风险,尤其是在共享托管环境中。

选项 2

第二种解决方案,如上面的摘录所述,是将 FcgidMaxRequestsPerProcess 设置为小于或等于 PHP_FCGI_MAX_REQUESTS 的值。然而,文档忽略了一个重要点:该值还必须大于零(因为零在此上下文中表示“无限制”或“禁用检查”)。鉴于 FcgidMaxRequestsPerProcess 的默认值为零,而 PHP_FCGI_MAX_REQUESTS 的默认值为 500,任何未覆盖这些值的管理员都将遇到间歇性的 500 响应代码。因此,我无法理解为什么 FcgidMaxRequestsPerProcess 和 PHP_FCGI_MAX_REQUESTS 不共享相同的默认值。也许这是因为这样配置这两个指令会产生与将 PHP_FCGI_MAX_REQUESTS 设置为零相同的净结果;文档在这方面含糊不清。

选项 3

第三种解决方案是完全放弃 Fast-CGI,转而采用类似的替代方案,例如 suPHP 或普通的 CGI + SuExec。我对各种 PHP 模式进行了一些基本的原始性能基准测试,结果如下:

  1. 修改版 PHP 77.7
  2. 电脑特效 69.0
  3. 超级PHP 67.0
  4. 快速 CGI 55.7

Mod-PHP 的表现最高,得分为 77.7。分数是任意的,仅用于展示不同 PHP 模式下页面加载时间的相对差异。

如果我们假设这些基准测试具有相当的代表性,那么似乎没有理由坚持使用 Fast-CGI,因为其实现中存在一个(相当严重的)缺陷。我想到的唯一实质性原因是操作码缓存。我的理解是 PHP 无法通过 CGI 或 suPHP 模式利用操作码缓存(因为进程不会在请求之间持久化)。

尽管 Fast-CGI 不能充分利用操作码缓存(例如通过 APC),聪明的用户已经设计出一种使用 Fast-CGI 有效渲染 APC 的方法(通过每个用户缓存):http://www.brandonturner.net/blog/2009/07/fastcgi_with_php_opcode_cache/。但是,也存在一些缺点:

  1. 内存 (RAM) 需求相当大,因为每个用户都有专用的缓存。(从角度来看,在 Mod-PHP 模式下,所有用户共享一个缓存。)
  2. Apache 必须使用较旧的模块 mod_fastcgi,而不是较新的模块 mod_fcgid。(有关详细信息,请参阅上文引用的文章。)
  3. 配置相当复杂。

作为相关的推论,您在问题中说了以下内容:

首先,我使用 APC,因此 PHP 可以控制它自己的进程,而不是 FastCGI。

除非您使用的是 mod_fastcgi(而不是 mod_fcgid),并且除非您遵循了与上面几段中提到的步骤类似的步骤,否则 APC 会消耗资源而没有效果。因此,您可能希望禁用 APC。

解决方案摘要

采取以下三种措施之一:

  1. 将 PHP_FCGI_MAX_REQUESTS 环境变量设置为零。(这会导致 PHP 脚本中的内存泄漏失控。)
  2. 将 FcgidMaxRequestsPerProcess 设置为小于或等于 PHP_FCGI_MAX_REQUESTS 但大于零的值。
  3. 放弃 Fast-CGI,转而采用类似的替代方案,例如 suPHP 或普通 CGI + SuExec。

答案2

我在某处读到(处理 lighttpd,而不是 apache)php 出于某种原因无法处理超过 500 个请求。第 501 个请求将因某种原因而崩溃。

抱歉,我没有更多信息,但至少值得一试。

tl;dr 尝试将 PHP_FCGI_MAX_REQUESTS 设置为 500,看看问题是否自行解决。

查了一下资料,是适用于Lighttpd的,不知道是否适用于apache。

测试一下,我很想知道这是否只是 lighttpd 的问题,或者这是否是一个普遍问题。

为什么我的 PHP 应用程序有时会返回错误 500?

“该问题似乎源于 PHP 中一个鲜为人知的问题:在处理 500 个请求后,PHP 将停止接受新的 FastCGI 连接;不幸的是,在 PHP 清理代码中存在一个潜在的竞争条件,其中 PHP 可以关闭但仍然保持套接字打开,因此 lighty 可以将请求编号 501 发送到 PHP 并使其“被接受”,但随后 PHP 似乎只是退出,导致 lighty 返回 500。

为了限制这种情况的发生,请将 PHP_FCGI_MAX_REQUESTS 设置为 500。”

--http://redmine.lighttpd.net/projects/1/wiki/Docs:PerformanceFastCGI

答案3

感谢您的回复。我已将所有 PHP 错误记录到日志文件中。我收到一些通知,但没有错误。我必须承认我没有编写此代码。目前,我已使用“.htaccess”规则将所有 500 个错误重定向到 index.php。但我肯定遗漏了什么。这不应该发生。我唯一的猜测是,一旦“PHP_FCGI_MAX_REQUESTS”达到最大值,php 就会终止子进程,这会使 FastCGI 感到困惑。但是,如果我理解正确的话,PHP 有一个父进程,它应该是 FastCGI 唯一与之通信的进程,所以我不确定它是否就是它……这是我的包装器脚本:

#!/bin/bash
# Shell Script To Run PHP5 using mod_fastcgi under Apache 2.x
# Tested under Red Hat Enterprise Linux / CentOS 5.x
### Set PATH ###
PHP_CGI='/usr/bin/php-cgi -d apc.shm_size=60M'
PHP_FCGI_CHILDREN=25
PHP_FCGI_MAX_REQUESTS=1000
### no editing below ###
export PHP_FCGI_CHILDREN
export PHP_FCGI_MAX_REQUESTS
exec $PHP_CGI

这是一个非常高容量的服务器,所以 PHP_FCGI_CHILDREN 设置得如此之高。

再次感谢,本

相关内容