Dubbo ShutdownHook 優雅停機整理


Dubbo是通過JDK的ShutdownHook來完成優雅停機的
所以如果用戶使用 kill -9 PID 等強制關閉命令,是不會執行優雅停機的,只有通過 kill PID時,才會執行

Dubbo 中實現的優雅停機機制主要包含6個步驟:
(1)收到 kill PID 進程退出信號,Spring 容器會觸發容器銷毀事件。
(2)provider 端會注銷服務元數據信息(刪除ZK節點)。
(3)consumer 會拉取最新服務提供者列表。
(4)provider 會發送 readonly 事件報文通知 consumer 服務不可用。
(5)服務端等待已經執行的任務結束並拒絕新任務執行。


 
Dubbo優雅停機機制

Spring 容器下 Dubbo 的優雅停機

由於現在大多數開發者選擇使用 Spring 構建 Dubbo 應用,Spring 框架本身也依賴於 shutdown hook 執行優雅停機,並且與 Dubbo 的優雅停機會並發執行,而 Dubbo 的一些 Bean 受 Spring 托管,當 Spring 容器優先關閉時,會導致 Dubbo 的優雅停機流程無法獲取相關的 Bean 而報錯,從而優雅停機失效。Dubbo 開發者們迅速意識到了 shutdown hook 並發執行的問題,開始了一系列的補救措施。

Dubbo 2.6.3 中新增了ShutdownHookListener類

    private static class ShutdownHookListener implements ApplicationListener {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextClosedEvent) {
                // we call it anyway since dubbo shutdown hook make sure its destroyAll() is re-entrant.
                // pls. note we should not remove dubbo shutdown hook when spring framework is present, this is because
                // its shutdown hook may not be installed.
                DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();
                shutdownHook.destroyAll();
            }
        }
    }

Spring 先發布ContextClosedEvent事件,調用關閉 Dubbo 應用的鈎子,然后再關閉自身的 Spring 應用。從而解決了上述因 Spring 鈎子早於 Dubbo 鈎子執行導致 Dubbo 優雅停機失效的問題。

dubbo 2.6.3 版本,也有缺點,因為它仍然保留了原先的 Dubbo 注冊 JVM 關閉鈎子,只是這個鈎子的報錯不會影響 Spring 鈎子中關閉 Dubbo 應用的執行,因為它們是兩個獨立的線程。但是 Dubbo 注冊 JVM 關閉鈎子的操作難免有點多余,所以網上能見到類似remove dubbo JVM 鈎子的方案。



/**
 * @author liuliu
 * this working for dubbo 2.6.+
 */
@Configuration
public class ShutdownHookListener implements ApplicationListener {
    
    private static final Logger log = LoggerFactory.getLogger(ShutdownHookListener.class);
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartedEvent) {
            Runtime.getRuntime().removeShutdownHook(DubboShutdownHook.getDubboShutdownHook());
            log.info("dubbo default shutdown hook removed,will be managed by spring");
        } else if (event instanceof ContextClosedEvent) {
            log.info("start destroy dubbo on spring close event");
            DubboShutdownHook.getDubboShutdownHook().destroyAll();
            log.info("dubbo destroy finished");
        }
    }
}

Dubbo 2.7 方案

在 dubbo 2.7.x 版本中,通過SpringExtensionFactory類移除了該操作。

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);
    }
}

 

該方案完美的解決了上述並發鈎子問題,直接取消掉 Dubbo 的 JVM 的鈎子。

業務線程池如何優雅關閉?

線程池的錯誤關閉很有可能會造成,dubbo的優雅關機無法關閉。所以在關閉線程池的時候要注意,下面就介紹下錯誤的和正確的做法。
錯誤的做法:

Runtime.getRuntime().addShutdownHook(new Thread(){
// 線程池雖然關閉,但是隊列中的任務任然繼續執行,所以用 shutdown()方式關閉線程池時需要考慮是否是你想要的效果
//如果希望立即停止,拋棄隊列中的任務,可以使用shutdownNow()
threadPoolExecutor.shutdown();
});

 

上面的代碼忽視了多個鈎子函數是並發執行的問題,線程池的業務邏輯可能需要數據源鏈接、redis鏈接等,但是這個時候有可能數據源已經關閉了。
正確的做法:

 @PostConstruct
    public void afterPropertiesSet() throws Exception {
    DEAL_EVENT_THREAD_POOL = ThreadPoolUtils.newExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
                QUEUE_MAX_SIZE, "DealEventLogTask-service");
    }

    @PreDestroy
    public void destroy() throws Exception {
        ThreadPoolUtils.stop(DEAL_EVENT_THREAD_POOL);
    }

 

文章轉載自:

作者:liuliuzo
鏈接:https://www.jianshu.com/p/81adc725be65
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。





免責聲明!

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



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