三歪最近每天都很忙,一直在整合系统,期间遇到了不少奇奇怪怪的问题。今天想分享一个Bug,如果后面你遇到了希望你能想起这篇文章。
Bug不会无缘无故出现,修复一个又一个Bug往往不是凭借着运气。?
看到我这篇文章的读者,其中也不乏多年开发经验的老兵,如果你有一些不错的修Bug技巧或者心得或者工具,希望可以在评论区不吝分享?
背景
一个系统会有多个工程,一个工程会有多个依赖,工程与工程之间的依赖很容易就冲突。
如果有做过系统整合的同学会发现,每天可能会花很长的时间排除依赖。
举个很简单的例子:现在工程上有两个依赖,但是两个依赖的版本不一样。在编译的时候没有任何问题,但是启动的时候就会有各种稀奇古怪的错误。
这些稀奇古怪的错误往往不能指引你去立马发现这是依赖冲突导致的问题,但是有经验的人会想到:「这绝壁是依赖冲突了,我的代码不可能有问题!!」?
在某年某月某日,我系统已经启动起来了,在上线之前肯定要自测一下各个功能有无问题。测试到短信下发渠道的时候,发现出错了。
众所周知,下发一条短信是需要知道用户的手机号的。如果业务方传给我的是一个站内的userId,那我需要将userId转成手机号。
顺便来简单科普一下这里的技术设计:
- 如果用户绑定了手机号,那么在绑定后会触发一条
消息。我这边会监听到这条消息,然后将这条消息清洗/处理,完了之后写到引擎中。topic
- 提供一个对外暴露引擎Client客户端使用(传入userId,查到userId对应的手机号)
对外暴露api层给业务方使用(封装了引擎的客户端)
我们想要对Id进行转换,那么就引入
searchClient
的依赖,然后调他封装好的方法,就OK了。
分享遇到的Bug
于是我把
searchClient
引入到项目里边,项目正常启动起来。我在验证功能是否正常的时候,重复报了一个错误:
java.lang.NoClassDefFoundError: Could not initialize class xxx
复制
一次印象:我见到这个错误的时候,会觉得:我的依赖包没引入进去吧?怎么没找到呢
于是我就去确认我的包到底有没有引入进去,经过一大轮验证,我的依赖包是的的确确是引入了进去啊。那怎么会报
NoClassDefFoundError
的呢。
于是我就怀疑,是不是我引入的依赖包跟原有的工程依赖冲突呢?于是我就去对比版本,也发现好像没什么问题啊。searchClient依赖的版本都一样的,Zookeeper的版本也是一样的,怎么还是报
NoClassDefFoundError
呢。
检查maven版本,我一般是先用maven的插件”Maven Helper“在当前的工程下去看看有没有类似的包冲突了,如果有类似的包冲突了那直接在插件上Exclude就好了。
如果发现Maven Helper 不好使,我就会用
mvn dependency:tree
去看看项目里有没有版本不一样的依赖,效果如下:
但走到一步,貌似没找到相关的依赖有啥问题(毕竟也不能瞎排依赖)。于是就只能去DeBug看源码,看源码的时候发现出错是在创建单例对象的时候。
代码里边用了静态内部类的方式去创建对象,一直报的错就是没找到这个内部类。那也挺符合
NoClassDefFoundError
这个报错信息的。
于是我就去Google也搜了一下,搜到了相关的博客:
仔细一看,发现说得真的很有道理。跟我的错误长得一模一样,我终于找到了......
这个博主好像也没怎么说解决啊?那怎么把内部类的class文件加载到对应的目录呢??折腾了半天,把依赖包的版本也升级了一把,貌似也没啥用啊。。
于是我陷入了沉思,这就为啥报
NoClassDefFoundError
呢,不对劲啊,我依赖明明是有的,依赖包好像也没冲突啊。
如果你直接以去搜是很难搜到你想要的信息,但是以
NoClassDefFoundError
的关键字去搜,检索出来的信息就不一样了。
NoClassDefFoundError 静态内部类
我就继续Google,又学习到了另一个问题:单例对象以静态内部类的方式实现,如果在创建对象的时候出了问题,那么报的就是
NoClassDefFoundError
。
比如以下的代码(分别在main方法获取了三次单例对象,但是在创建对象的时候失败了):
/**
* 作者:Java3y
*/
public class SingleTon {
private SingleTon() {
int i = 1 / 0;
}
private static class Holder {
private static final SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance() {
return Holder.INSTANCE;
}
public static void main(String[] args) {
try {
System.out.println("First");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
try {
System.out.println("Second");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
try {
System.out.println("Three");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
复制
报错信息:
看到这里,我在怀疑:是不是我没有好好看第一次的调用失败信息,其实在创建单例的时候内部有出错了呢。然后在心里边也有个问号:这别人提供好的二方包依赖,怎么可能这么随意就失败了,不可能吧。
于是我就重启了一把,然后顺便Debug进去看看相关的逻辑。进去创建单例对象时,走到某条分支的时候,发现有连接ZK的 curator包有两个依赖可供我选择。那我知道了,绝壁又是依赖冲突了。
学习笔记:
类加载时静态变量只会在第一次加载时,进行初始化,此后不管成不成功,都不会进行第二次初始化了。
这个Bug在最开始的时候已经想过是不是依赖冲突的问题,但是我们怀疑版本依赖往往只会在顶层的jar包上怀疑,至于内部的jar包冲突一般也不好发现,发现了也不敢去乱排(毕竟复现的报错不是包依赖的问题啊!!)。
排Bug经验反思:
看错误信息固然很重要,但不要忽略第一次的报错信息(像整合工程这样的操作,绝大多数时候都是依赖冲突的问题。如果报错的信息比较诡异,可以重启直接Debug相应的位置上看看究竟是什么原因)
参考资料:
- https://www.jianshu.com/p/f48c90270fae
Tomcat打印大量的debug日志
整合了个系统以后,发现
tomcat_stdout.log
这个文件疯狂打印debug日志,都快把三歪打哭了。
于是我就一顿分析:项目里边用的是
logback
去打印日志的,那么我控制一下
logback
的日志级别应该就行了吧。但是我没找到
tomcat_stdout.log
这个日志文件的级别设置。
于是我就根据关键字搜:
tomcat_stdout.log DEBUG日志太多
。搜索了一轮,感觉都没啥卵用。虽然提供了很多解决方案(但这些我都看不懂,我也不怀疑是服务器上的配置存在问题。)
搜了一轮
logback 设置日志级别
、
logback debug 日志太多
这些关键字都没有用,隐隐约约能发现是因为logback和log4j冲突了。
最后我搜了一下日志打出的debug信息,以
ClientCnxn debug
关键字去搜索就才搜到相关的解决方案。
发现还是包依赖冲突的问题,把Zookeeper的
log4j
的包排掉,就解决了。
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
复制
排Bug经验反思:
上面的表面现象的确是Tomcat debug日志量太大了,而仅仅用这个关键字(
tomcat_stdout.log DEBUG
)去搜索引擎搜索很多时候都不是自己想要的解决方法。用
logback 设置日志级别
、
logback debug日志量
等关键字去搜索也只能搜出
logback
的相关教程。
透过表面现象,把输出的Debug日志加上关键字去搜,往往就能得出想要的结果。
参考资料:
- https://blog.csdn.net/qq_32465815/article/details/82081300