引言

二狗:二胖快醒醒,赶紧看看刚才报警邮件,你上次写的保存用户接口耗时(《二胖的参数校验坎坷之路》)大大上升,赶紧排查下原因。
二胖:好的,马上看,内心戏可十足(心里却在抱怨,大中午的搅我发财美梦,刚刚梦见我买的股票又涨停了就被叫醒了)。牢骚归牢骚,自己的问题还是得看啊,毕竟是自己写的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三个软件的使用。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。

记一次Apache的代码导致生产服务耗时增加的更多相关文章

  1. 阿里短信回持.net sdk的bug导致生产服务cpu 100%排查

    一:背景 1. 讲故事 去年阿里聚石塔上的所有isv短信通道全部对接阿里通信,我们就做了对接改造,使用阿里提供的.net sdk. 网址:https://help.aliyun.com/documen ...

  2. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  3. 记一次因证书问题导致请求失败问题SSLHandshakeException

    记一次因证书问题导致请求失败问题SSLHandshakeException 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10989813.html 最近接一外 ...

  4. Myeclipse版本引发的css样式问题:头部自动生成一行代码导致样式引入不成功

    在运行新的项目之后,发现样式全部没了 利用开发者模式查看原因:是因为css样式文件的顶部自动生成了一行代码导致的 生成的代码:[genuitec-file-id="wc2-7"], ...

  5. MyEclipse格式化JSP代码导致Java表达式<%= %>自动换行的解决办法

    MyEclipse格式化JSP代码导致Java表达式<%= %>自动换行的解决办法: 可以将Java表达式<%= %>换成EL表达式.

  6. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  7. 记-ItextPDF+freemaker 生成PDF文件---导致服务宕机

    摘要:已经上线的项目,出现服务挂掉的情况. 介绍:该服务是专门做打印的,业务需求是生成PDF文件进行页面预览,主要是使用ItextPDF+freemaker技术生成一系列PDF文件,其中生成流程有:解 ...

  8. 基于 Apache APISIX 的下一代微服务架构

    2019 年 12 月 14 日,又拍云联合 Apache APISIX 社区举办 API 网关与高性能服务最佳实践丨Open Talk 广州站活动,Apache APISIX PPMC 温铭做了题为 ...

  9. 生产服务GC调优实践基本流程总结

    Photo by Pixabay from Pexels 本文作者:夜色微光 - 博客园 (cnblogs.com) 前言 对Java虚拟机进行性能调优是一个非常宽泛的话题,在实践上也是非常棘手的过程 ...

随机推荐

  1. V-指令,细节补充

    v-for遍历对象的时候 v-for=" (item) in person" 这里item是person对象里面的value值 而v-for=" (item,key) i ...

  2. MIT-6.006算法导论(2011秋)

    L01 Algorithmic Thinking,Peak Finding 算法定义:高效处理大量数据的程序 在学本课之前最好先学习6.042,本课进阶为6.046 本门课的8个主要章节:算法思想.排 ...

  3. AtomicInteger的addAndGet(int delta)与getAndAdd(int delta)有什么区别?

    结论:区别仅在于返回的结果,修改的值是相同的,但是返回的值不同. 看一下源码注释 1 /** 2 * Atomically adds the given value to the current va ...

  4. Python函数独立星号(*)分隔的命名关键字参数

    如果需要限制关键字参数的输入名字,就需要使用到命名关键字参数的形式,所谓命名关键字参数就是给关键字参数限定指定的名字,输入其他名字不能识别.命名关键字参数和位置参数之间使用独立的星号(*)分隔,星号后 ...

  5. PyQt(Python+Qt)学习随笔:QTreeView树形视图的autoExpandDelay属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTreeView树形视图的autoExpandDelay属性用于控制在视图中拖拽时,当鼠标停留在一 ...

  6. PyQt学习随笔:QStandardItemModel使用注意事项

    老猿Python博文目录 老猿Python博客地址 在使用QStandardItemModel或其派生类作为view对象的数据存储时,有如下几点需要注意: 1.如果是多行多列的数据存储,对应视图如果没 ...

  7. PyQt(Python+Qt)学习随笔:QAbstractItemView的autoScroll和autoScrollMargin属性

    老猿Python博文目录 老猿Python博客地址 QAbstractItemView的autoScroll属性用于确认鼠标在视口边缘时是否自动滚动内容,默认值为True,autoScrollMarg ...

  8. Docker 基本概念(三)-生命周期详解(镜像、容器、仓库)

    Docker三大组件:镜像.容器.仓库.  一.镜像 1 从仓库获取镜像 #一.从仓库获取镜像,帮助命令:docker pull -help 命令:docker pull [选项] [docker R ...

  9. 如何在苹果电脑上创建一个html格式文件,并在浏览器正确打开

    之前一直使用windows系统的电脑,创建文件很简单,改格式也非常的简单.但换了苹果电脑,如何创建一个HTML文件?却把我给整蒙了. 首先,为什么mac上不能直接新建文本文件? 因为mac一都是以应用 ...

  10. 【Alpha冲刺阶段】Scrum Meeting Daily5

    [Alpha冲刺阶段]Scrum Meeting Daily5 1.会议简述 会议开展时间 2020/5/27 8:30-9:00 PM 会议基本内容摘要 大家讲述了自己的任务完成情况以及遇到的问题 ...