记录一次大数据量接口优化过程

问题描述

记录一次大数据量接口优化过程。最近在优化一个大数据量的接口,是提供给安卓端APP调用的,因为安卓端没做分批次获取,接口的数据量也比较大,因为加载速度超过一两分钟,所以导致接口超时的异常,要让安卓APP分批次调用需要收取费用,所以只能先优化一下接口的速度。

分析问题

先使用阿里开源的监控工具Arthas来分析接口,Arthas 是一款线上监控诊断平台,可以实时查看应用 load、内存、gc、线程的状态信息,可以在不修改代码的情况,定位问题,分析接口耗时、传参、异常等情况,提高线上问题排查效率
在这里插入图片描述
找到对应的接口代码,使用Arthas的trace命令跟踪一下接口耗时情况,listOrder是对应方法名,skipJDKMethod是不打印jdk里面的方法

trace com.sample.order.orderServiceImpl listOrder -n 1  --skipJDKMethod

要筛选出响应时间大于1000毫秒的,可以使用如下命令

trace com.sample.order.orderServiceImpl listOrder '#cost > 1000' -n 1

通过Arthas就可以分析出一个接口方法里具体那个调用耗时,然后一层层分析即可,Arthas分析的接口大致如下,仅供参考

--[100.00% 3434.755668ms ] org.springframework.cglib.proxy.MethodInterceptor:intercept()
            `---[100.00% 3434.620187ms ] cn.test.server.business.VisitorBusiness:getStaffList()
                +---[0.00% 0.045431ms ] cn.test.client.dto.StaffListReqDto:getComId() #188
                +---[0.00% 0.019135ms ] cn.core.common.utils.CoreUtils:isEmpty() #188
                +---[0.00% 0.021816ms ] cn.hutool.core.date.DateUtil:timer() #192
                +---[0.00% 0.019987ms ] com.google.common.base.Splitter:on() #194
                +---[0.00% 0.005501ms ] cn.test.client.dto.StaffListReqDto:getComId() #194
                +---[0.00% 0.026292ms ] com.google.common.base.Splitter:splitToList() #194
                +---[0.00% 0.131507ms ] cn.hutool.core.convert.Convert:toInt() #195
                +---[0.34% 11.668407ms ] cn.core.user.client.querier.SchoolQuerierService:findBySchoolId() #198
                +---[0.00% 0.024199ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #203
                +---[0.02% 0.535107ms ] org.slf4j.Logger:info() #203
                +---[2.66% 91.504718ms ] cn.core.user.client.querier.RelationQuerierService:queryUserByIdsAndRoleType() #206
                +---[0.00% 0.019208ms ] com.google.common.collect.Lists:newArrayList() #206
                +---[0.00% 0.01107ms ] cn.core.common.utils.CoreUtils:isEmpty() #207
                +---[0.00% 0.013517ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #211
                +---[0.02% 0.532242ms ] org.slf4j.Logger:info() #211
                +---[0.00% 0.016216ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #218
                +---[0.01% 0.435469ms ] org.slf4j.Logger:info() #218
                +---[0.00% 0.009024ms ] cn.test.client.dto.StaffListReqDto:getComId() #221
                +---[0.53% 18.336268ms ]cn.test.persistence.dao.LogMapper:listStaffSyncRecordByComId() #221
                +---[0.00% 0.009043ms ] com.google.common.collect.Lists:newArrayList() #221
                +---[0.00% 0.019237ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #223
                +---[0.01% 0.279834ms ] org.slf4j.Logger:info() #223
                +---[0.00% 0.014057ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #227
                +---[0.01% 0.270155ms ] org.slf4j.Logger:info() #227
                +---[0.00% 0.006338ms ] com.google.common.collect.Lists:newArrayList() #231
                +---[0.00% 0.019707ms ] cn.hutool.core.date.TimeInterval:intervalRestart() #263
                +---[0.02% 0.548875ms ] org.slf4j.Logger:info() #263
                `---[0.00% 0.027475ms ] cn.test.client.dto.Result:success() #265

通过Arthas分析问题,定位到接口里,是因为查询出所有的数据后,再循环这些数据,在循环里又调了API去做业务处理,针对这种情况,怎么做调优?

处理问题

针对这种情况,我想到了分批次,分页来获取接口比较好,但是安卓端要改,需要收费额外的费用,所以我只能用多线程来做分批次处理了,JDK8里提供了CompletableFuture这个api来处理多任务,多线程,所以使用这个API加上线程池来处理接口

public OrderResult<List<OrderListDto>> getOrderList(ListReqDto reqDto) throws ExecutionException, InterruptedException {
        if (CoreUtils.isEmpty(reqDto.getComId())) {
            return OrderResult.fail("comId can not be null");
        }
		// Hutool计时器
        TimeInterval timer = DateUtil.timer();

       
        EmsReqVo reqVo = new EmsReqVo();
        reqVo.setComId(reqDto.getComId());
        List<OrderRecVo> orderRecList = Optional.ofNullable(orderMapper.queryOrderRecVo(reqVo )).orElse(Lists.newArrayList());
        LOG.info("查询所有预约订单:{}", timer.intervalRestart());
        if (CollUtil.isEmpty(orderRecList )) {
            return OrderResult.success(Lists.newArrayList());
        }
		
		// 任务列表
        List<CompletableFuture<OrderListDto>> fList = new ArrayList<>();
        // 自定义线程池
        ExecutorService executor = new ThreadPoolExecutor(
                10, 100, 5,
                TimeUnit.MINUTES,
                new ArrayBlockingQueue<>(10000)
        );
        List<OrderListDto> orderDtoList= Lists.newArrayList();
        orderRecList.stream().forEach(v -> {
            CompletableFuture<OrderListDto> f = CompletableFuture.supplyAsync(
                    ()-> {
                        Long userId = v.getUserId;
						// 根据userId去调用户数据,比较耗时
						UserDto userDto = userService.selectOne(userId);
						
                        // 封装参数返回
                        OrderListDto orderListDto = OrderListDto.builder()
                                .code(generator(v.getId())) // 流水号
                                .name(v.getOwnerName()) // 预约人姓名
                                .sex(gender)           // 预约人性别
                                .identity(v.getIdentityNum()) // 证件号码
                                .addr(v.getAddress()) // 证件地址
                                .tel(v.getMobile()) // 联系电话
                                .build();
                        return orderListDto;
                    },
                    executor
            );
            fList.add(f);
        });
        // 获取所有的任务
        CompletableFuture<Void> all= CompletableFuture.allOf(fList.toArray(new CompletableFuture[0]));
        CompletableFuture<List<OrderListDto>> allInfo = all.thenApply(v-> fList.stream().map(a-> {
            try {
                return a.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            return null;
        }).collect(Collectors.toList()));
        // 返回list
        orderDtoList= allInfo.get();
        // 关闭线程池
        executor.shutdown();

        LOG.info("查询预约订单记录 use:{}", timer.intervalRestart());

        return OrderResult.success(orderDtoList);
    }

通过CompletableFuture多任务处理,接口速度提高都十几秒
在这里插入图片描述

所以需要继续调优,在循环里调api会有网络带宽的影响,所以改成通过userId的集合去获取所有数据,封装成一个map集合,在循环里调用

 List<Integer> userIds = orderRecList.stream().map(OrderRecVo::getReceiveId).collect(Collectors.toList());
 List<UserDto> usersList = Optional.ofNullable(userService.getUsersByIds(userIds)).orElse(Lists.newArrayList());
 Map<Integer, UserDto> usersMap = usersList.stream().collect(Collectors.toMap(UserDto::getId, u -> u));

在循环里再通过map获取即可

UserDto userDto = Optional.ofNullable(usersMap.get(v.getUserId())).orElse(new TinyUserDto());

通过所有id集合获取总的所有数据,再通过map去获取的方式,接口速度快了很多,大概到毫秒级别
在这里插入图片描述

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

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

相关文章

【C++干货基地】探索C++模板的魅力:如何构建高性能、灵活且通用的代码库(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

天空卫士旗舰产品入选《网络安全专用产品指南》

权威认证 近日&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;发布了第一版《网络安全专用产品指南》。这一权威指南中&#xff0c;天空卫士荣获殊荣&#xff0c;旗下三款尖端产品荣耀入选&#xff0c;分别是增强型Web安全网关&#xff08;ASWG&#xff09;、数…

广交会烹饪机器人用上大模型 支付宝小程序云提供技术支持

近日&#xff0c;第135届广交会正在火热进行&#xff0c;记者获悉&#xff0c;支付宝小程序云助力合作伙伴田螺云厨&#xff0c;在烹饪机器人上开始用上大模型技术。各类智能产品的亮相&#xff0c;从中国制造迈向中国创造&#xff0c;也成为广交会的一个亮点。 &#xff08;图…

ipad的文件如何传到手机里 iPad较大文件怎么发送出去 iMazing下载教程

在现代生活中&#xff0c;随着移动设备的普及和多样化&#xff0c;我们经常需要在不同设备之间传输文件&#xff0c;以便在工作、学习或娱乐中更加便捷地使用这些文件。iPad和iPhone是用户广泛使用的设备&#xff0c;我们时常使用它们来存储和访问大量的个人数据。但有时&#…

人脸识别开源算法库和开源数据库

目录 1. 人脸识别开源算法库 1.1 OpenCV人脸识别模块 1.2 Dlib人脸识别模块 1.3 SeetaFace6 1.4 DeepFace 1.5 InsightFace 2. 人脸识别开源数据库 2.1 CelebA 2.2 LFW 2.3 MegaFace 2.4 Glint360K 2.5 WebFace260M 人脸识别 (Face Recognition) 是一种基于人的面部…

C#命名空间常用函数

在C#中&#xff0c;不同命名空间下有各种常用函数&#xff0c;下面列举一些常见的函数及其对应的命名空间&#xff1a; System命名空间&#xff1a; Console.WriteLine()&#xff1a;用于向控制台输出信息。Convert.ToInt32()&#xff1a;用于将其他数据类型转换为整数类型。 S…

python与上位机开发day04

模块和包、异常、PyQt5 一、模块和包 1.1 模块 Python中模块就是一个.py文件&#xff0c;模块中可以定义函数&#xff0c;变量&#xff0c;类。模块可以被其他模块引用 1.1.1 导入模块 """ 导入格式1&#xff1a; import 模块名 使用格式&#xff1a; …

【百度Apollo】探索自动驾驶:Apollo 新版本 Beta 全新的Dreamview+,便捷灵活更丰富

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引入一、Dreamview介绍二、Dreamview 新特性2.1、基于模式的多场景——流程更简洁地图视角调节&#xff1a;调试流…

JavaEE技术之MySql高级(索引、索引优化、sql实战、View视图、Mysql日志和锁、多版本并发控制)

文章目录 1. MySQL简介2. MySQL安装2.1 MySQL8新特性2.2 安装MySQL2.2.1 在docker中创建并启动MySQL容器&#xff1a;2.2.2 修改mysql密码2.2.3 重启mysql容器2.2.4 常见问题解决 2.3 字符集问题2.4 远程访问MySQL(用户与权限管理)2.4.0 远程连接问题1、防火墙2、账号不支持远程…

Python中的类(Class)详解——新手指南

在Python编程中&#xff0c;类&#xff08;Class&#xff09;是一个非常重要的概念&#xff0c;它允许程序员创建自己的对象类型。这些对象类型可以包含数据&#xff08;称为属性&#xff09;和函数&#xff08;称为方法&#xff09;&#xff0c;它们定义了这些对象的行为。本文…

Spring-Mybatis-Xml管理(动态sql语句,sql语句复用)

目录 前置条件 动态SQL语句 动态删除数据 1.集合类型:数组 2.集合类型: List 型 SQL语句重用 说明 &#x1f9e8;前置条件 已经创建了实体类(这边举个例子) 实体类User表 表中的字段名User实体类的属性值id (bigint auto increment) 长整型 自动增长private Long iduser…

场景文本检测识别学习 day06(Vi-Transformer论文精读)

Vi-Transformer论文精读 在NLP领域&#xff0c;基于注意力的Transformer模型使用的非常广泛&#xff0c;但是在计算机视觉领域&#xff0c;注意力更多是和CNN一起使用&#xff0c;或者是单纯将CNN的卷积替换成注意力&#xff0c;但是整体的CNN 架构没有发生改变VIT说明&#x…

C++入门第二节

点赞关注不迷路&#xff01;&#xff0c;本节涉及c入门关键字、命名空间、输入输出... 1. C关键字 C总计63个关键字&#xff0c;C语言32个关键字 asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenam…

LeetCode-hot100题解—Day5

原题链接&#xff1a;力扣热题-HOT100 我把刷题的顺序调整了一下&#xff0c;所以可以根据题号进行参考&#xff0c;题号和力扣上时对应的&#xff0c;那么接下来就开始刷题之旅吧~ 1-8题见LeetCode-hot100题解—Day1 9-16题见LeetCode-hot100题解—Day2 17-24题见LeetCode-hot…

V23 中的新增功能:LEADTOOLS React Medical Web 查看器

LEADTOOLS (Lead Technology)由Moe Daher and Rich Little创建于1990年&#xff0c;其总部设在北卡罗来纳州夏洛特。LEAD的建立是为了使Daher先生在数码图象与压缩技术领域的发明面向市场。在过去超过30年的发展历程中&#xff0c;LEAD以其在全世界主要国家中占有的市场领导地位…

21.Nacos集群搭建

模拟Nacos三个节点&#xff0c;同一个ip,启动三个不同的端口&#xff1a; 节点 nacos1, 端口&#xff1a;8845 节点 nacos2, 端口&#xff1a;8846 节点 nacos3, 端口&#xff1a;8847 1.搭建数据库&#xff0c;初始化数据库表结构 这里我们以单点的数据库为例 首先新建一…

2024年 Java 面试八股文——Redis篇

目录 1、介绍下Redis Redis有哪些数据类型 难度系数&#xff1a;⭐ 2、Redis提供了哪几种持久化方式 难度系数&#xff1a;⭐ 3、Redis为什么快 难度系数&#xff1a;⭐ 4、Redis为什么是单线程的 难度系数&#xff1a;⭐ 5、Redis服务器的的内存是多大…

基于飞腾D2000全国产化高速公路一体化收费站解决方案:站数据服务器、站AI服务器、收费系统、监控系统

高速公路一体化收费站解决方案 行业 交通工程及沿路设施作为公路的一个重要组成部分&#xff0c;对城市互联和城市发展具有重要意义&#xff0c;因此围绕高速公路的专用收费 站设计和建设&#xff0c;将有效促进枢纽集散系统与高速公路连通&#xff0c;显著提升城市高速集散能…

ES的脑裂现象

目录 0 集群结点的职责1 什么是脑裂现象2 造成脑裂现象的原因2.1 网络问题&#xff08;最常见&#xff09;2.2 主节点负载过大&#xff0c;资源耗尽&#xff0c;别的结点ping不到主节点2.3 主节点JVM内存回收时间过长导致 3 脑裂现象的解决方案3.1 局域网部署3.2 角色分离&…

C语言从入门到精通-C静态库的生成及使用

静态库 什么是静态库 C静态库&#xff08;Static Library&#xff09;是C语言编程中常用的一种库文件形式。与动态库&#xff08;Dynamic Library&#xff09;相比&#xff0c;静态库在程序编译时会被完全嵌入到最终的可执行文件中&#xff0c;因此生成的可执行文件不依赖于外…