专栏首页网管叨bi叨Go 服务进行自动采样性能分析的方案设计与实现

Go 服务进行自动采样性能分析的方案设计与实现

线上服务的性能分析,一直以来都是比较难的点,主要是难在无法在性能出现异常的当时捕捉到现场信息。有人可能会说,这有什么难的,直接用 Go 工具集里的 pprof 访问一下,进行采样拿下来分析就行了。话虽不假,不过抛开现实场景谈解决方案一般都会非常打脸,真的不行。

举个现实中的例子,比如服务可能由于哪里的代码写的不好,内存占用率一点点涨上来,表现出来的现象就是服务隔一段时间就会重启一次(被集群重新拉起来),如果服务部署在 K8s上,那 K8s 集群更是会发现 Pod 的资源超限后把 Pod 杀掉重新创建一个。即使你能 24 小时 Oncall,在收到告警的那一刻程序可能已经重启了,所以传统的访问 pprof 路由主动采样在这种场景下不可取。

前段时间发表的文章学会这几招让 Go 程序自己监控自己 有读者问到,让Go程序监控自己进程的各项指标有何用处,我的回答是可以做一些服务治理,或者程序内部分析的事情。今天就借此跟大家分享一下,怎么给 Go 程序做自动采样。

下面我带着大家先看一看 Go 程序怎么采样,再剖析一下怎么让 Go 程序进行自动采样。

Go 的采样工具

Gopprof工具集,提供了Go程序内部多种性能指标的采样能力,我们常会用到的性能采样指标有这些:

  • profile:CPU采样
  • heap:堆中活跃对象的内存分配情况的采样
  • goroutine:当前所有goroutine的堆栈信息
  • allocs: 会采样自程序启动所有对象的内存分配信息(包括已经被GC回收的内存)
  • threadcreate:采样导致创建新系统线程的堆栈信息

怎么获取采样信息

网上最常见的例子是在服务端开启端口让客户端通过HTTP访问指定的路由进行各种信息的采样

import (
 "net/http/pprof"
)

func main() {
 http.HandleFunc("/debug/pprof/heap", pprof.Index)
 http.HandleFunc("/debug/pprof/profile", pprof.Profile)
 http.HandleFunc("/", index)
 ......
 http.ListenAndServe(":80", nil)
}
$ go tool pprof http://localhost/debug/pprof/profile

这种方法的弊端就是

  • 需要客户端主动请求特定路由进行采样,没法在资源出现尖刺的第一时间进行采样。
  • 会注册多个/debug/pprof类的路由,相当于对 Web 服务有部分侵入。
  • 对于非 Web 服务,还需在服务所在的节点上单独开 HTTP 端口,起 Web 服务注册 debug 路由才能进行采集,对原服务侵入性更大。

Runtime pprof

除了上面通过HTTP访问指定路由进行采样外,还有一种主要的方法是使用runtime.pprof 提供的Lookup方法完成各资源维度的信息采样。

// lookup takes a profile name
pprof.Lookup("heap").WriteTo(some_file, 0)
pprof.Lookup("goroutine").WriteTo(some_file, 0)
pprof.Lookup("threadcreate").WriteTo(some_file, 0)

CPU的采样方式runtime/pprof提供了单独的方法在开关时间段内对 CPU 进行采样

bf, err := os.OpenFile('tmp/profile.out', os.O_RDWR | os.O_CREATE | os.O_APPEND, 0644)
err = pprof.StartCPUProfile(bf)
time.Sleep(2 * time.Second)
pprof.StopCPUProfile()

这种方式是操作简单,把采样信息可以直接写到文件里,不需要额外开端口,再手动通过HTTP进行采样,但是弊端也很明显--不停的采样会影响性能。固定时间间隔的采样(比如每隔五分钟执行一次采样)不够有针对性,有可能采样的时候资源并不紧张。

适合采样的时间点

经过了上面的分析,现在看来只要让Go进程在自己占用资源突增或者超过一定的阈值时再用pprof对程序Runtime进行采样,才是最合适的,那么接下来我们就要想一下,到底以什么样的规则,才能判断出当前周期是适合采样的时段呢。

判断采样时间点的规则

CPU 使用,内存占用和 goroutine 数,都可以用数值表示,所以无论是使用率慢慢上升直到超过阈值,还是突增之后迅速回落,都可以用简单的规则来表示,比如:

  • cpu/mem/goroutine数 突然比正常情况下的平均值高出了一定的比例,比如说资源占用率突增25%就是出现了资源尖刺。
  • cpu/mem/goroutine数 超过了程序正常运行情况下的阈值,比如说80%就定义为服务资源紧张。

这两条规则可以描述大部分情况下的异常,第一个规则可以表示瞬时的,剧烈的抖动,之后可能迅速恢复正常的情况。

规则二可以用来表示那些缓慢上升,但最终超出阈值的情况,例如下图中内存使用率一直在慢慢上升,直到超过了设置的80%的阈值。

内存使用率超过80%

而规则一判断资源突增,需要与历史均值对比才行。在没有历史数据的情况下,就只能在程序内自行收集了,比如进程的内存使用率,我们可以以每 10 秒为一个周期,运行一次采集,在内存中保留最近 5 ~ 10 个周期的内存使用率,并持续与之前记录的内存使用率均值进行比较:

内存使用率突增超过25%

比如像上图里的情况,前五个周期收集到的内存占用率在 35% 左右波动,而最新周期收集到的数据为70%,这显然是瞬时突增导致的异常情况,那么我们就可以在这个时间点,自动让程序调用 pprof 把 mem 信息采样到文件里,后续无论服务是否已重启都能把采样信息拿下来分析。

而关于怎么让Go程序获取本进程的CPU、Mem使用量在系统中的占比,我们在之前的文章 学会这几招让 Go 程序自己监控自己 已经跟大家分享过了,只需要编码实现在上面描述的两个采样时机进行性能采样即可。

开源的自动采样库

社区里其实已经有开源库实现了类似的功能,比如曹大在蚂蚁的时候设计的Holmes ,其实曹大在桃花源公众号里发文章分享过。

使用起来也比较方便,比如下面是一个对内存使用率突增 25% 和超过阈值 80% 这两种情况下让程序自动进行Mem信息采样的例子。如果你公司里的基建还没有到把持续采样做到统一平台里的水平的话,Holmes 是一个比较好的选择,能快速解决问题,比较适合中小型的业务快速发展的团队。

简单的看一个 Web 服务接入 Holmes 分析内存占用率的例子。

package main

import (
 "net/http"
 "time"

 "github.com/mosn/holmes"
)

func init() {
 http.HandleFunc("/make1gb", make1gbslice)
 go http.ListenAndServe(":10003", nil)
}

func main() {
 h, _ := holmes.New(
  holmes.WithCollectInterval("2s"),
  holmes.WithCoolDown("1m"),
  holmes.WithDumpPath("/tmp"),
  holmes.WithTextDump(),
  holmes.WithMemDump(3, 25, 80),
 )
 h.EnableMemDump().Start()
 time.Sleep(time.Hour)
}

func make1gbslice(wr http.ResponseWriter, req *http.Request) {
 var a = make([]byte, 1073741824)
 _ = a
}
  • WithCollectInterval("2s") 指定 2s 为区间监控进程的资源占用率, 线上建议设置大于10s的采样区间。
  • WithMemDump(3, 25, 80) 指定进程的mem占用率超过3%后(线上建议设置成30),如果有25%突增,或者总占用率超过80%后进行采样

通过采样能获取到了内存资源突增时的程序调用栈,[1: 1073741824] 表示有一个对象消耗了1GB的内存,通过调用栈分析我们也能快速找到找到造成资源占用的代码位置。

heap profile: 0: 0 [1: 1073741824] @ heap/1048576
0: 0 [1: 1073741824] @ 0x42ba3ef 0x4252254 0x4254095 0x4254fd3 0x425128c 0x40650a1
# 0x42ba3ee main.make1gbslice+0x3e   /Users/xargin/go/src/github.com/mosn/holmes/example/1gbslice.go:24
# 0x4252253 net/http.HandlerFunc.ServeHTTP+0x43 /Users/xargin/sdk/go1.14.2/src/net/http/server.go:2012
# 0x4254094 net/http.(*ServeMux).ServeHTTP+0x1a4 /Users/xargin/sdk/go1.14.2/src/net/http/server.go:2387
# 0x4254fd2 net/http.serverHandler.ServeHTTP+0xa2 /Users/xargin/sdk/go1.14.2/src/net/http/server.go:2807
# 0x425128b net/http.(*conn).serve+0x86b  /Users/xargin/sdk/go1.14.2/src/net/http/server.go:1895

关于这个库更详细的使用介绍可以直接看仓库提供的Get Started 教程 https://github.com/mosn/holmes

另外我还做了个docker 方便进行试验,镜像已经上传到了Docker Hub上,大家感兴趣的可以Down下来自己在电脑上快速试验一下。

通过以下命令即可快速体验。

docker run --name go-profile-demo -v /tmp:/tmp -p 10030:80 --rm -d kevinyan001/go-profiling

容器里Go服务提供的路由如下

Holmes 试验容器的路由

三个路由分别对应了内存、CPU过载、通道阻塞,可以直接压测访问,观察效果。自动采样的结果除了进到容器里去看外,还可以在本地和容器做映射的 /tmp 目录中找到。

文章分享自微信公众号:
网管叨bi叨

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:KevinYan11
原始发表时间:2021-12-27
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 容器环境下 go 服务性能诊断方案设计与实现

    业务上量以后,对程序进行 profiling 性能诊断对很多后端程序员来说就是家常便饭。一个趁手的工具往往能让这个事情做起来事半功倍。

    李海彬
  • 利用火焰图对 Go 程序进行性能分析

    学会对应用系统进行运行时数据采集与性能分析是软件工程实践常用的基本技能。通常使用 profile 表示性能分析与采集,或者使用 profiling 代表性能分析...

    aoho求索
  • day3 | 高质量编程与性能调优实战 | 第三届字节跳动青训营笔记

    实际应用场景千变万化,各种语言的特性和语法各不相同。但是高质量编程遵循的原则是相通的

    千羽
  • Go学习_30_Golang代码性能分析工具

    Golang内置了一些性能分析工具,可以将性能分析的结果文件输出,我们可以使用图形化的工具查看分析结果,在使用这些工具之前,我们需要安装一些工具,以便于查看分析...

    码农帮派
  • 基于 Nebula Graph 构建图学习能力

    经常看技术文章的小伙伴可能会留意到除了正在阅读的那篇文章,在文章页面的正文下方或者右侧区域会有若干同主题、同作者的文章等你阅读;经常逛淘宝、京东的小伙伴可能发现...

    NebulaGraph
  • 白话 Golang pprof

    有时,我们开发的 Golang 程序会出现性能上不去,CPU 使用率达到 100%,内存使用量过大,死锁等性能问题,我们该如何定位程序出现上诉问题的具体位置,来...

    Dabelv
  • 深度解密Go语言之pprof

    相信很多人都听过“雷神 3”关于性能优化的故事。在一个 3D 游戏引擎的源码里,John Carmack 将 1/sqrt(x) 这个函数的执行效率优化到了极致...

    梦醒人间
  • Go语言核心36讲(Go语言实战与应用二十七)--学习笔记

    在上一篇文章中,我们围绕着“怎样让程序对 CPU 概要信息进行采样”这一问题进行了探讨,今天,我们再来一起看看它的拓展问题。

    郑子铭
  • Golang性能调优(go-torch, go tool pprof)

    Go语言已经为开发者内置配套了很多性能调优监控的好工具和方法,这大大提升了我们profile分析的效率。此外本文还将重点介绍和推荐uber开源的go-torch...

    Walton
  • 想在研发群里装?先学会这几个排查K8s问题的办法

    新手学习 K8s 最大的难度感觉是在起步动手实践的时候,Pod 没有正常启动起来,或者运行了一段时间 Pod 自己崩溃了。那么是什么问题导致了它没运行起来,又或...

    KevinYan
  • 扩散几个高端职位吧

    运维部落
  • 微服务中网关(API Gateway)的技术选型

    用 Spring Cloud 微服务实战中,大家都知道用 Zuul 作为智能网关。API 网关(API Gateway)主要负责服务请求路由、组合及协议转换。下...

    天涯泪小武
  • 生产环境Go程序内存泄露,用pprof如何快速定位

    内存泄漏可以在整个系统中以多种形式出现,除了在写代码上的疏忽,忘了关闭该关闭的资源外,更多的时候导致系统发生内存泄露原因可能是设计上决策不对、或者业务逻辑上的疏...

    KevinYan
  • 浅谈分布式链路追踪之Jaeger

    因此,在实际的生产业务场景中,为了能够全方位地追踪每一个相关组件的行为轨迹,就需要一些能够可以帮助我们理解、追踪系统行为、用于分析性能问题的工具,以便...

    Luga Lee
  • 应用性能前端监控,字节跳动这些年经验都在这了

    随着用户数量的不断增长,对于站点体验衡量的的需求也日益紧迫,用户会将产品和他们每天使用的体验最好的 Web 站点进行比较。想着手优化,则必须先有相关的监控数据,...

    ssh_晨曦时梦见兮
  • 前端每周清单第 53 期:Go 与 WebAssembly, React Suspense 演练, CSS 技巧

    前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎关注...

    王下邀月熊
  • Go语言核心36讲(Go语言实战与应用二十六)--学习笔记

    Go 语言为程序开发者们提供了丰富的性能分析 API,和非常好用的标准工具。这些 API 主要存在于:

    郑子铭
  • Go Execution Tracer工具使用

    在Go工具链中,go tool pprof(与runtime/pprof或net/http/pprof联合使用)是一个基于采样(sampling)的性能剖析(p...

    Johns
  • 百亿级图数据在快手安全情报的应用与挑战

    快手是一家全球领先的内容社区和社交平台,旨在通过短视频的方式帮助人们发现所需、发挥所长,持续提升每个人独特的幸福感。

    NebulaGraph

扫码关注腾讯云开发者

领取腾讯云代金券