分佈式服務框架概述:
服務協調中間件:Zookeeper 負責服務註冊、發現、通知。
服務提供方:
服務啓動 使用 Spring ApplicationListener 接口 當容器中bean 完成加載時,變量容器中的bean 將RPC bean 發佈到Zookeeper ,並保存到內存。
服務監聽 使用netty 啓動遠程服務請求監聽端口,接收到遠程服務請求數據 通過反射方式調用本地服務。
服務消費方:
服務發現 通過主動拉取Zookeeper 數據和watch 監聽服務變更。
服務調用 本地生成遠程接口代理,代理接口將請求數據打包通過netty 發送到服務提供方監聽端口。
服務消費方 基於以上流程實現遠程服務調用,本地代理接口向服務提供方發生請求數據時包含的三元組是 服務編碼(唯一標識一個服務bean)、方法名稱、參數類型(唯一標識一個javabean 方法)。這就需要在消費方引入服務接口jar包。
通常我們是在工程的POM 文件中添加maven 座標的方式依賴遠程服務的接口API。但是這種方式不易擴展,業務變更需要調用新接口時需要通過編寫新代碼重新發布系統。
於是便設計一套基於JDK動態加載jar包和JavaBean反射機制的服務調用方式,方案圖解:
主要實現類:
public class RMURLJarLoader extends URLClassLoader {
Logger logger = LoggerFactory.getLogger(RMURLJarLoader.class);
// 緩存jar數據.
private JarURLConnection cacheJar = null;
public RMURLJarLoader() {
super(new URL[] {});
}
/**
* 將指定的文件url添加到類加載器的classpath中去,並緩存jar connection,方便以後卸載jar
* 一個可想類加載器的classpath中添加的文件url
*
* @param
*/
public void addURLJar(URL file) {
try {
// 打開並緩存文件url連接
URLConnection uc = file.openConnection();
if (uc instanceof JarURLConnection) {
uc.setUseCaches(true);
((JarURLConnection) uc).getManifest();
cacheJar = (JarURLConnection) uc;
}
} catch (Exception e) {
System.err.println("Failed to cache plugin JAR file: " + file.toExternalForm());
logger.error("Failed to cache plugin JAR file: " + file.toExternalForm());
}
try {
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
add.setAccessible(true);
add.invoke(classLoader, file);
} catch (Exception exp) {
logger.error("Failed to cache plugin JAR file: " + file.toExternalForm());
}
}
public void unloadJarFile() {
if (cacheJar == null) {
return;
}
try {
System.err.println("Unloading plugin JAR file " + cacheJar.getJarFile().getName());
cacheJar.getJarFile().close();
cacheJar = null;
} catch (Exception e) {
System.err.println("Failed to unload JAR file\n" + e);
logger.error("Failed to unload JAR file\n" + e);
} finally {
}
}
}
public class RMURLClassLoader implements DisposableBean {
private final static Logger logger = LoggerFactory.getLogger(RMURLJarLoader.class);
private final static ConcurrentHashMap<String, RMURLJarLoader> LOCAL_CACHE = new ConcurrentHashMap<>();
private RMURLJarLoader loadJar(String jarPath) {
RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);
if (jar != null) {
return jar;
}
jar = new RMURLJarLoader();
try {
URI uri = new URI(jarPath);
jar.addURLJar(uri.toURL());
LOCAL_CACHE.put(jarPath, jar);
} catch (URISyntaxException e) {
logger.error("Laod jar:" + jarPath + " exception!");
} catch (MalformedURLException e) {
logger.error("Laod jar:" + jarPath + " exception!");
}
return jar;
}
public Class loadClass(String jarPath, String fullClassName) {
if (StringUtils.isNullOrEmpty(jarPath) || StringUtils.isNullOrEmpty(fullClassName)) {
return null;
}
RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);
if (jar == null) {
jar = loadJar(jarPath);
}
try {
return jar.loadClass(fullClassName);
} catch (ClassNotFoundException e) {
logger.error("Laod jar:" + jarPath + "class :" + fullClassName + " exception!");
}
return null;
}
public void unloadJarFile(String jarPath) {
RMURLJarLoader jar = LOCAL_CACHE.get(jarPath);
if (jar == null) {
return;
}
jar.unloadJarFile();
jar = null;
LOCAL_CACHE.remove(jarPath);
}
/**
* Invoked by a BeanFactory on destruction of a singleton.
*
* @throws Exception in case of shutdown errors.
* Exceptions will get logged but not rethrown to allow
* other beans to release their resources too.
*/
@Override
public void destroy() throws Exception {
for (RMURLJarLoader jar : LOCAL_CACHE.values()) {
jar.unloadJarFile();
}
}
}