01 性能优化流程:刚开始做性能优化从何入手?
你好,我是徐逸。
作为研发,在我们的工作中,除了做产品功能需求,有很多时间需要主动做技术需求,而其中比较重要的一项,就是服务性能优化。它关乎着用户体验以及我们可以支撑的业务体量。
然而,很多研发刚开始接触这方面工作时常常无从下手,最常见的痛点有这样几个。
- 对性能优化的流程不是很清晰。在着手进行优化工作时,往往不知从何处开始,也不清楚该如何定位到性能瓶颈点。
- 对性能优化的工具不了解。针对瓶颈点,不清楚该用哪个工具,也不知道该如何用工具分析引起性能问题的原因。
- 对常见的服务优化手段缺乏足够的了解。即便知道了引起性能问题的原因,也不知道可以运用哪些手段才能有效解决,对如何制定优化方案一筹莫展。
所以,这一章我就带你来解决上述的3个痛点。而今天这节课,我们先来梳理一下Go服务性能优化流程,迈出我们定位和解决性能问题的第一步。
我根据自己实践的经验,把服务性能优化流程分成5步。它们分别是定目标、找瓶颈点、分析瓶颈原因、性能优化和验证目标是否达成。
你可能对每一步的含义不是很了解,没关系,我们后面会逐一解释。首先,在我们陷入性能优化的细节之前,先想一想,你的性能目标是什么?
定目标:你的性能目标是什么?
所谓性能目标,是我们预先设定的一系列期望达成的量化指标。常见的量化指标包括QPS、延时、数据量等。比如说我们的接口要支持到100wQPS,平均延时要到10ms以内,这就是我们的量化指标。
之所以要设定性能目标,一方面是为了避免过早过度优化。过早过度优化,会让应用的复杂程度大大提高,应用的可维护性也跟着下降。比如有些人为了能让性能达到极致,明明一个简单的数据库一张表就能够轻松搞定的事,非要大费周章地上分布式缓存、本地缓存。结果设计出来的架构和代码变得非常复杂,后续维护也很困难。另一方面,是为了避免优化不足,导致我们的系统无法支撑业务的后续发展。
弄清楚性能目标的含义和设定性能目标的原因,我们该如何设定性能目标呢?
实际上,我们的性能目标来自于接口的使用场景、会影响到接口性能的数据以及降本的业务诉求。这样说可能还是有点抽象,我再结合一些常见场景列举几个具体目标,帮助你理解。
- 当我们的接口在延迟敏感链路,响应时间不及预期时。比如各大App首页里的搜索场景,搜索链路就像下面图中的一样,由综合搜索服务去调电商、图文等各个业务的搜索服务,为了整体的搜索体验,一旦某个业务的搜索服务接口延时比较高,综合搜索服务会直接放弃等待这个业务的返回。为了避免我们的服务延时高导致业务内容无法在App中展示,我们的目标可以设定为在满足现有QPS要求的情况下,接口延时p99从20ms下降为10ms。
- 当我们的接口在大促、秒杀等运营活动链路,大促活动会导致接口QPS大幅度增加时。我们的性能目标可以设定为在保证延时不上涨的前提下,支持QPS从10w提升到100w。
- 当有大批商家入驻、用户数突然增长,我们的表数据量也随之增长,导致我们接口延时上涨、甚至OOM时(比如为了性能考虑,我们有一些本地内存缓存全量数据的场景,突然出现大量数据,内存不够就会OOM)。这时我们的性能目标可以设定为,在保证QPS和延时不上涨的前提下,支持的入驻商家数从10w提升到100w。
- 对于一些成本压力大的部门,会有一些降本的诉求。而对于研发来说,就需要提升资源利用率,降低资源成本。在这种情况下,我们的性能目标可以设定为在保证满足整体QPS诉求和延时不上涨的前提下,月机器成本从100w降到10w,单机吞吐从1000QPS升到1wQPS。
制定了性能目标,我们就有了努力方向。接下来需要解决的问题是,找到我们的服务本身和下游中哪个组件可能会导致性能达不到目标。
找瓶颈点:我们的服务哪里有瓶颈?
为了找到可能会导致性能达不到目标的组件,我们可以做性能测试。性能测试包括两种,单个请求测试和集群整体压测。
单个请求测试的目的,是看我们的接口延时是否符合预期。如果达不到预期,就需要根据链路性能,定位到是本服务还是下游的哪个组件响应时间比较长。
在单个请求达成延时目标的情况下,我们还需要做服务集群整体压测,看看服务整体的QPS是否能达到要求。在逐步加压的过程中,需要监控延时变化。如果目标QPS没达成,但延时已经上涨超过上限,可以根据监控定位到是哪个组件没达到目标QPS,但是已经超时。
通过单个请求测试和服务集群整体压测,我们可以找到限制延时和吞吐达到性能目标的组件,也就是瓶颈点。
不过当服务和下游组件延时都比较长或者吞吐都不达标,链路存在多个瓶颈点时,又该怎么办呢?
为了控制改造成本,同时方便评估优化后的线上效果,建议一次只选一个相对好解决的瓶颈处理,后续达不到性能目标再优化其它瓶颈点。一般来说,服务自身的瓶颈就好处理一些,而涉及下游组件架构优化的话,改造成本要高很多。
瓶颈分析:造成瓶颈的原因是什么?
在找到了待优化的瓶颈点之后,接下来我们就得想一想,是什么导致了组件的性能比较差?这就来到了我们的原因分析环节。
瓶颈组件不同,分析思路也不同。对于服务自身的瓶颈,我们可以采用下面这2个分析思路。
- 如果是接口内部计算逻辑导致延时过长或单机CPU资源消耗过高,吞吐上不去,我们可以用pprof抓取CPU火焰图,看看哪段逻辑消耗CPU比较多。
- 如果压测发现,是因为内存消耗过高导致单机吞吐上不去,我们可以用pprof抓取内存火焰图,看哪段逻辑在频繁地分配内存。
对于下游组件瓶颈,比如说常用的MySQL,下面这3个分析思路比较常见。
- 是否是服务调用MySQL的IO时间过长,这种情况可以看下是不是返回的数据量过大。
- 查看我们的MySQL请求是否存在慢查询,判断SQL、索引使用合不合理。
- 如果排除了上述两个因素,需要分析下是否是因为我们的表太大、从库不够,还是说MySQL本身支持不了这么高的吞吐和这么严的延时要求。
当我们找到造成瓶颈的原因后,针对瓶颈原因,我们该怎么优化呢?
性能调优:我该如何针对瓶颈进行优化?
瓶颈原因不一样,优化方案也不同。针对不同瓶颈原因的优化技巧在后面课程里还会展开,这里我们先掌握3个优化方向。
- 服务自身瓶颈。我们需要针对瓶颈代码,做代码优化。
- 调用下游IO瓶颈。除了服务代码上的优化,比如说分页请求、压缩等,我们还可以从框架层想办法。
- 下游组件瓶颈。比如常用的MySQL,在基本的使用问题排除后,我们需要做架构上的优化。
做完改造后,我们怎么确认性能目标达成了呢?
验证目标达成:如何确认性能目标是否达成?
为了验证性能目标是否达成,我们可以对调优过的应用进行性能测试,与优化前的各项指标进行对比,观测其是否符合预期。如果瓶颈点没有消除或者性能指标不符合预期,则需要重复找瓶颈点、瓶颈分析、性能调优和验证目标是否达成的步骤,直到达成性能目标。
小结
今天这节课的内容就到这里了,在这节课里,我构建了一个服务性能优化流程,并为你梳理了每一步的含义和要点。现在我们回顾一下性能优化的流程。
第一步,我们需要确定性能目标,既要避免过早过度优化,也要避免优化不足,阻碍业务的发展。
第二步,我们需要做接口单请求测试和集群整体压测,找到造成性能不达标的瓶颈节点。
第三步,在我们找到瓶颈点之后,需要用工具分析造成瓶颈的原因 。
第四步,根据找到的瓶颈原因,从代码层、框架层和架构层针对性做优化。
第五步,为了验证性能优化后,性能目标是否达成,需要做性能测试,并和优化之前做对比,如果性能目标没有达成,需要重复2-5步,直到目标达成。
现在你已经掌握了性能优化的通用流程,下次当你要做性能优化时不妨实际试一试,相信能够帮你更快地找到瓶颈点并达成优化目标。
课后练习
借鉴这节课的性能优化流程,结合你的性能优化实践,分析每一步你是如何做的,用来指导你后续的性能优化实践。
欢迎你把你的答案分享在评论区,也欢迎你把这节课的内容分享给需要的朋友,我们下节课再见!
- CodeFish-Xiao 👍(4) 💬(1)
其实更加好奇的是:指标该定制多大,一个正常的指标区间该是什么样的? 例如接口时延 50ms算正常还是1s算正常,这些指标该如何去制定,按照什么标准制定
2024-12-09 - kkgo 👍(2) 💬(1)
目前主要是通过skywalking来分析服务 存在服务,数据库等问题 再单个优化
2024-12-09 - 请务必优秀 👍(0) 💬(1)
突然发现如果是做内部平台(比如运维平台啥的),基本没人关注延时,哈哈哈,都是能用就行,这种情况下,应该先紧着功能增加迭代的速度,实现目标为优,无须花精力优化,这就叫不过度设计吧 哈哈哈
2024-12-12 - Fukans 👍(0) 💬(1)
“调用下游 IO 瓶颈。除了服务代码上的优化,比如说分页请求、压缩等,我们还可以从框架层想办法。” 这句指的是优化ORM框架之类的吗?
2024-12-11