線上定時指令碼執行慢,分析過程

gaohuag發表於2018-03-15

問題描述:
有一個指令碼是用來確認收貨的。
像淘寶一樣,我們買的商品,快遞送完貨以後,可以手動點選已收貨,或者是過幾天自動改狀態為已收貨。
就做這個功能的指令碼執行時間很長,是一開始挺快的,越來越慢。

解決過程:
開始的時候感覺效能有問題,把一次性取出所有資料改成一次取出100條
下手太快,感覺沒找到問題原因。

用什麼工具解決這個問題呢?
常用的xhprof,我本地是裝了這個擴充套件的,
問題是本地環境沒有這麼多資料,我把線上資料匯入本地環境(線上資料大,費時費力),執行了下,沒發現這個問題。

我想到線上環境和本地環境不一樣,還是要到線上環境除錯。
問題是線上沒有許可權修改程式碼,再說了修改程式碼容易出錯,使用者訪問時候會報錯。

線上環境沒有xhprof,用什麼工具好呢?
經常聽人說使用用strace調查一些問題,我也試試看

strace -p pid
看輸出看不懂

我把問題和我們的架構師說了一遍,給他看strace的輸出,問他看懂嗎?有什麼辦法解決這個問題。
他很果斷,說是看程式碼,不用其他工具。

我從頭看程式碼,呼叫的來來回回,暈暈乎乎。

後來想起來把線上程式碼拷到我的賬號的家目錄,可以除錯,接下來順利多了
我線上發現了有一個方法,執行一段時間需要2s甚至更長時間
strace列印的日誌中看到了:

sendto(6, "*4\r\n$7\r\nHINCRBY\r\n$23\r\norder_stat"..., 77, MSG_DONTWAIT, NULL, 0) = 77
sendto(6, "*1\r\n$4\r\nEXEC\r\n", 14, MSG_DONTWAIT, NULL, 0) = 14
sendto(6, "*2\r\n$6\r\nEXISTS\r\n$23\r\norder_stati"..., 46, MSG_DONTWAIT, NULL, 0) = 46

關鍵字HINCRBY EXEC EXISTS,像是redis操作

主要是程式一直沒有卡住過,到了後來一直就是這些操作
我透過下面命令檢視了
lsof -d 6
結果是:

COMMAND   PID  USER   FD   TYPE     DEVICE SIZE/OFF       NODE NAME
php     25106          6u  IPv4 3612956560      0t0        TCP *.*.*.*:25571->*.*.*.*:6379 (ESTABLISHED)

6379埠是我熟悉的redis埠

問了同事,什麼地方有redis加一操作,他告訴了我程式碼的位置,我一看知道了就是這兒的問題

為什麼一開始沒有那麼多的自增加一操作,執行到後來卻有了呢?

我看到程式碼中有用到事件模型,猜測是這兒的原因,果不其然:
迴圈中呼叫了下面程式碼:

            Helper::statistics($this, 'orderFinish', [
                'user_name' => !empty($order_info['member_name']) ? $order_info['member_name'] : '',
                'user_id' => !empty($order_info['member_id']) ? $order_info['member_id'] : 0
            ]);

該方法定義是這樣:

        $statistic = new OrderStatistics();
        $obj->on($action, [$statistic, $action], $data);
        $obj->trigger($action); 

可以看到監聽的事件越來越多,所以就是我們看到的現象,執行到後來越來越慢

問題總結:
事件模型使用不當,迴圈中,給一個物件不斷新增事件。而我們想要的效果是迴圈一次,事件要定義一次,觸發一次,銷燬一次。

解決辦法:
我的解決方案,程式碼改成這樣:

        $statistic = new OrderStatistics();
        $event = new Event();
        $event->data = $data;
        $statistic->{$action}($event);

我感覺在這個地方應用事件模型,有點牽強。

另外一個解決方案,

        $statistic = new OrderStatistics();
        $obj->on($action, [$statistic, $action], $data);
        $obj->trigger($action);
        $obj->off($action, [$statistic, $action]);//加了這一行,呼叫一次,立馬把事件取消掉

大家喜歡哪種解決方案?

參考資料:
https://www.jianshu.com/p/8a247cae629a

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章