记一次go项目协程泄露问题排查

454 阅读2分钟

背景

日常维护的ADX项目突然发生了协程报警线。日常 goroutine 数量为 3k左右,而报警线为 8k,而CPU,延迟等观测指标正常,内存30% 左右了【正常20%】。行叭,是发生了协程泄露。

排查思路

1、最近是否有影响系统稳定性的业务代码上线?

查了一下上线的 list列表发现没有影响系统代码的需求上线。

2、项目所采用的中间件是否不稳定?

参考 记一次go项目内存泄露问题排查,也就只有codis不稳定了。

3、那就抓pprof文件吧

在机器上执行以下命令。【因为项目中的pyroscope未接入goroutine指标采集,因此只能手动执行了】 项目前期引入了以下代码

image.png

因此采取下列命令:go tool pprof 127.0.0.1:9099/debug/pprof/goroutine

image.png 通过控制台交互输入 traces 命令,可以得到下图

image.png 可以很清晰的看到 go-redis/redis/internal/pool.(*ConnPool).reaper 占用了大量协程。

解释一下为什么不怀疑 gopark , 其实gopark其实只是表面原因,简单理解的话可以表示为 挂起G,chanrecvchanrecv2是两种从channel中接收数据的格式。

因为服务器上的代码并没有源码,因此无法分析 source,下载到本地后,执行以下命令,对目标文件以Web的方式打开

go tool pprof --source_path=/Users/whoops/Desktop/project/go -http=:8080 pprof.caifeng-adx.goroutine.001.pb.gz
  • --source_path为源代码目录

image.png 可以看到是 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

果然存在相同的问题,重点就在于

image.png 就是计时器和失效连接的 channel关闭同时存在。

行叭,既然都存在相同问题了,那解决起来就简单多了。为了改动最小,直接升级 go-redis 由 v6.15.9 到 v7.2.0 就好了。

为什么决定采用 v7.2.0

尽可能多的用最新版本呗。因为 v8引入了 context 上下文,改动较多。先从最小改动上入手,至此问题解决。

参考更多case