最近发现联调环境上,某web应用日志频繁报下面的错误:
2017-01-11 13:48:51.619 WARN 385 --- [68.124.11:2181)] org.apache.zookeeper.ClientCnxn : Session 0x0 for server 10.168.124.11/10.168.124.11:2181, unexpected error, closing socket connection and attempting reconnect
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/ConnectRequest
at org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:857) ~[zookeeper-3.4.6.jar!/:3.4.6-1569965]
at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:363) ~[zookeeper-3.4.6.jar!/:3.4.6-1569965]
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081) ~[zookeeper-3.4.6.jar!/:3.4.6-1569965]
虽然web服务也能正常执行,但log日志一直不停地打,这肯定是不行的。刚开始看到NoClassDefFoundError这个错,以为又是缺包少类,或者jar包冲突。但是这个类是ZK的核心包里的类,按道理不应该会没有jar包。况且NoClassDefFoundError和NoClassException多少还是有些区别的。
于是胸有成竹自以为是jar包冲突,就把spring-boot打的web-jar包解开,结果lib目录下,仅有一个zookeeper-3.4.6.jar,还有一个zkCLient的jar包。于是jar包冲突被排除了。
无奈,只能在网上寻觅答案了。搜索了一会,网上只能找到下面的错误排查原因:
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/SetWatches
看了看里面介绍的详细原因:(原文请参看:http://blog.csdn.net/hengyunabc/article/details/41450003)
这个异常的原因,是某些zookeeper的类没有加载到。
最终原因分析
梳理下整个流程:
- Tomcat启动,初始化webcontext;
- 初始化spring, spring初始某些些bean,这些bean包括了zookeeper的连接相关的bean;
- 这时zkClient(独立线程)已经连接上服务器了,但是classloader没有加载到org/apache/zookeeper/proto/SetWatches类;
- spring初始化失败,导致Tomcat webcontext初始化也失败,应用在挂起状态,但zkClient线程还是正常的;
- zookeeper服务器重启,zkClient开始重连,连接上zookeeper服务器;
- zkClient触发watch的一些代码,ClassLoader尝试加载org/apache/zookeeper/proto/SetWatches类,但是发现找不到类,于是抛出异常;
- zkClient捕获到异常,认为重连失败,close掉connection,休眠几秒之后,再次重连;
-
于是出现了zkClient反复重试连接zookeeper服务器,而且都是秒连秒断的情况。
总结一下:
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/SetWatches的原因是由于应用程序没有正常启动,而zkClient线程在应用程序进程启动失败前已经正常启动了,所以导致zkClient没有在jvm中找到SetWatches该类。
由于下面两个问题太相似,所以我大胆猜测产生原因是相同的,于是去查机器上的java进程。ps -ef | grep java
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/ConnectRequest
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/SetWatches
果然,发现了在服务器上有三个相同应用程序进程:
也就是说,前面两个都是启动失败挂起状态。而最后一个是成功的,所以web服务能正常服务,但同样385或者
585也在不停往log日志中循环输出。这一定是某个粗心的同事干的好事。启动之前不知道先杀掉旧进程。
最后,解决办法:把385和585进程杀掉。kill -9 385 && kill -9 585
日志就没再打印了。