一、前言
在上一篇文章中我们讲述来一次RPC请求的流程,并且编写了一个简单的服务端和消费端的Netty通信Demo,既然现在可以通信了,那么接下来我们就要开始尝试调用接口了。
二、服务注册
在前文中我们已经知道,消费端要调用服务端的接口必须要知道服务端的地址信息(IP+端口),当然这个可以直接写死,不过显然我们不会这么做。正确的做法是将服务端信息维护在第三方,然后让消费端去获取,这个第三方可以是数据库、可以是注册中心也可以是缓存中。考虑到性能、维护的简单性来说我们推荐使用注册中心来实现这一功能。这里我先使用Zookeeper来实现这一功能。
1、明确要注册的信息
对于消费者(调用方)而言,我首先得知道服务端(服务提供方)暴露了那些接口,所以首先我们要把服务端的信息注册到注册中心。同时我们知道,无论RPC怎么封装最后到服务端来说就是一次方法调用,所以我们需要知道这个方法在哪里,即类的全路径。再加之是网络请求所以我们需要知道服务端的IP,综上所述服务端注册的信息至少应该包含以下几个方面。
1、方法名:这个很好理解,我提供了什么方法总得告诉消费端
2、IP:有了IP才能点对点的通信
3、类的全路径:有了这个信息,服务端才知道最终要调用的方法所在的类在哪里。
目前咱们使用的是ZK作为注册中心,所以结合ZK的数据结构,我们设计的结构是,方法名作为路径,IP、全路径作为data;
2、Coding
1、首先我们修改一下引导类
java
package cn.zcy.gov.core;
import cn.zcy.gov.core.register.Registry;
import cn.zcy.gov.core.register.RegistryConfig;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author hardy(叶阳华)
* @Description
* @Date 2023/8/11 17:24
* @Modified By: Copyright(c) cai-inc.com
*/
public class ZrpcBootstrap {
private ZrpcBootstrap() {
}
//单例:饿汉式
private static final ZrpcBootstrap zrpcBootstrap = new ZrpcBootstrap();
public static ZrpcBootstrap getInstance() {
return zrpcBootstrap;
}
/**
* 应用名称
*/
private String application;
/**
* 注册中心配置:
*/
private RegistryConfig registryConfig;
/**
* 注册实例:根据依赖导致原则,我们应该依赖抽象而非具体实现
*/
private Registry registry;
/**
* 服务列表: key:接口名称 value:服务配置
*/
private final ConcurrentHashMap<String, ServiceConfig<?>> SERVICE_MAP = new ConcurrentHashMap<>();
/**
* 设置应用名称
*
* @param application 应用名称
* @return this
*/
public ZrpcBootstrap application(String application) {
this.application = application;
return this;
}
/**
* 添加要提供的服务
*
* @param service 服务
* @return this
*/
public ZrpcBootstrap service(ServiceConfig<?> service) {
SERVICE_MAP.put(service.getInterface().getName(), service);
return this;
}
/**
* 批量添加要提供的服务
*
* @param serviceConfigList 服务List
* @return this
*/
public ZrpcBootstrap service(List<ServiceConfig<?>> serviceConfigList) {
serviceConfigList.forEach(this::service);
return this;
}
/**
* 添加注册中心信息
*
* @param registryConfig 注册中心相关信息
* @return this
*/
public ZrpcBootstrap registry(RegistryConfig registryConfig) {
this.registryConfig = registryConfig;
registry = registryConfig.getRegistry();
return this;
}
/**
* 开启框架:暂时这么写
*/
public void start() {
try {
publish();
System.out.println("发布完成");
Thread.sleep(600000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 将要暴露的接口注册到注册中心
*/
private void publish() {
//1、判断是否有要注册到接口
if (SERVICE_MAP.isEmpty()) {
return;
}
//2、将服务注册到注册中心
List<ServiceConfig<?>> serviceConfigList = new ArrayList<>(SERVICE_MAP.size());
serviceConfigList.addAll(SERVICE_MAP.values());
registry.register(serviceConfigList);
//TODO 剩下还有好多步骤
}
}
修改的点,主要是将Registry改成接口,因为我们的接口之后可能会有多种注册中心,根据以来导致原则,我们应该依赖抽象而不是具体实现。
2、注册逻辑我们放到publish方法中
java
package cn.zcy.gov.core.register;
import cn.zcy.gov.common.constant.Constant;
import cn.zcy.gov.common.util.NetUtils;
import cn.zcy.gov.common.util.zookeeper.ZookeeperNode;
import cn.zcy.gov.common.util.zookeeper.ZookeeperUtils;
import cn.zcy.gov.core.ServiceConfig;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooKeeper;
/**
* @Author hardy(叶阳华)
* @Description
* @Date 2023/8/17 17:15
* @Modified By: Copyright(c) cai-inc.com
*/
public class ZookeeperRegistry implements Registry {
private final ZooKeeper zooKeeper;
public ZookeeperRegistry(final String connectionStr, final int registryTimeout) {
this.zooKeeper = ZookeeperUtils.createZookeeper(connectionStr, registryTimeout);
}
@Override
public void register(final List<ServiceConfig<?>> serviceConfigList) {
//1、判断当前是否已经创建了基础路径(根路径)
if (!ZookeeperUtils.exists(zooKeeper, Constant.BASE_PATH, null)) {
/*
没有则创建基础路径(根路径)
参数1:要创建的路径
参数2:该路径下的data(对于根路径来说是空)
参数3:ACL权限控制
参数4:创建的模式,根路径是持久节点
*/
ZookeeperUtils.createNode(zooKeeper, new ZookeeperNode(Constant.BASE_PATH, null), null,
CreateMode.PERSISTENT);
}
//2、判断是否创建了服务提供方基本路径(同上)
if (!ZookeeperUtils.exists(zooKeeper, Constant.BASE_PROVIDERS_PATH, null)) {
ZookeeperUtils.createNode(zooKeeper, new ZookeeperNode(Constant.BASE_PROVIDERS_PATH, null), null,
CreateMode.PERSISTENT);
}
//3、开始注册服务端信息
if (serviceConfigList == null || serviceConfigList.isEmpty()) {
return;
}
for (ServiceConfig<?> service : serviceConfigList) {
//service路径 例如:/zrpc-metadata/providers/cn.zcy.gov.api.GreetingsService
String servicePath = Constant.BASE_PROVIDERS_PATH + "/" + service.getInterface().getName();
if (!ZookeeperUtils.exists(zooKeeper, servicePath, null)) {
//创建服务端暴露的接口节点:
ZookeeperUtils.createNode(zooKeeper, new ZookeeperNode(servicePath, null), null,
CreateMode.PERSISTENT);
}
//创建本机的临时节点 ip:端口
//服务提供方的端一般自己设定
String ip = NetUtils.getIp();
String node = servicePath + "/" + ip + ":" + 8088;
if (!ZookeeperUtils.exists(zooKeeper, node, null)) {
ZookeeperUtils.createNode(zooKeeper, new ZookeeperNode(node, null), null, CreateMode.EPHEMERAL);
}
}
}
}
代码解析:这个方法主要是将服务端要暴露的接口上报的注册中心,其中要注意的点是针对具体的服务信息(IP+端口)的节点我们需要注册成临时节点。为什么呢?因为可能呢会有很多个服务端,而客户端关心的只是在线的服务端,所以我们需要及时的将不在线的服务端剔除,这就用到了ZK的临时节点特性。
3、结果
我们在ZK中查看注册的信息
完整代码在gitee中:https://gitee.com/zaige/zprc/tree/main/