goroutine泄漏检测神器---goleak
在日常开发中,go 出去的goroutine通常伴随着死循环,这些goroutine可能处于阻塞状态,一直运行,直到进程结束。
对于线上服务来说,一直是在运行的,除非panic重启等,不然一旦出现goroutine泄漏,资源被一直占用,cpu/内存将会直线陡增,对于线上服务影响巨大,通常出了问题,可以采用pprof来进行分析,日常开发中,如果能做到边开发,边检测出这种泄漏问题,将会非常有用,这里便会引用到了goroutine离线检测工具:goleak。
1.使用
代码语言:javascript复制func TestA(t *testing.T) {
defer goleak.VerifyNone(t)
// test logic here.
}
在进行单元测试时可以直接使用,非常方便。
例如:下面是有异常的goroutine,使用goleak检测的结果:
代码语言:javascript复制found unexpected goroutines:
[Goroutine 23 in state chan receive, with go.uber.org/goleak.TestIgnoreCurrent.func2.1 on top of the stack:
goroutine 23 [chan receive]:
go.uber.org/goleak.TestIgnoreCurrent.func2.1(0xc000118c60)
/go_proj/goleak/leaks_test.go:111 0x34
created by go.uber.org/goleak.TestIgnoreCurrent.func2
/go_proj/goleak/leaks_test.go:110 0x11d
]
2.原理
像这种检测工具具体怎么实现的呢?比较好奇,就阅读了一下源码,发现源码非常的少,里面有很多技巧与学习的东西。
第一个点:如何检测?
这里的检测是尝试20次,每次最大时间睡眠100ms(允许耗时的goroutine运行)。每次检测过程为:过滤掉传入的goroutine,通过runtime.Stack获取到各种状态的goroutine,20次之后如果还有剩余的goroutine,那么就是有问题的,输出出来就好了。
内部实现的一些技巧:
- 过滤函数
对外暴露IgnoreTopFunction接口允许传入goroutine关键字符串,内部会去做匹配,用户不用关心细节,内部函数作为参数,调用addFilter,还支持传入当前goroutine,调用IgnoreCurrent。
代码语言:javascript复制func IgnoreTopFunction(f string) Option {
return addFilter(func(s stack.Stack) bool {
return s.FirstFunction() == f
})
}
func IgnoreCurrent() Option {
excludeIDSet := map[int]bool{}
for _, s := range stack.All() {
excludeIDSet[s.ID()] = true
}
return addFilter(func(s stack.Stack) bool {
return excludeIDSet[s.ID()]
})
}
func addFilter(f func(stack.Stack) bool) Option {
return optionFunc(func(opts *opts) {
opts.filters = append(opts.filters, f)
})
}
- 获取所有goroutine的栈信息
内部通过定义stack结构体,创建私有的getStacks进行解析,getStacks会解析出多个stack结构,内部实现原理为读取runtime.Stack的所有goroutine信息。
代码语言:javascript复制// Stack represents a single Goroutine's stack.
type Stack struct {
id int
state string
firstFunction string
fullStack *bytes.Buffer
}
最后,会获取到每个goroutine的id、状态、函数名,栈信息。