一次显著的性能优化

在当今的数字时代,性能优化已经成为了各类技术系统和应用的核心需求。无论是大型的企业级系统,还是个人用户的手机应用,性能优化都是提升用户体验、提高系统效率、降低运营成本的关键。性能优化不仅关乎用户体验,更关乎系统的稳定性和可靠性。一个性能低下的系统,不仅会让用户感到困扰,更可能导致数据丢失、系统崩溃等严重后果。因此,性能优化是每一个技术团队都需要关注和投入的重要工作。

接下来我描述一个我们在系统中进行性能优化的案例。我们当时遇到的问题是这样的,在测试环境测试人员访问内管系统的时候,发现很慢,经常要等几秒到10s,测试经常反馈受阻,人多的时候尤其明显,最终反馈到我们研发这边,然后我们就开始排查,因为监控系统还没搭建,所以基本比较手工,我们只能人肉排查,确实效率比较低,但是也没办法。

一、梳理整体逻辑

首先我们在网页上看返回接口的耗时基本都在1到10s之间,找到了最慢的那个接口,初步梳理了下接口逻辑,如下:

1、全局请求打印log;

2、组装参数,调用二方库方,打印返回参数;

3、返回的信息记录到数据库,然后返回数据库记录成功或者失败的信息;

4、返回参数打印log。

看着逻辑不复杂啊,为什么会用这么长时间呢?

二、优化案例

由于没有监控系统,所以我只能用stopwatch这样的组件来排查、然后看占用耗时占比。接入stopwatch后,我们用lightproxy的转调方式将请求回放到自己的机器进行调试,从stopwatch的结果来看,几个部分耗时都很长,排名分别是3、2、1、4,耗时分别是4s,1s,几百毫秒,几毫秒,根据这样的排名,我把重心放到前三个流程中了。

1、优化数据库

首先我看了3中记录数据库的流程:

1、主要是先从数据库里面获取一个唯一id;

2、组装返回的参数;

3、入库。

我开始的直觉是是数据库的索引太多导致的,然后我去看了下数据库的表结构,有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、优化日志打印

接下来我们又开始看调用二方库的流程,看逻辑主要:

1、打印请求参数;

2、然后调用dubbo接口进行上传文件信息;

3、打印返回参数。

照例再用stopwatch进行排查,发现打印请求参数和调用dubbo接口耗时会比较多,然后我看了下请求参数,没想到请求参数中有一个参数竟然是一个图片经过base64后的信息,极其大,还用json.jsonstring打印信息,我赶紧把该参数从打印参数中剔除了,因为这样的参数打印没有任何含义,还耗时。

4、优化dubbo配置

然后我看duboo的线程配置的竟然是10,问了下原因是因为在测试环境混合部署,为了节省资源所以改小的,我查了下dubbo的线程模型和一些接口配置,是这样的:

一)消费者端配置

    dubbo.consumer.timeout=1000,控制消费者等待服务端返回消息的最大时间,默认1秒;【默认配置下,某些服务端又存在慢方法,很容易导致请求超时报错;】

(二)服务提供端配置

    dubbo.protocol.threadpool=cached,配置业务线程池类型;其他线程池类型如下:【若不配置,线程池默认使用SynchronousQueue队列(除eager 线程池),SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素】

      fixed 固定大小线程池,启动时建立线程,不关闭,一直持有;(默认)

      cached 缓存线程池,空闲一分钟自动删除,需要时重建;

      limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题;

      eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached,cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)。

    dubbo.protocol.threads=10,限制业务线程池最大线程数;等于并发访问量,超过线程数的请求直接触发拒绝策略;(默认fixed 线程池最大200个线程)

    dubbo.protocol.dispatcher=message,配置dispatcher调度模式;【一般情况下建议配置调度模式为message】,其他调度模式如下:

      all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等;

      direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行;

      message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行;

      execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行;

      connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

    dubbo.provider.actives=10,每个服务消费者,每个方法最大并发调用数;从消费端控制,并发数是业务线程池大小的子集。

    dubbo.provider.executes=10,每个服务提供者各方法最大可并行执行请求数;从提供端控制,并发数是业务线程池大小的子集(小于等于业务线程池大小)。

然后看了下我们的配置,基本都是默认的,只是**dubbo.protocol.threads改为了10,**所以我改回了默认,再进行测试,发现这段的耗时也很小了,基本就在即使毫秒,这么慢的原因肯定和报文太大有关系,然后我打印了下报文大小,基本在2到6m之间,幸亏没有超过dubbo的默认8M这个大小,否则还得改参数。

5、日志优化

再重新体验了下,还是有一些接口通用的都在1s多,然后我就又把目光投向了全局打印请求和响应log这块,看耗时这块确实是大头,但是报文太大也是其中一个很重要的原因,看了下用的日志组件是log4j2的,突然想起几年前做过的一个log改为log4j2可以提升接口性能的活,然后就进行了改造,改造后确实有效果,我们的最慢的接口已经稳定在0.5s了,其他大部分接口耗时都已经在1s以内了,至此优化完成。

三、优化总结

那么,性能优化有没有一些方法论或者技巧呢?下面是一些常用的性能优化考虑点:

一、代码优化

代码是程序的基础,因此优化代码是实现性能提升的首要任务。这包括:

    简化代码逻辑:去除冗余和不必要的代码,减少代码复杂度,使程序更加简洁高效。

    算法优化:选择更高效的算法和数据结构,减少计算量,提高运算速度。

    异步处理:对于耗时的操作,可以采用异步处理方式,避免阻塞主线程,提高响应速度。

二、数据库优化

数据库是许多应用的关键组件,优化数据库可以显著提升系统性能。具体做法包括:

    索引优化:为经常查询的字段建立索引,提高查询速度。

    查询优化:避免使用复杂的查询语句,减少不必要的JOIN操作,提高查询效率。

    数据库连接优化:使用连接池技术,减少数据库连接的开销,提高数据库访问速度。

三、缓存技术

缓存可以减少对后端系统的访问次数,提高响应速度和吞吐量。具体实现方式有:

    使用缓存系统:如Redis、Memcached等,缓存热点数据,减少对数据库的访问。

    写时复制:对于频繁读写的数据,采用写时复制策略,减少对原数据的修改,提高数据访问速度。

四、并发和并行处理

优化系统的并发和并行处理能力,可以提高系统的吞吐量和响应速度。具体做法包括:

    使用线程池:通过线程池技术,将任务并行化和分配给多个处理单元,提高系统的处理能力。

    异步处理:对于可以异步执行的任务,采用异步处理方式,避免阻塞主线程。

五、资源管理

优化系统的资源管理,包括内存、CPU、网络带宽等,可以避免资源瓶颈和性能下降。具体做法包括:

    内存优化:使用内存映射技术、大页等技术,减少内存的动态分配,提高内存访问性能。

    CPU优化:通过编译器优化、减少不必要的计算等方式,降低CPU使用率,提高CPU处理效率。

    网络优化:使用I/O多路复用、长连接代替短连接等技术,减少网络延时和请求数,提高网络性能。

在实际工作中,我们可以使用合适的性能测试工具和方法,模拟真实负载并收集性能指标,可以找出系统的性能瓶颈,为优化提供数据支持。需要注意的是,性能优化是一个持续的过程,需要根据实际需求和系统特点不断调整和优化。同时,优化过程中也需要关注系统的稳定性和可靠性,避免过度优化导致系统出现新的问题。

相关推荐
长天一色5 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
裴云飞1 天前
鸿蒙性能优化之布局优化
性能优化·harmonyos
kingapex12 天前
性能优化-数据库分区技术深入解析
数据库·oracle·性能优化·数据库设计
胡耀超2 天前
知识图谱入门——6:Cypher 查询语言高级组合用法(查询链式操作、复杂路径匹配、条件逻辑、动态模式创建,以及通过事务控制和性能优化处理大规模数据。
性能优化·知识图谱·cypher
EterNity_TiMe_3 天前
【Linux进程间通信】Linux匿名管道详解:构建进程间通信的隐形桥梁
linux·运维·redis·缓存·性能优化·学习方法
PangPiLoLo3 天前
高性能架构—存储高性能
java·数据库·redis·性能优化·架构
Hello Dam4 天前
【文件增量备份系统】MySQL百万量级数据量分页查询性能优化
java·mysql·性能优化·springboot·深分页优化
黑马金牌编程4 天前
nginx常用的性能优化
运维·服务器·nginx·性能优化
百年孤独_4 天前
对于基础汇编的趣味认识
汇编·性能优化