面試官:你知道 Dubbo 怎麼做優雅上下線的嗎?你:優雅上下線是啥?

作者 l Hollis

來源 l Hollis(ID:hollischuang)

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

Dubbo 是一個比較著名的 RPC 框架,很多人對於他的一些網絡通信、通信協議、動態代理等等都有一定的瞭解,這位候選人也一樣。

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

他沒怎麼說的上來,我以爲他不理解我的問題,我接着問他:我就是想問下 Dubbo 是如何做優雅上下線的你知道嗎?

接着他問我:優雅上下線是啥??

好吧。

這篇文章,我來介紹一下這個知識點吧。

優雅上下線

關於 "優雅上下線" 這個詞,我沒找到官方的解釋,我嘗試解釋一下這是什麼。

首先,上線、下線大家一定都很清楚,比如我們一次應用發佈過程中,就需要先將應用服務停掉,然後再把服務啓動起來。這個過程就包含了一次下線和一次上線。

那麼,"優雅" 怎麼理解呢?

先說什麼情況我們認爲不優雅:

1、服務停止時,沒有關閉對應的監控,導致應用停止後發生大量報警。

2、應用停止時,沒有通知外部調用方,很多請求還會過來,導致很多調用失敗。

3、應用停止時,有線程正在執行中,執行了一半,JVM 進程就被幹掉了。

4、應用啓動時,服務還沒準備好,就開始對外提供服務,導致很多失敗調用。

5、應用啓動時,沒有檢查應用的健康狀態,就開始對外提供服務,導致很多失敗調用。

以上,都是我們認爲的不優雅的情況,那麼,反過來,優雅上下線就是一種避免上述情況發生的手段。

一個應用的優雅上下線涉及到的內容其實有很多,從底層的操作系統、容器層面,到編程語言、框架層面,再到應用架構層面,涉及到的知識很廣泛。

其實,優雅上下線中,最重要的還是優雅下線。因爲如果下線過程不優雅的話,就會發生很多調用失敗了、服務找不到等問題。所以很多時候,大家也會提優雅停機這樣的概念。

本文後面介紹的優雅上下線也重點關注優雅停機的過程。

操作系統 & 容器的優雅上下線

關於操作系統,我之前有一篇文章專門介紹過這個話題,可能大家沒有注意到,那時候介紹的主題是爲什麼不能在線上機器中隨便執行 kill -9

其實,這背後的思考就是優雅上下線。

我們知道,kill -9之所以不建議使用,是因爲kill -9特別強硬,系統會發出 SIGKILL 信號,他要求接收到該信號的程序應該立即結束運行,不能被阻塞或者忽略。

這個過程顯然是不優雅的,因爲應用立刻停止的話,就沒辦法做收尾動作。而更優雅的方式是kill -15

當使用kill -15時,系統會發送一個 SIGTERM 的信號給對應的程序。當程序接收到該信號後,具體要如何處理是自己可以決定的。

kill -15會通知到應用程序,這就是操作系統對於優雅上下線的最基本的支持。

以前,在操作系統之上就是應用程序了,但是,自從容器化技術推出之後,在操作系統和應用程序之間,多了一個容器層,而 Docker、k8s 等容器其實也是支持優雅上下線的。

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

docker stop就像kill -15一樣,他會向容器內的進程發送 SIGTERM 信號,在 10S 之後(可通過參數指定)再發送 SIGKILL 信號。

docker kill就像kill -9,直接發送 SIGKILL 信號。

JVM 的優雅上下線

在操作系統、容器等對優雅上下線有了基本的支持之後,在接收到docker stopkill -15等命令後,會通知應用進程進行進程關閉。

而 Java 應用在運行時就是一個獨立運行的進程,這個進程是如何關閉的呢?

Java 程序的終止運行是基於 JVM 的關閉實現的,JVM 關閉方式分爲正常關閉、強制關閉和異常關閉 3 種。

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

當然,開發者也是可以自定義做一些額外的事情的,比如通知應用框架優雅上下線操作。

而這種機制是通過 JDK 中提供的 shutdown hook 實現的。JDK 提供了 Java.Runtime.addShutdownHook(Thread hook) 方法,可以註冊一個 JVM 關閉的鉤子。

例子如下:

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...");

        }

    }

執行命令:

jps
 6520 ShutdownHookTest
 6521 Jps
kill 6520

控制檯輸出內容:

 hook execute...

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

可以看到,當我們使用 kill(默認 kill -15)關閉進程的時候,程序會先執行我註冊的 shutdownHook,然後再退出,並且會給出一個提示:interrupted by signal 15: SIGTERM

Spring 的優雅上下線

有了 JVM 提供的 shutdown hook 之後,很多框架都可以通過這個機制來做優雅下線的支持。

比如 Spring,他就會向 JVM 註冊一個 shutdown hook,在接收到關閉通知的時候,進行 bean 的銷燬,容器的銷燬處理等操作。

同時,作爲一個成熟的框架,Spring 也提供了事件機制,可以藉助這個機制實現更多的優雅上下線功能。

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

開發者可以實現 ApplicationListener 接口,監聽到 Spring 容器的關閉事件(ContextClosedEvent),來做一些特殊的處理:

 @Component

    public class MyListener implements ApplicationListener<ContextClosedEvent> {

        @Override

        public void onApplicationEvent(ContextClosedEvent event) {

            // 做容器關閉之前的清理工作

        }

    }

Dubbo 的優雅上下線

因爲 Spring 中提供了 ApplicationListener 接口,幫助我們來監聽容器關閉事件,那麼,很多 web 容器、框架等就可以藉助這個機制來做自己的優雅上下線操作。

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

這裏簡答說一下 Dubbo 的,在 Dubbo 的官網中,有關於優雅停機的介紹:

應用在停機時,接收到關閉通知時,會先把自己標記爲不接受(發起)新請求,然後再等待 10s(默認是 10 秒)的時候,等執行中的線程執行完。

那麼,之所以他能做這些事,是因爲從操作系統、到 JVM、到 Spring 等都對優雅停機做了很好的支持。

關於 Dubbo 各個版本中具體是如何藉助 JVM 的 shutdown hook 機制、或者說 Spring 的事件機制的優雅停機,我的一位同事的一篇文章介紹的很清晰,大家可以看下:

https://www.cnkirito.moe/dubbo-gracefully-shutdown/

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

目前,Dubbo 中實現方式如下,同樣是用到了 Spring 的事件機制:

   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);

        }

    }

總結

本文從操作系統開始,分別介紹了 Linux、Docker、JVM、Spring、Dubbo 等對優雅停機的支持。

可以看到,一個簡單的優雅停機功能,上下游需要這麼多底層基礎設施和上層應用的支持。

相信通過學習本文,你一定對優雅上下線有了更多的瞭解。

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

我在工作中,就有很多次使用過這樣的機制的實例,後面有機會給大家介紹幾個實例。

好了,本文的全部內容就是這麼多啦,如果對你有幫助,記得一鍵三連哦~

參考 :

https://zhuanlan.zhihu.com/p/29093407

https://www.cnkirito.moe/dubbo-gracefully-shutdown/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/qtIEYXNHhlMOxsP61k12mg