题记
这是我参与「第五届青训营 」伴学笔记创作活动的第 7天,本文用于记录在青训营的学习笔记和一些心得。
day7 1月21日
性能调优案例
在实际工作中,当服务规模比较小的时候,可能不会触发很多性能问题,同时性能优化带来的效果也不明显,很难体会到性能调优带来的收益而当业务量逐渐增大,比如一个服务使用了几千台机器的时候,性能优化一个百分点,就能节省数百台机器,成本降低是非常可观的。
接下来我们来了解下工程中进行性能调优的实际案例
程序从不同的应用层次上看,可以分为业务服务、基础库和Go语言本身三类,对应优化的适用范围也是越来越广,业务服务一般指直接提供功能的程序,比如专门处理用户评论操作的程序。基础库一般指提供通用功能的程序,主要是针对业务服务提供功能,比如监控组件,负责收集业务服务的运行指标。另外还有对Go语言本身进行的优化项。
基本概念
- 服务:能单独部署,承载一定功能的程序
- 依赖:Service A 的功能实现依赖 Service B 的响应结果,称为 Service A 依赖 Service B
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包、中间件
业务优化
-
流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
那么接下来就来看一下业务服务优化的主要流程,主要分四步,这些流程也是性能调优相对通用的流程,可以适用其他场景
和上面评估代码优化效果的benchmark工具类似,对于服务的性能也需要一个评估手段和标准
优化的核心是发现服务性能的瓶颈,这里主要也是用pprof采样性能数据,分析服务的表现
发现瓶颈后需要进行服务改造,重构代码,使用更高效的组件等
最后一步是优化效果验证,通过压测对比和正确性验证之后,服务可以上线进行实际收益评估
整体的流程可以循环并行执行,每个优化点可能不同,可以分别评估验证
之所以不用benchmark是因为实际服务逻辑比较复杂,希望从更高的层面分析服务的性能问题,同时机器在不同负载下的性能表现也会不同。右图是负载和单核qps的对应数据另外因为逻辑复杂,不同的请求参数会走不同的处理逻辑,对应的性能表现也不相同,需要尽量模拟线上真实情况,分析真正的性能瓶颈 压测会录制线上的请求流量,通过控制回放速度来对服务进行测试,测试范围可以是单个实例,也可以是整个集群,同样性能采集也会区分单机和集群
评估手段建立后,它的产出是什么呢?实际是一个服务的性能指标分析报告 实际的压测报告截图,会统计压测期间服务的各项监控指标,包括qps,延迟等内容,同时在压测过程中,也可以采集服务的ppro数据,使用之前的方式分析性能问题
有了服务优化前的性能报告和一些性能采样数据,我们可以进行性能瓶颈分析了
业务服务常见的性能问题可能是使用基础组件不规范
比如这里通过火焰图看出 json 的解析部分占用了较多的CPU资源,那么我们就能定位到具体的逻辑代码,是在每次使用配置时都会进行 json 解析,拿到配置项,实际组件内部提供了缓存机制,只有数据变更的时候才需要重新解析json。
还有是类似日志使用不规范,一部分是调试日志发布到线上,一部分是线上服务在不同的调用链路上数据有差别,测试场景日志量还好,但是到了真实线上全量场景,会导致日志量增加,影响性能。
另外常见的性能问题就是高并发场景的优化不足,左边是服务高峰期的火焰图,右边是低峰期的火焰图,可以发现metrics,即监控组件的CPU资源占用变化较大,主要原因是监控数据上报是同步请求,在请求量上涨,监控打点数量增加时,达到性能瓶颈,造成阻塞,影响业务逻辑的处理,后续是改成异步上报的机制提升了性能。
定位到性能瓶颈后,我们也有了对应的修复手段,但是修改完后能直接发布上线吗? 性能优化的前提是保证正确性,所以在变动较大的性能优化上线之前,还需要进行正确性验证,因为线上的场景和流程太多,所以要借助自动化手段来保证优化后程序的正确性同样是线上请求的录制,不过这里不仅包含请求参数录制,还会录制线上的返回内容,重放时对比线上的返回内容和优化后服务的返回内容进行正确性验证 比如图中作者信息相关的字段值在优化有有变化,需要进一步排查原因
改造完成后,可以进行优化效果验证了 验证分两部分,首先依然是用同样的数据对忧化后的服务进行压测,可以看到现在的数据比优化前好很多,能够支持更多的qps正式上线的时候会逐步放量,记录真正的优化效果 同时压测并不能保证和线上表现完全一致,有时还要通过线上的表现再进行分析改进,是个长期的过程
以上的内容是针对单个服务的优化过程,从更高的视角看,性能是不是还有优化空间?在熟悉服务的整体部署情况后,可以针对具体的接链路进行分析调优 比如Service A调用Service B是否存在重复调用的情况,调用Service B服务时,是否更小的结果数据集就能满足需求,接口是否一定要实时数据,能否在Service A层进行缓存,减轻调用压力这种优化只使用与特定业务场景,适用范围窄,不过能更合理的利用资源。
适用范围更广的就是基础库的优化
比如在实际的业务服务中,为了评某些功能上线后的效果,经常需要进行AB实验,看看不同策略对核心指标的影响,所以公司内部多数服务都会使得AB实验的SDK,如果能优化AB组件库的性能,所有用到的服务都会有性能提
升类似业务服务的优化流程,也会先统计下各个服务中AB组件的资源占用情况,看看AB组件的哪些逻辑更耗费资源,提取公共问题进行重点优化
图中看到有部分性能耗费在序列化上,因为AB相关的数据量较大,所以在制定优化方案时会考虑优化数据序列化协议,同时进行按需加载,只处理服务需要的数据
完成改造和内部压测验证后,会逐步选择线上服务进行试点放量,发现潜在的正确性和使用上的问题,不断迭代后推广到更多服务
接下来是适用范围最广的优化,就是针对Go本身进行的优化,会优化编译器和运行时的内存分配策略,构建更高效的go发行版本
这样的优化务服务接入非常简单,只要调整编译配置即可,通用性很强,几乎对所有go的程序都会生效,比如右图中服务只是换用新的发行版本进行编译,CPU占用降低8%