• 一次Go项目进程重启故障问题排查


    有个go项目的容器近两天几乎每天都异常重启一次,且两个节点基本都是差不多的时间异常重启。看了监控指标,发现CPU平稳,而内存是缓慢涨上去后,进程被操作系统kill掉,导致pod重启。
    在这里插入图片描述

    从内存指标可以看出,不会是因为突然的请求量上升所导致,而是应该存在内存泄漏了。
    另外,从网络带宽指标看,流量也确实波动并不大,趋于平稳。
    在这里插入图片描述

    从监控指标还可以看出,go的线程数是平稳趋势,可以排除goroutine导致的内存泄漏。
    在这里插入图片描述

    这里我们需要借助pprof工具查看内存泄漏问题。pprof(Profiling in Go)是Go语言内置的一个性能分析工具。该工具可用于在运行时进行应用程序性能分析和剖析,帮助我们找出go进程的性能瓶颈和资源利用问题。例如:

    • 查找应用程序中最耗时的函数,以及耗时函数的调用路径。
    • 查找内存泄漏问题,能够追踪到内存是哪一行代码分配的。
    • 分析协程信息,找出协程阻塞或泄露的原因。

    排查思路:由于容器已经重启过,当前go进程内存消耗还是正常值,不过从监控指标已经看出,内存会缓慢的涨上去,因此我们是先查看当前时间的内存使用情况,记录下来,待一个小时以后,再看一次,对比看哪里的内存是上涨的,然后再追踪内存是从哪里分配的,最后再看代码,看看哪里占用了内存没有释放。

    我们已经给该go项目启用pprof:

    import _ "net/http/pprof"
    
    func init() {
        go func() {
           http.ListenAndServe("0.0.0.0:7005", nil)
        }()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    进入容器,执行命令“go tool pprof -inuse_space http://127.0.0.1:7005/debug/pprof/heap”。
    输入top命令,查看内存占用的前10。
    在这里插入图片描述

    目前看各项占用的内存都是在正常值范围,比较可疑的是“crypto/x509.parseCertificate”,还不急着分析泄漏问题。

    不过这里还看出一个问题,就是我们执行操作系统的top命令,看到占用的内存(RES),也是监控指标显示的内存值,当前已经1.4g,而pprof的top命令显示的heap内存占用才626MB,相差很大。
    在这里插入图片描述

    类似Java的堆内存+堆外内存,以及堆内存实际使用与已占用操作系统的内存。go除了堆内存使用,还有栈、gc、go的一些底层数据结构等使用的内存也是计算在堆外的,而堆内存已申请和已使用也跟java类似, gc后会有空闲的堆内存,不会全部马上归还给操作系统。而pprof的top统计的是当前已使用,不包含空闲的堆内存,所以看到的差距很大。

    进入容器里面执行curl http://127.0.0.1:7005/debug/pprof/heap?debug=1命令,可以查看go的内存占用情况。
    在这里插入图片描述

    字段含义说明:

    • Sys:进程总共从操作系统申请的字节数,包含运行时的heap、stack和其他内部数据结构的总和。
    • HeapSys:go进程heap申请的字节数。
    • HeapInuse:go进程当前使用的字节数。
    • HeapReleased:go进程已释放归还给操作系统的字节数。
    • HeapIdle:没有被使用的空闲字节数,包含HeapReleased,可以被再次申请,甚至作为栈内存使用。HeapIdle-HeapReleased=GC保留的。

    从图中可以看出,go进程总共为heap申请了1.2G的内存,当前已使用785MB,空闲461MB。top命令显示的进程占用1.4g,go进程heap占用1.2G,还差两百M,就是Stack(栈内存使用)、MSpan+MCache+BucjHashSys(go底层内部结构体使用)、GCSys(GC使用)、OtherSys(其它内存使用)。所以内存使用是对得上的。

    大概一个小时后,重新执行pprof的top命令,输出的前10堆内存使用如下图。
    在这里插入图片描述

    (方便对比,上一次的)
    在这里插入图片描述

    其中gitlab.lizhi.fm/middleware/lz_common_romefs/fio.(*ByteBufPool).Get占用64.32mb刚好是内存池的最大大小,这是我们自己实现的内存池,说明内存池没有泄漏。而bytes.makeSlice、crypto/x509.parseCertificate都往上涨了。
    通过peek bytes.makeSlice,发现bytes.(*Buffer).Write占用64.33MB,bytes.(*Buffer).Grow占用110.48MB。
    在这里插入图片描述

    继续peek Grow,发现与tls有关,占用110.48MB。
    在这里插入图片描述

    继续peek Write,由于是模糊匹配,一共有三个结果,中间第二个才是我们需要的,发现也与tls有关,占用64.33mb内存。
    在这里插入图片描述

    代码中,与tls有关的地方就是发送https请求从s3下载文件,所以检查下载文件调用链路上是否存在可疑的内存泄漏,发现如下疑点。
    在这里插入图片描述

    统计了访问日记,发现确实经常出现响应403。
    所以问题就清晰了,由于403是有body的,没有close响应的body导致的内存泄漏。
    修改后指标恢复正常。
    在这里插入图片描述

  • 相关阅读:
    从校园到职场,如果是你会和我一样吗?
    MySQL主主复制
    redis五大数据类型+redis6 新类型(详解+指令)
    第二证券|券商12月金股出炉!多只地产股成热门,科创仍是中长期主线
    ORACLE的utl_raw函数在不同字符集的数据库中的用法
    计算机毕业设计之java+ssm理发店会员管理系统
    广东省2022下半年软考报名时间已定!
    ddns-go配合aliyun域名解析通过ipv6实现共享桌面
    Python基础学习笔记1(AI Studio)
    任你五花八门预训练方法,我自监督学习依然能打!
  • 原文地址:https://blog.csdn.net/baidu_28523317/article/details/133783158