优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊!

  其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

  再比如,就是不会让调用方的请求处理了一增,一下就中断了。而处理完本次后,再停止服务。

  Java语言中,我们可以通过Runtime.getRuntime().addShutdownHook()方法来注册钩子,以保证程序平滑退出。(其他语言也类似)

来个栗子:

public class ShutdownGracefulTest {

    /**
* 使用线程池处理任务
*/
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);
boolean done = false;
System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " all thread's done.");
}
catch (InterruptedException e) {
e.printStackTrace();
// 尝试再次关闭
if(!executorService.isTerminated()) {
executorService.shutdownNow();
}
}
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...");
System.exit(0);
}
}

运行结果如下:

  很明显,确实是优雅了,虽然最后收到了一关闭信号,但是仍然保证了任务的处理完成。很棒吧!

  那么,在实际应用中是如何体现优雅停机呢?

kill - pid 

通过该命令发送一个关闭信号给到jvm, 然后就开始执行 Shutdown Hook 了,你可以做很多:
  1. 关闭 socket 链接
  2. 清理临时文件
  3. 发送消息通知给订阅方,告知自己下线
  4. 将自己将要被销毁的消息通知给子进程
  5. 各种资源的释放
  ...

  而在平时工作中,我们不乏看到很多运维同学,是这么干的:

kill - 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运行了死循环代码。

实现原理:

Runtime.getRuntime().addShutdownHook(hook);        // 添加钩子,开启优雅之路

// 具体流程如下:

    /**
* Registers a new virtual-machine shutdown hook.
*
* @param hook
* An initialized but unstarted <tt>{@link Thread}</tt> object
*
* @throws IllegalArgumentException
* If the specified hook has already been registered,
* or if it can be determined that the hook is already running or
* has already been run
*
* @throws IllegalStateException
* If the virtual machine is already in the process
* of shutting down
*
* @throws SecurityException
* If a security manager is present and it denies
* <tt>{@link RuntimePermission}("shutdownHooks")</tt>
*
* @see #removeShutdownHook
* @see #halt(int)
* @see #exit(int)
* @since 1.3
*/
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
// 添加到 application 中
ApplicationShutdownHooks.add(hook);
} // java.lang.ApplicationShutdownHooks.add(hook);
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive())
throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
// hooks 以map类型保存, k->k 形式存储,保证每一个钩子都是独立的
hooks.put(hook, hook);
} // java.lang.ApplicationShutdownHooks 会先注册一个静态块,添加一个任务到 Shutdown 中
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
// 即当该任务被调用时,调用自身的运行方法,使所有注册的 hook 运行起来
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
} // runHooks 执行所有钩子线程,进行异步调用
/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
} for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
try {
// 阻塞等待所有完成
hook.join();
} catch (InterruptedException x) { }
}
}

到现在为止,我们已经知道关闭钩子是如何执行的,但是,还不是知道,该钩子是何时触发?

    // java.lang.Shutdown.add() 该方法会jvm主动调用,从而触发 后续钩子执行
/* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
* thread has finished. Unlike the exit method, this method does not
* actually halt the VM.
*/
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
// 执行序列
sequence();
}
}
// 而 sequence() 则会调用 runHooks(), 调用自定义的钩子任务
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
} // 执行钩子,此处最多允许注册 10 个钩子,且进行同步调用,当然这是最顶级的钩子,钩子下还可以添加钩子,可以任意添加n个
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
// 同步调用注册的hook, 即 前面看到 ApplicationShutdownHooks.runHooks()
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}

如此,整个关闭流程完美了。

简化为:

  1. 注册流程(应用主动调用)
    Runtime.addShutdownHook -> ApplicationShutdownHooks.add()/static -> java.lang.Shutdown.add()/shutdown()
  2. 执行流程(jvm自动调用)
    java.lang.Shutdown.shutdown()->sequence()->runHooks() -> ApplicationShutdownHooks.runHooks() -> hooks 最终

哦,这就是java的优雅停机?(实现及原理)的更多相关文章

  1. 你的Kubernetes Java应用优雅停机了吗?

    Java 应用优雅停机 我们首先考虑下,一般在什么场景下数据会丢失呢? 升级服务时 pod重启时 服务器断电时 因为服务器断电属于极端情况,我们暂且不考虑.那就只有 Java 退出时我们要保证数据的完 ...

  2. Java优雅停机

    Java的优雅停机通常通过注册JDK的ShootDownHook实现,当系统接受到退出指令后,首先标记系统处于退出状态,不再接受新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各 ...

  3. dubbo-2.5.6优雅停机研究

    不优雅的停机: 当进程存在正在运行的线程时,如果直接执行kill -9 pid时,那么这个正在执行的线程被中断,就好像一个机器运行中突然遭遇断电的情况,所导致的结果是造成服务调用的消费端报错,也有可能 ...

  4. Dubbo 优雅停机演进之路

    一.前言 在 『ShutdownHook- Java 优雅停机解决方案』 一文中我们聊到了 Java 实现优雅停机原理.接下来我们就跟根据上面知识点,深入 Dubbo 内部,去了解一下 Dubbo 如 ...

  5. JAVA优雅停机的实现

    最近在项目中需要写一个数据转换引擎服务,每过5分钟同步一次数据.具体实现是启动engine server后会初始化一个ScheduledExecutorService和一个ThreadPoolExec ...

  6. Java 技术栈中间件优雅停机方案设计与实现全景图

    欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...

  7. Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  8. Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现

    Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现 Anoyi 精讲JAVA 精讲JAVA 微信号 toooooooozi 功能介绍 讲解java深层次 ...

  9. ShutdownHook- Java 优雅停机解决方案

    想象一下,如果你现在刚好在 word 上写需求文档,电脑突然重启.等待开机完成,你可能会发现写了一个小时文档没有保存,就这么没了... 一个正在运行 Java 应用如果突然将其停止,影响不止数据丢失, ...

随机推荐

  1. eclipse java tomcat 远程调试

    在远程linux上修改tomcat 中bin 文件夹下 修改catalina.sh文件,在最前面加上如下代码: CATALINA_OPTS="-Xdebug -Xrunjdwp:transp ...

  2. python 子进程 subpocess 的使用方法简单介绍

    python的子进程嘛,就是利用python打开一个子进程(当然像是一句废话),但是可能和我们理解的不太一样. 一:如何理解? 我们可能的理解:多开一个进程运行某个python函数(如果只想实现这个功 ...

  3. eclipse汉化包

    把eclipse英文汉化成中文,首先我们要知道自己安装的eclipse版本,可以在eclipse的安装目录下找到readme用浏览器打开查看版本,或者用记事本打开.eclipseproduct文件,查 ...

  4. MySQL设置global变量和session变量

    1.在MySQL中要修改全局(global)变量,有两种方法: 方法一,修改my.ini配置文件,如果要设置全局变量最简单的方式是在my.ini文件中直接写入变量配置,如下图所示.重启数据库服务就可以 ...

  5. VB批量替换文本字符

    Private Sub Command1_Click()Open App.Path & "\abc.txt" For Binary As #1a = Input(LOF(1 ...

  6. 【MyBatis-Spring】Mybatis和并入Spring框架

    1.参考书籍:<深入浅出MyBatis技术原理与实战> 2.知识基础:Spring.Mybatis.Spring MVC. 配置数据源 配置SqlSessionFactory SqlSes ...

  7. C++ openmp并行程序在多核linux上如何最大化使用cpu

    以上代码中,#pragma omp parallel for 这一行的作用即是调用openmp的功能,根据检测到的CPU核心数目,将for (i = 0; i < 1000000000; i++ ...

  8. MongoDB学习记录(四) - MongoDB的"增查改删"操作之"改"

    更新文档主要有以下几种方法: db.collection.updateOne(filter, update, options) db.collection.updateMany(filter, upd ...

  9. Listener随笔

    [1]监听器简介   > Listener是JavaWeb中三大组件之一.Servlet.Filter.Listener   > 三大组件都有的共同特点,都需要实现一个接口,并在web.x ...

  10. 从零开始学习java(一)java基础语法

    从公司裸辞一个月,原本工作是做VB的,现在想从事java:在找工作的时候总是要什么项目经验,多少有些不爽,所有语言都有共 通性,我就不信java有这么难?给自己点时间来学习.坚持一个月自学,看看自己的 ...