记录一次接口优化的过程。接口响应时间从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
	* 
FROM
	cal_company_load_data 
WHERE
	company_id = 50 
	AND deleted = 0
ORDER BY
	load_date ASC

        优化方法:

               1)添加索引

                2)减少查询的字段

SELECT
	power,load_date
FROM
	cal_company_load_data 
WHERE
	company_id = 50 
	AND deleted = 0
ORDER BY
	load_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),待续........

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/614916.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

旅游系统小程序基于Uniapp+FastAdmin+ThinkPHP(源码搭建/上线/运营/售后/更新)

一款基于UniappFastAdminThinkPHP开发的旅游系统&#xff0c;包含消费者端&#xff08;手机端&#xff09;、机构工作人员&#xff08;手机端&#xff09;、机构端&#xff08;PC&#xff09;、平台管理端&#xff08;PC&#xff09;。机构可以发布旅游线路、景点项目&#xff…

ASP.NET一个简单的媒体播放器的设计与实现

摘 要 本论文所描述的播放器是在Microsoft Visual Studio .NET 2003平台下利用Visual Basic.NET语言完成的。使用Visual Basic.NET提供的Windows Media Player控件以及文件处理&#xff0c;最终实现一款别致的&#xff0c;贴近用户操作习惯的媒体播放器。 该播放器实现了对WAV…

excel表格里,可以把百分号放在数字前面吗?

在有些版本里是可以的&#xff0c;这样做&#xff1a; 选中数据&#xff0c;鼠标右键&#xff0c;点击设置单元格格式&#xff0c;切换到自定义&#xff0c;在右侧栏输入%0&#xff0c;点击确定就可以了。 这样设置的好处是&#xff0c;它仍旧是数值&#xff0c;并且数值大小没…

进程间通信(二)

共享内存 当进程A和进程B有一块共享的内存空间时&#xff0c;这两个进程之间的数据交互就会变的很简单&#xff0c;只需要像读取自己内存空间中的元素一样去读取数据即可。实现共享内存进行数据交互的一般步骤&#xff1a; 创建/打开共享内存内存映射数据交换断开与共享内存的…

icap对flash的在线升级

文章目录 一、icap原语介绍&#xff08;针对 S6 系列的 ICap&#xff09;&#xff0c;之后可以拓展到A7、K7当中去二、程序1设计2.1信号结构框图2.2 icap_delay设计2.3 icap_ctrl设计&#xff08;可以当模板使用&#xff0c;之后修改关键参数即可&#xff09; 三、程序2设计四、…

C++中调用python函数(VS2017+WIN10+Anaconda虚拟环境)

1.利用VS创建C空项目 step1 文件——新建——项目 step2 Visual C—— Windows桌面——Windows桌面向导 step3 选择空项目 step4 源文件——新建项——添加 step5 Visual C——C文件&#xff08;.cpp&#xff09; 2.配置环境 Step1. 更换成Release与X64 Step2. 打开项目属性&…

巨坑啊! before-upload返回false 会执行on-remove

通过对on-remove对应参数的打印&#xff0c;发现回调中的file参数有个status&#xff0c;若是是在before-upload中就被过滤了&#xff0c;就是ready&#xff0c;若是已经上传成功了去点击删除&#xff0c;status是success&#xff0c;就他了。 onRemove(file,fileList){if(file…

探索Linux:深入理解各种指令与用法

文章目录 cp指令mv指令cat指令more指令less指令head指令tail指令与时间相关的指令date指令 cal指令find指令grep指令zip/unzip指令总结 上一个Linux文章我们介绍了大部分指令&#xff0c;这节我们将继续介绍Linux的指令和用法。 cp指令 功能&#xff1a;复制文件或者目录 语法…

在 Python 的哪个版本之后,字典的添加顺序与键的顺序是一致的?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 在 Python 的不同版本中&#xff0c;字典&#xff08;dict&#xff09;类型的行为发生了显著变化。在 Python 3.6 及之前的版本中&#xff0c;字典是无序的&#xff0c;这意味着字典在遍历时不能保证按…

图鸟模板-官网:基于Vue 3的前端开发新篇章

一、引言 随着前端技术的飞速发展&#xff0c;企业对于官网的需求也从简单的展示型网站向功能丰富、交互体验良好的方向转变。在这样的背景下&#xff0c;图鸟模板-官网以其基于Vue 3的纯前端开发特性&#xff0c;以及支持微信小程序、支付宝小程序、APP和H5的跨平台能力&…

【.NET Core】你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟

你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟 文章目录 你认识Attribute之CallerMemberName、CallerFilePath、CallerLineNumber三兄弟一、概述二、CallerMemberNameAttribute类三、CallerFilePathAttribute 类四、CallerLineNumberAttribute 类…

每个初创企业创始人都应了解的搜索引擎优化基础知识

会话式AI引擎&#xff1a;如何革新您的业务通讯&#xff1f; 对于已经身兼数职的初创企业创始人来说&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;似乎是一项艰巨的任务。然而&#xff0c;在数字时代&#xff0c;它是推动流量、建立品牌知名度和实现长期成功不可或缺的…

Golang编译优化——稀疏条件常量传播

文章目录 一、概述二、稀疏条件常量传播2.1 初始化worklist2.2 构建def-use链2.3 更新值的lattice2.4 传播constant值2.5 替换no-constant值 一、概述 常量传播&#xff08;constant propagation&#xff09;是一种转换&#xff0c;对于给定的关于某个变量 x x x和一个常量 c …

c++ 归并排序

归并排序是一种遵循分而治之方法的排序算法。它的工作原理是递归地将输入数组划分为较小的子数组并对这些子数组进行排序&#xff0c;然后将它们合并在一起以获得排序后的数组。 简单来说&#xff0c;归并排序的过程就是将数组分成两半&#xff0c;对每一半进行排序&#xff0c…

车辆运动模型中LQR代码实现

一、前言 最近看到关于架构和算法两者关系的一个描述&#xff0c;我觉得非常认同&#xff0c;分享给大家。 1、好架构起到两个作用&#xff1a;合理的分解功能、合理的适配算法&#xff1b; 2、好的架构是好的功能的必要条件&#xff0c;不是充分条件&#xff0c;一味追求架构…

贝壳面试:MySQL联合索引,最左匹配原则是什么?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 1.谈谈你对MySQL联合索引的认识&#xff1f; 2.在MySQ…

【强训笔记】day20

NO.1 思路&#xff1a;先判断能对砍几个回合&#xff0c;取最小值&#xff0c;因为回合数是整数&#xff0c;所以可能存在都大于0的情况&#xff0c;再判断一下如果都存活就再对砍一次&#xff0c;直到一家存活或者都死亡。 代码实现&#xff1a; #include<iostream>u…

即插即用篇 | YOLOv8 引入多光谱通道注意力 | 频率领域中的通道注意力网络

本改进已集成到 YOLOv8-Magic 框架。 注意力机制,尤其是通道注意力,在计算机视觉领域取得了巨大成功。许多工作聚焦于如何设计高效的通道注意力机制,同时忽略了一个基本问题,即通道注意力机制使用标量来表示通道,这很困难,因为会造成大量信息的丢失。在这项工作中,我们从…

OGG几何内核开发-BRepAlgoAPI_Fuse与BRep_Builder.MakeCompound比较

最近在与同事讨论BRepAlgoAPI_Fuse与BRep_Builder.MakeCompound有什么区别。 一、从直觉上来说&#xff0c;BRepAlgoAPI_Fuse会对两个实体相交处理&#xff0c;相交的部分会重新的生成相关的曲面。而BRep_Builder.MakeCompound仅仅是把两个实体组合成一个新的实体&#xff0c;…

【一支射频电缆的诞生】GORE 戈尔

工具连接&#xff1a; https://microwave-cablebuilder.gore.com/ 控制参数&#xff1a; 连接器&#xff1a; 欣赏