PHP 技能精進之 PHP-FPM 多進程模型

PHP-FPM 提供了更好的 PHP 進程管理方式,能夠有效控制內存和進程、能夠平滑重載PHP配置。那麼當咱們談論 PHP-FPM 多進程模型的時候,做爲 PHPer 的你瞭解多少呢?php

首先,讓咱們一塊兒看幾個問題:數據庫

①:PHP-FPM 啓動進程的方式主要有哪幾種,區別是什麼?緩存

②:PHP-FPM,是主進程接收請求轉給子進程,仍是子進程單獨接收請求並處理,如何驗證?服務器

③:爲什麼在 PHP-FPM 模式下,PHP 代碼不多有人去作鏈接池?網絡

④:PHP-FPM 模式性能差的體現有哪些,如何優化?併發

⑤:PHP-FPM 模式下的 YAC 爲什麼沒法和 CLI 模式沒法共享內存?memcached

1. 如何啓動進程函數

PHP-FPM 是多進程模式,由 Master 進程管理 Worker 進程。進程的數量,均可以經過 php-fpm.conf 作具體配置。 PHP-FPM 的進程能夠分爲動態模式及靜態模式:高併發

①:靜態(Static)php-fpm

直接開啓指定數量的 PHP-FPM 進程,再也不增長或者減小;啓動固定數量的進程,佔用內存高。但在用戶請求波動大的時候,對 Linux 操做系統進程的處理上耗費的系統資源低。

②:動態(Dynamic)

開始時開啓必定數量的 PHP-FPM 進程,當請求量變大的時候,動態增長 PHP-FPM 進程數到上限,當空閒的時候自動釋放空閒進程數到一個下限。

動態模式會根據 max、min、idle children 配置,動態的調整進程數量。在用戶請求較爲波動,或者瞬間請求增高的時候,動態模式下會進行大量進程的建立、銷燬等操做,而形成 Linux 負載波動升高。簡單來講,請求量少,PHP-FPM 進程數少,請求量大,進程數多。優點就是,當請求量小的時候,進程數少,內存佔用也小。

③:按需 (Ondemand)

這種模式下,PHP-FPM 的 Master 不會 Fork 任何子進程,純粹就是按需啓動。

這種模式一般不多使用,由於它基本沒法適應有必定量級的線上業務。因爲 php-fpm 是短鏈接的,因此每次請求都會先創建鏈接,創建鏈接的過程必然會觸發上圖的執行步驟。因此,在大流量的系統上 Master 進程會變得繁忙,佔用系統 CPU 資源,不適合大流量環境的部署。

借用一張網絡圖片來講明:

須要注意 2 個點,「鏈接」和「數據」到來。有鏈接進來再 Fork 進程,一樣能夠達到子進程繼承父進程上下文,而後子進程處理用戶請求這個目的。

(關於動態、靜態進程模式的相關參數,可參考 PHP 官方文檔。)

咱們須要關注的是對於咱們自身的業務,應該選擇的 PHP-FPM 模式爲動態仍是靜態。

一般來講,對於比較大內存的服務器,設置爲靜態的話會提升效率。由於頻繁開關 php-fpm 進程也會有時滯,因此內存夠大的狀況下開靜態效果會更好。數量也能夠根據 內存/30M 獲得。好比說 2GB 內存的服務器,能夠設置爲 50;4GB 內存能夠設置爲 100 等。高配機器選靜態,低配機器(省內存)選動態,高配機器用動態不能充分利用內存資源和 CPU 資源,也沒法及時應對瞬時高併發。

2. 如何進行請求處理和驗證

PHP-FPM 的進程管理方式和 Nginx 的進程管理方式有些相似。在處理請求時,並不是由主進程接受請求後轉給子進程,而是子進程「搶佔式」地接受用戶請求。本質上 PHP-FPM 多進程以及 Nginx 多進程,都是在主進程監聽同一個端口後,Fork 子進程達到多個進程監聽同一端口的目的。

Linux 系統全部的進程 IO 操做,都須要和操做系統打交道。也就是說,系統知道全部 IO 操做。這個過程就是咱們常說的「系統調用」。咱們能夠從系統調用入手解決這個問題。系統調用的查看,可使用 Strace。

如何驗證相對簡單,咱們能夠採起 2 種方式:

  • 看 PHP-FPM 進程的日誌。這須要配置好合適的 PHP-FPM 日誌格式;
  • 既然 IO 數據會經過內核態過分到用戶態進程,咱們能夠經過 strace -p <pid> 命令去跟蹤系統調用。分別跟蹤 PHP-FPM 的主進程 ID 以及子進程 ID,而後訪問 Nginx,由 Nginx 經過 fast-cgi 協議轉到 PHP-FPM 進程上,看在哪一個進程上發送了系統調用。

3. 爲什麼不在 PHP-FPM 下作代碼鏈接池 ?

首先,在 PHP-FPM 模式下,一個請求的生命週期註定只有 1 次。也就是說,從 FPM 請求到請求、解析 PHP 腳本,到 FPM 的 Zend 虛擬機分配資源執行,再到最後的處理結束,PHP-FPM 會回收此次請求的全部資源。

這種方式一是爲了讓開發不須要關心資源的回收處理,因此你可能沒怎麼關心過網絡的關閉、文件描述符的關閉等等。二是爲了減小內存溢出的狀況。

若是在這種模式下,你實現了鏈接池,也意味着請求結束,鏈接池消失,作了一次無用功而已。

「雞肋的」PConnect(持久化連接)。持久化連接也就是連接不釋放。但問題在於,PHP-FPM 是多進程模式,而持久化的連接存在於進程中。這就意味着,若是一臺機器有 300 個 FPM 進程,會一次性初始化 300 個持久化連接。若是由於面臨業務活動需求冒然對機器擴容,極可能形成業務的數據庫鏈接數直接打滿。

4. 如何優化性能

首先,咱們應該思考致使性能差可能的緣由是什麼。若是一個應用的性能差,咱們每每會從 2 個方面來分析,一個是 IO 性能,一個是計算性能。

IO 方面,由於 PHP-FPM 模式下難以作鏈接池,因此高併發業務下的網絡處理會有劣勢。注意我這裏一直說的都是 PHP-FPM 模式下,在 CLI 模式下仍是能夠本身作鏈接池的。只不過這個鏈接池僅限於 CLI 模式的單進程內,並且這個模式不能用於處理網絡請求(好比 HTTP 請求)。由於 PHP 默認單進程模式,FPM、CLI 都是默認單進程,即使 CLI 能夠作鏈接池 ,也不方便作連接保活(不能同時作心跳檢測)。

計算性能上來講,雖然 PHP 是用 C 寫的,若是單純論計算性能是不錯的。但問題在於 PHP 處理請求時,每次都要解析 PHP 腳本、翻譯 PHP 代碼爲 Opcode、用 Zend 虛擬機執行 Opcode,處理結束,釋放資源。經歷這樣的過程 是致使 PHP 計算性能慢的最大緣由之一。

如何優化:

  • 對於計算性能來講,使用 Zend OPcache 擴展,緩存字節碼。
  • 對於** IO 性能**來講,使用文件 cache 或者 memcached 減輕對網絡 Cache 的壓力;使用 Yac 減輕對 Cache 層的壓力;在同一次請求中;複用連接不要每次都用新的;合理設計日誌組件類庫,優化 Logger 減小對文件操做的次數來減小 IO 的壓力。

關於設計一個合格的 Logger 組件,咱們須要注意幾個點:

① 每次請求,只作一第二天志寫操做,不要每次別人調用你的函數,你都去執行一次相似 file_put_contents 的操做。

② 兼容各類相似錯誤。換句話說,即便 PHP fatal error 了,你也得能把知名錯誤以前的日誌記錄下來。這個實現能夠藉助 PHP 類的析構方法來作。也可使用更好的 register_shutdown_function 來註冊一個鉤子,在 PHP 請求結束的時候,回調此鉤子,完成作最後的日誌操做。

5. YAC 爲什麼沒法和 CLI 模式共享內存

咱們知道,PHP 擴展開發中首要執行的一個宏是 PHP_MINIT_FUNCTION。YAC 擴展須要在 PHP-FPM 進程啓動時起就初始化一塊共享內存,供各個進程來共享使用。所以,實現共享的關鍵在於須要一個讓各個進程都知道的相同標識。

YAC 擴展的初始化流程爲:

咱們查看 create_segments 的具體實現:

上面作了一些註釋,最關鍵的是要開啓共享內存須要的系統 ID,shared_segment_name,此值包含了進程 ID。也就是 PHP-FPM 的主進程 ID。有相同的共享內存標識 ID,就是 PHP-FPM 模式全部進程間可以通訊的奧祕所在。而若是咱們是想要經過 PHP 腳本使用 yac 擴展讀取這個共享內存,會這樣作:

在 CLI 模式下,這樣是不可能拿到 PHP-FPM 模式下設置的共享內存數據的。由於 CLI 模式下執行 PHP 腳本、進程 ID,和 PHP-FPM 模式下的進程 ID 徹底不相同。

後面的文章中,咱們會找機會講一講進程間通信,以及基於共享內存的通信。總結來講,多進程要共享內存通訊,必需要一開始就協調好一個惟一 ID。這個 ID 多個進程間都要知道。PHP-FPM 是多進程,主進程 fork 子進程出來,子進程天然知道這個惟一 ID 是什麼(由於 Linux 進程 fork 會把整個進程的堆棧內存都 fork 一遍)。可是,php a.php 這樣執行,實際上是一個徹底獨立的進程,和 PHP-FPM 沒任何關係,這樣的進程,也就不能知道 PHP-FPM 進程裏的那個惟一 ID 是什麼。

本文做者:董紅帥,馬蜂窩系統部研發工程師。

關注馬蜂窩技術,找到更多你想要的內容

相關文章
相關標籤/搜索