Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

相关文章:

Dubbo源码学习文章目录

前言

主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题,同时最近又看了一下 Dubbo 的源码,想重新写一下 Dubbo 相关的文章。

优雅停机原理

对于一个 java 应用,如果想在关闭应用时,执行一些释放资源的操作一般是通过注册一个 ShutDownHook ,当关闭应用时,不是调用 kill -9 命令来直接终止应用,而是通过调用 kill -15 命令来触发这个 ShutDownHook 进行停机前的释放资源操作。
对于 Dubbo 来说,需要停机前执行的操作包括两部分:

  1. 对于服务的提供者,需要通知注册中心来把自己在服务列表中摘除掉。
  2. 根据所配置的协议,关闭协议的端口和连接。

而何为优雅停机呢?就是在集群环境下,有一个应用停机,并不会出现异常。下面来看一下 Dubbo 是怎么做的。

注册ShutDownHook

Duubo 在 AbstractConfig 的静态构造函数中注册了 JVM 的 ShutDownHook,而 ShutdownHook 主要是调用 ProtocolConfig.destroyAll() ,源码如下:

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

ProtocolConfig.destroyAll()

先看一下 ProtocolConfig.destroyAll() 源码:


  public static void destroyAll() {
        if (!destroyed.compareAndSet(false, true)) {
            return;
        }
        AbstractRegistryFactory.destroyAll();  //1.

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

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

ProtocolConfig.destroyAll() 有三个比较重要的操作:

  1. 在1这个点调用AbstractRegistryFactory.destroyAll(),其内部会对每个注册中心进行 destroy 操作,进而把注册到注册中心的服务取消注册。
  2. 2这个点是最近 Dubbo 版本新增的操作,用来增强 Dubbo 的优雅停机,在老版本的 Dubbo 其逻辑是直接摘除服务列表,关闭暴露的连接,因为服务取消注册,注册中心是异步的通知消费者变更其存放在自己内存中的提供者列表。因为是异步操作,当调用量比较大的应用时消费者会拿到已经关闭连接点的提供者进行调用,这时候就会产生大量的错误,而2这个点就是通过Sleep 来延迟关闭协议暴露的连接。
  3. 因为 Dubbo 的扩展机制 ,loader.getLoadedExtensions() 会获取到已使用的所有协议,遍历调用 destroy 方法来关闭其打开的端口和连接。

而在第3步会在 Exchange 层 对所有打开的连接进行判断其有没有正在执行的请求,如果有会自旋 Sleep 直到设置的 ServerShutdownTimeout 时间或者已经没有正在执行的请求了才会关闭连接,源码如下:

  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)) {
               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); //正在的关闭连接
   }

## 在 SpringBoot 应用中存在的问题

简单的描述一下问题:就是在应用停机时,瞬间会产生大量的报错,比如拿到的数据库连接已经关闭等问题。 其实一看就知道是在停机时还存在正在处理的请求,而这些请求所需要的资源被 Spring 容器所关闭导致的。原来在SpringBoot 启动时会在 refreshContext 操作也注册一个 ShotdownHook 来关闭Spring容器。

    private void refreshContext(ConfigurableApplicationContext context) {
       this.refresh(context);
       if (this.registerShutdownHook) {
           try {
               context.registerShutdownHook();
           } catch (AccessControlException var3) {
           }
       }
   }

而要解决这个问题就需要取消掉这个 ShutDownHook ,然后再 Dubbo 优雅停机执行后关闭 Spring 容器。具体的修改如下:

  1. 在启动Main方法中,修改SpringBoot 启动代码,取消注册ShutDownHook。
    public static void main(String[] args) {
       SpringApplication app = new SpringApplication(XxxApplication.class);
       app.setRegisterShutdownHook(false);
       app.run(args);
   }
  1. 注册一个Bean 来让 Dubbo 关闭后关闭Spring容器。
public class SpringShutdownHook {
   private static final Logger logger = LoggerFactory.getLogger(SpringShutdownHook.class);
   @Autowired
   private ConfigurableApplicationContext configurableApplicationContext;

   public SpringShutdownHook() {
   }

   @PostConstruct
   public void registerShutdownHook() {
       logger.info("[SpringShutdownHook] Register ShutdownHook....");
       Thread shutdownHook = new Thread() {
           public void run() {
               try {
                   int timeOut = ConfigUtils.getServerShutdownTimeout();
                   SpringShutdownHook.logger.info("[SpringShutdownHook] Application need sleep {} seconds to wait Dubbo shutdown", (double)timeOut / 1000.0D);
                   Thread.sleep((long)timeOut);
                   SpringShutdownHook.this.configurableApplicationContext.close();
                   SpringShutdownHook.logger.info("[SpringShutdownHook] ApplicationContext closed, Application shutdown");
               } catch (InterruptedException var2) {
                   SpringShutdownHook.logger.error(var2.getMessage(), var2);
               }

           }
       };
       Runtime.getRuntime().addShutdownHook(shutdownHook);
   }
}

原文地址:https://www.cnblogs.com/javanoob/p/dubbo_graceful_shutdown.html

时间: 2024-10-20 01:14:51

Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题的相关文章

Dubbo源码学习(二)

@Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; } @Adapt

Dubbo 源码分析 - 自适应拓展原理

1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Cluster.LoadBalance 等.有时,有些拓展并非想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载.这听起来有些矛盾.拓展未被加载,那么拓展方法就无法被调用(静态方法除外).拓展方法未被调用,就无法进行加载,这似乎是个死结.不过好在也有相应的解决办法,通过代理模式就可

dubbo源码学习(四)初始化过程细节:解析服务

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. 前面大概介绍了一下关于学习dubbo源码的一些基本知识,今天将真正去看dubbo内部的实现过程,看dubbo的源码前我先把dubbo的用户指南和开发指指南大概的看了一遍,然后从上面找到相应的切入点去看源码,今天将介绍的是dubbo的初始化解析bean的过程.从之前使用过dubbo一些经验,加上http://dubbo.io/的

dubbo源码学习(五)dubbo暴露服务的过程

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点,处理消费者请求,返回处理后的消息给消费者,消费者使用服务时主要是订阅服务的节点,监听zookeeper节点目录,服务端的变化时z

Dubbo源码学习之-Adaptive自适应扩展

前言 最近三周基本处于9-10-6与9-10-7之间,忙碌的节奏机会丢失了自己.除了之前干施工的那段经历,只看参加软件开发以来,前段时间是最繁忙的了.忙的原因,不是要完成的工作量大,而是各种环境问题,各种沟通协调问题.从这个项目,我是体会到了人一多,花在沟通协调上的成本真的会不成比例的放大,制度好,再加上协调好,会极大的提高整体工作效率.怪不得当年华为跟IBM学完工作组织管理制度之后能爆发出如此强劲的战斗力.从另一个角度,也能发觉出为什么大公司招人都比较注重员工的个人实力与团队协作能力,因为如果

Dubbo源码学习之-服务导出

前言 忙的时候,会埋怨学习的时间太少,缺少个人的空间,于是会争分夺秒的工作.学习.而一旦繁忙的时候过去,有时间了之后,整个人又会不自觉的陷入一种懒散的状态中,时间也显得不那么重要了,随便就可以浪费掉几个小时.可见普通人的学习之路要主动地去克服掉很多阻碍,最主要的阻碍还是来自于自身,周期性的不想学习.不自觉的懒散.浅尝辄止的态度.好高骛远贪多的盲目...哎,学习之路,还是要时刻提醒自己,需勤勉致知. 闲话少叙,今天的学习目标是要尽量的了解清楚Dubbo框架中的服务导出功能,先附上Dubbo官网上的

dubbo源码学习(一)基础知识及使用的相关技术

初学dubbo的源码,只做尝试性的去学习,做为自己学习的一个记录,各位看官如果觉得写的有错误或理解的不对,请在留言区告诉我,互相学习.本人能力有限,有大神进入 时请指点. Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合),我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配置就能够实现分布式服务调用,也就

Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)

环境搭建 1. Github上下载 "Dubbo最新发布版本" ,楼主下载版本为2.5.7. 2. cd到源码解压目录,maven编译,命令为: 3. 生成Intellij idea相关配置文件,命令为: 4. 双击运行生成的 文件 Java SPI SPI是Service Provider Int http://p.baidu.com/itopic/main/qlog?qid=ae116162633263383961612700&type=questionlog http:/

Dubbo源码学习--服务发布(ProxyFactory、Invoker)

上文分析了Dubbo服务发布的整体流程,但服务代理生成的具体细节介绍得还不是很详细.下面将会接着上文继续分析.上文介绍了服务代理生成的切入点,如下: Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); 这里的proxyFactory是在ServiceConfig中定义的,是final类型静态变量,赋值后无法进行修改.如下: private static final ProxyFactor