服務提供方停止時,先標記為不接收新請求,新請求過來時直接報錯,讓客戶端重試其它機器。然后,檢測線程池中的線程是否正在運行,如果有,等待所有線程執行完成,除非超時,則強制關閉。服務消費方停止時,不再發起新的調用請求,所有新的調用在客戶端即報錯。
然后,檢測有沒有請求的響應還沒有返回,等待響應返回,除非超時,則強制關閉。
這里先講一下什么是鈎子程序:
在Java程序中可以通過添加關閉鈎子,實現在程序退出時關閉資源、平滑退出的功能。
使用Runtime.addShutdownHook(Thread hook)方法,可以注冊一個JVM關閉的鈎子,這個鈎子可以在以下幾種場景被調用:
- 程序正常退出
- 使用System.exit()
- 終端使用Ctrl+C觸發的中斷
- 系統關閉
- 使用Kill pid命令干掉進程
我們通過Runtime.getRuntime().addShutdownHook()
注冊一個鈎子,發現被ApplicationShutdownHooks.add(hook)
調用,最后被保存到一個叫HOOKS
的IdentityHashMap當中,那是什么時候觸發鈎子程序的呢?原來ApplicationShutdownHooks
里面有一個靜態塊:
static { try { Shutdown.add(1 /* shutdown hook invocation order */, false /* not registered if shutdown in progress */, new Runnable() { public void run() { runHooks(); } } ); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { hooks = null; } }
最終會調用runHooks
方法。我們查看System.exit()
,其實最終還是會通過ShutDown.exit()->sequence()
進來,然后調用runHooks
調用鈎子程序。那Java是怎么響應kill
命令的呢?竟是通過SignalHandler
來實現的,在openjdk的windows目錄和solaris目錄下都有一個Terminator.java
,里面有這樣一段代碼:
SignalHandler sh = new SignalHandler() { public void handle(Signal sig) { Shutdown.exit(sig.getNumber() + 0200); } }; Signal.handle(new Signal("HUP"), sh); Signal.handle(new Signal("INT"), sh); Signal.handle(new Signal("TERM"), sh);
最后通過void* oldHandler = os::signal(sig, newHandler)
獲取到linux系統的signal信號。
回過頭了看dubbo,可以設置優雅停機超時時間,缺省超時時間是10秒:(超時則強制關閉)
<dubbo:application ...> <dubbo:parameter key="shutdown.timeout" value="60000" /> <!-- 單位毫秒 --> </dubbo:application>
看一下服務端鈎子程序:
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { if (logger.isInfoEnabled()) { logger.info("Run shutdown hook now."); } ProtocolConfig.destroyAll(); } }, "DubboShutdownHook"));
其最終還是調用了ProtocolConfig.destroyAll()
方法:
public static void destroyAll() { AbstractRegistryFactory.destroyAll(); ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class); for (String protocolName : loader.getLoadedExtensions()) { try { Protocol protocol = loader.getLoadedExtension(protocolName); if (protocol != null) { protocol.destroy(); } } catch (Throwable t) { logger.warn(t.getMessage(), t); } } }
加載所有的Protocol
協議,然后循環調用destroy
方法,下面看一下DubboProtocol
的destroy
方法:
public void destroy() { for (String key : new ArrayList<String>(serverMap.keySet())) { ExchangeServer server = serverMap.remove(key); if (server != null) { try { if (logger.isInfoEnabled()) { logger.info("Close dubbo server: " + server.getLocalAddress()); } server.close(getServerShutdownTimeout()); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } for (String key : new ArrayList<String>(referenceClientMap.keySet())) { ExchangeClient client = referenceClientMap.remove(key); if (client != null) { try { if (logger.isInfoEnabled()) { logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress()); } client.close(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } for (String key : new ArrayList<String>(ghostClientMap.keySet())) { ExchangeClient client = ghostClientMap.remove(key); if (client != null) { try { if (logger.isInfoEnabled()) { logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress()); } client.close(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } stubServiceMethodsMap.clear(); super.destroy(); }
- 關閉server
因為服務端是通過DubboProtocol
的openServer
通過Netty開啟服務的,serverMap.put(key, createServer(url))
。當關閉的時候肯定需要要服務進行關閉,釋放端口和系統資源。 - 關閉reference client
共享鏈接,ReferenceCountExchangeClient - 關閉ghost client(官方注釋叫幽靈client)
這個操作只為了防止程序bug錯誤關閉client做的防御措施 - 清空stub方法Map
- suer.destroy
關閉Invoker,將服務設置成不可用。然后通過Exporter.unexport()關閉導出的服務
作者:jerrik
鏈接:https://www.jianshu.com/p/6e4d1ecb0815
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。