「Go實(shí)戰(zhàn)營(yíng)系列」大型生產(chǎn)系統(tǒng)的問(wèn)題定位
本文是武漢 gopher meetup 的分享內(nèi)容整理而成,分享內(nèi)容在“無(wú)人值守”的兩篇和其它社區(qū)分享中亦有提及。(也就是說(shuō)你看過(guò)那兩篇,這個(gè)可以不用看了)
先來(lái)看看苦逼的開(kāi)發(fā)人員
老板說(shuō):

隊(duì)友說(shuō):

外組同事說(shuō):

底層團(tuán)隊(duì)說(shuō):

你:

業(yè)界的思路?
混口飯吃也是不容易,既然有問(wèn)題了,我們還是要解決的。要先看看有沒(méi)有現(xiàn)成的思路可以借鑒?
Google 在這篇論文里提到過(guò)其內(nèi)部的線(xiàn)上 profile 流程:

架構(gòu)圖已經(jīng)比較簡(jiǎn)單了,線(xiàn)上應(yīng)用暴露 profile 接口,collector 定時(shí)將 profile 信息采集到二進(jìn)制存儲(chǔ),由統(tǒng)一的在線(xiàn)平臺(tái)展示。
這篇論文催生了一些開(kāi)源項(xiàng)目和創(chuàng)業(yè)公司,例如在 這篇文章 中,對(duì) continuous profiling 有很不錯(cuò)的解釋。

我們?nèi)粘5?CI 和 CD 流水線(xiàn)可以高頻次發(fā)布線(xiàn)上系統(tǒng),當(dāng)線(xiàn)上有 continuous profiling 系統(tǒng)在運(yùn)行時(shí),每次發(fā)布我們都能夠得到實(shí)時(shí)的性能快照,并與發(fā)布前的性能做快速對(duì)照。
系統(tǒng)的性能問(wèn)題和其它技術(shù)問(wèn)題一樣,同樣是發(fā)現(xiàn)得越早,解決起來(lái)就越快,損失越小。
Google Cloud 上也有 profiler 相關(guān)的產(chǎn)品,是由 Go 原先的 pprof 開(kāi)發(fā)者開(kāi)發(fā)的,她在文章中聲稱(chēng)每分鐘對(duì)應(yīng)用采集 10s 的 profile,大約有 5% 的性能損失,還是可以接受的。
簡(jiǎn)單來(lái)說(shuō),continuous profiling 帶給我們的優(yōu)勢(shì)主要就是三點(diǎn):
縮短性能問(wèn)題反饋周期 節(jié)省服務(wù)器成本 改進(jìn)開(kāi)發(fā)者的工作流程
有了思路,再看看有沒(méi)有具體的開(kāi)源產(chǎn)品可以參考。
相關(guān)開(kāi)源產(chǎn)品

stackimpact-go 是社區(qū)比較早的開(kāi)源項(xiàng)目了,不過(guò)只開(kāi)源了 client 部分,所以 dashboard 和后端部分一直是一個(gè)謎,不過(guò)我們大概也能看出來(lái),有這種曲線(xiàn)圖形式的 profile 數(shù)據(jù),有性能衰減時(shí)能及時(shí)發(fā)現(xiàn)。

conprof 其實(shí)也差不多。

profefe 也差不多。
看起來(lái)思路都一樣,這個(gè)需求很簡(jiǎn)單,我們只要找監(jiān)控團(tuán)隊(duì)幫忙做一套皮膚就可以了!
但監(jiān)控團(tuán)隊(duì)也很無(wú)奈。

我們的方案
求人不如求己,我們需要定位的是抖動(dòng)問(wèn)題,那我們以 5s 為單位,把進(jìn)程的 CPU 使用(gopsutil),RSS(gopsutil),goroutine 數(shù)(runtime.NumGoroutine),用 10 大小的環(huán)形數(shù)組保存下來(lái),每次采集到新值時(shí),與之前多個(gè)周期的平均值進(jìn)行 diff 就可以了:

當(dāng)波動(dòng)率超過(guò)可以接受的范圍,則認(rèn)為當(dāng)前進(jìn)程發(fā)生了資源使用抖動(dòng),那么:
若 CPU 抖動(dòng),從當(dāng)前開(kāi)始采集 CPU profile 5秒 若 RSS 抖動(dòng),將當(dāng)前進(jìn)程的 heap profile 記錄下來(lái) 若 goroutine 抖動(dòng),將當(dāng)前進(jìn)程的 goroutine profile 記錄下來(lái)
一套方案做下來(lái),還挺簡(jiǎn)單的。當(dāng)我們收到模塊的 CPU、內(nèi)存或 goroutine 數(shù)報(bào)警時(shí),上線(xiàn)到相應(yīng)的實(shí)例來(lái)查看即可。
實(shí)際的案例
解碼 bug
某模塊突然出現(xiàn)了 RSS 使用飚升,上線(xiàn)看之后發(fā)現(xiàn)自動(dòng) dump 文本的 profile 中,單個(gè)對(duì)象的 inuse_space 超過(guò)了 1GB:
// inuse_objects: inuse_space [alloc_objects : alloc_space]
1 : 1024000000 [1 : 1024000000] git.xxx.xxx
這是很反常的,閱讀代碼后發(fā)現(xiàn)在 decode 中沒(méi)有對(duì)這些情況進(jìn)行一定的防御操作,有形如下面的代碼:
var l = readLenFromPacket()
var list = make([]byte, l)
雖然理論上 body 是可以傳 1GB 的,不過(guò)內(nèi)部的 RPC 框架,還是應(yīng)該對(duì)這種情況進(jìn)行一些限制。
CPU 尖刺
有些在線(xiàn)系統(tǒng)是定時(shí)任務(wù)調(diào)用的,所以其訪問(wèn)峰值非常不平均,在定時(shí)任務(wù)觸發(fā)后,會(huì)有一段時(shí)間 CPU 使用非常高,有了自動(dòng) CPU profile dump,就非常容易找到具體哪里使用的 CPU 很高了。
goroutine 泄露

類(lèi)似這樣的 goroutine 泄露問(wèn)題,也是可以很容易發(fā)現(xiàn)的。
deadlock(擴(kuò)展)
因?yàn)槲覀兛梢圆杉剿?goroutine 的棧信息,所以理論上通過(guò)遍歷我們可以發(fā)現(xiàn)哪些 goroutine 是持鎖的,例如下列代碼,當(dāng)發(fā)生死鎖時(shí),我們可以直接把持鎖的 goroutine dump 下來(lái),就很容易發(fā)現(xiàn)死鎖了:

thread block(擴(kuò)展)
,當(dāng)線(xiàn)程因?yàn)檎{(diào)用 cgo 等原因發(fā)生了阻塞,會(huì)造成線(xiàn)程數(shù)暴漲,這時(shí)候我們可以將 goroutine 和 thread profile dump 下來(lái),進(jìn)行診斷。
總結(jié)
有了自動(dòng)化的工具,日子更好過(guò)了。
想要獲取更多關(guān)于本書(shū)的相關(guān)信息,趕緊掃碼進(jìn)群哦~
如果群滿(mǎn),可加小助手,就能拉你一起加入群聊啦

