日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

无人值守的自动 dump(二)

發布時間:2024/4/11 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 无人值守的自动 dump(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

之前在這篇無人值守(一)[1]簡單介紹了我們針對線上抖動問題定位的工具的設計思路,思路很簡單,技術含量很低,是個人都可以想得到,但是它確實幫我們查到了很多很難定位的問題。

在本篇里,我們重點講一講這個工具[2]在生產環境幫我們發現了哪些問題。

OOM 類問題

RPC decode 未做防御性編程,導致 OOM

應用側的編解碼可能是非官方實現(如 node 之類的無官方 SDK 的項目),在一些私有協議 decode 工程中會讀諸如 list len 之類的字段,如果外部編碼實現有問題,發生了字節錯位,就可能會讀出一個很大的值。

非標準 app ----encode-------> 我們的應用 decode -----> Boom!

decoder 實現都是需要考慮這種情況的,類似這樣[3]。如果對請求方的數據完全信任,碰到對方的 bug 或者惡意攻擊,可能導致自己的服務 OOM。

在線上實際發現了一例內存瞬間飚升的 case,收到報警后,我們可以看到:

1:?1330208768?[1:?1330208768]?@?0x11b1df3?0x11b1bcb?0x119664e?0x11b1695?0x1196f77?0x11a956a?0x11a86c7?0x1196724?0x11b1695?0x11b1c29?0x119664e?0x11b1695?0x11b1c29?0x119664e?0x11b1695?0x11b1c29?0x119664e?0x11bb360?0x168f143?0x179c2fc?0x1799b70?0x179acd6?0x16d3306?0x16d1088?0xf59386?0xf59474?0xf54e5f?0xf54987?0xf539f1?0xf6043a?0xcd8c0d?0x49b481 ....下面是表示棧內容的,這不重要

1: 1330208768 [1: 1330208768] 表示 inuse_objects : inuse_space [alloc_objects : alloc_space],這里可以看到一個對象就直接用掉了 1GB 內存,顯然不是正常情況,查看代碼后,發現有未進行大小判斷而完全信任用戶輸入數據包的 decode 代碼。

修復起來很簡單,像前面提到的 async-h1 一樣加個判斷就可以了。

tls 開啟后線上進程占用內存上漲,直至 OOM

線上需要做全鏈路加密,所以有開啟 tls 的需求,但開啟之后發現內存一路暴漲,直至 OOM,工具可以打印如下堆棧:

heap?profile:?1460:?27614136?[45557:?1080481472]?@?heap/1048576 727:?23822336?[730:?23920640]?@?0xc56b96?0xc591e8?0xc58e68?0xc59ed1?0xdd55ff?0xde15b8?0xde13ef?0xde09e9?0xde050c?0x13bfa13?0x13bf475?0x14c33d0?0x14c49f8?0x14cb398?0x14bffab?0x14cdf78?0xddcf90?0x45eda1 #?0xc56b95??*****mtls/crypto/tls.(*block).reserve+0x75???*****mtls/crypto/tls/conn.go:475

查閱老版本的 Go 代碼,發現其 TLS 的 write buffer 會隨著寫出的數據包大小增加而逐漸擴容,其擴容邏輯比較簡單:

func?(b?*block)?reserve(n?int)?{if?cap(b.data)?>=?n?{return}m?:=?cap(b.data)if?m?==?0?{m?=?1024}for?m?<?n?{m?*=?2}data?:=?make([]byte,?len(b.data),?m)copy(data,?b.data)b.data?=?data }

初始為 1024,后續不夠用每次擴容為兩倍。但是閱讀 tls 的代碼后得知,這個寫出的數據包大小最大實際上只有 16KB + 額外的一個小 header 大小左右,但老版本的實現會導致比較多的空間浪費,因為最終會擴容到 32KB。

這段比較浪費空間的邏輯在 Go1.12 之后已經進行了優化:

func?sliceForAppend(in?[]byte,?n?int)?(head,?tail?[]byte)?{if?total?:=?len(in)?+?n;?cap(in)?>=?total?{head?=?in[:total]}?else?{head?=?make([]byte,?total)copy(head,?in)}tail?=?head[len(in):]return }

變成了需要多少,分配多少的樸實邏輯。所以會比老版本在這個問題上有不少緩解,不過在我們的場景下,新版本的代碼依然沒法滿足需求,所以還需要進一步優化,這就是后話了。

goroutine 暴漲類問題

本地 app GC hang 死,導致 goroutine 卡 channel send

在我們的程序中有一段和本地進程通信的邏輯,write goroutine 會向一個 channel 中寫數據,按常理來講,同物理機的兩個進程通過網絡通信成本比較低,類似下面的代碼按說不太可能出問題:

concurrently: taskChan?<-?taskconsumer: for?task?:=?range?taskChan?{//?憋一些?task?一起寫localConnection.write(task?們) }

看起來問題不大,但是線上經常都有和這個 channel send 相關的抖動,我們通過工具拿到的現場:

2020-11-03?08:00:05,950?[ERROR]?[diag.goroutine]?[diagnose]?pprof?goroutine,?config_min?:?3000,?config_diff?:?25,?config_abs?:?200000,??previous?:?[41402?44257?47247?50085?52795?55509?29762?32575?35451?38460],?current?:?55509,?profile?:?goroutine?profile:?total?55513 40844?@?0x46daaf?0x4433ab?0x443381?0x443165?0xf551f7?0x12fd2e7?0x12fc94f?0x13f41d5?0x13fc45f?0xf43ee4?0xcd8c0d?0x49b481 #????****channel.Send?這是個假的棧,你理解意思就行了 #?

當前憋了 5w 個 goroutine,有 4w 個卡在 channel send 上,這個 channel 的對面還是一條本地連接,令人難以接受。

但是要考慮到,線上的業務系統是 Java,Java 發生 FG? 的時候可不是鬧著玩的。對往本地連接的 write buffer 寫數據一定不會卡的假設是有問題的。

既然出問題了,說明在這里對我們的程序進行保護是必要的,修改起來也很簡單,給 channel send 加一個超時就可以了。

應用邏輯死鎖,導致連接不可用,大量 goroutine 阻塞在 lock 上

大多數網絡程序里,我們需要在發送應用層心跳,以保證在一些異常情況(比如拔網線)下,能夠把那些無效連接從連接池里剔除掉。

對于我們的場景來說,客戶端向外創建的連接,如果一直沒有請求,那么每隔一段時間會向外發送一個應用心跳請求,如果心跳連續失敗(超時) N 次,那么將該連接進行關閉。

在這個場景下會涉及到兩把鎖:

  • 對連接進行操作的鎖 conn lock

  • 記錄心跳請求的 request map lock

心跳成功的流程:收到心跳響應包,獲取 conn lock -> 獲取 request map lock 心跳失敗的流程:timer 超時,獲取 request map lock -> 需要關閉連接 -> 獲取 conn lock

可以看出來,心跳的成功和失敗流程并發時,獲取鎖的流程符合死鎖的一般定義:持有鎖、非搶占、循環等待。

這個 bug 比較難觸發,因為心跳失敗要失敗 N 次才會關閉連接,而正好在最后一次發生了心跳成功和失敗并發才會觸發上述的死鎖,線上可以通過 goroutine 短時間的上漲發現這個問題,goroutine 的現場也是可以看得到的。簡單分析就可以發現這個死鎖問題(因為后續的流程都會卡在其中一把鎖上)。

知道原因解決起來就不麻煩了,涉及到一些具體的業務邏輯,這里就不贅述了。

CPU 尖刺問題

應用邏輯導致死循環問題

國際化業務涉及到冬夏令時的切換,從夏令時切換到冬令時,會將時鐘向前拔一個月,但天級日志輪轉時,會根據輪轉前的時間計算 24 小時后的時間,并按與 24:00 的差值來進行 time.Sleep,這時會發現整個應用的 CPU 飚高。自動采樣結果發現一直在循環計算時間和重命名文件。

list 一下相關的函數,能很快地發現執行死循環的代碼位置。這里就不截真實代碼了,隨便舉個例子:

????????.??????????.?????23:func?cpuex(wr?http.ResponseWriter,?req?*http.Request)?{.??????????.?????24:?go?func()?{17.73s?????19.37s?????25:??for?{.??????????.?????26:??}.??????????.?????27:?}().??????????.?????28:}

參考資料

[1]

無人值守(上): https://xargin.com/autodumper-for-go/

[2]

工具: https://github.com/mosn/holmes

[3]

這樣: https://github.com/http-rs/async-h1/blob/main/src/server/decode.rs#L41

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的无人值守的自动 dump(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。