优雅停止服务器实践思考

1,153 阅读3分钟
  1. 不进行优雅停止服务器会出现的情况

    请求丢失:在内存队列中转发的数据没有通知完

    数据丢失:存于内存缓存中数据未持久化到磁盘

    业务中断:处理到一半的业务被强行中断,会造成数据的不一致性。

    防止出现资源占用不释放情况,比如防止出现数据处理到一半,又或者是比如在数据库进行了加锁操作,但是服务器在加锁时间段内,服务器进程被杀掉了,那数据库锁就不会得到合理的释放。所以在关闭服务之前,我们需要做一些善后工作,比如保存数据、请求资源、下线服务,然后才退出应用。

  2. 哪些方面需要做善后工作

    Sever(Socket)停止,不再接收新的请求

    关闭现有连接通道

    关闭线程池

  3. 用什么方式进行关闭,以及使用什么进行善后工作的调用

    在linux下,我们使用kill pid ,关闭进程,然后通过触发java shutdownhook的方式进行。

    kill -15 pid,15信号是默认信号,实际上是可写可不写,该信号是给进程一次自己杀死自己的机会

    kill -9 pid,从外部强制停止进程

    我们一般使用 第一种的命令,kill -15 pid。

    实践过程中,发现问题:

    kill pid 会触发shutdownhook

    pkill -p parentPid 并不会触发shutdownhook。

  4. Java ShutdownHook编写

    Java语言提供一种ShutdownHook(钩子)机制,当JVM接受到系统的关闭通知之后,调用ShutdownHook内的方法,用以完成清理工作,从而平滑的退出应用。

    Runtime.getRuntime().addShutdownHook(new Thread()
    {     
        @Override     
        public void run()     
        {       
            // 关闭server       
            // 停止线程池     
                                         
        }});
    

    在server启动时将该逻辑加入。

    (1) 触发调用ShutdownHook场景

    • kill -15 pid
    • 代码执行结束,JVM正常退出
    • 应用代码中调用System.exit() 方法
    • 应用中发生OOM错误,导致JVM关闭
    • 终端中使用Ctr+c (非后台使用)

    (2) 注意点

    • 可以多次调用,从而增加多个

    • 多个ShutdownHook之间并无任何顺序,Java并不会按照加入顺序执行,反而将会并发执行。

    • ShutdownHook需要尽快执行结束。

      不要ShutdownHook执行需要被阻塞代码,或者死锁。

      为了避免ShutdownHook线程被长时间阻塞,我们可以引入超时机制

  5. 线程池的关闭

    (1)ExecutorService.shutdown() 非阻塞

    启动关闭执行先前提交的任务,但不会接受任何新任务。但该方法不会阻塞等待任务执行完成,而是发出一个停止的信号。

    (2)ExecutorService.shutdownNow() 非阻塞

    尝试停止所有正在执行的任务,暂停正在等待的任务处理,并返回正在等待执行的任务的列表。此方法不会等待主动执行的任务终止,而是尝试强制停止它们。

    (3)ExecutorService.awaitTermination() 等待阻塞

​ 阻塞直到关闭请求后所有任务都已完成执行,或者发生超时,或者当前线程被终端。

​ 我们一般是使用shutdown 和 awaitTermination结合使用,shutdown意味着执行程序服务不在接受传入的任务,awaitTermination是在shutdown请求后调用。

  1. 重启程序脚本编写

    启动时将对应进程id写入到中间文件,这边是最后进程的子进程,以下代码是为了获取最后进程的子进程在停止脚本时,使用kill 与 wait指令进行配合使用。

    if [ -f server.pid ]; then ! kill $(cat server.pid);  wait ${cat server.pid} fi
    nohup sh -c "java .....jar"
    echo $(ps --ppid $! -o pid,cmd |grep java|awk '{print $1}') > server.pid
    

    wait pid 命令讲解

    wait 是linux是内置命令,它等待完成任何正在运行的进程。wait命令与特定的进程ID或作业ID一起使用。阻塞等待pid 进程运行完成。 详解wait命令链接:linuxhint.com/wait_comman…

​7.更正第6条

经过实践,wait 命令这样使用不起作用,会报如下错:

wait: pid 856 is not a child of this shell

我猜想它大概是不能等待非shell运行时产生的process。没有时间去探究了,然后换了一种方式去做了。

将阻塞等待kill pid 执行完毕再往下走的命令修改成如下:

while ps -p $(cat server.pid) > /dev/null; do sleep 1; done;

这段命令的意思是一直循环检查该process是否还在。具体详解链接如下: blog.joncairns.com/2013/03/wai…