二狗:二胖快醒醒,赶忙看看刚才报警邮件,你上次写的保存用户接口耗时(《二胖的参数校验坎坷之路》)大大上升,赶忙排查下缘由。
二胖:好的,立刻看,心里戏可十足(内心却在抱怨,大中午的搅我发财好梦,刚刚梦见我买的股票又涨停了就被叫醒了)。牢骚归牢骚,本身的问题仍是得看啊,毕竟是本身写的bug
,含着泪也要把它修复掉。二胖对分析这种问题仍是驾轻就熟的,毕竟已是久经职场的老油条了。html
二胖首先经过内部的监控工具看了下这段时间的网络是否正常,以及cpu
的使用状况、数据库
的耗时等,这些指标看起来都是正常的,惟一稍微有点区别的是这段时间流量上涨了一些,确定又是公司花钱搞营销砸广告了。接着二胖又经过cat(大众点评开源监控工具)分析了几个请求,每一个阶段的耗时看下来都ok
。卧槽这可咋办列竟然难倒二胖了,若是生产环境问题能够在测试环境复现就行了,这样解觉问题就简单多了。生产不是流量上涨了一些吗?那测试环境来压测一把吧,二胖果断的下载了一个jmeter(压测工具)在测试环境进行了一把疯狂的压测,果真出现了和生产同样的问题。可以复现问题就好,这样离解决问题就近了一大步。java
问题是复现了,接下来就是找出接口比较耗时的地方了。通常咱们找接口耗时较长的地方,都是经过记录日志打印每一步的耗时。这是比较常见作法,不过二胖记得上次部门技术大拿“二狗”分享过一个神器arthas能够输出方法路径上的每一个节点上耗时。苦于一直没有机会拿它来用于实际操做,今天终于能够拿它来好好练手了。安装什么的就不介绍了,这个官网都写的比较详细,而且文档也是中文的,很是容易上手。下面咱们就来使用下arthas
吧。
启动成功的界面
下面咱们根据arthas提供的trace
命令来看看接口的耗时都是在哪里。咱们从上面能够看出主要耗时是集中在
org.apache.commons.beanutils.BeanUtils#copyProperties
这个方法上面的,不就一个实体之间的属性赋值转换吗,须要这么耗时这么久吗?不科学啊,apache
提供的方法还能这么low
吗?带着这些问题咱们看看其余提供的属性拷贝的工具类效率如何。git
get
、set
方法复制。cglib
的BeanCopier
。Spring
的BeanUtils
apache
的BeanUtils
MapStruct
下面咱们就来对上面这些操做来进行一波性能比较。
编写下面的测试类。github
/** * @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); } }
最后的测试结果以下所示:spring
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
get
、set
方法复制,其次是mapStruct
和cglib的BeanCopier
,再接着是Spring的beanUtils
,最后的是apache的BeanUtils
。github
上可自行下载运行对比下结果。代码地址 JMH
的使用就不介绍了,感兴趣的可自行谷歌。不过若是要进行性能比较的话,真心推荐使用下,结果能够经过导出json
文件而后生成图表。apacheBeanUtils
和spring
的beanUtils
都是底层都是使用反射来进行赋值的,为何apacheBeanUtils
的性能要差一大截列。源码之下无秘密,下面咱们来看看这个方法的源码。Apache BeanUtils
打印了大量的日志、以及各类转换、类型的判断等等致使性能变差。数据库
spring
的beanUtil
直接使用反射省,干净利索,核心代码见下图。copy
避免使用apcheBeanUtils
Apache BeanUtils
的话须要替换spring BeanUtils
的话须要注意下他们两个虽然提供的方法都是copyProperties
可是他们的参数是反的,这点须要注意下,不要直接换个引入的包名完事。get
和set
方法复制,容易漏掉属性而且也是一个体力活。推荐使用mapStruct
,在编译过程当中,MapStruct
将生成该接口的实现,而且它还能够实现不一样名字的映射,好比能够把name
映射到username
,灵活性比较高。jmeter
、arthas
、JMH
三个软件的使用。