Tomcat启动失败的问题排查与解决

前言

最近在某应用更新代码后部分机器发布失败,发布失败的机器上Tomcat一直没有启动成功,日志卡在Deploying web application,重启数次之后仍然是一样的情况。所以进行排查问题,下面记录了所有的排查过程,需要的朋友们可以参考学习。

排查过程

1. Tomcat启动线程卡住

下文中Tomcat启动线程代指线程名为localhost-startStop-$id的线程。

使用jstack打印出Tomcat的线程堆栈:

jstack `jps |grep Bootstrap |awk '{print $1}'` > jstack.log

从jstack.log里面可以看到线程localhost-startStop-1处于WAITING状态,堆栈如下:

"localhost-startStop-1" #26 daemon prio=5 os_prio=0 tid=0x00007fe6c8002000 nid=0x3dc1 waiting on condition [0x00007fe719c1e000]
 java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for <0x00000007147be150> (a xxx.heartbeat.network.client.FutureResult)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
 at java.util.concurrent.FutureTask.get(FutureTask.java:191)
 at xxx.HeartBeatContainer.invoke(HeartBeatContainer.java:183)
 at xxx.HeartBeatContainer.registry(HeartBeatContainer.java:130)

对应的代码如下:

final ResponseFuture<XxxMessage<Result>> future = responseFutureFactory.newResponseFuture(request);
channel.writeAndFlush(request);
XxxMessage<Result> response = future.get();

线程一直卡在future.get()没有返回。这个步骤是在等待客户端向Xxx-Server发送的注册请求的返回。

2. Xxx注册请求没返回

用tcpdump抓了下包(Xxx-Server的服务端口是yyy):

tcpdump -X -s0 -i bond0 port yyy

发现只有建连接的包,没有length != 0的数据包:

IP app-ip.56599 > xxx-server-ip.yyy: Flags [S], seq 3536490816, win 14600, options [mss 1460,sackOK,TS val 3049061547 ecr 0], length 0
IP xxx-server-ip.yyy > app-ip.56599: Flags [S.], seq 2500877640, ack 3536490817, win 14480, options [mss 1460,sackOK,TS val 1580197458 ecr 3049061547], length 0
IP app-ip.56599 > xxx-server-ip.yyy: Flags [.], ack 1, win 14600, options [nop,nop,TS val 3049061548 ecr 1580197458], length 0

所以,推断注册请求没返回的原因是请求压根儿没有发送出去。

3. Xxx注册请求没发送出去

Xxx代码里面调用了channel.writeAndFlush,但是数据却没有发送出去。这块的代码,更友好的做法应该是writeAndFlush之后对返回的ChannelFuture注册一个Listener,在write操作完成之后的回调里面判断状态。

在Netty大神 – @yh的指导下用BTrace跟了一下Netty的代码。

在Tomcat启动逻辑相关脚本bin/catalina.sh里面加上参数让Btrace agent和Tomcat一起启动:

JAVA_OPTS="$JAVA_OPTS -javaagent:${BTRACE_HOME}/build/btrace-agent.jar=script=${BTRACE_HOME}/scripts/HangDebug.class,stdout=true,debug=true,noServer=true"

HangDebug.class里面包含了一些需要查看的方法,下面是排查没有发送请求原因的步骤:

  • 首先发现没有调用接口io.netty.channel.Channel.Unsafe的write方法,验证了请求没有发送出去的推论;
  • 然后发现调用接口io.netty.channel.ChannelOutboundHandler的write方法时报错;
  • 最后定位到调用类io.netty.handler.codec.MessageToByteEncoder的write方法时抛出了异常,异常堆栈为:
io.netty.handler.codec.EncoderException: java.lang.NoSuchMethodError: io.netty.util.internal.MathUtil.safeFindNextPositivePowerOfTwo(I)I
 io.netty.handler.codec.MessageToByteEncoder.write(MessageToByteEncoder.java:125)
 ...
Caused by: java.lang.NoSuchMethodError:
 io.netty.util.internal.MathUtil.safeFindNextPositivePowerOfTwo(I)I
 io.netty.buffer.PoolThreadCache$MemoryRegionCache.<init>(PoolThreadCache.java:372)
 ...

这个时候,问题的原因比较明确了:
io.netty.util.internal.MathUtil.safeFindNextPositivePowerOfTwo这个方法没有找到。

最后找到问题的BTrace Method如下:

@OnMethod(
 clazz = "+io.netty.channel.ChannelOutboundHandler",
 method = "write",
 location = @Location(value = Kind.ERROR)
)
public static void errorChannelOutboundHandlerWrite(@ProbeClassName String className, Throwable cause) {
 println("error ChannelOutboundHandler.write, real class: " + className);
 Threads.jstack(cause);
 println("=====================");
}

这里有一个问题:为什么这个异常日志里面没有打印呢?

这个问题可以从io.netty.channel.AbstractChannelHandlerContext代码里找到答案:

private void invokeWrite(Object msg, ChannelPromise promise) {
 try {
 ((ChannelOutboundHandler)this.handler()).write(this, msg, promise);
 } catch (Throwable var4) {
 notifyOutboundHandlerException(var4, promise);
 }
}

notifyOutboundHandlerException会去通知对应的Listener,Xxx的这段老代码没有注册Listener,所以没有打印出这个异常。

4. NoSuchMethodError原因

再次查看了下$WEBAPP-DIR/WEB-INF/lib下Netty的版本:

netty-3.10.6.Final.jar
netty-all-4.1.4.Final.jar
netty-buffer-4.1.5.Final.jar
netty-codec-4.1.5.Final.jar
netty-codec-http-4.1.5.Final.jar
netty-common-4.1.5.Final.jar
netty-handler-4.1.5.Final.jar
netty-resolver-4.1.5.Final.jar
netty-transport-4.1.5.Final.jar
transport-netty3-client-5.0.0.jar
transport-netty4-client-5.0.0.jar

比较扎眼的是netty-all-4.1.4.Final.jar的版本和其它jar包版本不太一致。需要进一步确认一下,io.netty.buffer.PoolThreadCache$MemoryRegionCacheio.netty.util.internal.MathUtil这两个类分别是从哪个jar包中加载的。

在Tomcat启动逻辑相关脚本bin/catalina.sh里面加上启动参数,打印Class加载的日志:

JAVA_OPTS="$JAVA_OPTS -verbose:class"

可以看到:

...
[Loaded io.netty.buffer.PoolThreadCache$MemoryRegionCache from file:$WEBAPP-DIR/WEB-INF/lib/WEB-INF/lib/netty-buffer-4.1.5.Final.jar]
...
[Loaded io.netty.util.internal.MathUtil from file:$WEBAPP-DIR/WEB-INF/lib/netty-all-4.1.4.Final.jar]
...

从netty-all-4.1.4.Final.jar中加载的io.netty.util.internal.MathUtil,是没有safeFindNextPositivePowerOfTwo这个方法的(正常情况下,应该从netty-common-4.1.5.Final.jar中加载这个类)。

至此为止,弄清楚了启动卡住的原因:

Netty包加载问题 => Xxx调用channel.writeAndFlush发送注册请求时异常 => 没有回包,future.get()一直卡住 => Tomcat启动线程卡住

还有一个令人费解的现象:为什么有的机器启动正常,有的机器启动不正常呢?

5. 不同机器表现不同

再回头看一下启动有问题的机器上Netty相关jar包的顺序,这里我们使用ls -f命令(只关注和问题相关的jar包):

$ ls -f |grep netty
netty-buffer-4.1.5.Final.jar
netty-all-4.1.4.Final.jar
...
netty-common-4.1.5.Final.jar
...

ls加-f参数的含义可以通过man手册看到:

-f do not sort, enable -aU, disable -ls --color

意思是直接使用系统调用getdents的返回,不再做排序。从man手册可以看到,ls默认排序方法是Sort entries alphabetically if none。

NoSuchMethodError的原因是:从netty-buffer-4.1.5.Final.jar中加载了io.netty.buffer.PoolThreadCache$MemoryRegionCache,这个类是会调用io.netty.util.internal.MathUtil.safeFindNextPositivePowerOfTwo这个方法的;从netty-all-4.1.4.Final.jar加载的io.netty.util.internal.MathUtil没有这个方法。

对比看下启动正确的机器上的Netty相关jar包的顺序:

$ ls -f |grep netty
...
netty-all-4.1.4.Final.jar
...
netty-common-4.1.5.Final.jar
netty-buffer-4.1.5.Final.jar
...

由此可以看出所有Netty相关的Class均从netty-all-4.1.4.Final.jar中加载,不会有不兼容的问题产生。

要么问题来了:为什么在ext4中,拥有相同目录项的目录,ls -f出来的顺序是不一样的呢?

这个问题我暂时也回答不上来,至少我还没有拿到令自己信服的代码级别的解释。

嗯,没有代码的解释不是解释,没有deadline的任务不是任务,没有流程图或分享的源码阅读不是源码阅读,没有报告的性能测试不是性能测试。

这里有一个基于现象的解释,我觉得还比较靠谱:

On modern filesystems where directory data structures are based on a search tree or hash table, the order is practically unpredictable.

我们可以做的

事后诸葛亮时间 :) 开玩笑的,遇事多review下才能少犯错误。

  • 基础组件:多考虑失败的情况,不吞异常;可能阻塞的操作考虑超时时间(自勉)
  • 发布系统:能够添加一些规则,哪些包不能共存,比如上述问题中的netty-all和netty-common这些
  • 容器隔离:隔离中间件使用的三方包和业务使用的三方包

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 如何把spring boot项目部署到tomcat容器中

    把spring-boot项目按照平常的web项目一样发布到tomcat容器下 一.修改打包形式 在pom.xml里设置 <packaging>war</packaging> 二.移除嵌入式tomcat插件 在pom.xml里找到spring-boot-starter-web依赖节点,在其中添加如下代码, <dependency> <groupId>org.springframework.boot</groupId> <artifactId&

  • 同一台服务器(电脑)运行多个Tomcat的设置方法步骤

    大多人在Windows平台用的Tomcat都是免安装版本的,很自然想到复制几份目录,就是在同一个电脑上跑多个Tomcat服务了.实际上是不可以的.经过如下方法就可以实现统一台服务器(电脑)上运行多个Tomcat的目的了. 1.配置电脑"环境变量" 单个Tomcat的配置步骤不再重述,直接从配置第二个Tomcat开始.对电脑"环境变量"做调整,增加环境变量CATALINA_HOMEB,值为新的tomcat的地址. 2.更改Tomcat的 catalina.bat.st

  • Spring Boot启动过程(四)之Spring Boot内嵌Tomcat启动

    之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServ

  • Spring Boot 项目发布到 Tomcat 服务器的操作步骤

    第 1 步:将这个 Spring Boot 项目的打包方式设置为 war. <packaging>war</packaging> SpringBoot 默认有内嵌的 tomcat 模块,因此,我们要把这一部分排除掉. 即:我们在 spring-boot-starter-web 里面排除了 spring-boot-starter-tomcat ,但是我们为了在本机测试方便,我们还要引入它,所以我们这样写: <dependency> <groupId>org.s

  • 详解将Web项目War包部署到Tomcat服务器基本步骤

    1. 常识: 1.1 War包 War包一般是在进行Web开发时,通常是一个网站Project下的所有源码的集合,里面包含前台HTML/CSS/JS的代码,也包含Java的代码. 当开发人员在自己的开发机器上调试所有代码并通过后,为了交给测试人员测试和未来进行产品发布,都需要将开发人员的源码打包成War进行发布. War包可以放在Tomcat下的webapps或者word目录下,随着tomcat服务器的启动,它可以自动被解压. 1.2 Tomcat服务器 Tomcat服务器是一个免费的开放源代码

  • Tomcat报错: JDBC unregister 解决办法

     Tomcat报错: JDBC unregister 解决办法 摘要: The web application [web application] registered the JDBC driver [net.sourceforge.jtds.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has be

  • Tomcat启动失败的问题排查与解决

    前言 最近在某应用更新代码后部分机器发布失败,发布失败的机器上Tomcat一直没有启动成功,日志卡在Deploying web application,重启数次之后仍然是一样的情况.所以进行排查问题,下面记录了所有的排查过程,需要的朋友们可以参考学习. 排查过程 1. Tomcat启动线程卡住 下文中Tomcat启动线程代指线程名为localhost-startStop-$id的线程. 使用jstack打印出Tomcat的线程堆栈: jstack `jps |grep Bootstrap |aw

  • spring cloud eureka 服务启动失败的原因分析及解决方法

    目录 环境: 错误log 环境: <spring-boot-version>2.3.5.RELEASE</spring-boot-version> <spring-cloud-version>Hoxton.SR8</spring-cloud-version> 错误log Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerEx

  • tomcat启动startup.bat一闪而过问题的解决方法【亲测有效】

    遇到很多次运行startup.bat后,一个窗口一闪而过的问题,但是从来没去纠正怎样修改配置才是正确的,现在从网上查阅的资料整理如下: tomcat在启动时,会读取环境变量的信息,需要一个CATALINA_HOME 与JAVA_HOME的信息,CATALINA_HOME即tomcat的主目录,JAVA_HOME即java安装的主目录,jdk的主目录. 首先,要在环境变量处,配置JAVA_HOME,注意变量值是jdk的主目录,不是bin目录,并且不要加分号,如图: 然后,如果这样配置,startu

  • 解决tomcat启动 ssm项目出现乱码的问题

    0.乱码产生原因:编码和解码的方式是不同 1.出现乱码的解决方式[推荐]: 在tomcat 的配置文件web.xml 中添加上请求编码过滤器: <!-- 请求编码过滤器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</fil

  • Linux服务器安装Docker,启动失败问题的解决

    目录 背景 启动失败 查看具体报错 & 解决 总结 背景 Docker启动失败的原因和使用了阿里云的源有关. 如果大家有使用到阿里云的源仓库,可以继续看下是否也可以解决问题. 启动失败 在Linux服务器上安装docker,执行docker 和docker version没有问题. 但是执行docker images等命令报错,按照提示首先 执行:systemctl status docker 可以看到,报错信息为“无法启动Docker应用的容器引擎”,这里其实只能看到启动失败,状态为faile

  • 解决应用启动失败但tomcat不报错的方法

    发现问题 最近遇到了一个奇葩的问题就是应用启动失败,但是tomcat没有报任何的日志出来,log4j.properties日志级别写的是debug级别也没用. 解决过程 遇到这种情况,可以试着在web应用的classpath目录添加一个logging.properties文件来试一下 handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler ##################################

  • 解决因缺少Log4j依赖导致应用启动失败的问题

    前言 最近公司在做版本升级,所有对aaa(指代某个内部依赖)有依赖的应用需要排除掉.从这点看,几乎不会有什么问题,因为仅仅是排除一些maven依赖而已嘛.但是,一位同学在排除依赖的时候,仅仅是把aaa排除了,而没有在测试环境进行测试,在线上发布的时候,日志报dubbo服务注册失败(抛异常和dubbo admin没有看到注册的服务),导致应用启动失败(回滚后正常),影响正常业务5分钟. 事后排查这个问题的时候发现,有两个原因导致了应用启动失败: 去除aaa依赖后,导致应用有多个slf4j的依赖 去

  • Tomcat启动成功访问主页失败的原因解决方案

    Tomcat启动成功访问主页失败的原因解决方案 如果在 网上搜一圈"tomcat启动成功,当输入http://localhost:8080报404错误".有说查看日志定位问题的,有说检查端口有没有冲突,建议去server.xml修改端口的:有建议在cmd命令中输入netstat -ano查看tomcat端口是否已经被使用的:也有说环境变量配置有问题的. 这些可能都是对的,但还有一种情况容易被忽视:tomcat,webapps目录下没有ROOT文件夹! 有经验的人看到这个可能会淡淡的一笑

  • 解决IDEA配置tomcat启动报错问题

    在配置servlet不同路径时遇上以下两个错误: java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String:  java.lang.ClassNotFoundException: org.apache.jsp.index_jsp: 对于第一个问题,我上网查阅了很多资料后发现,可能是tomcat版本冲突导致,catalina log如下: 19-Feb-2018

  • 解决Idea的tomcat启动报多个listener的错误问题

    错误截图 找不到出错的异常位置在哪里.解决办法:在resources目录下创建一个logging.properties的文件,然后重启 handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler ############################################################ # Handler specific properties. # Describes speci

随机推荐