Linux 环境如何使用 kill 命令优雅停止 Java 服务

举报
陈皮的JavaLib 发表于 2021/08/04 22:47:36 2021/08/04
【摘要】 你知道怎么优雅停止Java服务吗?你知道运维控制服务启停的脚本文件是长什么样子的吗?

我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复【资料】,即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板。

1 前言

我们在开发 Web 服务的时候,如果是使用外部 Tomcat 应用容器,那么启停服务可以直接使用 Tomcat 自带的脚本。但是现在大多数服务是使用 SpringBoot 框架开发,使用的是内嵌的 Tomcat / Jetty 应用容器,所以一般是我们自己使用命令或者编写脚本来启停服务。

对于停止运行中的服务,一般会使用两步骤。

首先使用 JDK 自带的 jps 命令或者 Linux ps(process status) 命令来查出服务进程 ID。

[root@chenpi ~]# jps
1618 Jps
1573 jar
[root@chenpi ~]# jps -l
1573 kill-demo-1.0.0.jar
1628 sun.tools.jps.Jps
[root@chenpi ~]# 

然后使用 linux kill 命令杀死特定的进程。

kill -9 1573

下面我们就来了解这个 kill 命令的用法以及开发中需要注意的事项。

2 kill 命令

kill() 是一个计算机编程语言函数,kill 函数可以向进程发送 signal()。在 Linux 里使用的 kill 命令,实际上是对 kill() 函数的一个包装。-- 来自百度百科的对 kill 命令的介绍。

简而言之,Linux kill 命令可以将指定的信号发送至程序,将指定程序终止。

查看 kill 命令的使用语法,直接在 Linux 服务上执行 kill 命令即可查看。

[root@chenpi ~]# kill
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

对于可选参数 -s sigspec | -n signum | -sigspec,说明如下:

  • sigspec:信号声明
  • signum:信号编号

那么信号声明和信号编号有哪些呢?可以使用 kill -l 命令查看。

[root@chenpi ~]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Linux Signals 的信号声明和信号编号相应关系以及解释如下表格。

Signal Name Number Description
SIGHUP 1 Hangup (POSIX)
SIGINT 2 Terminal interrupt (ANSI)
SIGQUIT 3 Terminal quit (POSIX)
SIGILL 4 Illegal instruction (ANSI)
SIGTRAP 5 Trace trap (POSIX)
SIGIOT 6 IOT Trap (4.2 BSD)
SIGBUS 7 BUS error (4.2 BSD)
SIGFPE 8 Floating point exception (ANSI)
SIGKILL 9 Kill(can’t be caught or ignored) (POSIX)
SIGUSR1 10 User defined signal 1 (POSIX)
SIGSEGV 11 Invalid memory segment access (ANSI)
SIGUSR2 12 User defined signal 2 (POSIX)
SIGPIPE 13 Write on a pipe with no reader, Broken pipe (POSIX)
SIGALRM 14 Alarm clock (POSIX)
SIGTERM 15 Termination (ANSI)
SIGSTKFLT 16 Stack fault
SIGCHLD 17 Child process has stopped or exited, changed (POSIX)
SIGCONT 18 Continue executing, if stopped (POSIX)
SIGSTOP 19 Stop executing(can’t be caught or ignored) (POSIX)
SIGTSTP 20 Terminal stop signal (POSIX)
SIGTTIN 21 Background process trying to read, from TTY (POSIX)
SIGTTOU 22 Background process trying to write, to TTY (POSIX)
SIGURG 23 Urgent condition on socket (4.2 BSD)
SIGXCPU 24 CPU limit exceeded (4.2 BSD)
SIGXFSZ 25 File size limit exceeded (4.2 BSD)
SIGVTALRM 26 Virtual alarm clock (4.2 BSD)
SIGPROF 27 Profiling alarm clock (4.2 BSD)
SIGWINCH 28 Window size change (4.3 BSD, Sun)
SIGIO 29 I/O now possible (4.2 BSD)
SIGPWR 30 Power failure restart (System V)

每一个信号量都有对应的信号声明和信号编号,比如 9) SIGKILL ,那么我们可以使用下面任何一种命令来强制终止进程(假设进程 ID 为1760)。因为信号声明前缀都是 SIG ,所以也可以省略前缀。

kill -9 1760
kill -n 9 1760
kill -s SIGKILL 1760
kill -SIGKILL 1760
kill -KILL 1760

如果没有显示指定具体的信号,默认是 15) SIGTERM。当程序接收到此信号后,程序会做停止前的处理工作,例如释放相应的资源,然后再停止,但是程序有可能会继续运行着。即 SIGTERM 信号是可以被阻塞的,可能被忽略的。

# 两种等效
kill 1755
kill -15 1755

使用 kill 命令终止一个进程,后面需要跟参数 pid | jobspec ...,可以是程序的 PID,PGID,或者工作编号等。

# 1755 是进程ID
kill 1755

还可以终止指定用户下的所有进程,如下两种方式都可以。

# 找出 chenpi 用户进程,再终止
kill -9 $(ps -ef | grep chenpi)

# 或者直接终止 chenpi 用户下的所有进程
kill -u chenpi

3 应用

那我们该如何使用 kill 命令来停止服务呢?有些人说直接使用 kill -9 PID,其实这个是很粗暴的而且极其危险的动作。因为它不管程序现在有没有未处理完的事情,直接强制终止进程,这对于一些比较敏感的服务,例如金融交易等业务是致命的。

例如,我们在 Springboot 服务启动后增加个 ShutdownHook,服务正常关闭情况下,会调用执行这个钩子函数。

package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class KillDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(KillDemoApplication.class, args);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("我是陈皮的进程,我快要挂了,我要说点遗言...");
        }));
    }
}

启动服务后,我们直接使用 kill -9 PID 命令杀死进程。发现控制台只打印一句 Killed 字样,服务直接被强制终止了。

如果我们使用 kill PID 命令停止服务,发现关机钩子函数被正常执行了,实现了优雅停止服务。而且你也会发现线程池也执行了关闭操作,实现了在停止服务前执行完线程池的任务,释放资源等操作。

所以当我们执行 kill pid 命令时,JVM 接收到关闭指令,会发布一些关闭事件,监听事件的监听器就可以在关闭服务前做相应的处理;也会触发注册的所有 Shutdown Hook,从而实现优雅停止服务。

所以比较合理的处理方式是应该先用默认信号(15) SIGTERM 尝试停止服务,如果多次尝试未停止成功,再考虑使用强制信号(9) SIGKILL 终止服务。

当然,停止服务的方式有很多种,比如 Springboot 也有相应的使用接口调用的方式来停止服务,不过应该还是使用编写脚本的方式用得比较多,毕竟也方便运维统一管理。

4 shutdown 脚本

运维一般使用统一的脚本文件来控制服务的启停,因为考虑到通用性以及兼容各种格式服务包的情况,所以脚本文件一般也会比较复杂一些。

以下我使用一些简单的命令编写脚本文件 shutdown.sh,实现优雅终止指定的 Java 服务。假设服务 jar 包是 kill-demo-1.0.0.jar

#!/bin/bash

# 主类
APP_MAINCLASS="kill-demo-1.0.0.jar"

# 进程ID
psid=0

# 记录尝试次数
num=0

# 获取进程ID,如果进程不存在则返回0,
# 当然你也可以在启动进程的时候将进程ID写到一个文件中,
# 然后使用的使用读取这个文件即可获取到进程ID
getpid() {
   javaps=`jps -l | grep $APP_MAINCLASS`
   if [ -n "$javaps" ]; then
      psid=`echo $javaps | awk '{print $1}'`
   else
      psid=0
   fi
}

stop() {
   getpid
   num=`expr $num + 1`  
   if [ $psid -ne 0 ]; then
    # 重试次数小于3次则继续尝试停止服务
    if [ "$num" -le 3 ];then
	  echo "attempt to kill... num:$num"
      kill $psid
      sleep 1
    else
	  # 重试次数大于3次,则强制停止
      echo "force kill..."
      kill -9 $psid      
    fi
	# 检查上述命令执行是否成功
    if [ $? -eq 0 ]; then
       echo "Shutdown success..."
    else
       echo "Shutdown failed..."
    fi
 
    # 重新获取进程ID,如果还存在则重试停止
    getpid
    if [ $psid -ne 0 ]; then
       stop
    fi
   else
      echo "App is not running"
   fi
}

stop

使用也很简单,直接使用 sh shutdown.sh 命令即可执行脚本。以下是成功停止服务以及服务没有运行时执行脚本的结果。

[root@chenpi kill-demo]# sh shutdown.sh 
attempt to kill... num:1
Shutdown success...

[root@chenpi kill-demo]# sh shutdown.sh 
App is not running

本次分享到此结束啦~~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,您的支持就是我创作最大的动力!

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。