Dubbo原始碼學習--優雅停機原理及在SpringBoot中遇到的問題
前言
主要是前一陣子換了工作,第一個任務就是解決目前團隊在 Dubbo 停機時產生的問題,同時最近又看了一下 Dubbo 的原始碼,想重新寫一下 Dubbo 相關的文章。
優雅停機原理
對於一個 java 應用,如果想在關閉應用時,執行一些釋放資源的操作一般是透過註冊一個 ShutDownHook ,當關閉應用時,不是呼叫 kill -9 命令來直接終止應用,而是透過呼叫 kill -15 命令來觸發這個 ShutDownHook 進行停機前的釋放資源操作。
對於 Dubbo 來說,需要停機前執行的操作包括兩部分:
對於服務的提供者,需要通知註冊中心來把自己在服務列表中摘除掉。
根據所配置的協議,關閉協議的埠和連線。
而何為優雅停機呢?就是在叢集環境下,有一個應用停機,並不會出現異常。下面來看一下 Dubbo 是怎麼做的。
註冊ShutDownHook
Duubo 在 AbstractConfig 的靜態建構函式中註冊了 JVM 的 ShutDownHook,而 ShutdownHook 主要是呼叫 ProtocolConfig.destroyAll() ,原始碼如下:
static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { if (logger.isInfoEnabled()) { logger.info("Run shutdown hook now."); } ProtocolConfig.destroyAll(); } }, "DubboShutdownHook")); }
ProtocolConfig.destroyAll()
先看一下 ProtocolConfig.destroyAll() 原始碼:
public static void destroyAll() { if (!destroyed.compareAndSet(false, true)) { return; } AbstractRegistryFactory.destroyAll(); //1. // Wait for registry notification try { Thread.sleep(ConfigUtils.getServerShutdownTimeout()); //2. } catch (InterruptedException e) { logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!"); } ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class); for (String protocolName : loader.getLoadedExtensions()) { try { Protocol protocol = loader.getLoadedExtension(protocolName); if (protocol != null) { protocol.destroy(); //3. } } catch (Throwable t) { logger.warn(t.getMessage(), t); } } }
ProtocolConfig.destroyAll() 有三個比較重要的操作:
在1這個點呼叫AbstractRegistryFactory.destroyAll(),其內部會對每個註冊中心進行 destroy 操作,進而把註冊到註冊中心的服務取消註冊。
2這個點是最近 Dubbo 版本新增的操作,用來增強 Dubbo 的優雅停機,在老版本的 Dubbo 其邏輯是直接摘除服務列表,關閉暴露的連線,因為服務取消註冊,註冊中心是非同步的通知消費者變更其存放在自己記憶體中的提供者列表。因為是非同步操作,當呼叫量比較大的應用時消費者會拿到已經關閉連線點的提供者進行呼叫,這時候就會產生大量的錯誤,而2這個點就是透過Sleep 來延遲關閉協議暴露的連線。
因為 Dubbo 的擴充套件機制 ,loader.getLoadedExtensions() 會獲取到已使用的所有協議,遍歷呼叫 destroy 方法來關閉其開啟的埠和連線。
而在第3步會在 Exchange 層 對所有開啟的連線進行判斷其有沒有正在執行的請求,如果有會自旋 Sleep 直到設定的 ServerShutdownTimeout 時間或者已經沒有正在執行的請求了才會關閉連線,原始碼如下:
public void close(final int timeout) { startClose(); if (timeout > 0) { final long max = (long) timeout; final long start = System.currentTimeMillis(); if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) { sendChannelReadOnlyEvent(); } while (HeaderExchangeServer.this.isRunning() //判斷是否還有正在處理的請求 && System.currentTimeMillis() - start < max) { //判斷是否超時 try { Thread.sleep(10); } catch (InterruptedException e) { logger.warn(e.getMessage(), e); } } } doClose(); server.close(timeout); //正在的關閉連線 }
## 在 SpringBoot 應用中存在的問題
簡單的描述一下問題:就是在應用停機時,瞬間會產生大量的報錯,比如拿到的資料庫連線已經關閉等問題。 其實一看就知道是在停機時還存在正在處理的請求,而這些請求所需要的資源被 Spring 容器所關閉導致的。原來在SpringBoot 啟動時會在 refreshContext 操作也註冊一個 ShotdownHook 來關閉Spring容器。
private void refreshContext(ConfigurableApplicationContext context) { this.refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException var3) { } } }
而要解決這個問題就需要取消掉這個 ShutDownHook ,然後再 Dubbo 優雅停機執行後關閉 Spring 容器。具體的修改如下:
在啟動Main方法中,修改SpringBoot 啟動程式碼,取消註冊ShutDownHook。
public static void main(String[] args) { SpringApplication app = new SpringApplication(XxxApplication.class); app.setRegisterShutdownHook(false); app.run(args); }
註冊一個Bean 來讓 Dubbo 關閉後關閉Spring容器。
public class SpringShutdownHook { private static final Logger logger = LoggerFactory.getLogger(SpringShutdownHook.class); @Autowired private ConfigurableApplicationContext configurableApplicationContext; public SpringShutdownHook() { } @PostConstruct public void registerShutdownHook() { logger.info("[SpringShutdownHook] Register ShutdownHook...."); Thread shutdownHook = new Thread() { public void run() { try { int timeOut = ConfigUtils.getServerShutdownTimeout(); SpringShutdownHook.logger.info("[SpringShutdownHook] Application need sleep {} seconds to wait Dubbo shutdown", (double)timeOut / 1000.0D); Thread.sleep((long)timeOut); SpringShutdownHook.this.configurableApplicationContext.close(); SpringShutdownHook.logger.info("[SpringShutdownHook] ApplicationContext closed, Application shutdown"); } catch (InterruptedException var2) { SpringShutdownHook.logger.error(var2.getMessage(), var2); } } }; Runtime.getRuntime().addShutdownHook(shutdownHook); } }
作者:
出處:https://www.cnblogs.com/javanoob/p/dubbo_graceful_shutdown.html
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4479/viewspace-2818000/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- SpringBoot2.0 優雅停機Spring Boot
- SOFAJRaft原始碼閱讀-ShutdownHook如何優雅的停機Raft原始碼Hook
- Dubbo的優雅下線原理分析
- loadrunner學習中遇到的問題
- weex學習中遇到的問題
- rocketmq優雅停機往事MQ
- 學習Java中遇到的繼承問題Java繼承
- 學習vue過程中遇到的問題Vue
- 原始碼安裝apache(附遇到的問題及解決)原始碼Apache
- SpringBoot遇到的某些問題Spring Boot
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- Android原始碼學習中遇到的標籤記錄Android原始碼
- 學習本站Laravel教程中遇到的問題筆記Laravel筆記
- 停機問題
- 學習httprunner遇到的問題記錄HTTP
- 如何用 SpringBoot 優雅的寫程式碼Spring Boot
- springboot對接dubbo遇到的巨坑Spring Boot
- dubbo繼承springboot出現的問題繼承Spring Boot
- weex中UISegmentControl實現及遇到的問題UI
- Dubbo原始碼學習之-SPI介紹原始碼
- Dubbo原始碼學習--Rmi協議(八)原始碼協議
- 你的Kubernetes Java應用優雅停機了嗎?Java
- Vue使用中遇到的程式碼問題Vue
- Java 如何實現優雅停服?刨根問底Java
- 學習Python遇到的熱門問題整理Python
- SpringBoot優雅編碼之:Lombok加持Spring BootLombok
- SpringBoot原始碼學習3——SpringBoot啟動流程Spring Boot原始碼
- 手機端頁面在專案中遇到的一些問題及解決辦法
- Dubbo原始碼學習之-服務匯出原始碼
- Dubbo SPI 原始碼學習 & admin安裝(二)原始碼
- 通俗講明白機器學習中的學習問題 - svpino機器學習
- 剛開始學習nlp時遇到的問題
- Dubbo2.7.3版本原始碼學習系列六: Dubbo服務匯出原始碼解析原始碼
- 如何優雅的分析 ThinkPHP 框架原始碼PHP框架原始碼
- Mybatis原始碼初探——優雅精良的骨架MyBatis原始碼
- 圖解原始碼 | SpringBoot中自動配置原理圖解原始碼Spring Boot
- 【機器學習】--迴歸問題的數值優化機器學習優化
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat