關於動態註冊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的思路,做法