性能优化那点事

‘不管项目大小,一旦上线,或多或少都会遇到性能问题’性能问题就像是魔咒一般藏绕着我们。

性能优化应该什么时候开始

有些性能问题是随着时间的积累慢慢产生的,比如系统一开始数据量很小的时候,没有什么问题,等到数据积累到一定程度,问题就暴露出来了;有些问题是由于访问量的过大造成的,比如系统平时没问题,一到搞活动时就挂;也有些问题是遗留系统经过太多人去维护修改导致各种坏代码味道性能问题仿佛到处存在。性能问题就如同一颗定时炸弹,只要数据量访问量一上来,或者各个团队在开发迭代中没有注重性能的意识,早晚会炸。既然迟早会出问题,那我们应该什么时候开始进行性能优化呢?是等出了问题后在进行优化,还是在编码的过程中就尝试避免那些错误的代码模式呢,或者采用一些手段尽可能的避免踩坑呢?

有人会说项目压力大,如果开始过程中要考虑性能问题那么会影响进度。我觉得这是在给自己或者给后人挖坑,我们在一开始设计接口的时候就应该考虑性能问题,不仅仅要考虑接口的合理性易用性同时也要考虑接口是否有批量调用的情况。最简单的方法就是在设计接口的时候就直接设计批量接口,这样这个接口又能支持批量又能支持单个,当然考虑到批量会有额外的工作要做,但总比出了问题到处去填坑强吧,这需要我们有能力识别未来业务上对批量的需求,并不是每个接口都需要支持批量操作。

我们还可以用很多方法来保证代码质量以提高系统性能的,比如:

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

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

3.编写高效 SQL、合理使用索引和事务来提升数据库性能,使用ORM工具时注意N+1问题,有些看起来很便捷的方法请理解其细节再去使用。

4.多考虑接口的使用场景,是否有批量的可能,如果有提供批量接口

5.如果对性能要求很高,是否考虑使用Netty等异步手段

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

这里有个有争议的优化手段:“不使用的对象应手动赋值为 NULL”有利于 GC 更早地回收内存,但在大多数场景下,不使用的局部变量是否设置为 NULL,对 GC 没有任何影响,毕竟方法执行完毕,栈帧就从操作数栈中弹出,方法中的局部变量就没了,是否设置为 NULL 也就没有任何影响。但是如果你是开发中间件的,或者某个复杂算法,那么手动设置为NULL确实在某些情况会有利于GC,比如临时变量占用了大量内存当遇到‘安全点’时如果不主动设置为NULL在JDK运行在‘解释’阶段时确实会导致GC回收的比较慢。你可以在J.U.C包中经常看到xxx=null,注释都是help gc,但是我们经常写业务代码的其实没必要这么做。

在系统开发完成以后,可以根据一些预期的指标 ( 比如,并发数 ) 和硬件资源来对系统进行测试,通过各种分析统计工具来判断各项指标是否在预期范围内。等到系统上线后,还要根据日志、监控系统来观测系统性能,一旦发现问题,就要及时分析并修复。这里可以使用的软件很多,比如Dynatrace等各类APM工具,但如果你的系统比较定制也比较奇特的化,那么恐怕很难找到现成的工具,我们可以自己开发一套监控系统,其实知道原理也很简单的。

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

两原则:不去优化没有测试的软件(单元测试要有,不然优化出了bug都不知道)、不去优化你不了解的软件

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

性能测试的主要指标

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

响应时间

响应时间可以从端到端的响应时间细分下去:比如数据库的响应时间,IO的响应时间,HTTPClient的响应时间。当我们优化系统的时候通过收集这些响应时间可以精确定位性能问题出现在哪。

并发数

并发数是指系统能够同时处理请求的数量,这个数字也反映了系统的负载承受能力。

吞吐量

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

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

如何严谨地做性能测试

那如何更严谨地做性能测试?分享一个做性能测试比较科学的方法(来源自COOLSHELL):

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

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

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

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

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

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

影响系统性能的主要因素

我们要先了解下一般情况哪些因素会影响到系统的性能,这样我们可以逐个排查。

硬件

一般硬件是我们首先考虑的因素,如果可以提升硬件那么一般可以解决一些性能问题。常见的影响因素有CPU、内存、磁盘 I/O 、网络等,如果内存不够或者CPU长期满负载那么就需要升级硬件了,如果业务中IO很重那么要考虑换个SSD硬盘,如果流量很大要考虑网络带宽够不够,网卡性能跟得上不。

系统

系统相关的点实在是太多了,这里简单介绍几种常见的情况:

1)Linux文件描述符限制,有时候默认的值比较低,影响并发。

2)Linux中Swap强烈建议关闭,打开坏处多于好处,会有意想不到的问题。

3)高流量的应用需要注意网卡中断问题,使用CPU亲和性绑定网卡。

软件

一般有几个因素需要重点关注

1)数据库:数据库操作不仅涉及大量的内存以及 CPU 计算,还涉及到大量的磁盘读写。对数据库的性能优化是整个系统的核心,比如,我们常用的各种缓存都是为了减少对数据库的压力。开启慢SQL搜集,通过分析慢SQL来优化系统中效率低下的SQL语句。

2)锁竞争:单机环境下,锁的使用可能会带来大量的线程资源浪费,从而给系统带来性能开销;而分布式环境下,使用分布式锁也可能造成大量的请求堆积,影响整个系统性能。优化重心在于锁粒度的控制,以及如何采用无锁模型去替代。

3)线程池:线程池的不恰当申明和配置也会带来问题,请确保你的线程池都是有界的,确保你的线程池大小是合理的。

4)异步系统与同步IO:确保你理解Netty相关知识,不要在Reactor线程中去使用同步IO。

5)循环与外部请求:不要将外部请求放到循环中,而是应该尽可能通过批量方式一次请求。

6)看似便利确暗藏杀机:很多库提供了看似便利的方法,其实暗藏杀机,不要使用你不了解原理的所谓高级用法。

兜底策略

性能优化做得再好,系统总会存在极限,因此,兜底的策略也是性能优化的一部分,常见的兜底策略有限流、降级和熔断。很多中间件都有这样的功能,我们应当合理使用。还有我们可以通过减少涌入服务器的流量来避免高流量对我们服务器的冲击,比如接入CDN,利用CDN的节点优化和缓存能力能很好的优化我们的性能,当然能使用更高级的边缘计算技术那么在某些场景下会有质的飞跃。

需要着重强调的是任何的性能优化都得结合业务场景明确已知的性能问题和性能目标,不能为了优化而优化。市面上有很多APM工具和性能分析工具可以帮助你定位性能问题,但如果你的系统非常的复杂且并不是标准容器,那么很可能你需要自己开发个APM工具来帮助你定位性能问题了,那么如何开发自己的性能分析工具呢,请听下回分解。