一次生產環境CPU佔用高的排查

1. 項目背景

甲方是保密級別非常高的政府部門。所以我們全程拿不到任何測試數據,只能是自己模擬數據進行測試。
項目部署的時候,公司派了一人到甲方現場,在甲方客戶全程監督下,進行部署,調試,導入數據等工作。因爲前期看不到真實的數據,所以很多功能都是憑客戶口述進行,到了現場難免發現問題。
這時需要改動代碼重新打包部署,需要客戶特製U盤需入密碼才能傳到內網。總之,非常麻煩。
如果有什麼BUG,全靠同事口述。如果是功能性的什麼BUG還好,溝通雖然困難,大體都能很快解決。

但是,對的,“但是”雖遲但到,同事在現場遇到一個所謂"很奇怪"的問題。當導入某一批文件的時候,程序就卡在那裏不動了。

2. 技術背景

長話短說就是,通過頁面批量上傳文件,後端接到文件後會做一些word,pdf轉換按頁按段落提取文字,並通過消息隊列kafka異步進行下一步處理:包括不限於語義分析通過算法提取出關鍵要素,以及要素之間的關聯等等。
嗯,不涉及具體業務,不算涉密。

同事描述的現象包括:

  • 1.程序卡住了
  • 2.kafka報錯了
  • 3.算法返回500
  • 4.重啓了spring boot應用,kafka,算法服務都沒法恢復。

以上是同事在現場用手機打字交流的一個簡單覆盤。非嚴格按照時間線來的。

2.1 程序卡住了

同事只給出了程序卡住的現象描述,沒給能出具體的節點。我只能通過不停的加日誌來確定具體卡在哪裏。
最終確定卡在某個算法調用期間。

結合同事曾說過算法調用出了問題,看看算法問題。

2.2 算法問題

算法是我使用django封裝了一個python web接口。具說http請求返回了500,那應該是web服務內部異常。
但是奇怪的是,同事說算法日誌沒有看到異常信息。

因爲NLP涉及到vocab,如果有生僻字或者vocab文件裏不包含的字符,可能會導致算法異常或者解析異常。

但同事反饋說沒有相關的異常。

2.3 kafka報錯了

這應該是一個烏龍。
我在spring kafkatemplate發送消息的時候打印了回調消息。

    * 向kafka發送doc相關信息
     * @param info
     */
    public void sendDocInfo(String info) {
        this.template.send(docTopic, info);
        ListenableFuture listenableFuture = this.template.send(docTopic, info);
        boolean bool = listenableFuture.isDone();
        log.info("kafka發送狀態 :" + bool);
    }

在某個算法調用期間一直卡住,超過了kafka默認的5分鐘的限制,返回了false。
換句話說,這裏的日誌不能表明kafka出了問題。它只是程序卡住的一個連鎖現象反映,遠不是本質原因。

2.4 重啓服務都沒用

兜兜轉轉,打包上傳到現場,客戶再手動導入。時間一步步流逝。

聽到同事說重啓了spring boot應用,kafka,算法應用都沒用以後,我一度有點懵逼了。
如果是某個線程一直空耗沒釋放,一定會導致CPU升高,(這裏插一句,提過top jstack查看CPU高的線程的堆棧日誌),但是重啓應用肯定會釋放的呀。

這怎麼會重啓了還是這樣呢?

th.jpg

小半天就這麼過了,下午5點客戶就催下班了。又是羨慕體制內的一天。

3.重新確定方向

經過一晚的大腦放空,我覺得不能被零散的信息牽着鼻子走。
我覺得我應該放過多餘信息,直搗黃龍。
程序卡住了?這種系統假死問題,第一反應肯定是查看CPU啊,有沒有佔用CPU高的進程,再查看應用裏具體的是哪個線程在佔用着cpu,再通過jstack找到具體的應用代碼。

就算是最後不是這個問題,以上操作也是一個非新手程序員的基操啊。

而且,爲什麼重啓應用後會重現卡住的現象?因爲kafka啊。
kafka爲了確保不丟消息。在生產端,集羣,消費端都需要做出努力。當然,這其實不是kafka獨有,任何消息隊列都是相同的思路。
具體到kafka消費端,爲保證不丟消息,至少保證在沒有異常的情況下才做ack.
當程序假死的時候,沒有執行ack操作,那麼當程序重啓的時候,kafka當然會去做重新拉取信息的動作。又造成程序假死。

這很合理,完全可以說得通。

當然這只是我上班路上的推測。

一大早,新的一天,一切清零。到公司,跟同事說了,他重啓了程序,沒有上傳文件,通過後臺日志,觀察到kafka確實在消費之前的數據。

很好,驗證得到證實,完美的開始。

接下來,就是通過top命令查看是否有CPU佔用高的現象。

涉密項目哦,可不敢可沒有截圖哦。

企業微信截圖_16760201883784.png

同事說到有個進程佔用CPU超過100%,從佔用時間上推測,從重啓過後就一直沒釋放。

非常好,驗證了第2個猜想。

再通過 top -H -p pid命令得到應用內佔用CPU高的具體線程。將線程ID轉爲十六進程,再通過jastck grep 線程碼就可以定位到具體的代碼。

具體的定位過程因爲項目原因,不便不能展示過多,技術方面的細節參考我的另一篇文章( 【工作隨手記】deaklock排查)

告訴同事重點查看帶有自己公司包名的代碼,很快就定位到了代碼位置。

因爲完全與業務無關,可以把代碼放上來。

/**
 * 獲取一個字符串和關鍵詞,查找這個字符串出現的次數
 * @param str 原文
 * @param key 關鍵詞
 * @return
 */
public static int getStringCount(String str, String key) {
    int count = 0;
    int index = 0;
    while ((index = str.indexOf(key)) != -1) {
        count++;
        str = str.substring(index + key.length());
    }
    return count;
}

看到這個while我一瞬間就知道,癥結找到了。

當傳入的關鍵詞爲""空串時,直接就會陷入while死循環。這就是程序假死的本質原因。

4.總結

從一下午找不到重點,到第二天半小時解決問題。我也在思考,這中間的差異出了什麼問題。

項目的特殊背景,是一個原因,但不是主要原因。身處這個行業,以後總要有這方面的需求和場景,不能每次都拿項目特殊性來背書和推脫。

我覺得以下幾點是我從這件事當中學到的,在今後的工作的應該儘量避免或者學習的。

  • 1.技術人應該相信自己的直覺。
  • 2.溝通應該要有效率。要有目的。區分主次。
  • 3.從紛繁的反饋中提取有效的信息,是一種能力且可鍛鍊。
  • 4.保持清醒的大腦。否則,寧願停下來。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章