dubbo学习四-优雅停机

目录

 

1 Dubbo优雅停机概述

2 Dubbo优雅停机实现

2.1 AbstractRegistryFactory.destroyAll

2.1.1 registry.destroy(ZookeeperRegistry):

2.1.2 registry.destroy(FailbackRegistry)

2.1.3 通用destroy

2.2 protocol.destroy

2.2.1 RegistryProtocol.destroy

2.2.2 DubboProtocol.destroy


1 Dubbo优雅停机概述

优雅停机主要应用于线上服务版本迭代的过程。如果老版本服务没有正常关闭的话会造成内存清理问题。
Dubbo优雅停机的实现依赖于JDK的ShutdownHook方法,这个方法在如下场景会被调用

  • 程序正常退出
  • 程序中使用System.exit()退出JVM
  • 系统发生OutofMemory异常
  • 使用kill pid干掉JVM进程的时候(kill -9时候是不能触发ShutdownHook生效的

2 Dubbo优雅停机实现

在AbstractConfig中的静态代码块添加了ShutdownDownHook机制

static {
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
        public void run() {
            if (logger.isInfoEnabled()) {
                logger.info("Run shutdown hook now.");
            }
            ProtocolConfig.destroyAll();
        }
    }, "DubboShutdownHook"));
}

public static void destroyAll() {
    if (!destroyed.compareAndSet(false, true)) {
        return;
    }
    // 关闭并清理注册中心
    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);
        }
    }
}

2.1 AbstractRegistryFactory.destroyAll

public static void destroyAll() {
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("Close all registries " + getRegistries());
    }
    // 加锁,防止关闭多次
    LOCK.lock();
    try {
        // 关闭所有已创建的注册中心
        for (Registry registry : getRegistries()) {
            try {
                registry.destroy();
            } catch (Throwable e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        REGISTRIES.clear();
    } finally {
        // 释放锁
        LOCK.unlock();
    }
}

2.1.1 registry.destroy(ZookeeperRegistry):

public void destroy() {
    super.destroy();
    try {
        // 关闭zk客户端
        zkClient.close();
    } catch (Exception e) {
        logger.warn("Failed to close zookeeper client " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

2.1.2 registry.destroy(FailbackRegistry)

public void destroy() {
    if (!canDestroy()){
        return;
    }
    // 首先要明白FailbackRegistry的核心就在于失败重试,所以这一层的关闭只要关闭retryFuture就可以
    super.destroy();
    try {
        retryFuture.cancel(true);
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

2.1.3 通用destroy

public void destroy() {
    if (!destroyed.compareAndSet(false, true)) {
        return;
    }

    if (logger.isInfoEnabled()) {
        logger.info("Destroy registry:" + getUrl());
    }

    // 移除内存中已经注册的服务
    Set<URL> destroyRegistered = new HashSet<URL>(getRegistered());
    if (!destroyRegistered.isEmpty()) {
        for (URL url : new HashSet<URL>(getRegistered())) {
            if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
                try {
                    //从已注册的列表中移除该URL
                    unregister(url);
                    if (logger.isInfoEnabled()) {
                        logger.info("Destroy unregister url " + url);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                }
            }
        }
    }

    // 取消所有的服务订阅
    Map<URL, Set<NotifyListener>> destroySubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
    if (!destroySubscribed.isEmpty()) {
        for (Map.Entry<URL, Set<NotifyListener>> entry : destroySubscribed.entrySet()) {
            URL url = entry.getKey();
            for (NotifyListener listener : entry.getValue()) {
                try {
                    //将listener从订阅者对应的listener集合中移除(监听的服务变更将不再进行通知)
                    unsubscribe(url, listener);
                    if (logger.isInfoEnabled()) {
                        logger.info("Destroy unsubscribe url " + url);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                }
            }
        }
    }
}

梳理一下AbstractRegistryFactory.destroyAll方法(没有先后顺序关系):
1 关闭zk客户端
2 由于服务注册zk的时候创建的临时节点,这些临时节点也会删除
3 关闭retryFuture
4 移除内存中已经注册的服务
5 取消所有的服务订阅

2.2 protocol.destroy

Protocol的实现类中主要有两个,RegistryProtocol和DubboProtocol

2.2.1 RegistryProtocol.destroy

public void destroy() {
    List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(bounds.values());
    for (Exporter<?> exporter : exporters) {
        exporter.unexport();
    }
    bounds.clear();
}

DubboExporter.unexport

public void unexport() {
    super.unexport();
    // 把内存中的export移除
    exporterMap.remove(key);
}

AbstractExporter.unexport

public void unexport() {
    if (unexported) {
        return;
    }
    unexported = true;
    getInvoker().destroy();
}

DubboInvoker.destroy

public void destroy() {
    if (super.isDestroyed()) {
        return;
    } else {
        // 加锁,防止并发destroy
        destroyLock.lock();
        try {
            if (super.isDestroyed()) {
                return;
            }
            // 关闭invoker和exchange
            super.destroy();
            if (invokers != null) {
                invokers.remove(this);
            }
            for (ExchangeClient client : clients) {
                try {
                    client.close(getShutdownTimeout());
                } catch (Throwable t) {
                    logger.warn(t.getMessage(), t);
                }
            }

        } finally {
            destroyLock.unlock();
        }
    }
}

注册协议只关心跟注册有关的内容,而Exporter和Invoker都是RegistryProtocol下层的内容,所以在调用注册协议关闭服务的时候会将其下的Exporter和Invoker都关闭掉。

2.2.2 DubboProtocol.destroy

public void destroy() {
    //关停所有的Server,作为provider将不再接收新的请求
    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());
                }
                // HeaderExchangeServer中会停止发送心态的任务,关闭channel
                server.close(getServerShutdownTimeout());
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
    }

    //关停所有的Client,作为consumer将不再发送新的请求
    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());
                }
                // HeaderExchangeClient中会停止发送心态的任务,关闭channel
                client.close(getServerShutdownTimeout());
            } 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(getServerShutdownTimeout());
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
    }
    stubServiceMethodsMap.clear();
    super.destroy();
}

梳理一下protocol.destroy流程
1 把内存中的export移除
2 关闭RegistryProtocol管理的invoker和exchange
3 关停所有的Server,作为provider将不再接收新的请求
4 关停所有的Client,作为consumer将不再发送新的请求

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值