優化 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/zh-hant/archives/3368.html
如果您覺得超哥分享對您有所幫助的話,記得打賞給我😀