手写RPC框架--4.服务注册

news2025/4/6 18:12:45

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)

服务注册

  • 服务注册
    • a.添加服务节点和主机节点
    • b.抽象注册中心
    • c.本地服务列表

服务注册

a.添加服务节点和主机节点

主要完成服务注册和发现的功能,其具体流程如下:

  • 1.服务提供方将服务注册到注册中心中

  • 2.消费端拉取服务列表。

  • 3.消费端简单的选取一个可以服务(后续会进行改造,实现负载均衡)

在这里插入图片描述

1.修改framework/common的Constants类:定义注册中心的路径常量

public class Constant {

    // 略........

    // 服务提供方的在注册中心的基础路径
    public static final String BASE_PROVIDERS_PATH = "/dcyrpc-metadata/providers";

    // 服务调用方的在注册中心的基础路径
    public static final String BASE_CONSUMERS_PATH = "/dcyrpc-metadata/consumers";
}

2.在core中引入common的依赖项

3.修改framework/core的DcyRpcBootstrap类:定义一些相关的基础配置

  • 定义相关变量:应用名称, Config, 默认端口
  • 定义Zookeeper实例
  • 完善方法代码:application() / registry() / protocol() / publish()
  • publish()发布服务:将接口与匹配的实现注册到服务中心
// 略......
private static final DcyRpcBootstrap dcyRpcBootstrap = new DcyRpcBootstrap();
// 定义一些相关的基础配置
private String applicationName = "default";
private RegistryConfig registryConfig;
private ProtocolConfig protocolConfig;
private int port = 8088;

// 维护一个Zookeeper实例
private ZooKeeper zooKeeper;

// 略......

/**
 * 定义当前应用的名字
 * @param applicationName 应用名称
 * @return
 */
public DcyRpcBootstrap application(String applicationName) {
    this.applicationName = applicationName;
    return this;
}

/**
 * 配置一个注册中心
 * @param registryConfig 注册中心
 * @return this
 */
public DcyRpcBootstrap registry(RegistryConfig registryConfig) {
    // 维护一个zookeeper实例,但是,如果这样写就会将zookeeper和当前的工程耦合
    zooKeeper = ZookeeperUtils.createZookeeper();
    this.registryConfig = registryConfig;
    return this;
}

/**
 * 配置当前暴露的服务使用的协议
 * @param protocolConfig 协议的封装
 * @return this
 */
public DcyRpcBootstrap protocol(ProtocolConfig protocolConfig) {
    this.protocolConfig = protocolConfig;
    if (log.isDebugEnabled()) {
        log.debug("当前工程使用了:{}协议进行序列化", protocolConfig.toString());
    }
    return this;
}

/**
 * --------------------------------服务提供方的相关api--------------------------------
 */

/**
 * 发布服务:将接口与匹配的实现注册到服务中心
 * @param service 封装需要发布的服务
 * @return
 */
public DcyRpcBootstrap publish(ServiceConfig<?> service) {

    // 服务名称的节点
    String parentNode = Constant.BASE_PROVIDERS_PATH + "/" + service.getInterface().getName();

    // 判断节点是否存在,不存在则创建节点(持久)
    if (!ZookeeperUtils.existNode(zooKeeper, parentNode, null)) {
        ZookeeperNode zookeeperNode = new ZookeeperNode(parentNode, null);
        ZookeeperUtils.createNode(zooKeeper, zookeeperNode, null, CreateMode.PERSISTENT);
    }

    // 创建本机的临时节点,ip:port
    // 服务提供方的端口(一般自己设定),还需要获取ip的方法
    // /dcyrpc-metadata/providers/com.dcyrpc.DcyRpc/192.168.195.1:8088
    String node = parentNode + "/" + NetUtils.getIp() + ":" + port;
    if (!ZookeeperUtils.existNode(zooKeeper, node, null)) {
        ZookeeperNode zookeeperNode = new ZookeeperNode(node, null);
        ZookeeperUtils.createNode(zooKeeper, zookeeperNode, null, CreateMode.EPHEMERAL);
    }
	
    if (log.isDebugEnabled()) {
        log.debug("服务{},已经被注册", service.getInterface().getName());
    }
    return this;
}

// 略......

/**
 * 启动netty服务
 */
public void start() {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

// 略......

}

4.修改framework/common的utils.zookeeper.ZookeeperUtils类:添加方法:判断节点是否存在

/**
 * 判断节点是否存在
 * @param zooKeeper
 * @param node
 * @param watcher
 * @return true:存在  false:不存在
 */
public static boolean existNode(ZooKeeper zooKeeper, String node, Watcher watcher) {
    try {
        return zooKeeper.exists(node, watcher) != null;
    } catch (KeeperException | InterruptedException e) {
        log.error("判断节点:{} 是否存在时发生异常:", node, e);
        throw new ZookeeperException(e);
    }
}

5.修改framework/common的exceptions.ZookeeperException类:完善内容

public class ZookeeperException extends RuntimeException{
    public ZookeeperException() {
        super();
    }

    public ZookeeperException(Throwable cause) {
        super(cause);
    }
}

6.在framework/common的utils包下,创建NetUtils类:Network工具类

/**
 * Network utils
 */
@Slf4j
public class NetUtils {

    public static String getIp() {
        try {
            // 获取所有的网卡信息
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                // 过滤非回环接口和虚拟接口
                if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) {
                    continue;
                }
                Enumeration<InetAddress> addresses = iface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    // 过滤IPv6地址和回环地址
                    if (addr instanceof Inet6Address || addr.isLoopbackAddress()) {
                        continue;
                    }
                    String ipAddress = addr.getHostAddress();
                    System.out.println("局域网IP地址:" + ipAddress);
                    return ipAddress;
                }
            }

            throw new NetworkException();
        } catch (SocketException e) {
            log.error("获取局域网IP时发送异常", e);
            throw new NetworkException(e);
        }
    }
}

7.在framework/common的exceptions包下创建NetworkException类:编写自定义异常

public class NetworkException extends RuntimeException{
    public NetworkException() {
        super();
    }
    
    public NetworkException(String message) {
        super(message);
    }

    public NetworkException(Throwable cause) {
        super(cause);
    }
}

b.抽象注册中心

在当前项目中我们的确使用的是zookeeper作为我们项目的注册中心。但是,我们希望在我们的项目是可以扩展使用其他类型的注册中心的,如nacos,redis,甚至是自己独立开发注册中心。为后来的扩展提供可能性,所以在整个工程中我们再也不能单独的面向具体的对象编程,而是面向抽象,我们将抽象出**【注册中心】**整个抽象的概念

1.在core下com.dcyrpc下创建discovery包,创建Registry接口:抽象注册中心接口:注册服务,发现服务,下线服务

/**
 * 抽象注册中心:注册服务,发现服务,下线服务
 */
public interface Registry {
    /**
     * 注册服务
     * @param serviceConfig 服务的配置内容
     */
    public void register(ServiceConfig<?> serviceConfig);
}

2.在core下com.dcyrpc.discovery下创建AbstractRegistry抽象类:提炼共享内容,还可以做模板方法 (待开发)

/**
 * 提炼共享内容,还可以做模板方法
 * 所有注册中心都有的公共方法
 */
public abstract class AbstractRegistry implements Registry{
}

3.在core下com.dcyrpc.discovery下创建impl包,创建ZookeeperRegistry类继承AbstractRegistry

  • DcyRpcBootstrap类里的publish()方法,提炼到该类中
@Slf4j
public class ZookeeperRegistry extends AbstractRegistry {

    private ZooKeeper zooKeeper = ZookeeperUtils.createZookeeper();

    @Override
    public void register(ServiceConfig<?> service) {
        // 服务名称的节点
        String parentNode = Constant.BASE_PROVIDERS_PATH + "/" + service.getInterface().getName();

        // 判断节点是否存在,不存在则创建节点(持久)
        if (!ZookeeperUtils.existNode(zooKeeper, parentNode, null)) {
            ZookeeperNode zookeeperNode = new ZookeeperNode(parentNode, null);
            ZookeeperUtils.createNode(zooKeeper, zookeeperNode, null, CreateMode.PERSISTENT);
        }

        // 创建本机的临时节点,ip:port
        // 服务提供方的端口(一般自己设定),还需要获取ip的方法
        // /dcyrpc-metadata/providers/com.dcyrpc.DcyRpc/192.168.195.1:8088
        // TODO:后续处理端口问题
        String node = parentNode + "/" + NetUtils.getIp() + ":" + 8088;
        if (!ZookeeperUtils.existNode(zooKeeper, node, null)) {
            ZookeeperNode zookeeperNode = new ZookeeperNode(node, null);
            ZookeeperUtils.createNode(zooKeeper, zookeeperNode, null, CreateMode.EPHEMERAL);
        }

        if (log.isDebugEnabled()) {
            log.debug("服务{},已经被注册", service.getInterface().getName());
        }
    }
}

4.修改DcyRpcBootstrap

// 略.....
private static final DcyRpcBootstrap dcyRpcBootstrap = new DcyRpcBootstrap();

// 定义一些相关的基础配置
private String applicationName = "default";
private RegistryConfig registryConfig;
private ProtocolConfig protocolConfig;
private int port = 8088;

// 注册中心
private Registry zookeeperRegistry;

// 略.....

/**
 * 配置一个注册中心
 * @param registryConfig 注册中心
 * @return this
 */
public DcyRpcBootstrap registry(RegistryConfig registryConfig) {
    // 维护一个zookeeper实例,但是,如果这样写就会将zookeeper和当前的工程耦合
    // 使用 registryConfig 获取一个注册中心
    this.zookeeperRegistry = registryConfig.getRegistry();
    return this;
}

// 略.....

/**
 * 发布服务:将接口与匹配的实现注册到服务中心
 * @param service 封装需要发布的服务
 * @return
 */
public DcyRpcBootstrap publish(ServiceConfig<?> service) {
    // 抽象了注册中心的概念,使用注册中心的一个实现完成注册
    zookeeperRegistry.register(service);
    return this;
}

/**
 * 批量发布服务
 * @param services 封装需要发布的服务集合
 * @return this
 */
public DcyRpcBootstrap publish(List<ServiceConfig<?>> services) {
    for (ServiceConfig<?> service : services) {
        this.publish(service);
    }
    return this;
}

// 略.....

}

5.修改RegistryConfig类,将类放入discovery包下

public class RegistryConfig {

    // 定义连接的 url
    private final String connectString;

    public RegistryConfig(String connectString) {
        this.connectString = connectString;
    }

    /**
     * 可以使用简单工厂来完成
     * @return 具体的注册中心实例
     */
    public Registry getRegistry() {
        // 1.获取注册中心
        //   1.获取类型
        //   2.获取主机地址
        String registryType = getRegistryType(connectString, true).toLowerCase().trim();
        if (registryType.equals("zookeeper")) {
            String host = getRegistryType(connectString, false);
            return new ZookeeperRegistry(host, Constant.TIME_OUT);
        }
        throw new DiscoveryException("未发现合适的注册中心");
    }

    private String getRegistryType(String connectString, boolean ifType) {
        String[] typeAndHost = connectString.split("://");
        if (typeAndHost.length != 2) {
            throw new RuntimeException("给定的注册中心连接url不合法");
        }
        if (ifType){
            return typeAndHost[0];
        }else {
            return typeAndHost[1];
        }
    }
}

6.在framework/common的exceptions中创建DiscoveryException类:服务注册与发现异常处理

/**
 * 服务注册与发现异常处理
 */
public class DiscoveryException extends RuntimeException{
    public DiscoveryException() {
        super();
    }
    
    public DiscoveryException(String message) {
        super(message);
    }

    public DiscoveryException(Throwable cause) {
        super(cause);
    }
}

c.本地服务列表

服务调用方需要通过服务中心发现服务列表

  • 1.使用Map进行服务列表的存储
  • 2.使用动态代理生成代理对象
  • 3.从注册中心,寻找一个可用的服务

1.修改DcyRpcBootstrap部分代码:使用Map进行服务列表的存储

// 维护已经发布且暴露的服务列表 key:interface的全限定名  value:ServiceConfig
private static final Map<String, ServiceConfig<?>> SERVERS_LIST = new HashMap<>(16);

/**
 * 发布服务:将接口与匹配的实现注册到服务中心
 * @param service 封装需要发布的服务
 * @return
 */
public DcyRpcBootstrap publish(ServiceConfig<?> service) {
    // 抽象了注册中心的概念,使用注册中心的一个实现完成注册
    zookeeperRegistry.register(service);

    // 1.当服务调用方,通过接口、方法名、具体的方法参数列表 发起调用,提供方怎么知道使用哪一个实现
    //  (1) new 1 个
    //  (2) spring beanFactory.getBean(Class)
    //  (3) 自己维护映射关系
    SERVERS_LIST.put(service.getInterface().getName(),  service);
    return this;
}

2.修改ReferenceConfig部分代码

// 略.....
private Registry registry;

/**
 * 代理设计模式,生成一个API接口的代理对象
 * @return 代理对象
 */
public T get() {
    // 使用动态代理完成工作
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Class[] classes = new Class[]{interfaceRef};

    // 使用动态代理生成代理对象
    Object helloProxy = Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            // 1.发现服务,从注册中心,寻找一个可用的服务
            // 传入服务的名字,返回ip+端口 (InetSocketAddress可以封装端口/ip/host name)
            InetSocketAddress address = registry.lookup(interfaceRef.getName());
            if (log.isInfoEnabled()){
                log.debug("服务调用方,发现了服务{}的可用主机{}", interfaceRef.getName(), address);
            }
            // 2.使用netty连接服务器,发送 调用的 服务名字+方法名字+参数列表,得到结果

            return null;
        }
    });

    return (T) helloProxy;
}
	
public Registry getRegistry() {
    return registry;
}

public void setRegistry(Registry registry) {
    this.registry = registry;
}

3.修改Registry接口,添加发现服务的接口

/**
 * 从注册中心拉取一个可用的服务
 * @param serviceName 服务名称
 * @return 服务的ip+端口
 */
InetSocketAddress lookup(String serviceName);

4.修改ZookeeperRegistry部分代码,实现发现服务的业务逻

@Override
public InetSocketAddress lookup(String serviceName) {
    // 1.找到对应服务的节点
    String serviceNode = Constant.BASE_PROVIDERS_PATH + "/" + serviceName;

    // 2.从zk中获取它的子节点,
    List<String> children = ZookeeperUtils.getChildren(zooKeeper, serviceNode, null);
    // 获取所有的可用的服务列表
    List<InetSocketAddress> inetSocketAddressList = children.stream().map(ipString -> {
        String[] ipAndPort = ipString.split(":");
        String ip = ipAndPort[0];
        int port = Integer.valueOf(ipAndPort[1]);
        return new InetSocketAddress(ip, port);
    }).toList();

    if (inetSocketAddressList.size() == 0){
        throw new DiscoveryException("未发现任何可用的服务主机");
    }

    return inetSocketAddressList.get(0);
}

5.修改ZookeeperUtils部分代码,添加与实现获取子节点的方法

/**
 * 查询一个节点的子元素
 * @param zooKeeper
 * @param serviceNode 服务节点
 * @return 子元素列表
 */
public static List<String> getChildren(ZooKeeper zooKeeper, String serviceNode, Watcher watcher) {
    try {
        return zooKeeper.getChildren(serviceNode, watcher);
    } catch (KeeperException | InterruptedException e) {
        log.error("获取节点{}的子元素时发生异常:{}", serviceNode, e);
        throw new ZookeeperException(e);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/972722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能配电室运维云平台

智能配电室运维云平台依托电易云-智慧电力物联网&#xff0c;是通过物联网技术实现配电设备智能化管理和运维的云服务系统。该平台可以实时监测配电设备的运行状态、能耗情况、故障报警等信息&#xff0c;并通过云计算、大数据等技术进行分析和处理&#xff0c;提供精准的数据支…

数据结构前言

一、什么是数据结构&#xff1f; 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。 上面是百度百科的定义&#xff0c;通俗的来讲数据结构就是数据元素集合与数据元素集合或者数据元素与数据元素之间的组成形式。 举个…

MyBatisPlus 基础实现(一)

说明 创建一个最基本的MyBatisPlus项目&#xff0c;参考官网。 依赖 MyBatisPlus 依赖&#xff0c;最新版是&#xff1a;3.5.3.2 &#xff08;截止2023-9-4&#xff09;。 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-bo…

Xcode打包ipa文件,查看app包内文件

1、Xcode发布ipa文件前&#xff0c;在info中打开如下两个选项&#xff0c;即可在手机上查看app包名文件夹下的文件及数据。

基于QWebEngine实现无头浏览器

无头浏览器 无头浏览器&#xff08;Headless Browser&#xff09;是一种没有图形用户界面&#xff08;GUI&#xff09;的浏览器。它通过在内存中渲染页面&#xff0c;然后将结果发送回请求它的用户或程序来实现对网页的访问&#xff0c;而不会在屏幕上显示网页。这种方式使得无…

编译OpenWrt内核驱动

编译OpenWrt内核驱动可以参考OpenWrt内部其它驱动的编写例程&#xff0c;来修改成自己需要的驱动 一、OpenWrt源代码获取与编译 1.1、搭建环境 下载OpenWrt的官方源码&#xff1a; git clone https://github.com/openwrt/openwrt.git1.2、安装编译依赖项 sudo apt update -…

机器人中的数值优化(九)——拟牛顿方法(下)、BB方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

CocosCreator3.8研究笔记(六)CocosCreator 脚本装饰器的理解

一、什么是装饰器&#xff1f; 装饰器是TypeScript脚本语言中的概念。 TypeScript的解释&#xff1a;在一些场景下&#xff0c;我们需要额外的特性来支持标注或修改类及其成员。装饰器&#xff08;Decorators&#xff09;为我们在类的声明及成员上通过元编程语法添加标注提供了…

Java23种设计模式之【单例模式】

目录 一.单例模式的起源&#xff0c;和应用场景 1.单例模式的前世今生&#xff01; 2.什么是单例模式&#xff1f; 2.1使用单例模式的注意事项 2.2如何理解单例模式&#xff1f; 2.3单例模式的优势以及不足&#xff01; 2.4使用场景 二.实现 1.实现思路 1.1创建一个 S…

华为OD机试-贪吃蛇

题目描述 【贪吃蛇】贪吃蛇是一个经典游戏&#xff0c;蛇的身体由若干方格连接而成&#xff0c;身体随蛇头移动。蛇头触碰到食物时&#xff0c;蛇的长度会增加一格。蛇头和身体的任一方格或者游戏版图边界碰撞时&#xff0c;游戏结束。 下面让我们来完成贪吃蛇游戏的模拟&…

Ajax + Promise复习简单小结simple

axios使用 先看看老朋友 axios axios是基于Ajaxpromise封装的 看一下他的简单使用 安装&#xff1a;npm install axios --save 引入&#xff1a;import axios from axios GitHub地址 基本使用 axios({url: http://hmajax.itheima.net/api/province}).then(function (result…

接口自动化测试系列-yml管理测试用例

项目源码 目录结构及项目介绍 整体目录结构&#xff0c;目录说明参考 测试用例结构类似httprunner写法&#xff0c;可参考demo 主要核心函数 用例读取转换json import yaml import main import os def yaml_r():curpath f{main.BASE_DIR}/quality_management_logic/ops_ne…

centos7挂载nfs存储

centos7挂载nfs存储 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.安装NFS服务 #安装nfs yum -y install rpcbind nfs-utils#创建目录&#xff08;我这边是/data/upload&#xff09; mkdir -p /data/upload#写/etc/fstab文件&#xff0c;添加要挂载的nfs盘 172.16.…

Ubuntu18.04系统下通过ROS控制Kinova真实机械臂-多种实现方式

所用测试工作空间test_ws&#xff1a;包含官网最原始的功能包 一、使用Kinova官方Development center控制真实机械臂 0.在ubuntu系统安装Kinova机械臂的Development center&#xff0c;这一步自行安装&#xff0c;很简单。 1.使用USB连接机械臂和电脑 2.Development center…

【力扣周赛】第 361 场周赛(⭐前缀和+哈希表 树上倍增、LCA⭐)

文章目录 竞赛链接Q1&#xff1a;7020. 统计对称整数的数目竞赛时代码——枚举预处理 Q2&#xff1a;8040. 生成特殊数字的最少操作&#xff08;倒序遍历、贪心&#xff09;竞赛时代码——检查0、00、25、50、75 Q3&#xff1a;2845. 统计趣味子数组的数目竞赛时代码——前缀和…

jQuery成功之路——jQuery动画效果和遍历效果概述

一、jQuery动画效果 1.1显示效果 方法 方法名称解释show([speed],[easing],[fn]])显示元素方法hide([speed],[easing],[fn]])隐藏元素方法toggle([speed],[easing],[fn])切换元素方法&#xff0c;显示的使之隐藏&#xff0c;隐藏的使之显示 参数 参数名称解释speed三种预…

CocosCreator3.8研究笔记(五)CocosCreator 脚本说明及使用(下)

在Cocos Creator中&#xff0c;脚本代码文件分为模块和插件两种方式&#xff1a; 模块一般就是项目的脚本&#xff0c;包含项目中创建的代码、引擎模块、第三方模块。 插件脚本&#xff0c;是指从 Cocos Creator 属性检查器中导入的插件&#xff0c;一般是引入第三方引入库文件…

管理类联考——数学——汇总篇——知识点突破——数据分析——排列组合

角度——&#x1f434; 角度——&#x1f469; 排列组合的基本步骤&#xff08;固定解题体系&#xff09; 先取后排&#xff1a;即先取出元素&#xff0c;后排列元素&#xff0c;切勿边取边排. 逐次进行&#xff1a;按照一定的顺序逐次进行排列组合. 实验结束&#xff1a;整个…

vscode保存格式化自动去掉分号、逗号、双引号

之前每次写完代码都是双引号还有分号&#xff0c;看着很难受&#xff0c;就像修改一下&#xff0c;让它变成单引号&#xff0c;并且不加上引号&#xff1a;如下形式&#xff0c;看着简洁清晰明了 修改方式&#xff1a;更改 settings.json 文件 快捷键“Ctrl Shift P”打开命令…

UmeTrack: Unified multi-view end-to-end hand tracking for VR 复现踩坑记录

在 github 上找到了开源代码&#xff1a;https://github.com/facebookresearch/UmeTrack/tree/main 环境配置 运行第三行&#xff0c;报错&#xff0c;缺少torch。改成先运行第四行&#xff0c;成功。 再运行第三行&#xff0c;报错&#xff0c;required to install pyproj…