关注“Java有道”,星标或置顶,

更多干货等你发现!

转自程序通事

这次分享一下上个月碰到的离奇的问题。一个简单的问题,硬是因为异常被悄咪咪吃掉,过关难度直线提升,导致小黑哥排查一个晚上。这个美好的晚上,本想着开两把 LOL 无限火力,在召唤师峡谷来个五杀的~哎,就这样没了啊!我知道,你们一定能理解这种五杀被抢的感觉~下次,真的,谁再让我看到悄咪咪的吃掉异常,我真的要上去一 Jio 了!


好了,本文可不是水文,看完本篇文章,你可以学到以下知识点:

  • Arthas 排查技巧
  • 啥是 NoClassDefFoundError
  • Dubbo 异常内部处理方式

好了,同学们,打开小本子,准备记好知识点~



1
起因

我们有个业务系统,应用之间调用链如下所示:


A 应用是业务发生起始应用,在这个应用中将会根据一定规则选择最后的通讯渠道 C,然后将这个渠道标识传递给 B 应用。B 应用的功能类似网关,这个应用将会根据 A 应用传递过来的渠道标识,将会请求路由下发到具体的 C 应用,起到服务路由的功能。C 应用是与外部应用交互的应用,我们将其称为渠道通讯机。假设一次业务中,A 应用根据规则选择 C2 的渠道标识,然后传递给 B 应用。B 应用根据这个标识选择使用 C2 进行通讯,最后 C2 调用外部应用完成一次业务调用。介绍完业务的基本情况,现在我们来看下到底发生了啥事。一次业务需求中,需要改动 C2 应用,这次改动功能点真的很小,很快就完成了。小黑哥想着闲着也是闲着,于是就把之前 C2 应用中打印的日志中一些没有脱敏的信息,进行脱敏处理。由于之前日志框架脱敏处理存在一些问题,于是就将日志框架从 Log4j 升级为 LogBack。升级之后,为了防止不同日志框架中之间的产生冲突,于是使用 IDEA Maven Helper 插件,统一将应用中所有的 Log4j 相关依赖都给排除了。改动完成之后,将 C2 应用发布到测试环境,再次从 A 应用发起测试, B 应用返回异常提示未找到 C2 应用。B 应用业务代码类似如下:

public Response pay(Request req) {try {if (!isSupport(req.getChnlCode())) {return new Response("ERROR", "未找到相关渠道应用");        }return doPay(req);    } catch (Exception e) {return new Response("ERROR", "未找到相关渠道应用");    }}

正常情况下,若是配置存在问题,B 应用将会返回未找到具体渠道,请求也会在 B 应用结束,不会调用到 C2 应用(也没办法调用)。然而此次配置什么都没问题, 而且最诡异的是 C2 应用居然收到了请求,并且成功处理了业务请求。2排查问题由于 B 应用异常处理时,将异常吃掉了,我们没办法得知这个过程到底发生了啥事,所以第一要紧的事获取异常信息。最简单的办法就是,将 B 应用改造一下,加入打印异常日志。不过当时比较懒,不想改造应用,就想获取异常信息,于是想到使用 **Arthas**[1]

Arthas 排错技巧

Arthas 是Alibaba开源的Java诊断工具,这里就不再详细介绍这个工具,主要讲下这次排错用到的命令-**watch**[2]watch 命令可以方便观察指定方的调用情况,可以具体观察方法的返回值、抛出异常、入参,另外还可以通过 OGNL表达式查看对应的变量。这里我们主要为了查看方法抛出的异常信息,执行命令如下:

watch com.dubbo.example.DemoService doPay -e -x 2 '{params,throwExp}'

上述命令将会在方法异常之后观察方法的入参以及异常信息。注意,我们需要查看doPay方法,而不是pay方法。这是因为pay方法中我们将异常捕获,不太可能会抛出异常哦~异常信息如下所示:


真正引起此次错误的异常信息为:

java.lang.NoClassDefFoundError: Could not initialize class xx.xxx.xx.GELogger

由于此次 B 应用不存在改动,所以推测这个异常实际发生在 C2 应用,于是在 C2 应用处再次使用 Arthas watch 命令,同样观察到相同的错误信息。

NoClassDefFoundError

NoClassDefFound,从名字上我们可以推测是因为类不存在,从而引发的这个错误。按照这个思路,我们首先可以简单查看一下 B 应用中是否存在 GELogger 相关类。查看 B 应用相关依赖包,从中发现了这个类文件,这说明这个类确实存在。在 IDEA 反编译查看 GELogger类相关源码,从中发现了问题。

private static Logger logger;static {    System.out.println("static init");    logger = Logger.getLogger(NoClassDefFoundErrorTestService.class);    System.out.println("Logger init success");}

GELogger存在一个静态代码块,用于初始化一个 org.apache.log4j.Logger日志类。然后在上面改动中,全部的 Log4j依赖都被排除了,所以这里运行时应该会抛出另外一个找到 org.apache.log4j.Logger 错误。执行以下代码,模拟抛错过程。

System.out.println("模拟第一次 Error");try {    NoClassDefFoundErrorTestService noClassDefFoundErrorTestService=new NoClassDefFoundErrorTestService();} catch (Throwable e) {    e.printStackTrace();}System.out.println("模拟第二次 Error");try {    NoClassDefFoundErrorTestService noClassDefFoundErrorTestService=new NoClassDefFoundErrorTestService();} catch (Throwable e) {    e.printStackTrace();}

异常信息如下所示:

异常信息

第一次创建 NoClassDefFoundErrorTestService实例时,Java 虚拟机读取加载时,将会初始化静态代码块时。由于 org.apache.log4j.Logger类不存在,静态代码块执行异常,从而导致类加载失败。第二次再创建 NoClassDefFoundErrorTestService 实例时,Java 虚拟机不会再次读取加载,所以直接返回了以下异常。

java.lang.NoClassDefFoundError: Could not initialize class com.dubbo.example.NoClassDefFoundErrorTestService

找到问题真正原因,解决办法也很简单,直接排除 GELogger 所在依赖包。3Dubbo 内部异常处理虽然问题到此解决了,但是这里还有一个疑问,为何 C2 应用发生了异常,却没有相关错误日志,并且 C2 业务逻辑也正常处理完成。这就要说到 Dubbo 内部异常错误处理方式,上面 GELogger 其实作用在一个 Dubbo  自定义 Filter 中,用来记录结果,模拟代码如下:

@Activate(        group = {"provider", "consumer"})public class ErrorFilter implements Filter {@Overridepublic Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {        Result result = invoker.invoke(invocation);        NoClassDefFoundErrorTestService noClassDefFoundErrorTestService=new NoClassDefFoundErrorTestService();// 处理业务逻辑return result;    }}

这个自定义 Filter 中首先执行 invoker 方法,这个方法将会调用真正的业务方法,这就是为什么 C2 应用逻辑是正常处理完成。业务方法处理完成之后,然后执行后续逻辑。由于 NoClassDefFoundErrorTestService将会抛出 Error,最终这个 Error,将会在 HeaderExchangeHandler#handleRequest 被捕获,然后将会把相关异常信息返回给调用 Dubbo 消费者。

Dubbo 2.7

而在 Dubbo 消费者接受到服务提供者返回信息之后,将会在 DefaultFuture#doReceived转化成 RemotingException

dubbo consumer 2.7

RemotingException 最终将会在 FailoverClusterInvoker#doInvoke 转换成 RpcException返回给业务代码。



4
总结

好了,说了这么多,总结一下本文知识点

  1. 异常捕获之后,一定要记得打印日志,并且要记得输出堆栈信息
  2. 运行时类不存在,将会导致 NoClassDefFoundError,类加载过程失败,也会导致 NoClassDefFoundError
  3. 对外提供的二方包,最好不要依赖特定日志框架,如 Log4j,Logback 等,应该使用 Slf4j 框架。

参考资料

[1]

Arthas: https://alibaba.github.io/arthas/index.html

[2]

watch: https://alibaba.github.io/arthas/watch.html

[3]

当Dubbo遇上Arthas:排查问题的实践: https://dubbo.apache.org/zh-cn/blog/dubbo-meet-arthas.html

[4]

java.lang.NoClassDefFoundError 的解决方法一例: https://www.codelast.com/原创-java-lang-noclassdeffounderror-的解决方法一例/

[5]

noclassdeffounderror-could-not-initialize-class-error: https://stackoverflow.com/questions/1401111/noclassdeffounderror-could-not-initialize-class-error

推荐阅读

• 互联网大厂的后端技术栈• 面试官问:List如何一边遍历,一边删除?• 面试官:BigDecimal一定不会丢失精度吗?•  再见,Lombok,没想到你才是让我们代码处于了“亚健康”状态的真正元凶!

slf4j打印未捕获异常信息_谁再悄咪咪的吃掉异常,我上去就是一 JIO相关推荐

  1. java打印对象头信息_打印Java对象头

    打印Java对象头 对象头形式 JVM中对象头的方式有以下两种(以32位JVM为例)普通对象|----------------------------------------------------- ...

  2. c++ 外部组件发生异常_谁再悄咪咪的吃掉异常,我上去就是一 JIO

    又到周末了,周更选手申请出站~ 这图太魔性了啊 这次分享一下上个月碰到的离奇的问题.一个简单的问题,硬是因为异常被悄咪咪吃掉,过关难度直线提升,导致小黑哥排查一个晚上. 这个美好的晚上,本想着开两把 ...

  3. python自定义抛出异常信息_浅谈python抛出异常、自定义异常, 传递异常

    一. 抛出异常 Python用异常对象(exception object)表示异常情况,遇到错误后,会引发异常.如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终 ...

  4. java 堆栈信息_每天学习一个命令:jstack 打印 Java 进程堆栈信息

    Jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息. 这里需要注意的是 Java 8 引入了 Java Mission Control, ...

  5. 准考证打印系统关闭怎么办_准考证打印系统关闭怎么办_广东省人力资源和社会保障厅网站...

    准考证打印系统关闭怎么办_广东省人力资源和社会保障厅网站,更多关于广东事业单位考试准考证打印,事业单位准考证打印的内容,请关注广东事业单位考试网/广东人事考试网! 本次公开招聘考试采取"笔试 ...

  6. 优化器统计信息_高水位_柱状图等

    本文章为网络笔记,看了warehouse老师的视频受益匪浅,更是感觉自己技术太过初级,特写了本笔记,方便以后反复学习! 如有任何不妥,请发邮件至102448567@qq.com删除文章! 关于ware ...

  7. uniapp同步获取用户信息_微信小程序云开发教程微信小程序的API入门获取用户身份信息系列API...

    同学们大家好,我是小伊同学,上一节我们介绍了一些常用API,今天我们接着来学习一组API,那就是获取用户身份信息的API. 在微信小程序中,我们往往需要获取用户的身份信息,比如昵称.头像.性别.地区等 ...

  8. cx_oracle写日志信息_日志系统的设计

    笔者在写作本章节的时候,并不敢把此章节的标题叫做<高性能日志系统的设计>,之所以不敢加上"高性能"三个字的原因是: 第一,我对于日志系统设计知识和经验都来自于学习和工作 ...

  9. Python程序异常处理:try、except、else、finally,捕获指定异常类型、捕获多个异常类型、捕获所有异常类型、捕获异常信息、异常的传递、raise抛出自定义异常

    输入与预期不匹配,触发异常,程序退出: 一.异常处理:使用try.except进行错误处理 为了保证程序运行的稳定性,错误应该被程序捕捉并合理控制 Python使用保留字try和except进行异常处 ...

  10. python读取日志错误信息_使用Python将Exception异常错误堆栈信息写入日志文件

    假设需要把发生异常错误的信息写入到log.txt日志文件中去: import traceback import logging logging.basicConfig(filename='log.tx ...

最新文章

  1. 顶尖程序员不同于常人的 5 个区别
  2. ubuntu 10.04 顶部任务栏消失!!
  3. Java Web实现分页查询
  4. c++中虚函数和纯虚函数定义
  5. 字符变量赋值规则_C#的变量、运算符
  6. sqlserver binary varbinary image 的区别
  7. SAP链接外部数据库的实现方法
  8. 计算机复试面试题总结
  9. 解决request-html chromium下载失败原因
  10. KVM/QEMU虚拟机申请和释放内存的步骤
  11. matlab设置固定的窗宽窗位,如何设定窗宽窗位,附正常人体组织CT值
  12. 蓝桥杯——鲁卡斯队列
  13. 红帽牵手阿里云,水到渠成的合作
  14. “三天打鱼,两天晒网”问题
  15. 设计一个学生学籍管理系统
  16. 我叫mt4 服务器维护,我叫MT48月22日停机维护公告 我叫MT4最新维护内容一览-游侠手游...
  17. SQLZOO练习答案(一):SELECT names/zh
  18. curl -O 下载文件 curl -o 下载文件并重命名
  19. 【雷达通信】基于matlab大规模MIMO三维信道【含Matlab源码 2105期】
  20. 墙都不扶就服你!javaredisson分布式锁

热门文章

  1. IDEA Maven Mybatis generator 自动生成代码
  2. ZBrush中Tool工具的保存
  3. Bill Gates 2007年哈佛演讲(中/英文)
  4. 怎么step into MFC Source code
  5. 名词解释:什么是RSS? [转贴]
  6. OpenCV问题集锦,图片显示不出来,WaitKey(0),imread()不能读图片,未经处理的异常,等问题集合
  7. ArcGIS学习总结(四)——缓冲区分析应用
  8. EXCEL 分列功能的使用
  9. js实现全排列组合算法
  10. 实习成长之路——SpringBean一:BeanDefinition元信息有什么?除了Bean名称和类型,还有那些Bean的元信息值得关注?