日常 Bug 排查-連接突然全部關(guān)閉
前言
日常 Bug 排查系列都是一些簡單 Bug 的排查。筆者將在這里介紹一些排查 Bug 的簡單技巧,同時(shí)順便積累素材。
Bug 現(xiàn)場
最近碰到一個(gè)問題,一臺機(jī)器上的連接數(shù)在達(dá)到一定連接數(shù) (大概 4.5W) 連接數(shù)之后會突然急速下降到幾百。在應(yīng)用上的表現(xiàn)就是大量的連接報(bào)錯(cuò),系統(tǒng)失去響應(yīng),如下圖所示:
思路
思路 1: 第一步肯定是懷疑代碼寫錯(cuò)了,筆者看了下,使用的是成熟的框架,不是自己操作的連接,那么代碼的問題應(yīng)該較小。
思路 2:那么筆者就開始懷疑是內(nèi)核的限制,例如文件描述符到頂了之類,但這又有一個(gè)矛盾點(diǎn)。一旦是內(nèi)核對連接數(shù)量限制的話,應(yīng)該是連接數(shù)到達(dá)一定程度就漲不上去,而不是連接數(shù)跳水式下降。
思路 2.1: 進(jìn)一步,筆者就開始想,很有可能是某個(gè)間接資源的限制導(dǎo)致到達(dá)這個(gè)瓶頸后,所有的連接獲取這個(gè)資源獲取不到而導(dǎo)致全部報(bào)錯(cuò)。再結(jié)合 TCP 連接消耗的資源無非就是 CPU / 內(nèi)存 / 帶寬。
監(jiān)控信息
有了上面的思路,我們就可以觀察相關(guān)監(jiān)控信息了。 CPU 監(jiān)控:CPU 消耗很高達(dá)到了將近 70%,但獲取不到 CPU 一般只會導(dǎo)致響應(yīng)變慢,和問題現(xiàn)象不匹配。 帶寬監(jiān)控:帶寬利用率達(dá)到了 50%,這個(gè)帶寬利用率算不上高。 內(nèi)存監(jiān)控:確實(shí)使用了大量的內(nèi)存,RSS 達(dá)到了 26G,但是比起 128G 的內(nèi)存而言,這點(diǎn)消耗量顯然不可能成為瓶頸。 好了,看了這三個(gè)數(shù)據(jù)之后,就發(fā)現(xiàn)系統(tǒng)的資源消耗還稱不上達(dá)到瓶頸。但是,筆者從一開始就懷疑內(nèi)存的使用可能觸發(fā)了某個(gè)特殊的瓶頸。因?yàn)橹挥袃?nèi)存資源申請不到之后,TCP 連接才有可能直接報(bào)錯(cuò)進(jìn)而 Drop 連接。
TCP 監(jiān)控信息
當(dāng)傳統(tǒng)的監(jiān)控已經(jīng)不足以分析我們問題的時(shí)候,筆者就直接掏出針對 TCP 問題最有效的統(tǒng)計(jì)命令了,祭出法寶:
# 這條命令詳細(xì)的輸出了tcp連接的各種統(tǒng)計(jì)參數(shù),很多問題都可以通過其輸出獲得線索
netstat -s
筆者在這條命令的輸出中詳細(xì)的觀察 TCP 以及 TCP 內(nèi)存相關(guān)的輸出項(xiàng),定睛一看,就發(fā)現(xiàn)一個(gè)很不尋常的地方:
...
TcpExt:
TCP ran low on memoery 19 times
......
這個(gè)輸出就和筆者對于內(nèi)存限制的猜想完全對應(yīng)起來了。TCP 內(nèi)存不夠了,導(dǎo)致讀取或者寫入數(shù)據(jù)的時(shí)候申請內(nèi)存失敗進(jìn)而將 TCP 連接本身給 Drop 了。
修改內(nèi)核參數(shù)
因?yàn)楣P者之前詳細(xì)的閱讀過 Linux TCP 的源代碼以及其所有的可調(diào)整的內(nèi)核參數(shù)。所以對 TCP 的內(nèi)存限制有映像。有了 GPT 之后,只需要知道一個(gè)大致的方向就好了,直接問 GPT 就給出了答案,就是 tcp_mem 這個(gè)參數(shù)。
cat /proc/sys/net/ipv4/tcp_mem
1570347 2097152 3144050
這三個(gè)值分別代表了 tcp 對于內(nèi)存在不同閾值下的不同使用策略,單位是頁,也就是 4KB。具體解釋可以直接去問 GPT,在此就不贅述了。核心就是 TCP 消耗的內(nèi)存總量在大于第三個(gè)值也就是 3144050 (12G,占 128G 內(nèi)存的 9.35%) 的時(shí)候 TCP 就開始由于內(nèi)存申請不到而 Drop 連接。而對應(yīng)的應(yīng)用由于每個(gè)請求高達(dá)好幾 M 確實(shí)會讓每個(gè) TCP 連接消耗大量的內(nèi)存。
在內(nèi)存消耗過程中一旦超限,那么 TCP 連接就會被內(nèi)核強(qiáng)制 Drop,這也解釋了為什么基本所有連接在很短的時(shí)間內(nèi)就跳水式 Drop,因?yàn)樗麄兌荚诓煌I暾垉?nèi)存,而達(dá)到臨界閾值后全部都報(bào)錯(cuò),進(jìn)而整個(gè)系統(tǒng)的所有連接都關(guān)閉導(dǎo)致系統(tǒng)失去響應(yīng)。如下圖所示:
圖片
知道是這個(gè)問題就很簡單了,直接將 tcp_mem 調(diào)大即可:
cat /proc/sys/net/ipv4/tcp_mem
3570347 6097152 9144050
調(diào)整后系統(tǒng)保持穩(wěn)定
在經(jīng)過響應(yīng)的內(nèi)核調(diào)整之后,系統(tǒng)的連接數(shù)超過了 5W 之后依舊保持穩(wěn)定。這時(shí)候我們觀察相關(guān)的 TCP 消耗內(nèi)存頁的輸出:
cat /proc/net/sockstat
TCP: inuse xxx orphan xxx tw xxx alloc xxxx mem 4322151
從這個(gè)輸出我們可以看到系統(tǒng)平穩(wěn)運(yùn)行后,其常態(tài)使用的內(nèi)存頁數(shù)量 mem 為 4322151 已經(jīng)遠(yuǎn)大于之前的 3144050,這也從側(cè)面驗(yàn)證了筆者的判斷。
對應(yīng)的內(nèi)核棧
在此記錄下對應(yīng)的 Linux 內(nèi)核棧
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)
可以看到當(dāng) allocated 大于相關(guān)的內(nèi)存 limit 之后 Linux Kernel 會將此 TCP 連接直接 Drop。
總結(jié)
筆者在了解清楚 Bug 現(xiàn)場之后,大概花了 20 分鐘就定位到了是 TCP 內(nèi)存瓶頸的問題,然后借助 GPT 非??焖俚恼业搅讼嚓P(guān)解決方案。不得不說 GPT 能夠大幅加速我們搜索的過程,筆者個(gè)人感覺可以在很大程度上替代搜索引擎。但喂給 GPT 的 Prompt 還是需要通過 Bug 現(xiàn)場以及一定的經(jīng)驗(yàn)來構(gòu)造,它代替不了你的思考,但能大幅加速信息的檢索。