您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java 如何利用钩子函数实现优雅停服?刨根问底

时间:2020-04-30 12:04:54  来源:  作者:

JAVA 的世界里遨游,如果能拥有一双善于发现的眼睛,有很多东西留心去看,外加耐心助力,仔细去品,往往会品出不一样的味道。

通过本次分享,能让你轻松 get 如下几点,绝对收获满满。

a)如何让 Java 程序实现优雅停服?有思想才是硬道理!

 

b)addShutdownHook 的使用场景?会用才是王道!

 

c)addShutdownHook 钩子函数到底是个啥?刨根问底!

1. 如何让 Java 程序实现优雅停服?

无论是自研基础服务框架,还是分析开源项目源码,细心的 Java 开发同学,都会发现 Runtime.getRuntime().addShutdownHook 这么一句代码的身影,这句到底是干什么用的?

接下来就一起细品,看看它香不香?

阿里开源的数据同步神器 Canal 启动时的部分源码:

Java 如何利用钩子函数实现优雅停服?刨根问底

 

Apache 麾下的用于海量日志收集的 Flume 启动时的部分源码:

Java 如何利用钩子函数实现优雅停服?刨根问底

 

仰望了一下开源的项目,不妨从中提炼一下共性(同样的代码遇到多次,势必会品出味道),写段代码跑跑看(站在 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("服务启动完成");
    }
}

经常读文的我很清楚,耐心读文章中源码的同学应该很少,所以我还是用图给你简单捋一捋。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

标注1:start 方法利用线程池启动一个线程去定时监控内存信息;

标注2:stop 方法用于在退出程序之前,进行关闭线程池进而释放资源。

程序跑起来,效果如下。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

当进行 kill 操作时,程序确实进行了资源释放,效果确实很优雅。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

一切看似那么自然,一切又是那么完美,这是真的吗?杀进程时候如果用 kill -9,这种情况下会发生什么现象呢?

Java 如何利用钩子函数实现优雅停服?刨根问底

 

呜呼!结果不会骗人的,当用 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 钩子函数是个啥?

刨根还要问到底!

Java 如何利用钩子函数实现优雅停服?刨根问底

 

Hook 翻译过来是「钩子」的意思,那顾名思义就是用来挂东西的。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

如图所示,在现实生活中,要制作腊肉,首先用钩子把肉勾住,然后挂在竹竿上,这应该是钩子的作用。

生活如此,一切设计理念都源于生活,在 Java 的世界里,亦是如此。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

如上图 Runtime 的源码所示,遵循 Java 的核心思想「一切皆是对象」,那么可以把 addShutdownHook 方法可以视作挂钩子,其实称之为钩子函数会好一些,而现实生活中的肉就可以抽象为释放资源的线程。

只要有这个钩子函数,对外就提供了扩展能力,研发人员就可以往钩子上挂各种自定义的场景实现,这种设计你细品那绝对是香!这也就是 Canal、Flume、Tomcat 等不同应用,在优雅停服时有着不同的实现的原因吧。

大白话,钩子函数有了,想挂什么东西,根据心情自己定就好了。

再深入去刨会发现,由于底层数据结构采用 Map 来进行存储,那么就支持研发人员挂多个 shutdownHook 的实现,又带来了无限的可能性(又带来了无限的「刺激」,自己好好去体会)。

Java 如何利用钩子函数实现优雅停服?刨根问底

 

好了,避免头大,就刨到这儿吧,感兴趣的可自行顺着思路继续刨下去。

4. 寄语,写在最后

作为研发人员:要拥有一双善于发现的眼睛,要善于发现代码之美。

作为研发人员:要时常思考面对当前的项目,是否能够简单重构让程序跑的更顺溜。

作为研发人员:要多看、多悟、多提炼、多实践。

作为研发人员:请不要放弃代码,因为程序终会铸就人生。

本次分享就到这里,希望对你有所帮助吧。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。欢迎关注「一猿小讲」,会持续输出原创精彩分享,敬请期待!



Tags:Java 钩子函数   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
在 Java 的世界里遨游,如果能拥有一双善于发现的眼睛,有很多东西留心去看,外加耐心助力,仔细去品,往往会品出不一样的味道。通过本次分享,能让你轻松 get 如下几点,绝对收获满满。a...【详细内容】
2020-04-30  Tags: Java 钩子函数  点击:(75)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条