Dubbo源碼學(xué)習(xí)--優(yōu)雅停機(jī)原理及在SpringBoot中遇到的問題

相關(guān)文章:

Dubbo源碼學(xué)習(xí)文章目錄

前言

主要是前一陣子換了工作,第一個任務(wù)就是解決目前團(tuán)隊在 Dubbo 停機(jī)時產(chǎn)生的問題,同時最近又看了一下 Dubbo 的源碼,想重新寫一下 Dubbo 相關(guān)的文章。

優(yōu)雅停機(jī)原理

對于一個 java 應(yīng)用,如果想在關(guān)閉應(yīng)用時,執(zhí)行一些釋放資源的操作一般是通過注冊一個 ShutDownHook ,當(dāng)關(guān)閉應(yīng)用時,不是調(diào)用 kill -9 命令來直接終止應(yīng)用,而是通過調(diào)用 kill -15 命令來觸發(fā)這個 ShutDownHook 進(jìn)行停機(jī)前的釋放資源操作。
對于 Dubbo 來說,需要停機(jī)前執(zhí)行的操作包括兩部分:

  1. 對于服務(wù)的提供者,需要通知注冊中心來把自己在服務(wù)列表中摘除掉。
  2. 根據(jù)所配置的協(xié)議,關(guān)閉協(xié)議的端口和連接。

而何為優(yōu)雅停機(jī)呢?就是在集群環(huán)境下,有一個應(yīng)用停機(jī),并不會出現(xiàn)異常。下面來看一下 Dubbo 是怎么做的。

注冊ShutDownHook

Duubo 在 AbstractConfig 的靜態(tài)構(gòu)造函數(shù)中注冊了 JVM 的 ShutDownHook,而 ShutdownHook 主要是調(diào)用 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. 在1這個點調(diào)用AbstractRegistryFactory.destroyAll(),其內(nèi)部會對每個注冊中心進(jìn)行 destroy 操作,進(jìn)而把注冊到注冊中心的服務(wù)取消注冊。
  2. 2這個點是最近 Dubbo 版本新增的操作,用來增強(qiáng) Dubbo 的優(yōu)雅停機(jī),在老版本的 Dubbo 其邏輯是直接摘除服務(wù)列表,關(guān)閉暴露的連接,因為服務(wù)取消注冊,注冊中心是異步的通知消費者變更其存放在自己內(nèi)存中的提供者列表。因為是異步操作,當(dāng)調(diào)用量比較大的應(yīng)用時消費者會拿到已經(jīng)關(guān)閉連接點的提供者進(jìn)行調(diào)用,這時候就會產(chǎn)生大量的錯誤,而2這個點就是通過Sleep 來延遲關(guān)閉協(xié)議暴露的連接。
  3. 因為 Dubbo 的擴(kuò)展機(jī)制 ,loader.getLoadedExtensions() 會獲取到已使用的所有協(xié)議,遍歷調(diào)用 destroy 方法來關(guān)閉其打開的端口和連接。

而在第3步會在 Exchange 層 對所有打開的連接進(jìn)行判斷其有沒有正在執(zhí)行的請求,如果有會自旋 Sleep 直到設(shè)置的 ServerShutdownTimeout 時間或者已經(jīng)沒有正在執(zhí)行的請求了才會關(guān)閉連接,源碼如下:

  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); //正在的關(guān)閉連接
   }

在 SpringBoot 應(yīng)用中存在的問題

簡單的描述一下問題:就是在應(yīng)用停機(jī)時,瞬間會產(chǎn)生大量的報錯,比如拿到的數(shù)據(jù)庫連接已經(jīng)關(guān)閉等問題。 其實一看就知道是在停機(jī)時還存在正在處理的請求,而這些請求所需要的資源被 Spring 容器所關(guān)閉導(dǎo)致的。原來在SpringBoot 啟動時會在 refreshContext 操作也注冊一個 ShotdownHook 來關(guān)閉Spring容器。

    private void refreshContext(ConfigurableApplicationContext context) {
       this.refresh(context);
       if (this.registerShutdownHook) {
           try {
               context.registerShutdownHook();
           } catch (AccessControlException var3) {
           }
       }
   }

而要解決這個問題就需要取消掉這個 ShutDownHook ,然后再 Dubbo 優(yōu)雅停機(jī)執(zhí)行后關(guān)閉 Spring 容器。具體的修改如下:

  1. 在啟動Main方法中,修改SpringBoot 啟動代碼,取消注冊ShutDownHook。
    public static void main(String[] args) {
       SpringApplication app = new SpringApplication(XxxApplication.class);
       app.setRegisterShutdownHook(false);
       app.run(args);
   }
  1. 注冊一個Bean 來讓 Dubbo 關(guān)閉后關(guān)閉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);
   }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評論 6 342
  • 喜歡田園,畫的時候感覺回歸了大自然 臨摹自網(wǎng)上的圖片 過程拍的不多 自上而下,自遠(yuǎn)而近畫的 花海先鋪了一層明黃,再...
    鮑鮑不愛說話閱讀 1,847評論 14 30
  • 黎明的霞光閱讀 145評論 0 0
  • 下午,麗姐發(fā)的一條消息在群里炸了鍋:“剛剛才知道,我們一個運(yùn)營的妹子得了腦膜炎。她經(jīng)常加班,很辛苦。大家千萬不要拿...
    某某聶閱讀 947評論 2 2

友情鏈接更多精彩內(nèi)容