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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自www.cnblogs.com/sidesky/p/12669063.html