性能分析工具pprof原理 | 青训营笔记

117 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天。

性能分析工具pprof-采样过程和原理

上一节笔记展示了pprof的排查实战流程,接下来可以看看他们的内部实现。

CPU

首先,CPU采样会记录所有调用栈(函数调用)和他们的占用时间;采样率为固定100次/秒(每秒暂停100次),每次记录当前调用栈信息;汇总之后根据调用栈在采样中出现的次数判断函数的运行时间。需要手动启动和停止采样,定时暂停机制在unix或类unix是依赖信号机制实现的。具体流程如下图:

image.png

在CPU采样过程中三个相关角色分别是:

  • 操作系统OS:启动采样时,进程向OS注册一个定时器,OS每隔10ms向进程发送一个SIGPROF信号。
  • 进程本身:进程每次收到SIGPROF会记录调用堆栈。
  • 写缓冲:进程会启动一个写缓冲的goroutine,每隔100ms从进程中读取已经记录的堆栈信息,并写入输出流。 采样停止时,进程向OS取消定时器,不再接受信号,写缓冲读取不到新的堆栈时,结束输出。

image.png

Heap-堆内存

接下来,堆内存采样提到的内存指标是「堆内存」而不是「内存」,这是因为pprof内存采样是有局限性的。内存采样在实现上依赖了内存分配器的记录,所以只能记录在堆上分配,且会参与GC(Garbage Collection)的内存。

  • 采样率:每分配512KB内存会采样一次,可在运行开头修改,设为1则为每次分配都会记录。
  • 采样时间:不同于CPU和goroutine,内存采样是持续的,记录从程序运行开始的所有分配或释放的内存大小和对象数量,并在采样时遍历这些结果进行汇总。
  • 采样指标:alloc_space,alloc_objects是从程序运行开始的累计指标,inuse_space,inuse_objects是通过累计分配减去累计释放得到的程序当前持有的指标,也可以通过两次alloc差值得到某一时间程序分配的内存。
  • 计算方式:inuse=alloc-free

Goroutine-协程 & ThreadCreate-线程创建

Goroutine和系统线程采样指标在概念和实现上较相似,所以进行对比:

  • Goroutine采样会记录所有用户发起且在运行中的goroutine(即入口非runtime开头的),以及main函数所在goroutine的信息和创建这些goroutine的调用栈。
  • ThreadCreate采样会记录程序创建的所有系统线程的信息。

image.png

Block-阻塞 & Mutex-锁

Block和锁竞争采样在流程和原理上也十分相似,两个采样记录都是对应操作发生的调用栈、次数和耗时,不过他们的采样率含义不相同。

image.png

性能分析工具pprof小结

至此,记录了pprof的实战流程、常用指标和他们的实现原理(查看源码需参考阅读资料),在实战中,使用pprof排查思路和想法是通用的,接下来会介绍实际业务调优的案例。

性能调优案例

介绍

在实际工作中,当服务规模较小时,性能优化并不会带来明显效果;但当业务量增大,例如一个服务使用几千台机器时,性能优化一个百分点就能节省百台机器,效果十分显著。程序从不同应用层次来看,可以分为:

  • 业务服务:直接提供功能的程序,比如处理用户评论操作的程序。
  • 基础库:指主要针对业务服务提供通用功能的程序,比如监控组件、负责收集业务服务的运行指标等。
  • GO语言本身:程序本身优化项。

这三层对应的优化适用范围也越来越广。

性能调优案例-业务服务优化

概念

在介绍应对逻辑复杂的业务服务流程之前,先介绍系统部署的简单示意图及一些专业名词:

  • 服务:能单独部署,承载一定功能的程序
  • 依赖:Service A的功能实现依赖Service B的响应结果,称为Service A依赖Service B。
  • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系。
  • 基础库:公共的工具包、中间件。

image.png

客户端(电脑,手机等)请求经过网关转发,由不同的业务服务处理,业务服务可能依赖其他的服务,也可能会依赖存储、消息队列等组件,例如Service B被Service A依赖,同时也依赖了存储和Service D。

流程

接下来以业务服务优化为例,介绍性能调优的流程,主要分4步同时也适用于其他场景: 1,和benchmark类似,服务性能也需要建立评估手段和标准。 2,使用pprof采样性能数据,分析服务表现,定位服务性能瓶颈。 3,进行服务改造,重构代码,使用更高效的组件。 4,优化效果验证,通过压测对比和正确性验证之后,服务可以上线进行实际收益估计。

整体流程可以循环并行执行,每个优化点可能不同,可以分别评估验证。

建立服务性能评估手段

服务性能评估方式:

  • 单独benchmark无法满足复杂逻辑分析
  • 不同负载情况下性能表现差异

请求流量构造:

  • 不同请求参数覆盖逻辑不同
  • 线上真实流量情况

压测范围:

  • 单机器压测
  • 集群压测

性能数据采集:

  • 单机性能数据
  • 集群性能数据

评估手段建立后,产出是一个服务性能指标分析报告。实际的压测报告截图会统计压测期间服务的各项监控指标,包括qps,延迟等。也能在压测过程中采集服务的pprof数据,使用之前的方式分析性能问题。

常见的性能问题是:

  • 使用基础组件不规范,比如这里的火焰图可以看出json解析部分占用了较多CPU资源:

image.png

  • 日志使用不规范:

image.png

  • 高并发场景优化不足:

image.png

在定位完性能瓶颈并修复后,在变动较大的性能优化上线前,需要进行正确性验证。响应数据diff:线上请求数据录制回放,新旧逻辑接口数据diff。

优化效果验证

验证分两部分,首先用同样的数据对优化后的服务进行压测;再是上线评估优化效果:关注服务监控,逐步放量,收集性能数据。

进一步优化,服务整体链路分析

规范上游服务调用接口,明确场景需求;分析链路,通过业务流程优化提升服务性能。比如Service A调用Service B是否存在重复调用,调用Service B时,是否更小的结果数据集就能满足需求,接口是否一定要实时数据,能否在Serivce A层进行缓存减少压力。

性能调优案例-基础库

基础库的优化适用范围更广,例如AB实验SDK的优化:

  • 分析基础库核心逻辑和性能瓶颈,设计完善改造方案,数据按需获取,数据序列化协议优化。
  • 内部压测验证。
  • 推广业务服务落地验证。

性能调优案例-GO语言优化

GO语言优化是适用范围最广的优化,优化编辑器和运行时的内存分配策略,构建更高效的go发行版本,优化代码编译流程,生成更高效的程序。这样优化业务服务接入非常简单,只要调整编译配置即可,通用性很强,几乎对所有go程序都会生效。

例如图中服务只换用新的发行版本进行编译,但CPU占用减低8%。 image.png

总结

性能调优原则是要依靠真实数据不能盲目猜测,pprof工具可以通过分析实际程序熟悉相关功能,理解基本原理,才能更好的解决性能问题。在调优过程中,最重要的是保证正确性,再定位主要瓶颈。