优化 PHP-FPM 以获得高性能
PHP 无处不在,并且可以说是 Internet Web 上部署最广泛的语言。
然而,它并不以其高性能而闻名,尤其是在高并发系统方面。这就是为什么对于此类专门的用例,Node(是的,我知道,它不是一种语言)、Go和 Elixir 等语言正在取代。
也就是说,您可以采取很多措施来提高服务器上的PHP 性能。本文重点关注php-fpm
事情的侧面,如果您使用 Nginx,这是在服务器上配置的自然方法。
如果您知道php-fpm
是什么,请随时跳转到优化部分。
什么是 PHP-fpm?
没有多少开发人员对DevOps方面感兴趣,即使在那些感兴趣的开发人员中,也很少有人知道幕后发生了什么。有趣的是,当浏览器向运行 PHP 的服务器发送请求时,形成第一个接触点的并不是 PHP;而是 PHP。相反,它是 HTTP 服务器,其中主要的是 Apache 和 Nginx。然后,这些“Web 服务器”必须决定如何连接到 PHP,并将请求类型、数据和标头传递给它。

在现代 PHP 应用程序中,上面的“查找文件”部分是index.php
,服务器配置为将所有请求委托给它。
现在,网络服务器连接到 PHP 的具体方式已经发生了变化,如果我们要深入了解所有细节,那么这篇文章的长度将会爆炸。但粗略地说,在 Apache 作为首选 Web 服务器占据主导地位的时期,PHP 是服务器内部包含的一个模块。
因此,每当收到请求时,服务器就会启动一个新进程,该进程将自动包含 PHP,并执行它。这种方法被称为mod_php
“PHP as a module”的缩写。这种方法有其局限性,Nginx 克服了这一点php-fpm
。
在php-fpm
管理 PHP 的过程中,进程由服务器内的 PHP 程序负责。换句话说,网络服务器(在我们的例子中是 Nginx)并不关心 PHP 在哪里以及它是如何加载的,只要它知道如何从中发送和接收数据即可。如果您愿意,您可以将这种情况下的 PHP 本身视为另一台服务器,它管理传入请求的一些 PHP 子进程(因此,我们让请求到达服务器,该请求被服务器接收并传递到服务器) – 太疯狂了!:-P)。
如果你已经完成了 Nginx 设置,或者只是深入了解它们,你会遇到类似这样的情况:
位置 ~ .php$ { try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass unix:/run/php/php7.2-fpm.sock; fastcgi_index索引.php; 包括 fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
我们感兴趣的行是:fastcgi_pass unix:/run/php/php7.2-fpm.sock;
,它告诉 Nginx 通过名为 的套接字与 PHP 进程通信php7.2-fpm.sock
。因此,对于每个传入请求,Nginx 都会通过此文件写入数据,并在收到输出后将其发送回浏览器。
我必须再次强调,这并不是最完整或最准确的描述,但它对于大多数 DevOps 任务来说是完全准确的。
除此之外,让我们回顾一下到目前为止我们所学到的知识:
- PHP不直接接收浏览器发送的请求。像 Nginx 这样的 Web 服务器首先拦截这些。
- Web 服务器知道如何连接到 PHP 进程,并将所有请求数据(实际上是粘贴所有内容)传递给 PHP。
- 当 PHP 完成其职责时,它将响应发送回 Web 服务器,Web 服务器将其发送回客户端(或大多数情况下是浏览器)。
或者以图形方式:

到目前为止还不错,但现在出现了一个价值百万美元的问题:PHP-FPM 到底是什么?
PHP 中的“FPM”部分代表“快速进程管理器”,这只是一种奇特的说法,表示服务器上运行的 PHP 不是单个进程,而是生成、控制和终止的一些 PHP 进程由该 FPM 流程管理器关闭。Web 服务器将请求传递给此进程管理器。
PHP-FPM 本身就是一个完整的兔子洞,所以如果您愿意,请随意探索,但出于我们的目的,这么多解释就足够了。🙂
为什么要优化 PHP-fpm?
那么,当事情进展顺利时,为什么要担心所有这些舞蹈呢?为什么不让事情保持原样呢?
讽刺的是,这正是我针对大多数用例给出的建议。如果您的设置工作正常并且没有特殊用例,请使用默认值。但是,如果您希望扩展到单台机器之外的规模,那么从一台机器中挤出最大容量是至关重要的,因为它可以将服务器费用减少一半(甚至更多!)。
另一件需要意识到的事情是,Nginx 是为处理巨大的工作负载而构建的。它能够同时处理数千个连接,但如果您的 PHP 设置不是这样,您只会浪费资源,因为 Nginx 必须等待 PHP 完成当前进程并接受接下来,最终否定 Nginx 旨在提供的任何优势!
因此,抛开这一点,让我们看看在尝试优化时我们到底要改变什么php-fpm
。
如何优化PHP-FPM?
服务器上的配置文件位置php-fpm
可能有所不同,因此您需要进行一些研究才能找到它。如果在 UNIX 上,您可以使用find 命令。在我的 Ubuntu 上,路径是/etc/php/7.2/fpm/php-fpm.conf
. 当然,7.2 是我正在运行的 PHP 版本。
该文件的前几行如下所示:
;;;;;;;;;;;;;;;;;;;;;; ; FPM 配置; ;;;;;;;;;;;;;;;;;;;;;; ; 该配置文件中的所有相对路径都是相对于 PHP 的安装 ; 前缀(/usr)。该前缀可以通过使用动态更改 ; 来自命令行的“-p”参数。 ;;;;;;;;;;;;;;;;;; ; 全局选项; ;;;;;;;;;;;;;;;;;; [全球的] ; PID文件 ; 注意:默认前缀是/var ; 默认值:无 pid = /run/php/php7.2-fpm.pid ; 错误日志文件 ; 如果设置为“syslog”,则日志将发送到 syslogd 而不是写入 ; 到本地文件中。 ; 注意:默认前缀是/var ; 默认值:log/php-fpm.log error_log = /var/log/php7.2-fpm.log
有几件事应该是显而易见的:该行pid = /run/php/php7.2-fpm.pid
告诉我们哪个文件包含进程的进程 ID php-fpm
。
我们还看到这就是 存储日志的/var/log/php7.2-fpm.log
地方。php-fpm
在此文件中,再添加三个变量,如下所示:
紧急重启阈值 10 紧急重启间隔 1m process_control_timeout 10s
前两个设置是警告性的,告诉php-fpm
进程如果一分钟内有十个子进程失败,主php-fpm
进程应该重新启动。
这听起来可能不太健壮,但 PHP 是一个短暂的进程,确实会泄漏内存,因此在严重故障的情况下重新启动主进程可以解决很多问题。
第三个选项process_control_timeout
告诉子进程在执行从父进程接收到的信号之前等待这么长时间。例如,当父进程发送 KILL 信号时,子进程正在进行某些操作,这非常有用。有了十秒钟的时间,他们将有更好的机会完成任务并优雅地退出。
令人惊讶的是,这不是配置的核心php-fpm
!这是因为为了服务 Web 请求,php-fpm
创建了一个新的进程池,该进程池将具有单独的配置。就我而言,池名称是www
,我要编辑的文件是 /etc/php/7.2/fpm/pool.d/www.conf
.
让我们看看这个文件的开头是什么:
; 启动一个名为“www”的新池。 ; 变量 $pool 可以在任何指令中使用,并将被替换为 ; 池名称(此处为“www”) [万维网] ; 每个池前缀 ; 它仅适用于以下指令: ; - '访问.log' ; - '慢日志' ; -“听”(unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; 如果未设置,则应用全局前缀(或 /usr)。 ; 注意:该指令也可以相对于全局前缀。 ; 默认值:无 ;前缀 = /path/to/pools/$pool ; Unix 用户/进程组 ; 注:用户为必填项。如果未设置组,则默认用户组 ; 将会被使用。 用户 = www-数据 组 = www-数据
快速浏览一下上面代码片段的末尾就可以解决为什么服务器进程以www-data
. 如果您在设置网站时遇到文件权限问题,您可能已将目录的所有者或组更改为www-data
,从而允许 PHP 进程能够写入日志文件和上传文档等。
最后,我们找到问题的根源,即流程管理器 (pm) 设置。一般来说,您会看到类似这样的默认值:
pm = 动态 pm.max_children = 5 pm.start_servers = 3 pm.min_spare_servers = 2 pm.max_spare_servers = 4 pm.max_requests = 200
那么,这里的“动态”是什么意思呢?我认为官方文档最好地解释了这一点(我的意思是,这应该已经是您正在编辑的文件的一部分,但我在这里复制了它,以防万一它不是):
; 选择进程管理器如何控制子进程的数量。 ; 可能的值: ; static - 固定数量 (pm.max_children) 的子进程; ; 动态 - 子进程的数量根据 ; 遵循指令。通过这种流程管理,将有 ; 始终至少有 1 个孩子。 ; pm.max_children - 可以的最大子节点数 ; 同时活着。 ; pm.start_servers - 启动时创建的子项数量。 ; pm.min_spare_servers - 'idle' 中子进程的最小数量 ; 状态(等待处理)。如果数量 ; “空闲”进程的数量少于此 ; number 然后将创建一些子项。 ; pm.max_spare_servers - 'idle' 中子进程的最大数量 ; 状态(等待处理)。如果数量 ; “空闲”进程的数量大于此 ; 那么一些孩子就会被杀死。 ; ondemand - 启动时不会创建子项。当以下情况时,孩子们将被分叉: ; 新请求将连接。使用以下参数: ; pm.max_children - 最大子节点数 ; 可以同时活着。 ; pm.process_idle_timeout - 秒数 ; 空闲进程将被杀死。 ; 注意:该值是强制性的。
因此,我们看到存在三个可能的值:
- 静态:无论如何都会维护固定数量的 PHP 进程。
- 动态
php-fpm
:我们可以指定 在任何给定时间点保持活动状态的最小和最大进程数。 - ondemand:进程是按需创建和销毁的。
那么,这些设置有何作用呢?
简单来说,如果您的网站流量较低,“动态”设置大多数时候都是资源浪费。假设您pm.min_spare_servers
设置为 3,即使网站没有流量,也会创建并维护三个 PHP 进程。在这种情况下,“按需”是更好的选择,让系统决定何时启动新进程。
另一方面,处理大量流量或必须快速响应的网站将在这种情况下受到惩罚。创建新的 PHP 进程、使其成为池的一部分并对其进行监视是最好避免的额外开销。
使用pm = static
修复子进程的数量,让最大的系统资源用于服务请求而不是管理 PHP。如果您确实走这条路,请注意它有其指导方针和陷阱。这里有一篇关于它的相当密集但非常有用的文章。
最后的话
由于有关 Web 性能的文章可能会引发战争或令人困惑,因此我认为在结束本文之前应该说几句话。性能调优既是系统知识,也是猜测和黑暗艺术。
即使您熟记所有php-fpm
设置,也不能保证成功。如果您不知道 的存在php-fpm
,那么您无需浪费时间担心它。继续做你已经在做的事情并继续下去。
同时,避免成为性能迷。是的,您可以通过从头开始重新编译 PHP 并删除所有不会使用的模块来获得更好的性能,但这种方法在生产环境中不够明智。优化某些东西的整个想法是看看您的需求是否与默认值不同(他们很少这样做!),并根据需要进行微小的更改。
如果您还没有准备好花时间优化 PHP 服务器,那么您可以考虑利用像Kinsta这样负责性能优化和安全性的可靠平台。
原创文章,作者:超哥,如若转载,请注明出处:https://www.chaoneo.cn/archives/3368.html
如果您觉得超哥分享对您有所帮助的话,记得打赏给我😀