关于golang:GO-语言内存泄漏排查两例

5次阅读

共计 1585 个字符,预计需要花费 4 分钟才能阅读完成。

例 1:Goroutine 透露

景象

NumGoroutine 指标继续上涨,且低峰期未降落,判断呈现了 Goroutine 透露景象。

排查

  1. 通过拜访线上服务 pprof 裸露进去的 HTTP 接口,拿到以后所有协程的堆栈信息;
    curl http://「ip:port」/debug/pprof/goroutine?debug\=2
  1. 发现存在大量存活工夫超过上千分钟的 Goroutine,察看堆栈疑似是 http 连贯未开释导致,通过对下图 net.sockets.tcp.inuse(正在应用的 tcp socket 数量)指标的察看进行了进一步的确认;

论断

http

上面以本次 case http 服务为例,做简略介绍:

  • 上游服务作为客户端应用了 http1.1 并且将连贯设置为 keepalive;
  • 本服务作为服务端未设置 idletimeout 与 readtimeout;

当这两种状况同时产生时,如果上游持有对本服务的连贯不进行开释,那么服务端会始终维持这个连贯的存在,不进行回收,进而导致协程透露;

client
上游客户端可能为 GO、Java 等,以下为 GO 语言 http 客户端的闲暇连贯超时设置;

server

解决

倡议启动 http server 尽量用后者,前者尽管简略,然而服务不够强壮;

thrift

server

Tips

须要留神的一点是,这个 Goroutine 透露问题不止在 http 下会产生,在 thrift、grpc 中也是同样的情理,如果服务端不对连贯设置 timeout,某些状况下就会被上游拖死。

Reference

https://zhuanlan.zhihu.com/p/…

例 2:内存居高不下

景象

内存使用量(mem.rss)居高不下,且低峰期未降落,狐疑产生了内存透露景象;

排查

  1. 刚开始狐疑时内存透露,然而抓取 pprof heap 图察看后,未发现泄露问题,且内存调配合乎预期;
  2. 发现内存应用尽管居高不下,但未呈上涨趋势,因而批改关键字为“go 内存占用居高不下”,发现有雷同问题;

论断

问题来自于 GO 在将内存归还给操作系统时的内存开释策略,详情见官网 issues,以下做简略介绍。

GO 内存开释策略

(此节内容整顿自 压测后 go 服务内存暴涨)

不同策略的开释机制

  • MADV_DONTNEED:内核将会在适合的机会去开释内存,但过程的 RSS(常驻内存)将会立刻缩小。如果再次申请内存,内核会重新分配一块新的空间。
  • MADV_FREE:只能在 linux 内核版本 4.5 以上能力应用,此操作实践上只是打了一个标记位,只有在内核感觉到内存压力的时候才会将这些打标记的内存回收掉,调配给其余过程应用。这个策略下过程的 RSS 不会立刻缩小。

不同策略的理论差异

  • 实践上 MADV_FREE 效率要高一些,通过在页表中做标记的形式,提早内存的调配和回收,能够进步内存治理的效率,毕竟内存的回收和调配都是会耗费零碎性能的;
  • 导致的 RSS 指标变动
    MADV_DONTNEED 会导致过程 RSS 会有显著的降落;
    MADV_FREE 会导致过程 RSS 安稳在顶峰,不会失去立刻开释;

不同 GO 版本的开释策略

  • 在 GO1.12 之前,默认均抉择的 MADV_DONTNEED 策略进行内存回收;
  • 在 GO1.12~GO1.15,官网默认抉择 MADV_FREE 策略进行内存回收;
  • 在 GO1.16 及之后,又改回了 MADV_DONTNEED 策略进行回收内存。

在 GO1.12~GO1.15 且内核版本 4.5 以上,mem.rss 指标曾经无奈精确观测服务内存占用;

解决办法

  • 不解决,对程序性能无利,然而会升高一些可观测性;
  • 以下任一办法能够解决,但会损失肯定性能
    把 export GODEBUG=madvdontneed=1 写进服务 control.sh 脚本;
    降级 GO 版本至 1.16 及以上;

Reference

http://soiiy.com/go/17114.html
https://github.com/golang/go/…

正文完
 0