面試官:你知道Dubbo怎么做優(yōu)雅上下線的嗎?你:優(yōu)雅上下線是啥?
最近無(wú)論是校招還是社招,都進(jìn)行的如火如荼,我也承擔(dān)了很多的面試工作,在一次面試過(guò)程中,和候選人聊了一些關(guān)于Dubbo的知識(shí)。
Dubbo是一個(gè)比較著名的RPC框架,很多人對(duì)于他的一些網(wǎng)絡(luò)通信、通信協(xié)議、動(dòng)態(tài)代理等等都有一定的了解,這位候選人也一樣。
但是,我接下來(lái)問(wèn)了他一個(gè)問(wèn)題:你們?cè)谑褂肈ubbo的時(shí)候,應(yīng)用如果重啟,怎么保證一個(gè)請(qǐng)求不會(huì)被中斷處理的呢?
他沒(méi)怎么說(shuō)的上來(lái),我以為他不理解我的問(wèn)題,我接著問(wèn)他:我就是想問(wèn)下Dubbo是如何做優(yōu)雅上下線的你知道嗎?
接著他問(wèn)我:優(yōu)雅上下線是啥??
好吧。
這篇文章,我來(lái)介紹一下這個(gè)知識(shí)點(diǎn)吧。
優(yōu)雅上下線
關(guān)于"優(yōu)雅上下線"這個(gè)詞,我沒(méi)找到官方的解釋,我嘗試解釋一下這是什么。
首先,上線、下線大家一定都很清楚,比如我們一次應(yīng)用發(fā)布過(guò)程中,就需要先將應(yīng)用服務(wù)停掉,然后再把服務(wù)啟動(dòng)起來(lái)。這個(gè)過(guò)程就包含了一次下線和一次上線。
那么,"優(yōu)雅"怎么理解呢?
先說(shuō)什么情況我們認(rèn)為不優(yōu)雅:
1、服務(wù)停止時(shí),沒(méi)有關(guān)閉對(duì)應(yīng)的監(jiān)控,導(dǎo)致應(yīng)用停止后發(fā)生大量報(bào)警。
2、應(yīng)用停止時(shí),沒(méi)有通知外部調(diào)用方,很多請(qǐng)求還會(huì)過(guò)來(lái),導(dǎo)致很多調(diào)用失敗。
3、應(yīng)用停止時(shí),有線程正在執(zhí)行中,執(zhí)行了一半,JVM進(jìn)程就被干掉了。
4、應(yīng)用啟動(dòng)時(shí),服務(wù)還沒(méi)準(zhǔn)備好,就開始對(duì)外提供服務(wù),導(dǎo)致很多失敗調(diào)用。
5、應(yīng)用啟動(dòng)時(shí),沒(méi)有檢查應(yīng)用的健康狀態(tài),就開始對(duì)外提供服務(wù),導(dǎo)致很多失敗調(diào)用。
以上,都是我們認(rèn)為的不優(yōu)雅的情況,那么,反過(guò)來(lái),優(yōu)雅上下線就是一種避免上述情況發(fā)生的手段。
一個(gè)應(yīng)用的優(yōu)雅上下線涉及到的內(nèi)容其實(shí)有很多,從底層的操作系統(tǒng)、容器層面,到編程語(yǔ)言、框架層面,再到應(yīng)用架構(gòu)層面,涉及到的知識(shí)很廣泛。
其實(shí),優(yōu)雅上下線中,最重要的還是優(yōu)雅下線。因?yàn)槿绻戮€過(guò)程不優(yōu)雅的話,就會(huì)發(fā)生很多調(diào)用失敗了、服務(wù)找不到等問(wèn)題。所以很多時(shí)候,大家也會(huì)提優(yōu)雅停機(jī)這樣的概念。
本文后面介紹的優(yōu)雅上下線也重點(diǎn)關(guān)注優(yōu)雅停機(jī)的過(guò)程。
操作系統(tǒng)&容器的優(yōu)雅上下線
關(guān)于操作系統(tǒng),我之前有一篇文章專門介紹過(guò)這個(gè)話題,可能大家沒(méi)有注意到,那時(shí)候介紹的主題是為什么不能在線上機(jī)器中隨便執(zhí)行kill -9。
其實(shí),這背后的思考就是優(yōu)雅上下線。
我們知道,kill -9之所以不建議使用,是因?yàn)閗ill -9特別強(qiáng)硬,系統(tǒng)會(huì)發(fā)出SIGKILL信號(hào),他要求接收到該信號(hào)的程序應(yīng)該立即結(jié)束運(yùn)行,不能被阻塞或者忽略。
這個(gè)過(guò)程顯然是不優(yōu)雅的,因?yàn)閼?yīng)用立刻停止的話,就沒(méi)辦法做收尾動(dòng)作。而更優(yōu)雅的方式是kill -15。
當(dāng)使用kill -15時(shí),系統(tǒng)會(huì)發(fā)送一個(gè)SIGTERM的信號(hào)給對(duì)應(yīng)的程序。當(dāng)程序接收到該信號(hào)后,具體要如何處理是自己可以決定的。
kill -15會(huì)通知到應(yīng)用程序,這就是操作系統(tǒng)對(duì)于優(yōu)雅上下線的最基本的支持。
以前,在操作系統(tǒng)之上就是應(yīng)用程序了,但是,自從容器化技術(shù)推出之后,在操作系統(tǒng)和應(yīng)用程序之間,多了一個(gè)容器層,而Docker、k8s等容器其實(shí)也是支持優(yōu)雅上下線的。
如Docker中同樣提供了兩個(gè)命令, docker stop 和 docker kill
docker stop就像kill -15一樣,他會(huì)向容器內(nèi)的進(jìn)程發(fā)送SIGTERM信號(hào),在10S之后(可通過(guò)參數(shù)指定)再發(fā)送SIGKILL信號(hào)。
而docker kill就像kill -9,直接發(fā)送SIGKILL信號(hào)。
JVM的優(yōu)雅上下線
在操作系統(tǒng)、容器等對(duì)優(yōu)雅上下線有了基本的支持之后,在接收到docker stop、kill -15等命令后,會(huì)通知應(yīng)用進(jìn)程進(jìn)行進(jìn)程關(guān)閉。
而Java應(yīng)用在運(yùn)行時(shí)就是一個(gè)獨(dú)立運(yùn)行的進(jìn)程,這個(gè)進(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)閉過(guò)程中,JVM可以做一些清理動(dòng)作,比如刪除臨時(shí)文件。
當(dāng)然,開發(fā)者也是可以自定義做一些額外的事情的,比如通知應(yīng)用框架優(yōu)雅上下線操作。
而這種機(jī)制是通過(guò)JDK中提供的shutdown hook實(shí)現(xiàn)的。JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注冊(cè)一個(gè)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
 
控制臺(tái)輸出內(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)程的時(shí)候,程序會(huì)先執(zhí)行我注冊(cè)的shutdownHook,然后再退出,并且會(huì)給出一個(gè)提示:interrupted by signal 15: SIGTERM
Spring的優(yōu)雅上下線
有了JVM提供的shutdown hook之后,很多框架都可以通過(guò)這個(gè)機(jī)制來(lái)做優(yōu)雅下線的支持。
比如Spring,他就會(huì)向JVM注冊(cè)一個(gè)shutdown hook,在接收到關(guān)閉通知的時(shí)候,進(jìn)行bean的銷毀,容器的銷毀處理等操作。
同時(shí),作為一個(gè)成熟的框架,Spring也提供了事件機(jī)制,可以借助這個(gè)機(jī)制實(shí)現(xiàn)更多的優(yōu)雅上下線功能。
ApplicationListener是Spring事件機(jī)制的一部分,與抽象類ApplicationEvent類配合來(lái)完成ApplicationContext的事件機(jī)制。
開發(fā)者可以實(shí)現(xiàn)ApplicationListener接口,監(jiān)聽(tīng)到 Spring 容器的關(guān)閉事件(ContextClosedEvent),來(lái)做一些特殊的處理:
- @Component
 - public class MyListener implements ApplicationListener<ContextClosedEvent> {
 - @Override
 - public void onApplicationEvent(ContextClosedEvent event) {
 - // 做容器關(guān)閉之前的清理工作
 - }
 - }
 
Dubbo的優(yōu)雅上下線
因?yàn)镾pring中提供了ApplicationListener接口,幫助我們來(lái)監(jiān)聽(tīng)容器關(guān)閉事件,那么,很多web容器、框架等就可以借助這個(gè)機(jī)制來(lái)做自己的優(yōu)雅上下線操作。
如tomcat、dubbo等都是這么做的。
這里簡(jiǎn)答說(shuō)一下Dubbo的,在Dubbo的官網(wǎng)中,有關(guān)于優(yōu)雅停機(jī)的介紹:
應(yīng)用在停機(jī)時(shí),接收到關(guān)閉通知時(shí),會(huì)先把自己標(biāo)記為不接受(發(fā)起)新請(qǐng)求,然后再等待10s(默認(rèn)是10秒)的時(shí)候,等執(zhí)行中的線程執(zhí)行完。
那么,之所以他能做這些事,是因?yàn)閺牟僮飨到y(tǒng)、到JVM、到Spring等都對(duì)優(yōu)雅停機(jī)做了很好的支持。
關(guān)于Dubbo各個(gè)版本中具體是如何借助JVM的shutdown hook機(jī)制、或者說(shuō)Spring的事件機(jī)制的優(yōu)雅停機(jī),我的一位同事的一篇文章介紹的很清晰,大家可以看下:
https://www.cnkirito.moe/dubbo-gracefully-shutdown/
在從Dubbo 2.5 到 Dubbo 2.7介紹了歷史版本中,Dubbo為了解決優(yōu)雅上下線問(wèn)題所遇到的問(wèn)題和方案。
目前,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等對(duì)優(yōu)雅停機(jī)的支持。
可以看到,一個(gè)簡(jiǎn)單的優(yōu)雅停機(jī)功能,上下游需要這么多底層基礎(chǔ)設(shè)施和上層應(yīng)用的支持。
相信通過(guò)學(xué)習(xí)本文,你一定對(duì)優(yōu)雅上下線有了更多的了解。
除此之外,我還希望你,通過(guò)本文以后,遇到一些實(shí)際問(wèn)題的時(shí)候,可以想到文中提到的shutdown hook機(jī)制、Spring的event機(jī)制。很多時(shí)候,這些機(jī)制都能幫助我們解決很多問(wèn)題。
我在工作中,就有很多次使用過(guò)這樣的機(jī)制的實(shí)例,后面有機(jī)會(huì)給大家介紹幾個(gè)實(shí)例。
好了,本文的全部?jī)?nèi)容就是這么多啦,如果對(duì)你有幫助,記得一鍵三連哦~
參考 :
https://zhuanlan.zhihu.com/p/29093407
https://www.cnkirito.moe/dubbo-gracefully-shutdown/

















 
 
 














 
 
 
 