性能优化的一般思路
这篇文章是上一篇文章:软件性能问题排查方法与思路的姊妹篇,主要来讲一下在非故障场景下,如何做性能优化
性能优化是一个需要以目标为支撑,然后发现问题,解决问题的一个过程。
确定目标
做性能优化的第一步,就是先分析到底要不要做性能优化,在我的职业生涯中,对于非故障场景的性能优化,一般是出于下面几个原因:
- 通过测试确认软件的最大容量后,如最大可以承受多少用户,能处理多少数据,分析一下哪里有瓶颈,试试能不能做的更好。更偏向于是一种探索性的尝试,能做的更好当然是最好的。做不到也没关系,因为这并不影响软件的正常使用。
- 与竞品竞争,在相同的场景下,性能指标要超过竞争对手。在这种情况下,目标就是超越竞争对手的性能,即使是微小的提升,也可能带来竞争优势。
- 个人或团队自己提出的 OKR 目标。
识别瓶颈
性能优化本身是个比较宽泛的概念,只有找到可以具体优化的资源,性能优化才有可以着手的点。第一步就是确认需要针对什么资源做性能优化,在上一篇文章中我把性能底层资源分为 CPU、内存、磁盘、网络四大类,所以开始时就可以以这些大方向的性能指标作为切入点。
但首先我们需要理解软件的整体架构,即软件是由哪些部分构成,并了解主链路流程,这样我们才能梳理出软件会与哪些资源有交互,通过梳理出的的流程与架构,我们能初步确定瓶颈最有可能发生的位置。
像一个典型的 Web 应用架构,请求穿过网关来到后端服务器,后端服务器接着缓存跟数据库,一般来说有了缓存的加持,数据库是可能的瓶颈所在,因为数据库会与磁盘交互,而磁盘几乎是除网络通信最慢的部分。
识别瓶颈最重要的就是抓住主要矛盾。所谓瓶颈,一定是解决掉之后,能给系统带来大幅度提升的东西。这也是做性能优化的一个主要原则,二八原则或者加速大概率事件讲的都是这件事。千万不要对一个只能带来百分之一性能提升的东西投入百分之八十的精力。
同时,识别瓶颈是一个复杂的操作,这需要你对业务系统本身、操作系统、计算机体系结构、数据库等都要有比较深的理解,这只能靠自身不断地积累、实践,才能做的比较熟练。
但光靠这些基础原理与业务系统的理解也不足以识别出所有瓶颈,有时候还需要一些性能辅助工具的加持,比如 VisualVM 就能够采样 Java 程序的方法 cpu 时间分布,而像 Linux 下的 strace,也能够查看系统调用耗时。可以对涉及 CPU、内存、磁盘、网络相关资源的排查工具做些初步了解,等真正需要时,再去学具体的用法。
利用这些性能工具跟基础原理,就能找到最耗费的底层资源是什么,耗费这些底层资源的主体又是哪些。这样,我们就能准备解决瓶颈了。
解决瓶颈
这一步将根据上一步骤确定的瓶颈资源与资源使用主体,针对性地寻找解决方案。解决方案本身并没有什么通用的方式可以给出,因为解决瓶颈的方式大部分情况下都需要根据具体业务需求定制,其他场景的解决方案未必适用。
这一步还是需要我们结合自己的业务、以及基础原理等知识,看针对瓶颈有没有更好的实现方式,或是 workaround 能降低瓶颈资源的消耗。
但解决瓶颈这一步,我有一个属于自己的个人经验,那就是一定要直击问题的本质,构建一条完整的逻辑链,从原理上能解释你的优化手段对结果产生有效的影响。除此之外,最好也能通过性能工具监控一些指标,根据这些指标直接或间接地证明你的优化有效。
我曾经见过一个 Java 应用 FGC 频繁的问题,开发人员经过排查过后,只是简单地把堆内存给加大了,加大了之后,FGC 的频率的确有所下降,但 FGC 的根因并没有解决,最后根据排查根因是由内存泄露问题引起的。调大堆内存只是延缓了 FGC 频率,不仅不能解决,甚至还会引入其他问题,如 STW 变长了。
后面进一步排查这个问题,找到了具体造成内存泄露的对象,把相关问题解决之后,再通过导出堆快照的方式,发现造成内存泄露的对象数量显著减少,内存也比较平稳,FGC 频率也大大下降,问题解决。
止步
在识别瓶颈,解决瓶颈之后,可能会发现新的瓶颈,此时就会陷入一个发现问题和解决问题的循环中,这个循环是令人上瘾的,但当你最开始的目标达成之后,一定要克制住继续优化的冲动,不要因为走的太远,而忘了当时为什么而出发。因为越往下,解决性能问题带来的收益越少,而付出的成本可能会越来越多。