常见Java应用如何优雅关闭


prtyaa
prtyaa 2024-01-01 23:29:16 63352
分类专栏: 资讯

一、前言

在我们进行系统升级的时候,往往需要关闭我们的应用,然后重启。在关闭应用前,我们希望做一些前置操作,比如关闭数据库、redis连接,清理zookeeper的临时节点,释放分布式锁,持久化缓存数据等等。

二、Linux的信号机制

在linux上,我们关闭进程主要是使用kill <pid>的方式。

当执行该命令以后,linux会向进程发送一个信号,进程收到以后之后,可以做一些清理工作。

kill命令默认的信号值为15,即SIGTERM信号。

通过kill -l查看linux支持哪些信号:

 

 

linux提供了signal()api,可以将信号处理函数注册上去:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>

static void gracefulClose(int sig)  
{
    printf("执行清理工作\n");
    printf("JVM 已关闭\n");
    exit(0);    //正常关闭
}

int main(int argc,char *argv[])  
{
    if(signal(SIGTERM,gracefulClose) == SIG_ERR)
        exit(-1);

    printf("JVM 已启动\n");

    while(true)
    {
        // 执行工作
        sleep(1);
    }
}

三、Java提供的Shutdown Hook

Java并不支持类似于linux的信号机制,但是提供了Runtime.addShutdownHook(Thread hook)的api。

在JVM关闭前,会并发执行各个Hook线程。

public class ShutdownHook {

    public static void main(String[] args) throws InterruptedException {
        Runtime.getRuntime().addShutdownHook(new DbShutdownWork());
        System.out.println("JVM 已启动");

        while(true){
            Thread.sleep(10L);
        }
    }

    static class DbShutdownWork extends Thread{
        public void run(){
            System.out.println("关闭数据库连接");
        }
    }
}

四、Spring Boot提供的优雅关闭功能

我们一般采用如下的方式,启动一个Spring boot应用:

public static void main(String[] args) throws Exception {  
    SpringApplication.run(SampleController.class, args);
}

SpringApplication.run()代码如下,会调用到refreshContext(context)方法:

public ConfigurableApplicationContext run(String... args) {  
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

refreshContext()方法比较简单:

private void refreshContext(ConfigurableApplicationContext context) {  
    refresh(context);   //调用ApplicationContext.refresh()
    if (this.registerShutdownHook) {        //registerShutdownHook默认值为true
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

AbstractApplicationContext.registerShutdownHook()代码:

public void registerShutdownHook() {  
    if (this.shutdownHook == null) {
        this.shutdownHook = new Thread() {
            @Override
            public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
                }
            }
        };
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

很明显,Spring boot通过在启动时,向JVM注册一个ShutdownHook,从而实现JVM关闭前,正常关闭Spring容器。而Spring在销毁时,会依次调用bean的destroy动作来销毁。

五、Dubbo的优雅关闭策略

Dubbo同样是基于ShutdownHook实现的。

AbstractConfig的static代码:

static {  
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
        public void run() {
            if (logger.isInfoEnabled()) {
                logger.info("Run shutdown hook now.");
            }
            ProtocolConfig.destroyAll();
        }
    }, "DubboShutdownHook"));
}

六、总结

只要我们的应用运行在linux平台上,所有的优雅关闭方案都是基于linux提供的信号机制提供的,JVM也是如此。

Java并没有为我们提供与之一一对应的api,而是给出了个ShutdownHook机制,也能达到类似的效果,缺点是我们无法得知JVM关闭的原因。

像dubbo、spring boot等成熟的开源框架,都实现了自动注册ShutdownHook的功能,从而避免使用者忘记调用优雅关闭api引发问题,降低框架的使用难度。

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=33804
赞同 0
评论 0 条
prtyaaL0
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1325
银河麒麟打印带有图像的文档时出错  1238
银河麒麟添加打印机时,出现“server-error-internal-error”  1025
统信桌面专业版【如何查询系统安装时间】  953
统信操作系统各版本介绍  946
统信桌面专业版【全盘安装UOS系统】介绍  905
麒麟系统也能完整体验微信啦!  892
统信【启动盘制作工具】使用介绍  501
统信桌面专业版【一个U盘做多个系统启动盘】的方法  443
信刻全自动档案蓝光光盘检测一体机  389
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!