记一次 Go 服务内存泄漏问题调查

原文地址:https://github.com/x1nchen/bl...

记录下这个问题是因为这不同于传统典型的 Go 服务内存泄漏。

8月15日前后,钉钉监控告警群不定时出现服务机器内存占用超 80%
告警,一开始以为是 Prometheus exporter 堆积造成的,准备下掉 Prometheus
exporter 的集成代码,后来觉 得这个堆积速度太快了点,不到一天就吃掉
6GB,十分夸张,所以有空的时候用 gops 快照了 一下,内存情况如下图

(pprof) /app/bin # gops memstats 1
alloc: 54.66MB (57317968 bytes)
total-alloc: 186.21GB (199938346560 bytes)
sys: 6.72GB (7215977904 bytes)
lookups: 0
mallocs: 2906600570
frees: 2906521966
heap-alloc: 54.66MB (57317968 bytes)
heap-sys: 6.44GB (6909984768 bytes)
heap-idle: 6.38GB (6846406656 bytes)
heap-in-use: 60.63MB (63578112 bytes)
heap-released: 6.33GB (6794371072 bytes)
heap-objects: 78604
stack-in-use: 2.12MB (2228224 bytes)
stack-sys: 2.12MB (2228224 bytes)
stack-mspan-inuse: 266.02KB (272408 bytes)
stack-mspan-sys: 31.88MB (33423360 bytes)
stack-mcache-inuse: 6.78KB (6944 bytes)
stack-mcache-sys: 16.00KB (16384 bytes)
other-sys: 4.58MB (4802459 bytes)
gc-sys: 251.55MB (263773240 bytes)
next-gc: when heap-alloc >= 100.19MB (105060944 bytes)
last-gc: 2020-08-14 20:54:01.063567774 +0800 CST
gc-pause-total: 982.706218ms
gc-pause: 17439
num-gc: 3484
enable-gc: true
debug-gc: false

分析从上面的指标可以得出以下结论

  1. 当前正在使用的堆内存大致是 60 多M
  2. HeapReleased = 6.33 GB 返还给操作系统的物理内存的字节数是 6.33GB (
    HeapReleased 统计了从idle
    span中返还给操作系统,没有被重新获取的内存大小)

那问题就抽象为:\为什么 HeapReleased 上升,RSS 没有下降?\

搜索了一下有人踩过这个坑了,参考:https://zhuanlan.zhihu.com/p/...

这是因为 Go 底层用 mmap 申请的内存,会用 madvise 释放内存。具体见
go/src/runtime/mem~linux~.go 的代码。

madvise 将某段内存标记为不再使用时,有两种方式 MADV~DONTNEED~ 和
MADV~FREE~(通过标志参数传入):

  • MADV~DONTNEED标记过的内存如果再次使用~,会触发缺页中断
  • MADV~FREE标记过的内存~,内核会等到内存紧张时才会释放。在释放之前,这块内存依然
    可以复用。这个特性从linux 4.5版本内核开始支持

显然,MADV~FREE是一种用空间换时间的优化~。

  • 在Go 1.12之前,linux 平台下 Go runtime
    中的sysUnsed使用madvise(MADV~DONTNEED~)
  • 在Go 1.12之后,在MADV~FREE可用时会优先使用MADVFREE~

具体见 https://github.com/golang/go/...

Go
1.12之后,提供了一种方式强制回退使用MADV~DONTNEED的方式~,在执行程序前添加
GODEBUG=madvdontneed=1。具体见
https://github.com/golang/go/...

另外还有一个疑点:现在知道问题出现的条件是 Go \> 1.12 && Linux Kernel \>
4.5,但是 为什么之前阿里云没有出现,系统8月10号整体迁移到 aws 出现了?

调查发现

阿里云机器内核 4.4.0-105-generic,而 aws 机器内核
4.15.0-1054-aws,刚好符合预期,可以解释。

==

补充:8月24日

使用 GODEBUG=madvdontneed=1 强制回退使用
MADV~DONTNEED~,没有再出现内存泄漏问题。内存稳定在 200MB 左右

你可能感兴趣的:(golang)