調(diào)試busy進(jìn)程
有時(shí)候我們通過php start.php status
命令能看到有busy
狀態(tài)的進(jìn)程,說明對(duì)應(yīng)進(jìn)程正在處理業(yè)務(wù),正常情況下業(yè)務(wù)處理完畢對(duì)應(yīng)進(jìn)程會(huì)恢復(fù)為idle
狀態(tài),這一般情況下不會(huì)有什么問題。但是如果一直是busy狀態(tài)沒有恢復(fù)過idle
狀態(tài),則說明進(jìn)程內(nèi)的業(yè)務(wù)有阻塞或者無限循環(huán),可以通過以下方法定位。
利用strace+lsof命令定位
1、status里找到busy進(jìn)程的pid
運(yùn)行php start.php status
后顯示如下
圖中busy
的進(jìn)程的pid
為11725
和11748
2、strace 跟蹤進(jìn)程
挑選一個(gè)進(jìn)程pid
(這里選擇11725
),運(yùn)行strace -ttp 11725
顯示如下
可以看到進(jìn)程在不斷的循環(huán)poll([{fd=16, events=....
的系統(tǒng)調(diào)用,這是在等待fd為16的描述符可讀事件,也就是在等這個(gè)描述符返回?cái)?shù)據(jù)。
如果沒有顯示任何系統(tǒng)調(diào)用,保留當(dāng)前終端,重新再打開一個(gè)終端,運(yùn)行kill -SIGALRM 11725
(給進(jìn)程發(fā)送一個(gè)鬧鐘信號(hào)),然后看strace的終端是否有響應(yīng),是否阻塞在某個(gè)系統(tǒng)調(diào)用上。如果仍然沒有顯示任何系統(tǒng)調(diào)用說明程序很可能處于業(yè)務(wù)死循環(huán)中,參考頁面下部引起進(jìn)程長時(shí)間busy的其它原因 第2項(xiàng) 解決。
如果系統(tǒng)阻塞在epoll_wait或者select系統(tǒng)調(diào)用是正常情況,這說明進(jìn)程已經(jīng)處于idle狀態(tài)。
3、lsof 查看進(jìn)程描述符
運(yùn)行lsof -nPp 11725
顯示如下
述符16對(duì)應(yīng)的是16u的記錄(最后一行),能看fd=16
的描述符是一個(gè)tcp連接,遠(yuǎn)程地址是101.37.136.135:80
,說明進(jìn)程應(yīng)該是在訪問一個(gè)http資源,循環(huán)poll([{fd=16, events=....
是一直在等待http服務(wù)端返回?cái)?shù)據(jù),這解釋了為什么進(jìn)程處于busy
狀態(tài)
解決:
知道了進(jìn)程阻塞在哪里,接下來就容易解決了,例如上面經(jīng)過定位應(yīng)該是業(yè)務(wù)在調(diào)用curl,而對(duì)應(yīng)的url長時(shí)間沒有返回?cái)?shù)據(jù),導(dǎo)致進(jìn)程一直等待。這時(shí)候可以找url提供者定位url返回慢的原因,同時(shí)應(yīng)該在curl調(diào)用的時(shí)候加上超時(shí)參數(shù),比如2秒沒返回就超時(shí),避免長時(shí)間阻塞卡死(這樣進(jìn)程可能會(huì)出現(xiàn)2秒左右的busy狀態(tài))。
引起進(jìn)程長時(shí)間 busy 的其它原因
除了進(jìn)程阻塞或?qū)е逻M(jìn)程busy
,還有以下原因會(huì)引起進(jìn)程處于busy
狀態(tài)。
1、業(yè)務(wù)有致命錯(cuò)誤導(dǎo)致進(jìn)程不斷退出
現(xiàn)象: 這種情況下能看到系統(tǒng)負(fù)載比較高,status
中的load average
為1或者更高。能看到進(jìn)程的exit_count
數(shù)字很高,并且不斷增長
解決: debug方式運(yùn)行(php start.php start
不加-d
)workerman看下業(yè)務(wù)報(bào)錯(cuò),把報(bào)錯(cuò)解決即可
2、代碼里無限死循環(huán)
現(xiàn)象: top中能看到busy進(jìn)程占用cpu很高,strace -ttp pid
命令沒有打印出任何系統(tǒng)調(diào)用信息
解決: 參考鳥哥的文章通過gdb和php源碼定位,步驟總結(jié)大概如下:
1、php -v
查看版本
2、下載對(duì)應(yīng)php版本的源碼
3、gdb --pid=busy進(jìn)程的pid
4、source php源碼路徑/.gdbinit
,
5、zbacktrace
打印調(diào)用棧
最后一步可以看到php代碼當(dāng)前執(zhí)行的調(diào)用棧,也就是php代碼死循環(huán)的位置。
注意:如果zbacktrace
沒有打出調(diào)用棧,可能你的php編譯時(shí)沒有加入-g
參數(shù),需要重新編譯php,然后重啟workerman定位。
3、無限添加定時(shí)器
業(yè)務(wù)代碼不停的添加定時(shí)器又不刪除,導(dǎo)致進(jìn)程內(nèi)定時(shí)器越來越多,最終造成進(jìn)程無限運(yùn)行定時(shí)器。例如以下代碼:
$worker = new Worker;
$worker->onConnect = function($con){
Timer::add(10, function(){});
};
Worker::runAll();
以上代碼在有客戶端連接上來后會(huì)增加一個(gè)定時(shí)器,但是整個(gè)業(yè)務(wù)代碼里沒有刪除定時(shí)器的邏輯,這樣隨著時(shí)間推移,進(jìn)程內(nèi)會(huì)不斷增加定時(shí)器,最終導(dǎo)致進(jìn)程無限運(yùn)行定時(shí)器導(dǎo)致busy。
正確的代碼:
$worker = new Worker;
$worker->onConnect = function($con){
$con->timer_id = Timer::add(10, function(){});
};
$worker->onClose = function($con){
Timer::del($con->timer_id);
};
Worker::runAll();