我在一个非常繁忙的 Web 服务器上使用 PHP7.3-FPM 配置了 Apache 2.4 MPM Event,如下所示:
Timeout 90
<Proxy "unix:/run/php/php7.3-fpm.sock|fcgi://php-fpm">
ProxySet disablereuse=on timeout=90
</Proxy>
<FilesMatch ".+\.php$">
SetHandler proxy:fcgi://php-fpm
</FilesMatch>
/etc/php/7.3/fpm/php.ini 中的 PHP 配置设置为
max_execution_time=60
PHP-FPM 在 /etc/php/7.3/fpm/pool.d/www.conf 中配置为
request_terminate_timeout=90
读取超时在 /etc/apache2/mods-enabled/reqtimeout.conf 中配置如下:
RequestReadTimeout header=20-120,minrate=50
RequestReadTimeout body=60-120,minrate=50
在 FPM 日志中现在我可以看到每分钟有 1-2 个工作者在处理未知请求,这些请求在 90 多秒后被杀死。
[16-May-2019 09:25:32] WARNING: [pool www] child 105567, script '' (request: " ") execution timed out (113.002653 sec), terminating
[16-May-2019 09:25:32] WARNING: [pool www] child 105567 exited on signal 15 (SIGTERM) after 4050.136381 seconds from start
[16-May-2019 09:25:32] NOTICE: [pool www] child 110414 started
如果我不设置request_terminate_timeout
杀死这些工作者,他们会在“读取标题”状态下停留更长时间(约 5 分钟)并阻塞 PHP-FPM 池。
Apache MPM 中的线程似乎无论如何都不会被阻塞。没有任何东西达到最大工作线程值。
我如何查看这些 PHP 工作者属于哪个脚本/请求?为什么这些脚本在max_execution_time
60 秒后仍在运行?我如何避免此类请求阻塞 PHP 池?
我怀疑有时可能会有不完整的 HTTPS 请求以某种方式启动 PHP 工作程序。有没有办法避免为这些请求启动 PHP 工作程序?
答案1
正如@constantin-galbenu提到的他们的评论,Apache<Proxy>
指令enablereuse=on
与 PHP-FPM 的 prefork 多处理模型配合不佳,如中所述https://bugs.php.net/bug.php?id=69890
[2019-05-02 10:03 UTC] mp at webfactory dot de
Apache 手册位于 https://httpd.apache.org/docs/current/mod/mod_proxy_fcgi.html#examples 说:
启用与 PHP-FPM 等 FCGI 后端的连接重用
请记住,PHP-FPM(撰写本文时为 2018 年 2 月)使用 prefork 模型,即其每个工作进程一次可以处理一个连接。默认情况下,在使用线程 mpm(如 worker 或 event)时,mod_proxy(配置为 enablereuse=on)允许每个 httpd 进程与后端建立 ThreadsPerChild 个连接的连接池,因此应考虑以下用例:
在 HTTP/1.1 负载下,它可能会导致创建最多 MaxRequestWorkers 个与 FCGI 后端的连接。
在 HTTP/2 负载下,由于 mod_http2 的实现方式,存在额外的 h2 工作线程,可能会强制创建其他后端连接。池中的连接总数可能会增加到超过 MaxRequestWorkers。
需要明智地配置 PHP-FPM 工作进程的最大数量,因为它们有可能最终都会“忙于”处理空闲的持久连接,而没有任何空间来建立新的连接,最终用户体验将是一堆 HTTP 请求超时。
我对此的解释是,使用 enablereuse=on,每个 Apache MPM 工作进程/线程都可以与 PHP-FPM 建立开放连接。在 PHP-FPM 状态页面中,这些将显示为“正在读取标头...”(或类似内容)。这些进程似乎处于活动状态(等待)PHP-FPM,因此它们不会被终止。
我自己遇到的问题是使用 Apache 的事件多处理模块 (MPM) 使用 Apache 的示例配置将 PHP 执行传递给 PHP-FPMmod_proxy_fcgi 文档:
<FilesMatch "\.php$">
# Note: The only part that varies is /path/to/app.sock
SetHandler "proxy:unix:/path/to/app.sock|fcgi://localhost/"
</FilesMatch>
# Define a matching worker.
# The part that is matched to the SetHandler is the part that
# follows the pipe. If you need to distinguish, "localhost; can
# be anything unique.
<Proxy "fcgi://localhost/" enablereuse=on max=10>
</Proxy>
使用此配置,我会看到来自 Apache 的无法解释的 503 代理错误,并且应用程序会定期无响应,直到重新启动 PHP-FPM。在强制终止长时间运行的 PHP-FPM 请求后,request_terminate_timeout = 5m
我/etc/php-fpm.d/www.conf
的应用程序变得更加稳定,我开始在我的应用程序和不太频繁的故障中看到如下错误,/var/log/php-fpm/error.log
这些错误会持续几分钟,然后无需干预即可恢复:
[04-Jan-2023 15:19:26] WARNING: [pool www] child 349975, script '' (request: " ") execution timed out (362.004673 sec), terminating
[04-Jan-2023 15:19:26] WARNING: [pool www] child 349975 exited on signal 15 (SIGTERM) after 1200.012433 seconds from start
从我的 Apache 配置中删除该enablereuse=on
标志后,我不再看到上述超时错误/var/log/php-fpm/error.log
。新配置是:
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
# Pick one of the following approaches
#
# Use the standard TCP socket
#SetHandler "proxy:fcgi://localhost:9000"
# If your version of httpd is 2.4.9 or newer (or has the back-ported feature), you can use the unix domain socket
#SetHandler "proxy:unix:/path/to/app.sock|fcgi://localhost/"
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</If>
</FilesMatch>
# Defining a worker will improve performance
# And in this case, re-use the worker (dependent on support from the fcgi application)
# If you have enough idle workers, this would only improve the performance marginally
#
# enablereuse=on is only available in Apache >= 2.4.11
<Proxy "fcgi://localhost/" max=10>
</Proxy>