Hession反序列化导致CPU占用飙高

背景

今天发布一个线上服务,暂且称之为O,发布完后,依赖O服务的2个服务C和W大量Time报警,并且这两个服务的CPU占用都飙到了40%左右,平时只有10%的样子。

这时去看O服务的监控,Time并没有升高,QPS反倒降了一半。同时C和W服务器日志中出现了大量的WARNING,信息如下:

java.lang.ClassNotFoundException: com.我是不可描述的信息.PropertyAo
Dec 02, 2016 6:24:33 PM com.alibaba.com.caucho.hessian.io.SerializerFactory getDeserializer
WARNING: Hessian/Burlap: 'com.我是不可描述的信息.PropertyAo' is an unknown class in WebappClassLoader
  context:
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@21bf4c80

短时间内找不到原因,且C服务是核心服务,找QA童鞋把O服务回滚,C和W报警恢复,CPU占用回到正常。

定位

可能见过这个WARNING的朋友已经知道了我这次发布干了啥,其实就是在API返回的模型中增加了两个自定义类型的属性,如下:

private List<PropertyAo> properties1;
private List<PropertyAo> properties2;

这两个属性W中会用到,之所以会有上面提到的WARNING,是由于我先发布了O服务,O服务中设置了这两个属性,而W服务还没有发布,这样Hession在反序列化的时候,检测到了PropertyAo不存在,所以给出了WARNING。但这与CPU飙高有关系吗? 与同事讨论了一番,他提到了Hession反序列化时会使用到反射,他之前遇到过CPU占用飙高的情况(是由于反射代码被大量调用),这点提醒了我,顺着com.alibaba.com.caucho.hessian.io.SerializerFactory getDeserializer这个方法看到了这样的实现:

try {
    Class cl = Class.forName(type, false, _loader);
    deserializer = getDeserializer(cl);
} catch (Exception e) {
    log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + _loader + ":\n" + e);
    log.log(Level.FINER, e.toString(), e);
}

可以看到Hession是通过名字去拿到Class,这里使用了反射,当反射失败时就会打出上面的warning。这时聪明的你可能想到了,即使没有失败也是在使用反射啊,继续向下看代码:

if (deserializer != null) {
    if (_cachedTypeDeserializerMap == null)
        _cachedTypeDeserializerMap = new HashMap(8);

    synchronized (_cachedTypeDeserializerMap) {
        _cachedTypeDeserializerMap.put(type, deserializer);
    }
}

反射成功就会将其cache起来,也就是说,如果反射成功,只会调用一次反射,反射失败,则每次都会执行反射。

验证

先将C升级到最新api,然后发布,再发布O服务,C表现正常,W的CPU又开始飙高,执行jstack看一下事故现场,可以看到一些线程正在执行反射,栈信息如下:

"New I/O worker #17" daemon prio=10 tid=0x00007fb1ed33b000 nid=0x63fe runnable [0x00007fb22fcfa000]
    java.lang.Thread.State: RUNNABLE
         at java.lang.Class.forName0(Native Method)
         at java.lang.Class.forName(Class.java:270)
         at com.alibaba.com.caucho.hessian.io.SerializerFactory.getDeserializer(SerializerFactory.java:500)

解决

  • 当服务端模型升级时,尤其是新增自定义类型时,尽量让所有消费端升级,但当消费端过多时,这个方案成本太高,且不友好
  • 改进SerializerFactory,把反射失败的情况也缓存,避免重复反射,已推动公司内部解决
  • 给Dubbo提了issue,不过估计不会解决

结论

Hession默认的反序列化实现满足下面2点条件时,就会导致CPU占用飙高:

  • 服务端新增了自定义类型
  • 对该服务接口的调用QPS较高,我的应用中是100+

其本质原因还是由于反射,所以开发过程中慎用反射,反射得到的信息尽量Cache,避免频繁反射。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

WCF技术剖析之二十一:WCF基本异常处理模式[中篇]

通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的...

19910
来自专栏微信公众号:Java团长

从并发编程到分布式系统——如何处理海量数据(上)

在这里想写写自己在学习并发处理的学习思路,也会聊聊自己遇到的那些坑,以此为记,希望鞭策自己不断学习、永不放弃!

611
来自专栏java思维导图

Java中高级面试题

技术文章第一时间送达! 本文作者是CyanQueen,欢迎点击阅读原文 一.基础知识: 1)集合类:List和Set比较,各自的子类比较(ArrayList,V...

3505
来自专栏一枝花算不算浪漫

[Java拾遗四]JavaWeb基础之Servlet_Request&&Response

3588
来自专栏编程

看我是如何把SQLMap里的功能移植到我的程序的

不知道各位有没有听过不要重复造轮子?因为有些开源的工具,它们经过时间和众人的捶打,其实会比我们自己一个人造出来的轮子考虑的更加周到和全面。可是有时候有些开源工具...

19510
来自专栏walterlv - 吕毅的博客

利用 ReSharper 自定义代码中的错误模式,在代码审查之前就发现并修改错误

发布于 2018-03-20 11:54 更新于 2018-03...

740
来自专栏FreeBuf

Windows内核漏洞CVE-2016-0143分析

0x00 背景 4月20日,Nils Sommer在exploitdb上爆出了一枚新的Windows内核漏洞PoC。该漏洞影响所有版本的Windows操作系统,...

2076
来自专栏华章科技

干货:手把手教你用Python读写CSV、JSON、Excel及解析HTML

导读:本文要介绍的这些技法,会用Python读入各种格式的数据,并存入关系数据库或NoSQL数据库。

922
来自专栏郭霖

Android数据库高手秘籍(五)——LitePal的存储操作

经过前面几篇文章的学习,我们已经把LitePal的表管理模块的功能都很好地掌握了,相信大家都已经体会到了使用LitePal来创建表、升级表、以及建立表关联所带来...

2959
来自专栏分布式系统和大数据处理

.Net 项目代码风格参考

代码风格没有正确与否,重要的是整齐划一,这是我拟的一份《.Net 项目代码风格参考》,供大家参考。

1282

扫码关注云+社区