優化 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,並將請求類型、數據和標頭傳遞給它。

請求-響應-in-php-e1542398057451
PHP 的請求-響應周期(圖片來源:ProinerTech)

在現代 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 和 nginx
PHP 和 Nginx 如何協同工作(圖片來源:DataDog)

到目前為止還不錯,但現在出現了一個價值百萬美元的問題: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這樣負責性能優化和安全性的可靠平台。

新媒體運營,新媒體代運營,cloudneo

原創文章,作者:超哥,如若轉載,請註明出處:https://www.chaoneo.cn/zh-hant/archives/3368.html

0 0 投票
五星評級

如果您覺得超哥分享對您有所幫助的話,記得打賞給我😀

訂閱
提醒
guest
0 評論
內聯反饋
查看所有評論
QQ客服
加我微信
電話聯繫
電子郵件
我們將24小時內回復。
取消
0
喜歡你的想法,請評論x
()
x