PHPer 確定收到過這樣的投訴:小菊花一直在轉!大家網站怎麼這麼卡!當咱們線上業務遇到這種卡住(阻塞)的狀況,大部分 PHPer 會兩眼一抹黑,隨後想起那句名言:性能瓶頸都在數據庫
而後把鍋甩給DBA,趕忙找找慢sql,但這是很是錯誤的作法,由於有太多因素能致使業務卡住,下面列舉幾種常見的卡住問題。php
最多見的就是寫出了死循環代碼html
<?php while(1){ //do something if($condition){ //知足條件後退出循環 break; } }
上述代碼經過$condition
控制循環退出,若是程序驗證不嚴格,某些狀況$condition
永遠爲真就會致使請求卡死。前端
PHP的session鎖等待(ps:不少地方叫作session死鎖,這不太符合死鎖定義),這個相信大部分PHPer都遇到過,PHP默認會把session信息存儲在/tmp/sess_
下面的session文件裏面,調用session_start()
函數的時候會調用flock
系統調用給session文件加鎖,若是前一個請求沒有結束或者手動釋放session就會致使後面的請求沒法得到鎖,卡死在session_start()
這個地方。下面舉個例子,好比這種代碼:laravel
setInterval(function () { $.post("/ajax/doSomething", {}, function (result) {//1s進行一次ajax }); }, 1000)//1000ms == 1s
前端js定時經過ajax請求一下後端PHP的接口(/ajax/doSomething
)作一些比較耗時的事情,寫代碼的人可能想固然的認爲第一次的請求即便沒有處理完,也不會影響第二次的請求,由於有不少的FPM進程每次請求會分發到不通的進程,但卻不知第二次請求會卡死在session_start()
。web
最多見的場景就是寫日誌,在PHP代碼中確保每次fwrite
寫的日誌內容小於8k的狀況下咱們能夠利用append原子追加方式寫日誌,可是若是保證不了小於8k咱們就須要在每次寫日誌前給文件加文件鎖來避免兩第二天志間產生穿插的狀況,代碼以下:ajax
<?php $fp = fopen("/home/guoxinhua/php.log", "a+"); if (flock($fp, LOCK_EX)) { //給日誌文件加鎖 //do something fwrite($fp, "the huge string\n"); flock($fp, LOCK_UN); // 釋放鎖定 }
若是在A進程得到鎖後因爲某種問題阻塞了那麼B進程就會卡死在第三行flock
的位置,除非A進程被kill掉,系統會自動釋放這個文件鎖sql
注意還有不少其餘類型的鎖即便進程被kill也不會自動被釋放。
這個8k是能夠改的,和glibc中的fwrite不少細節也不同.這個在咱們的swoole課堂上會補充,11年架構師授課的TP五、laravel、swoole、swoft、高併發、,官方羣:677079770 ,大牛帶你飛 ,PHP/web從入門到架構 722584796
MySQL、CURL、Swoole\Client 等網絡客戶端未設置超時可能會致使進程阻塞。Swoole\Client 創建 TCP 鏈接的時候connect
方法的最後一個參數是超時時間,-1
即爲永不超時,注意這裏設置不是單指此次connect
方法,而是後面全部的send
,recv
都永不超時,在同步阻塞的編程模式下,若是此時對端機器直接宕機等緣由致使網絡不通,那麼本端業務的表現就是卡死狀態,全部的send
,recv
方法都將被阻塞,代碼以下:數據庫
<?php $cli = new Swoole\Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', 9501,-1)) { $cli->send("data"); $cli->recv(); } else { echo "connect failed."; }
在 Swoole 協程模式下,不正確的使用lock也會致使全部協程大面積卡死,以下代碼,經過go
方法建立2個協程(不理解協程的同窗能夠理解爲建立了2個線程),第一個協程lock得到鎖後在co::sleep
位置讓出了cpu此時開始執行第二個協程,第二個協程會卡死在第6行得到鎖的位置,同時第一個協程也永遠沒法恢復繼續執行。編程
<?php $lock = new Swoole\Lock(); $c = 2;//建立2個協程 while ($c--) { go(function () use ($lock) {//建立協程 $lock->lock();//得到鎖 Co::sleep(1);//讓出cpu $lock->unlock();//釋放鎖 }); }
上述只是舉了一些例子,真實業務中還有各類姿式的卡死,遇到這種問題有經驗的PHPer會用strace -p
命令查看當前PHP進程到底阻塞在哪一個系統調用上面來定位問題,但這種方式有幾個問題:後端
futex(0x7f4c8d567128, FUTEX_WAIT, 2, NULL)
這種信息,很是的不直觀,不少人根本不知道哪些PHP代碼會觸發futex
系統調用,還有前文提到session_start
那個問題,不少人根本不知道這裏會觸發flock
,也就說很難根據一個系統調用定位到具體問題。strace -p
哪一個進程呢?貌似只能碰碰運氣了。strace
命令的原理是追蹤全部的系統調用,若是是前文提到的第一種狀況,也就是死循環的卡死,strace
根本沒法得到任何有用的信息。此時咱們只能用gdb
工具來獲取當前死循環在哪裏具體,具體作法以下:首先:gdb attach
後面接個進程id。 p (char *)executor_globals.current_execute_data.func.op_array.filename.val
打印當前執行的PHP文件。 p (char *)executor_globals.current_execute_data.func.op_array.function_name.val
打印當前執行的函數名。 p executor_globals.current_execute_data.opline.lineno
打印當前執行的行數。 .gdbinit
能稍微減小點難度,可是也有不少其餘問題)。針對上述問題,Swoole官方出了一個解決方案 Swoole Tracker 的堆棧工具,同時支持FPM和Swoole。
使用方法很簡單:
swoole_tracker
擴展。調試器
=>進程列表
中點擊堆棧
按鈕就能得到當前卡在哪了,如圖:除了上面的卡死問題,還有一種狀況是調用變慢,好比原來一個系統調用5ms,可是因爲網絡等等緣由,這個調用100ms才返回,業務的表現是變慢了而不是卡死在那裏,這種狀況經過tracker的抓堆棧工具是沒法定位問題的,由於卡住時間很短,很難抓到調用堆棧,此時須要Swoole工具鏈中的另一個工具阻塞IO檢測工具
咱們會在後面給你們介紹。