欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

怎么排查goroutine泄漏問題

這篇文章主要介紹“怎么排查goroutine泄漏問題”,在日常操作中,相信很多人在怎么排查goroutine泄漏問題問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么排查goroutine泄漏問題”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習吧!

創(chuàng)新互聯(lián)公司致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營銷,包括網(wǎng)站制作、成都網(wǎng)站設(shè)計、SEO優(yōu)化、網(wǎng)絡(luò)推廣、整站優(yōu)化營銷策劃推廣、電子商務(wù)、移動互聯(lián)網(wǎng)營銷等。創(chuàng)新互聯(lián)公司為不同類型的客戶提供良好的互聯(lián)網(wǎng)應(yīng)用定制及解決方案,創(chuàng)新互聯(lián)公司核心團隊10多年專注互聯(lián)網(wǎng)開發(fā),積累了豐富的網(wǎng)站經(jīng)驗,為廣大企業(yè)客戶提供一站式企業(yè)網(wǎng)站建設(shè)服務(wù),在網(wǎng)站建設(shè)行業(yè)內(nèi)樹立了良好口碑。

在 golang 中創(chuàng)建 goroutine 是一件很容易的事情,但是不合理的使用可能會導(dǎo)致大量 goroutine  無法結(jié)束,資源也無法被釋放,隨著時間推移造成了內(nèi)存的泄漏。避免 goroutine 泄漏的關(guān)鍵是要合理管理 goroutine 的生命周期,通過導(dǎo)出  runtime 指標和利用 pprof 可以發(fā)現(xiàn)和解決 goroutine 泄漏問題。

筆者維護了一個通過 SSH 連接到目標機器并執(zhí)行命令的服務(wù),這是一個內(nèi)部小服務(wù),平時沒有問題的時候一般也不會關(guān)注。大約 4  個月前,最后一次更新的時候,增加了一個任務(wù)計數(shù)器并且導(dǎo)出到 prometheus 中監(jiān)控起來。近期發(fā)現(xiàn)這個計數(shù)器在穩(wěn)步增加。

怎么排查goroutine泄漏問題

第一反應(yīng)是,好事!調(diào)用量穩(wěn)步增長了!!但是一想不對啊,這內(nèi)部小服務(wù)哪兒來這么多調(diào)用量。于是再看看 goroutine 的監(jiān)控情況(這個數(shù)據(jù)從  runtime.NumGoroutine()獲取的)

怎么排查goroutine泄漏問題

goroutine 的數(shù)量也是穩(wěn)步增加的,單位時間請求量增加,goroutine 數(shù)量也增進,沒毛病。但是又再轉(zhuǎn)念一想,內(nèi)部小服務(wù),不可能不可能。

于是再看一下所有請求在 mm 系統(tǒng)的視圖:

怎么排查goroutine泄漏問題

可以看出,每 5 分鐘請求量在 2000 左右,平均下來每分鐘 400 的請求量,上面 prometheus 監(jiān)控圖中,每個曲線是一個實例,實際上部署了  4 個實例,因此 400 還要除以 4 得到單個實例(曲線)的請求量應(yīng)該在 100/min 左右,在服務(wù)剛啟動的時候該計數(shù)器也確實在 100/min  左右,隨著時間推移慢慢泄漏了。

Goroutine 泄漏 (Goroutine leak)

雖然心里想著 99%是泄漏了,但是也要看點詳細的信息。之前在服務(wù)里已經(jīng)啟用了 net/http/pprof,因此直接請求 pprof 暴露出來的 HTTP  接口。

先看一下導(dǎo)出的 goroutine 摘要:

怎么排查goroutine泄漏問題

有 1000+個 goroutine 處于同一個狀態(tài),簡單看是等待讀數(shù)據(jù),再看下導(dǎo)出的 goroutine 詳情:

怎么排查goroutine泄漏問題

不看不知道,一看嚇一跳,詳情里有 goroutine 阻塞的時間超過了 20w 分鐘(4 個月)……

可以肯定是 goroutine 泄漏無疑了。為什么會泄漏?只有順著 pprof 導(dǎo)出的 goroutine 信息去排查了。處于 IO wait  狀態(tài)最多的這 1000 多 goroutine 的調(diào)用棧都打出來了,根據(jù)這段調(diào)用棧內(nèi)容來看,找到對應(yīng)代碼的位置,從 ssh.Dial 開始一直到某個地方進行  io.ReadFull 便阻塞住了。

這個服務(wù)進行 ssh 連接使用的是 golang.org/x/crypto/ssh 這個包。先看一下在這個服務(wù)里調(diào)用 ssh.Dial 的地方:

clientConfig := &ssh.ClientConfig{     ...     Timeout: 3 * time.Second,     ... } // connet to ssh client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", s.Host, 36000), clientConfig)

看起來是沒啥問題的,畢竟傳入了一個 Timeout 參數(shù),不應(yīng)該會阻塞。接著繼續(xù)看下去發(fā)現(xiàn)了一些問題。直接來到調(diào)用棧中阻塞的地方(先不看 library  和 runtime,這兩個一般沒問題),是在進行 SSH Handshake 的第一個步驟,交換 SSH 版本這步。

// Sends and receives a version line.  The versionLine string should // be US ASCII, start with "SSH-2.0-", and should not include a // newline. exchangeVersions returns the other side's version line. func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {     ...     if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {         return     }      them, err = readVersion(rw)     return them, err }  // maxVersionStringBytes is the maximum number of bytes that we'll // accept as a version string. RFC 4253 section 4.2 limits this at 255 // chars const maxVersionStringBytes = 255  // Read version string as specified by RFC 4253, section 4.2. func readVersion(r io.Reader) ([]byte, error) {     versionString := make([]byte, 0, 64)     var ok bool     var buf [1]byte      for length := 0; length < maxVersionStringBytes; length++ {         _, err := io.ReadFull(r, buf[:]) // 阻塞在這里         if err != nil {             return nil, err         }         ...     }      ...     return versionString, nil }

看邏輯是在給對端發(fā)送完自己的版本信息后,等待對端回復(fù),但是一直沒有收到回復(fù)。但是為什么會沒回復(fù),為什么沒有超時,剛開始看到這里的我是懵逼的。我只能想到既然這些都阻塞在等待對端回復(fù)上,那么一定有對應(yīng)的連接存在,我先看看機器上的連接有什么問題。

TCP 連接的半打開狀態(tài) (TCP half-open)

在機器上執(zhí)行了一下 netstat 命令看了下連接數(shù)。

# netstat -anp|grep :36000|awk '{print $6}'|sort|uniq -c  2110 ESTABLISHED       1 LISTEN      41 TIME_WAIT

有大量處于 ESTABLISHED 的進程,數(shù)量和 goroutine 數(shù)能大致對上。先把注意力放到這些連接上,選其中一兩個看看有什么問題吧。

接著便發(fā)現(xiàn),有些連接,在本機有 6 個連接:

怎么排查goroutine泄漏問題

但是,對端一個也沒有(圖上那一個連接是我登錄到目標機器的 ssh 連接):

怎么排查goroutine泄漏問題

google 查了下,發(fā)現(xiàn)這種情況屬于 TCP 半打開狀態(tài),出現(xiàn)這種情況應(yīng)該是建立連接后對端掛掉了或者其他網(wǎng)絡(luò)無法連通的原因,而連接又沒有啟動  KeepAlive,導(dǎo)致一端無法發(fā)現(xiàn)這種情況,繼續(xù)顯示 ESTABLISHED  的連接,而另一端在機器掛掉重新啟動后便不存在這條鏈接了。現(xiàn)在要確認一下是否真的沒用啟用 KeepAlive:

# ss -aeon|grep :36000|grep -v time|wc -l 2110

全部沒開&hellip;&hellip;這里不帶 KeepAlive 的連接數(shù)和上面 netstat 顯示出來狀態(tài)為 ESTABLISHED  狀態(tài)的連接數(shù)一致,實際上在執(zhí)行這兩條命令的間隙肯定有新請求進來,這兩個數(shù)字對上不能說完全匹配,只能說大多數(shù)是沒有開啟的。這里能 Get 到點就行。

再看一下 ssh.Dial 的邏輯,建立連接用的是 net.DialTimeout,而現(xiàn)網(wǎng)發(fā)生泄漏的版本是用 go1.9.2 編譯的,這個版本的  net.DialTimeout 返回的 net.Conn 結(jié)構(gòu)體的 KeepAlive 是默認關(guān)閉的(go1.9.2/net/client.go )。

golang.org/x/crypto/ssh 包在調(diào)用 net.DialTimeout 時不會顯式啟用 KeepAlive,完全依賴于當前 go  版本的默認行為。在最新版的 go 里面已經(jīng)把建立 TCP 連接時啟動 KeepAlive 作為默認行為了,于是這里我把代碼遷移到 go1.13.3  重新編譯了一次發(fā)到現(xiàn)網(wǎng)了,以為問題就塵埃落定了。

SSH 握手阻塞 (SSH Handshake hang)

實際上不是的。用 go1.13.3 編譯的版本,運行一段時間后,用 pprof 看 goroutine 情況,還是存在不少處于 IO wait  狀態(tài)的,并且看調(diào)用棧還是原來的味道(SSH handshake 時交換版本信息)。再看一下機器上的連接情況:

# netstat -anp|grep :36000|awk '{print $6}'|sort|uniq -c      81 ESTABLISHED       1 LISTEN       1 SYN_SENT      23 TIME_WAIT # ss -aeon|grep :36000|grep time|wc -l 110 # ss -aeon|grep :36000|grep -v time|wc -l 1 # ss -aeon|grep :36000|grep -v time LISTEN     0      128         100.107.1x.x6:36000                    *:*      ino:2508898466 s

不帶 KeepAlive 那個連接是本機監(jiān)聽 36000 端口的 sshd,其他都帶上了,那沒什么問題。說明這些阻塞住的應(yīng)該不是因為 TCP  半打開導(dǎo)致阻塞的,選其中一個 IP 出來看看。

怎么排查goroutine泄漏問題

用 telnet 可以連上,但是無法斷開連接。說明 TCP 連接是可以建立的,對端卻因為一些不可知的原因不響應(yīng)。再看看這個 IP 的連接存在多久了

# netstat -atnp|grep 10.100.7x.x9 tcp        0      0 100.107.1x.x6:8851        10.100.7x.x9:36000         ESTABLISHED 33027/ssh_tunnel_se # lsof -p 33027|grep 10.100.7x.x9 ssh_tunne 33027  mqq   16u  IPv4 3069826111      0t0        TCP 100-107-1x-x6:8504->10.100.7x.x9:36000 (ESTABLISHED) # ls -l /proc/33027/fd/16 lrwx------ 1 mqq mqq 64 Dec 23 15:44 /proc/33027/fd/16 -> socket:[3069826111]

執(zhí)行這個命令的時間是 24 日 17 時 25 分,已經(jīng)阻塞一天多了。那這里的問題就是應(yīng)用層沒有超時控制導(dǎo)致的。再回過去看 ssh.Dial  的邏輯,Timeout 參數(shù)在 SSH handshake 的時候并沒有作為超時控制的參數(shù)使用。net.Conn 的 IO 等待在 Linux 下是用非阻塞  epoll_pwait 實現(xiàn)的,進入等待的 goroutine 會被掛起直到有事件進來,超時是通過設(shè)置 timer 喚醒 goroutine  進行處理的,暴露出來的接口便是 net.Conn 的 SetDeadline 方法,于是重寫了 ssh.Dial 的邏輯,給 SSH

handshake 階段添加超時:

// DialTimeout starts a client connection to the given SSH server. Differ from // ssh.Dial function, this function will be timeout when doing SSH handshake. // total timeout = ( 1 + timeFactor ) * config.Timeout // refs: https://github.com/cybozu-go/cke/pull/81/files func DialTimeout(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {     conn, err := net.DialTimeout(network, addr, config.Timeout)     if err != nil {         return nil, err     }      // set timeout for connection     timeFactor := time.Duration(3)     err = conn.SetDeadline(time.Now().Add(config.Timeout * timeFactor))     if err != nil {         conn.Close()         return nil, err     }      // do SSH handshake     c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)     if err != nil {         return nil, err     }      // cancel connection read/write timeout     err = conn.SetDeadline(time.Time{})     if err != nil {         conn.Close()         return nil, err     }     return ssh.NewClient(c, chans, reqs), nil }

用這個函數(shù)替換了 ssh.Dial 后,編譯上線,看下連接情況,恢復(fù)正常了。(恢復(fù)到一個小服務(wù)應(yīng)該有的樣子)

# netstat -anp|grep :36000|awk '{print $6}'|sort|uniq -c       3 ESTABLISHED       1 LISTEN      86 TIME_WAIT

到這里會發(fā)現(xiàn),其實本文解決的問題是對端如果出現(xiàn)各種異常了,如何及時關(guān)閉連接,而不是去解決對端的異常問題。畢竟 SSH  都異常了,誰還能上去查問題呢。現(xiàn)網(wǎng)服務(wù)器數(shù)量巨大,運行情況各不相同,因此出現(xiàn)異常也屬情理之中,一一解決不太現(xiàn)實。

結(jié)尾

剛開始發(fā)現(xiàn)泄漏的時候到機器上 top 看了下,當時被 50G 的 VIRT  占用給嚇著了,在咨詢了組內(nèi)大佬(zorro)的后,實際上這個值大多數(shù)時候都不用關(guān)心,只需關(guān)心 RES 占用即可。因為 RES 是實際占用的物理內(nèi)存。

怎么排查goroutine泄漏問題

只看這一個時間點的 VIRT 和 RES  也是看不出到底有多少是泄漏的。只能和不同的時間點的內(nèi)存占用對比,比如解決問題以后的版本,運行了三四天的情況下,VIRT 占用是 3.9G,而 RES 只占用了  16M。這樣比下來看,還是釋放了不少內(nèi)存?;蛘哒f可以見得泄漏的那些 goroutine 占據(jù)了多少內(nèi)存。

在 golang 中創(chuàng)建 goroutine 是一件很容易的事情,但是不合理的使用可能會導(dǎo)致大量 goroutine  無法結(jié)束,資源也無法被釋放,隨著時間推移造成了內(nèi)存的泄漏。

避免 goroutine 泄漏的關(guān)鍵是要合理管理 goroutine 的生命周期,通過 prometheus/client_golang 導(dǎo)出  runtime 指標和利用 net/http/pprof 可以發(fā)現(xiàn)和解決 goroutine 泄漏問題。

到此,關(guān)于“怎么排查goroutine泄漏問題”的學(xué)習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習,快去試試吧!若想繼續(xù)學(xué)習更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

文章標題:怎么排查goroutine泄漏問題
網(wǎng)站網(wǎng)址:http://aaarwkj.com/article26/igsicg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT網(wǎng)站建設(shè)、網(wǎng)站排名、用戶體驗、網(wǎng)站設(shè)計、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)
久久精品一区二区日韩| 国精品91人妻一区二区| 国产精品亚洲精品久久| 濑亚美莉在线观看一区二区三区| 日韩欧美国产精品福利| 四虎永久播放地址免费| 免费人成在线观看网站免费观看| 亚洲成人av毛片在线观看| 中文日韩av在线免费播放| 欧美夫妻香蕉视频网站| 亚洲成av人片青草影院| 国产精品一区二区三区久久| 成人高清在线观看91| 麻豆国产自拍在线视频| 蜜臀视频在线观看免费| 国产白浆一区二区视频| 少妇又色又爽又高潮欧美| 亚洲精品在线观看毛片| 国产精品久久久毛片av| 日本 一区二区在线| 国内久久婷婷综合五月趴| 99久久久国产精品蜜臀| 亚洲国产精品激情在线| 中文字幕伦理一区二区三区| 日本一区二区中文字幕视频| 亚洲一区二区中文字幕av| 中文字幕乱码一区二区欧美| 97全国免费观看视频| 国产欧美一区二区另类精品| 白白在线观看网站看看亚洲| 欧美艳星一区二区三区四区 | 韩国三级网站在线观看视频| av一区二区三区三| 国语自产拍在线观看不卡| 可以免费看的日韩黄色| 国产一级一片内射视频| 神马久久午夜免费福利| 91看看午夜福利视频| 81精品国产综合久久精品伦理| 日本在线一区二区三区免费视频| 激情五月开心五月麻豆|