首页 > 其他 > 详细

dubbo优雅停机

时间:2020-04-09 20:46:19      阅读:82      评论:0      收藏:0      [点我收藏+]

服务提供方停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。服务消费方停止时,不再发起新的调用请求,所有新的调用在客户端即报错。
然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。

这里先讲一下什么是钩子程序:
在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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

dubbo优雅停机

原文:https://www.cnblogs.com/sidesky/p/12669063.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!