共计 1385 个字符,预计需要花费 4 分钟才能阅读完成。
景象
-
07/09 15:00:
- 一波海量申请,向 OS 申请内存,拉高了 heap_sys、heap_inuse;
-
07/09 15:24:
- 流量峰值过来当前,heap_inuse 降下来了 (100MB),然而 heap_idle 还是很高 (500MB),导致整体 RSS 很高;
背景常识
Influxdb 的内存要害指标:
- go_memstats_heap_sys_bytes:go 过程从 OS 获取的 heap 内存;
- go_memstats_heap_idle_bytes: golang 过程中暂未应用的 heap 内存;
- go_memstats_heap_inuse_bytes: golang 过程以后 heap 理论应用的内存;
go_memstats_heap_sys_bytes = go_memstats_heap_idle_bytes + go_memstats_heap_inuse_bytes
从监控图上能够看出,申请过后 heap_idle_bytes 依然很高,阐明被 heap 占着没有开释。
起因剖析
heap_idle 为什么不立刻开释:
- heap_idle 是指存在 heap 内,临时没有被应用,然而给 runtime 预留的空间;
- 没有归还给 OS 是因为,应答前面 app 对内存的应用,不必再去 OS 申请了;
- 当产生内存压力的时候,OS 会开释掉这块内存,然而“感触到内存压力”可能因为判断不精确,导致 OS 内的其它过程因为申请不到内存而被 OOM;
Go 开释内存的策略:
- Go 底层用 mmap 申请内存,用 madvise 开释内存,参考 go/src/runtime/mem_linux.go 的代码:
-
madvise 将某段内存标记为不再应用时,有 2 种形式:
- MADV_DONTNEED 标记过的内存如果再次应用,会触发缺页中断 (page fault);
- MADV_FREE 标记过的内存,内核会期待内存缓和时才会开释;在开释之前,这块内存仍然能够服用;(linux 4.5 版本内核开始反对)
- 显然,MADV_FREE 是一种空间换工夫的优化;
heap_idle 没有被开释的起因是:Go madvise=MADV_FREE,内存被 runtime 预留没有开释。
解决办法
madvise 默认配置:
- Go1.12 之前,linux 平台下 Go runtime 应用 madvise==MADV_DONTNEED;
- Go1.12 之后,在 MADV_FREE 可用时默认优先应用 MADV_FREE,当然,用户能够在执行程序前增加 GODEBUG=madvdontneed= 1 来批改这一行为;
-
Go1.16(含) 之后,为了避免引起混同,Go 官网将 madvise 默认批改为 MADV_DONTNEED;
- commit: https://github.com/golang/go/…
故能够选用的解决办法:
- 执行 influx 过程时,增加 GODEBUG=madvdontneed= 1 参数,强制批改为 MADV_DONTNEED;
- 将 influx 降级为 Go1.16,因为它默认 =MADV_DONTNEED;
参考
- https://zhuanlan.zhihu.com/p/…
- https://github.com/AlexiaChen…
- https://github.com/golang/go/…
- https://github.com/golang/go/…
- https://github.com/VictoriaMe…
正文完