二狗:二胖快醒醒,赶紧看看刚才报警邮件,你上次写的保存用户接口耗时(《二胖的参数校验坎坷之路》)大大上升,赶紧排查下原因。
二胖:好的,马上看,内心戏可十足(心里却在抱怨,大中午的搅我发财美梦,刚刚梦见我买的股票又涨停了就被叫醒了)。牢骚归牢骚,自己的问题还是得看啊,毕竟是自己写的bug
,含着泪也要把它修复掉。二胖对分析这种问题还是得心应手的,毕竟已经是久经职场的老油条了。
二胖首先通过内部的监控工具看了下这段时间的网络是否正常,以及cpu
的使用情况、数据库
的耗时等,这些指标看起来都是正常的,唯一稍微有点区别的是这段时间流量上涨了一些,肯定又是公司花钱搞营销砸广告了。接着二胖又通过cat(大众点评开源监控工具)分析了几个请求,每个阶段的耗时看下来都ok
。卧槽这可咋办列居然难倒二胖了,如果生产环境问题可以在测试环境复现就好了,这样解觉问题就简单多了。生产不是流量上涨了一些吗?那测试环境来压测一把吧,二胖果断的下载了一个jmeter(压测工具)在测试环境进行了一把疯狂的压测,果然出现了和生产一样的问题。能够复现问题就好,这样离解决问题就近了一大步。
问题是复现了,接下来就是找出接口比较耗时的地方了。一般我们找接口耗时较长的地方,都是通过记录日志打印每一步的耗时。这是比较常见做法,不过二胖记得上次部门技术大拿“二狗”分享过一个神器arthas可以输出方法路径上的每个节点上耗时。苦于一直没有机会拿它来用于实际操作,今天终于可以拿它来好好练手了。安装什么的就不介绍了,这个官网都写的比较详细,并且文档也是中文的,非常容易上手。下面我们就来使用下arthas
吧。 启动成功的界面
下面我们根据arthas提供的trace
命令来看看接口的耗时都是在哪里。
我们从上面可以看出主要耗时是集中在 org.apache.commons.beanutils.BeanUtils#copyProperties
这个方法上面的,不就一个实体之间的属性赋值转换吗,需要这么耗时这么久吗?不科学啊,apache
提供的方法还能这么low
吗?带着这些问题我们看看其他提供的属性拷贝的工具类效率如何。
常见属性赋值的操作有以下几种
get
、set
方法复制。cglib
的BeanCopier
。Spring
的BeanUtils
apache
的BeanUtils
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
在这里插入图片描述
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
三个软件的使用。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。