现象

  • 07/09 15:00:

    • 一波海量请求,向OS申请内存,拉高了heap_sys、heap_inuse;
  • 07/09 15:24:

    • 流量峰值过去以后,heap_inuse降下来了(100MB),但是heap_idle还是很高(500MB),导致整体RSS很高;

image.png

背景知识

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;

故可以选用的解决办法:

  • 执行influx进程时,添加GODEBUG=madvdontneed=1参数,强制修改为MADV_DONTNEED;
  • 将influx升级为Go1.16,因为它默认=MADV_DONTNEED;

参考

  1. https://zhuanlan.zhihu.com/p/...
  2. https://github.com/AlexiaChen...
  3. https://github.com/golang/go/...
  4. https://github.com/golang/go/...
  5. https://github.com/VictoriaMe...

a朋
63 声望39 粉丝