面試官:你知道Dubbo怎么做優(yōu)雅上下線的嗎?你:優(yōu)雅上下線是啥?

最近無論是校招還是社招,都進(jìn)行的如火如荼,我也承擔(dān)了很多的面試工作,在一次面試過程中,和候選人聊了一些關(guān)于Dubbo的知識。

Dubbo是一個比較著名的RPC框架,很多人對于他的一些網(wǎng)絡(luò)通信、通信協(xié)議、動態(tài)代理等等都有一定的了解,這位候選人也一樣。

但是,我接下來問了他一個問題:你們在使用Dubbo的時候,應(yīng)用如果重啟,怎么保證一個請求不會被中斷處理的呢?

他沒怎么說的上來,我以為他不理解我的問題,我接著問他:我就是想問下Dubbo是如何做優(yōu)雅上下線的你知道嗎?

接著他問我:優(yōu)雅上下線是啥??

好吧。

這篇文章,我來介紹一下這個知識點(diǎn)吧。

優(yōu)雅上下線

關(guān)于"優(yōu)雅上下線"這個詞,我沒找到官方的解釋,我嘗試解釋一下這是什么。

首先,上線、下線大家一定都很清楚,比如我們一次應(yīng)用發(fā)布過程中,就需要先將應(yīng)用服務(wù)停掉,然后再把服務(wù)啟動起來。這個過成就包含了一次下線和一次上線。

那么,"優(yōu)雅"怎么理解呢?

先說什么情況我們認(rèn)為不優(yōu)雅:

1、服務(wù)停止時,沒有關(guān)閉對應(yīng)的監(jiān)控,導(dǎo)致應(yīng)用停止后發(fā)生大量報(bào)警。

2、應(yīng)用停止時,沒有通知外部調(diào)用方,很多請求還會過來,導(dǎo)致很多調(diào)用失敗。

3、應(yīng)用停止時,有線程正在執(zhí)行中,執(zhí)行了一半,JVM進(jìn)程就被干掉了。

4、應(yīng)用啟動時,服務(wù)還沒準(zhǔn)備好,就開始對外提供服務(wù),導(dǎo)致很多失敗調(diào)用。

5、應(yīng)用啟動時,沒有檢查應(yīng)用的健康狀態(tài),就開始對外提供服務(wù),導(dǎo)致很多失敗調(diào)用。

以上,都是我們認(rèn)為的不優(yōu)雅的情況,那么,反過來,優(yōu)雅上下線就是一種避免上述情況發(fā)生的手段。

一個應(yīng)用的優(yōu)雅上下線涉及到的內(nèi)容其實(shí)有很多,從底層的操作系統(tǒng)、容器層面,到編程語言、框架層面,再到應(yīng)用架構(gòu)層面,涉及到的知識很廣泛。

其實(shí),優(yōu)雅上下線中,最重要的還是優(yōu)雅下線。因?yàn)槿绻戮€過程不優(yōu)雅的話,就會發(fā)生很多調(diào)用失敗了、服務(wù)找不到等問題。所以很多時候,大家也會提優(yōu)雅停機(jī)這樣的概念。

操作系統(tǒng)&容器的優(yōu)雅上下線

我們知道,kill -9之所以不建議使用,是因?yàn)閗ill -9特別強(qiáng)硬,系統(tǒng)會發(fā)出SIGKILL信號,他要求接收到該信號的程序應(yīng)該立即結(jié)束運(yùn)行,不能被阻塞或者忽略。

這個過程顯然是不優(yōu)雅的,因?yàn)閼?yīng)用立刻停止的話,就沒辦法做收尾動作。而更優(yōu)雅的方式是kill -15。

當(dāng)使用kill -15時,系統(tǒng)會發(fā)送一個SIGTERM的信號給對應(yīng)的程序。當(dāng)程序接收到該信號后,具體要如何處理是自己可以決定的。

kill -15會通知到應(yīng)用程序,這就是操作系統(tǒng)對于優(yōu)雅上下線的最基本的支持。

以前,在操作系統(tǒng)之上就是應(yīng)用程序了,但是,自從容器化技術(shù)推出之后,在操作系統(tǒng)和應(yīng)用程序之間,多了一個容器層,而Docker、k8s等容器其實(shí)也是支持優(yōu)雅上下線的。

如Docker中同樣提供了兩個命令, docker stop 和 docker kill

docker stop就像kill -15一樣,他會向容器內(nèi)的進(jìn)程發(fā)送SIGTERM信號,在10S之后(可通過參數(shù)指定)再發(fā)送SIGKILL信號。
而docker kill就像kill -9,直接發(fā)送SIGKILL信號。

JVM的優(yōu)雅上下線

在操作系統(tǒng)、容器等對優(yōu)雅上下線有了基本的支持之后,在接收到docker stop、kill -15等命令后,會通知應(yīng)用進(jìn)程進(jìn)行進(jìn)程關(guān)閉。

而Java應(yīng)用在運(yùn)行時就是一個獨(dú)立運(yùn)行的進(jìn)程,這個進(jìn)程是如何關(guān)閉的呢?

Java程序的終止運(yùn)行是基于JVM的關(guān)閉實(shí)現(xiàn)的,JVM關(guān)閉方式分為正常關(guān)閉、強(qiáng)制關(guān)閉和異常關(guān)閉3種。

這其中,正常關(guān)閉就是支持優(yōu)雅上下線的。正常關(guān)閉過程中,JVM可以做一些清理動作,比如刪除臨時文件。

當(dāng)然,開發(fā)者也是可以自定義做一些額外的事情的,比如通知應(yīng)用框架優(yōu)雅上下線操作。

而這種機(jī)制是通過JDK中提供的shutdown hook實(shí)現(xiàn)的。JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注冊一個JVM關(guān)閉的鉤子。

例子如下:

package com.hollis;

public class ShutdownHookTest {

    public static void main(String[] args) {
        boolean flag = true;
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("hook execute...");
        }));

        while (flag) {
            // app is runing
        }

        System.out.println("main thread execute end...");
    }
}

執(zhí)行命令:

? jps
6520 ShutdownHookTest
6521 Jps
? kill 6520

控制臺輸出內(nèi)容:

hook execute...
Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

可以看到,當(dāng)我們使用kill(默認(rèn)kill -15)關(guān)閉進(jìn)程的時候,程序會先執(zhí)行我注冊的shutdownHook,然后再退出,并且會給出一個提示:interrupted by signal 15: SIGTERM

Spring的優(yōu)雅上下線

有了JVM提供的shutdown hook之后,很多框架都可以通過這個機(jī)制來做優(yōu)雅下線的支持。

比如Spring,他就會向JVM注冊一個shutdown hook,在接收到關(guān)閉通知的時候,進(jìn)行bean的銷毀,容器的銷毀處理等操作。
同時,作為一個成熟的框架,Spring也提供了事件機(jī)制,可以借助這個機(jī)制實(shí)現(xiàn)更多的優(yōu)雅上下線功能。

ApplicationListener是Spring事件機(jī)制的一部分,與抽象類ApplicationEvent類配合來完成ApplicationContext的事件機(jī)制。

開發(fā)者可以實(shí)現(xiàn)ApplicationListener接口,監(jiān)聽到 Spring 容器的關(guān)閉事件(ContextClosedEvent),來做一些特殊的處理:

@Component
public class MyListener implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 做容器關(guān)閉之前的清理工作
    }

}

Dubbo的優(yōu)雅上下線

因?yàn)镾pring中提供了ApplicationListener接口,幫助我們來監(jiān)聽容器關(guān)閉事件,那么,很多web容器、框架等就可以借助這個機(jī)制來做自己的優(yōu)雅上下線操作。

如tomcat、dubbo等都是這么做的。

這里簡答說一下Dubbo的,在Dubbo的官網(wǎng)中,有關(guān)于優(yōu)雅停機(jī)的介紹:

image

應(yīng)用在停機(jī)時,接收到關(guān)閉通知時,會先把自己標(biāo)記為不接受(發(fā)起)新請求,然后再等待10s(默認(rèn)是10秒)的時候,等執(zhí)行中的線程執(zhí)行完。

那么,之所以他能做這些事,是因?yàn)閺牟僮飨到y(tǒng)、到JVM、到Spring等都對優(yōu)雅停機(jī)做了很好的支持。

在從Dubbo 2.5 到 Dubbo 2.7介紹了歷史版本中,Dubbo為了解決優(yōu)雅上下線問題所遇到的問題和方案。

目前,Dubbo中實(shí)現(xiàn)方式如下,同樣是用到了Spring的事件機(jī)制:

public class SpringExtensionFactory implements ExtensionFactory {
    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
        BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
    }
}

總結(jié)

本文從操作系統(tǒng)開始,分別介紹了Linux、Docker、JVM、Spring、Dubbo等對優(yōu)雅停機(jī)的支持。

可以看到,一個簡單的優(yōu)雅停機(jī)功能,上下游需要這么多底層基礎(chǔ)設(shè)施和上層應(yīng)用的支持。

相信通過學(xué)習(xí)本文,你一定對優(yōu)雅上下線有了更多的了解。

除此之外,我還希望你,通過本文以后,遇到一些實(shí)際問題的時候,可以想到文中提到的shutdown hook機(jī)制、Spring的event機(jī)制。很多時候,這些機(jī)制都能幫助我們解決很多問題。

我在工作中,就有很多次使用過這樣的機(jī)制的實(shí)例,后面有機(jī)會給大家介紹幾個實(shí)例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容