写点什么

Go 应用服务疑似内存泄露问题排查

作者:SFLYQ
  • 2023-02-04
    福建
  • 本文字数:1942 字

    阅读完需:约 6 分钟

Go应用服务疑似内存泄露问题排查

为了保障业务的可用性,增加应用服务请求依赖服务(grpc、http)的熔断降级策略,避免依赖服务不可用的情况下,出现级联服务故障产生雪崩,通过熔断降级尽可能把影响缩放到最小。

因此需要在 go 应用服务中接入熔断组件,组件我们选择了:阿里开源的 sentinel circuitbreaker,一顿封装后,项目接入、压测、验证并成功上线,同时上线后也进行了跟进了几个小时,一切正常。

几天后,在一次常规性的应用服务的资源监控巡查中发现内存资源使用有些异常。


/imgs/20230204_1.webp


看时间节点就是从熔断组件集成到项目后,就开始出现了内存的持续增长,看监控数据估算了下,一天增长 10~20MB 左右,还蛮有规律的。

难道是熔断组件导致的内存泄露?于是就先回滚了熔断相关内容,继续观察内存使用情况,发现内存恢复正常平稳的状态。已经确定是熔断问题导致的内存增长的问题。但是还没定位到具体是什么原因导致的这个问题,毕竟这个组件是阿里开源,并且在社区中感受好评,不应该有这种问题,还是要给与 respect,带着这个疑惑,开始了内存泄露的排查之路。

排查

首先在测试环境进行压测,并发压测 qps 300+,看资源监控无法复现线上的情况。

组件的使用上也只是对多做了一层的封装,并没有做了什么消耗内存的附加操作,但还是要先自我怀疑,进行了封装熔断代码重新审查,也对一些地方做了改造,通过排除法进行压测对比,也没定位到什么问题。

应用项目加入性能分析采集,使用 pprof 工具进行,对当前使用中(inuse_space)的堆内存进行分析,并且在测试环境中,对发布和压测后的 heap 采集进行 diff 对比,也没发现哪个代码会导致内存泄露的问题。因为测试环境无法复现,又将性能分析采集发布到生产上,跟了几天,依然无法定位问题。

一直无法定位到问题,于是开始怀疑 Prometheus 里监控的 k8s pod 内存指标数值的准确性。查阅资料,发现端倪。


/imgs/20230204_2.webp


container_memory_working_set_bytes 指标统计的内存是包含(RSSCache)其中 cache 包含 file cache,系统内核为了提高磁盘 IO 的效率,将读写过的文件缓存在内存中。file cache 并不会随着进程退出而释放,只会当容器销毁或者系统内存不足时才会由系统自动回收。

但是因为应用服务没有做文件操作,所以也就不以为然,看看就过去了。

又历经了几天的排查,已经毫无思绪,继续 review 代码,最终在初始化 sentinel circuitbreaker 代码中,发现熔断组件会默认刷日志到文件。

然后回想起来 k8s 使用内存监控指标中包含 file cache 的问题。就此真正原因浮出水面了。

让运维帮忙查看线上日志:


/imgs/20230204_3.webp


可以看出每日的日志文件占用存储空间与之前监控的每天增长的内存范围差不多。

最终代码改造:

# ....// sentinel 初始化conf := config.NewDefaultConfig()// 设置不刷日志到文件(注意:k8s的使用内存指标包含:rss + file cache,使用写日志文件,会导致k8s pod监控的内存持续增长)conf.Sentinel.Log.Metric.FlushIntervalSec = 0# ....
复制代码

剧终~


番外 - 验证

k8s 中 memory.usage = working_set = memory.usage_in_bytes - total_inactive_file  根据 cgroup memory 关系有: memory.usage_in_bytes = memory.kmem.usage_in_bytes + rss + cache

memory.usage_in_bytes的统计数据是包含了所有的 file cache 的,total_active_filetotal_inactive_file都属于 file cache 的一部分。

所以采用memory.usage_in_bytes - total_inactive_file计算出的结果并不是当前 Pod 中应用程序所占用的内存,当 Pod 内存资源紧张时total_active_file也是可回收利用的。

Docker 容器内存相关状态:

cd /sys/fs/cgroup/memory cat memory.stat# 输出cache 1470464rss 35373056rss_huge 0mapped_file 864256swap 0pgpgin 32152pgpgout 23157pgfault 35743pgmajfault 4inactive_anon 0active_anon 35332096inactive_file 1466368active_file 4096unevictable 0hierarchical_memory_limit 1073741824hierarchical_memsw_limit 1073741824total_cache 1470464total_rss 35373056total_rss_huge 0total_mapped_file 864256total_swap 0total_pgpgin 0total_pgpgout 0total_pgfault 0total_pgmajfault 0total_inactive_anon 0total_active_anon 35332096total_inactive_file 1466368total_active_file 4096total_unevictable 0
复制代码

通过 shell 脚本,循环写内容到文件

  s=""  for ((i=1; i<=500000; i++))  do      s=$s$i;      ns="${s}\r\n";      # echo $ns;      echo $ns >> test.log  done
复制代码

进过多次的执行 shell,对比

before:


/imgs/20230204_4.webp


after:


/imgs/20230204_5.webp



可以明显看到,每次执行 shell 脚本,循环写入文件,文件越写越大,k8s pod 内存监控使用内存不断地增长,并且没有释放掉。

参考:

发布于: 刚刚阅读数: 3
用户头像

SFLYQ

关注

还未添加个人签名 2019-09-09 加入

生活不只有苟且和代码,还有远方 ~ http://blog.thankbabe.com/

评论

发布
暂无评论
Go应用服务疑似内存泄露问题排查_Go_SFLYQ_InfoQ写作社区