1eef49dafde6aa1ebba962e2a621dc2c.png

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

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

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

0bd478dce28fa5103e5ec3e16d1c4008.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

[]()

如图:

678445595643bf409f53a3b85b0863de.png
full goroutine stack dump
d2299ac3ad12fc2d7caf8243e234c71c.png
goroutine
e509f761d3292b14b8a12d77649bafb8.png

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

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

go tool pprof -inuse_space []()

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

time.NewTimer
1e69b080b76c35d325b75f8a5abb5582.png
list <函数名>list time.NewTimerNewTimerruntimeTimer
f6b5da9f4a59388cc213a04cd425c470.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")
	}