优雅停机? 这个名词我是服的, 如果抛开专业不谈, 多好的名词啊!
其实优雅停机, 就是在要关闭服务之前, 不是立马全部关停, 而是做好一些善后操作, 比如: 关闭线程, 释放连接资源等.
再比如, 就是不会让调用方的请求处理了一增, 一下就中断了. 而处理完本次后, 再停止服务.
Java 语言中, 我们可以通过 Runtime.getRuntime().addShutdownHook() 方法来注册钩子, 以保证程序平滑退出.(其他语言也类似)
来个栗子:
- public class ShutdownGraceFullTest {
- /**
- * 使用线程池处理任务
- */
- public static ExecutorService executorService = Executors.newCachedThreadPool();
- public static void main(String[] args) {
- // 假设有 5 个线程需要执行任务
- for(int i = 0; i < 5; i++){
- final int id = i;
- Thread taski = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis() + ": thread_" + id + "start...");
- try {
- TimeUnit.SECONDS.sleep(id);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(System.currentTimeMillis() + ": thread_" + id + "finish!");
- }
- });
- taski.setDaemon(true);
- executorService.submit(taski);
- }
- // 添加一个钩子处理未完任务
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "No1 shutdown hooking...");
- boolean shutdown = true;
- try {
- executorService.shutdown();
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "shutdown signal got, wait threadPool finish.");
- executorService.awaitTermination(1500, TimeUnit.SECONDS);
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "all thread's done.");
- }
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "No1 shutdown done...");
- }
- }));
- // 多个关闭钩子并发执行
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "No2 shutdown hooking...");
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "No2 shutdown done...");
- }
- }));
- System.out.println("main method exit...");
- // 故意调用 jvm 退出命令, 发送关闭信号, 否则正常情况下 jvm 会等待最后一个非守护线程关闭才会退出
- System.exit(0);
- }
- }
运行结果如下:
很明显, 确实是优雅了, 虽然最后收到了一关闭信号, 但是仍然保证了任务的处理完成. 很棒吧!
那么, 在实际应用中是如何体现优雅停机呢?
kill -15 pid
通过该命令发送一个关闭信号给到 jvm, 然后就开始执行 Shutdown Hook 了, 你可以做很多:
1. 关闭 socket 链接
2. 清理临时文件
3. 发送消息通知给订阅方, 告知自己下线
4. 将自己将要被销毁的消息通知给子进程
5. 各种资源的释放
...
而在平时工作中, 我们不乏看到很多运维同学, 是这么干的:
kill -9 pid
如果这么干的话, jvm 也无法了, kill -9 相当于一次系统宕机, 系统断电. 这会给应用杀了个措手不及, 没有留给应用任何反应的机会.
所以, 无论如何是优雅不起来了.
要优雅, 是代码
其中, 线程池的关闭方式为:
- executorService.shutdown();
- executorService.awaitTermination(1500, TimeUnit.SECONDS);
ThreadPoolExecutor 在 shutdown 之后会变成 SHUTDOWN 状态, 无法接受新的任务, 随后等待正在执行的任务执行完成. 意味着, shutdown 只是发出一个命令, 至于有没有关闭还是得看线程自己.
ThreadPoolExecutor 对于 shutdownNow 的处理则不太一样, 方法执行之后变成 STOP 状态, 并对执行中的线程调用 Thread.interrupt() 方法 (但如果线程未处理中断, 则不会有任何事发生), 所以并不代表 "立刻关闭".
shutdown() : 启动顺序关闭, 其中执行先前提交的任务, 但不接受新任务. 如果已经关闭, 则调用没有附加效果. 此方法不等待先前提交的任务完成执行.
shutdownNow(): 尝试停止所有正在执行的任务, 停止等待任务的处理, 并返回正在等待执行的任务的列表. 当从此方法返回时, 这些任务将从任务队列中耗尽 (删除). 此方法不等待主动执行的任务终止.
executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的时间, 防止任务无限期的运行 (前面已经强调过了, 即使是 shutdownNow 也不能保证线程一定停止运行).
注意:
虚拟机会对多个 shutdownhook 以未知的顺序调用, 都执行完后再退出.
如果接收到 kill -15 pid 命令时, 执行阻塞操作, 可以做到等待任务执行完成之后再关闭 JVM. 同时, 也解释了一些应用执行 kill -15 pid 无法退出的问题, 如: 中断被阻塞了, 或者 hook 运行了死循环代码.
来源: http://www.tuicool.com/articles/zeU7feJ