【重复造轮子系列】手撸RPC(三):服务注册

一、前言

在上一篇文章中我们讲述来一次RPC请求的流程,并且编写了一个简单的服务端和消费端的Netty通信Demo,既然现在可以通信了,那么接下来我们就要开始尝试调用接口了。

二、服务注册

在前文中我们已经知道,消费端要调用服务端的接口必须要知道服务端的地址信息(IP+端口),当然这个可以直接写死,不过显然我们不会这么做。正确的做法是将服务端信息维护在第三方,然后让消费端去获取,这个第三方可以是数据库、可以是注册中心也可以是缓存中。考虑到性能、维护的简单性来说我们推荐使用注册中心来实现这一功能。这里我先使用Zookeeper来实现这一功能。

1、明确要注册的信息

对于消费者(调用方)而言,我首先得知道服务端(服务提供方)暴露了那些接口,所以首先我们要把服务端的信息注册到注册中心。同时我们知道,无论RPC怎么封装最后到服务端来说就是一次方法调用,所以我们需要知道这个方法在哪里,即类的全路径。再加之是网络请求所以我们需要知道服务端的IP,综上所述服务端注册的信息至少应该包含以下几个方面。
1、方法名:这个很好理解,我提供了什么方法总得告诉消费端
2、IP:有了IP才能点对点的通信
3、类的全路径:有了这个信息,服务端才知道最终要调用的方法所在的类在哪里。
目前咱们使用的是ZK作为注册中心,所以结合ZK的数据结构,我们设计的结构是,方法名作为路径,IP、全路径作为data;

2、Coding

1、首先我们修改一下引导类

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方法中

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中查看注册的信息
【重复造轮子系列】手撸RPC(三):服务注册_第1张图片
完整代码在gitee中:https://gitee.com/zaige/zprc/tree/main/

你可能感兴趣的:(rpc,网络协议,网络)