From 1315d5a009055b6a79d9f4caff53a9f825dfde5c Mon Sep 17 00:00:00 2001 From: kkkiio Date: Sat, 14 Oct 2023 20:03:48 +0800 Subject: [PATCH] fix: rewrite the post about pprof goroutine leak --- _posts/2023-07-23-pprof-goroutine-leak.md | 32 ++++++++++------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/_posts/2023-07-23-pprof-goroutine-leak.md b/_posts/2023-07-23-pprof-goroutine-leak.md index 7cf694c..70c93a2 100644 --- a/_posts/2023-07-23-pprof-goroutine-leak.md +++ b/_posts/2023-07-23-pprof-goroutine-leak.md @@ -7,15 +7,17 @@ categories: engineering ![Goroutine Leak](/assets/image/goleak.png) -## 背景 +我们知道线程会占用内存和 CPU 调度资源,发生泄露时,程序内存会升高,运行效率也会因为过多的线程切换(context switch)而降低,一般上千个线程就会导致性能明显下降。既然 goroutine 被称为“轻量级线程”,那在泄露数量高了几个数量级后,是不是也会导致类似的问题呢?最近我刚好在生产环境遇到了这个问题。 -我司有一个Go后台服务,在运行几周后内存占用很高,即使在没有用户使用的闲置时段,它也仍然会占用大约3G的内存。另外,我们发现每天闲置时段内存占用都比前一天更高些,这是典型的内存泄漏现象。 +## 怎么发现的 -问题根源正如标题所说,是goroutine泄漏,我们可以拿这个作为例子,看看怎么分析内存泄露。 +我司有一个 Go 后台服务,在运行几周后内存占用很高,即使在没有用户使用的闲置时段,它也仍然会占用大约 3G 的内存。另外,我们发现每天闲置时段内存占用都比前一天更高些,这是典型的内存泄漏现象。 -## 还原现场 +## 用 pprof 分析内存泄露 -简化后的BUG代码如下[^1]: +如果没有标题剧透,我们排查内存泄露问题跟排查普通 BUG 一样,需要信息,而不是浪费时间猜测。 Go 官方提供了[pprof](https://pkg.go.dev/net/http/pprof)工具,可以用来了解 CPU/内存/Goroutine 等使用情况(这里省略工具的具体使用方法)。 + +我们用 pprof 分析下面简化后的 BUG 代码[^1]: ```go func calculate(multiple int) (int, error) { @@ -48,31 +50,25 @@ func asyncFetch(ctx context.Context) rxgo.Observable { } ``` -## 用 pprof 分析程序 - -假设我们不知道问题原因,我们应该如何处理这个内存泄露问题呢? - -跟排查所有BUG一样,我们需要信息,而不是浪费时间猜测。 Go 官方提供了[pprof](https://pkg.go.dev/net/http/pprof)工具,可以用来了解CPU/内存/Goroutine等使用情况,这里省略工具的具体使用方法。 - ### 分析内存使用 -我们用pprof生成“内存占用”的火焰图[^2]: +pprof 可以生成“内存占用”的火焰图[^2]: ![pprof heap火焰图](/assets/image/goleak-memory.png) -火焰图的宽度是使用资源的比例,可以看到,`runtime.malg`这个函数占用了很多内存,它是用来创建goroutine的,正常情况下它不应该占用很多内存,这种表现说明服务有大量的goroutine在运行,而且都没有退出,大概率是goroutine泄漏了。 +火焰图的宽度是使用资源的比例,可以看到,`runtime.malg`这个函数占用了很多内存,它是用来创建 goroutine 的,正常情况下它不应该占用很多内存,这种表现说明服务有大量的 goroutine 在运行,而且都没有退出,大概率是 goroutine 泄漏了。 -### 分析Goroutine状态 +### 分析 Goroutine 状态 -大多数程序的火焰图会充满许多细窄的调用栈,你只靠内存火焰图很难分析出是哪里泄露了goroutine,所以我们来进一步分析goroutine的火焰图: +大多数程序的火焰图会充满许多细窄的调用栈,你只靠内存火焰图很难分析出是哪里泄露了 goroutine,所以我们来进一步分析 goroutine 的火焰图: ![pprof goroutine火焰图](/assets/image/goleak-goroutine.png) -非常明显,`onecontext.(*OneContext).run`创建了大量的goroutine,而且都没有退出,这就是我们要找的泄漏点。 +非常明显,`onecontext.(*OneContext).run`创建了大量的 goroutine,而且都没有退出,这就是我们要找的泄漏点。 ## 总结 -泄露了这么多goroutine,听上去有些吓人,其实也只是多占用了一些资源,我们也只需根据现象去分析程序。 +泄露 100 万个 goroutine 只增加了一些内存占用,因为大多数情况下泄露的 goroutine 都在等待,没有增加调度的压力,也算对得起“轻量级线程”这个名字。 [^1]: https://github.com/ReactiveX/RxGo -[^2]: https://www.brendangregg.com/flamegraphs.html \ No newline at end of file +[^2]: https://www.brendangregg.com/flamegraphs.html