这篇文章回顾了我是如何发现内存泄漏、如何修复它、如何修复 Google
中的 Go
示例代码中的类似问题,以及我们是如何改进我们的基础库防止未来再次发生这种情况。
Google
云的 Go
的客户端基础库(https://github.com/googleapis/google-cloud-go)通常在底层使用 gRPC
来连接 Google
云的接口。当你创建一个客户端时,库会初始化一个与该 接口的连接,然后让这个连接保持打开状态,直到你在该客户端上调用 Close
操作。
client, err := api.NewClient()
// Check err.
defer client.Close()
客户端并发地使用是安全的,所以应该在使用完之前保留同一个客户端。但是,如果在应该关闭客户端的时候而没有关闭,会发生什么呢?
你会得到一个内存泄漏:底层连接从未被释放过。
Google
有一堆的 GitHub
自动化机器人,帮助管理数以百计的 GitHub
仓库。我们的一些机器人通过云上运行的(https://cloud.google.com/run/docs/quickstarts/build-and-deploy/go)上的Go 服务(https://github.com/googleapis/repo-automation-bots/tree/main/serverless-scheduler-proxy)代理它们的请求。我们的内存使用情况看起来就是典型的锯齿状内存泄漏情况。
我通过在程序中添加 pprof.Index
开始调试:
mux.HandleFunc("/debug/pprof/", pprof.Index)
pprof
提供运行时的分析数据,比如内存使用量。访问 Go
官方博客中的 分析 Go
程序(https://blog.golang.org/pprof)获取更多信息.
然后,我在本地构建并启动了该服务:
$ go build
$ PROJECT_ID=my-project PORT=8080 ./serverless-scheduler-proxy
然后,我向这个服务发送了一些构造的请求:
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
通过检查输出的结果,可以看到一些内存的使用情况,但并没有发现徒增的问题(这很好!因为我们刚刚启动服务!)。
$ go tool pprof heap.0.pprof
F