关于动态注册dubbo的思路,做法

Posted 故意养只喵叫顺儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于动态注册dubbo的思路,做法相关的知识,希望对你有一定的参考价值。

技术:

springboot,maven,dubbo,zookeeper

背景:

项目的功能类似一个中转路由,通过页面可以发送请求,请求到别的项目的接口,大家都知道dubbo接口的服务提供方需要把服务注册到zookeeper上,然后服务消费方获得服务提供方提供的facade包(也就是jar包),可以作为消费者去请求提供方的服务。

这里就有一个问题,dubbo接口的服务,无论是服务提供者还是服务消费者都有几种注册方式,可以查看官网。(为什么我说消费者也是注册,是因为在dubbo-admin中可以看到)

我的理解中比较常用的应该是:

1,xml

2,properties

3,api

 

需求:

动态注册dubbo服务,项目不需要重启

解释一下为什么有这个需求:

因为公司本身有自己的项目,这个独立的新的项目只是为了做测试各个接口的功能,有点类似于自动化测试,并且提供页面能够让开发人员测试自己写的接口。

那么也就是说,其他开发人员并不需要关注这个独立项目的代码,配置等等一切有关于这个项目本身的东西,他们只需要使用提供的功能,到服务器上看一看请求过来以后我们自己项目的日志。

这样子就必须要做到动态加载。因为人家不可能把你的代码拉到自己的电脑上,还要改你的代码配置,然后打包,重新部署。

所以,才会有这么一个需求。

问题:

作为服务的消费者,需要获得服务提供者提供的jar包与对应的接口信息才可以请求服务,那么问题来了,项目启动之后,服务提供者新提供了几个接口,那么现在作为消费者,我需要请求这几个新的接口,那么我能怎么做?

解决方案:

一般来说我都都会采用以下方式

更新maven的pom文件,下载新的jar包,更新xml,properties,api配置,重新打包。此类做法需要重启

 

但是现在需求是不重启,所以不能采用常规方式,必须动态加载

 


 

页面如下

 

 

在页面右上角提供一个按钮,点击效果如下

 


 

解决方案如下:

1,在springboot项目中提供一个外置文件dubbo_consumer.properties,里面的内容是key-value,key是方法名(也就是采用xml配置时的id),value是服务提供者提供的服务的类全路径(也就是采用xml配置时的interface)

xml配置如下

<dubbo:reference id="demoServiceRemote" interface="com.alibaba.dubbo.demo.DemoService" />

作为开发,自己写的接口,复制黏贴这个应该是没有问题的。

2,在开发好一个dubbo接口一个,作为服务提供者需要提供一个facade包也就是jar包给服务消费者,这个时候,我们就需要在springboot项目中提供一个外置的文件夹来存放jar包

3,springboot中的提供一个定时任务,每隔一分钟就去扫描这两个文件夹,查看是否有更新,如果有,那么dubbo_consumer.properties文件就直接替换,新的jar包就直接加载进项目

4,通过反射的形式,动态读取dubbo_consumer.properties中的配置,然后使用api的形式来注册dubbo消费者

 

具体的做法如下:

1,springboot项目启动的时候是1.0版本,这个时候在固定的路径下已经有dubbo_consumer.properties和一个facade包了

2,在项目中写一个单例作为缓存(之所以选择单例,而不使用redis之类是为了减少依赖)来存放初始加载的dubbo_consumer.properties配置和注册的dubbo消费者

3,定时任务每隔一分钟就去扫描这几个路径下的文件是否有更新(注意,项目启动之后,外置的facade包如果加载了是没有办法删除的,所以每次升级就是不断往里面添加facade包,关于数量问题,只需要定时重启项目,保留最新的facade包就行了,其他的删掉)

4,如果有更新就动态加载这些配置到单例缓存,并且注册新的dubbo消费者


下面提供部分代码:

dubbo基础配置:

public abstract class DubboBaseConfig {
    //应用名
    public static final String APPLICATION = "test";
    //连接zookeeper
    public static final String ADDRESS = "127.0.0.1:2181";
    //选择的协议
    public static final String PROTOCOL = "zookeeper";
    //zookeeper对外的端口
    public static final Integer PORT = 20880;

    //dubbo配置
    public static final ApplicationConfig applicationConfig = new ApplicationConfig();
    public static final RegistryConfig registryConfig = new RegistryConfig();

    //加载参数
    static {
        applicationConfig.setName(APPLICATION);
        registryConfig.setAddress(ADDRESS);
        registryConfig.setProtocol(PROTOCOL);
        registryConfig.setPort(PORT);
    }
}

 

 dubbo消费者配置:

public class DubboConsumerConfig extends DubboBaseConfig{
    private DubboConsumerConfig() {};

    private static final DubboConsumerConfig dubboConsumerConfig = new DubboConsumerConfig();

    public static DubboConsumerConfig getInstance() {
        return dubboConsumerConfig;
    }

    /**
     *
     * @param applicationConfig 当前应用配置
     * @param registryConfig    连接注册中心配置
     * @param cls                引用的远程服务
     * @param method
     */
    public void registerConsumer(ApplicationConfig applicationConfig,RegistryConfig registryConfig,Class<?> cls,String method) {
        // 引用远程服务
        ReferenceConfig<?> reference = new ReferenceConfig<>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(applicationConfig);
        reference.setRegistry(registryConfig); // 多个注册中心可以用setRegistries()
        reference.setInterface(cls);
        //放入缓存
        AppCache.getInstance().getReferenceConfigMapCache().put(method, reference);
    }
}

缓存配置:

public class AppCache {

    private AppCache(){}

    private static final AppCache serviceIdCache = new AppCache();

    public static AppCache getInstance(){
        return serviceIdCache;
    }

    //消费者缓存
    private Map<String,ReferenceConfig<?>> referenceConfigMapCache = new HashMap<>();

    public Map<String, ReferenceConfig<?>> getReferenceConfigMapCache() {
        return referenceConfigMapCache;
    }

    public void setReferenceConfigMapCache(Map<String, ReferenceConfig<?>> referenceConfigMapCache) {
        this.referenceConfigMapCache = referenceConfigMapCache;
    }
    
}

初始化调用写法:

    private void registryDubboService() {
        List<DubboServiceEntity> list = AppCache.getInstance().getInitServiceIdEntityList();
        for(DubboServiceEntity entity:list) {
            try {
                DubboConsumerConfig.getInstance().registerConsumer(DubboBaseConfig.applicationConfig, DubboBaseConfig.registryConfig, Class.forName(entity.getDubboServiceValue()), entity.getDubboServiceKey());
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

调用写法中的实体类

public class DubboServiceEntity {
    private String dubboServiceKey;//消费者服务ID
    private String dubboServiceValue;//消费的服务类名
    public String getDubboServiceKey() {
        return dubboServiceKey;
    }
    public void setDubboServiceKey(String dubboServiceKey) {
        this.dubboServiceKey = dubboServiceKey;
    }
    public String getDubboServiceValue() {
        return dubboServiceValue;
    }
    public void setDubboServiceValue(String dubboServiceValue) {
        this.dubboServiceValue = dubboServiceValue;
    }
    @Override
    public String toString() {
        return "DubboServiceEntity [dubboServiceKey=" + dubboServiceKey + ", dubboServiceValue=" + dubboServiceValue
                + "]";
    }
}

这样子就可以做到动态注册dubbo服务了


 

动态加载部分

在动态加载部分其实就是动态加载jar包

需要在绝对路径读取jar包,使用了URLClassLoader,此段代码参考如下:

package cn.fjs;
 
import java.io.File;  
import java.lang.reflect.Method;  
import java.net.URL;  
import java.net.URLClassLoader;  
import java.util.ArrayList;  
import java.util.List;
 
public final class JarLoaderUtil {
    /** URLClassLoader的addURL方法 */  
    private static Method addURL = initAddMethod();  
      
    /** 初始化方法 */  
    private static final Method initAddMethod() {  
        try {  
            Method add = URLClassLoader.class  
                .getDeclaredMethod("addURL", new Class[] { URL.class });  
            add.setAccessible(true);  
            return add;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    private static URLClassLoader system = (URLClassLoader) ClassLoader.getSystemClassLoader();  
  
    /** 
     * 循环遍历目录,找出所有的JAR包 
     */  
    private static final void loopFiles(File file, List<File> files) {  
        if (file.isDirectory()) {  
            File[] tmps = file.listFiles();  
            for (File tmp : tmps) {  
                loopFiles(tmp, files);  
            }  
        } else {  
            if (file.getAbsolutePath().endsWith(".jar") || file.getAbsolutePath().endsWith(".zip")) {  
                files.add(file);  
            }  
        }  
    }  
  
    /** 
     * <pre> 
     * 加载JAR文件 
     * </pre> 
     * 
     * @param file 
     */  
    public static final void loadJarFile(File file) {  
        try {  
            addURL.invoke(system, new Object[] { file.toURI().toURL() });  
            //System.out.println("加载JAR包:" + file.getAbsolutePath());  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    /** 
     * <pre> 
     * 从一个目录加载所有JAR文件 
     * </pre> 
     * 
     * @param path 
     */  
    public static final void loadJarPath(String path) {  
        List<File> files = new ArrayList<File>();  
        File lib = new File(path);  
        loopFiles(lib, files);  
        for (File file : files) {  
            loadJarFile(file);  
        }  
    }  
}
参考:http://blog.csdn.net/fjssharpsword/article/de

根据以上代码,再自行修改就能做到动态读取jar文件并且动态注册dubbo服务了

 

以上是关于关于动态注册dubbo的思路,做法的主要内容,如果未能解决你的问题,请参考以下文章

关于Dubbo

开发dubbo应用程序dubbo注册中心相关概述

Dubbo服务注册与动态发现机制的原理与实现细节

关于升级 Dubbo 版本到 2.6.5 后启动失败的“坑”

dubbo实现动态调用

dubbo服务调用是阻塞的吗