当前位置: 首页 > news >正文

excel导出使用arthas动态追踪方法调用耗时后性能优化的过程

目录

    • 一、背景
    • 二、发现问题
      • 1、本地调试
        • 1.1安装启动arthas
        • 1.2使用arthas追踪耗时情况
        • 1.3结论
      • 2、服务器调试
        • 2.1、测试一使用arthas查看修改后的代码是否生效
        • 2.2、测试二使用arthas追踪耗时情况
        • 2.3、结论
    • 三、解决问题
      • 1、原来数据写入excel的方法
      • 2、修改成使用fastexcel

一、背景

1、用户反应某个报表导出时等待很久也没有导出来,我F12查看导出接口在1.5min的时间请求被取消,后端服务器的网关(如Nginx、Apache)或后端应用本身可能设置了90秒的超时,用户一直等待也没有导出文件,所以需要优化导出逻辑加快导出时间。
在这里插入图片描述

二、发现问题

1、本地调试

1.1安装启动arthas

(1)、下载地址:https://arthas.aliyun.com/arthas-boot.jar
(2)、启动 java -jar arthas-boot.jar
(3)、选择目标应用id回车
(4)、执行tarce命令 -n 5 表示最多捕获5次方法调用,使用–skipJDKMethod false包含基础库调用耗时,还可以添加这个参数’#cost>500’表示只显示超过500毫秒的。

trace 全类名 方法名  -n 5 --skipJDKMethod false
1.2使用arthas追踪耗时情况

从代码看有三个主要逻辑,第一个使用远程调用的方式获取数据大概12万条,第二个是循环获取数据对某个字段进行国际化,第三个是把数据写入excel方法。在arthas终端看是远程获取数据耗时70s,数据国际化6s,写入excel耗时15s。待截图…

1.3结论

从耗时情况分析远程获取数据方法占用主要时间,所以需要优化查询逻辑,经过查看sql执行计划extra 中useing filesort,没有在排序字段上创建索引导致创建索引后重新看耗时时间,获取数据缩短到50s。国际化改成了sql表关联。总耗时65s左右小于90s本地可以正常导出。

2、服务器调试

本地调试好以后本以为问题就解决了,但是发布到生产服务器后,导出还是1.5min后取消了,没有什么效果,所以在生产服务器上进行了两个测试。
arthas下载命令:curl -O https://arthas.aliyun.com/arthas-boot.jar
然后启动和选择目标服务进入arthas终端
在这里插入图片描述

2.1、测试一使用arthas查看修改后的代码是否生效

在arthas终端中执行:

jad 全类名

反编码查看代码是否修改后的逻辑,我发现代码已经是修改后的。

2.2、测试二使用arthas追踪耗时情况

执行trace 全类名 方法名 -n 5 --skipJDKMethod false 后在生产上执行导出
在这里插入图片描述

2.3、结论

通过上面结果可以发现,73.86%耗时的是把数据写入excel的方法。所以优化重点是数据写入excel。

三、解决问题

1、原来数据写入excel的方法

循环遍历所有的数据,如果数据是实体类对象则使用反射的方式获取对象中的属性值,这里有多少数据量就会使用几次反射,反射开销很大。
在这里插入图片描述

2、修改成使用fastexcel

FastExcel 使用了缓存,避免重复使用反射方法获取属性值,提高了性能。
在这里插入图片描述
这个是优化后的追踪方法调用耗时情况,数据写入excel速度显著提高32秒左右,总耗时54秒左右。
在这里插入图片描述

ReflectionBeanMap 核心实现如下,initPropertyDescriptors 初始化属性描述符缓存,getValue时从缓存中获取PropertyDescriptor,反射调用 getter 方法。

public class ReflectionBeanMap<T> implements BeanMap<T> {private final Class<T> beanClass;private final Map<String, PropertyDescriptor> propertyDescriptors;private final BeanMapConfig config;public ReflectionBeanMap(Class<T> beanClass) {this(beanClass, BeanMapConfig.defaultConfig());}public ReflectionBeanMap(Class<T> beanClass, BeanMapConfig config) {this.beanClass = beanClass;this.config = config;this.propertyDescriptors = initPropertyDescriptors(beanClass);}/*** 初始化属性描述符缓存*/private Map<String, PropertyDescriptor> initPropertyDescriptors(Class<T> beanClass) {try {BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();Map<String, PropertyDescriptor> result = new LinkedHashMap<>();for (PropertyDescriptor descriptor : descriptors) {// 过滤掉没有 getter 方法的属性if (descriptor.getReadMethod() != null) {result.put(descriptor.getName(), descriptor);}}return Collections.unmodifiableMap(result);} catch (IntrospectionException e) {throw new RuntimeException("Failed to introspect bean: " + beanClass.getName(), e);}}/*** 获取属性值 - 核心反射调用*/@Overridepublic Object getValue(T bean, String propertyName) {if (bean == null) {return null;}PropertyDescriptor descriptor = propertyDescriptors.get(propertyName);if (descriptor == null) {if (config.isIgnoreMissingProperties()) {return null;}throw new IllegalArgumentException("Property '" + propertyName + "' not found in " + beanClass.getName());}Method readMethod = descriptor.getReadMethod();if (readMethod == null) {if (config.isIgnoreMissingGetters()) {return null;}throw new IllegalArgumentException("No getter method for property '" + propertyName + "' in " + beanClass.getName());}try {// 反射调用 getter 方法return readMethod.invoke(bean);} catch (IllegalAccessException | InvocationTargetException e) {throw new RuntimeException("Failed to get value for property '" + propertyName + "' from " + beanClass.getName(), e);}}/*** 设置属性值*/@Overridepublic void setValue(T bean, String propertyName, Object value) {if (bean == null) {return;}PropertyDescriptor descriptor = propertyDescriptors.get(propertyName);if (descriptor == null) {if (config.isIgnoreMissingProperties()) {return;}throw new IllegalArgumentException("Property '" + propertyName + "' not found in " + beanClass.getName());}Method writeMethod = descriptor.getWriteMethod();if (writeMethod == null) {if (config.isIgnoreMissingSetters()) {return;}throw new IllegalArgumentException("No setter method for property '" + propertyName + "' in " + beanClass.getName());}try {// 类型转换和反射调用 setter 方法Object convertedValue = convertValue(value, descriptor.getPropertyType());writeMethod.invoke(bean, convertedValue);} catch (IllegalAccessException | InvocationTargetException e) {throw new RuntimeException("Failed to set value for property '" + propertyName + "' in " + beanClass.getName(), e);}}/*** 获取所有属性名*/@Overridepublic Set<String> getPropertyNames() {return propertyDescriptors.keySet();}/*** 获取属性类型*/@Overridepublic Class<?> getPropertyType(String propertyName) {PropertyDescriptor descriptor = propertyDescriptors.get(propertyName);return descriptor != null ? descriptor.getPropertyType() : null;}/*** 值类型转换*/private Object convertValue(Object value, Class<?> targetType) {if (value == null) {return null;}// 如果类型匹配,直接返回if (targetType.isInstance(value)) {return value;}// 使用配置的转换器进行类型转换return config.getTypeConverter().convert(value, targetType);}
}

我写了一个便于理解的例子

public static void main(String[] args) throws Exception {BaseUser baseUser1=new BaseUser();baseUser1.setActiveFlag("1");BaseUser baseUser2=new BaseUser();baseUser2.setActiveFlag("2");List<BaseUser> list =new ArrayList<>();list.add(baseUser1);list.add(baseUser2);//原来的反射方式for(BaseUser item:list){Class cl=item.getClass();Field nameField=cl.getDeclaredField("activeFlag");nameField.setAccessible(true);System.out.println(nameField.get(item));}//修改后的反射方式Class cl =BaseUser.class;Method method= cl.getMethod("getActiveFlag");for(BaseUser item:list){System.out.println(method.invoke(item));}}
http://www.dtcms.com/a/473401.html

相关文章:

  • 【数据结构】强化训练:从基础到入门到进阶(2)
  • python异步编程 -什么是python的异步编程, 与多线程和多进程的区别
  • Linux系统--进程间通信--共享内存相关指令
  • 网站开发的实践报告石家庄市工程勘察设计咨询业协会
  • TensorFlow深度学习实战——图分类
  • SAP MM采购信息记录维护接口分享
  • 网站搭建装修风格大全2021新款简约
  • Mysql初阶第八讲:Mysql表的内外连接
  • SpringCloud 入门 - Gateway 网关与 OpenFeign 服务调用
  • uniapp 选择城市(城市列表选择)
  • AR小白入门指南:从零开始开发增强现实应用
  • 02_k8s资源清单
  • 2025年渗透测试面试题总结-109(题目+回答)
  • uniapp配置自动导入uni生命周期等方法
  • flink的Standalone-HA模式安装
  • Flink时态表关联:实现数据“时间旅行”的终极方案
  • 做哪类英文网站赚钱wordpress 页面 列表
  • nginx + spring cloud + redis + mysql + ELFK 部署
  • 【黑马点评 - 实战篇01】Redis项目实战(Windows安装Redis6.2.6 + 发送验证码 + 短信验证码登录注册 + 拦截器链 - 登录校验)
  • 汕头市通信建设管理局网站二网站手
  • FreeRTOS小记
  • 数据结构实战:顺序表全解析 - 从零实现到性能分析
  • 【C++进阶】继承上 概念及其定义 赋值兼容转换 子类默认成员函数的详解分析
  • 华为matebook16s 2022禁用触摸板和触摸屏操作
  • GridRow 和 Column 有啥区别
  • 030159网站建设与维护中国科技成就素材
  • Echarts 5.6.0 Grid 坐标系中 Y 轴可视化的优化之路
  • Java 线程池如何知道一个线程的任务已经执行完成
  • JVM字节码与类的加载(一):类的加载过程详解
  • 强军网网站建设网站需要备案才能建设吗