背景
日常维护的ADX项目突然发生了协程报警线。日常 goroutine 数量为 3k左右,而报警线为 8k,而CPU,延迟等观测指标正常,内存30% 左右了【正常20%】。行叭,是发生了协程泄露。
排查思路
1、最近是否有影响系统稳定性的业务代码上线?
查了一下上线的 list列表发现没有影响系统代码的需求上线。
2、项目所采用的中间件是否不稳定?
参考 记一次go项目内存泄露问题排查,也就只有codis不稳定了。
3、那就抓pprof文件吧
在机器上执行以下命令。【因为项目中的pyroscope未接入goroutine指标采集,因此只能手动执行了】 项目前期引入了以下代码
因此采取下列命令:go tool pprof 127.0.0.1:9099/debug/pprof/goroutine
通过控制台交互输入 traces 命令,可以得到下图
可以很清晰的看到
go-redis/redis/internal/pool.(*ConnPool).reaper 占用了大量协程。
解释一下为什么不怀疑 gopark , 其实gopark其实只是表面原因,简单理解的话可以表示为 挂起G,chanrecv与chanrecv2是两种从channel中接收数据的格式。
因为服务器上的代码并没有源码,因此无法分析 source,下载到本地后,执行以下命令,对目标文件以Web的方式打开
go tool pprof --source_path=/Users/whoops/Desktop/project/go -http=:8080 pprof.caifeng-adx.goroutine.001.pb.gz
--source_path为源代码目录
可以看到是
ticker.C 导致的,想到了之前关于Time导致 协程泄露的一个case,如下
func main() {
for {
select {
case <-time.Tick(3 * time.Second): // 这里会不断生成 ticker,而且 ticker 会进行重新调度,造成泄漏
fmt.Println("每隔3秒执行一次")
}
}
}
更多参考文章 # Golang 定时器(Timer 和 Ticker ),这篇文章就够了
理论上该处使用的 Ticker.C这种方式是没问题的。同时又由于是 internal内部代码,看看有无相似问题吧。
PS: 此时 go-redis 的版本为 v6.15.9
果然存在相同的问题,重点就在于
就是计时器和失效连接的 channel关闭同时存在。
行叭,既然都存在相同问题了,那解决起来就简单多了。为了改动最小,直接升级 go-redis 由 v6.15.9 到 v7.2.0 就好了。
为什么决定采用 v7.2.0
尽可能多的用最新版本呗。因为 v8引入了 context 上下文,改动较多。先从最小改动上入手,至此问题解决。