Skip to content

Commit

Permalink
fix: rewrite the post about pprof goroutine leak
Browse files Browse the repository at this point in the history
  • Loading branch information
kkkiio committed Oct 14, 2023
1 parent 6748714 commit 1315d5a
Showing 1 changed file with 14 additions and 18 deletions.
32 changes: 14 additions & 18 deletions _posts/2023-07-23-pprof-goroutine-leak.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
[^2]: https://www.brendangregg.com/flamegraphs.html

0 comments on commit 1315d5a

Please sign in to comment.