1. 程式人生 > >關於動態註冊dubbo的思路,做法

關於動態註冊dubbo的思路,做法

gis 端口 頁面 問題 trie oba 目錄 bsp 實例

技術:

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的思路,做法