php.ini 和 php-fpm.conf 中有很多超時相關的設定,那麼這些設定到底有什麼作用呢?在原始碼中又是怎麼實現的呢?這篇文章就來講講下面幾種超時設定:
執行環境: Mac 10.14.2 + PHP 7.3.7
php.ini的解析是在php_module_startup()
階段完成,ini_entry是在 main.c
中為每個php.ini設定定義的解析規則,格式如下:
ZEND_INI_ENTRY3_EX(name, default_value, modifiable, on_modify, arg1, arg2, arg3, displayer)
PHP為不同型別的設定定義了很多宏,ZEND_INI_ENTRY3_EX
是它們展開後的最終宏,比如PHP_INI_ENTRY
宏
PHP_INI_ENTRY(name, default_value, modifiable, on_modify)
name: 設定名稱
default_value: 設定預設值
modifiable: 設定的可被設定範圍
這些模式決定著一個 PHP 的指令在何時何地,是否能夠被設定。手冊中的每個指令都有其所屬的模式。例如有些指令可以在 PHP 指令碼中用 ini_set() 來設定,而有些則只能在 php.ini 或 httpd.conf 中。
例如 output_buffering 指令是屬於 PHP_INI_PERDIR,因而就不能用 ini_set() 來設定。但是 display_errors 指令是屬於 PHP_INI_ALL 因而就可以在任何地方被設定,包括 ini_set()。
模式 含義 PHP_INI_USER 可在使用者指令碼(例如 ini_set())或 Windows 登入檔(自 PHP 5.3 起)以及 .user.ini 中設定 PHP_INI_PERDIR 可在 php.ini,.htaccess 或 httpd.conf 中設定 PHP_INI_SYSTEM 可在 php.ini 或 httpd.conf 中設定 PHP_INI_ALL 可在任何地方設定
on_modify: 設定修改函數
因為max_input_time
和 max_execution_time
聯絡比較密切,所以放在一起來講。
max_input_time
; Maximum amount of time each script may spend parsing request data. It's a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
; long running scripts.
; Note: This directive is hardcoded to -1 for the CLI SAPI
; http://php.net/max-input-time翻譯過來就是:
max_input_time
是每個指令碼可以花在解析請求資料上的最大時間。在生產伺服器上通過限制max_input_time可以清除掉長時間執行的指令碼。在CLI模式下會寫死為-1,即無限制。max_execution_time
; Maximum execution time of each script, in seconds
; http://php.net/max-execution-...
; Note: This directive is hardcoded to 0 for the CLI SAPI翻譯:
max_execution_time
是每個指令碼的最大可執行時間。在CLI模式下寫死為0
// max_input_time,預設值為無限制 STD_PHP_INI_ENTRY("max_input_time", "-1", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateLong, max_input_time, php_core_globals, core_globals) // max_execution_time,預設值為30s,修改函數為OnUpdateTimeout PHP_INI_ENTRY("max_execution_time", "30", PHP_INI_ALL, OnUpdateTimeout)
OnUpdateTimeout()
函數如下,由第二節可知設定解析發生在php_module_startup()
階段,此時EG(timeout_seconds)
被賦值為了max_execution_time
,但還沒有設定定時器。
// main.c static PHP_INI_MH(OnUpdateTimeout) { if (stage==PHP_INI_STAGE_STARTUP) { /* Don't set a timeout on startup, only per-request */ /* EG(timeout_seconds) = max_execution_time */ ZEND_ATOL(EG(timeout_seconds), ZSTR_VAL(new_value)); return SUCCESS; } zend_unset_timeout(); ZEND_ATOL(EG(timeout_seconds), ZSTR_VAL(new_value)); zend_set_timeout(EG(timeout_seconds), 0); return SUCCESS; }
// main.c int php_request_startup(void) { ...... if (PG(max_input_time) == -1) { zend_set_timeout(EG(timeout_seconds), 1); } else { zend_set_timeout(PG(max_input_time), 1); } ...... } int php_execute_script(zend_file_handle *primary_file) { ...... if (PG(max_input_time) != -1) { zend_set_timeout(INI_INT("max_execution_time"), 0); } ...... }
從上面程式碼可以看到,如果設定了max_input_time
(即值不等於-1,-1可以認為是在CLI模式下),在php_request_startup()
階段會設定一個定時器,超時時間為max_input_time
;在php_execute_script()
階段會重新設定一個定時器,超時時間為max_execution_time
。那麼整個PHP指令碼執行的最大執行時間就等於max_input_time
+ max_execution_time
。
如果沒有設定max_input_time
的話(即值等於-1),在php_request_startup()
階段也會設定一個定時器,但超時時間被設為了EG(timeout_seconds)
,而EG(timeout_seconds)
已經在php_module_startup()
階段被賦值為max_execution_time
,所以此時的超時時間就是max_execution_time
;在php_execute_script()
階段不會重新設定定時器,前一階段設定的max_execution_time
定時器仍然生效著。那麼整個PHP指令碼的最大執行時間就是max_execution_time
。
zend_set_time()
使用setitimer(ITIMER_PROF, &t_r, NULL);
來實現定時器,ITIMER_PROF
會統計包括使用者態和核心態下所花費的時間,而像sleep()
這樣的系統呼叫會讓程序掛起,不佔用cpu時間片,所以這倆超時時間是不包括sleep()
時間的。
當定時器到時間後,ZendVM會丟擲E_ERROR
,即Fatal error
錯誤。
; Time limit for child processes to wait for a reaction on signals from master.
; Available units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds翻譯:
process_control_timeout
是留給子程序處理來自master程序訊號的時間限制。
當master程序接收到SIGINT
、SIGTERM
、SIGQUIT
、SIGUSR2
這些訊號時,會呼叫fpm_pctl()
來進行處理。
首先master程序會根據 接收到的訊號 和 當前fpm的執行狀態 來決定傳送給worker程序的是SIGQUIT
還是SIGTERM
訊號,同時註冊時間為process_control_timeout
的定時事件。
如果在process_control_timeout
時間內子程序沒有退出,那麼master程序會升級SIGQUIT
為SIGTERM
,SIGTERM
為SIGKILL
,並註冊1s的定時事件。SIGKILL
就直接終止worker程序了,SIGTERM
還能再給worker程序1s的時間。
綜上,process_control_timeout
可以理解為master程序留給worker程序結束自己的時間,要是到時間worker還沒搞定那就開始master自己的策略了。
因為request_terminate_timeout
和 request_slowlog_timeout
聯絡比較密切,所以放在一起來講。
request_terminate_timeout
; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0翻譯:執行一個請求的超時時間,在這之後worker程序將被終止。這個選項應該被用在
max_execution_time
這個ini選項由於某些原因不能停止指令碼執行的時候。request_slowlog_timeout
; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0翻譯:執行一個請求的超時時間,在這之後一個PHP的backtrace會被輸出到slowlog檔案裡。
request_slowlog_timeout
和 request_terminate_timeout
用在master程序的心跳檢測中(fpm_pctl_heartbeat()
),心跳時間heartbeat
簡化後的演演算法是
在開啟request_terminate_timeout
情況下:request_terminate_timeout/1000*3
在未開啟request_terminate_timeout
情況下:request_slowlog_timeout/1000*3
或者 0
request_terminate_timeout >= request_slowlog_timeout
第三條規則是為了保證slowlog不影響到正常的請求,heartbeat
取超時時間的1/3應該是為了避免心跳檢測過於頻繁,因為每次心跳檢測都需要遍歷所有worker程序。
如果超時事件發生了,那麼將直接kill掉worker程序,kill(child_pid, SIGTERM);
,之後核心回收資源關閉client_socket,nginx返回502錯誤給瀏覽器。
推薦學習:《》
以上就是淺析PHP組態檔中的幾種超時設定的詳細內容,更多請關注TW511.COM其它相關文章!