这是我参与「第三届青训营 -后端场」笔记创作活动的的第 3 篇笔记。
在开始前要提前安装graphviz:在第二节性能调优实战中打开web页面会用到。
什么是高质量 ————编写的代码能够达到正确可靠,简洁清晰的目标
1.2.2 小结
变量
举例:
i和index的作用域范围仅限于for循环内部时,index的额外冗长没有增加对于程序的理解
方法
举例:
package http
func Serve(l net.Listener, handler Handler) error
func ServeHTTP(l net.Listener, handler Handler) error
在http包下明显第一个方法名更好
包
以下规则尽量满足,以标准库包名为例
1.2.3 小结
避免嵌套,保持正常流程清晰
尽量保持正常代码路径为最小缩进
优化后:
1.2.4 小结
简单错误
错误的 Wrap 和 Unwrap
错误判定
panic
recover
1.2.5 小结
程序的输出是什么?
最终输出:31
简介
如何使用
go test -bench=. -benchmem
结果说明
slice 预分配内存
slice 预分配内存
slice结构体
另一个陷阱:大内存未释放
map 预分配内存
分析
字符串拼接方式
string
strings.Builder
bytes.Buffer
对比
strings.Builder 直接将底层的[]byte 转换成了字符串类型返回
bytes.buffer 转化为字符串时重新申请了一块空间
进行了预分配
使用空结构体节省内存
如何使用 atomic 包
1.3 小结
说明
搭建 pprof 实践项目
前置准备
浏览器查看指标
浏览器输入 http://localhost:6060/debug/pprof/
CPU
我们先从CPU问题排查开始,不同的操作系统工具可能不同,我们首先使用自己熟悉的工具看看程序进程的资源占用,CPU占用了58%,显然这里是有问题的
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
这里我们使用go tool pprof +采样链接来启动采样。
连接中就是刚刚「|炸弹」程序暴露出来的接回,连接结尾的tporief表采样的对金是CU使用。如果你在流说克器里直接打开这个钳接,会定动一个60s的采祥,并在纯束后下数文件。这里我和们加上scndses = 10的参势数,让他采样10s.
稍等片刻,我们需要的采样数据已经记录和下载完成,并展示出pprof终端
CPU
以上命令可以定位[炸弹]的位置,用q退出终端。
接下来就是将[炸弹]删除:将/animal/felidae/tiget/tiget.go文件下的 23-26行注释,重新运行
heap-堆内存
终端输入:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
这里浏览器打开的是[内存占用]
点击view按钮切换到source
根据top图路径打开mouse.go,在steal()这个函数会向固定的Buffer中不断追加1MG内存,直到Buffer达到1GB大小位置,和我们再Graph视图中发现的情况一致。我们将代码注释掉。
至此,[炸弹]已经拆除两个了
重新运行程序,内存降低了
我们注意到右上角有个 unknown inuse_space。
我们打开sample菜单,会发现最内存实际上提供了四种指标。
在堆内存采样中,默认展示的是inuse_space视图,只展示当前持有的内存,但如果有的内存已经释放,这时inuse采样就不会展示了。我们切换到alloc_ space指标。后续分析下alloc的内存问题
点击 alloc_space,鼠标点击一下dog
在进去 source,然后根据路径找到dog.go,将128M这行代码注释掉
至此,内存部分的[炸弹]已经被全部拆除。
goroutine-协程
Golang是一门自带垃圾回收的语言,一般情况下内存泄露是没那么容易发生的。
但是有一种例外: goroutine是很容易泄露的,进而会导致内存泄露。
打开http;//ocalhost6060/debug/pprof/,发现「炸弹」程序已经有65条goroutine在运行了,这个量级并不是很大,但对于一个简单的小程序来说还是很不正常的。
终端输入:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
就会出现一张非常长的调用关系图,但是这种比较不好阅读,推荐使用火焰图
火焰图:
打开view菜单,切换到flame graph视图,可以看到刚刚的节点被堆叠起来。
图中自顶向下展示了各个调用,表示各个函数之间的层级关系,每一行中,条形越长代表消耗的资源占比越多,可以看到wolfy资源占比95.24%,所以我们打开source。
发现每次都会发起十次无意义的goroutine,每次等待30秒菜退出,导致goroutine泄露。这里为了模拟泄漏场景,只设置了30秒,如果发起的goroutine没有退出,不断有新的goroutine被启动,内存占比持续增长,cpu调度压力不断增大,最终进程会被系统kill掉。
我们注释掉代码,重启,可以看到goroutine恢复正常水平
mutex-锁
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
修改链接后缀,改成mutex,然后打开网页观察,发现存在1个锁操作同样地,在Graph视图中定位到出问题的函数在Wolf.Howl()
然后在Source视图*中定位到具体哪—行发生了锁竞争
在这个函数中,goroutine足足等待了1秒才解锁,在这里阻塞住了,显然不是什么业务需求,注释掉。
block-阻塞
重启后,可以看到页面中block还剩两个
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
和刚才一样,graph到source视图切换,可以看到,在*Cat.Pee()函数中读取了一个time.Afterl)生成的channel这就导致了这个goroutine实际上阻塞了1秒钟,而不是等待了1秒钟。我们注释掉,不用重启
2.2.2 小结
俗话说,知其然还要知其所以然,接下来我们来看看它们内部的实现
cpu
首先来看CPU。CPU采样会记录所有的调用栈和它们的占用时间。
在采样时,进程会每秒暂停一百次,每次会记录当前的调用栈信息。汇总之后,根据调用栈在采样中出现的次数来推断函数的运行时间。
你需要手动地启动和停止采样。每秒100次的暂停频率也不能更改。这个定时暂停机制在unix或类unix系统上是依赖信号机制实现的。
每次「暂停」都会接收到一个信号,通过系统计时器来保证这个信号是固定频率发送的。接下来看看具体的流程。
Heap-堆内存
Goroutine-协程 & ThreadCreate-线程创建
Goroutie采样会记录所有用户发起,也就是入口不是runtime开头的goroutine, 以及main函数所在goroutine的信息和创建这些goroutine的调用栈;
他们在实现上非常的相似,都是会在STW之后,遍历所有goroutine/所有线程的列表(图中的m就是GMP模型中的m,在golang中和线程——对应)并输出堆栈,最后Start The World继续运行。这个采样是立刻触发的全量记录,你可以通过比较两个时间点的差值来得到某—时间段的指标。
Block-阻塞 & Mutex-锁
这两个指标在流程和原理上非常相似,
这两个采样记录的都是对应操作发生的调用栈、次数和耗时,不过这两个指标的采样率含义并不相同。
阻塞操作的采样率是一个「阈值」,消耗超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。记得炸弹程序的main代码吗?里面设置了rate=1。
锁竞争的采样率是一个「比例」,运行时会通过随机数来只记录固定比例的锁操作,1为每次操作都会记录。
它们在实现上也是基本相同的。都是—个「主动上报」的过程。
在阻塞操作或锁操作发生时,会计算出消耗的时间,连同调用栈一起主动上报给采样器,采样器会根据采样率可能会丢弃-些记录.
在采样时,采样器会遍历已经记录的信息,统计出具体操作的次数、调用栈和总耗时。和堆内存一样,你可以对比两个时间点的差值计算出段时间内的摄作指标.
2.2 小结
简介
介绍实际业务服务性能优化的隶例
对逻辑相对复杂的程序如何进行性能调优
务服务优化
基本概念
流程
建立服务性能评估手段
评估手段建立后,它的产出是什么呢?实际是一个服务的性能指标分析报告。
实际的压测报告截图,会统计压测期间服务的各项监控指标,包括qps,延迟等内容,同时在压测过程中,也可以采集服务的pprof数据,使用之前的方式分析性能问题
分析性能数据,定位性能瓶颈
比如这里通过火焰图看出json的解析部分占用了较多的CPU资源,那么我们就能定位到具体的逻辑代码,是在每次使用配置时都会进行json解析,拿到配置项,实际组件内部提供了缓存机制,只有数据变更的时候才需要重新解析json
低峰期性能数据
可以发现metrics,即监控组件的CPU资源占用变化较大,主要原因是监控数据上报是同步请求,在请求量上涨,监控打点数据量增加时,达到性能瓶颈,造成阻塞,影响业务逻辑的处理,后续是改成异步上报的机制提升了性能
重点优化项改造
定位到性能瓶颈后,我们也有了对应的修复手段,但是修改完后能直接发布上线吗?
性能优化的前提是保证正确性,所以在变动较大的性能优化上线之前,还需要进行正确性验证,因为线上的场景和流程太多,所以要借助自动化手段来保证优化后程序的正确性同样是线上请求的录制,不过这里不仅包含请求参数录制,还会录制线上的返回内容,重放时对比线上的返回内容和优化后服务的返回内容进行正确性验证
比如图中作者信息相关的字段值在优化有有变化,需要进—步排查原因
优化效果验证
进一步优化,服务整体链路分析
AB实验SDK的优化
编译器 & 运行时优化
图中服务知识换用新的版本进行编译,CPU占用降低8%
2.4 总结