dubbo優雅停機


服務提供方停止時,先標記為不接收新請求,新請求過來時直接報錯,讓客戶端重試其它機器。然后,檢測線程池中的線程是否正在運行,如果有,等待所有線程執行完成,除非超時,則強制關閉。服務消費方停止時,不再發起新的調用請求,所有新的調用在客戶端即報錯。
然后,檢測有沒有請求的響應還沒有返回,等待響應返回,除非超時,則強制關閉。

這里先講一下什么是鈎子程序:
在Java程序中可以通過添加關閉鈎子,實現在程序退出時關閉資源、平滑退出的功能。
使用Runtime.addShutdownHook(Thread hook)方法,可以注冊一個JVM關閉的鈎子,這個鈎子可以在以下幾種場景被調用:

  1. 程序正常退出
  2. 使用System.exit()
  3. 終端使用Ctrl+C觸發的中斷
  4. 系統關閉
  5. 使用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方法,下面看一下DubboProtocoldestroy方法:

    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
    因為服務端是通過DubboProtocolopenServer通過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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM