直接运行java程序

假设我们在容器中直接启动的是我们的程序,例如java -jar xxx.jar。如果执行docker stop <container>,则意味着调用了kill -15 1

对于java程序而言,它会接收到SIGTERM信号,Runtime的ShutdownHook钩子将会被回调。像spring这样的框架,都会把自己加入到ShutdownHook回调中,在回调中执行destroy、close等生命周期操作。

所以说如果只是执行kill -15 PID命令,java程序是可以优雅的关闭。但同时我们也能看出,如果在ShutdownHook钩子中执行很耗时的操作,那么执行这个命令短时间内也不会停止java程序。所以在ShutdownHook中尽量不要做耗时长的操作,而是应该处理一些IO关闭、缓存刷新的操作。

在shell中运行java程序

假设我们在容器中启动的不是程序,而是一个shell脚本,在shell脚本中启动了一个进程。此时,
如果执行docker stop <container>,shell脚本收到SIGTERM信号后并不会把信号传给它的子进程,也就是说java程序不会做任何动作,直到宽限期到期会强制关闭容器等于kill -9。

如果对java程序执行了kill -9操作,那么java程序会直接停止运行,跳过ShutdownHook钩子回调。跳过ShutdownHook回调对java程序来说,是最不友好的操作。像spring框架,业务上我们在destroy方法上操作了很多关闭连接、回刷硬盘、回刷缓存等等结束操作,一旦执行kill -9操作,java程序中的这些资源将不会被正常释放,业务逻辑将出现bug。

解决方案

方法1

java -jar xxx.jar &
pid="$!"

_kill() {
  kill $pid
  wait $pid
  exit 0
}
trap _kill SIGTERM
wait

使用&字符将java程序后台运行,然后获取到java程序的pid。使用trap命令接收关闭信号,并指定调用kill方法。最后使用wait命令挂起shel脚本。

kill方法中,正常kill java程序并等待java程序结束。

方法2

exec java -jar xxx.jar

如果shell脚本中只启动一个java程序,那么可以使用exec命令。shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。

方法3

容器启动时,在启动参数中增加--init。它的作用是在shell进程结束后,清理掉其他僵尸进程。

https://docs.docker.com/engine/reference/run/#specify-an-init-process

标签: java, Linux, docker

添加新评论