为了提高PHP-FPM的性能进行优化
PHP无处不在,可以说是互联网上应用最广泛的语言。
然而,它在高并发系统方面的性能能力并不出众。这就是为什么对于这种专业用例,像Node(是的,我知道,它不是一种语言),Go和Elixir这样的语言正在取而代之的原因。
话虽如此,你可以做很多事情来改善服务器上的PHP performance。本文重点介绍了事务处理器端(php-fpm)的优化,这是在使用Nginx时在服务器上进行配置的自然方式。
如果你知道什么是php-fpm
,可以跳到优化部分。
什么是PHP-fpm?
很少有开发人员对链接_2>等事务处理器端感兴趣,即使在那些感兴趣的开发人员中,对底层的了解也很少。有趣的是,当浏览器向运行PHP的服务器发送请求时,PHP不是形成第一个接触点的,而是HTTP服务器,其中最重要的是Apache和Nginx。这些“Web服务器”必须决定如何连接到PHP,并将请求类型、数据和请求头传递给它。
在现代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-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.log
是php-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,它可以负责性能优化和安全性。