go性能调试工具

虽然不一定都需要

  • gv,go自带的pprof命令,实际上是Graphviz,搜索安装就可以了
  • web,同gv,不过是在浏览器中显示
  • go-torch,火焰图

案例背景

源自于同事的socket连接服务,服务启动一段时间后,内存飙升,呈现累积不下的现象,初步判断为内存泄漏(top下单个服务占用29G内存).

问题定位

  1. 找到函数
    1
    go tool pprof --inuse_space {{host}}/debug/pprof/heap

结果如下:

upload successful

  1. 分析代码(由于代码变动,实际上137行移动到140行)
    upload successful

    • 很明显是函数中msgData泄漏了.但是一开始在分析怎么泄漏的时候走了不少弯路
    • 因为conn是socket连接,所以在一开始Read的时候,读出的数据包是要自定义解析的,初步判定msgLen值过大引起分配过大,至于为什么过大(是客户端传错了还是怎么的,暂不考虑,主要是也没打日志).
    • 那么为什么分配过大会引起内存泄漏?因为go是自带gc的语言
    • 弯路: 怀疑go线程泄漏(因为想当然的认为接下来的错误没有问题),于是去分析goroutine
      upload successful
    • 一顿分析之后还是觉得不是goroutine的泄漏,很简单,因为前面内存泄漏是指向这个函数的
    • 查看该函数相关的阻塞,当然肯定不是block里面的阻塞(为什么呢?因为这个函数很简单,msgData泄漏的唯一可能是这个变量没有被使用,于是怀疑上是阻塞).重新打开goroutine的pprof界面,搜索该方法及文件名,很快发现一些IO Wait状态线程,于是确定是阻塞

    upload successful

    • 那么,唯一可能的阻塞,也正如heap文件指向的 140(137)行之后的io.ReadFull方法
    • 一开始想到阻塞的时候甚至用byte.NewReader配合写了一个测试,然后否定了
      upload successful
    • 再次定位确认的时候,只好查阅官方文档.话外的意思就是说,一开开始读到了,不够了,要遇到EOF才返回.

    upload successful

    • 于是想到之前的测试用例,读完是有EOF的,本着严谨的态度确认一下去bytes包的文档,当然下面给出的是Buffer的注释(Reader下没有)

    upload successful

    • 在去看一下net.Conn下的Read方法的注释,于是确认了这里这样读是会阻塞的

    upload successful

解决问题

  1. 设置最大的buffer值.该同学设置过,但是使用默认的设置值过大,没有效果.这样可以避免内存阻塞过大,但是解决不了阻塞问题
  2. 使用SetReadDeadline读取数据,这样就要在143行读取之后,再次将该值设置成0或者比较大的一个值,比较麻烦.