Golang性能问题排查定位 | 青训营笔记

492 阅读5分钟

这是我参与「第三届青训营-后端场」笔记创作活动的第2篇笔记。

高质量编程

常见编码规范

  • 代码格式

    • gofmt
    • goimports
  • 注释

    • 应该

      • 解释代码作用
      • 解释如何做的
      • 解释代码实现原因
      • 解释代码在什么情况下会出错
    • 原则

      • 包中声明的每个公共符号(变量、常量、函数、结构)始终要注释
      • 公共功能必须注释
      • 对库中任何函数都必须进行注释
    • 小结

      • 代码是最好的注释
      • 注释提供代码未表达出的上下文信息
  • 命名规范

    • 变量
    • 函数
  • 控制流程
  • 错误和异常处理

性能优化建议

  • 基准性能测试--benchmark工具
  • slice

    • 预分配内存
    • 释放未使用的内存
  • map

    • 预分配内存
  • 字符串处理

    • 字符串拼接用strings.Builder
  • 空结构体

    • 空结构体struct{}实例不占据任何内存空间,可用作占位符
  • atomic包

    • 锁通过操作系统实现,属于系统调用,而atomic通过硬件实现,效率比锁高

性能调优

简介

  • 原则

    • 依靠数据而不是猜测
    • 定位最大瓶颈而不是细枝末节
    • 不要过早优化
    • 不要过度优化

性能分析工具pprof

功能说明

  • 说明:用于可视化和分析性能分析数据的工具
  • 功能

    • 分析

      • 网页
      • 可视化终端
    • 工具

      • runtime/pprof
      • net/http/pprof
    • 采样

      • CPU
      • 堆内存-Heap
      • 协程-Goroutine
      • 锁-Mutex
      • 阻塞-Block
      • 线程创建-ThreadCreate
    • 展示

      • 列表-List
      • Top
      • 调用图-Graph
      • 火焰图-FlameGraph
      • Peek
      • 源码-Source
      • 反汇编-Disassemble

实际案例

  • 使用pprof定位性能问题点

    • 进入pprof测试项目文件夹,运行go run main.go

    • 在浏览器中打开 http://localhost:6060/debug/pprof/

    • 展示字段说明

      • allocs:内存分配情况
      • blocks:阻塞操作情况
      • cmdline:程序启动命令
      • goroutine:当前所有goroutine的堆栈信息
      • heap:堆上内存使用情况
      • mutex:锁竞争操作情况
      • profile:CPU占用情况
      • threadcreate:当前创建的所有系统线程的堆栈信息
      • trace:程序运行跟踪信息
    • 浏览器直接查看指标时可读性很差

    • CPU资源占用过高问题排查定位

      • 查看程序进程的资源占用情况

      • 启动采样得到pprof采样结果

        • 途径1 - 直接点击"profile"打开链接,默认会启动一个60s的采样,时间结束后会下载文件
        • 途径2 - go tool pprof "``http://localhost:6060/debug/pprof/profile?``seconds=10"会自动下载profile文件并分析
      • 使用go tool pprof命令分析profile文件

        • 下载并分析profile文件,进入pprof终端

        • 使用top/topN命令查看占用CPU资源最多的函数

        • 指标

          • flat-当前函数本身的执行耗时
          • falt%-flat占CPU总时间的比例
          • sum%-上面每一行的flat%的总和
          • cum-当前函数本身加上其调用函数的总耗时
          • cum%-cum占CPU总时间的比例
        • 说明

          • 默认展示资源占用最高的10个函数;使用命令topN可以只查看资源占用最高的N个函数
          • flat == cum: 函数中没有调用其他的函数
          • flat == 0:函数中只有其他函数的调用
        • 使用list命令根据指定的正则表达式查找代码行

        •       从图中结果可以看出,有一处100亿次的空循环,问题定位成功

        • 使用web命令生成调用关系图

          • Windows下直接下载可执行文件安装,但安装完成后记得要重启,否则环境变量会不生效
          •         调用关系图中,会显示每个节点的资源占用情况,最大的方框即为定位出的CPU“炸弹”。因此,我们将这段引起CPU资源占用异常的问题代码注释掉。
          •         重新运行程序,可以发现进程CPU占用资源已经降下来了。但,内存使用还是很高,所以接下来排查内存使用异常的问题。
    • Heap-堆内存使用问题排查定位

      • 使用命令go tool pprof -http=:8080 "``http://localhost:6060/debug/pprof/heap``",通过-http=:8080参数,可以开启pprof自带的Web UI,可以看到内存占用情况。在VIEW中还可以切换不同的视图。
      •     可以看出,内存使用异常出现在上图代码,故将其注释掉。
      •     至此,内存使用异常“炸弹”已排除。
    • Goroutine问题排查定位

      • 使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

        • 节点图
        • 火焰图
        •       火焰图中,由上到下表示调用顺序;每一块代表了一个函数,越长代表占用CPU的时间越长;火焰图是动态的,支持点击块进行分析。
        •       可以看到(*Wolf).Drink这个调用创建了超过90%的Goroutine,于是将视图切换至源码页面,通过正则搜索出Wolf相关函数。
        •       可以定位到问题是没有及时退出Goroutine。
    • Mutex问题排查定位

      • 使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
      •     可以发现Goroutine足足等待了1秒才解锁,因此导致锁竞争的产生。将等待的1秒注释后,锁的竞争导致的阻塞时间显著减少。
    • Block问题排查定位

      • 使用命令go tool pprof -http=:8080 "``http://localhost:6060/debug/pprof/block``"
      •     在程序中,除了锁的竞争会导致阻塞之外,很多逻辑如读取一个Channel也会导致堵塞。