Java 如何实现优雅停服?刨根问底
在 Java 的世界里遨游,如果能拥有一双善于发现的眼睛,有很多东西留心去看,外加耐心助力,仔细去品,往往会品出不一样的味道。
通过本次分享,能让你轻松 get 如下几点,绝对收获满满。
a)如何让 Java 程序实现优雅停服?有思想才是硬道理!
b)addShutdownHook 的使用场景?会用才是王道!
c)addShutdownHook 钩子函数到底是个啥?刨根问底!
1. 如何让 Java 程序实现优雅停服?
无论是自研基础服务框架,还是分析开源项目源码,细心的 Java 开发同学,都会发现 Runtime.getRuntime().addShutdownHook 这么一句代码的身影,这句到底是干什么用的?
接下来就一起细品,看看它香不香?
阿里开源的数据同步神器 Canal 启动时的部分源码:
Apache 麾下的用于海量日志收集的 Flume 启动时的部分源码:
仰望了一下开源的项目,不妨从中提炼一下共性(同样的代码遇到多次,势必会品出味道),写段代码跑跑看(站在 flume 源码的肩膀上,起飞)。
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* 体验 Java 优雅停服
*
* @author 一猿小讲
*/
public class Application { /**
* 监控服务
*/
private ScheduledThreadPoolExecutor monitorService; public Application() {
monitorService = new ScheduledThreadPoolExecutor(1);
} /**
* 启动监控服务,监控一下内存信息
*/
public void start() {
System.out.println(String.format("启动监控服务 %s", Thread.currentThread().getId()));
monitorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(String.format("最大内存: %dm 已分配内存: %dm 已分配内存中的剩余空间: %dm 最大可用内存: %dm",
Runtime.getRuntime().maxMemory() / 1024 / 1024,
Runtime.getRuntime().totalMemory() / 1024 / 1024,
Runtime.getRuntime().freeMemory() / 1024 / 1024,
(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() +
Runtime.getRuntime().freeMemory()) / 1024 / 1024));
}
}, 2, 2, TimeUnit.SECONDS);
} /**
* 释放资源(代码来源于 flume 源码)
* 主要用于关闭线程池(看不懂的同学莫纠结,当做黑盒去对待)
*/
public void stop() {
System.out.println(String.format("开始关闭线程池 %s", Thread.currentThread().getId()));
if (monitorService != null) {
monitorService.shutdown();
try {
monitorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("Interrupted while waiting for monitor service to stop");
}
if (!monitorService.isTerminated()) {
monitorService.shutdownNow();
try {
while (!monitorService.isTerminated()) {
monitorService.awaitTermination(10, TimeUnit.SECONDS);
}
} catch (InterruptedException e) {
System.err.println("Interrupted while waiting for monitor service to stop");
}
}
}
System.out.println(String.format("线程池关闭完成 %s", Thread.currentThread().getId()));
} /**
* 应用入口
*/
public static void main(String[] args) {
Application application = new Application();
// 启动服务(每隔一段时间监控输出一下内存信息)
application.start(); // 添加钩子,实现优雅停服(主要验证钩子的作用)
final Application appReference = application;
Runtime.getRuntime().addShutdownHook(new Thread("shutdown-hook") {
@Override
public void run() {
System.out.println("接收到退出的讯号,开始打扫战场,释放资源,完成优雅停服");
appReference.stop();
}
});
System.out.println("服务启动完成");
}
}
经常读文的我很清楚,耐心读文章中源码的同学应该很少,所以我还是用图给你简单捋一捋。
标注1:start 方法利用线程池启动一个线程去定时监控内存信息;
标注2:stop 方法用于在退出程序之前,进行关闭线程池进而释放资源。
程序跑起来,效果如下。
当进行 kill 操作时,程序确实进行了资源释放,效果确实很优雅。
一切看似那么自然,一切又是那么完美,这是真的吗?杀进程时候如果用 kill -9,这种情况下会发生什么现象呢?
呜呼!结果不会骗人的,当用 kill -9 的时候,就显得很粗暴了,压根不管什么资源释放,不管三七二十一,就是终止程序。
估计很多同学,都擅长用 kill -9 进行杀进程,为了线上的应用安全,还是用 kill -15 命令杀进程吧,这样会给应用留点时间去打扫一下战场,释放一下资源。
好了,通过仔细品味,借助 JDK 自带的 addShutdownHook 来助力应用,确实能让线上服务跑起来很优雅。
有思想才是硬道理!
2. addShutdownHook 的使用场景?
通过代码试验,能够感知 addShutdownHook(new Thread(){}) 是 JVM 销毁前要执行的一个线程,那么只要是涉及到资源回收的场景,应该都可以满足,下面简单列举几个。
a)数据同步神器 Canal 借助它,来进行关闭 socket 链接、释放 canal 的工作节点、清理缓存信息等;
b)海量日志收集 Flume 借助它,来实现线程池资源关闭、工作线程停止等;
c)在应用正常退出时,执行特定的业务逻辑、关闭资源等操作。
d)在 OOM 宕机、 CTRL+C、或执行 kill pid,导致 JVM 非正常退出时,加入必要的挽救措施成为可能。
其实,在 Java 的世界里遨游,只有想不到的,没有做不到的!
3. addShutdownHook 钩子函数是个啥?
刨根还要问到底!
Hook 翻译过来是「钩子」的意思,那顾名思义就是用来挂东西的。
如图所示,在现实生活中,要制作腊肉,首先用钩子把肉勾住,然后挂在竹竿上,这应该是钩子的作用。
生活如此,一切设计理念都源于生活,在 Java 的世界里,亦是如此。
如上图 Runtime 的源码所示,遵循 Java 的核心思想「一切皆是对象」,那么可以把 addShutdownHook 方法可以视作挂钩子,其实称之为钩子函数会好一些,而现实生活中的肉就可以抽象为释放资源的线程。
只要有这个钩子函数,对外就提供了扩展能力,研发人员就可以往钩子上挂各种自定义的场景实现,这种设计你细品那绝对是香!这也就是 Canal、Flume、Tomcat 等不同应用,在优雅停服时有着不同的实现的原因吧。
大白话,钩子函数有了,想挂什么东西,根据心情自己定就好了。
再深入去刨会发现,由于底层数据结构采用 Map 来进行存储,那么就支持研发人员挂多个 shutdownHook 的实现,又带来了无限的可能性(又带来了无限的「刺激」,自己好好去体会)。
好了,避免头大,就刨到这儿吧,感兴趣的可自行顺着思路继续刨下去。
4. 寄语,写在最后
作为研发人员:要拥有一双善于发现的眼睛,要善于发现代码之美。
作为研发人员:要时常思考面对当前的项目,是否能够简单重构让程序跑的更顺溜。
作为研发人员:要多看、多悟、多提炼、多实践。
作为研发人员:请不要放弃代码,因为程序终会铸就人生。
本次分享就到这里,希望对你有所帮助吧。一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。
会持续输出原创精彩分享,敬请期待!关注同名公众号:一猿小讲,回复「1024」可以获取精心为您准备的职场打怪进阶资料。
Java 如何实现优雅停服?刨根问底的更多相关文章
- Eureka+SpringBoot2.X版本实现优雅停服
在客户端添加如下配置 pom依赖 actuator.jar包 <dependency> <groupId>org.springframework.cloud</group ...
- 微软停服 XP系统到底伤害了谁?
http://majihua.baijia.baidu.com/article/10386 微软现在成了招人恨的角色,因为其史上最成功的操作系统WINDOWS XP在4月8日就将停止服务,而社会上对X ...
- 服务器 Python服务停服、起服脚本
近日,在阿里云服务器上部署了一个Python,Web框架为Tornado,服务器为Ubuntu 16.04. 服务的启动也十分的简单: python services.py 我是利用Xshell工具连 ...
- 五分钟学Java:打印Java数组最优雅的方式是什么?
在逛 Stack Overflow 的时候,发现了一些访问量像安第斯山一样高的问题,比如说这个:打印 Java 数组最优雅的方式是什么?访问量足足有 220W+,想不到啊,这么简单的问题竟然有这么多 ...
- Java中如何优雅地删除List中的元素
在工作中的许多场景下,我们都会使用到List这个数据结构,那么同样的有很多场景下需要删除List中的某一个元素或某几个元素,那么我们该如何正确无误地删除List中的元素的,今天我来教大家三种方式. 前 ...
- 在Java中如何优雅地判空
判空灾难 作为搬砖党的一族们,我们对判空一定再熟悉不过了,不要跟我说你很少进行判空,除非你喜欢NullPointerException. 不过NullPointerException对于很多猿们来 ...
- 利用java在服务器和客服端建立连接,进行通讯(代码实例)
客服端代码:有注释 package javanet; import java.io.IOException; import java.io.InputStream; import java.io.Ou ...
- 这样设计 Java 异常更优雅,赶紧学!
来源:lrwinx.github.io/2016/04/28/如何优雅的设计java异常/ 导语 异常处理是程序开发中必不可少操作之一,但如何正确优雅的对异常进行处理确是一门学问,笔者根据自己的开发经 ...
- 这样设计 Java 异常更优雅
转自:lrwinx.github.io/2016/04/28/如何优雅的设计java异常/ 导语 异常处理是程序开发中必不可少操作之一,但如何正确优雅的对异常进行处理确是一门学问,笔者根据自己的开发经 ...
随机推荐
- 破解idea2019版本至2089年(windows系统)
1.首先必须安装idea,然后找到安装目录下的bin目录下面 2.编辑俩个以.exe.vmoptions的文件(俩个文件的最后一行都得这样写) 3.重新打开idea,找到help下面的Register ...
- 如何快速地恢复你的win10
win10清单 这份List不会介绍如何安装系统,而是当面对一个新系统,如何最快的搭建,或者说恢复到一个生产力环境. 必备习惯 备份软件安装包和常用内容上云是高效恢复的两点关键. 备份软件安装包 我的 ...
- Hbase详细架构图解
@ 目录 主要组件 数据模型 注意:Hbase是依赖zookeeper和hdfs的,需要启动zk和hdfs. 主要组件 Zookeeper: HBase 通过 Zookeeper 来做 Master ...
- ES6新增的Map和WeakMap 又是什么玩意?非常详细的解释
上一篇文章讲了set和weakSet,这节咱就讲Map和weakMap是什么?这两篇文章并没有什么联系,主要知识用法类似而已.嘿嘿,是不是感觉舒服多了. 什么是Map 介绍什么是Map,就不得不说起O ...
- DEDE中自定义表单HTML 怎么写
用DEDE嵌套网站时,有时我们需要添加自定义字段,而自定义字段的HTML样式如何设置呢? 功能地图(核心/频道模型/内容模型管理/)——普通文章的修改——字段管理——你的自定义字段的修改——最下面自定 ...
- QT 获取可执行程序的路径
获取到生成.exe目录 QCoreApplication::applicationDirPath(); 获取当前工程目录 QDir::currentPath()
- 崛起于Springboot2.X之开发拦截器(21)
为什么80%的码农都做不了架构师?>>> 序言:几乎所有项目都需要拦截器,所以小伙伴们必须要掌握这门技术哦,不然只会mybaits增删改查那是实习生干的活呀. 1.创建拦截器类, ...
- 基于opencv的人脸识别程序
1. 解析opencv自带人脸识别源码(……/opencv-3.1.0/samples/cpp/facedetect.cpp) @ 操作系统:Ubuntu 15.04 OpenCV版本:3.1.0 # ...
- Eclipse中获取html jsp 标签的属性提示信息方法
操作方法: windows------preferences----------搜索 html----------找到 html Source这项---------找到 content assist ...
- 如何使用badboy录制一个脚本并成功的导入jmeter中?
前言: 虽然,很多人已经不适用这种方式进行录制脚本了,因为不好维护.但是,还是有一些朋友在刚开始学习的过程中使用badboy. 可能有人会好奇了,人家五一都出去玩了,你在家学习吗?正巧前一阵有粉丝留言 ...