提及php的執行時間,相信每個phper都遇到過這方面的問題,特別是在CGI模式下,通常咱們都會經過修改max_execution_time或者在代碼開頭添加set_time_limit(0)來解決問題,但下面這個場景你們可能也曾經遇到過:
咱們先將php.ini的執行時間設置爲60Sphp
max_execution_time = 60
再在代碼的開頭設置執行時間爲60S,讓二者統一
而後運行sleep讓程序模擬運行20S:數據庫
set_time_limit(60); sleep(20); echo 1;
會發現程序在執行到第16S的時候就報出了502 Bad Gatewayjson
說好的能夠執行60S呢?江湖規矩報錯先翻看日誌,查看php-fpm.log,能夠發現有這麼一段信息segmentfault
[06-Dec-2019 12:44:13] WARNING: [pool www] child 19910, script '/home/wwwroot/public/index.php' (request: "GET /index.php") execution timed out (16.120721 sec), terminating [06-Dec-2019 12:44:13] WARNING: [pool www] child 19910 exited on signal 15 (SIGTERM) after 2573.443300 seconds from start [06-Dec-2019 12:44:13] NOTICE: [pool www] child 21861 started
這三行日誌分別告訴了咱們三個信息
1.子進程19910的執行時間超過了16S被終結了
2.子進程19910在啓動了2573.44S後被關閉了
3.子進程19910在關閉後的同一秒子進程child 21861被fork出來開始運行數組
也就是說,PHP-CGI在執行的過程當中,除去咱們以前已經設置好的兩個參數,應該還有另一個參數限制了這個進程的執行時間,打開php目錄下的php-fpm.conf看看有無異常架構
... pm.min_spare_servers = 16 pm.max_spare_servers = 60 request_terminate_timeout = 15 request_slowlog_timeout = 0 slowlog = var/log/slow.log ...
能夠看到有一個參數request_terminate_timeout = 15 和咱們的超時閾值很是接近,翻看註釋找到關於這個參數的解釋:函數
; 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'.
大概的意思是這個參數的設置是當max_execution_time啓用時,爲了防止php子進程由於某些緣由沒法中止運行而設置的一個保護措施,固然這個保護措施比較簡單粗暴,就是直接kill超時的子進程,而後直接fork一個新的php-fpm
結合解釋,咱們就很好理解前面日誌中出現的三條信息了:由於執行時間超過了max_execution_time設置的閾值,子進程19910被直接kill了,而後又生成了一個新的子進程21861測試
因而咱們將php-fpm.conf中的request_terminate_timeout改成30,重啓php,再次執行以前的代碼,再也不報502了spa
看到這裏,可能會有小夥伴會說,PHP的執行執行時間影響的參數有點多,真的記不住應該改那個才真正的有效,咱們不妨從php運行的架構來梳理一下,不太清楚php運行架構的小夥伴能夠看看我以前寫的《淺析PHP-FPM、CGI、Fast CGI的關係》
PHP-FPM的程序架構是由一個master進程來進行管理一個PHP-CGI的子進程池,當一個請求由master進程轉發到worker時,master進程便會開始計時,當超過設定的執行時間時master進程,便會直接kill掉超時的worker進程(程序的世界也很差混),而咱們設置max_execution_time設置的時間是對於workder進程而言的,因此不管單個worker進程的執行時間設置多少,都不得超過fpm中的request_terminate_timeout,不然一概kill
文末,再補充兩條在翻看手冊時翻到的知識點:
在代碼中使用set_time_limit()會從零開始從新啓動超時計數器
換句話說,若是超時默認是30秒,在腳本運行了了25秒時調用 set_time_limit(20),那麼,腳本在超時以前可運行總時間爲45秒。
這個相對簡單,就不上測試代碼了,你們有空能夠驗證一下
set_time_limit()函數和配置指令max_execution_time隻影響腳本自己執行的時間
其餘發生在諸如使用system()的系統調用,流操做,數據庫操做等的腳本執行的最大時間不包括其中。也就是說,好比sleep或者file_get_contents等操做消耗的時間是不會計入max_execution_time的超時時間中的
因此其實我前文寫的代碼即便把sleep設置爲999也不會報執行超時的錯誤
,用代碼驗證下:
set_time_limit(10); sleep(20);
能夠發現程序確實沒有報超時的錯誤,接着咱們再編寫一段代碼,讓php執行非系統調用和數據流等操做的耗時任務
set_time_limit(10); //將計數器清零,容許執行時間爲10S sleep(10); $json[] = str_repeat("123456789,",10000); //生成一個內容量較大的數組 $count = 1000000; //循環執行1000000次數據,json_encode和json_decode在對於長字符串的轉化效率不高,因此比較耗時 while ($count--){ $string = json_encode($json); json_decode($string,true); }
結果如上圖,程序一共執行了20S,其中有10S是在sleep也就是系統調用不算入執行超時時間,另外10s執行的是一段cpu密集型的操做,符合算入max_execution_time超時時間的要求,因而符合條件拋出錯誤
文末總結:有空能夠多翻翻手冊,天天都有新發現