曹大帶我學 Go(8)—— 一個打點引發的事故

你好,我是小 X。

曹大最近開 Go 課程了,小 X 正在和曹大學 Go。

這個系列會講一些從課程中學到的讓人醍醐灌頂的東西,撥雲見日,帶你重新認識 Go。

最近線上事故頻發,搞得焦頭爛額,但是能用上跟曹大學的知識並定位出了問題,還是值得高興一把的。畢竟 “打破砂鍋問到底”,“定位出根因” 一直是技術人的優良品質。

雖然我們總是逃不過事故驅動開發的魔咒,但喫一塹長一智,看別人的事故,學到的是自己的能力。

現象

一個平凡的午高峯,服務在全量上線的過程中,碰到一個非常重要的下游接口超時(後來發現該下游也在上線,可用實例數變少,正常實例負載變高,超時了一丟丟),拿不到該拿的數據,阻塞了啓動。這樣,打到線上正常實例上的流量就增加了。

不大會兒,線上大量實例 OOM,報警滿天飛。不得已,回滾(遇到事故,第一件事就是回滾)。沒想到,回滾無效,OOM 的實例數還是一直在增加,給跪了。

中間查到服務在啓動過程中,一直在嘗試調那個超時的下游,就臨時把超時時間加大,並取消回滾,緊急上線了一把。

之後,正常啓動的實例越來越多,服務逐漸恢復。

第一天排查

發生事故之後就是排查過程了。

第一天,只查到了 OOM 的實例 goroutine 數暴漲,接口 QPS 有尖峯,比正常翻了幾倍。所以,得到的結論就是接口流量太多,超過服務極限,導致 OOM。

接着嘗試在預覽環境壓測,沒有復現 OOM,gg。

結論有問題!

第二天排查

第二天運氣比較好,發現了線上有一個實例在不斷地重啓,一看監控,發現正常啓動後,5 分鐘左右就 OOM 了。

有現場就不慌。

正好跟着曹大學會了如何用 pprof 查問題,馬上就安排,三下五除二就搞定了。

先看 inuse_space:

inuse_space

雖然,這個 model 佔用的內存比較高,但這是正常的業務邏輯,看了看相關的代碼最近也沒有改動。

所以,這個只能是果,不是因。

再看 CPU:

CPU

中間加粗的紅色線條和方框就差要告訴我有問題的代碼在哪一行了!圖上方有調 metrics 的函數名(這裏沒展示出來),一搜就搜出來了。

再看了下 goroutine:

goroutine

結論還是一樣的。

順着圖上的函數名,馬上就在一個多重嵌套循環裏找到了一處不那麼顯眼的打點。

metrics 打點的劇本我熟,之前看了曹大的 “幾個 Go 系統可能遇到的鎖問題” 的文章(點擊閱讀原文可讀),邏輯大概是這樣的:

由於 metrics 底層是用 udp 發送的,有文件鎖,大量打點的情況下,會引起激烈的鎖衝突,造成 goroutine 堆積、請求堆積,和請求關聯的 model 無法釋放,於是就 OOM 了。

然後我們替換 bin 上到這臺 OOM 的機器,果然恢復正常了,收工!

最後看了下 metrics 的代碼,其實還不是文件鎖的原因。不過也差不多了,也是一把大鎖,所有 goroutine 的打點都會先 append 到一個 slice 裏,append 前要先加鎖。

由於這個地方的打點非常多,幾十萬 QPS,一衝突,goroutine 都 gopark 去等鎖了,持有的內存無法釋放,服務一會兒就 gg 了。

總結

引發這次事故的代碼其實是一年多前寫的,跑了一年都沒出事,這次就遇到了,你說可氣不?

幸虧不是自己寫的,但也要敲個警鐘:我寫的每一行代碼,將來都可能會引發事故,一定要認真對待。

遇到 OOM 不可怕,有現場就行。拿不到現場,我也沒啥好辦法。實在不行,用曹大的 holmes 試試,這個工具還是很厲害的,還幫曹大貢獻了個 golang 的 mr。

平時要多看相關的事故排查文章,必要的時候練習一下 pprof 工具的使用,關鍵的時候還是能頂用的。

好了,這就是今天全部的內容了~ 我是小 X,我們下期再見~


歡迎關注曹大的 TechPaper 以及碼農桃花源~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8puLJSJ3GeXnmDlMxdzGgQ