性能优化的分析思路
系统性能的定义
-
Throughput:吞吐量
-
Latency:请求耗时
-
Error rate:错误率
以上三点条件是衡量一个系统性能好坏的常用指标。三者也是缺一不可的。比如如果吞吐量能达到很高,但是延迟也很高,还都是错误,那就没有任何意义。一般在满足错误率为定值时,latency越低,throughput越高,因为响应速度越快,那么单位时间能处理的请求就更多。同时如果throughput越高,那么latency也会变高,系统变得繁忙,自然响应速度就会降低,甚至错误率也会提升。测试一个系统的性能,实际上就是收集这几个参数,找到满足业务要求时的最优值,比如满足error rate为0和latency在一定数值时(比如小于50ms)的throughput。
系统性能测试
定义好可以接受的latency的值
这个可以根据实际业务来定,或者在微服务中,根据系统的总请求耗时拆分各个微服务的最大请求耗时,把这个值作为服务可以接受的最大latency。
进行性能测试
然后就可以使用一些性能测试工具,通过制造尽可能高的throughput,找到满足指定latency条件下的最大值,就是这个系统的最大负载了。压测的过程可能比较长,以保证能拿到准确而丰富的数据。
压测时有一些点是需要注意的:
-
当throughput比较小时,latency往往比较稳定,当逐步加大throughput时,latency往往会出现剧烈的抖动,需要测量latency的分布,比如99分位的latency是多少时是可以接受的。很多压测工具一般都可以提供这个参数,比如我经常使用的Locust和K6,或者老牌的压测工具Jmeter。
-
通过不断提升throughput值,找到满足条件的最大throughput以得到系统的最大负载,然后可以取该值的2/3作为一个软警戒线,80%作为一个硬警戒线,是我们能承受的最大值。
-
最好长时间比如连续7天不间断的压测,看看系统是不是稳定的。
-
使用burst test。比如运行5分钟的最大throughput,然后运行一分钟的极限throughput,循环执行一段时间,看看系统是否仍然稳定。
-
还要测试一下throughput较小时系统的性能,因为在QPS较小时,可能会因为TCP的粘包等的问题,导致latency反而提高了,这种情况下往往是系统TCP连接的相关参数没有设置对。
定位性能瓶颈
这个十分重要,因为如果无法找到系统的性能瓶颈,那性能优化就无从下手。而往往一个系统很复杂的情况下,性能瓶颈也不是那么好找。可以从以下几个方面入手
查看操作系统的负载
首先要看的是CPU利用率、内存利用率、磁盘IO吞吐量、网络IO吞吐量这些参数。
如果CPU利用率不高,但是throughput却上不去,那就说明我们的应用程序没有忙于计算,可能是忙于IO,这时就要看看是不是因为IO导致的性能上不去。Linux下有很多工具可以查看CPU使用率。
Tips:
对于多核CPU来说,还需要关注CPU 0,要保证它的负载不能过高,因为各个核是通过CPU 0进行调度的,CPU 0的负载过高会影响整体的性能。
如果CPU不大,那么就看看IO,主要看三个方面
-
磁盘文件IO:
linux有很多工具可以查看当前的磁盘IO,比如
sar
、iostat
、vmstat
等。比如:
$ sar -d -p 1 2
Linux 4.19.0-13-amd64 2022年03月02日 _x86_64_ (48 CPU)
11时24分43秒 DEV tps rkB/s wkB/s areq-sz aqu-sz await svctm %util
11时24分44秒 sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
11时24分45秒 sda 21.00 0.00 260.00 12.38 0.14 7.00 0.38 0.80
Average: sda 10.50 0.00 130.00 12.38 0.07 7.00 0.38 0.40
-
驱动程序IO:
比如网卡,也可以通过
iostat
测量 -
内存换页率
linux内核会把某些进程暂时不用的内存页保存到磁盘中,当进程需要用到某块当前内存中没有而存在磁盘的数据时,就需要从磁盘中再读回内存,内存淘汰用的是LRU算法,以实现系统可用的内存远大于实际真实的物理内存。如果内存换页太频繁,肯定会影响程序性能
如果CPU不高,IO也不高,性能还是差,那就可以看看网络带宽。可能是带宽过小了,导致网络传输效率低,这个可以有很多命令可以用,iftop
、iptraf
、tcpdump
或者也可以使用wireshark。
如果网络带宽也没问题,那就大概率是应用程序的性能问题是当前系统的瓶颈,接下来我们讨论一下。
应用程序性能瓶颈和优化
通过一些profile工具对应用程序进行benchmark,比如golang官方的pprof,找到各个函数或方法的运行时间以及调用的次数,对于CPU的使用率等,如果关注CPU缓存行的调优,可以使用Vtune。
需要重点观察哪些调用次数较多的函数,因为对这些函数进行小小的提升,对整个应用程序的提升最大,比如一个函数被调用了1000次,那么优化它1ms,那就有整整1s的优化。
一个应用程序的性能分布往往也遵循二八定律。20%的代码消耗了整体80%的性能,那么找到这20%的代码,对它进行重点优化。
代码优化的常见方式
-
算法优化。比如:遍历能不能改成二分,能不能用空间换时间比如使用hash,能不能分治等等。降低算法的时间复杂度以提高代码执行效率。
-
代码逻辑的优化。比如:
-
字符串的优化,拼接方式、匹配子串、系列化和反序列化等
-
多用并发操作,这一点golang有天然的优势
-
内存优化,尽量减少内存碎片,比如golang的内存对齐,成员变量存储在一起更节省空间,也更容易利用到CPU缓存行,减少内存的随机访问
-
异步,代码逻辑可不可以用异步的方式执行
-
第三方包的使用,有些包可能会有效率问题,看能不能换成效率更好的,比如golang官方的json包
-
网络调优
TCP调优,尽量配置KeepAlive参数,及时关闭连接,节省资源的消耗,还有就是如果流量较大的话,增大缓冲区,不要让一个包分成多次传输。
UDP调优,注意MTU最大传输单元,一个包尽量装满了再发。
网卡,一些网卡的配置参数,比如txquenelen,receive buffer等。
系统调优
IO模型。各种IO模型的使用,尽量采用多路复用。
多核CPU的使用,尽量利用到多核CPU,同时注意CPU 0。
文件系统调优,linux的文件系统也是有cache的,os buffer,尽量利用到系统的优化,这一点MySQL有很多值得参考的地方。
数据库调优
数据库调优有很多值得写的地方。大体上可以分成索引的合理使用、查询方式的优化、表结构的优化、引擎的合理配置等。
其他方面的一些优化策略
- 空间换时间
比如各种cache的使用,减少IO,或者把IO放到异步,通过缓存提升性能。
使用CDN,静态资源加速。
- 时间换空间
如果瓶颈在网络传输,或者使用了压缩算法,那么空间对效率的影响可能要高于时间,那就可以通过时间换空间的思路,整体上进行优化。
- 用好并行
充分利用CPU的多核特性,单个CPU也别让他太闲。
- 分布式
实在不行可以搞分布式,通过负载均衡提高系统性能,也就是加机器。