zoukankan      html  css  js  c++  java
  • dubbo优雅停机

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

    这里先讲一下什么是钩子程序:
    在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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    学习Java的第八天
    学习Java的第七天
    学习Java的第六天
    学习Java的第五天
    学习Java的第四天
    学习Java的第三天
    学习Java的第二天
    学习Java的第一天
    第九天
    第八次
  • 原文地址:https://www.cnblogs.com/sidesky/p/12669063.html
Copyright © 2011-2022 走看看