性能优化:如何做好性能优化

用户头像
CHEN川
关注
发布于: 2020 年 07 月 22 日
性能优化:如何做好性能优化

性能优化算是老生常谈的话题了,不管项目大小,一旦上线,或多或少都会遇到性能问题。有些性能问题是随着时间的积累慢慢产生的,比如系统刚上线,数据量很小的时候,没啥问题,等到数据积累到一定程度,问题就暴露出来了;也有些问题是由于访问量的波动造成的,比如系统平时没问题,一到搞促销活动时就挂。因此,性能问题就如同一颗定时炸弹,只要数据量和访问量一上来,早晚会炸。

何时开始性能优化

既然性能问题早晚会炸,那我们什么时候开始呢?一定要等到系统上线出问题以后再来解决吗?还是说在编码时就尝试各种性能优化手段?



在项目开发的初期,如果过于在意性能优化,可能会让我们疲于应付实际并不会发生的问题,还会影响到开发进度,说不定还会带来新的问题。



注意这里说的是,不要过于在意性能优化,并不是说不要在意。我们还是有很多方法来保证代码质量以提高系统性能的,比如:

  • 利用设计模式来解决多变的业务问题

  • 使用合理的数据结构和算法,比如,同样是列表,LinkedList就比ArrayList的插入性能高很多

  • 多线程环境下合理选择锁的类型和使用场景

  • 编写高效SQL、合理使用索引和事务来提升数据库性能



你的脑袋里应该有一大堆这样的手段,在开发过程中,可以尽情发挥。但有一点需要着重强调:不要使用任何你不知道背后原理的优化技巧



就比如:“不使用的对象应手动赋值为NULL”有利于GC更早地回收内存,但在大多数场景下,不使用的局部变量是否设置为NULL,对GC没有任何影响,毕竟方法执行完毕,栈帧就从操作数栈中弹出,方法中的局部变量就没了,是否设置为NULL也就没有任何影响。



再比如,设计MySQL表的时候,有个整型字段,其取值范围为0~99。有的同学可能想都没想,就把字段设计成a INT(2) NOT NULL,想当然地认为括号中的数字表示整数的位数,但括号中的2实际指的是显示宽度,INT类型能存储的范围永远都是固定的,这里正确的做法是使用TINYINT



在系统开发完成以后,可以根据一些预期的指标 ( 比如,并发数 ) 和硬件资源来对系统进行测试,通过各种分析统计工具来判断各项指标是否在预期范围内。等到系统上线后,还要根据日志、监控系统来观测系统性能,一旦发现问题,就要及时分析并修复。



不管是新系统还是老系统,也不管是上线前还是上线后,做性能优化都要遵循两原则三步骤

  • 两原则:不去优化没有测试的软件、不去优化你不了解的软件

  • 三步骤:测试、分析、调优



后面会展开来说,但这儿,先从测试开始。

性能测试的主要指标

性能的测试的最终目标是发现系统现有的性能问题以及可能的瓶颈点,所以在开始性能测试之前,有必要对系统的主要性能指标以及哪些资源可能成为系统的性能瓶颈做一些简单的了解。



一般来说,衡量系统的性能,主要有以下几个指标:

响应时间

响应时间是衡量系统性能的重要指标之一,响应时间越短越好,一般接口的响应时间是在毫秒级,如果达到秒级,那就可以考虑系统存在性能问题了。在一个典型的系统中,响应时间大致可以分为如下几类:



  • 网络通讯时间:客户端到服务端网络传输耗时

  • 服务端响应时间:负载均衡端分发请求耗时以及服务端程序执行耗时

  • 数据库响应时间:各种DB (业务DB、缓存DB) 操作耗时,往往是整个链条中最耗时的

并发数

并发数是指系统能够同时处理请求的数量,这个数字也反映了系统的负载承受能力。当系统的并发数上升时,观察系统的响应时间的上升趋势是否平缓。这条曲线可以很直观地反馈系统所能承受的负载压力极限,其大致的示意图如下图所示。

当你对系统进行压测时,系统的响应时间会随着系统并发数的增加而增加,直到系统能够承受的最大负载,这时再继续增加并发数,系统会抛出大量错误,达到极限。

吞吐量

吞吐量是指单位时间内系统处理的请求数量,体现的是系统的处理能力。在Web系统中,常常用TPS ( 每秒事务处理量 ) 或者QPS ( 每秒查询量 ) 来衡量系统的吞吐量。在不考虑网卡等网络设备限制的情况下,可以使用下面的公式来大致估算系统的吞吐量:

吞吐量 = (1000/响应时间ms) x 并发数



TPS中的事务指的是客户端向服务端发送请求,然后服务端响应客户端的过程。客户端发出请求到收到响应的时间间隔即为一个事务的耗时。一般来说,对于TPS和QPS的测试只针对核心业务即可,对于非核心的业务,无需太过于关注其性能,也不用花太多精力来对其做性能优化。

如何严谨地做性能测试

在做性能测试时,需要注意几个问题。首先,所有指标不能取平均值,平均值对测试没有任何意义。比如测试10次请求,有9次1ms,1次1s,平均耗时100ms,很明显,这完全不能反映性能的真实情况。简单点,就统计中位数;复杂点,就按百分比进行分布统计,比如,50%的请求都小于某个值。



其次,不要单纯地看某个指标。就比如前面并发数的曲线图,不考虑响应时间的情况下,并发数可以达到250,但实际情况是,并发数超过100,响应时间就长到不能接受了。



最后,前面的3个指标或其他更多指标,都要与成功率挂钩。毕竟,如果测试的每次请求都不成功,那么测试也就没有任何意义。



那如何更严谨地做性能测试?COOLSHELL上面总结了以下几点,分享给你:

  1. 定义一个系统的响应时间latency,建议是TP99,以及成功率。比如路透的定义:99.9%的响应时间必须在1ms之内,平均响应时间在1ms以内,100%的请求成功。当然一般的Web系统不用定义的这么苛刻,99.9%的响应时间在100ms内即可。

  2. 在这个响应时间的限制下,来测试系统的吞吐量。测试用的数据,需要有大中小各种尺寸的数据,并可以混合。最好使用生产线上的测试数据。

  3. 在这个吞吐量做浸泡测试,比如:使用第二步测试得到的吞吐量连续7天的不间断的压测系统。然后收集CPU,内存,硬盘/网络IO,等指标,查看系统是否稳定,比如,CPU是平稳的,内存使用也是平稳的。那么,这个值就是系统的性能。

  4. 找到系统的极限值。比如:在成功率100%的情况下 (不考虑响应时间的长短),系统能保持10分钟的吞吐量。

  5. 做Burst Test。用第二步得到的吞吐量执行5分钟,然后在第四步得到的极限值执行1分钟,再回到第二步的吞吐量执行5分钟,再到第四步的权限值执行1分钟,如此往复个一段时间,比如2天。收集系统数据:CPU、内存、硬盘/网络IO等,观察他们的曲线,以及相应的响应时间,确保系统是稳定的。

  6. 低吞吐量和网络小包的测试。有时候,在低吞吐量的时候,可能会导致延迟上升,比如TCP_NODELAY的参数没有开启会导致延迟上升,而网络小包会导致带宽用不满也会导致性能上不去,所以,性能测试还需要根据实际情况有选择的测试一下这两个场景。



一般情况下,可能很少会把这几个步骤做完,但至少前四个步骤得认真做完,才能得到一个稍微严谨的测试结果。

影响系统性能的主要因素

通过性能测试找到了系统的性能瓶颈,紧接着就要开始对系统进行优化,但在着手优化之前,至少得知道影响系统性能的主要因素有哪些?

硬件资源

硬件资源对系统性能的影响再怎么强调都不为过,而且如果要做性能优化,首先也应当看看硬件是否有提升的空间。常见的影响因素有CPU、内存、磁盘I/O等,而一个常常被大家忽略的因素是网络。如果是云服务器,要注意网络带宽大小的选择;如果是自建的服务器,则要注意交换机、网卡、网线等的带宽和传输速率,还要及时升级驱动程序。

软件资源

软件层面,一般考虑以下3个因素:

  • 数据库:数据库操作不仅涉及大量的内存以及CPU计算,还涉及到大量的磁盘读写。对数据库的性能优化是整个系统的核心,比如,我们常用的各种缓存都是为了减少对数据库的压力。

  • 锁竞争:单机环境下,锁的使用可能会带来大量的上下文切换,从而给系统带来性能开销;而分布式环境下,使用分布式锁也可能造成大量的请求堆积,影响整个系统性能。

  • 异常:Java应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能。如果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。因此,在使用自定义异常的时候,可以将writableStackTrace 设置为false来避免构造堆栈信息。

常见性能优化策略

前面用了很大篇幅介绍在性能测试中需要关注的指标和需要注意的问题,接着就是去做性能测试,性能测试的方法有挺多的,比如:性能测试、负载测试、压力测试等,性能测试的工具也挺多,知名的有 JMeter 和 LoadRunner。但具体如何使用工具去做性能测试,不在本文讨论范围之内。



在测试完成之后,就应该分析查找性能问题了。这是一个复杂且细致的过程,某些性能问题可能是一个原因导致的,有些则是几个问题共同导致的。但不管问题是什么,解决的策略无外乎以下几种。

优化代码

应用层面的很多性能问题,往往都是因为代码写得不讲究造成的。比如,不分场合的使用ArrayList 和 LinkedList;一次从数据库中读取所有数据到内存;直接在类上使用事务注解,造成大事务等等。这些看似没有问题的代码,在高并发的情况下会引发严重性能问题。因此,在代码层面往往需要从以下几个方面入手:

  • 制定并遵守代码规范:就Java层面,业界比较知名的,比如Google的Java编码规范以及阿里的编码规约。遵守这些规范,能够避免很多代码层面的BUG。

  • 优化设计:设计模式是改善代码的利器,其不仅提升代码的可扩展性和可维护性,还能够提升系统的性能。

  • 优化算法:这其实没什么好说的,一个好的算法可以大大提升系统的性能。比如,在已经排好序的队列中查找元素要使用二分查找来代替遍历查找。

  • 空间换时间:这应该是我们最常用的优化手段了,常见的比如数据库的分片、缓存等等都是空间换时间的典型手段。

参数调优

除了代码层面的优化以外,一些参数对系统的性能影响也挺大。就Java应用来说,JVM、Web容器、线程池、数据库连接等参数设置不合理的话,就有可能导致系统的性能瓶颈。



比如,根据自己的业务场景,合理地选择JVM垃圾回收算法可以有效提升系统性能。

最后

性能优化做得再好,系统总会存在极限,因此,兜底的策略也是性能优化的一部分,常见的兜底策略有限流、降级和熔断。很多中间件都有这样的功能,我们应当合理使用。最后需要着重强调的是任何的性能优化都得结合业务场景明确已知的性能问题和性能目标,不能为了优化而优化。

参考资料

性能测试应该怎么做?

Java性能调优 (第1、2讲) - 极客时间

发布于: 2020 年 07 月 22 日 阅读数: 67
用户头像

CHEN川

关注

Because, I love. 2017.10.16 加入

这里本来应该有简介的,但我还没想好 ( 另外,所有文章会同步更新到公众号:时光虚度指南,欢迎关注 ) 。

评论

发布
暂无评论
性能优化:如何做好性能优化