Java 杂谈污力_Java

Dubbo服务之优雅停机

2018-07-27  本文已影响55人  jerrik

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

这里先讲一下什么是钩子程序:
在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();
    }
总结

线上服务不要轻易的kill -9,可进行kill触发应用的钩子程序,做相关的资源清理,如果一直关闭不掉,最终可以通过kill -9执行。另外网上也有说Dubbo不支持优雅停机的言论,大家可以借鉴一下:
https://my.oschina.net/u/1398931/blog/790709
http://frankfan915.iteye.com/blog/2254097

上一篇下一篇

猜你喜欢

热点阅读