一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - 定位并修復 Go 中的內存泄露問題

定位并修復 Go 中的內存泄露問題

2021-11-24 11:29Go語言中文網 Golang

Go 是一門帶 GC 的語言,這篇文章回顧了我如何發現內存泄漏、如何修復它,以及我如何修復 Google 示例 Go 代碼中的類似問題,以及我們如何改進我們的庫以防止將來發生這種情況,感興趣的朋友一起看看吧

Go 是一門帶 GC 的語言,因此,大家很容易認為它不會有內存泄露問題。 大部分時候確實不會,但如果有些時候使用不注意,也會導致泄露。

本文案例來自谷歌云的代碼,探討如何找到并修復 Go 中的內存泄露。(確切來說是因為資源泄露導致的內存泄露,除了本文介紹的,還有一些其他泄露的情況)

這篇文章回顧了我如何發現內存泄漏、如何修復它,以及我如何修復 Google 示例 Go 代碼中的類似問題,以及我們如何改進我們的庫以防止將來發生這種情況。

Google Cloud Go 客戶端庫 [1] 通常在后臺使用 gRPC 來連接 Google Cloud API。創建 API 客戶端時,庫會初始化與 API 的連接,然后保持該連接處于打開狀態,直到你調用 Client.Close 。

?
1
2
3
client, err := api.NewClient()
// Check err.
defer client.Close()

客戶端可以安全地同時使用,所以你應該保持相同 Client 直到你的任務完成。但是,如果在應該 Close 的時候不 Close client 會發生什么呢?

會出現內存泄漏。底層連接永遠不會被清理。

Google 有一堆 GitHub 自動化機器人來幫助管理數百個 GitHub 存儲庫。我們的一些機器人通過在 Cloud Run [2] 上運行的 Go 服務器 [3] 代理它們的請求。我們的內存使用看起來像一個經典的鋸齒形內存泄漏:

我通過向服務器添加 pprof.Index 處理程序開始調試:

?
1
mux.HandleFunc("/debug/pprof/", pprof.Index)

`pprof` [4] 提供運行時 profiling 數據,如內存使用情況。有關更多信息,請參閱 Go 官方博客上的 profiling Go 程序 [5] 。

然后,我在本地構建并啟動了服務器:

?
1
2
$ go build
$ PROJECT_ID=my-project PORT=8080 ./serverless-scheduler-proxy

然后向服務器發送一些請求:

?
1
2
3
4
for i in {1..5}; do
  curl --header "Content-Type: application/json" --request POST --data '{"name": "HelloHTTP", "type": "testing", "location": "us-central1"}' localhost:8080/v0/cron
  echo " -- $i"
done

確切的有效負載和端點特定于我們的服務器,與本文無關。

為了獲得正在使用的內存的基線,我收集了一些初始 pprof 數據:

curl http://localhost:8080/debug/pprof/heap > heap.0.pprof

檢查輸出,你可以看到一些內存使用情況,但沒有什么會立即成為一個大問題(這很好!我們剛剛啟動了服務器!):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ go tool pprof heap.0.pprof
File: serverless-scheduler-proxy
Type: inuse_space
Time: May 4, 2021 at 9:33am (EDT)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 2129.67kB, 100% of 2129.67kB total
Showing top 10 nodes out of 30
      flat  flat%   sum%        cum   cum%
 1089.33kB 51.15% 51.15%  1089.33kB 51.15%  google.golang.org/grpc/internal/transport.newBufWriter (inline)
  528.17kB 24.80% 75.95%   528.17kB 24.80%  bufio.NewReaderSize (inline)
  512.17kB 24.05%   100%   512.17kB 24.05%  google.golang.org/grpc/metadata.Join
         0     0%   100%   512.17kB 24.05%  cloud.google.com/go/secretmanager/apiv1.(*Client).AccessSecretVersion
         0     0%   100%   512.17kB 24.05%  cloud.google.com/go/secretmanager/apiv1.(*Client).AccessSecretVersion.func1
         0     0%   100%   512.17kB 24.05%  github.com/googleapis/gax-go/v2.Invoke
         0     0%   100%   512.17kB 24.05%  github.com/googleapis/gax-go/v2.invoke
         0     0%   100%   512.17kB 24.05%  google.golang.org/genproto/googleapis/cloud/secretmanager/v1.(*secretManagerServiceClient).AccessSecretVersion
         0     0%   100%   512.17kB 24.05%  google.golang.org/grpc.(*ClientConn).Invoke
         0     0%   100%  1617.50kB 75.95%  google.golang.org/grpc.(*addrConn).createTransport

下一步是向服務器發送一堆請求,看看我們是否可以 (1) 重現可能的內存泄漏和 (2) 確定泄漏是什么。

發送 500 個請求:

?
1
2
3
4
for i in {1..500}; do
  curl --header "Content-Type: application/json" --request POST --data '{"name": "HelloHTTP", "type": "testing", "location": "us-central1"}' localhost:8080/v0/cron
  echo " -- $i"
done

收集和分析更多 pprof 數據:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl http://localhost:8080/debug/pprof/heap > heap.6.pprof
$ go tool pprof heap.6.pprof
File: serverless-scheduler-proxy
Type: inuse_space
Time: May 4, 2021 at 9:50am (EDT)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 94.74MB, 94.49% of 100.26MB total
Dropped 26 nodes (cum <= 0.50MB)
Showing top 10 nodes out of 101
      flat  flat%   sum%        cum   cum%
   51.59MB 51.46% 51.46%    51.59MB 51.46%  google.golang.org/grpc/internal/transport.newBufWriter
   19.60MB 19.55% 71.01%    19.60MB 19.55%  bufio.NewReaderSize
    6.02MB  6.01% 77.02%     6.02MB  6.01%  bytes.makeSlice
    4.51MB  4.50% 81.52%    10.53MB 10.51%  crypto/tls.(*Conn).readHandshake
       4MB  3.99% 85.51%     4.50MB  4.49%  crypto/x509.parseCertificate
       3MB  2.99% 88.51%        3MB  2.99%  crypto/tls.Client
    2.50MB  2.49% 91.00%     2.50MB  2.49%  golang.org/x/net/http2/hpack.(*headerFieldTable).addEntry
    1.50MB  1.50% 92.50%     1.50MB  1.50%  google.golang.org/grpc/internal/grpcsync.NewEvent
       1MB     1% 93.50%        1MB     1%  runtime.malg
       1MB     1% 94.49%        1MB     1%  encoding/json.(*decodeState).literalStore

google.golang.org/grpc/internal/transport.newBufWriter 使用大量內存真的很突出!這是泄漏與什么相關的第一個跡象:gRPC。查看我們的應用程序源代碼,我們唯一使用 gRPC 的地方是 Google Cloud Secret Manager [6] :

?
1
2
3
4
client, err := secretmanager.NewClient(ctx)
if err != nil {
    return nil, fmt.Errorf("failed to create secretmanager client: %v", err)
}

在每個請求創建 client 時,我們沒有調用 client.Close() !所以,我添加了一個 Close 調用,問題就消失了:

defer client.Close()

我提交了修復,然后 自動部署 [7] ,鋸齒立即消失了!

大約在同一時間,用戶在我們的 Cloud 的 Go 示例存儲庫中 [8] 提交了一個問題,其中包含 cloud.google.com 上 [9] 文檔的大部分 Go 示例。用戶注意到我們忘記調用 client.Close 了。

我曾多次看到同樣的事情出現,所以我決定調查整個 repo。

我開始粗略估計有多少受影響的文件。使用 grep ,我們可以獲得包含 NewClient 樣式調用的所有文件的列表,然后將該列表傳遞給另一個調用 grep 以僅列出不包含 Close 的文件,同時忽略測試文件:

$ grep -L Close $(grep -El 'New[^(]*Client' **/*.go) | grep -v test

竟然有 207 個文件……就上下文而言,我們 .go 在 GoogleCloudPlatform/golang-samples [10] 存儲庫中有大約 1300 個文件。

考慮到問題的規模,我認為一些自動化是 值得的 [11] 。我不想寫一個完整的 Go 程序來編輯文件,所以我使用 Bash:

$ grep -L Close $(grep -El 'New[^(]*Client' **/*.go) | grep -v test | xargs sed -i '/New[^(]*Client/,/}/s/}/}\ndefer client.Close()/'

它是完美的嗎?不。它對工作量有很大的影響嗎?是的!

第一部分(直到 test )與上面完全相同——獲取所有可能受影響的文件的列表(那些似乎創建了 Client 但從沒調用 Close 的文件)。

然后,我將該文件列表傳遞給 sed 進行實際編輯。 xargs 調用你給它的命令,每一行都以 stdin 作為參數傳遞給給定的命令。

要理解該 sed 命令,查看 golang-samples repo 示例是什么樣子有助于理解(省略導入和客戶端初始化后的所有內容):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// accessSecretVersion accesses the payload for the given secret version if one
// exists. The version can be a version number as a string (e.g. "5") or an
// alias (e.g. "latest").
func accessSecretVersion(w io.Writer, name string) error {
    // name := "projects/my-project/secrets/my-secret/versions/5"
    // name := "projects/my-project/secrets/my-secret/versions/latest"
    // Create the client.
    ctx := context.Background()
    client, err := secretmanager.NewClient(ctx)
    if err != nil {
        return fmt.Errorf("failed to create secretmanager client: %v", err)
    }
    // ...
}

在高層次上,我們初始化客戶端并檢查是否有錯誤。每當你檢查錯誤時,都會有一個右花括號 ( } )。我使用這些信息來自動化編輯。

但是,該 sed 命令仍然很笨拙:

sed -i '/New[^(]*Client/,/}/s/}/}\ndefer client.Close()/'

-i 表示直接編輯文件。這不是問題,因為代碼用 git 管理了。

接下來,我使用 s 命令在檢查錯誤 defer client.Close() 后假定的右花括號 ( } )之后插入。

但是,我不想替換每個 } ,我只想要在 調用 NewClient 后 的 第一個 。要做到這一點,你可以給一個 地址范圍 [12] 的 sed 搜索。

地址范圍可以包括在應用接下來的任何命令之前要匹配的開始和結束模式。在這種情況下,開始是 /New[^(]*Client/ ,匹配 NewClient 類型調用,結束(由 a 分隔 , )是 /}/ ,匹配下一個大括號。這意味著我們的搜索和替換僅適用于調用 NewClient 和結束大括號之間!

通過了解上面的錯誤處理模式, if err != nil 條件的右大括號正是我們想要插入 Close 調用的位置。

一旦我自動編輯了所有示例文件,我用 goimports 開始修復格式。然后,我檢查了每個編輯過的文件,以確保它做了正確的事情:

  • 在服務器應用程序中,我們應該關閉客戶端,還是應該保留它以備將來的請求使用?
  • 是 Client 實際的名字 client 還是別的什么?
  • 是否有一個以上的 Client 調用了 Close ?

完成后,只剩下 180 個已編輯的文件 [13] 。

最后一項工作是努力使其不再發生在用戶身上。我們想到了幾種方法:

  1. 更好的示例代碼;
  2. 更好的 GoDoc。我們更新了庫生成器,在生成庫時加上注釋,告知 client 需要調用 Close;
  3. 更好的庫。有沒有辦法可以自動 Close 客戶端?Finalizers?知道何能做得更好嗎?歡迎在 https://github.com/googleapis/google-cloud-go/issues/4498 上交流;

我希望你對 Go、內存泄漏 pprof 、gRPC 和 Bash 有所了解。我很想聽聽你關于發現的內存泄漏以及修復它們的方法的故事!如果你對我們如何改進我們的 庫 [14] 或 示例 [15] 有任何想法,請通過提交 issue 告訴我們。

參考資料

[1]
Google Cloud Go 客戶端庫: https://github.com/googleapis/google-cloud-go

[2]
Cloud Run: https://cloud.google.com/run/docs/quickstarts/build-and-deploy/go

[3]
Go 服務器: https://github.com/googleapis/repo-automation-bots/tree/main/serverless-scheduler-proxy

[4]
pprof: https://pkg.go.dev/net/http/pprof

[5]
profiling Go 程序: https://go.dev/blog/pprof

[6]
Google Cloud Secret Manager: https://cloud.google.com/secret-manager/docs/quickstart

[7]
自動部署: https://cloud.google.com/build/docs/deploying-builds/deploy-cloud-run

[8]
Cloud 的 Go 示例存儲庫中: https://github.com/GoogleCloudPlatform/golang-samples

[9]
cloud.google.com 上: https://cloud.google.com/

[10]
GoogleCloudPlatform/golang-samples: https://github.com/GoogleCloudPlatform/golang-samples

[11]
值得的: https://xkcd.com/1205/

[12]
地址范圍: https://www.gnu.org/software/sed/manual/html_node/Addresses.html

[13]
180 個已編輯的文件: https://github.com/GoogleCloudPlatform/golang-samples/pull/2080

[14]
庫: https://github.com/googleapis/google-cloud-go

[15]
示例: https://github.com/GoogleCloudPlatform/golang-samples

到此這篇關于定位并修復 Go 中的內存泄露的文章就介紹到這了,更多相關定位Go內存泄露內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://mp.weixin.qq.com/s/NEsoMtHElJwQ-n2CIH-Ogg

延伸 · 閱讀

精彩推薦
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
主站蜘蛛池模板: 韩剧在线观看 | 国产成人理在线观看视频 | 操尼姑| 欧美一区精品 | 久久青青草原 | 国产精品免费_区二区三区观看 | 青青在线国产视频 | 无人影院在线播放 | 本土自拍 | 国产二区视频在线观看 | 欧美vpswindowssex 欧美va在线高清 | juliaann大战黑人 | 人成午夜免费大片在线观看 | 亚洲网站在线播放 | 精品国产品在线18年 | 国产一久久香蕉国产线看观看 | 久久久无码精品亚洲A片软件 | 免费人成在线观看 | 亚洲AV中文字幕无码久久 | 99久久国产综合精品女不卡 | 性妲己| 国产99区| 好湿好紧好多水c | 逼123| 亚洲男1069gay男猛男 | 亚洲欧美国产另类 | 国产综合亚洲专区在线 | 99久久香蕉国产综合影院 | 免费观看在线观看 | 色先锋影音资源 | 黑人好大| 男人机机桶女人机机 | 欧美日本道免费一区二区三区 | 国产日韩精品欧美一区 | 国产视频中文字幕 | 国产高清自拍视频 | a级aaaaaaaa毛片| 动漫美女日批 | 九九热视频免费观看 | 三上悠亚久久国产 | 免费精品国产在线观看 |