FastCGI 和 Apache 500 间歇性错误

ben*_*rn1 4 php fastcgi 500 mod-fastcgi apache-2.2

我有一个 FastCGI (mod_fastcgi) 问题。它每隔一段时间就会发生一次,并且不会导致服务器完全崩溃,只会出现 500 个错误。这里有几件事。首先我使用 APC,所以 PHP 可以控制它自己的进程,而不是 FastCGI。另外,我将 webroot 设置为:

/var/www/html
Run Code Online (Sandbox Code Playgroud)

还有里面的 fcgi-bin:

/var/www/html/fcgi-bin
Run Code Online (Sandbox Code Playgroud)

首先是 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/
Run Code Online (Sandbox Code Playgroud)

我还在“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
Run Code Online (Sandbox Code Playgroud)

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

谢谢,本

Ben*_*son 7

概要

我已经观察到与 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 Internal Server Error。这种 PHP 行为可以通过将 PHP_FCGI_MAX_REQUESTS 设置为 0 来禁用,但如果 PHP 应用程序泄漏资源,这可能会成为一个问题。或者,可以将 PHP_FCGI_MAX_REQUESTS 设置为比默认值高得多的值,以减少出现此问题的频率。可以将 FcgidMaxRequestsPerProcess 设置为小于或等于 PHP_FCGI_MAX_REQUESTS 的值来解决问题。

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

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

我们有它。

可能的解决方案

选项1

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

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

在任何情况下,将 PHP_FCGI_MAX_REQUESTS 设置为零都存在固有风险,尤其是在共享托管环境中。

选项 2

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

选项 3

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

  1. Mod-PHP 77.7
  2. 电脑动画 69.0
  3. 苏PHP 67.0
  4. 快速 CGI 55.7

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

如果我们假设这些基准测试具有相当的代表性,那么考虑到 Fast-CGI 实施中的这个(相当严重的)缺陷,似乎没有什么理由坚持使用 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。


小智 0

感谢您的答复。我将所有 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
Run Code Online (Sandbox Code Playgroud)

这是一个非常大容量的服务器,因此这就是 PHP_FCGI_CHILDREN 设置如此高的原因。

再次感谢,本