Dubbo|Dubbo ShutdownHook 优雅停机整理

Dubbo优雅停机的机制 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|Dubbo ShutdownHook 优雅停机整理
文章图片
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); }

【Dubbo|Dubbo ShutdownHook 优雅停机整理】文章参考自:
https://www.jianshu.com/p/ebb83e6d35fc
https://www.jianshu.com/p/aa22eac09d8c
https://blog.csdn.net/liubenlong007/article/details/79958076

    推荐阅读