hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

Dubbo 文雅停机演进之路

2019-11-18杂谈搜奇网33°c
A+ A-

一、媒介

在 『ShutdownHook- Java 文雅停机处置惩罚计划』 一文中我们聊到了 Java 完成文雅停机道理。接下来我们就跟依据上面知识点,深切 Dubbo 内部,去相识一下 Dubbo 怎样完成文雅停机。

二、Dubbo 文雅停机待处置惩罚的题目

为了完成文雅停机,Dubbo 须要处置惩罚一些题目:

  1. 新的要求不能再发往正在停机的 Dubbo 效劳供应者。
  2. 若封闭效劳供应者,已吸收到效劳要求,须要处置惩罚终了才下线效劳。
  3. 若封闭效劳消耗者,已发出的效劳要求,须要守候响应返回。

处置惩罚以上三个题目,才使停机对营业影响下降到最低,做到文雅停机。

三、2.5.X

Dubbo 文雅停机在 2.5.X 版本完成比较完全,这个版本的完成相对简朴,比较轻易明白。所以我们先以 Dubbo 2.5.X 版本源码为基本,先来看一下 Dubbo 怎样完成文雅停机。

3.1、文雅停机整体完成计划

文雅停机进口类位于 AbstractConfig 静态代码中,源码以下:

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

这里将会注册一个 ShutdownHook ,一旦运用停机将会触发挪用 ProtocolConfig.destroyAll()

ProtocolConfig.destroyAll()源码以下:

public static void destroyAll() {
    // 防备并发挪用
    if (!destroyed.compareAndSet(false, true)) {
        return;
    }
    // 先注销注册中间
    AbstractRegistryFactory.destroyAll();

    // Wait for registry notification
    try {
        Thread.sleep(ConfigUtils.getServerShutdownTimeout());
    } catch (InterruptedException e) {
        logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
    }

    ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
    // 再注销 Protocol
    for (String protocolName : loader.getLoadedExtensions()) {
        try {
            Protocol protocol = loader.getLoadedExtension(protocolName);
            if (protocol != null) {
                protocol.destroy();
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
    }
    }

从上面能够看到,Dubbo 文雅停机重要分为两步:

  1. 注销注册中间
  2. 注销一切 Protocol

3.2、注销注册中间

注销注册中间源码以下:

public static void destroyAll() {
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("Close all registries " + getRegistries());
    }
    // Lock up the registry shutdown process
    LOCK.lock();
    try {
        for (Registry registry : getRegistries()) {
            try {
                registry.destroy();
            } catch (Throwable e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        REGISTRIES.clear();
    } finally {
        // Release the lock
        LOCK.unlock();
    }
}

这个要领将会将会注销内部生成注册中间效劳。注销注册中间内部逻辑比较简朴,这里就不再深切源码,直接用图片展现。

ps: 源码位于:AbstractRegistry

以 ZK 为例,Dubbo 将会删除其对应效劳节点,然后作废定阅。由于 ZK 节点信息变动,ZK 效劳端将会关照 dubbo 消耗者下线该效劳节点,末了再封闭效劳与 ZK 衔接。

经由历程注册中间,Dubbo 能够实时关照消耗者下线效劳,新的要求也不再发往下线的节点,也就处置惩罚上面提到的第一个题目:新的要求不能再发往正在停机的 Dubbo 效劳供应者。

然则这里照样存在一些弊病,由于收集的断绝,ZK 效劳端与 Dubbo 衔接能够存在肯定耽误,ZK 关照能够不能在第一时刻关照消耗端。考虑到这类状况,在注销注册中间以后,到场守候进制,代码以下:

// Wait for registry notification
try {
    Thread.sleep(ConfigUtils.getServerShutdownTimeout());
} catch (InterruptedException e) {
    logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
}

默许守候时刻为 10000ms,能够经由历程设置 dubbo.service.shutdown.wait 掩盖默许参数。10s 只是一个经验值,能够依据现实情设置。不过这个守候时刻设置比较考究,不能设置成太短,太短将会致使消耗端还未收到 ZK 关照,供应者就停机了。也不能设置太长,太长又会致使关停运用时刻边长,影响宣布体验。

3.3、注销 Protocol

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);
    }
}

loader#getLoadedExtensions 将会返回两种 Protocol 子类,分别为 DubboProtocolInjvmProtocol

DubboProtocol 用与效劳端要求交互,而 InjvmProtocol 用于内部要求交互。假如运用挪用本身供应 Dubbo 效劳,不会再实行收集挪用,直接实行内部要领。

这里我们重要来剖析一下 DubboProtocol 内部逻辑。

DubboProtocol#destroy 源码:

public void destroy() {
    // 封闭 Server
    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(ConfigUtils.getServerShutdownTimeout());
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
    }
    // 封闭 Client
    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(ConfigUtils.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(ConfigUtils.getServerShutdownTimeout());
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
    }
    stubServiceMethodsMap.clear();
    super.destroy();
}

Dubbo 默许运用 Netty 作为其底层的通信框架,分为 ServerClientServer 用于吸收其他消耗者 Client 发出的要求。

上面源码中起首封闭 Server ,住手吸收新的要求,然后再封闭 Client。如许做就下降效劳被消耗者挪用的能够性。

3.4、封闭 Server

起首将会挪用 HeaderExchangeServer#close,源码以下:

public void close(final int timeout) {
    startClose();
    if (timeout > 0) {
        final long max = (long) timeout;
        final long start = System.currentTimeMillis();
        if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
       // 发送 READ_ONLY 事宜
            sendChannelReadOnlyEvent();
        }
        while (HeaderExchangeServer.this.isRunning()
                && System.currentTimeMillis() - start < max) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
    // 封闭定时心跳检测
    doClose();
    server.close(timeout);
}

private void doClose() {
    if (!closed.compareAndSet(false, true)) {
        return;
    }
    stopHeartbeatTimer();
    try {
        scheduled.shutdown();
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

这里将会向效劳消耗者发送 READ_ONLY 事宜。消耗者接收以后,主动消除这个节点,将要求发往其他平常节点。如许又进一步下降了注册中间关照耽误带来的影响。

接下来将会封闭心跳检测,封闭底层通信框架 NettyServer。这里将会挪用 NettyServer#close 要领,这个要领现实在 AbstractServer 处完成。

AbstractServer#close 源码以下:

public void close(int timeout) {
    ExecutorUtil.gracefulShutdown(executor, timeout);
    close();
}

这里起首封闭营业线程池,这个历程将会尽能够将线程池中的使命实行终了,再封闭线程池,末了在再封闭 Netty 通信底层 Server。

Dubbo 默许将会把要求/心跳等要求派发到营业线程池中处置惩罚。

封闭 Server,文雅守候线程池封闭,处置惩罚了上面提到的第二个题目:若封闭效劳供应者,已吸收到效劳要求,须要处置惩罚终了才下线效劳。

Dubbo 效劳供应者封闭流程如图:

ps:为了轻易调试源码,附上 Server 封闭挪用联。

DubboProtocol#destroy
    ->HeaderExchangeServer#close
        ->AbstractServer#close
            ->NettyServer#doClose                

3.5 封闭 Client

Client 封闭体式格局大抵同 Server,这里重要引见一下处置惩罚已发出要求逻辑,代码位于HeaderExchangeChannel#close

// graceful close
public void close(int timeout) {
    if (closed) {
        return;
    }
    closed = true;
    if (timeout > 0) {
        long start = System.currentTimeMillis();
    // 守候发送的要求响应信息
        while (DefaultFuture.hasFuture(channel)
                && System.currentTimeMillis() - start < timeout) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
    close();
}

封闭 Client 的时刻,假如还存在未收到响应的信息要求,将会守候肯定时刻,直到确认一切要求都收到响应,或许守候时刻凌驾超时时刻。

ps:Dubbo 要求会暂存在 DefaultFuture Map 中,所以只需简朴推断一下 Map 就可以晓得要求是不是都收到响应。

经由历程这一点我们就处置惩罚了第三个题目:若封闭效劳消耗者,已发出的效劳要求,须要守候响应返回。

Dubbo 文雅停机整体流程如图所示。

ps: Client 封闭挪用链以下所示:

DubboProtocol#close
    ->ReferenceCountExchangeClient#close
        ->HeaderExchangeChannel#close
            ->AbstractClient#close

四、2.7.X

Dubbo 平常与 Spring 框架一同运用,2.5.X 版本的停机历程能够致使文雅停机失效。这是由于 Spring 框架封闭时也会触发响应的 ShutdownHook 事宜,注销相干 Bean。这个历程若 Spring 领先实行停机,注销相干 Bean。而这时候 Dubbo 封闭事宜中引用到 Spring 中 Bean,这就将会使停机历程当中发作异常,致使文雅停机失效。

为相识决该题目,Dubbo 在 2.6.X 版本最先重构这部份逻辑,而且不停迭代,直到 2.7.X 版本。

新版本新增 ShutdownHookListener,继续 Spring ApplicationListener 接口,用以监听 Spring 相干事宜。这里 ShutdownHookListener 仅仅监听 Spring 封闭事宜,当 Spring 最先封闭,将会触发 ShutdownHookListener 内部逻辑。


public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
    private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            // 注册 ShutdownHook
            ((ConfigurableApplicationContext) context).registerShutdownHook();
            // 作废 AbstractConfig 注册的 ShutdownHook 事宜
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
        BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
    }
    // 继续 ApplicationListener,这个监听器将会监听容器封闭事宜
    private static class ShutdownHookListener implements ApplicationListener {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextClosedEvent) {
                DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();
                shutdownHook.doDestroy();
            }
        }
    }
}

当 Spring 框架最先初始化以后,将会触发 SpringExtensionFactory 逻辑,以后将会注销 AbstractConfig 注册 ShutdownHook,然后增添 ShutdownHookListener。如许就圆满处置惩罚上面『双 hook』 题目。

五、末了

文雅停机看起来完成不难,然则内里设想细枝末节却异常多,一个点完成有题目,就会致使文雅停机失效。假如你也正在完成文雅停机,无妨参考一下 Dubbo 的完成逻辑。

Dubbo 系列文章引荐

1.假如有人问你 Dubbo 中注册中间工作道理,就把这篇文章给他
2.不晓得怎样完成效劳的动态发明?快来看看 Dubbo 是怎样做到的
3.Dubbo Zk 数据结构
4.缘起 Dubbo ,讲讲 Spring XML Schema 扩大机制

协助文章

1、强烈引荐浏览 kirito 大神文章:一文聊透 Dubbo 文雅停机

迎接关注我的民众号:顺序通事,取得一样平常干货推送。假如您对我的专题内容感兴趣,也能够关注我的博客:studyidea.cn

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
Dubbo 文雅停机演进之路

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282224.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>