当前位置: 首页 > news >正文

Simple RPC - 06 从零开始设计一个服务端(上)_注册中心的实现

文章目录

  • Pre
  • 核心内容
  • 服务端结构概述
  • 注册中心的实现
    • 1. 注册中心的架构
    • 2. 面向接口编程的设计
    • 3. 注册中心的接口设计
    • 4. SPI机制的应用
  • 小结

在这里插入图片描述

Pre

Simple RPC - 01 框架原理及总体架构初探

Simple RPC - 02 通用高性能序列化和反序列化设计与实现

Simple RPC - 03 借助Netty实现异步网络通信

Simple RPC - 04 从零开始设计一个客户端(上)

Simple RPC - 05 从零开始设计一个客户端(下)_ 依赖倒置和SPI


核心内容

  1. 服务端结构概述:注册中心和RPC服务的结构及作用。
  2. 注册中心实现:通过单机版的注册中心实现共享元数据,分析其接口设计和SPI机制。
  3. RPC服务实现:理解服务端处理RPC请求的核心逻辑,包括如何注册服务和处理请求。
  4. 请求分发机制:深入了解RequestInvocation和RpcRequestHandler类中的请求分发机制。
  5. 代码分析与总结:通过代码实例进一步理解设计思想,并总结整体架构和设计原则。

服务端结构概述

在RPC框架中,服务端可以分为两个主要部分:注册中心RPC服务

  • 注册中心:负责管理服务元数据,并提供服务发现的功能。
  • RPC服务:负责处理客户端发来的RPC请求,并调用相应的业务服务。

简单来说:注册中心的作用是帮助客户端来寻址,找到对应 RPC 服务的物理地址;RPC 服务用于接收客户端桩的请求,调用业务服务的方法,并返回结果。


注册中心的实现

1. 注册中心的架构

通常,一个完整的注册中心包括客户端服务端两部分:

  • 客户端:向调用方提供 API,负责与注册中心服务端的通信。

  • 服务端:实际管理和记录每个 RPC 服务的注册信息,并将这些信息存储在元数据中。当客户端需要查找服务时,服务端会返回对应的服务地址。

在本例中,出于简化考虑,我们实现了一个单机版的注册中心。这个注册中心只有客户端部分,多个客户端通过读写同一个本地元数据文件实现服务信息的共享。

该注册中心只能在单机环境下运行,不支持跨服务器调用。

2. 面向接口编程的设计

尽管当前实现是单机版的注册中心,但通过“面向接口编程”的设计模式,我们可以在不修改已有代码的情况下,通过 SPI 插件机制,扩展出一个支持跨服务器调用的注册中心(例如,基于 HTTP 协议的实现)。


3. 注册中心的接口设计

在 RPC 框架的接入点接口 RpcAccessPoint 中,增加了一个用于获取注册中心实例的方法:

public interface RpcAccessPoint extends Closeable {/*** 获取注册中心的引用* @param nameServiceUri 注册中心 URI* @return 注册中心引用*/NameService getNameService(URI nameServiceUri);
}
  • 该方法接受一个注册中心的 URI 作为参数,并返回一个 NameService 接口的实例。这个 NameService 接口表示注册中心的客户端,可以用来和注册中心服务端通信。

NameService 接口中定义了与注册中心通信的核心方法:

public interface NameService {/*** 返回所有支持的协议*/Collection<String> supportedSchemes();/*** 连接注册中心* @param nameServiceUri 注册中心地址*/void connect(URI nameServiceUri);
}
  • supportedSchemes 方法返回注册中心支持的协议(例如 filehttp)。
  • connect 方法根据 URI 建立与注册中心的连接。

完整代码如下

/*** 注册中心接口定义* 该接口用于服务的注册和发现,支持多种通信协议** @author artisan*/
public interface NameService {/*** 获取所有支持的协议** @return 支持的协议集合*/Collection<String> supportedSchemes();/*** 连接注册中心** @param nameServiceUri 注册中心的URI地址*/void connect(URI nameServiceUri);/*** 注册服务** @param serviceName 服务名称* @param uri 服务的URI地址* @throws IOException 如果连接或注册失败,则抛出此异常*/void registerService(String serviceName, URI uri) throws IOException;/*** 查询服务地址** @param serviceName 服务名称* @return 服务的URI地址* @throws IOException 如果查找失败,则抛出此异常*/URI lookupService(String serviceName) throws IOException;
}

4. SPI机制的应用

通过 SPI 机制,RpcAccessPoint 可以根据 URI 中指定的协议,动态加载不同的 NameService 实现类。例如,在单机版注册中心中,NameService 的实现类是 LocalFileNameService,其具体功能是读写本地文件,存储和查找服务信息。

public class LocalFileNameService implements NameService {@Overridepublic Collection<String> supportedSchemes() {return Collections.singleton("file");}@Overridepublic void connect(URI nameServiceUri) {// 连接到本地文件,初始化文件读写工具}// 其他方法实现...
}

通过这种方式,新的注册中心实现可以通过 SPI 动态添加到系统中。例如,要实现一个基于 HTTP 的注册中心,只需开发一个新的 NameService 实现类,并将其添加到系统的 CLASSPATH 中即可。


LocalFileNameService代码如下


/*** LocalFileNameService 类实现了 NameService 接口,提供了一种基于文件系统来管理服务名称和URI的实现方式* 它使用 "file" 协议来操作本地文件,并将服务信息存储在文件中* @author artisan*/
public class LocalFileNameService implements NameService {private static final Logger logger = LoggerFactory.getLogger(LocalFileNameService.class);/*** 支持的协议集合,本实现仅支持 "file" 协议*/private static final Collection<String> schemes = Collections.singleton("file");/*** 用于存储服务信息的文件对象*/private File file;/*** 返回此服务支持的协议集合** @return 支持的协议集合*/@Overridepublic Collection<String> supportedSchemes() {return schemes;}/*** 连接到指定的名称服务URI,如果支持该URI的协议,则将URI解析为本地文件* 此方法首先检查给定的URI是否使用受支持的协议如果协议受支持,则将URI转换为本地文件路径* 如果不支持该协议,则抛出运行时异常** @param nameServiceUri 名称服务的URI,用于连接和解析* @throws RuntimeException 如果URI的协议不受支持,则抛出此异常*/@Overridepublic void connect(URI nameServiceUri) {// 检查URI的协议是否在支持的协议列表中if (schemes.contains(nameServiceUri.getScheme())) {// 如果协议受支持,则将URI转换为本地文件路径file = new File(nameServiceUri);} else {// 如果协议不受支持,则抛出异常throw new RuntimeException("Unsupported scheme!");}}/*** 注册服务,将服务名称和服务URI写入到文件中* 此方法是同步的,以确保并发访问时的数据一致性** @param serviceName 服务名称* @param uri 服务的URI* * @throws IOException 如果发生I/O错误*/@Overridepublic synchronized void registerService(String serviceName, URI uri) throws IOException {// 记录服务注册的日志信息logger.info("Register service: {}, uri: {}.", serviceName, uri);// 使用RandomAccessFile和FileChannel来读写文件,并确保资源在使用后能够正确关闭try (RandomAccessFile raf = new RandomAccessFile(file, "rw");FileChannel fileChannel = raf.getChannel()) {// 获取文件锁,以确保并发访问时的数据一致性FileLock lock = fileChannel.lock();try {// 获取文件长度,用于后续判断文件是否为空int fileLength = (int) raf.length();Metadata metadata;byte[] bytes;// 如果文件长度大于0,说明文件非空,读取并解析文件内容if (fileLength > 0) {bytes = new byte[(int) raf.length()];ByteBuffer buffer = ByteBuffer.wrap(bytes);// 循环读取文件内容到ByteBuffer中while (buffer.hasRemaining()) {fileChannel.read(buffer);}// 解析字节码为Metadata对象metadata = SerializeSupport.parse(bytes);} else {// 如果文件为空,创建一个新的Metadata对象metadata = new Metadata();}// 根据服务名获取或创建一个空的URI列表List<URI> uris = metadata.computeIfAbsent(serviceName, k -> new ArrayList<>());// 如果列表中不存在该URI,则添加进去if (!uris.contains(uri)) {uris.add(uri);}// 记录更新后的Metadata信息logger.info(metadata.toString());// 将Metadata对象序列化为字节码bytes = SerializeSupport.serialize(metadata);// 清空文件,为写入新的字节码做准备fileChannel.truncate(bytes.length);// 将文件指针移到文件开头,准备写入fileChannel.position(0L);// 将字节码写入文件fileChannel.write(ByteBuffer.wrap(bytes));// 强制将写入操作刷入磁盘fileChannel.force(true);} finally {// 释放文件锁lock.release();}}}/*** 根据服务名称查找服务的URI* 如果文件中存在对应的服务URI,则随机返回一个** @param serviceName 服务名称* @return 服务的URI,如果找不到则返回null* @throws IOException 如果发生I/O错误*/@Overridepublic URI lookupService(String serviceName) throws IOException {Metadata metadata;// 使用try-with-resources语句确保文件资源正确关闭try (RandomAccessFile raf = new RandomAccessFile(file, "rw");FileChannel fileChannel = raf.getChannel()) {// 获取文件锁以确保数据的一致性FileLock lock = fileChannel.lock();try {// 读取文件内容到字节数组byte[] bytes = new byte[(int) raf.length()];ByteBuffer buffer = ByteBuffer.wrap(bytes);// 循环读取直到文件末尾while (buffer.hasRemaining()) {fileChannel.read(buffer);}// 如果文件非空,则反序列化为Metadata对象,否则创建新的空Metadata对象metadata = bytes.length == 0 ? new Metadata() : SerializeSupport.parse(bytes);// 记录日志logger.info(metadata.toString());} finally {// 释放文件锁lock.release();}}// 从Metadata中获取服务的所有URIList<URI> uris = metadata.get(serviceName);// 如果没有找到对应的URI列表,返回nullif (null == uris || uris.isEmpty()) {return null;} else {// 随机选择一个URI返回return uris.get(ThreadLocalRandom.current().nextInt(uris.size()));}}
}

小结

  • 面向接口编程:设计时面向接口编程,使得系统具有良好的扩展性,可以通过增加 SPI 插件方式扩展新的功能。
  • 单机版注册中心:当前实现的是一个单机版的注册中心,通过本地文件共享元数据,不支持跨服务器调用。
  • SPI机制:通过 SPI 机制,可以动态加载不同的 NameService 实现,支持多种协议的注册中心。

这种设计模式确保了系统的灵活性可扩展性,为后续的功能扩展提供了便利。

在这里插入图片描述

相关文章:

Simple RPC - 06 从零开始设计一个服务端(上)_注册中心的实现

文章目录 Pre核心内容服务端结构概述注册中心的实现1. 注册中心的架构2. 面向接口编程的设计3. 注册中心的接口设计4. SPI机制的应用 小结 Pre Simple RPC - 01 框架原理及总体架构初探 Simple RPC - 02 通用高性能序列化和反序列化设计与实现 Simple RPC - 03 借助Netty实现…...

【深度学习】基于Transformers的大模型推理框架

本文旨在介绍基于transformers的decoder-only语言模型的推理框架。与开源推理框架不同的是&#xff1a; 本框架没有利用额外的开源推理仓库&#xff0c;仅基于huggingface&#xff0c;transformers&#xff0c;pytorch等原生工具进行推理&#xff0c;适合新手学习大模型推理流…...

电脑监控怎样看回放视频?一键解锁电脑监控回放,守护安全不留死角!高效员工电脑监控,回放视频随时查!

你是否曾好奇那些键盘敲击背后的秘密&#xff1f;电脑监控不仅是守护企业安全的隐形盾牌&#xff0c;更是揭秘高效与合规的魔法镜&#xff01;一键解锁安企神监控回放&#xff0c;就像打开时间宝盒&#xff0c;让过去的工作瞬间跃然眼前。无论是精彩瞬间还是潜在风险&#xff0…...

【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)

文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布&#xff0c;2.0版本迎来第一个稳定版本&#xff0c;同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架&#xff0c;可以让你使用前端技术来构建桌面…...

deque容器

deque容器的基本概念 deque 是 C 标准库中的双端队列&#xff08;double-ended queue&#xff09;容器&#xff0c;提供了在两端进行插入和删除操作的功能。 deque与vector区别&#xff1a; vector对于头部的插入删除效率低&#xff0c;数据量越大效率越低。deque相对而言&am…...

Redis远程字典服务器(9)—— 类型补充

类型查询传送门&#xff1a;Understand Redis data types | Docs 一&#xff0c;stream类型 官方文档对于这个类型的解释是&#xff1a;streams是一个数据结构&#xff0c;它表现得像一个 “append-only log”&#xff0c;就是只能往后面添加&#xff0c;底层是字符串&#x…...

VMware虚拟机nat无法联通主机

VMware在nat模式下主机无法ping通虚拟机 原因&#xff1a; 虚拟机和对应的网卡不在一个网段 虚拟机开启了防火墙 解决方法: 首先判断虚拟机的网络ip是否和网卡在一个网段上 判断虚拟机使用的网卡 nat模式在VMware虚拟机中一般只有一个对应的网卡 如图笔者的nat网卡为VM…...

「字符串」详解AC自动机并实现对应的功能 / 手撕数据结构(C++)

目录 前置知识 概述 核心概念&#xff1a;fail指针 作用 构建 图示 Code 成员变量 创建销毁 添加词库 文本扫描 复杂度 Code 前置知识 在此前&#xff0c;你应该首先了解trie树&#xff08;字典树&#xff09;的概念&#xff1a; 「字符串」详解Trie&#xff0…...

freecad遭遇网络不同无法安装插件Addon Manager: Unexpected 0 response from server

16:31:18 Addon Manager: Unexpected 0 response from server 16:31:18 Failed to connect to GitHub. Check your connection and proxy settings. 打开freecad的插件管理器时候&#xff0c;有些地方&#xff0c;比如我在家里就不行&#xff0c;在公司就ok。 于是找到了解…...

Ruby模板引擎:构建动态视图的艺术

标题&#xff1a;Ruby模板引擎&#xff1a;构建动态视图的艺术 在Ruby on Rails的世界里&#xff0c;模板引擎是构建动态网页的基石。它们允许开发者将服务器端的逻辑嵌入到HTML中&#xff0c;实现数据的动态展示。本文将深入探讨Ruby中几种常用的模板引擎&#xff0c;包括ERB…...

HarmonyOS NEXT星河版零基础入门(3)

目录 1. 系统弹出框 2.interface转成class类 3.vp/fp 4. 写一个正方形 设置它的宽度 但不设定高度 不论屏幕怎么变实现他的宽高比 5.State 6.图片和资源 7.淘宝镜像 7.1windows 脚本禁用&#xff08;操作策略 允许npm包的命令可执行&#xff09; 8. es6&ArkTS中…...

第二十讲 python中的异常结构-try except-else-finally

目录 1.try... except 结构 2. try... 多个except结构 3. try...except...else结构 4. try...except...finally结构 5. return语句和异常处理问题 5.1 异常处理前的 return 5.2异常处理后的 return 5.3 finally 块中的 return 6.常见的异常 1.try... except 结构 try except 是…...

springer 投稿系统中返修注意点

初次提交 初次提交时&#xff0c; manuscript 提交的是 pdf 文件 返修后提交 在经过返修之后需要提交的是注意一下几点&#xff1a; 此时提交的Blined manuscript &#xff0c;虽然名字没变&#xff0c;但不能再提交pdf 文件&#xff0c; 而需要提交的是可编辑的源文件 .te…...

CSS:display和visiblity

隐藏元素- display:none和visibility:hidden display 属性设置一个元素应如何显示&#xff0c;visibility 属性指定一个元素应可见还是隐藏。 隐藏一个元素可以通过吧display属性设置为“none”&#xff0c;或者把visibility属性设置为“hidden”。但是这两种会产生不同的结果…...

43.x86游戏实战-XXX寻找吸怪坐标

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...

Redis地理位置相关应用

下面是一个结合 MySQL 数据库和 Redis 的地理位置服务示例&#xff0c;包含表结构、PHP 代码和 Redis 操作&#xff0c;用于处理基于地理位置的数据存储和查询。 1. 创建 MySQL 数据库表 首先&#xff0c;创建一个用于存储位置信息的 MySQL 表&#xff0c;如下所示&#xff1…...

优化WAN流量:如何通过调整系统设置降低企业网络成本

一、症状与问题背景 当电脑显示空闲状态时&#xff0c;如果满足以下条件&#xff0c;第二拨号链接可能会意外激活&#xff1a; 您正在使用基于 Microsoft Windows 的计算机&#xff0c;该计算机连接到远程网络并且是 Active Directory 域服务 (AD DS) 域的成员。 您通过二级…...

Java-HttpHeaders请求头或响应头

HttpHeaders 是 Spring Framework 中的一个类,用于封装 HTTP 头部信息。它提供了一种方便的 方式来设置 HTTP 请求头和处理 HTTP 响应头。下面分别介绍如何使用 HttpHeaders 来设置请求 头和处理响应头。 设置请求头 在发送 HTTP 请求时,可以通过 HttpHeaders 设置各种请…...

Elasticsearch高阶查询

Elasticsearch高阶查询 文章目录 Elasticsearch高阶查询相关性和相关性算分相关性 (Relevance)什么是TF-IDFBM25explain关键字Boosting如何通过Boost控制想要的文档排在前面&#xff1f; 布尔查询&#xff08;bool Query&#xff09;查询语法语法格式 单字符串多字段查询三种场…...

【流媒体】RTMPDump—RTMP_Connect函数(握手、网络连接)

目录 1. RTMP_Connect函数1.1 网络层连接&#xff08;RTMP_Connect0&#xff09;1.2 RTMP连接&#xff08;RTMP_Connect1&#xff09;1.2.1 握手&#xff08;HandShake&#xff09;1.2.2 RTMP的NetConnection&#xff08;SendConnectPacket&#xff09; 2.小结 RTMP协议相关&am…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...

2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案

一、延迟敏感行业面临的DDoS攻击新挑战 2025年&#xff0c;金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征&#xff1a; AI驱动的自适应攻击&#xff1a;攻击流量模拟真实用户行为&#xff0c;差异率低至0.5%&#xff0c;传统规则引…...

【SSM】SpringMVC学习笔记7:前后端数据传输协议和异常处理

这篇学习笔记是Spring系列笔记的第7篇&#xff0c;该笔记是笔者在学习黑马程序员SSM框架教程课程期间的笔记&#xff0c;供自己和他人参考。 Spring学习笔记目录 笔记1&#xff1a;【SSM】Spring基础&#xff1a; IoC配置学习笔记-CSDN博客 对应黑马课程P1~P20的内容。 笔记2…...