接口调优:从2s到200ms的心路历程分享

2023-10-26 15:25:00 浏览数 (1)

hello,大家好,我是灰小猿!

平常在开发过程中,时常会遇到一些复杂查询或复杂业务,导致接口出现性能问题,基本上每一个程序猿在工作中都离不开对接口性能的调优,

今天我就在这里记录一下最近在实际开发中对于一个业务和查询都复杂,且数据量较大的查询性能优化的心路历程,也算是对自己本次优化过程做一个总结,对后面的开发起到一个帮助作用,

接口优化前:

5W数据的业务处理,ApiPost请求耗时约2s,前端页面请求直接不用说了,加上界面渲染,直接干到3s多,而且这个接口还是查询比较频繁的接口。显然这样子的接口性能肯定是不行的。

接口优化后:

5W相同数据,优化后前端请求耗时平均约0.2s左右,接口查询刷新基本不用等待,达成要求!

下面我就对本次接口优化的过程做一个总结,

一、避免重复的查询sql调用

一般优化性能的第一步就是看你的代码是否存在明显的逻辑错误或重复调用,一般来说,性能耗时主要就是体现在两个地方:

  1. 业务复杂导致的业务处理耗时,
  2. 数据库查询耗时,

想要知道整个代码块中具体耗时体现在哪些地方就需要对功能模块分别加日志输出执行时间。

在对代码结构进行盘查的过程中,发现存在一些基本相同的sql被重复调用了,这就导致了接口在执行的过程中会不断的访问数据库,每一次访问数据库都是一个IO操作,重复这样的操作势必会造成耗时增加。sqk的重复调用主要体现在两点上:

(1)方法调用内外同样的表查询被执行两遍

也就是说,虽然可能方法内外都需要表中的某条数据,但是一般我们只需要在方法调用前执行一次该查询就可以了,之后将返回值作为参数的形式传入需要的方法即可,这样就可以完全避免sql在方法内外重复执行,从而增加查询消耗。

(2)for循环执行sql查询

这个算是比较基本的问题了,通过for循环遍历某些条件,再通过这些条件去分别请求数据库操作,一定会增加数据库的交互,从而增加耗时,所以在代码中无论业务大小,一定要避免for循环内部重复执行库操作。

可行的操作是:先通过stream流等操作,拿到所有的查询条件,然后对数据库执行批量操作,这样只需要一次sql交互,就可以将后面想要的数据都给查出来,如果需要获取,后面再通过map封装一下就可以了。

通过把上面的问题逐一优化之后,再去测试代码性能有了明显的提升,耗时降到了1.5s左右,这也可以算是通过优化业务代码来优化接口性能了。但是这个时间在操作比较频繁的接口来讲用户体验还不算好,因此还不能完全满足,那就继续对耗时代码进行优化。

二、不要用联表查询

继续对代码执行过程中输出日志,看具体的耗时代码,这一次发现耗时的代码主要体现在一些联表查询上,对于比较复杂的查询,在做联表查询时如果连接的表比较多,联表使用不当,势必也会造成性能问题,

因此我将联表查询拆分成单表查询加数据封装的形式,经过性能对比发现,采用单表的方式比联表查询的性能要优得多,通过看了其他的联调查询的耗时,也发现都是一样的,

因此建议大家在写联表查询的时候,如果时间允许,可以对比下单表和联表的情况下性能是否有明显差别,如果有当然要选择更优的一方。

因此,第二步我将耗时的联表查询改成了性能更优的单表批处理方式。

三、常用字段添加索引处理

除了将耗时的联表查询改为性能更优的单表查询外,还有一个数据查询比较关键的优化方案就是给常用字段增加索引,这个应该也是最快优化查询性能的方法。

比如我们可能在做批量查询时,经常会对某一字段执行in条件查询,那么就可以给这些常用字段增加上索引,来提高查询性能,

但是一般来说,一张表中的索引个数不建议超过三个,而且在使用索引的过程中,要注意索引是否真正使用,索引是否失效等问题,如果可以有效的使用索引,那数据库查询的性能也将会有明显的提升。

经过上述优化之后,原本2s耗时的接口,现在前端请求只需要大约0.6-0.8s左右,优化了一倍左右,其实效果还是蛮不错的,

但是对于对性能要求严格的程序猿们来讲,哪怕是0.1s的优化,也是对极致性能的追求!

因此在做完上面三步优化后,我继续对代码输出日志进行分析,发现整个执行流程中,业务封装的耗时仅占总耗时的百分之三十不到,大部分耗时还是体现在了数据查询上,这里说明一点,我的持久层框架使用的是mybatisPlus,因此在数据查询到模型映射的过程中也会存在一定的耗时问题,但是这些耗时是你只要使用数据库查询,基本上都是不可避免的。

四、增加缓存,Redis or 本地?

对于数据库查询耗时的问题,我又想到能否通过增加缓存,来避免直接操作数据库,而是通过缓存来获取数据,这样不就可以避免数据库查询耗时了嘛,经过Redis和本地缓存的对比,我最终选择了本地缓存caffeine作为缓存框架,具体caffeine的使用后面我会单独出一期使用教程,

我采用的方式是,针对整个接口中复杂且耗时的库查询,采用缓存的形式来存储数据,仅在缓存未命中的情况下执行库查询,对于通过缓存可以拿到的数据,直接从缓存中获取。从缓存中获取数据的时间几乎是毫秒级可以忽略,

因此,通过增加缓存,就在每次查询时完美的避开了耗时的库查询操作,将缓存完整的加上去之后,再去执行前端请求,5W数据的情况下接口耗时从原来的2s多稳定到了0.2s上下,性能优化了进10倍,至此,整个接口也算是优化到了比较满意的地步。

上面是我对最近解决性能问题的一些方法和思路,小伙伴们有好的建议或想法,欢迎提出讨论!

我是灰小猿,我们下期见!

0 人点赞