为了提高PHP-FPM的性能进行优化

PHP无处不在,可以说是互联网上应用最广泛的语言。

然而,它在高并发系统方面的性能能力并不出众。这就是为什么对于这种专业用例,像Node(是的,我知道,它不是一种语言),Go和Elixir这样的语言正在取而代之的原因。

话虽如此,你可以做很多事情来改善服务器上的PHP performance。本文重点介绍了事务处理器端(php-fpm)的优化,这是在使用Nginx时在服务器上进行配置的自然方式。

如果你知道什么是php-fpm,可以跳到优化部分。

什么是PHP-fpm?

很少有开发人员对链接_2>等事务处理器端感兴趣,即使在那些感兴趣的开发人员中,对底层的了解也很少。有趣的是,当浏览器向运行PHP的服务器发送请求时,PHP不是形成第一个接触点的,而是HTTP服务器,其中最重要的是Apache和Nginx。这些“Web服务器”必须决定如何连接到PHP,并将请求类型、数据和请求头传递给它。

PHP的请求-响应循环(图片来源:ProinerTech)

在现代PHP应用程序中,上面的“查找文件”部分是index.php,服务器被配置为将所有请求委托给它。

现在,Web服务器如何连接到PHP已经发生了变化,如果我们要详细介绍所有细节,这篇文章将会非常冗长。但是,粗略地说,在Apache主导作为首选Web服务器的时代,PHP是作为服务器内部包含的模块。因此,每当接收到请求时,服务器将启动一个新的进程,该进程将自动包含PHP并执行它。这种方法被称为mod_php,即“PHP作为模块”。这种方法有其局限性,Nginx则通过php-fpm克服了这些局限性。

php-fpm中,管理PHP进程的责任属于服务器内的PHP程序。换句话说,Web服务器(在我们的例子中是Nginx)不关心PHP位于何处以及如何加载它,只要它知道如何与PHP发送和接收数据即可。如果愿意,你可以认为在这种情况下,PHP是另一个服务器,为传入的请求管理一些子PHP进程(所以,我们有一个请求到达服务器,由一个服务器接收并传递给另一个服务器 – 这相当疯狂! :-P)。

如果你已经进行过任何Nginx设置,或者甚至只是探究过它们,你会遇到像这样的东西:

location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

我们感兴趣的是这一行:fastcgi_pass unix:/run/php/php7.2-fpm.sock;,它告诉Nginx通过名为php7.2-fpm.sock的套接字与PHP进程通信。因此,对于每个传入的请求,Nginx通过此文件写入数据,并在接收到输出后将其发送回浏览器。

我必须再次强调,这不是对所发生的一切最完整或最准确的描述,但对于大多数DevOps任务来说,它完全准确。

除此之外,让我们回顾一下我们目前学到的内容:

  • PHP不直接接收浏览器发送的请求。像Nginx这样的Web服务器首先拦截这些请求。
  • Web服务器知道如何连接到PHP进程,并将所有请求数据(字面上粘贴到所有内容)传递给PHP。
  • 当PHP完成自己的工作后,它将响应发送回Web服务器,Web服务器将其发送回客户端(或浏览器,在大多数情况下)。

或者图形化表示:

PHP和Nginx如何协同工作(图片来源:DataDog)

目前为止很好,但现在出现了一个关键问题:PHP-FPM到底是什么?

PHP中的“FPM”部分代表“快速进程管理器”,这只是一种说法,即运行在服务器上的PHP不是一个单独的进程,而是由此FPM进程管理器生成、控制并终止的一些PHP进程。就是这个进程管理器将请求传递给Web服务器。

PHP-FPM本身就是一个完整的领域,所以如果您愿意,可以随意探索,但对于我们的目的来说,这样的解释就足够了。🙂

为什么要优化PHP-FPM?

那么,当一切正常工作时,为什么要担心所有这些操作呢?为什么不保持现状。

具有讽刺意味的是,对于大多数用例,这正是我提供的建议。如果您的配置正常工作且没有特殊的用例,使用默认设置即可。但是,如果您希望扩展到多台机器,那么从一台机器中挤出最大价值非常重要,因为这样可以将服务器费用减少一半(甚至更多!)。

另一个需要意识到的事实是,Nginx是用于处理大量工作负载的。它能够同时处理成千上万个连接,但如果您的PHP配置不具备相同的能力,那么Nginx只能等待PHP完成当前进程并接受下一个进程,从而消耗资源,从而消除了Nginx提供的任何优势!

因此,现在我们来看看在尝试优化php-fpm时需要改变的内容。

如何优化PHP-FPM?

php-fpm的配置文件位置可能因服务器而异,因此您需要进行一些研究以查找它。您可以使用find command(如果在UNIX上)。在我的Ubuntu上,路径是/etc/php/7.2/fpm/php-fpm.conf。当然,7.2是我运行的PHP版本。

这个文件的前几行如下:

;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;

; All relative paths in this configuration file are relative to PHP's install
; prefix (/usr). This prefix can be dynamically changed by using the
; '-p' argument from the command line.

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;

[global]
; Pid file
; Note: the default prefix is /var
; Default Value: none
pid = /run/php/php7.2-fpm.pid

; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /var
; Default Value: log/php-fpm.log
error_log = /var/log/php7.2-fpm.log

有几件事应立即显而易见:pid = /run/php/php7.2-fpm.pid这一行告诉我们哪个文件包含php-fpm进程的进程ID。

我们还看到/var/log/php7.2-fpm.logphp-fpm将存储其日志的位置。

在这个文件中,添加三个更多的变量,如下所示:

emergency_restart_threshold 10
emergency_restart_interval 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')替换。
[www]

;每个进程池的前缀
;它只适用于以下指令:
;- 'access.log'
;- 'slowlog'
;- 'listen'(unixsocket)
;- 'chroot'
;- 'chdir'
;- 'php_values'
;- 'php_admin_values'
;当未设置时,将使用全局前缀(或/usr)。
;注意:此指令也可以相对于全局前缀。
;默认值:无
;prefix = /path/to/pools/$pool

;进程的Unix用户/组
;注意:用户是必需的。如果未设置组,将使用默认用户组。
user = www-data
group = www-data

快速查看上面片段的末尾可以解开服务器进程以www-data身份运行的谜题。如果在设置网站时遇到文件权限问题,您可能已经将目录的所有者或组更改为www-data,从而使PHP进程能够写入日志文件和上传文档等。

最后,我们来到问题的源头,进程管理器(pm)设置。通常,您会看到默认值如下:

pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

那么,这里的“dynamic”是什么意思?我认为官方文档最好地解释了这一点(我的意思是,这应该已经是您正在编辑的文件的一部分了,但我在这里重复了一遍以防万一):

;选择进程管理器将如何控制子进程的数量。
; 可能的值:
;   static  - 固定数量(pm.max_children)的子进程;
;   dynamic - 子进程的数量根据以下指令动态设置。使用此进程管理,始终至少有一个子进程。
;             pm.max_children      - 同一时间可以存活的最大子进程数。
;             pm.start_servers     - 启动时创建的子进程数。
;             pm.min_spare_servers - '空闲'状态(等待处理)的子进程的最小数量。如果'空闲'进程数少于此数,将创建一些子进程。
;             pm.max_spare_servers - '空闲'状态(等待处理)的子进程的最大数量。如果'空闲'进程数大于此数,将杀死一些子进程。
;  ondemand - 在启动时不创建子进程。新请求连接时将派生子进程。使用以下参数:
;             pm.max_children           - 同一时间可以存活的最大子进程数。
;             pm.process_idle_timeout   - 空闲进程将被终止的秒数。
; 注:此值是必需的。

因此,我们可以看到有三个可能的值:

  • Static:无论如何,将维护一定数量的PHP进程。
  • Dynamic:我们可以指定php-fpm在任何给定时间内保持活动的最小和最大进程数。
  • ondemand:进程根据需要创建和销毁。

那么,这些设置有什么影响呢?

简单来说,如果您的网站流量较低,大部分时间使用“dynamic”设置都是浪费资源。假设您将pm.min_spare_servers设置为3,即使网站没有流量,仍会创建并维护三个PHP进程。在这种情况下,“ondemand”是更好的选择,让系统决定何时启动新的进程。

另一方面,在这种设置下,需要快速响应的网站将受到惩罚。创建一个新的PHP进程,使其成为一个进程池的一部分,并监控它,这是额外的开销,最好避免。

使用pm = static可以固定子进程的数量,充分利用系统资源来处理请求,而不是管理PHP。如果您选择这种方式,请注意它有其指导方针和陷阱。关于此的一篇相当深入但非常有用的文章是[链接5]。

最后的话

由于关于网站性能的文章可能引起争议或混淆读者,因此在结束本文之前,我觉得有必要简单说明一下。性能调优既需要猜测也需要黑魔法,同时还需要系统知识。

即使您对php-fpm的所有设置了如指掌,也不能保证成功。如果您对php-fpm的存在一无所知,那么您不需要浪费时间去担心它。只需继续做您已经在做的事情,并继续前进即可。

同时,避免成为一个追求性能的狂热者。是的,你可以通过重新编译PHP并删除所有不需要的模块来获得更好的性能,但在生产环境中,这种方法并不明智。优化的整个思想是要看一下你的需求是否与默认值不同(它们很少不同!),然后根据需要进行微小的更改。

如果你没有准备好花时间来优化你的PHP服务器,那么你可以考虑利用一个可靠的平台,如Kinsta,它可以负责性能优化和安全性。

类似文章