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
核心内容
- 服务端结构概述:注册中心和RPC服务的结构及作用。
- 注册中心实现:通过单机版的注册中心实现共享元数据,分析其接口设计和SPI机制。
- RPC服务实现:理解服务端处理RPC请求的核心逻辑,包括如何注册服务和处理请求。
- 请求分发机制:深入了解RequestInvocation和RpcRequestHandler类中的请求分发机制。
- 代码分析与总结:通过代码实例进一步理解设计思想,并总结整体架构和设计原则。
服务端结构概述
在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 方法返回注册中心支持的协议(例如
file
或http
)。 - 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语言模型的推理框架。与开源推理框架不同的是: 本框架没有利用额外的开源推理仓库,仅基于huggingface,transformers,pytorch等原生工具进行推理,适合新手学习大模型推理流…...

电脑监控怎样看回放视频?一键解锁电脑监控回放,守护安全不留死角!高效员工电脑监控,回放视频随时查!
你是否曾好奇那些键盘敲击背后的秘密?电脑监控不仅是守护企业安全的隐形盾牌,更是揭秘高效与合规的魔法镜!一键解锁安企神监控回放,就像打开时间宝盒,让过去的工作瞬间跃然眼前。无论是精彩瞬间还是潜在风险࿰…...

【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)
文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布,2.0版本迎来第一个稳定版本,同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架,可以让你使用前端技术来构建桌面…...

deque容器
deque容器的基本概念 deque 是 C 标准库中的双端队列(double-ended queue)容器,提供了在两端进行插入和删除操作的功能。 deque与vector区别: vector对于头部的插入删除效率低,数据量越大效率越低。deque相对而言&am…...
Redis远程字典服务器(9)—— 类型补充
类型查询传送门:Understand Redis data types | Docs 一,stream类型 官方文档对于这个类型的解释是:streams是一个数据结构,它表现得像一个 “append-only log”,就是只能往后面添加,底层是字符串&#x…...

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

「字符串」详解AC自动机并实现对应的功能 / 手撕数据结构(C++)
目录 前置知识 概述 核心概念:fail指针 作用 构建 图示 Code 成员变量 创建销毁 添加词库 文本扫描 复杂度 Code 前置知识 在此前,你应该首先了解trie树(字典树)的概念: 「字符串」详解Trie࿰…...

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的插件管理器时候,有些地方,比如我在家里就不行,在公司就ok。 于是找到了解…...
Ruby模板引擎:构建动态视图的艺术
标题:Ruby模板引擎:构建动态视图的艺术 在Ruby on Rails的世界里,模板引擎是构建动态网页的基石。它们允许开发者将服务器端的逻辑嵌入到HTML中,实现数据的动态展示。本文将深入探讨Ruby中几种常用的模板引擎,包括ERB…...

HarmonyOS NEXT星河版零基础入门(3)
目录 1. 系统弹出框 2.interface转成class类 3.vp/fp 4. 写一个正方形 设置它的宽度 但不设定高度 不论屏幕怎么变实现他的宽高比 5.State 6.图片和资源 7.淘宝镜像 7.1windows 脚本禁用(操作策略 允许npm包的命令可执行) 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 投稿系统中返修注意点
初次提交 初次提交时, manuscript 提交的是 pdf 文件 返修后提交 在经过返修之后需要提交的是注意一下几点: 此时提交的Blined manuscript ,虽然名字没变,但不能再提交pdf 文件, 而需要提交的是可编辑的源文件 .te…...
CSS:display和visiblity
隐藏元素- display:none和visibility:hidden display 属性设置一个元素应如何显示,visibility 属性指定一个元素应可见还是隐藏。 隐藏一个元素可以通过吧display属性设置为“none”,或者把visibility属性设置为“hidden”。但是这两种会产生不同的结果…...

43.x86游戏实战-XXX寻找吸怪坐标
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...
Redis地理位置相关应用
下面是一个结合 MySQL 数据库和 Redis 的地理位置服务示例,包含表结构、PHP 代码和 Redis 操作,用于处理基于地理位置的数据存储和查询。 1. 创建 MySQL 数据库表 首先,创建一个用于存储位置信息的 MySQL 表,如下所示࿱…...

优化WAN流量:如何通过调整系统设置降低企业网络成本
一、症状与问题背景 当电脑显示空闲状态时,如果满足以下条件,第二拨号链接可能会意外激活: 您正在使用基于 Microsoft Windows 的计算机,该计算机连接到远程网络并且是 Active Directory 域服务 (AD DS) 域的成员。 您通过二级…...
Java-HttpHeaders请求头或响应头
HttpHeaders 是 Spring Framework 中的一个类,用于封装 HTTP 头部信息。它提供了一种方便的 方式来设置 HTTP 请求头和处理 HTTP 响应头。下面分别介绍如何使用 HttpHeaders 来设置请求 头和处理响应头。 设置请求头 在发送 HTTP 请求时,可以通过 HttpHeaders 设置各种请…...
Elasticsearch高阶查询
Elasticsearch高阶查询 文章目录 Elasticsearch高阶查询相关性和相关性算分相关性 (Relevance)什么是TF-IDFBM25explain关键字Boosting如何通过Boost控制想要的文档排在前面? 布尔查询(bool Query)查询语法语法格式 单字符串多字段查询三种场…...

【流媒体】RTMPDump—RTMP_Connect函数(握手、网络连接)
目录 1. RTMP_Connect函数1.1 网络层连接(RTMP_Connect0)1.2 RTMP连接(RTMP_Connect1)1.2.1 握手(HandShake)1.2.2 RTMP的NetConnection(SendConnectPacket) 2.小结 RTMP协议相关&am…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...