不优雅的停机: 当进程存在正在运行的线程时,如果直接执行kill -9 pid时,那么这个正在执行的线程被中断,就好像一个机器运行中突然遭遇断电的情况,所导致的结果是造成服务调用的消费端报错,也有可能导致服务端产生脏数据的后果。

1  什么叫优雅停机?

优雅停机:  当进程存在未执行完毕的线程或者存在待释放的资源时,先让这些线程执行完,和释放资源,再关闭进程。

2  JAVA实现优雅停机

   我们可以通过Runtime.getRuntime().addShutdownHook()方法来注册钩子,以保证程序平滑退出。当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。

Runtime.getRuntime().addShutdownHook(

//此线程会在kill pid后执行

new Thread(new Runnable() {

@Override public void run() {

//释放资源,检测未完结线程等...

System.out.println("hook running...");

}

}

3 dubbo实现优雅停机原理

  1) 关闭所有已创建注册中心(zookeeper)的服务

 

2) 关闭服务

 

   3) 关闭客户端

));

4 优雅关闭的代码入口:

类 com.alibaba.dubbo.config.AbstractConfig中的注册的jvm关闭钩子事件:

   static {

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            public void run() {

                if (logger.isInfoEnabled()) {

                    logger.info("Run shutdown hook now.");

                }

                ProtocolConfig.destroyAll(); //方法实现在下面

            }

        }, "DubboShutdownHook"));

}

com.alibaba.dubbo.config.ProtocolConfig的destroyAll方法的实现

  public static void destroyAll() {

         if (!destroyed.compareAndSet(false, true)) {

             return;

         }

         AbstractRegistryFactory.destroyAll();//关闭在zookeeper上面注册的服务

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

             }

         }

     }

5 关闭所有已创建到注册中心(zookeeper)的服务过程

com.alibaba.dubbo.config.ProtocolConfig的destroyAll方法

 public static void destroyAll() {

         if (!destroyed.compareAndSet(false, true)) {

             return;

         }

         AbstractRegistryFactory.destroyAll(); //----->关闭zookeeper上面的服务

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

             }

         }

 }

 

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

        }

    }

其中zookeeper关闭注册服务的实现如下:

com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doUnregister(URL url)的实现:

  protected void doUnregister(URL url) {

         try {

             zkClient.delete(toUrlPath(url));

         } catch (Throwable e) {

             throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);

         }

     }

6 关闭服务的过程:

关闭服务主要设计的类和方法:

com.alibaba.dubbo.remoting.transport.netty.NettyServer

|     

com.alibaba.dubbo.remoting.transport.AbstractServer

主要关闭方法为AbstractServer的public void close(int timeout)方法:

public void close(int timeout) {

ExecutorUtil.gracefulShutdown(executor, timeout);

close();

}

其中executor为AbstractServer的成员变量,为server的执行任务的线程池。

Dubbo线程池优雅关闭的方法代码如下:

 /**

  * executor 服务线程池

  * timeout 关闭超时时间--->

  */

     public static void gracefulShutdown(Executor executor, int timeout) {

         if (!(executor instanceof ExecutorService) || isShutdown(executor)) {

             return;

         }

         final ExecutorService es = (ExecutorService) executor;

         try {

             es.shutdown(); // 禁止提交新的任务

         } catch (SecurityException ex2) {

             return;

         } catch (NullPointerException ex2) {

             return;

         }

         try {

             if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {

                 es.shutdownNow();

             }

         } catch (InterruptedException ex) {

             es.shutdownNow();

             Thread.currentThread().interrupt();

         }

         if (!isShutdown(es)) {

             newThreadToCloseExecutor(es);

         }

     }

8 dubbox和dubbo检测优雅停机demo

测试目标: 1 服务关闭后,消费端不能访问到该节点的服务。

         2 服务关闭,服务端未执行完成的线程,能等待其执行完成后再关闭,客户端能收到正常的返回..而不是exception,或者其他错误的响应。

 

代码实现:

//服务端接口

public interface ProviderDemoService {

   public String sayHello();

}

//服务端接口的实现:在接到请求的时候,系统关闭(kill pid),任务线程睡眠较长时间

package com.zhanglang.dubbo.a_provider.service.impl;

import java.text.SimpleDateFormat;import java.util.Date;

import org.springframework.stereotype.Component;

import com.alibaba.dubbo.config.annotation.Service;import com.zhanglang.dubbo.a_provider.service.ProviderDemoService;

@Service@Componentpublic class ProviderDemoServiceImpl implements ProviderDemoService{

    public String sayHello() {        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " method is called.");        Thread exitsThread = new Thread(new Runnable() {            public void run() {                System.exit(0);//此命令相当于 kill pid            }        });        exitsThread.start();        try{            Thread.sleep(20000);//模拟一个执行时间较长的过程        } catch (Exception e){            System.out.println(e.getMessage());        }        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " thread has weakup!!! ");        return " hello Word ";    }

                public class ShutDownHoog implements Runnable {        public void run() {                    }    }            public static void main(String[] args) {        Runtime.getRuntime().addShutdownHook(                  //此线程会在kill pid后执行                  new Thread(new Runnable() {                         @Override public void run() {                                //释放资源,检测未完结线程等...                               System.out.println("hook running...");                          }                   }            ));        System.exit(0);//此命令相当于 kill pid    }    }

//客户端实现

设计思想:客户端并发启动两个线程,检测第二个线程执行过程是否还能访问到第一个线程访问到的provider。

* 如果 第二个线程访问返回No available Provider 异常信息,则代表provider已经下线..实现了consumer端的优雅停机

 

package com.zhanglang.dubbo.a_consumer;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import com.alibaba.dubbo.config.annotation.Reference;
import com.zhanglang.dubbo.a_provider.service.ProviderDemoService;

/**
 * Hello world!
 *
 */
@Component
public class ConsumerService implements InitializingBean{
    @Reference
    private ProviderDemoService providerDemoService;
    
    public void firstCall(){
        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date())+" first call  start ");
        String response = providerDemoService.sayHello();
        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date())+" first call response : " + response);
    }
    
    public void secondCall(){
        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date())+" second call  start ");
        String response = null;
        try{
            response = providerDemoService.sayHello();
        }catch(Exception e){
            System.out.println("异常信息:"+e.getMessage());
            response =" 第二次访问异常 ,异常信息:"+e.getMessage();
        }
        System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " second call response : " + response);
    }

public void setProviderDemoService(ProviderDemoService providerDemoService) {
        this.providerDemoService = providerDemoService;
    }

public void afterPropertiesSet() throws Exception {
        /**
         * 客户端并发启动两个线程,检测第二个线程执行过程是否还能访问到第一个线程访问到的provider。
         * 如果 第二个线程访问返回No available Provider 异常信息,则代表provider已经下线..实现了consumer端的优雅停机
         *
         */
        Thread firstCallThread = new Thread(new Runnable() {
            public void run() {
                firstCall();
            }
        },"firstCallThread");
        firstCallThread.start();
        //暂停1秒钟,看还能不能访问到原来ip的服务者
        Thread.sleep(1000);
        
        Thread secondCallThread = new Thread(new Runnable() {
            public void run() {
                secondCall();
            }
        },"secondCallThread");
        secondCallThread.start();
    }
    
    
}

Dubbox-2.8.4控制台打印日志:

[2017-12-14 16:08:04] first call  start

[2017-12-14 16:08:05] second call  start

异常信息:Forbid consumer 192.168.2.104 access service com.zhanglang.dubbo.a_provider.service.ProviderDemoService from registry 127.0.0.1:2181 use dubbo version 2.8.4, Please check registry access list (whitelist/blacklist).

[2017-12-14 16:08:05] second call response :  第二次访问异常 ,异常信息:Forbid consumer 192.168.2.104 access service com.zhanglang.dubbo.a_provider.service.ProviderDemoService from registry 127.0.0.1:2181 use dubbo version 2.8.4, Please check registry access list (whitelist/blacklist).

第一个请求隔了25秒后返回:Waiting server-side response timeout by scan timer. start time: 2017-12-14 16:08:04.715, end time: 2017-12-14 16:08:29.735, client elapsed: 49 ms, server elapsed: 24971 ms, timeout: 25000 ms

结果:dubbox2.8.4不能实现优雅关闭

Dubbo-2.5.6控制台打印日志:

[2017-12-14 16:14:46] first call start

[2017-12-14 16:14:47] second call  start

[2017-12-14 16:14:47] second call response :  第二次访问异常 ,异常信息:No provider available from registry 127.0.0.1:2181 for service com.zhanglang.dubbo.a_provider.service.ProviderDemoService on consumer 192.168.2.104

[2017-12-14 16:14:51] first call response :  hello Word

结果:dubbo2.5.6版本能实现优雅关闭

在provider端和consumer端的main启动里面加入关机超时配置,就能实现关机时间设置了:
System.setProperty("dubbo.service.shutdown.wait", "25000");

dubbo-2.5.6优雅停机研究的更多相关文章

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

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  2. dubbo之优雅停机

    优雅停机 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才 ...

  3. Dubbo 优雅停机演进之路

    一.前言 在 『ShutdownHook- Java 优雅停机解决方案』 一文中我们聊到了 Java 实现优雅停机原理.接下来我们就跟根据上面知识点,深入 Dubbo 内部,去了解一下 Dubbo 如 ...

  4. Dubbo 如何优雅停机?

    Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才 会执行.

  5. ShutdownHook- Java 优雅停机解决方案

    想象一下,如果你现在刚好在 word 上写需求文档,电脑突然重启.等待开机完成,你可能会发现写了一个小时文档没有保存,就这么没了... 一个正在运行 Java 应用如果突然将其停止,影响不止数据丢失, ...

  6. Spring Boot 系列:最新版优雅停机详解

    爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方. 开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cub ...

  7. 【mq】从零开始实现 mq-05-实现优雅停机

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  8. Java 技术栈中间件优雅停机方案设计与实现全景图

    欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...

  9. JAVA优雅停机的实现

    最近在项目中需要写一个数据转换引擎服务,每过5分钟同步一次数据.具体实现是启动engine server后会初始化一个ScheduledExecutorService和一个ThreadPoolExec ...

随机推荐

  1. QTP测试.NET程序的时候,找不到对象或无法录制的解决方案

    解决方案: .NET程序编译的时候:目标平台必须设置为x86,否则QTP找不到对象,不会完成录制

  2. 201772020113 李清华《面向对象程序设计(java)》第16周学习总结

    1.实验目的与要求 (1) 掌握线程概念: (2) 掌握线程创建的两种技术: (3) 理解和掌握线程的优先级属性及调度方法: (4) 掌握线程同步的概念及实现技术: 2.实验内容和步骤 实验1:测试程 ...

  3. 创建windoes 硬盘 挂载u盘

    一,创建 windows 硬盘 创建 2,我的电脑右击鼠标,计算机管理 3.同理执行,在linux 下创建虚拟磁盘 选择路径为之前存放 windows 系统路径 之后 mount  /dev/sdc ...

  4. SQL SERVER 死锁

    sp_lock 查看锁表名称 select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableNamefr ...

  5. MySql TIMEDIFF做计算之后,后台报Illegal hour value '24' for java.sql.Time type 问题

    页面需要显示这种格式:31:01:20 但是后台Springboot会提示Illegal hour value '24' for java.sql.Time type in value '24:00: ...

  6. anu小程序快速入门

    众所周知,微信推出小程序以来,可谓火遍大江南北,就像当前互联网兴起时,大家忙着抢域名与开私人博客一样.小程序之所以这么火,是因为微信拥有庞大的用户量,并且腾讯帮你搞定后台问题及众多功能问题(如分享,支 ...

  7. Maven打包后的文件存在中文乱码

    发现打包的js文件虽然是UTF-8格式的编码,但是有中文有乱码 可设置jvm的编码,两种方法: 在系统的环境变量中添加一个变量,名为: JAVA_TOOL_OPTIONS, 值为:-Dfile.enc ...

  8. jquery通过AJAX从后台获取信息并显示在表格上,并支持行选中

    不想用Easyui的样式,但是想要他的表格功能,本来一开始是要到网上找相关插件的,但是没找到就开始自己写,没想到这么简单. 后台代码:(这个不重要) public ActionResult GetDi ...

  9. mysql安装好之后,查询显示MySQL不是内部命令或外部命令问题

    使用cmd来调用MySQL的时候提示错误,错误是说MySQL不是内部或外部命令. 1.如图所示,遇到的mysql命令错误. 2.现在就要查询mysql是安装在哪,我们在计算机里面搜索mysql.exe ...

  10. intellij idea在project下同时打开多个工程(maven工程)

    前提:我的工程都是maven工程   我有两个工程,一个是接口contract,一个是接口的具体实现server.想要同时在一个工作空间下展示,方便调试开发,加载后效果如下   idea有worksp ...