哦,这就是java的优雅停机?(实现及原理)

优雅停机?这个名词我是服的,如果抛开专业不谈,多好的名词啊!

其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

再比如,就是不会让调用方的请求处理了一增,一下就中断了。而处理完本次后,再停止服务。

Java语言中,我们可以通过

Runtime.getRuntime().addShutdownHook()

方法来注册钩子,以保证程序平滑退出。(其他语言也类似) 

来个栗子:

publicclassShutdownGraceFullTest {
    /**
     * 使用线程池处理任务
     */
    publicstaticExecutorService executorService = Executors.newCachedThreadPool();
    publicstaticvoid main(String[] args) {
        //假设有5个线程需要执行任务
        for(int i = 0; i < 5; i++){
            finalint id = i;
            Thread taski = newThread(newRunnable() {
                @Override
                publicvoid run() {
                    System.out.println(System.currentTimeMillis() + " : thread_" + id + " start...");
                    try {
                        TimeUnit.SECONDS.sleep(id);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(System.currentTimeMillis() + " : thread_" + id + " finish!");
                }
            });
            taski.setDaemon(true);
            executorService.submit(taski);
        }
        // 添加一个钩子处理未完任务
        Runtime.getRuntime().addShutdownHook(newThread(newRunnable() {
            @Override
            publicvoid run() {
                System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown hooking...");
                boolean shutdown = true;
                try {
                    executorService.shutdown();
                    System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() +  " shutdown signal got, wait threadPool finish.");
                    executorService.awaitTermination(1500, TimeUnit.SECONDS);
                    System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() +  " all thread's done.");
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No1 shutdown done...");
            }
        }));
        // 多个关闭钩子并发执行
        Runtime.getRuntime().addShutdownHook(newThread(newRunnable() {
            @Override
            publicvoid run() {
                try {
                    System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown hooking...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " No2 shutdown done...");
            }
        }));
        System.out.println("main method exit...");
        // 故意调用jvm退出命令,发送关闭信号,否则正常情况下 jvm 会等待最后一个非守护线程关闭才会退出
        System.exit(0);
    }
}

运行结果如下:

很明显,确实是优雅了,虽然最后收到了一关闭信号,但是仍然保证了任务的处理完成。很棒吧!

那么,在实际应用中是如何体现优雅停机呢?

kill -15 pid

通过该命令发送一个关闭信号给到jvm, 然后就开始执行 Shutdown Hook 了,你可以做很多:

1、 关闭 socket 链接

2、 清理临时文件

3、 发送消息通知给订阅方,告知自己下线

4、 将自己将要被销毁的消息通知给子进程

5、 各种资源的释放

...

而在平时工作中,我们不乏看到很多运维同学,是这么干的:

kill -9 pid

如果这么干的话,jvm也无法了,kill -9 相当于一次系统宕机,系统断电。这会给应用杀了个措手不及,没有留给应用任何反应的机会。

所以,无论如何是优雅不起来了。

要优雅,是代码

其中,线程池的关闭方式为:

executorService.shutdown();
executorService.awaitTermination(1500, TimeUnit.SECONDS);

ThreadPoolExecutor 在 shutdown 之后会变成 SHUTDOWN 状态,无法接受新的任务,随后等待正在执行的任务执行完成。意味着,shutdown 只是发出一个命令,至于有没有关闭还是得看线程自己。

ThreadPoolExecutor 对于 shutdownNow 的处理则不太一样,方法执行之后变成 STOP 状态,并对执行中的线程调用 Thread.interrupt() 方法(但如果线程未处理中断,则不会有任何事发生),所以并不代表“立刻关闭”。

shutdown() :启动顺序关闭,其中执行先前提交的任务,但不接受新任务。如果已经关闭,则调用没有附加效果。此方法不等待先前提交的任务完成执行。

shutdownNow():尝试停止所有正在执行的任务,停止等待任务的处理,并返回正在等待执行的任务的列表。当从此方法返回时,这些任务将从任务队列中耗尽(删除)。此方法不等待主动执行的任务终止。

executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的时间,防止任务无限期的运行(前面已经强调过了,即使是 shutdownNow 也不能保证线程一定停止运行)。

注意:

虚拟机会对多个shutdownhook以未知的顺序调用,都执行完后再退出。

如果接收到 kill -15 pid 命令时,执行阻塞操作,可以做到等待任务执行完成之后再关闭 JVM。同时,也解释了一些应用执行 kill -15 pid 无法退出的问题,如:中断被阻塞了,或者hook运行了死循环代码。

出处:https://dwz.cn/nRS7c1Zg

—————END—————



更多精彩:Java实战项目视频,给需要的读者,收藏!
27个阿里 Java 开源项目,值得收藏!
从0到1搭建后端架构的经历
Spring Boot 注解大全,一键收藏!
差点被开除:一次订单号重复的事故!
一款仿网易云音乐Java开源系统(附源码)

关注公众号,查看更多优质文章
最近,整理一份Java资料《Java从0到1》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:关注公众号并回复 Java 领取,更多Java内容陆续奉上。
明天见(。・ω・。)ノ♡
<think>嗯,用户想了解Dubbo中如何实现优雅停机。首先,我需要回忆一下Dubbo的相关知识,尤其是优雅停机实现方式。优雅停机指的是在服务关闭时,能够先处理完当前请求,避免强制中断导致的数据不一致或任务失败。根据之前的引用资料,特别是引用[2]提到了Dubbo 2.5.x版本的实现,通过添加ShutdownHook来销毁协议。而引用[4]也提到了服务延迟暴露的配置,这可能和停机时的处理有关。 首先,用户可能需要知道优雅停机的基本概念,为什么需要它,然后才是具体的配置方法。根据引用[2],Dubbo在AbstractConfig类中注册了一个ShutdownHook,当JVM关闭时触发ProtocolConfig.destroyAll(),这应该是销毁所有协议,释放资源。但可能还需要考虑不同版本的差异,比如2.5.x和后续版本的改进。 接下来,配置步骤。用户可能需要知道如何启用优雅停机,是否需要额外的配置。根据引用[4],Dubbo 2.6.5之后延迟暴露的逻辑有变化,但优雅停机的配置可能涉及服务提供者和消费者的配置,比如设置wait参数,等待处理中的请求完成。可能需要指导用户如何配置dubbo.properties或者XML中的相关参数。 另外,Dubbo的优雅停机可能涉及到注册中心的注销,防止新的请求被路由到正在关闭的节点。同时,服务提供者需要等待处理中的请求完成,而消费者可能需要停止发送新的请求。这些步骤需要详细说明,包括配置项和可能需要注意的版本差异。 还要考虑如何验证优雅停机是否生效,比如通过日志查看停机时的处理过程。可能还要提到一些注意事项,比如超时时间的设置,确保在等待时间内请求能够处理完毕,否则可能强制关闭。 用户可能还会关心Dubbo版本之间的差异,比如2.5.x和2.6.5之后的版本在优雅停机上的不同处理方式,是否需要不同的配置方法。比如引用[2]中的代码示例显示,旧版本通过静态代码块添加ShutdownHook,而新版本可能有更优化的处理流程。 总结下来,回答应该包括优雅停机原理、配置步骤(服务提供者和消费者)、版本差异、验证方法和注意事项。需要引用相关的文档,如引用[2]和[4],来支持配置参数和实现机制的解释。同时,可能生成相关问题,比如如何调整等待时间,处理无法关闭的情况,或者与其他停机机制结合使用等。</think>### Dubbo优雅停机实现方式及配置教程 #### 一、优雅停机核心原理 Dubbo优雅停机通过**注册JVM关闭钩子**实现,在服务终止前完成以下关键操作: 1. 停止接收新请求 2. 等待处理中的请求完成(默认等待10秒) 3. 注销注册中心节点 4. 释放协议资源 引用Dubbo 2.5.x版本的实现代码[^2]: ```java Runtime.getRuntime().addShutdownHook(new Thread(() -> { ProtocolConfig.destroyAll(); }, "DubboShutdownHook")); ``` #### 二、具体配置步骤 1. **服务提供者配置** ```xml <dubbo:provider shutdown="true" wait="30000" /> ``` - `shutdown="true"`:启用优雅停机(默认已启用) - `wait="30000"`:最大等待时间30秒(单位:毫秒) 2. **消费者配置** ```xml <dubbo:consumer check="false" /> ``` - `check="false"`:停机时不强制检查提供者状态 3. **配置文件方式(dubbo.properties)** ```properties dubbo.service.shutdown.wait=30000 dubbo.service.shutdown.silent=false ``` #### 三、版本差异说明 | 版本范围 | 特性差异 | |---------------|--------------------------------------------------------------------------------------| | 2.5.x及之前 | 基础停机逻辑,通过静态钩子销毁协议资源[^2] | | 2.6.5及之后 | 优化停机时序,延迟暴露服务与Spring生命周期绑定[^4] | | 3.x | 支持Kubernetes原生集成,增强无损上下线能力 | #### 四、验证方法 1. 查看停机日志: ``` [DubboShutdownHook] INFO o.a.d.rpc.Protocol - [DUBBO] Close dubbo connect... [DUBBO] qos-server stopped. ``` 2. 使用telnet测试: ```bash telnet 127.0.0.1 20880 > status ``` #### 五、注意事项 1. 确保`wait`时间 > 最长请求处理时间 2. 禁止在停机期间发起新注册: ```properties dubbo.registry.dynamic=false ``` 3. 预热场景配合延迟暴露: ```xml <dubbo:service delay="5000" /> // 延迟5秒暴露[^4] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值