记录一次接口优化的过程。接口响应时间从500s下降到5s。

2024-05-12 04:04

本文主要是介绍记录一次接口优化的过程。接口响应时间从500s下降到5s。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

记录一次接口优化的过程。接口响应时间从500s下降到5s。

接口说明:

该接口通过用户导入的一年内每天的厂区用电功率数据来计算用户安装储能设备后的收益情况。

用电功率数据具体为每15分钟一条,一年约有 12*30*24*4 = 34560 条。

代码循环情况为:一层循环(根据经验,储能设备1-35台) 二层循环(月份,12) 三层循环(天,大约30)四层循环(根据业务模型,一天分为18个时间段)共计需要循环:35*12*30*18 = 226,800 次。

业务模型,一天分为18个时间段:

步骤:

一、分析代码,列出怀疑的耗时代码,对该段代码进行计时

        1、怀疑点A  这段代码在主代码流程里(不参与循环),getDayPowerInfo方法就是一次性读取整年用电功率数据的方法,返回按月分组的的二维数组,二维数组里共有 (约)34560 条数据。

TimeInterval interval = DateUtil.timer();
System.err.println("开始计时:"+interval.start());
Map<String, Double[][]> monthAndDayPowerInfo = getDayPowerInfo(req.getCompanyId());
System.err.println("getDayPowerInfo耗时:"+interval.intervalSecond());

         平均耗时:13秒

         2、怀疑点B 这段代码在二层循环里(按月循环)

 // 单天单台的理论收益值
Double Price_standard = provinceMonthIncomeService.calVoltageDayIncome(year + "", month + "",req.getVoltageId());
System.err.println("t_1 耗时:"+interval.intervalSecond());

            平均耗时:240ms (乘以循环次数 35台12月之后,耗时100.8秒

            3、怀疑点C 这段代码在二层循环里(按月循环)

// 用电波形
Map<Long, List<BasedataElecRuleDO>> ruleMap = elecRuleService.getVoltageYearMonthElecRule(Arrays.asList(voltageId), year + "", month + "");
System.err.println("t_2:"+interval.intervalSecond());

        平均耗时:70ms(乘以循环次数 35台12月之后,耗时29.4秒

        4、怀疑点D 这段代码在二层循环里(按月循环)

// 电价
Map<Long, List<BasedataElecPriceDO>> priceMap = elecPriceService.priceByVoltageIdList(year + "",month + "", Arrays.asList(voltageId));
System.err.println("t_3:"+interval.intervalSecond());

          平均耗时:40ms(乘以循环次数 35台12月之后,耗时16.8秒

          5、怀疑点E 这段代码在二层循环里(按月循环)

// 实际用电量(根据波形)Map<Integer, Double> elecTypeEnergyMap = getElecTypeEnergy(year + "", month + "", req.getVoltageId(),req.getCompanyId());
System.err.println("t_4:"+interval.intervalSecond());

          平均耗时:460ms(乘以循环次数 35台12月之后,耗时193.2秒

二、分析并进行优化

        1、怀疑点A,这段代码主要耗时在查询3万多的数据,该表目前50w数据

              原始SQL如下

SELECT* 
FROMcal_company_load_data 
WHEREcompany_id = 50 AND deleted = 0
ORDER BYload_date ASC

        优化方法:

               1)添加索引

                2)减少查询的字段

SELECTpower,load_date
FROMcal_company_load_data 
WHEREcompany_id = 50 AND deleted = 0
ORDER BYload_date ASC

        优化结果: 11s  ->   3s 

        目前仍旧不是很满意,如有高手,请帮忙指正。

        2、怀疑点B,这个代码主要耗时点为,2次查询SQL和1次查询外部接口

// 1. 获取年月电价
Map<Long, List<BasedataElecPriceDO>> voltagePriceMap =priceService.priceByVoltageIdList(year, month, Arrays.asList(voltageId));
List<BasedataElecPriceDO> priceList = voltagePriceMap.get(voltageId);// 2. 获取年月规则
Map<Long, List<BasedataElecRuleDO>> voltageRuleMap =ruleService.getVoltageYearMonthElecRule(Arrays.asList(voltageId), year, month);
List<BasedataElecRuleDO> ruleList = voltageRuleMap.get(voltageId);// 调用外部接口
HttpResponse response = HttpUtil.createPost(rankUrl.get(0).getName()).header("Content-Type", "application/json; charset=UTF-8").body(jsonParam).execute();

                优化方法:

                1)电价和规则(波形)的获取,可以提取到代码主流程里进行统一查询,然后整合成一个map,以月份为key,再把map传入该方法使用,这样可以避免在月份的循环里去查SQL

//在主代码流程里进行统一查询
TreeMap<String, List<BasedataElecRuleDO>> voltageByMonth =elecRuleService.getVoltageYearElecRule(voltageId, yearS + "");
TreeMap<String, List<BasedataElecPriceDO>> priceByMonth =elecPriceService.getVoltageYearElecPrice(yearS + "", voltageId);// 把整合的map传入该方法
Double Price_standard = provinceMonthIncomeService.calVoltageDayIncome(year + "", month + "",req.getVoltageId(),ruleMap,priceMap);// 在方法中直接从map里取,不用再查数据库
// 1. 获取年月电价
List<BasedataElecPriceDO> priceList = voltagePriceMap.get(voltageId);
// 2. 获取年月规则
List<BasedataElecRuleDO> ruleList = voltageRuleMap.get(voltageId);

                2) 查询外部接口暂时无法优化,耗时约50ms

                优化结果:460ms ->  80ms  (乘以循环次数 35台12月之后,耗时33.6秒)       

        3、怀疑C和怀疑点D,问题一样都是在二层循环里进行SQL查询

在对怀疑点B的优化中,其实我已经把对于C和D的查询放到里代码主流程里,然后整合成map在循环里使用,所以这里其实不用再优化了。

        红色为原代码,方法里去查SQL了,蓝色为优化后代码,从map里取数据。

        优化结果:40ms+70ms  -> 1ms+1ms  (乘以循环次数 35台12月之后,耗时<1秒)

        4、怀疑点E  该方法主要是通读取用户导入的负载数据(3万多条那个)来计算用户实际使用的电能。

        优化方法:

                1) 这段代码经过上下游业务分析,发现与该接口业务不是强相关,完全可以单独形成一个接口,前端可以同时调用这2个接口,以减少页面等待的总时间。

// 分离出一个接口
@PostMapping(value = "/calEnergyUsed")
@ApiOperation("根据负载功率曲线计算实际用电量")
public CommonResult<Map<Integer, Double>> calEnergyUsed(Long companyId,Long voltageId) {Map<Integer, Double>resp=companyLoadDataService.calEnergyUsed(companyId,voltageId);return success(resp);
}

         2) 该接口与怀疑点A一样,查询了3w条数据,所以也和A的优化方法一样,对SQL进行优化

        优化结果:

        460ms (注意这里是分月查询DB,乘以循环次数 35台12月之后,总耗时193.2秒)->  5秒(注意这里是一次查询全年数据)

目前优化总结:

目前5个怀疑点的总耗时由 13秒+100.8秒+29.4秒+16.8秒+193.2秒 = 360 秒 ,优化到

3s +33.6秒+<1秒+5秒(并行,忽略) =  37 秒  似乎还是无法接受

三、进一步优化

经过分析,发现怀疑点B,还有优化空间。

怀疑点B 这段代码在二层循环里(按月循环)

 // 单天单台的理论收益值
Double Price_standard = provinceMonthIncomeService.calVoltageDayIncome(year + "", month + "",req.getVoltageId());
System.err.println("t_1 耗时:"+interval.intervalSecond());

我们仔细观察B的代码,发现该B与一层循环没有数据上的关系,他只与二层循环(月份循环)有关。由于他比较耗时,也就是说,我们可以将此方法抽离出大的循环之外,以减少该方法的循环次数。简单计算一下:本来需要循环 35*12次,提取出来之后,只需循环12次。

// 在大循环之前,提前对每个月份的月理论收益值进行循环计算,整合成map,再传递到后面的循环里去使用
Map<Integer,Double> monthAndSaveTheory = new HashMap<>();
for (Map.Entry<String, Double[][]> stringEntry : monthAndDayPowerInfo.entrySet()) {String yearAndMonth = stringEntry.getKey();Integer year = Integer.parseInt(yearAndMonth.split("-")[0]);Integer month = Integer.parseInt(yearAndMonth.split("-")[1]);......// 单天单台的理论收益值Double Price_standard = provinceMonthIncomeService.calVoltageDayIncome(year+"", month + "",req.getVoltageId(),ruleMap,priceMap);monthAndSaveTheory.put(month, Price_standard);
}

优化结果:

33.6秒 ->  1秒

再次优化总结:

一次优化:3s +33.6秒+<1秒+5秒(并行,忽略) =  37 秒

二次优化:3s+1s+<1秒+5秒(并行,忽略) = 5秒

目前这个接口接口已经由500秒 优化到5秒 

四、再进一步优化

目前看来,最大的耗时为3w条数据的SQL查询时间(3s),待续........

这篇关于记录一次接口优化的过程。接口响应时间从500s下降到5s。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/981542

相关文章

Python UV安装、升级、卸载详细步骤记录

《PythonUV安装、升级、卸载详细步骤记录》:本文主要介绍PythonUV安装、升级、卸载的详细步骤,uv是Astral推出的下一代Python包与项目管理器,主打单一可执行文件、极致性能... 目录安装检查升级设置自动补全卸载UV 命令总结 官方文档详见:https://docs.astral.sh/

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

MySQL存储过程之循环遍历查询的结果集详解

《MySQL存储过程之循环遍历查询的结果集详解》:本文主要介绍MySQL存储过程之循环遍历查询的结果集,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言1. 表结构2. 存储过程3. 关于存储过程的SQL补充总结前言近来碰到这样一个问题:在生产上导入的数据发现

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

MybatisPlus service接口功能介绍

《MybatisPlusservice接口功能介绍》:本文主要介绍MybatisPlusservice接口功能介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录Service接口基本用法进阶用法总结:Lambda方法Service接口基本用法MyBATisP

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

统一返回JsonResult踩坑的记录

《统一返回JsonResult踩坑的记录》:本文主要介绍统一返回JsonResult踩坑的记录,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录统一返回jsonResult踩坑定义了一个统一返回类在使用时,JsonResult没有get/set方法时响应总结统一返回

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

java对接海康摄像头的完整步骤记录

《java对接海康摄像头的完整步骤记录》在Java中调用海康威视摄像头通常需要使用海康威视提供的SDK,下面这篇文章主要给大家介绍了关于java对接海康摄像头的完整步骤,文中通过代码介绍的非常详细,需... 目录一、开发环境准备二、实现Java调用设备接口(一)加载动态链接库(二)结构体、接口重定义1.类型