欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

定位并修復(fù) Go 中的內(nèi)存泄露問(wèn)題

 更新時(shí)間:2021年10月26日 16:24:13   作者:Go語(yǔ)言中文網(wǎng)  
Go 是一門(mén)帶 GC 的語(yǔ)言,這篇文章回顧了我如何發(fā)現(xiàn)內(nèi)存泄漏、如何修復(fù)它,以及我如何修復(fù) Google 示例 Go 代碼中的類似問(wèn)題,以及我們?nèi)绾胃倪M(jìn)我們的庫(kù)以防止將來(lái)發(fā)生這種情況,感興趣的朋友一起看看吧

Go 是一門(mén)帶 GC 的語(yǔ)言,因此,大家很容易認(rèn)為它不會(huì)有內(nèi)存泄露問(wèn)題。 大部分時(shí)候確實(shí)不會(huì),但如果有些時(shí)候使用不注意,也會(huì)導(dǎo)致泄露。

本文案例來(lái)自谷歌云的代碼,探討如何找到并修復(fù) Go 中的內(nèi)存泄露。(確切來(lái)說(shuō)是因?yàn)橘Y源泄露導(dǎo)致的內(nèi)存泄露,除了本文介紹的,還有一些其他泄露的情況)

這篇文章回顧了我如何發(fā)現(xiàn)內(nèi)存泄漏、如何修復(fù)它,以及我如何修復(fù) Google 示例 Go 代碼中的類似問(wèn)題,以及我們?nèi)绾胃倪M(jìn)我們的庫(kù)以防止將來(lái)發(fā)生這種情況。

Google Cloud Go 客戶端庫(kù) [1] 通常在后臺(tái)使用 gRPC 來(lái)連接 Google Cloud API。創(chuàng)建 API 客戶端時(shí),庫(kù)會(huì)初始化與 API 的連接,然后保持該連接處于打開(kāi)狀態(tài),直到你調(diào)用 Client.Close 。

client, err := api.NewClient()
// Check err.
defer client.Close()

客戶端可以安全地同時(shí)使用,所以你應(yīng)該保持相同 Client 直到你的任務(wù)完成。但是,如果在應(yīng)該 Close 的時(shí)候不 Close client 會(huì)發(fā)生什么呢?

會(huì)出現(xiàn)內(nèi)存泄漏。底層連接永遠(yuǎn)不會(huì)被清理。

Google 有一堆 GitHub 自動(dòng)化機(jī)器人來(lái)幫助管理數(shù)百個(gè) GitHub 存儲(chǔ)庫(kù)。我們的一些機(jī)器人通過(guò)在 Cloud Run [2] 上運(yùn)行的 Go 服務(wù)器 [3] 代理它們的請(qǐng)求。我們的內(nèi)存使用看起來(lái)像一個(gè)經(jīng)典的鋸齒形內(nèi)存泄漏:

我通過(guò)向服務(wù)器添加 pprof.Index 處理程序開(kāi)始調(diào)試:

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

`pprof` [4] 提供運(yùn)行時(shí) profiling 數(shù)據(jù),如內(nèi)存使用情況。有關(guān)更多信息,請(qǐng)參閱 Go 官方博客上的 profiling Go 程序 [5] 。

然后,我在本地構(gòu)建并啟動(dòng)了服務(wù)器:

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

然后向服務(wù)器發(fā)送一些請(qǐng)求:

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

確切的有效負(fù)載和端點(diǎn)特定于我們的服務(wù)器,與本文無(wú)關(guān)。

為了獲得正在使用的內(nèi)存的基線,我收集了一些初始 pprof 數(shù)據(jù):

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

檢查輸出,你可以看到一些內(nèi)存使用情況,但沒(méi)有什么會(huì)立即成為一個(gè)大問(wèn)題(這很好!我們剛剛啟動(dòng)了服務(wù)器!):

$ 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

下一步是向服務(wù)器發(fā)送一堆請(qǐng)求,看看我們是否可以 (1) 重現(xiàn)可能的內(nèi)存泄漏和 (2) 確定泄漏是什么。

發(fā)送 500 個(gè)請(qǐng)求:

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 數(shù)據(jù):

$ 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 使用大量?jī)?nèi)存真的很突出!這是泄漏與什么相關(guān)的第一個(gè)跡象:gRPC。查看我們的應(yīng)用程序源代碼,我們唯一使用 gRPC 的地方是 Google Cloud Secret Manager [6] :

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

在每個(gè)請(qǐng)求創(chuàng)建 client 時(shí),我們沒(méi)有調(diào)用 client.Close() !所以,我添加了一個(gè) Close 調(diào)用,問(wèn)題就消失了:

defer client.Close()

我提交了修復(fù),然后 自動(dòng)部署 [7] ,鋸齒立即消失了!

大約在同一時(shí)間,用戶在我們的 Cloud 的 Go 示例存儲(chǔ)庫(kù)中 [8] 提交了一個(gè)問(wèn)題,其中包含 cloud.google.com 上 [9] 文檔的大部分 Go 示例。用戶注意到我們忘記調(diào)用 client.Close 了。

我曾多次看到同樣的事情出現(xiàn),所以我決定調(diào)查整個(gè) repo。

我開(kāi)始粗略估計(jì)有多少受影響的文件。使用 grep ,我們可以獲得包含 NewClient 樣式調(diào)用的所有文件的列表,然后將該列表傳遞給另一個(gè)調(diào)用 grep 以僅列出不包含 Close 的文件,同時(shí)忽略測(cè)試文件:

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

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

考慮到問(wèn)題的規(guī)模,我認(rèn)為一些自動(dòng)化是 值得的 [11] 。我不想寫(xiě)一個(gè)完整的 Go 程序來(lái)編輯文件,所以我使用 Bash:

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

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

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

然后,我將該文件列表傳遞給 sed 進(jìn)行實(shí)際編輯。 xargs 調(diào)用你給它的命令,每一行都以 stdin 作為參數(shù)傳遞給給定的命令。

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

// 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)
    }
    // ...
}

在高層次上,我們初始化客戶端并檢查是否有錯(cuò)誤。每當(dāng)你檢查錯(cuò)誤時(shí),都會(huì)有一個(gè)右花括號(hào) ( } )。我使用這些信息來(lái)自動(dòng)化編輯。

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

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

-i 表示直接編輯文件。這不是問(wèn)題,因?yàn)榇a用 git 管理了。

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

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

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

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

一旦我自動(dòng)編輯了所有示例文件,我用 goimports 開(kāi)始修復(fù)格式。然后,我檢查了每個(gè)編輯過(guò)的文件,以確保它做了正確的事情:

  • 在服務(wù)器應(yīng)用程序中,我們應(yīng)該關(guān)閉客戶端,還是應(yīng)該保留它以備將來(lái)的請(qǐng)求使用?
  • 是 Client 實(shí)際的名字 client 還是別的什么?
  • 是否有一個(gè)以上的 Client 調(diào)用了 Close ?

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

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

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

我希望你對(duì) Go、內(nèi)存泄漏 pprof 、gRPC 和 Bash 有所了解。我很想聽(tīng)聽(tīng)你關(guān)于發(fā)現(xiàn)的內(nèi)存泄漏以及修復(fù)它們的方法的故事!如果你對(duì)我們?nèi)绾胃倪M(jìn)我們的 庫(kù) [14] 或 示例 [15] 有任何想法,請(qǐng)通過(guò)提交 issue 告訴我們。

參考資料

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

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

[3]
Go 服務(wù)器: 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]
自動(dòng)部署: https://cloud.google.com/build/docs/deploying-builds/deploy-cloud-run

[8]
Cloud 的 Go 示例存儲(chǔ)庫(kù)中: 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 個(gè)已編輯的文件: https://github.com/GoogleCloudPlatform/golang-samples/pull/2080

[14]
庫(kù): https://github.com/googleapis/google-cloud-go

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

到此這篇關(guān)于定位并修復(fù) Go 中的內(nèi)存泄露的文章就介紹到這了,更多相關(guān)定位Go內(nèi)存泄露內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go檢查結(jié)構(gòu)體中是否存在某個(gè)字段及創(chuàng)建結(jié)構(gòu)體切片或映射

    Go檢查結(jié)構(gòu)體中是否存在某個(gè)字段及創(chuàng)建結(jié)構(gòu)體切片或映射

    這篇文章主要為大家介紹了Go檢查結(jié)構(gòu)體中是否存在某個(gè)字段及創(chuàng)建結(jié)構(gòu)體切片或映射實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • GoLang中Module的基本使用方法

    GoLang中Module的基本使用方法

    Go module是從Go 1.11版本才引入的新功能,下面這篇文章主要給大家介紹了關(guān)于GoLang中Module的基本使用方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • golang之?dāng)?shù)據(jù)驗(yàn)證validator的實(shí)現(xiàn)

    golang之?dāng)?shù)據(jù)驗(yàn)證validator的實(shí)現(xiàn)

    這篇文章主要介紹了golang之?dāng)?shù)據(jù)驗(yàn)證validator的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • 深入理解 Go 語(yǔ)言中的 Context

    深入理解 Go 語(yǔ)言中的 Context

    這篇文章主要介紹了 理解 Go 語(yǔ)言中的 Context,需要的朋友可以參考下
    2020-06-06
  • golang替換無(wú)法顯示的特殊字符(\u0000,?\000,?^@)

    golang替換無(wú)法顯示的特殊字符(\u0000,?\000,?^@)

    這篇文章主要介紹了golang替換無(wú)法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細(xì)資料,需要的小伙伴可以參考一下
    2022-04-04
  • Go構(gòu)建高性能的事件管理器實(shí)例詳解

    Go構(gòu)建高性能的事件管理器實(shí)例詳解

    這篇文章主要為大家介紹了Go構(gòu)建高性能的事件管理器實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go通過(guò)不變性優(yōu)化程序詳解

    Go通過(guò)不變性優(yōu)化程序詳解

    這篇文章主要為大家介紹了Go通過(guò)不變性優(yōu)化程序?qū)嵗斀?,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang的時(shí)區(qū)和神奇的time.Parse的使用方法

    golang的時(shí)區(qū)和神奇的time.Parse的使用方法

    這篇文章主要介紹了golang的時(shí)區(qū)和神奇的time.Parse的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Go語(yǔ)言Goroutinue和管道效率詳解

    Go語(yǔ)言Goroutinue和管道效率詳解

    這篇文章主要為大家介紹了Go語(yǔ)言Goroutinue和管道效率使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 圖文詳解Go中的channel

    圖文詳解Go中的channel

    Channel是go語(yǔ)言內(nèi)置的一個(gè)非常重要的特性,也是go并發(fā)編程的兩大基石之一,下面這篇文章主要給大家介紹了關(guān)于Go中channel的相關(guān)資料,需要的朋友可以參考下
    2023-02-02

最新評(píng)論