在当今的数字时代,性能优化已经成为了各类技术系统和应用的核心需求。无论是大型的企业级系统,还是个人用户的手机应用,性能优化都是提升用户体验、提高系统效率、降低运营成本的关键。性能优化不仅关乎用户体验,更关乎系统的稳定性和可靠性。一个性能低下的系统,不仅会让用户感到困扰,更可能导致数据丢失、系统崩溃等严重后果。因此,性能优化是每一个技术团队都需要关注和投入的重要工作。
接下来我描述一个我们在系统中进行性能优化的案例。我们当时遇到的问题是这样的,在测试环境测试人员访问内管系统的时候,发现很慢,经常要等几秒到10s,测试经常反馈受阻,人多的时候尤其明显,最终反馈到我们研发这边,然后我们就开始排查,因为监控系统还没搭建,所以基本比较手工,我们只能人肉排查,确实效率比较低,但是也没办法。
一、梳理整体逻辑
首先我在网页上看返回接口的耗时基本都在1到10s之间,找到了最慢的那个接口,初步梳理了下接口逻辑,如下:
1、全局请求打印log;
2、组装参数,调用二方库方,打印返回参数;
3、返回的信息记录到数据库,然后返回数据库记录成功或者失败的信息;
4、返回参数打印log。
看着逻辑不复杂啊,为什么会用这么长时间呢?
二、优化案例
由于没有监控系统,所以我只能用 stopwatch 这样的组件来排查、然后看占用耗时占比。接入 stopwatch 后,我用 lightproxy 的转调方式将请求回放到自己的机器进行调试,从 stopwatch 的结果来看,几个部分耗时都很长,排名分别是3、2、1、4,耗时分别是4s、1s、几百毫秒、几毫秒,根据这样的排名,我把重心放到前三个流程中了。
1、优化数据库
首先我看了步骤3中记录数据库的流程:
- 主要是先从数据库里面获取一个唯一 id;
- 组装返回的参数;
- 入库。
我开始的直觉是是数据库的索引太多导致的,然后我去看了下数据库的表结构,有7个索引,确实有点多,然后想当然的了解下业务逻辑,进行了优化到了4个索引,再进行尝试,发现耗时虽然有下降,但是并不如预想的明显,而且耗时最多的还是在保存数据库这一步骤,然后再看了下,没看出什么异样啊,想不通。
只能在这块的三个步骤:获取数据库id,组装参数,保存数据库三个步骤再进行用stopwatch 排查,排查后发现获取数据库 id 这一步骤偶发性的会出现耗时3s多的情况,这引起了我的注意,然后我再进去看了下里面的逻辑,主要是这样的:通过 synchronized 修饰获取id的方法,然后从数据库密码获取id。
2、优化连接池和锁
获取数据库id的连接池是 Druid ,其中 maxActive 、 minIdle 、initialSize 三者配置的都是1,maxWait 配置的是10s,这就可以理解了,当请求很多的时候,大家都在竞争获取数据库 id 的方法导致等待而慢,然后我了解下线上部署的情况是3到5台机器,所以 synchronized 这样想当锁的逻辑也就失效了,所以做了如下改动:
1、synchronized 改为了分布式锁,
2、Druid 连接池的 maxActive 、 minIdle 、 initialSize 都改为了8。
再进行尝试的时候,发现几个接口的耗时没有偶发性的在保存时候出现耗时很多的情况了,保存基本只有几毫秒了,总耗时大部分接口也都下降了,看来这是一个共性问题。
3、优化日志打印
接下来我们又开始看调用二方库的流程,看逻辑主要:
- 打印请求参数;
- 然后调用 dubbo 接口进行上传文件信息;
- 打印返回参数。
照例再用 stopwatch 进行排查,发现打印请求参数和调用 dubbo 接口耗时会比较多,然后我看了下请求参数,没想到请求参数中有一个参数竟然是一个图片经过 base64 后的信息,极其大,还用 json.jsonstring 打印信息,我赶紧把该参数从打印参数中剔除了,因为这样的参数打印没有任何含义,还耗时。
4、优化 dubbo 配置
然后我看 dubbo 的线程配置的竟然是10,问了下原因是因为在测试环境混合部署,为了节省资源所以改小的,我查了下 dubbo 的线程模型和一些接口配置,然后看了下我们的配置,基本都是默认的,只是 dubbo.protocol.threads 改为了10,所以我改回了默认,再进行测试,发现这段的耗时也很小了,基本就在即使毫秒,这么慢的原因肯定和报文太大有关系,然后我打印了下报文大小,基本在2到6m之间,幸亏没有超过dubbo 的默认8M这个大小,否则还得改参数。
5、日志优化
再重新体验了下,还是有一些接口通用的都在1s多,然后我就又把目光投向了全局打印请求和响应log这块,看耗时这块确实是大头,但是报文太大也是其中一个很重要的原因,看了下用的日志组件是 log4j2 的,突然想起几年前做过的一个 log 改为 log4j2可以提升接口性能的活,然后就进行了改造,改造后确实有效果,我们的最慢的接口已经稳定在0.5s了,其他大部分接口耗时都已经在1s以内了,至此优化完成。
三、优化总结
那么,性能优化有没有一些方法论或者技巧呢?下面是一些常用的性能优化考虑点:
1、代码优化
代码是程序的基础,因此优化代码是实现性能提升的首要任务。这包括:
- 简化代码逻辑:去除冗余和不必要的代码,减少代码复杂度,使程序更加简洁高效。
- 算法优化:选择更高效的算法和数据结构,减少计算量,提高运算速度。
- 异步处理:对于耗时的操作,可以采用异步处理方式,避免阻塞主线程,提高响应速度。
2、数据库优化
数据库是许多应用的关键组件,优化数据库可以显著提升系统性能。具体做法包括:
- 索引优化:为经常查询的字段建立索引,提高查询速度。
- 查询优化:避免使用复杂的查询语句,减少不必要的JOIN操作,提高查询效率。
- 数据库连接优化:使用连接池技术,减少数据库连接的开销,提高数据库访问速度。
3、缓存技术
缓存可以减少对后端系统的访问次数,提高响应速度和吞吐量。具体实现方式有:
- 使用缓存系统:如Redis、Memcached等,缓存热点数据,减少对数据库的访问。
- 写时复制:对于频繁读写的数据,采用写时复制策略,减少对原数据的修改,提高数据访问速度。
4、并发和并行处理
优化系统的并发和并行处理能力,可以提高系统的吞吐量和响应速度。具体做法包括:
- 使用线程池:通过线程池技术,将任务并行化和分配给多个处理单元,提高系统的处理能力。
- 异步处理:对于可以异步执行的任务,采用异步处理方式,避免阻塞主线程。
5、资源管理
优化系统的资源管理,包括内存、CPU、网络带宽等,可以避免资源瓶颈和性能下降。具体做法包括:
- 内存优化:使用内存映射技术、大页等技术,减少内存的动态分配,提高内存访问性能。
- CPU优化:通过编译器优化、减少不必要的计算等方式,降低CPU使用率,提高CPU处理效率。
- 网络优化:使用I/O多路复用、长连接代替短连接等技术,减少网络延时和请求数,提高网络性能。
在实际工作中,我们可以使用合适的性能测试工具和方法,模拟真实负载并收集性能指标,可以找出系统的性能瓶颈,为优化提供数据支持。需要注意的是,性能优化是一个持续的过程,需要根据实际需求和系统特点不断调整和优化。同时,优化过程中也需要关注系统的稳定性和可靠性,避免过度优化导致系统出现新的问题。