分布式服务框架概述:
服务协调中间件: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();
}
}
}