學習總結
-
static 靜態模式,啓動的時候創建固定數量的worker 進程,實際請求大於worker進程的時候 包warning
-
ondemand 按需分配模式,啓動的時候不會創建worker進程,根據需要創建,釋放在idle_timeout之後
這樣不能及時的釋放連接和建立連接需要消耗資源
-
dynamic 動態模式(默認):啓動的時候創建指定數量的worker進程,根據情況合理的worker,定期檢查worker,關閉閒置連接
PHP-FPM & FastCGI
-
PHP-FPM(FastCGI Process Manager)是一個PHPFastCGI進程管理器,從其英文名稱和定義可以看出,FPM的核心功能就是進程管理。
-
FastCGI可以理解爲一種協議,用於web服務器(nginx、Apache)和處理程序間進行通信,是一種應用層通信協議。
-
工作原理大致如下圖
fpm的基本實現
- fpm創建master進程,在master進程中創建work pool並監聽socket,然後fork出多個子進程(work),這些work在啓動後阻塞fcgi_accept_request()上,各自accept請求,有請求到達後worker開始讀取請求數據,讀取完成後開始處理然後再返回,在這期間是不會接收其它請求的,也就是說fpm的子進程同時只能響應一個請求,只有把這個請求處理完成後纔會accept下一個請求。
- fpm的master與work進程間不會直接通訊,master通過共享內存獲取worker進程的信息,比如worker進程當前狀態、已處理請求數等,master要殺死worker進程也是通過發送信號
- 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的工作流程包含以下幾個步驟
- 等待請求:fcgi_accept_request()阻塞等待請求
- 接收請求:fastcgi請求到達後被worker接收並解析,一直到完全接收,然後將method、query、uri等信息保存到worker進程的fpm_scoreboard_proc_s結構中
- 初始化請求:php_request_startup()執行,此步驟會調用每個擴展的PHP_RINIT_FUNCTION方法,初始化一些操作
- 處理請求(編譯、執行):php代碼編譯執行階段,由 php_execute_script方法完成
- 關閉請求:返回響應,執行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進程
原理
配置項要求
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
原理
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)]
原理
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×tamp=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