手写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.mzph.cn/news/72642.shtml

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

相关文章

【Springcloud】elk分布式日志

【Springcloud】elk分布式日志 【一】基本介绍【二】Elasticsearch【1】简介【2】下载【3】安装【4】启动 【三】Logstash【1】简介【2】下载【3】安装【4】启动 【四】Kibana【1】简介【2】下载【3】安装【4】启动 【五】切换中文【六】日志收集 【一】基本介绍 &#xff08;…

【测试开发】Mq消息重复如何测试?

本篇文章主要讲述重复消费的原因&#xff0c;以及如何去测试这个场景&#xff0c;最后也会告诉大家&#xff0c;目前互联网项目关于如何避免重复消费的解决方案。 Mq为什么会有重复消费的问题? Mq 常见的缺点之一就是消息重复消费问题&#xff0c;产生这种问题的原因是什么呢…

VMware 安装 黑群晖7.1.1-42962 DS918+

本例的用的文件 1、ARPL 1.0beat 引导文件 vmdk格式&#xff1a; https://download.csdn.net/download/mshxuyi/88309308 2、DS918_42962.pat&#xff1a;https://download.csdn.net/download/mshxuyi/88309383 一、引导文件 1、创建一个虚拟机 2、下一步&#xff0c;选稍后…

Linux图形栈入门概念

Mesa在图形栈中的位置 游戏引擎&#xff1a; 游戏引擎指的是一种软件框架&#xff0c;通过编程和各种工具&#xff0c;帮助开发者设计、构建和运行视频游戏。它相当于一个虚拟的世界创造工具&#xff0c;提供了各种功能模块和资源&#xff0c;如渲染引擎、物理引擎(碰撞检测、重…

HTTP代理只能代理HTTP协议吗?

HTTP代理是一种代理服务器&#xff0c;它可以充当客户端和服务器之间的中介&#xff0c;以帮助客户端访问服务器上的资源。但是&#xff0c;HTTP代理并不仅仅只能代理HTTP协议。 HTTP代理可以代理的协议 除了HTTP协议之外&#xff0c;HTTP代理还可以代理其他协议&#xff0c;例…

Apache实现weblogic集群配置

安装apache&#xff0c;安装相对稳定的版本。如果安装后测试能否正常启动&#xff0c;可以通过访问http://localhost/进行测试。安装Weblogic&#xff0c;参见文档将bea安装目录 weblogic81/server/bin 下的 mod_wl_20.so 文件copy到 apache安装目录下Apache2/modules/目录下A…

C++ 浅拷贝和深拷贝

目录 1. 浅拷贝 2. 深拷贝 1. 浅拷贝 浅拷贝只是拷贝一个指针&#xff0c;并没有新开辟一个地址&#xff0c;拷贝的指针和原来的指针指向同一块地址&#xff0c;如果原来的指针所指向的资源释放了&#xff0c;那么再释放浅拷贝的指针的资源就会出现错误 对一个已知对象进行拷贝…

【深入解析spring cloud gateway】06 gateway源码简要分析

上一节做了一个很简单的示例&#xff0c;微服务通过注册到eureka上&#xff0c;然后网关通过服务发现访问到对应的微服务。本节将简单地对整个gateway请求转发过程做一个简单的分析。 一、核心流程 主要流程&#xff1a; Gateway Client向 Spring Cloud Gateway 发送请求请求…

探索Apache Hive:融合专业性、趣味性和吸引力的数据库操作奇幻之旅

文章目录 版权声明一 数据库操作二 Hive数据表操作2.1 表操作语法和数据类型2.2 Hive表分类2.3 内部表Vs外部表2.4 内部表操作2.4.1 创建内部表2.4.2 其他创建内部表的形式2.4.3 数据分隔符2.4.4 自定义分隔符2.4.5 删除内部表 2.5 外部表操作2.5.1 创建外部表2.5.2 操作演示2.…

代码生成商业化一些思考

代码生成解决方案 生成项目代码有3大类的解决思路&#xff1a; 1.从底到上的生成&#xff0c;部分代码生成生成一行代码或者一个方法种一小块代码生成&#xff0c;ide插件代码生成基本这种思路 2.大语言模型作为软件工程不同角色agent&#xff0c;用户给出idea每个agent自动…

BFS练习1

BFS练习1 - 题目 - Daimayuan Online Judge 问题描述&#xff1a; 刚开始吓一跳&#xff0c;以为有什么更简单的呢&#xff0c;因为每一次都要走一次bfs&#xff0c;看了数据范围后&#xff0c;感觉跑一次bfs进行记录即可。 代码&#xff1a; void solve() {int a,k; cin>…

超详细的 pytest 教程(一)使用入门篇

前言 pytest到目前为止还没有翻译的比较好全面的使用文档&#xff0c;很多英文不太好的小伙伴&#xff0c;在学习时看英文文档还是很吃力。本来去年就计划写pytest详细的使用文档的&#xff0c;由于时间关系一直搁置&#xff0c;直到今天才开始写。本文是第一篇&#xff0c;主…

leetcode 1609.奇偶树

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;奇偶树 思路&#xff1a; 树的层序遍历&#xff0c;用队列辅助。用一个变量记录当前是多少层&#xff0c;以及当前层的节点个数&#xff0c;依次遍历&#xff0c;因为需要判断当前层是否严格递增或递减&#xff0c;如果正…

数据结构——带头双向循环链表

数据结构——带头双向循环链表 一、带头双向循环链表的定义二、带头双向循环链表的实现2.1初始化创建带头双向循环链表的节点2.2申请新节点2.3节点的初始化2.4带头双向循环链表的尾插2.5带头双向循环链表的头插2.6判空函数2.7带头双向循环链表的打印函数2.8带头双向循环链表的尾…

软件生命周期及流程【软件测试】

软件的生命周期 软件生命周期是软件开始研制到最终被废弃不用所经历的各个阶段。 瀑布型生命周期模型 规定了它们自上而下、相互衔接的固定次序&#xff0c;如同瀑布流水&#xff0c;逐级下落&#xff0c;具有顺序性和依赖性。每个阶段规定文档并需进行评审。 特点&#xff…

SpringMVC:从入门到精通

一、SpringMVC是什么 SpringMVC是Spring提供的一个强大而灵活的web框架&#xff0c;借助于注解&#xff0c;Spring MVC提供了几乎是POJO的开发模式【POJO是指简单Java对象&#xff08;Plain Old Java Objects、pure old java object 或者 plain ordinary java object&#xff0…

redis 5.0.x 部署

PS&#xff1a;对于使用者来说&#xff0c;Redis5.0和4.0都是一样的&#xff0c;但是redis 4.0的集群部署需要额外安装ruby的东西&#xff0c;5.0中则集成到了redis-cli&#xff0c;部署起来更方便 1.1 安装Redis 本章基于CentOS 7.9.2009编写而成&#xff0c;由于Linux发行版…

【网络知识点】三次握手和四次挥手

文章目录 一、三次握手二、四次挥手 一、三次握手 三次握手的原理如下&#xff1a; 客户端向服务器发送一个SYN&#xff08;同步&#xff09;包&#xff0c;其中包含一个随机生成的初始序列号&#xff08;ISN&#xff09;。 服务器收到SYN包后&#xff0c;会发送一个SYNACK&…

QT设计一个小闹钟

设置一个闹钟&#xff0c;左侧窗口显示当前时间&#xff0c;右侧设置时间&#xff0c;以及控制闹钟的开关&#xff0c;下方显示闹钟响时的提示语。当按启动按钮时&#xff0c;设置时间与闹钟提示语均不可再改变。当点击停止时&#xff0c;关闭闹钟并重新启用设置时间与闹钟提示…

微服务-kubernetes安装

文章目录 一、前言二、kubernetes2.1、Kubernetes (K8S) 是什么2.1.1、主要特性&#xff1a;2.2.2、传统部署方式&#xff1a;2.2.3、虚拟机部署2.2.4容器部署2.2.5什么时候需要 Kubernetes2.2.6、Kubernetes 集群架构 三、kubernetes安装3.1、主节点需要组件3.1.1、设置对应主…