又到周末了,周更选手申请出站~html
这图太魔性了啊java
此次分享一下上个月碰到的离奇的问题。一个简单的问题,硬是由于异常被悄咪咪吃掉,过关难度直线提高,致使小黑哥排查一个晚上。git
这个美好的晚上,本想着开两把 LOL 无限火力,在召唤师峡谷来个五杀的~github
哎,就这样没了啊!我知道,大家必定能理解这种五杀被抢的感受~apache
下次,真的,谁再让我看到悄咪咪的吃掉异常,我真的要上去一 Jio 了!框架
好了,本文可不是水文,看完本篇文章,你能够学到如下知识点:ide
好了,同窗们,打开小本子,准备记好知识点~工具
咱们有个业务系统,应用之间调用链以下所示:测试
A 应用是业务发生起始应用,在这个应用中将会根据必定规则选择最后的通信渠道 C,而后将这个渠道标识传递给 B 应用。插件
B 应用的功能相似网关,这个应用将会根据 A 应用传递过来的渠道标识,将会请求路由下发到具体的 C 应用,起到服务路由的功能。
C 应用是与外部应用交互的应用,咱们将其称为渠道通信机。
假设一次业务中,A 应用根据规则选择 C2 的渠道标识,而后传递给 B 应用。B 应用根据这个标识选择使用 C2 进行通信,最后 C2 调用外部应用完成一次业务调用。
上述全部应用都基于 Dubbo 进行远程通信,B 应用实现原理在小黑哥以前文章「支付路由系统演进史」中有写过,感兴趣的同窗能够查看一下。
介绍完业务的基本状况,如今咱们来看下到底发生了啥事。
一次业务需求中,须要改动 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 应用竟然收到了请求,而且成功处理了业务请求。
因为 B 应用异常处理时,将异常吃掉了,咱们没办法得知这个过程到底发生了啥事,因此第一要紧的事获取异常信息。
最简单的办法就是,将 B 应用改造一下,加入打印异常日志。不过当时比较懒,不想改造应用,就想获取异常信息,因而想到使用 *Arthas*[1]。
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 命令,一样观察到相同的错误信息。
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
所在依赖包。
虽然问题到此解决了,可是这里还有一个疑问,为什么 C2 应用发生了异常,却没有相关错误日志,而且 C2 业务逻辑也正常处理完成。
这就要说到 Dubbo 内部异常错误处理方式,上面 GELogger
其实做用在一个 Dubbo 自定义 Filter 中,用来记录结果,模拟代码以下:
@Activate( group = {"provider", "consumer"} ) public class ErrorFilter implements Filter { @Override public 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
返回给业务代码。
好了,说了这么多,总结一下本文知识点
NoClassDefFoundError
,类加载过程失败,也会致使 NoClassDefFoundError
。[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