SOFAJRaft源码阅读-ShutdownHook如何优雅的停机
Java程序经常会遇到进程挂掉的情况,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JAVA中的ShutdownHook提供了比较好的方案。而在SOFAJRaft-example模块的CounterServer-main方法中就使用了shutdownHook实现优雅停机。
@Author:Akai-yuan
@更新时间:2023/1/25
1.触发场景与失效场景
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子这个钩子可以在以下几种场景中被调用:
- 程序正常退出
- 执行了System.exit()方法
- 终端使用Ctrl+C触发的中断
- 系统关闭
- OutOfMemory宕机
- 使用Kill pid命令干掉进程(使用 **kill -9 pid **是不会被调用的)
以下几种情况中是无法被调用的:
- 通过kill -9命令杀死进程——所以kill -9一定要慎用;
- 程序中执行了Runtime.getRuntime().halt()方法;
- 操作系统突然崩溃,或机器掉电(用电设备因断电、失电、或电的质量达不到要求而不能正常工作)。
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;
}
- shutdown():停止接收新任务,原来的任务继续执行
1、停止接收新的submit的任务;
2、已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;
3、等到第2步完成后,才真正停止;
- shutdownNow():停止接收新任务,原来的任务停止执行
1、跟 shutdown() 一样,先停止接收新submit的任务;
2、忽略队列里等待的任务;
3、尝试将正在执行的任务interrupt中断;
4、返回未执行的任务列表;
说明:
它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt() 方法是无法中断当前的线程的。
所以,shutdownNow() 并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。
- 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如何优雅的停机的更多相关文章
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- Hive cli源码阅读和梳理
对Cli的重新认识*). hive cli有两种模式, 本地模式: 采用持有的driver对象来处理, 远程模式: 通过连接HiveServer来实现, 由此可见之前的架构图中的描述还是模糊且带有误导 ...
- jQuery.merge 源码阅读
jQuery.merge(first,second) 概述 合并两个数组 返回的结果会修改第一个数组的内容——第一个数组的元素后面跟着第二个数组的元素. 参数 first:第一个待处理数组,会改变其中 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- [源码阅读] 阿里SOFA服务注册中心MetaServer(1)
[源码阅读] 阿里SOFA服务注册中心MetaServer(1) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 0x00 摘要 0x01 服务注册中心 1.1 服务注册中心简 ...
- [源码阅读] 阿里SOFA服务注册中心MetaServer(2)
[源码阅读] 阿里SOFA服务注册中心MetaServer(2) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(2) 0x00 摘要 0x01 MetaServer 注册 1.1 ...
- [源码阅读] 阿里SOFA服务注册中心MetaServer(3)
[源码阅读] 阿里SOFA服务注册中心MetaServer(3) 目录 [源码阅读] 阿里SOFA服务注册中心MetaServer(3) 0x00 摘要 0x01 概念 1.1 分布式一致性 1.2 ...
- 【原】FMDB源码阅读(三)
[原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...
随机推荐
- vue Excel导入,下载Excel模板,导出Excel
vue Excel导入,下载Excel模板,导出Excel vue Excel导入,下载Excel模板 <template> <div style="display: ...
- 【多服务场景化解决方案】AR虚拟技术助力智能家装
1 .介绍 总览 本应用采用了华为图形引擎服务的AR虚拟技术,您可以在手机相机里摆放想要购置的家具家电,交互式体验让您可以轻松操控它们的3D图例,以此来确定这些家具家电是否适合摆放在您的家里. 特 ...
- rocky二进制安装mysql8.0
(ubuntu的有点问题) 点击查看代码 #!/bin/bash Version=`cat /etc/os-release |awk -F'"| ' '/^NAME/{print $2}'` ...
- 【Azure 环境】Azure 云环境对于OpenSSL 3.x 的严重漏洞(CVE-2022-3602 和 CVE-2022-3786)的处理公告
问题描述 引用报告:(OpenSSL3.x曝出严重漏洞 : https://www.ctocio.com/ccnews/37529.html ) 最近OpenSSL 3.x 爆出了严重安全漏洞,分别是 ...
- CF452F等差子序列 & 线段树+hash查询区间是否为回文串
记录一下一个新学的线段树基础trick(真就小学生trick呗) 给你一个1到n的排列,你需要判断该排列内部是否存在一个3个元素的子序列(可以不连续),使得这个子序列是等差序列.\(n\) <= ...
- 强软弱引用,ThreadLocal和内存泄漏
强引用 写法:Object obj=new Object() 引用强度:最强 只要被引用着,就不会被gc(垃圾回收)回收掉. 软引用 写法:SoftReference<String> sr ...
- ROS应用层通信协议解析
参考:http://wiki.ros.org/ROS/Master_API http://wiki.ros.org/ROS/Connection Header 说明 ROS本质上就是一个松耦合的通信框 ...
- PHY驱动调试之 ---PHY设备驱动(三)
1. 前言 内核版本:linux 4.9.225,以freescale为例.(部分内容待修改和补充,不一定准确) 2. 概述 上一篇文章讲了控制器的驱动使用的是platform总线的连接方式,本节要讲 ...
- ArcEngine要素编辑遇到的一些问题
1.如何开启编辑 IMap myMap = this._Aplication.ActiveView.FocusMap; IWorkspace myWorkspace = (myMap25Sheet.P ...
- js拓展-Dom与事件,字符串,数组方法,object对象,作用域
js-扩展-Dom与事件 ```text # dom 文档对象模型,document object model ### 获取dom(js中的选择器) js let list=document.getE ...