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

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:8088String 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();@Overridepublic 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 {// 定义连接的 urlprivate 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() {@Overridepublic 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.rhkb.cn/news/120942.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# PaddleDetection yolo 印章检测

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq…

SourceTree安装教程

PS&#xff1a;SourceTree是一款流行的免费Git和Mercurial版本控制工具&#xff0c;由Atlassian开发和维护。它提供了一个直观且功能强大的图形用户界面&#xff0c;方便开发人员管理和浏览代码仓库 说白了&#xff0c;他就是一个可视化的git界面&#xff0c;还是非常好用的&am…

Excel中将文本格式的数值转换为数字

在使用excel时&#xff0c;有时需要对数字列进行各种计算&#xff0c;比如求平均值&#xff0c;我们都知道应该使用AVERAGE()函数&#xff0c;但是很多时候结果却“不尽如人意”。 1 问题&#xff1a; 使用AVERAGE函数&#xff1a; 结果&#xff1a; 可以看到单元格左上角有个…

【机器视觉】喇叭的外圆以及金属内圆的同心度视觉检测--康耐德智能

客户的需求 检测内容 喇叭的外圆以及金属内圆的同心度测量 检测要求 精度0.02mm&#xff0c;速度没要求&#xff0c;抽检产品。 评估 视觉可行性分析 对贵司的样品进行了光学实验&#xff0c;并进行图像处理&#xff0c;原则上可以使用机器视觉进行测试测量。 结果 对所有样…

[git]分支操作

Checkout 相当于切换到该分支&#xff0c;但是因为不能直接操作远程分支&#xff0c;会在本地同步一个完全一样的分支。 注意&#xff1a;切换分支前本地先进行提交&#xff08;addcommit&#xff09;&#xff0c;否则有可能代码会丢失。 New Branch from Selected... 创建一…

CVE-2023-3450:锐捷 RG-BCR860 命令执行漏洞复现

锐捷 RG-BCR860 命令执行漏洞(CVE-2023-3450)复现 0x01 前言 本次测试仅供学习使用&#xff0c;如若非法他用&#xff0c;与本文作者无关&#xff0c;需自行负责&#xff01;&#xff01;&#xff01; 0x02 漏洞描述 Ruijie Networks RG-BCR860是中国锐捷网络&#xff08;R…

Android jni引用第三方so动态库和.a静态库并且调用(c)方法

最近花了一周时间来入门学习 Android JNI方面的知识&#xff0c;因为后续的工作很多需要用到c c库&#xff0c;我需要用jni来包装一下c函数&#xff0c;来提供给上次java调用。总之多学点知识对自己有好处。 案例效果&#xff1a; 上文我们讲解了 android studio cmake生成.a…

MASM32编程状态栏显示字符动画,按钮跑马灯

一、需求分析 由于sysInfo扫描的内容比较多&#xff0c;打算为它增加一点动画效果&#xff0c;提醒用户程序正在运行&#xff0c;耐心等待。 二、构建测试窗口 测试窗口上放置有一个按钮&#xff0c;按钮上的初始文字是“开始扫描”&#xff1b;并使用状态栏&#xff0c;状态…

微波系统中散射参量S、阻抗参量Z及导纳参量Y之间的关系及MATLAB验证

微波系统中散射参量S、阻抗参量Z及导纳参量Y之间的关系及MATLAB验证 用HFSS设计了一微波元件&#xff0c;仿真出了其散射参量S、阻抗参量Z及导纳参量Y&#xff0c;用MATLAB验证他们之间的关系 HFSS设计螺旋线圈 用HFSS设计了一个螺旋线圈&#xff0c;如上图所示。 进行仿真&…

框架分析(8)-React Native

框架分析&#xff08;8&#xff09;-React Native 专栏介绍React Native特性和优势跨平台开发&#xff1a;热更新原生性能组件化开发第三方库支持社区支持 限制和挑战性能问题第三方库兼容性学习曲线 总结 专栏介绍 link 主要对目前市面上常见的框架进行分析和总结&#xff0c…

用「渣男」心态去面试,爽翻!

转载自博学谷公众号&#xff01; 面试是一场“销售”自己的考试&#xff0c;很多程序员掌握的技术、知识都非常过关&#xff0c;但在面试中却不能完全展现出来&#xff0c;屡屡发挥失常。 同样是天选打工人 别人的offer&#xff0c;像猴子摘苞谷&#xff0c;应接不暇 而你的of…

NFTScan 浏览器正式版上线 2 周年!

NFTScan 成立于 2021 年 4 月份&#xff0c;总部位于香港。在 2021 年的 7 月份&#xff0c;NFTScan 团队对外发布了 NFTScan 浏览器公测版&#xff0c;并在同年的 9 月 4 号&#xff0c;对外发布了 NFTScan 浏览器正式版&#xff0c;同步启用了全球品牌域名&#xff1a;NFTSCA…

pycharm创建的虚拟环境为什么用conda env list命令查询不到?

问题描述&#xff1a;pycharm创建的虚拟环境为什么用conda env list命令查询不到。 pycharm开发环境可以创建虚拟环境&#xff0c;目的是为隔绝其他环境种库带来的版本干扰&#xff0c;但是发现一个问题&#xff0c;无论是在windows终端、anaconda终端、Pycharm开发环境中的终…

WebStorm软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 WebStorm是一款由JetBrains开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于开发Web应用程序。它支持多种前端技术&#xff0c;如HTML、CSS、JavaScript、TypeScript等&#xff0c;并提供了丰富的功能和工具&…

第三方软件检测机构有哪些资质,2023年软件测评公司推荐

软件第三方测试报告 伴随着软件行业的蓬勃发展&#xff0c;软件测试也迎来了热潮&#xff0c;但是国内的软件测试行业存在着测试入行门槛低、测试投入少、测试人员专业性不足等问题&#xff0c;这些问题不但会阻碍软件测试行业的良性发展&#xff0c;而且难以保证软件产品的质…

DR IP-SoC China 2023 Day演讲预告 | 龙智Perforce专家解析芯片开发中的数字资产管理

2023年9月6日&#xff08;周三&#xff09;&#xff0c;龙智即将亮相于上海举行的D&R IP-SoC China 2023 Day&#xff0c;呈现集成了Perforce与Atlassian产品的芯片开发解决方案&#xff0c;助力企业更好、更快地进行芯片开发。 D&R IP-SoC China 2023 Day 是中国首个…

html 标签简介

概述 标签的效果不重要&#xff0c;重要的是标签的语义。 文本标签 文本标签用于包裹&#xff1a;词汇、短语等。排版标签&#xff0c;比如div,p,h1等。排版标签更宏观&#xff08;大段的文字&#xff09;&#xff0c;文本标签更微观&#xff08;词汇、短语&#xff09;。文…

uniapp中UView中 u-form表单在v-for循环下如何进行表单校验

1、数据data格式 注&#xff1a;rule绑定的tableFromRule中要和表单tableFrom下面放置一个同名数组&#xff0c;确保u-form能找到 tableFrom: {tableData: [//数据详情列表]},tableFromRule: {//校验tableData: [//数据详情列表]},formRules:{localation:[{required: true,mes…

css让多个盒子强制自动等宽

1.width: calc( 100 / n‘%" ) 2.display:flex; flex:1;width:100px;&#xff08;width是几都行 只要保证一样就可以 会自动等分&#xff09; 3.display:grid;grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 但是其中某一个内容较长的时候 会破坏1:1:1的…

MySQL的内置函数复合查询内外连接

文章目录 内置函数时间函数字符串函数数学函数其他函数 复合查询多表笛卡尔积自连接在where中使用子查询多列子查询在from中使用子查询 内连接外连接左外连接右外连接 内置函数 时间函数 函数描述current_date()当前日期current_time()当前时间current_timestamp()当前时间戳…