项目中使用了这个属性赋值方法,接口耗时提升了几十毫秒

简介: 使用了这个属性赋值方法,接口耗时提升了几十毫秒

引言

二狗:二胖快醒醒,赶紧看看刚才报警邮件,你上次写的保存用户接口耗时(《二胖的参数校验坎坷之路》)大大上升,赶紧排查下原因。
二胖:好的,马上看,内心戏可十足(心里却在抱怨,大中午的搅我发财美梦,刚刚梦见我买的股票又涨停了就被叫醒了)。牢骚归牢骚,自己的问题还是得看啊,毕竟是自己写的bug,含着泪也要把它修复掉。二胖对分析这种问题还是得心应手的,毕竟已经是久经职场的老油条了。

测试环境复现问题

二胖首先通过内部的监控工具看了下这段时间的网络是否正常,以及cpu的使用情况、数据库的耗时等,这些指标看起来都是正常的,唯一稍微有点区别的是这段时间流量上涨了一些,肯定又是公司花钱搞营销砸广告了。接着二胖又通过cat(大众点评开源监控工具)分析了几个请求,每个阶段的耗时看下来都ok。卧槽这可咋办列居然难倒二胖了,如果生产环境问题可以在测试环境复现就好了,这样解觉问题就简单多了。生产不是流量上涨了一些吗?那测试环境来压测一把吧,二胖果断的下载了一个jmeter(压测工具)在测试环境进行了一把疯狂的压测,果然出现了和生产一样的问题。能够复现问题就好,这样离解决问题就近了一大步。

arthas定位问题

问题是复现了,接下来就是找出接口比较耗时的地方了。一般我们找接口耗时较长的地方,都是通过记录日志打印每一步的耗时。这是比较常见做法,不过二胖记得上次部门技术大拿“二狗”分享过一个神器arthas可以输出方法路径上的每个节点上耗时。苦于一直没有机会拿它来用于实际操作,今天终于可以拿它来好好练手了。安装什么的就不介绍了,这个官网都写的比较详细,并且文档也是中文的,非常容易上手。下面我们就来使用下arthas吧。
启动成功的界面
在这里插入图片描述
下面我们根据arthas提供的trace命令来看看接口的耗时都是在哪里。
在这里插入图片描述我们从上面可以看出主要耗时是集中在 org.apache.commons.beanutils.BeanUtils#copyProperties这个方法上面的,不就一个实体之间的属性赋值转换吗,需要这么耗时这么久吗?不科学啊,apache提供的方法还能这么low吗?带着这些问题我们看看其他提供的属性拷贝的工具类效率如何。

使用JMH对常见属性赋值操作性能比较

  • 使用getset方法复制。
  • cglibBeanCopier
  • SpringBeanUtils
  • apacheBeanUtils
  • MapStruct

下面我们就来对上面这些操作来进行一波性能比较。
编写下面的测试类。

/**
 * @author:
 * @Date: 2020/7/11
 * @Description:
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(6)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class BeanCopyTest {
    @Param(value = {"1","10","100"})
    private int count;

    public UserBO bo;

    public  BeanCopier copier;

    @Setup(Level.Trial) // 初始化方法,在全部Benchmark运行之前进行
    public void init() {
        copier = BeanCopier.create(UserBO.class, UserVO.class, false);
        bo = new UserBO();
        bo.setUserName("java金融");
        bo.setAge(1);
        bo.setIdCard("88888888");
        bo.setEmail("java金融@qq.com");
    }


    public static void main(String[] args) throws RunnerException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
       Options opt = new OptionsBuilder().include(BeanCopyTest.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build();
        new Runner(opt).run();

    }

    /**
     * 使用mapStruct来操作
     */
    @Benchmark
    public void mapStruct() {
        for (int i = 1; i <= count; i++) {
            UserVO vo = UserMapping.INSTANCE.converter(bo);
        }
    }

    /**
     * 手动set和Get
     */
    @Benchmark
    public void setAndGet() {
        for (int i = 1; i <= count; i++) {
            UserVO userVO = new UserVO();
            userVO.setUserName(bo.getUserName());
            userVO.setEmail(bo.getEmail());
            userVO.setSex(bo.getSex());
            userVO.setIdCard(bo.getIdCard());
            userVO.setAge(bo.getAge());
        }
    }

    /**
     * 使用cglib的copy方法
     */
    @Benchmark
    public void cglibBeanCopier() {
        for (int i = 1; i <= count; i++) {
            UserVO vo = new UserVO();
            copier.copy(bo, vo, null);
        }
    }

    /**
     * 使用spring提供的copyProperties方法
     */
    @Benchmark
    public void springBeanUtils() {
        for (int i = 1; i <= count; i++) {
            UserVO vo = new UserVO();
            BeanUtils.copyProperties(bo, vo);
        }
    }

    /**
     * 使用apache的copyProperties方法
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Benchmark
    public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException {
        for (int i = 1; i <= count; i++) {
            UserVO vo = new UserVO();
            org.apache.commons.beanutils.BeanUtils.copyProperties(vo, bo);
        }
    }

最后的测试结果如下所示:

Benchmark                     (count)  Mode  Cnt          Score          Error  Units
BeanCopyTest.apacheBeanUtils        1  avgt    5    2462103.419 ±  2292830.495  ns/op
BeanCopyTest.apacheBeanUtils       10  avgt    5   21025926.689 ± 11254755.603  ns/op
BeanCopyTest.apacheBeanUtils      100  avgt    5  193235312.113 ± 37929707.246  ns/op
BeanCopyTest.cglibBeanCopier        1  avgt    5          4.936 ±        1.187  ns/op
BeanCopyTest.cglibBeanCopier       10  avgt    5          4.820 ±        1.963  ns/op
BeanCopyTest.cglibBeanCopier      100  avgt    5          4.269 ±        0.890  ns/op
BeanCopyTest.mapStruct              1  avgt    5          4.809 ±        1.720  ns/op
BeanCopyTest.mapStruct             10  avgt    5          4.947 ±        1.320  ns/op
BeanCopyTest.mapStruct            100  avgt    5          4.440 ±        1.191  ns/op
BeanCopyTest.setAndGet              1  avgt    5          3.780 ±        1.785  ns/op
BeanCopyTest.setAndGet             10  avgt    5          3.930 ±        1.788  ns/op
BeanCopyTest.setAndGet            100  avgt    5          4.069 ±        2.181  ns/op
BeanCopyTest.springBeanUtils        1  avgt    5       1190.563 ±      165.574  ns/op
BeanCopyTest.springBeanUtils       10  avgt    5      10887.244 ±     1228.026  ns/op
BeanCopyTest.springBeanUtils      100  avgt    5     109686.562 ±     7485.261  ns/op

在这里插入图片描述

  • 从上述结论中我们可以发现性能最好的是排名 用getset方法复制,其次是mapStructcglib的BeanCopier,再接着是Spring的beanUtils,最后的是apache的BeanUtils
  • 如果对上述测试性能感兴趣的话,代码都已上传到github上可自行下载运行对比下结果。
  • 关于对JMH的使用就不介绍了,感兴趣的可自行谷歌。不过如果要进行性能比较的话,真心推荐使用下,结果可以通过导出json文件然后生成图表。

为什么apacheBeanUtils性能最差

apacheBeanUtilsspringbeanUtils都是底层都是使用反射来进行赋值的,为什么apacheBeanUtils的性能要差一大截列。源码之下无秘密,下面我们来看看这个方法的源码。
在这里插入图片描述
Apache BeanUtils 打印了大量的日志、以及各种转换、类型的判断等等导致性能变差。

  • springbeanUtil直接使用反射省,干净利索,核心代码见下图。

在这里插入图片描述

  • 其实在《阿里巴巴开发手册》(可在公众号【java金融】回复“泰山”获取)里面也有说明属性的copy避免使用apcheBeanUtils

在这里插入图片描述

  • 如果生产环境已经大量使用Apache BeanUtils的话需要替换spring BeanUtils的话需要注意下他们两个虽然提供的方法都是copyProperties但是他们的参数是反的,这点需要注意下,不要直接换个引入的包名完事。

总结

  • 实际使用中的话一般是不会使用getset方法复制,容易漏掉属性并且也是一个体力活。推荐使用mapStruct,在编译过程中,MapStruct将生成该接口的实现,并且它还可以实现不同名字的映射,比如可以把name映射到username,灵活性比较高。
  • 二胖感觉今天收获满满啊,一下学到了jmeterarthasJMH三个软件的使用。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
目录
相关文章
|
3月前
|
运维 Serverless 网络安全
函数计算产品使用问题之函数运行时间大于24小时该如何解决
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
存储 Java Serverless
函数计算产品使用问题之执行一个比较耗时的操作导致请求超时时,该怎么办
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
5月前
|
运维 Java Serverless
函数计算产品使用问题之是否会受执行超时时间的限制
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
6月前
|
存储 缓存 前端开发
JavaScrip实现一个有时间限制的缓存类
JavaScrip实现一个有时间限制的缓存类
42 0
|
监控 Cloud Native Java
字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》
在前面的ASM、Javassist 章节中也有陆续实现过获取方法的出入参信息,但实现的方式还是偏向于字节码控制,尤其ASM,更是需要使用到字节码指令将入参信息压栈操作保存到局部变量用于输出,在这个过程中需要深入了解Java虚拟机规范,否则很不好完成这一项的开发。但!ASM也是性能最牛的。其他的字节码编程框架都是基于它所开发的。
700 0
字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》
|
JSON JavaScript 前端开发
javascript对象数据合并实现补齐24小时时间段同时赋值0的解决方案
javascript对象数据合并实现补齐24小时时间段同时赋值0的解决方案
124 0
|
开发者
计算代码的执行时长 | 学习笔记
快速学习计算代码的执行时长,介绍了计算代码的执行时长系统机制, 以及在实际应用过程中如何使用。
|
C++ Windows
c++计算代码执行时间的方法,毫秒级
方法一、 #include#includeusing namespace std;class CTimer{public:CTimer(){_start=clock();}~CTimer(){_end=clock();cout
1144 0
C++中尽可能延迟变量定义的时间
C++中尽可能延迟变量定义的时间
141 0