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()关闭导出的服务

总结

线上服务不要轻易的kill -9,可进行kill触发应用的钩子程序,做相关的资源清理,如果一直关闭不掉,最终可以通过kill -9执行。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值