这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
一、本节课重点内容
1. 性能调优原则
2. 性能分析工具
3. 性能调优案例
二、详细知识点介绍
1. 性能调优原则
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
2. 性能分析工具
性能调优的核心是性能瓶颈的分析,对于Go程序,最方便的是pprof工具
pprof功能说明
- pprof是用于可视化和分析性能分析数据的工具
- 可以知道应用在什么地方耗费了多少CPU、memory等运行指标
pprof实践
- 排查CPU问题
命令行分析
- go tool pprof “http://localhost:6060/debug/pprof/profile?seconds=10”
- top命令
- list命令
- 熟悉web页面分析
- 调用关系图、火焰图
- go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/cpu”
- 排查堆内存问题
go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/heap”
- 排查协程问题
go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/goroutine”
- 排查锁问题
go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/mutex”
- 排查阻塞问题
go tool pprof -http=:8080 “http://localhost:6060/debug/pprof/block”
3. pprof的采样过程和原理
CPU采样
启动采样时,进程向OS注册一个定时器,OS会每10ms向进程发送一个SIGPROF信号,进程接收到信号后就对当前调用栈进行记录。同时进程启动一个写缓冲的goroutine,它每隔100ms从进程中读取已记录的堆栈信息,并写入到输出流。当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。
堆内存采样
堆内存采样在实现上依赖内存分配器的记录,一些其他的内存分配,如栈内存、一些更底层使cgo调分配的内存,不会被采样记录,采样率是默认每分配512KB内存采样一次,采样率可以调整,设为1则每次分配都会记录。与CPU和goroutine都不同的是,内存的采样是个持续的过程,它记录从程序运行起的所有分配或释放的内存大小和对象数量,并在采样时谝历这些结果进行汇总。
协程和系统线程采样
Goroutie采样会记录所有用户发起,也就是入口不是runtime开头的goroutine,以及main所在goroutine的信息和创建这些goroutine的调用栈
它们都是会在STW后,漏历所有goroutine/线程的列表〔图中的m就是GMP模型中的m,在golang中和线程对应)并输出堆栈,最后STW继续运行。该采样是立刻触发的全量记录,可以比较两个时间点的差值来得到某一时间段的指标。
阻塞操作和锁竞争采样
两指标在流程和原理上相似,不过指标的采样率含义不同:
- 阻塞操作的采样率是个阈值,超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。炸弹程序的main里面设置rate=1
- 锁竟争的采样率是个比例,运行时会通过随机数来记录固定比例的锁操作,1为每次操作都记录
实现也基本相同,在阻塞或锁操作发生时,会算出消耗的时间,连同调用栈一起主动上报给采样器,采样时,采样器会遍历已记录的信息,统计出具体操作次数、调用栈和总耗时。同样可以算两个时间点的差值算出短时间内的操作指标。
4. 性能调优案例
基本概念
- 服务:能单独部署,承载一定功能的程序
- 依赖:Service A的功能实现依赖Service B的响应结果,称为Service A依赖Service B
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包、中间件
业务优化
- 流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
- 建立压测评估链路
- 服务性能评估
- 构造请求流量
- 压测范围
- 性能数据采集
- 分析性能火焰图、定位性能瓶颈
- pprof火焰图
- 重点优化项分析
- 规范组件库使用
- 高并发场景优化
- 增加代码检查规则避免增量劣化出现
- 优化正确性验证
- 上线验证评估
- 逐步放量,避免出现问题
- 进一步优化,服务整体链路分析
- 规范上游服务调用接口,明确场景需求
- 分析业务流程,通过业务流程优化提升服务性能
基础库优化
适应范围更广,覆盖更多服务,包括:
- AB实验SDK的优化
- 分析基础库核心逻辑和性能瓶颈
- 完善改造方案,按需获取,序列化协议优化
- 内部压测验证
- 推广业务服务落地验证
- Go 语言优化
- 适应范围广通用性强,接入简单只需调整编译配置
- 优化方式
- 优化内存分配策略
- 优化代码编译流程,生成更高效的程序
- 内部压测验证
- 推广业务服务落地验证
三、课后个人总结
在课后需要了解一下其他语言的编码规范,看是否和 Go 语言编码规范有相通之处。编码规范或者性能优化建议大部分是通用的。从链接中选择感兴趣的包,阅读官方代码。在使用Go进行并发编程时一定要规范编码,避免引发性能陷阱。