PHP-FPM三種運行模式

學習總結

  • static 靜態模式,啓動的時候創建固定數量的worker 進程,實際請求大於worker進程的時候 包warning

  • ondemand 按需分配模式,啓動的時候不會創建worker進程,根據需要創建,釋放在idle_timeout之後

    ​ 這樣不能及時的釋放連接和建立連接需要消耗資源

  • dynamic 動態模式(默認):啓動的時候創建指定數量的worker進程,根據情況合理的worker,定期檢查worker,關閉閒置連接

PHP-FPM & FastCGI

  1. PHP-FPM(FastCGI Process Manager)是一個PHPFastCGI進程管理器,從其英文名稱和定義可以看出,FPM的核心功能就是進程管理。

  2. FastCGI可以理解爲一種協議,用於web服務器(nginx、Apache)和處理程序間進行通信,是一種應用層通信協議。

  3. 工作原理大致如下圖
    在這裏插入圖片描述

fpm的基本實現

  1. fpm創建master進程,在master進程中創建work pool並監聽socket,然後fork出多個子進程(work),這些work在啓動後阻塞fcgi_accept_request()上,各自accept請求,有請求到達後worker開始讀取請求數據,讀取完成後開始處理然後再返回,在這期間是不會接收其它請求的,也就是說fpm的子進程同時只能響應一個請求只有把這個請求處理完成後纔會accept下一個請求。
  2. fpm的master與work進程間不會直接通訊,master通過共享內存獲取worker進程的信息,比如worker進程當前狀態、已處理請求數等,master要殺死worker進程也是通過發送信號
  3. pm可以同時監聽多個端口,每個端口對應一個worker pool,而每個pool下對應多個worker進程,類似nginx中server概念, 在php-fpm.conf中可以配置多個,例如:
    [web1]
    listen:127.0.0.1:9000
    [web2]
    listen:127.0.0.1:9001
    在這裏插入圖片描述

php生命週期

PHP在web方式中如何改了文件就立即生效的,重要的幾個概念:

● sapi: 可以簡單的理解爲php引擎對外的一個統一接口,使得php可以和外部程序進行交互

● php的生命週期中關鍵四個調用: MINT -> RINT -> RSHUTDOWN -> MSHUTDOWN

數據初始化(mint)==》請求初始化(rint) ==》編譯腳本(rshuntdown) ==》執行代碼(mshutdown)

● fpm: fastcgi進程管理器

FPM 流程

fpm通過sapi接口與php進程交互

1.fpm啓動會調用各擴展的MINT方法,進行一些數據初始化(長駐內存)

2.每個請求過來,先會執行RINT對單個請求行一個初始化

3.執行php腳本(在沒有緩存opcode的情況下,這裏的php腳本是動態執行的,所以更新php腳本後,會執行新的php腳本,詳情不在這裏敘述)

4.執行RSHUTDOWN方法

5.如果你要停止fpm了,纔會執行MSHUTDOWN

fpm對每個請求的處理都是一直在在重複執行 2~4步,在第三步中,php的腳本是動態執行的,由於每次都要執行一次php腳本,而每次php腳本都要有一個把php文件翻譯成opcode的流程(比較耗時), 於是就產生的opcache工具。

opcache

直接把php翻譯後的opcode代碼樹保存到共享內存中,以便直接使用,從而減少每次都把php翻譯成opcode的開銷。

opcache的問題: 按照他的描述,修改了php文件,並不能立即被更新。

opcache的解決方案: 有一個配置來設置隔多長時間檢測文件是否更新了,從而有機會在第二步重新來reload相關的文件。

當然,直接reload fpm,從而達到php熱更新的效果(opcache擴展可以在第四步把相關的opcode cache給清空)。

work 工作流程

worker的工作流程包含以下幾個步驟

  1. 等待請求:fcgi_accept_request()阻塞等待請求
  2. 接收請求:fastcgi請求到達後被worker接收並解析,一直到完全接收,然後將method、query、uri等信息保存到worker進程的fpm_scoreboard_proc_s結構中
  3. 初始化請求:php_request_startup()執行,此步驟會調用每個擴展的PHP_RINIT_FUNCTION方法,初始化一些操作
  4. 處理請求(編譯、執行):php代碼編譯執行階段,由 php_execute_script方法完成
  5. 關閉請求:返回響應,執行php_request_shutdown方法關閉請求,然後進入第一步繼續等待請求,此步驟會執行每個擴展的PHP_RSHUTDOWN_FUNCTION進行一些收尾工作
int main(int argc, char *argv[])
{
    ...變量定義,參數初始化
    //註冊SAPI
    sapi_startup(&cgi_sapi_module);
    ...
    //執行php_module_starup()
    if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
        return FPM_EXIT_SOFTWARE;
    }
    //初始化
    if(0 > fpm_init(...)){
        //記錄日誌並退出
        return FPM_EXIT_CONFIG;
    }
    ...
    fpm_is_running = 1;//fpm運行狀態標識
    fcgi_fd = fpm_run(&max_requests);//進程初始化,調用fork()創建work進程
     ...
    fcgi_init_request(&request, fcgi_fd); //初始化請求;
    //此階段的php_request_startup()會調用每個擴展的:PHP_RINIT_FUNCTION();
    if (UNEXPECTED(php_request_startup() == FAILURE)) {
    ...
    }
    ...
    php_fopen_primary_script(&file_handle TSRMLS_CC); //打開腳本; 
    ...
    php_execute_script(&file_handle TSRMLS_CC); //執行腳本; 
    ...
    //worker進程退出
    php_module_shutdown();
    ...
}

PHP-FPM運行三種模式

PHP7 默認是

pm=dynamic
pm.max_children=50

fpm.conf 參數

  • pm.start_servers = 5

    動態方式下的起始php-fpm進程數量

    只有當pm的配置爲dynamic時候纔會有效如果爲static,會忽略此參數

    php啓動的時候,開啓子進程的數量。具體的子進程的數量會根據請求的變化發生變化,但是最大不會超過pm.max_children配置的數值。

    min_spare_servers + (max_spare_servers - min_spare_servers) / 2;
    一般而言,設置成10-20之間的數據足夠滿足需求了。

  • pm.max_children = 50

    表示php-fpm能啓動的子進程的最大數

    static下只有這一個參數生效

    計算方式

    一般來說一臺服務器正常情況下每一個php-cgi所耗費的內存在20M~30M左右,因此我的”max_children”我設置成40個,20M*40=800M也就是說在峯值的時候所有PHP-CGI所耗內存在800M以內,低於我的有效內存2Gb。

    而如果我 的”max_children”設置的較小,比如5-10個,那麼php-cgi就會“很累“,處理速度也很慢,等待的時間也較長,佔用的CPU也很高。

    如果長時間沒有得到處理的請求就會出現 504 Gateway Time-out 這個錯誤,而正在處理的很累的那幾個php-cgi如果遇到了問題就會出現 502 Bad gateway 這個錯誤。

    max_children較好的設置方式根據req/s(吞吐率,單位時間裏服務器處理的最大請求數,單位req/s)來設置,若程序是 100 req/s 的處理能力,那麼就設置 100比較好,這是動態來調整的。

  • pm.max_spare_servers

    動態方式下空閒狀態最大php-fpm數量

    pm.max_spare_servers的值只能小於等於pm.max_children

    系統會在php-fpm運行開始時啓動pm.start_servers個php-fpm進程,然後根據系統的需求動態在pm.min_spare_servers和pm.max_spare_servers之間調整php-fpm進程數。

  • pm.process_idle_timeout = 10s

    worker 空閒多少秒之後被kill,按需分配模式dynamic

    默認10s

  • pm.max_requests=500

    每個進程處理多少個請求之後自動終止,可以有效防止內存溢出,如果爲0則不會自動終止,默認爲0#設置每個子進程重生之前服務的請求數. 對於可能存在內存泄漏的第三方模塊來說是非常有用的. 如果設置爲 ‘0’ 則一直接受請求. 等同於 PHP_FCGI_MAX_REQUESTS 環境變量. 默認值: 0

  • pm.status_path

    註冊的URI,以展示php-fpm狀態的統計信息

    在php-fpm.conf中打開
    在nginx中配置
    server {
        ......
    	
        # 在 server 中添加以下配置
        location = /status {
    	include fastcgi_params;
    	fastcgi_pass 127.0.0.1:9000;
    	fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
        }
    	
        .....
    }
    

    在這裏插入圖片描述

    pool – fpm池子名稱,大多數爲www
    process manager – 進程管理方式,值:static, dynamic or ondemand. dynamic
    start time – 啓動日期,如果reload了php-fpm,時間會更新
    start since – 運行時長
    accepted conn – 當前池子接受的請求數
    listen queue – 請求等待隊列,如果這個值不爲0,那麼要增加FPM的進程數量
    max listen queue – 請求等待隊列最高的數量
    listen queue len – socket等待隊列長度
    idle processes – 空閒進程數量
    active processes – 活躍進程數量
    total processes – 總進程數量
    max active processes – 最大的活躍進程數量(FPM啓動開始算)
    max children reached - 進程最大數量限制的次數,如果這個數量不爲0,那說明你的最大進程數量太小了,請改大一點。
    slow requests – 啓用了php-fpm slow-log,緩慢請求的數量

  • ping.path

    ping url,可以用來測試php-fpm是否存活並可以響應

  • ping.response
    ping url的響應正文返回爲 HTTP 200 的 text/plain 格式文本. 默認值: pong.
    ping.response = pong
    11)pid = run/php-fpm.pid
    #pid設置,默認在安裝目錄中的var/run/php-fpm.pid,建議開啓

  • error_log = log/php-fpm.log
    #錯誤日誌,默認在安裝目錄中的var/log/php-fpm.log

  • log_level = notice
    #錯誤級別. 可用級別爲: alert(必須立即處理), error(錯誤情況), warning(警告情況), notice(一般重要信息), debug(調試信息). 默認: notice.

  • emergency_restart_threshold = 60
    emergency_restart_interval = 60s
    #表示在emergency_restart_interval所設值內出現SIGSEGV或者SIGBUS錯誤的php-cgi進程數如果超過 emergency_restart_threshold個,php-fpm就會優雅重啓。這兩個選項一般保持默認值。

  • process_control_timeout = 0
    #設置子進程接受主進程複用信號的超時時間. 可用單位: s(秒), m(分), h(小時), 或者 d(天) 默認單位: s(秒). 默認值: 0.

  • daemonize = yes
    #後臺執行fpm,默認值爲yes,如果爲了調試可以改爲no。在FPM中,可以使用不同的設置來運行多個進程池。 這些設置可以針對每個進程池單獨設置。

  • listen = 127.0.0.1:9000
    #fpm監聽端口,即nginx中php處理的地址,一般默認值即可。可用格式爲: ‘ip:port’, ‘port’, ‘/path/to/unix/socket’. 每個進程池都需要設置.

  • listen.backlog = -1
    #backlog數,-1表示無限制,由操作系統決定,此行註釋掉就行。 19)listen.allowed_clients = 127.0.0.1
    #允許訪問FastCGI進程的IP,設置any爲不限制IP,如果要設置其他主機的nginx也能訪問這臺FPM進程,listen處要設置成本地可被訪問的IP。默認值是any。每個地址是用逗號分隔. 如果沒有設置或者爲空,則允許任何服務器請求連接
    listen.owner = www
    listen.group = www
    listen.mode = 0666

  • #unix socket設置選項,如果使用tcp方式訪問,這裏註釋即可。
    user = www
    group = www
    #啓動進程的帳戶和組

  • request_terminate_timeout = 0
    #設置單個請求的超時中止時間. 該選項可能會對php.ini設置中的’max_execution_time’因爲某些特殊原因沒有中止運行的腳本有用. 設置爲 ‘0’ 表示 ‘Off’.當經常出現502錯誤時可以嘗試更改此選項。

  • request_slowlog_timeout = 10s
    #當一個請求該設置的超時時間後,就會將對應的PHP調用堆棧信息完整寫入到慢日誌中. 設置爲 ‘0’ 表示 ‘Off’

  • slowlog = log/$pool.log.slow
    #慢請求的記錄日誌,配合request_slowlog_timeout使用

  • rlimit_files = 1024
    #設置文件打開描述符的rlimit限制. 默認值: 系統定義值默認可打開句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。

  • rlimit_core = 0
    #設置核心rlimit最大限制值. 可用值: ‘unlimited’ 、0或者正整數. 默認值: 系統定義值.

  • chroot =
    #啓動時的Chroot目錄. 所定義的目錄需要是絕對路徑. 如果沒有設置, 則chroot不被使用.

  • chdir =
    #設置啓動目錄,啓動時會自動Chdir到該目錄. 所定義的目錄需要是絕對路徑. 默認值: 當前目錄,或者/目錄(chroot時)

  • catch_workers_output = yes
    #重定向運行過程中的stdout和stderr到主要的錯誤日誌文件中. 如果沒有設置, stdout 和 stderr 將會根據FastCGI的規則被重定向到 /dev/null . 默認值: 空.

static 靜態模式

php-fpm啓動的時候,創建固定的worker數量

該模式比較簡單,在啓動時按照配置pm.max_children啓動固定數量的的進程,這些進程阻塞進行請求的接收
方法執行流程:
fpm_run()->fpm_children_create_initial()->fpm_children_make()
啓動fpm的時候會調用fpm_run方法,而fpm_run方法內部會調用子進程初始化方法fpm_children_create_initial,
在該方法內部會調用fpm_children_make方法創建worker進程。

php-fpm啓動採用固定大小數量的worker,在運行期間也不會擴容,雖然也有1秒的定時器,僅限於統計一些狀態信息,例如空閒worker個數,活動worker個數,網絡連接隊列長度等信息。

配置

pm=static
pm.max_children=3

static的時候只有這個參數生效

在這裏插入圖片描述
方法執行流程:
fpm_run()->fpm_children_create_initial()->fpm_children_make()
啓動fpm的時候會調用fpm_run方法,而fpm_run方法內部會調用子進程初始化方法fpm_children_create_initial,
在該方法內部會調用fpm_children_make方法創建worker進程

原理

img

配置項要求

1、pm.max_children> 0 必須配置,且只有這一個參數生效

優缺點

如果配置成static,只需要考慮max_children的數量,數量取決於cpu的個數和應用的響應時間,我司配置的是50。

我司不考慮動態的增加減少那麼十幾個或者幾十個worker,我們的內存沒有緊張到這個程度,所以,我們一步到位,把worker數配置到支持最大流量,(哈哈,50也是隨便定的,足矣足矣呢)

ondemand 按需分配模式

pm = ondemand
pm.process_idle_timeout = 60 #worker 空閒多少秒之後被kill,按需分配模式dynamic
pm.max_children=3

在這裏插入圖片描述

原理

img

ondemand原理圖

\1. 從上圖可以看出,新建worker的觸發條件是連接的到來,而不是實際的請求(例如,只進行連接比如telnet,不發請求數據也會新建worker)

\2. worker的數量受限於pm.max_children配置,同時受限全局配置process.max(準確的說,三種模式都受限於全局配置)

3.1秒定時器作用

找到空閒worker,如果空閒時間超過pm.process_idle_timeout大小,關閉。這個機制可能會關閉所有的worker。

配置項要求

\1. pm.max_children> 0

\2. pm.process_idle_timeout> 0,如果不設置,默認10s

優缺點

優點:按流量需求創建,不浪費系統資源(在硬件如此便宜的時代,這個優點略顯雞肋)

缺點:由於php-fpm是短連接的,所以每次請求都會先建立連接,建立連接的過程必然會觸發上圖的執行步驟,所以,在大流量的系統上master進程會變得繁忙,佔用系統cpu資源,不適合大流量環境的部署

代碼流程

ondemand模式,運行流程和第一步相同,不同之處是在第二個函數中不會分配work進程,而是註冊了一個事件回調函數fpm_pctl_on_socket_accept(),部分代碼如下:

if (wp->config->pm == PM_STYLE_ONDEMAND) { 
 	wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s));   
  	......   
 	memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));    
  	fpm_event_set(wp->ondemand_event,wp->listening_socket,FPM_EV_READ|FPM_EV_EDGE,fpm_pctl_on_socket_accept, wp);    
  	......
}

以上代碼片段只保留了部分本文所需要的內容

ondemand模式work進程的創建,回調函數fpm_pctl_on_socket_accept()的部分代碼如下:

if (wp->running_children >= wp->config->pm_max_children) {   //判斷進程數是否超過最大限制
       ......    
       return;
}
for (child = wp->children; child; child = child->next) { 
   //fpm_request_is_idle函數返回return proc->request_stage == FPM_REQUEST_ACCEPTING
   if (fpm_request_is_idle(child)) { 
       return;   // FPM_REQUEST_ACCEPTING代表處於等待請求階段
 }
}
...... 
fpm_children_make(wp, 1, 1, 1);//創建work進程

ondemand模式work進程的關閉
PFM註冊了一個定時事件fpm_pctl_perform_idle_server_maintenance_heartbeat檢查當前模式下work進程的運行情況,當空閒進程等待請求時間超過pm_process_idle_timeout後,會對最後一個空閒worker進程發出關閉信號,此操作由主進程進行處理,部分代碼如下:

if (wp->config->pm == PM_STYLE_ONDEMAND) {
    struct timeval last, now;    
    if (!last_idle_child) continue;//最後一個idle進程
     ......
    // last.tv_sec爲上次接收請求的時間
    if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
        last_idle_child->idle_kill = 1;
        fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
    }
    continue;
}

dynamic模式:動態模式

dynamic模式,啓動時分配固定數量的work進程,然後隨着請求的增加會增加進程數,此模式下幾個重要的配置項如下:

max_children 最大進程數
pm_max_spare_servers 允許最大的空閒進程數
min_spare_servers 允許最小的空閒進程數
start_servers 啓動時的進程數

執行過程和ondemand模式類似,啓動時主進程都會創建一個定時事件來定時檢查work的運行狀況,不同的是dynamic模式初始化的時候會創建一定數量的進程,而ondemand模式不會創建

配置

pm = dynamic
pm.max_children = 3
pm.start.servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 6

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZehrV8gR-1579172905068)(PHP-FPM.assets/image-20200116174736466.png)]

原理

img

dynamic原理圖

\1. 1秒定時器作用

檢查空閒worker數量,按照一定策略動態調整worker數量,增加或減少。增加時,worker最大數量<=max_children· <=全局process.max;減少時,只有idle >pm.max_spare_servers時纔會關閉一個空閒worker。

idle > pm.max_spare_servers,關閉啓動時間最長的一個worker,結束本次處理

idle >= pm.max_children,打印WARNING日誌,結束本次處理

idle < pm.max_children,計算一個num值,然後啓動num個worker,結束本次處理

配置項要求

\1. pm.min_spare_servers/pm.max_spare_servers有效範圍(0,pm.max_children]

\2. pm.max_children> 0

\3. pm.min_spare_servers<=pm.max_spare_servers

\4. pm.start_servers有效範圍[pm.min_spare_servers,pm.max_spare_servers]如果沒有配置,默認pm.min_spare_servers + (pm.max_spare_servers - pm.min_spare_servers) / 2

優缺點

優點:動態擴容,不浪費系統資源,master進程設置的1秒定時器對系統的影響忽略不計;

缺點:如果所有worker都在工作,新的請求到來只能等待master在1秒定時器內再新建一個worker,這時可能最長等待1s;

總結

  • static 靜態模式,啓動的時候創建固定數量的worker 進程,實際請求大於worker進程的時候 包warning

  • ondemand 按需分配模式,啓動的時候不會創建worker進程,根據需要創建,釋放在idle_timeout之後

    ​ 這樣不能及時的釋放連接和建立連接需要消耗資源

  • dynamic 動態模式(默認):啓動的時候創建指定數量的worker進程,根據情況合理的worker,定期檢查worker,關閉閒置連接

參考文件,如有不合適的,請告知刪除

鏈接:https://www.jianshu.com/p/c9a028c834ff

https://www.toutiao.com/a6776929604788552196/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1579141890&app=news_article&utm_source=mobile_qq&utm_medium=toutiao_android&req_id=202001161031300100080431041DC7F156&group_id=6776929604788552196

https://blog.csdn.net/njrclj/article/details/85062459

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章