Java程序经常会遇到进程挂掉的情况,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JAVA中的ShutdownHook提供了比较好的方案。而在SOFAJRaft-example模块的CounterServer-main方法中就使用了shutdownHook实现优雅停机。

@Author:Akai-yuan

@更新时间:2023/1/25

1.触发场景与失效场景

JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子这个钩子可以在以下几种场景中被调用:

  1. 程序正常退出
  2. 执行了System.exit()方法
  3. 终端使用Ctrl+C触发的中断
  4. 系统关闭
  5. OutOfMemory宕机
  6. 使用Kill pid命令干掉进程(使用 **kill -9 pid **是不会被调用的)

以下几种情况中是无法被调用的:

  1. 通过kill -9命令杀死进程——所以kill -9一定要慎用;
  2. 程序中执行了Runtime.getRuntime().halt()方法;
  3. 操作系统突然崩溃,或机器掉电(用电设备因断电、失电、或电的质量达不到要求而不能正常工作)。

2.addShutdownHook方法简述

Runtime.getRuntime().addShutdownHook(shutdownHook);

该方法指,在JVM中增加一个关闭的钩子,当JVM关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,JVM才会关闭。所以这些钩子可以在JVM关闭的时候进行内存清理、对象销毁、关闭连接等操作。

3.SOFAJRaft中钩子函数的实现

通过反射获取到grpcServer实例的shutdown方法和awaitTerminationLimit方法,并添加到钩子函数当中

public static void blockUntilShutdown() {
if (rpcServer == null) {
return;
}
//当RpcFactoryHelper中维护的工厂类型是GrpcRaftRpcFactory时进入if条件内部
if ("com.alipay.sofa.jraft.rpc.impl.GrpcRaftRpcFactory".equals(RpcFactoryHelper.rpcFactory().getClass()
.getName())) {
try {
//反射获取grpcServer中维护的(io.grpc包下的)server实例
Method getServer = rpcServer.getClass().getMethod("getServer");
Object grpcServer = getServer.invoke(rpcServer);
//反射获取server实例的shutdown方法和awaitTerminationLimit方法
Method shutdown = grpcServer.getClass().getMethod("shutdown");
Method awaitTerminationLimit = grpcServer.getClass().getMethod("awaitTermination", long.class,
TimeUnit.class);
//添加一个shutdownHook线程执行方法
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
shutdown.invoke(grpcServer);
awaitTerminationLimit.invoke(grpcServer, 30, TimeUnit.SECONDS);
} catch (Exception e) {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
e.printStackTrace(System.err);
}
}
});
//执行awaitTermination方法
Method awaitTermination = grpcServer.getClass().getMethod("awaitTermination");
awaitTermination.invoke(grpcServer);
} catch (Exception e) {
LOG.error("Failed to block grpc server", e);
}
}
}

4.grpc中的shutdown方法

GrpcServer下的shutdown方法与本文的钩子函数无关,此处再对比分析一下GrpcServer的shutdown方法。

    public void shutdown() {
//CAS
//当且仅当期待值为true时(与当前AtomicBoolean类型的started一致),设置为false关闭
if (!this.started.compareAndSet(true, false)) {
return;
}
ExecutorServiceHelper.shutdownAndAwaitTermination(this.defaultExecutor);
GrpcServerHelper.shutdownAndAwaitTermination(this.server);
}

ExecutorServiceHelper#shutdownAndAwaitTermination:

我们可以发现实际上就是在执行ExecutorService 中 的shutdown()、shutdownNow()、awaitTermination() 方法,那么我们来区别以下这几个方法

public static boolean shutdownAndAwaitTermination(final ExecutorService pool, final long timeoutMillis) {
if (pool == null) {
return true;
}
// 禁止提交新任务
pool.shutdown();
final TimeUnit unit = TimeUnit.MILLISECONDS;
final long phaseOne = timeoutMillis / 5;
try {
// 等待一段时间以终止现有任务
if (pool.awaitTermination(phaseOne, unit)) {
return true;
}
pool.shutdownNow();
// 等待一段时间,等待任务响应被取消
if (pool.awaitTermination(timeoutMillis - phaseOne, unit)) {
return true;
}
LOG.warn("Fail to shutdown pool: {}.", pool);
} catch (final InterruptedException e) {
// (Re-)cancel if current thread also interrupted
pool.shutdownNow();
// preserve interrupt status
Thread.currentThread().interrupt();
}
return false;
}

  1. shutdown():停止接收新任务,原来的任务继续执行

1、停止接收新的submit的任务;

2、已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;

3、等到第2步完成后,才真正停止;


  1. shutdownNow():停止接收新任务,原来的任务停止执行

1、跟 shutdown() 一样,先停止接收新submit的任务;

2、忽略队列里等待的任务;

3、尝试将正在执行的任务interrupt中断;

4、返回未执行的任务列表;

说明:

它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt() 方法是无法中断当前的线程的。

所以,shutdownNow() 并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。


  1. awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞

当前线程阻塞,直到:

  • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
  • 或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
  • 或者 线程被中断,抛出InterruptedException

然后会监测 ExecutorService 是否已经关闭,返回true(shutdown请求后所有任务执行完毕)或false(已超时)

GrpcServerHelper#shutdownAndAwaitTermination

与ExecutorServiceHelper类中的shutdownAndAwaitTermination方法类似的,该方法将优雅的关闭grpcServer.

public static boolean shutdownAndAwaitTermination(final Server server, final long timeoutMillis) {
if (server == null) {
return true;
}
// disable new tasks from being submitted
server.shutdown();
final TimeUnit unit = TimeUnit.MILLISECONDS;
final long phaseOne = timeoutMillis / 5;
try {
// wait a while for existing tasks to terminate
if (server.awaitTermination(phaseOne, unit)) {
return true;
}
server.shutdownNow();
// wait a while for tasks to respond to being cancelled
if (server.awaitTermination(timeoutMillis - phaseOne, unit)) {
return true;
}
LOG.warn("Fail to shutdown grpc server: {}.", server);
} catch (final InterruptedException e) {
// (Re-)cancel if current thread also interrupted
server.shutdownNow();
// 保持中断状态
Thread.currentThread().interrupt();
}
return false;
}

SOFAJRaft源码阅读-ShutdownHook如何优雅的停机的更多相关文章

  1. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  2. Hive cli源码阅读和梳理

    对Cli的重新认识*). hive cli有两种模式, 本地模式: 采用持有的driver对象来处理, 远程模式: 通过连接HiveServer来实现, 由此可见之前的架构图中的描述还是模糊且带有误导 ...

  3. jQuery.merge 源码阅读

    jQuery.merge(first,second) 概述 合并两个数组 返回的结果会修改第一个数组的内容——第一个数组的元素后面跟着第二个数组的元素. 参数 first:第一个待处理数组,会改变其中 ...

  4. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  5. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  6. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  7. [源码阅读] 阿里SOFA服务注册中心MetaServer(1)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 0x00 摘要 0x01 服务注册中心 1.1 服务注册中心简 ...

  8. [源码阅读] 阿里SOFA服务注册中心MetaServer(2)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(2) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(2) 0x00 摘要 0x01 MetaServer 注册 1.1 ...

  9. [源码阅读] 阿里SOFA服务注册中心MetaServer(3)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(3) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(3) 0x00 摘要 0x01 概念 1.1 分布式一致性 1.2 ...

  10. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

随机推荐

  1. 一篇了解全MVCC

    一.什么是MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制,是一种并发控制的方法,一般用在数据库管理系统中,实现对数据库的并发访问,比如在M ...

  2. SpringCloud(二) - Eureka注册中心,feign远程调用,hystrix降级和熔断

    1.项目模块介绍 2. 父项目 主要依赖 spring-cloud 的 版本控制 <properties> <!-- springCloud 版本 --> <scd.ve ...

  3. JS中的键盘Keycode

    常用的keycode 组合键 if ((window.event.ctrlKey)&&(window.event.keyCode==49)) alert("您按下了CTRL+ ...

  4. 论文笔记 - MetaICL: Learning to Learn In Context

    Motivation Facebook 的 MetaICL,牛逼就对了: 对 LM 针对 ICL 进行微调(而不是特定的任务): 去除了自然语言的 Template,使用更直接的方式,排除了 Temp ...

  5. Day1:Markdown文本基础操作

    Markdown学习 标题 一级标题 输入:#+空格+标题名字 :快捷键(Ctrl+1~6) 二级标题 输入:##+空格+标题名字 为二级标题 依次加#(最多六级标题) 三级标题 四级标题 字体 He ...

  6. 题解合集 (update on 11.5)

    收录已发布的题解 按发布时间排序. 部分可能与我的其他文章有重复捏 qwq . AtCoder for Chinese: Link ZHOJ: Link 洛谷 \(1\sim 5\) : [题解]CF ...

  7. 【题解】CF1714F Build a Tree and That Is It

    题面传送门 解决思路 题目中虽然说是无根树,但我们可以钦定这棵树的根为节点 \(1\),方便构造,这是不 影响结果的. 以下记给定的三段长度为 \(a,b,c\). 先考虑无解的情况. 首先,给出的三 ...

  8. windows查看端口和杀掉端口

    //执行下面命令 netstat --help 获取netstat的所有命令参数 //例如查看8080端口占用 netstat -ano | findstr 8080 //查看该端口是什么 taskl ...

  9. 国产图形化的msf——Viper初体验

    目录 免责声明: Viper简介 安装 使用 免责声明: 本文章仅供学习和研究使用,严禁使用该文章内容对互联网其他应用进行非法操作,若将其用于非法目的,所造成的后果由您自行承担,产生的一切风险与本文作 ...

  10. ThinkPHP 6.0 RC2 版本发布——架构升级、精简核心

    自从5.2版本变更为6.0以来,官方一直致力于优化架构和精简核心,同时也在准备手册和测试工作,在经过近1个月的开发迭代后,官方宣布发布ThinkPHP6.0RC2版本. 主要更新 相比较RC1版本更新 ...