a5de59d142782277828b32de2013e713.png

我使用 Golang 写的一个融合日志服务, 定时融合不同云厂商的日志, 该服务部署在 K8S 上.

在国庆期间日志量倍增, 该服务频繁被 Killed.

通过仪表盘, 发现该服务运行一次后, 内存不会释放, 怀疑是内存泄露导致的. 见下图.

ac37227ee9060ed8a897dca53e029222.png

最终, 我通过使用 pprof 解决了该问题.

1. 开启 pprof 服务

pprof 是 Golang 自带的性能分析工具. 可以 2 步 开启 pprof 服务.

// 1. 引入 net/http/pprof 包
_ import "net/http/pprof"

// 2. 开启 http 服务
go http.ListenAndServe(":9999", nil)

2. 通过 pprof 的 Web 页面分析 goroutine

[]()

如图:

6a706644eae36fdadd5b6e41f507c064.png
full goroutine stack dump
1f59b425b1feb6a3dd6939eb0556fbfe.png
goroutine
22258cf50f2e273d6be847865a07d0f4.png

我在 Web 页面上并未发现有价值的信息, 于是使用命令行分析.

3. 通过 pprof 的命令行分析 heap

go tool pprof -inuse_space []()

这个命令的作用是, 抓取当前程序已使用的 heap. 抓取后, 就可以进行类似于 gdb 的交互操作.

time.NewTimer
e0b377a27b975bb86cbac4cea91fb1b0.png
list <函数名>list time.NewTimerNewTimerruntimeTimer
5aae33a95378d6ed2fb09d51533e6ff0.png
for ... select ... time.After

原来程序中存在如下代码:

for {
		select {

		case a := <-chanA:
			...

		case b := <-chanB:
			....

		case <-time.After(20*time.Minutes):
			return nil, errors.New("download timeout")
	}
time.AfterNewTimertime.After
func After(d Duration) <-chan Time {
	return NewTimer(d).C
}
NewTimer
downloadTimeout := time.NewTimer(20 * time.Minute)
defer downloadTimeout.Stop()

for {
		select {

		case a := <-chanA:
			...

		case b := <-chanB:
			....

		case <-downloadTimeout.C:
			return nil, errors.New("download timeout")
	}