RPC Demo(二) 基于 Zookeeper 的服务发现
简介
基于上篇的:RPC Demo(一) Netty RPC Demo 实现
第二部分来实现使用Zookeeper作为服务注册中心,去掉在RPC调用中的显示传参
完整项目工程地址:RpcDemoJava
改进说明
在客户端调用中,我们需要显示的传入后端服务器的地址,这样显的有些不方便,代码大致如下:
UserService userService = jdk.create(UserService.class, "http://localhost:8080/");
利用Zookeeper作为注册中心,客户端可以从Zookeeper中获取接口实现的服务器相关地址,就不必再显式传入地址了,改进后大致如下:
UserService userService = jdk.create(UserService.class);
编码思路
进过调研和思考,实现的思路和步骤大致如下:
- 1.服务端将Provider注册到Zookeeper中
- 2.客户端拉取所有的Provider信息到本地,建立接口(Consumer)和Provider列表的映射关系
- 3.客户端能监听服务端Provider的增删改查,同步到客户端,便于删除和更新变化后的Provider信息
- 4.客户端反射调用时从Provider列表中获取相关url地址,进行访问,返回结果
需要在本地启动一个zk,使用docker即可,相关命令如下:
# 拉取ZK镜像启动ZK,后面的三个命令是基于运行了这个命令后的
docker run -dit --name zk -p 2181:2181 zookeeper
# 查看ZK运行日志
docker logs -f zk
# 重启ZK
docker restart zk
# 启动ZK
docker start zk
# 停止ZK
docker stop zk
Provider信息结构约定
我们约定一个Provider信息如下:
@Data
public class ProviderInfo {
/**
* Provider ID:ZK注册后会生成一个ID
* Client 获取Provider列表时,将此ID设置为获取的ZK生成的ID
*/
String id;
/**
* Provider对应的后端服务器地址
*/
String url;
/**
* 标签:用于简单路由
*/
List<String> tags;
/**
* 权重:用于加权负载均衡
*/
Integer weight;
public ProviderInfo() {
}
public ProviderInfo(String id, String url, List<String> tags, int weight) {
this.id = id;
this.url = url;
this.tags = tags;
this.weight = weight;
}
}
1.服务端将Provider注册到Zookeeper中
首先,我们要为各个接口的实现指定Provider名称、分组、版本、标签、权重,这里我们使用注解进行实现
/**
* RPC provider service 初始化注解
*
* group,version,targs 都有默认值,是为了兼容以前的版本
*
* @author lw1243925457
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProviderService {
/**
* 对应 API 接口名称
* @return API service
*/
String service();
/**
* 分组
* @return group
*/
String group() default "default";
/**
* version
* @return version
*/
String version() default "default";
/**
* tags:用于简单路由
* 多个标签使用逗号分隔
* @return tags
*/
String tags() default "";
/**
* 权重:用于加权负载均衡
* @return
*/
int weight() default 1;
}
接下来,借鉴Mybatis的设置包扫描路径的思路,写一个通过扫描指定包路径下的所有的class,获取class后判断其是否是Provider(有相应的注解),如果是,提取信息,注册到ZK
中,大致的代码如下:
/**
* 提供RPC Provider 的初始化
* 初始化实例放入 Map 中,方便后续的获取
*
* @author lw1243925457
*/
@Slf4j
public class ProviderServiceManagement {
/**
* 通过服务名、分组、版本作为key,确实接口实现类的实例
* service:group:version --> class
*/
private static Map<String, Object> proxyMap = new HashMap<>();
/**
* 初始化:通过扫描包路径,获取所有实现类,将其注册到ZK中
* 获取实现类上的Provider注解,获取服务名、分组、版本
* 调用ZK服务注册,将Provider注册到ZK中
* @param packageName 接口实现类的包路径
* @param port 服务监听的端口
* @throws Exception exception
*/
public static void init(String packageName, int port) throws Exception {
System.out.println("\n-------- Loader Rpc Provider class start ----------------------\n");
DiscoveryServer serviceRegister = new DiscoveryServer();
Class[] classes = getClasses(packageName);
for (Class c: classes) {
ProviderService annotation = (ProviderService) c.getAnnotation(ProviderService.class);
if (annotation == null) {
continue;
}
String group = annotation.group();
String version = annotation.version();
List<String> tags = Arrays.asList(annotation.tags().split(","));
String provider = Joiner.on(":").join(annotation.service(), group, version);
int weight = annotation.weight();
proxyMap.put(provider, c.newInstance());
serviceRegister.registerService(annotation.service(), group, version, port, tags, weight);
log.info("load provider class: " + annotation.service() + ":" + group + ":" + version + " :: " + c.getName());
}
System.out.println("\n-------- Loader Rpc Provider class end ----------------------\n");
}
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException exception
* @throws IOException exception
*/
private static Class[] getClasses(String packageName) throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<>();
for (File directory : dirs) {
classes.addAll(findClasses(directory

本文介绍了如何在RPC系统中使用Zookeeper作为服务发现组件。首先,解释了改进的必要性,即避免在客户端调用时手动输入服务器地址。接着详细阐述了编码思路,包括服务端如何将Provider注册到Zookeeper,以及客户端如何从Zookeeper获取并更新Provider信息,同时监听Provider的变化。此外,还展示了服务端和客户端的关键代码片段。
最低0.47元/天 解锁文章
1265

被折叠的 条评论
为什么被折叠?



