记录一次接口优化的过程。接口响应时间从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

相关文章

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

PyInstaller打包selenium-wire过程中常见问题和解决指南

《PyInstaller打包selenium-wire过程中常见问题和解决指南》常用的打包工具PyInstaller能将Python项目打包成单个可执行文件,但也会因为兼容性问题和路径管理而出现各种运... 目录前言1. 背景2. 可能遇到的问题概述3. PyInstaller 打包步骤及参数配置4. 依赖

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Feign Client超时时间设置不生效的解决方法

《FeignClient超时时间设置不生效的解决方法》这篇文章主要为大家详细介绍了FeignClient超时时间设置不生效的原因与解决方法,具有一定的的参考价值,希望对大家有一定的帮助... 在使用Feign Client时,可以通过两种方式来设置超时时间:1.针对整个Feign Client设置超时时间

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,