博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Tomcat进程意外退出的问题分析
阅读量:7209 次
发布时间:2019-06-29

本文共 4260 字,大约阅读时间需要 14 分钟。

hot3.png

节前某个部门的测试环境反馈tomcat会意外退出,我们到实际环境排查后发现不是jvm crash,日志里有进程销毁的记录,从pause到destory的整个过程:

org.apache.coyote.AbstractProtocol pause Pausing ProtocolHandler org.apache.catalina.core.StandardService stopInternal Stopping service Catalina org.apache.coyote.AbstractProtocol stop Stopping ProtocolHandler org.apache.coyote.AbstractProtocol destroy Destroying ProtocolHandler

从上面日志来可以判断: 1) tomcat不是通过脚本正常关闭(viaport: 即通过8005端口发送shutdown指令),因为正常关闭(viaport)的话会在 pause 之前有这样的一句warn日志:

org.apache.catalina.core.StandardServer await A valid shutdown command was received via the shutdown port. Stopping the Server instance. 然后才是 pause -> stop -> destory

2) tomcat的shutdownhook被触发,执行了销毁逻辑,而这又有两种情况,一是应用代码里有地方用System.exit来退出jvm,二是系统发的信号(kill -9除外,SIGKILL信号JVM不会有机会执行shutdownhook)。

先通过排查代码,应用方和中间件团队都排查了System.exit在这个应用中使用的可能。那就只剩下Signal的情况了;经过一番排查后,发现每次tomcat意外退出的时间与ssh会话结束的时间正好吻合。

有了这个线索之后,立刻看了一下对方测试环境的脚本,简化后如下:

$ cat test.sh #!/bin/bash cd /data/server/tomcat/bin/ ./catalina.sh start tail -f /data/server/tomcat/logs/catalina.out

tomcat启动后,当前shell进程并没有退出,而是挂住在tail进程,往终端输出日志内容。这种情况下,如果用户直接关闭ssh终端的窗口(用鼠标或快捷键),则java进程也会退出。而如果先ctrl-c终止test.sh进程,然后再关闭ssh终端的话,则java进程不会退出。

这是一个有趣的现象,catalina.sh start方式启动的tomcat会把java进程挂到init(进程id为1)的父进程下,已经与当前test.sh进程脱离了父子关系,也与ssh进程没有关系,为什么关闭ssh终端窗口会导致java进程退出?

我们的推测是ssh窗口在关闭时,对当前交互的shell以及正在运行的test.sh等子进程发送某个退出的Signal,找了一台装有systemtap的机器来验证,所用的stap脚本都是copy的:

function time_str: string () {    return ctime(gettimeofday_s() + 8 * 60 * 60);}probe begin {    printdln(" ", time_str(), "BEGIN");}probe end {    printdln(" ", time_str(), "END");}probe signal.send {    if (sig_name == "SIGHUP" || sig_name == "SIGQUIT" ||         sig_name=="SIGINT" || sig_name=="SIGKILL" || sig_name=="SIGABRT") {        printd(" ", time_str(), sig_name, "[", uid(), pid(), cmdline_str(),                 "] -> [", task_uid(task), sig_pid, pid_name, "], ");        task = pid2task(pid());        while (task_pid(task) > 0) {            printd(" ", "[", task_uid(task), task_pid(task), task_execname(task), "]");            task = task_parent(task);        }        println("");    }}

模拟时的进程层级(pstree)大致如下,tomcat启动后java进程已经脱离test.sh,挂在init下:

|-sshd(1622)-+-sshd(11681)---sshd(11699)---bash(11700)---test.sh(13285)---tail(13299)

最终,我们发现: a) 用 ctrl-c 终止当前test.sh进程时,系统events进程向 java 和 tail 两个进程发送了SIGINT 信号

SIGINT [ 0 11 ] -> [ 0 20629 tail ] SIGINT [ 0 11 ] -> [ 0 20628 java ] SIGINT [ 0 11 ] -> [ 0 20615 test.sh ] 注pid 11是events进程

b) 关闭ssh终端窗口时,sshd向下游进程发送SIGHUP, 为何java进程也会收到?

SIGHUP [ 0 11681 sshd: hongjiang.wanghj [priv] ] -> [ 57316 11700 bash ] SIGHUP [ 57316 11700 -bash ] -> [ 57316 11700 bash ] SIGHUP [ 57316 11700 ] -> [ 0 13299 tail ] SIGHUP [ 57316 11700 ] -> [ 0 13298 java ] SIGHUP [ 57316 11700 ] -> [ 0 13285 test.sh ]

确定了是由signal引起的之后,我的疑惑变成了:

  1. 为什么SIGINT (kill -2) 不会让tomcat进程退出?
  2. 为什么SIGHUP (kill -1) 会让tomcat进程退出?

**shell在非交互模式下对后台进程处理SIGINT信号时设置的是IGNORE。**交互模式与非交互模式对作业控制(job control)默认方式不同。

为什么在交互模式下shell不会对后台进程处理SIGINT信号设置为忽略,而非交互模式下会设置为忽略呢?还是比较好理解的,举例来说,我们先某个前台进程运行时间太长,可以ctrl-z中止一下,然后通过bg %n把这个进程放入后台,同样也可以把一个cmd &方式启动的后台进程,通过fg %n放回前台,然后在ctrl-c停止它,当然不能忽略SIGINT。

为何交互模式下的后台进程会设置一个自己的进程组ID呢?因为默认如果采用父进程的进程组ID,父进程会把收到的键盘事件比如ctrl-c之类的SIGINT传播给进程组中的每个成员,假设后台进程也是父进程组的成员,因为作业控制的需要不能忽略SIGINT,你在终端随意ctrl-c就可能导致所有的后台进程退出,显然这样是不合理的;所以为了避免这种干扰后台进程设置为自己的pgid。

而非交互模式下,通常是不需要作业控制的,所以作业控制在非交互模式下默认也是关闭的(当然也可以在脚本里通过选项set -m打开作业控制选项)。不开启作业控制的话,脚本里的后台进程可以通过设置忽略SIGINT信号来避免父进程对组中成员的传播,因为对它来说这个信号已经没有意义。

回到tomcat的例子,catalina.sh脚本通过start参数启动的时候,就是以非交互方式后台启动,java进程也被shell设置了忽略SIGINT信号,因此在ctrl-c结束test.sh进程时,系统发送的SIGINT对java没有影响。

SIGHUP (kill -1) 让tomcat进程退出的原因。

在非交互模式下,shell对java进程设置了SIGINT,SIGQUIT信号设置了忽略,但并没有对SIGHUP信号设为忽略。再看一下当时的进程层级:

|-sshd(1622)-+-sshd(11681)---sshd(11699)---bash(11700)---test.sh(13285)---tail(13299)

sshd把SIGHUP传递给bash进程后,bash会把SIGHUP传递给它的子进程,并且对于其子进程test.sh,bash还会对test.sh的进程组里的成员都传播一遍SIGHUP。因为java后台进程从父进程catalina.sh(又是从其父进程test.sh)继承的pgid,所以java进程仍属于test.sh进程组里的成员,收到SIGHUP后退出。

如果我们在test.sh里设置开启作业控制的话,就不会让java进程退出了。

#!/bin/bash set -m

cd /home/admin/tt/tomcat/bin/ ./catalina.sh start tail -f /home/admin/tt/tomcat/logs/catalina.out

此时java后台进程继承父进程catalina.sh的pgid,而catalina.sh不再使用test.sh的进程组,而是自己的pid作为pgid,catalina.sh进程在执行完退出后,java进程挂到了init下,java与test.sh进程就完全脱离关系了,bash也不会再向它发送信号。

转载于:https://my.oschina.net/xianggao/blog/391038

你可能感兴趣的文章
一个游戏
查看>>
如何让帝国CMS7.2搜索模板支持动态标签调用
查看>>
公众号和小程序可以同名了 名称支持同主体复用
查看>>
96.2. Yum 安装
查看>>
[再寄小读者之数学篇](2015-06-08 一个有意思的定积分计算)
查看>>
搭建本地 Registry - 每天5分钟玩转 Docker 容器技术(20)
查看>>
logger异常日志要点总结
查看>>
chrony软件使用说明
查看>>
SetWindowHookEx 做消息响应
查看>>
数据库事务的四个隔离级别
查看>>
Elasticsearch——利用Parent-Child关系解决大数据场景下的实时查询
查看>>
pyMagic:用python控制的Geek入门神器
查看>>
ubuntu for win10 里运行apache+php
查看>>
在线 Python运行工具
查看>>
云上的数据安全,是企业互联网化发展的生命线!
查看>>
应变界的翘楚:硅基谐振式传感器灵敏度非常高
查看>>
Lean WM在交货中的应用[翻译]
查看>>
dataguard备库的数据文件的迁移
查看>>
《Oracle DBA工作笔记》第一章
查看>>
26.3. 促销优惠组件设计
查看>>