通过 profiling 定位 golang 性能问题 - 内存篇

https://mp.weixin.qq.com/s?__biz=MzAxMTA4Njc0OQ==&mid=2651439020&idx=1&sn=c2094f4dccb53385dc207958e7f42f9e&chksm=80bb615eb7cce8481eb7a8f09d4a13e2974b3785c241dd31245647cd7540dde414d64f2b3719&mpshare=1&scene=1&srcid=0402ToVRKZ6tQK0fSwHDJhfU&sharer_sharetime=1585807513172&sharer_shareid=db2ae73baacdd02d57e5caf4852f19e2&key=1b1c011b6d58afbcd7ca18accd056d19fed9a2582113bb78e20728e3791cb6bcff986148efc178ded752f35e5531c97031a937cb66f42a223136a151fabbecfbf87ad08f941b170b930a552db091728a&ascene=1&uin=NDMwNDI2OTM1&devicetype=Windows+10&version=6208006f&lang=zh_CN&exportkey=AS39andHynxHnFtgVjyErRE%3D&pass_ticket=9JHridWjn9I38PSBQKVpaveigDEeQ4tJR%2BWt0tjOqhQ%2F3QrpoXAJ%2BqiE%2FWooMeCh

在遇到线上的性能问题时,面对几百个接口、成吨的日志,如何定位具体是哪里的代码导致的问题呢?这篇文章会分享一下 profiling 这个定位性能问题的利器,内容主要有:

1.如何通过做 profiling 来精准定位故障源头

2.两个工作中通过 profiling 解决性能问题的实际例子

3.总结在做 profiling 时如何通过一些简单的现象来快速定位问题的排查方向

4.日常 golang 编码时要避开的一些坑

5.部分 golang 源码解析

profiling是什么

profile 一般被称为 性能分析,词典上的翻译是 概况(名词)或者 描述…的概况(动词)。对于计算机程序来说,它的 profile,就是一个程序在运行时的各种概况信息,包括 cpu 占用情况,内存情况,线程情况,线程阻塞情况等等。知道了程序的这些信息,也就能容易的定位程序中的问题和故障原因。
golang 对于 profiling 支持的比较好,标准库就提供了profile库 "runtime/pprof" 和 "net/http/pprof",而且也提供了很多好用的可视化工具来辅助开发者做 profiling

使用:

import _ "net/http/pprof"
func main() {
  go func() {

    log.Println(http.ListenAndServe("0.0.0.0:8005", nil))

   }()

// ..... 下面业务代码不用动
}

golang 对于 profiling 的支持比较完善,如代码所示,只需要简单的引入 "net/http/pprof" 这个包,然后在 main 函数里启动一个 http server 就相当于给线上服务加上 profiling 了,通过访问 8005 这个 http 端口就可以对程序做采样分析。 
服务上开启pprof之后,在本地电脑上使用 go tool pprof 命令,可以对线上程序发起采样请求,golang pprof 工具会把采样结果绘制成一个漂亮的前端页面供人们排查问题。

 


等到故障再次复现时,我们首先对 cpu 性能进行采样分析:
对CPU进行分析:
brew install graphviz # 安装graphviz,只需要安装一次就行了
go tool pprof -http=:1234 http://your-prd-addr:8005/debug/pprof/profile?seconds=30 

打开 terminal,输入上面命令,把命令中的 your-prd-addr 改成线上某台机器的地址,然后回车等待30秒后,会自动在浏览器中打开一个页面,这个页面包含了刚刚30秒内对线上cpu占用情况的一个概要分析。点击左上角的 View 选择 Flame graph,会用火焰图(Flame graph)来显示cpu的占用情况:

 

 

对内存进行分析:
go tool pprof http://your-prd-addr:8005/debug/pprof/heap 

然后在浏览器里点击左上角 VIEW-》flame graph,然后点击 SAMPLE-》inuse_objects。
这样显示的是当前的对象数量:

 

 

在做内存问题相关的 profiling 时:

 

  • 若 gc 相关函数占用异常,可重点排查对象数量

  • 解决速度问题(CPU占用)时,关注对象数量( --inuse/alloc_objects )指标

  • 解决内存占用问题时,关注分配空间( --inuse/alloc_space )指标

  • inuse 代表当前时刻的内存情况,alloc 代表从从程序启动到当前时刻累计的内存情况,一般情况下看 inuse 指标更重要一些,但某些时候两张图对比着看也能有些意外发现。

 

在日常 golang 编码时:

 

  • 参数类型要检查,尤其是 sql 参数要检查(低级错误)
  • 传递struct尽量使用指针,减少复制和内存占用消耗(尤其对于赋值给interface,会分配到堆上,额外增加gc消耗)
  • 尽量不使用循环引用,除非逻辑真的需要
  • 能在初始化中做的事就不要放到每次调用的时候做











 

posted @ 2020-04-02 14:16  small_lei_it  阅读(375)  评论(0)    收藏  举报