日常Bug排查-連線突然全部關閉

无毁的湖光-Al發表於2024-05-13

日常Bug排查-連線突然全部關閉

前言

日常Bug排查系列都是一些簡單Bug的排查。筆者將在這裡介紹一些排查Bug的簡單技巧,同時順便積累素材。

Bug現場

最近碰到一個問題,一臺機器上的連線數在達到一定連線數(大概4.5W)連線數之後會突然急速下降到幾百。在應用上的表現就是大量的連線報錯,系統失去響應,如下圖所示:

思路

思路1: 第一步肯定是懷疑程式碼寫錯了,筆者看了下,使用的是成熟的框架,不是自己操作的連線,那麼程式碼的問題應該較小。
思路2:那麼筆者就開始懷疑是核心的限制,例如檔案描述符到頂了之類,但這又有一個矛盾點。一旦是核心對連線數量限制的話,應該是連線數到達一定程度就漲不上去,而不是連線數跳水式下降。
思路2.1: 進一步,筆者就開始想,很有可能是某個間接資源的限制導致到達這個瓶頸後,所有的連線獲取這個資源獲取不到而導致全部報錯。再結合TCP連線消耗的資源無非就是CPU/記憶體/頻寬。

監控資訊

有了上面的思路,我們就可以觀察相關監控資訊了。
CPU監控:CPU消耗很高達到了將近70%,但獲取不到CPU一般只會導致響應變慢,和問題現象不匹配。
頻寬監控:頻寬利用率達到了50%,這個頻寬利用率算不上高。
記憶體監控:確實使用了大量的記憶體,RSS達到了26G,但是比起128G的記憶體而言,這點消耗量顯然不可能成為瓶頸。
好了,看了這三個資料之後,就發現系統的資源消耗還稱不上達到瓶頸。但是,筆者從一開始就懷疑記憶體的使用可能觸發了某個特殊的瓶頸。因為只有記憶體資源申請不到之後,TCP連線才有可能直接報錯進而Drop連線。

TCP監控資訊

當傳統的監控已經不足以分析我們問題的時候,筆者就直接掏出針對TCP問題最有效的統計命令了,祭出法寶:

# 這條命令詳細的輸出了tcp連線的各種統計引數,很多問題都可以透過其輸出獲得線索
netstat -s 

筆者在這條命令的輸出中詳細的觀察TCP以及TCP記憶體相關的輸出項,定睛一看,就發現一個很不尋常的地方:

...
TcpExt:
 TCP ran low on memoery 19 times
 ......

這個輸出就和筆者對於記憶體限制的猜想完全對應起來了。TCP記憶體不夠了,導致讀取或者寫入資料的時候申請記憶體失敗進而將TCP連線本身給Drop了。

修改核心引數

因為筆者之前詳細的閱讀過Linux TCP的原始碼以及其所有的可調整的核心引數。所以對TCP的記憶體限制有映像。有了GPT之後,只需要知道一個大致的方向就好了,直接問GPT就給出了答案,就是tcp_mem這個引數。

cat /proc/sys/net/ipv4/tcp_mem
1570347 2097152 3144050

這三個值分別代表了tcp對於記憶體在不同閾值下的不同使用策略,單位是頁,也就是4KB。具體解釋可以直接去問GPT,在此就不贅述了。核心就是TCP消耗的記憶體總量在大於第三個值也就是3144050(12G,佔128G記憶體的9.35%)的時候TCP就開始由於記憶體申請不到而Drop連線。而對應的應用由於每個請求高達好幾M確實會讓每個TCP連線消耗大量的記憶體。
在記憶體消耗過程中一旦超限,那麼TCP連線就會被核心強制Drop,這也解釋了為什麼基本所有連線在很短的時間內就跳水式Drop,因為他們都在不停申請記憶體,而達到臨界閾值後全部都報錯,進而整個系統的所有連線都關閉導致系統失去響應。如下圖所示:

知道是這個問題就很簡單了,直接將tcp_mem調大即可:

cat /proc/sys/net/ipv4/tcp_mem
3570347 6097152 9144050

調整後系統保持穩定

在經過響應的核心調整之後,系統的連線數超過了5W之後依舊保持穩定。這時候我們觀察相關的TCP消耗記憶體頁的輸出:

cat /proc/net/sockstat
TCP: inuse xxx orphan xxx tw xxx alloc xxxx mem 4322151

從這個輸出我們可以看到系統平穩執行後,其常態使用的記憶體頁數量mem為4322151已經遠大於之前的3144050,這也從側面驗證了筆者的判斷。

對應的核心棧

在此記錄下對應的Linux核心棧

tcp_v4_do_rcv
 |->tcp_rcv_established
  |->tcp_data_queue
   |->tcp_data_queue
    |->tcp_try_rmem_schedule
     |->sk_rmem_schedule
      |->sk_rmem_schedule
       |->__sk_mem_raise_allocated
         |-> /* Over hard limit. */
          if (allocated > sk_prot_mem_limits(sk, 2))
          goto suppress_allocation;
   |->goto drop:
    tcp_drop(sk,skb)

可以看到當allocated大於相關的記憶體limit之後Linux Kernel會將此TCP連線直接Drop。

總結

筆者在瞭解清楚Bug現場之後,大概花了20分鐘就定位到了是TCP記憶體瓶頸的問題,然後藉助GPT非常快速的找到了相關解決方案。不得不說GPT能夠大幅加速我們搜尋的過程,筆者個人感覺可以在很大程度上替代搜尋引擎。但餵給GPT的Prompt還是需要透過Bug現場以及一定的經驗來構造,它代替不了你的思考,但能大幅加速資訊的檢索。
.

相關文章