性能优化的分析思路

Sun, Feb 6, 2022 阅读时间 1 分钟

系统性能的定义

  • Throughput:吞吐量

  • Latency:请求耗时

  • Error rate:错误率

以上三点条件是衡量一个系统性能好坏的常用指标。三者也是缺一不可的。比如如果吞吐量能达到很高,但是延迟也很高,还都是错误,那就没有任何意义。一般在满足错误率为定值时,latency越低,throughput越高,因为响应速度越快,那么单位时间能处理的请求就更多。同时如果throughput越高,那么latency也会变高,系统变得繁忙,自然响应速度就会降低,甚至错误率也会提升。测试一个系统的性能,实际上就是收集这几个参数,找到满足业务要求时的最优值,比如满足error rate为0和latency在一定数值时(比如小于50ms)的throughput。

系统性能测试

定义好可以接受的latency的值

这个可以根据实际业务来定,或者在微服务中,根据系统的总请求耗时拆分各个微服务的最大请求耗时,把这个值作为服务可以接受的最大latency。

进行性能测试

然后就可以使用一些性能测试工具,通过制造尽可能高的throughput,找到满足指定latency条件下的最大值,就是这个系统的最大负载了。压测的过程可能比较长,以保证能拿到准确而丰富的数据。

压测时有一些点是需要注意的:

  1. 当throughput比较小时,latency往往比较稳定,当逐步加大throughput时,latency往往会出现剧烈的抖动,需要测量latency的分布,比如99分位的latency是多少时是可以接受的。很多压测工具一般都可以提供这个参数,比如我经常使用的LocustK6,或者老牌的压测工具Jmeter

  2. 通过不断提升throughput值,找到满足条件的最大throughput以得到系统的最大负载,然后可以取该值的2/3作为一个软警戒线,80%作为一个硬警戒线,是我们能承受的最大值。

  3. 最好长时间比如连续7天不间断的压测,看看系统是不是稳定的。

  4. 使用burst test。比如运行5分钟的最大throughput,然后运行一分钟的极限throughput,循环执行一段时间,看看系统是否仍然稳定。

  5. 还要测试一下throughput较小时系统的性能,因为在QPS较小时,可能会因为TCP的粘包等的问题,导致latency反而提高了,这种情况下往往是系统TCP连接的相关参数没有设置对。

定位性能瓶颈

这个十分重要,因为如果无法找到系统的性能瓶颈,那性能优化就无从下手。而往往一个系统很复杂的情况下,性能瓶颈也不是那么好找。可以从以下几个方面入手

查看操作系统的负载

首先要看的是CPU利用率内存利用率磁盘IO吞吐量网络IO吞吐量这些参数。

如果CPU利用率不高,但是throughput却上不去,那就说明我们的应用程序没有忙于计算,可能是忙于IO,这时就要看看是不是因为IO导致的性能上不去。Linux下有很多工具可以查看CPU使用率。

Tips:

对于多核CPU来说,还需要关注CPU 0,要保证它的负载不能过高,因为各个核是通过CPU 0进行调度的,CPU 0的负载过高会影响整体的性能。

如果CPU不大,那么就看看IO,主要看三个方面

  1. 磁盘文件IO:

    linux有很多工具可以查看当前的磁盘IO,比如sariostatvmstat等。比如:

$ 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
  1. 驱动程序IO:

    比如网卡,也可以通过iostat测量

  2. 内存换页率

    linux内核会把某些进程暂时不用的内存页保存到磁盘中,当进程需要用到某块当前内存中没有而存在磁盘的数据时,就需要从磁盘中再读回内存,内存淘汰用的是LRU算法,以实现系统可用的内存远大于实际真实的物理内存。如果内存换页太频繁,肯定会影响程序性能

如果CPU不高,IO也不高,性能还是差,那就可以看看网络带宽。可能是带宽过小了,导致网络传输效率低,这个可以有很多命令可以用,iftopiptraftcpdump或者也可以使用wireshark。

如果网络带宽也没问题,那就大概率是应用程序的性能问题是当前系统的瓶颈,接下来我们讨论一下。

应用程序性能瓶颈和优化

通过一些profile工具对应用程序进行benchmark,比如golang官方的pprof,找到各个函数或方法的运行时间以及调用的次数,对于CPU的使用率等,如果关注CPU缓存行的调优,可以使用Vtune

需要重点观察哪些调用次数较多的函数,因为对这些函数进行小小的提升,对整个应用程序的提升最大,比如一个函数被调用了1000次,那么优化它1ms,那就有整整1s的优化。

一个应用程序的性能分布往往也遵循二八定律。20%的代码消耗了整体80%的性能,那么找到这20%的代码,对它进行重点优化。

代码优化的常见方式

  • 算法优化。比如:遍历能不能改成二分,能不能用空间换时间比如使用hash,能不能分治等等。降低算法的时间复杂度以提高代码执行效率。

  • 代码逻辑的优化。比如:

    1. 字符串的优化,拼接方式、匹配子串、系列化和反序列化等

    2. 多用并发操作,这一点golang有天然的优势

    3. 内存优化,尽量减少内存碎片,比如golang的内存对齐,成员变量存储在一起更节省空间,也更容易利用到CPU缓存行,减少内存的随机访问

    4. 异步,代码逻辑可不可以用异步的方式执行

    5. 第三方包的使用,有些包可能会有效率问题,看能不能换成效率更好的,比如golang官方的json包

网络调优

TCP调优,尽量配置KeepAlive参数,及时关闭连接,节省资源的消耗,还有就是如果流量较大的话,增大缓冲区,不要让一个包分成多次传输。

UDP调优,注意MTU最大传输单元,一个包尽量装满了再发。

网卡,一些网卡的配置参数,比如txquenelen,receive buffer等。

系统调优

IO模型。各种IO模型的使用,尽量采用多路复用。

多核CPU的使用,尽量利用到多核CPU,同时注意CPU 0。

文件系统调优,linux的文件系统也是有cache的,os buffer,尽量利用到系统的优化,这一点MySQL有很多值得参考的地方。

数据库调优

数据库调优有很多值得写的地方。大体上可以分成索引的合理使用查询方式的优化表结构的优化引擎的合理配置等。

其他方面的一些优化策略

  1. 空间换时间

比如各种cache的使用,减少IO,或者把IO放到异步,通过缓存提升性能。

使用CDN,静态资源加速。

  1. 时间换空间

如果瓶颈在网络传输,或者使用了压缩算法,那么空间对效率的影响可能要高于时间,那就可以通过时间换空间的思路,整体上进行优化。

  1. 用好并行

充分利用CPU的多核特性,单个CPU也别让他太闲。

  1. 分布式

实在不行可以搞分布式,通过负载均衡提高系统性能,也就是加机器。