我的 Java 应用程序有时会被外部脚本终止。这可以通过 SIGTERM 或 SIGKILL 来完成。
该应用程序是一个每秒接收许多连接的服务器,它可能会在尝试为这些连接提供服务时被终止。
我希望在应用程序被终止时重新启动它,因此我为此准备了一个脚本。
问题是,一旦应用程序被终止,新的应用程序实例就无法绑定到前一个实例使用的端口,因为“地址已被使用”。前一个实例的进程确实已被终止,无论如何,有问题的监听端口仍然存在,但它被分配给了 bash(或者在其他机器上 sh)。
显然,我的目标是重新启动应用程序并让它成功绑定到以前的地址。
我尝试等待超过 200 秒再重新启动,但没有成功,无论如何我等不起那么久。
我在运行该应用程序的所有机器上都遇到了这个问题(这是一个装有 java 1.6 的 jetty 服务器)。
任何建议都值得赞赏,谢谢,
西尔维奥
编辑终止 jvm 进程不是我退出应用程序的常规方式,只有在出现问题(OutOfMemoryErrors)时才使用。而且我从来不需要用 SIGKILL 来终止它,因为 SIGTERM 总是足够的,我只在 SIGTERM 失败的情况下才使用 SIGKILL,这种情况从未发生过。我正在研究一个长期的解决方案,同时我必须通过到处应用补丁来保持我的应用程序运行。
编辑更清楚一点:这是我在终止进程之前看到的 netstat -tunap | grep 行:
tcp6 0 0 :::8898 :::* LISTEN 22709/java
这是在终止进程之后
tcp6 0 0 :::8898 :::* LISTEN 23665/sh
请注意,PID 为 22709 的进程已被终止并消失,但端口仍然存在(但被 sh 锁定)
更新 在我终止我的应用程序后,使用 netstat 我可以看到一长串处于 CLOSE_WAIT 状态的待处理连接,我的 IP 是目标。此外,我可以看到处于 LISTEN 状态的 sh 进程正在监听我的端口:当我终止它时,一个睡眠进程会替换它并监听同一个端口:当我最终终止这个睡眠进程时,端口被释放,我可以成功重启我的服务器。这可能是释放我的端口的解决方案,但我担心为了释放端口而自动终止进程有点冒险
答案1
由于您仅手动执行此操作,因此可能必须添加另一项检查。
netstat -p
并杀死与打开套接字关联的 pid,即使它是 bash 或 sh。
另外,您提到大多数情况下 SIGTERM 都能正常工作。如果是这样,您的应用应该会捕获 SIGTERM 并跳转到一些优雅的退出代码,该代码会 RST 所有打开的连接,然后关闭套接字。
高血压
答案2
在监听套接字关闭后,服务器仍需要从客户端接收一些数据包并保持端口分配。应用程序可以使用 SO_REUSEADDR 套接字选项来允许立即重用套接字地址。
以下是我的 Linux ip(7) 手册页的摘录:
已绑定的 TCP 本地套接字地址在关闭后一段时间内不可用,除非设置了 SO_REUSEADDR 标志。使用此标志时应小心,因为它会降低 TCP 的可靠性。
应用程序或应用程序服务器可能具有使用此套接字选项的配置设置。
答案3
您实际上并没有终止您的 java 应用程序,您实际上是终止了正在运行您的 java 应用程序的 java 虚拟机(jvm)实例。
这不是终止 Java 进程的理想方法。
如果你必须使用 kill -9 终止 jvm,jvm 将无法自行清理,从而使操作资源处于不确定状态。:-(
向您的应用添加一些功能,使其正常退出。如果您别无选择,请尝试使用 -15 终止您的 jvm,这可能会帮助它自行清除。
如果您的 java 程序确实挂起了 jvm,那么您需要获取调试器并消除这些害虫。
终止进程并重新启动是一种黑客行为,但不是解决办法。只有当进程没有响应任何其他方法时,您才应该使用 SIGKILL。
我通常会尝试
杀死-15
那么只能将 kill -9 作为最后的手段。
为了好玩……
答案4
根据您的更新,我有了另一种解释。保持套接字打开的 sh 进程必须是您的应用程序的子进程,在侦听套接字打开后分叉。它没有随其父进程一起消亡,而是被 init 进程收养。
您应该尝试找出该 shell 进程的用途(可能是由您的应用程序启动的某个脚本)以及它为什么没有终止。也许修复脚本以便它在完成工作后终止就足够了?或者有一种方法可以让它不与父进程分离(如果它是同一进程组的一部分,它应该与父进程一起死亡)或让它关闭从父进程继承的所有不需要的文件描述符。
您可以尝试:
fuser -p $pid_of_the_sh_process
查看它还打开了哪些其他文件。其中一个很可能是 shell 脚本。知道它是什么后,我们就可以找到解决问题的方法。