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

news2024/11/25 22:38:25

文章目录

  • 概述
  • RPC 框架是怎么调用远程服务的?
    • 客户端侧的逻辑
    • 服务端侧的逻辑
    • 完整流程
  • 客户端是如何找到服务端地址的呢?
    • 核心:NamingService
    • 跨语言的RPC实现原理
  • RPC 框架的总体结构
    • 对外接口服务
    • 注册中心
    • 如何使用
      • 业务服务接口
      • 客户端
      • 服务端
    • 模块介绍
  • 小结

在这里插入图片描述

概述

RPC,全称为Remote Procedure Call(远程过程调用),是一种计算机通信协议,用于允许程序在不同的计算机或网络节点上通过远程方式调用函数或方法。它允许开发者编写分布式应用程序,使得分布在不同位置的计算机能够像本地调用一样进行通信。

以下是RPC的主要特点和工作原理:

  1. 远程过程调用:RPC允许一个程序(客户端)调用另一个程序(服务器)上的函数或方法,就像调用本地函数一样,而无需了解底层网络通信细节。

  2. 抽象接口:RPC通常使用IDL(接口定义语言)来定义远程方法的接口,确保客户端和服务器之间的通信是基于一致的数据结构和方法签名的。IDL提供了一种标准化的方式来描述接口,以便生成客户端和服务器端的代码。

  3. 序列化和反序列化:在RPC中,数据需要在客户端和服务器之间进行序列化和反序列化。序列化是将数据转换为可在网络上传输的格式,而反序列化是将接收到的数据重新还原为本地数据结构。

  4. 通信协议:RPC可以基于不同的通信协议运行,如HTTP、TCP、UDP等。常见的RPC框架包括gRPC、Apache Thrift、CORBA等,它们可以根据需要选择合适的通信协议和传输层。

  5. 安全性:RPC通常提供安全性功能,以确保数据在传输过程中的机密性和完整性。这可以通过使用加密、身份验证和授权来实现。

  6. 并发和性能:RPC框架通常会考虑并发性和性能优化,以支持多个并发请求,并在网络通信方面进行性能优化。

总之,RPC是一种用于构建分布式系统的通信协议,它允许应用程序在不同计算机之间进行远程调用,以实现分布式计算和协作。不同的RPC框架提供不同的功能和性能特性,开发者可以根据项目需求选择合适的RPC解决方案。


RPC 框架是怎么调用远程服务的?

所有的 RPC 框架,它们的总体结构和实现原理都是一样的。接下来,我们以最常使用的 Spring 和 Dubbo 配合的微服务体系为例,一起来看一下,RPC 框架到底是如何实现调用远程服务的。

一般来说,我们的客户端和服务端分别是这样的:


@Component
public class HelloClient {
    @Reference // dubbo注解
    private HelloService helloService;
    public String hello() {
      return helloService.hello("World");
    }
}




@Service // dubbo注解
@Component
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "Hello " + name;
    }
}
  • 在客户端,我们可以通过 @Reference 注解,获得一个实现了 HelloServicer 这个接口的对象,我们的业务代码只要调用这个对象的方法,就可以获得结果。对于客户端代码来说,调用就是 helloService 这个本地对象,但实际上,真正的服务是在远程的服务端进程中实现的

  • 在服务端我们的实现类 HelloServiceImpl,实现了 HelloService 这个接口。然后,我们通过 @Service 这个注解(注意,这个 @Service 是 Dubbo 提供的注解,不是 Spring 提供的同名注解),在 Dubbo 框架中注册了这个实现类 HelloServiceImpl。在服务端,我们只是提供了接口 HelloService 的实现,并没有任何远程调用的实现代码。

对于业务代码来说,无论是客户端还是服务端,除了增加了两个注解以外,和实现一个进程内调用没有任何区别。Dubbo 看起来就像把服务端进程中的实现类“映射”到了客户端进程中一样。


接下来我们一起来看一下,Dubbo 这类 RPC 框架是如何来实现调用远程服务的。

客户端侧的逻辑

在客户端,业务代码得到的 HelloService 这个接口的实例,并不是我们在服务端提供的真正的实现类 HelloServiceImpl 的一个实例。它实际上是由 RPC 框架提供的一个代理类的实例。这个代理类有一个专属的名称,叫“桩(Stub)”

在不同的 RPC 框架中,这个桩的生成方式并不一样,

  • 有些是在编译阶段生成的,
  • 有些是在运行时动态生成的,

这个和编程语言的语言特性是密切相关的,所以在不同的编程语言中有不同的实现,这部分很复杂,可以先不用过多关注。我们只需要知道这个桩它做了哪些事儿就可以了。

我们知道,HelloService 的桩,同样要实现 HelloServer 接口,客户端在调用 HelloService 的 hello 方法时,实际上调用的是桩的 hello 方法,在这个桩的 hello 方法里面,它会构造一个请求,这个请求就是一段数据结构,请求中包含两个重要的信息

  • 请求的服务名,在我们这个例子中,就是 HelloService#hello(String),也就是说,客户端调用的是 HelloService 的 hello 方法;
  • 请求的所有参数,在我们这个例子中,就只有一个参数 name, 它的值是“World”

然后,它会把这个请求发送给服务端,等待服务的响应


这个时候,请求到达了服务端,然后我们来看服务端是怎么处理这个请求的

服务端侧的逻辑

  • 服务端的 RPC 框架收到这个请求之后,先把请求中的服务名解析出来,然后,根据这个服务名找一下,在服务端进程中,有没有这个服务名对应的服务提供者。

    在这个例子的服务端中,由于我们已经通过 @Service 注解向 RPC 框架注册过 HelloService 的实现类,所以,RPC 框架在收到请求后,可以通过请求中的服务名找到 HelloService 真正的实现类 HelloServiceImpl。

  • 找到实现类之后,RPC 框架会调用这个实现类的 hello 方法,使用的参数值就是客户端发送过来的参数值。

  • 服务端的 RPC 框架在获得返回结果之后,再将结果封装成响应,返回给客户端。

  • 客户端 RPC 框架的桩收到服务端的响应之后,从响应中解析出返回值,返回给客户端的调用方。

完整流程

这样就完成了一次远程调用。我把这个调用过程画成一张图放在下面,可以对着这张图再消化一下上面的流程。

在这里插入图片描述


客户端是如何找到服务端地址的呢?

核心:NamingService

在上面的这个调用流程中,我们忽略了一个问题,那就是客户端是如何找到服务端地址的呢?在 RPC 框架中,这部分的实现原理其实和消息队列的实现是完全一样的,都是通过一个 NamingService 来解决的

在 RPC 框架中,这个 NamingService 一般称为注册中心。

  • 服务端的业务代码在向 RPC 框架中注册服务之后,RPC 框架就会把这个服务的名称和地址发布到注册中心上。

  • 客户端的桩在调用服务端之前,会向注册中心请求服务端的地址,请求的参数就是服务名称,也就是我们上面例子中的方法签名 HelloService#hello

  • 注册中心会返回提供这个服务的地址,然后客户端再去请求服务端

跨语言的RPC实现原理

有些 RPC 框架,比如 gRPC,是可以支持跨语言调用的。它的服务提供方和服务调用方是可以用不同的编程语言来实现的。比如,我们可以用 Python 编写客户端,用 Go 语言来编写服务端,这两种语言开发的服务端和客户端仍然可以正常通信。这种支持跨语言调用的 RPC 框架的实现原理和普通的单语言的 RPC 框架并没有什么本质的不同

再回顾一下上面那张调用的流程图,如果需要实现跨语言的调用,也就是说,图中的客户端进程和服务端进程是由两种不同的编程语言开发的。其实,只要客户端发出去的请求能被服务端正确解析,同样,服务端返回的响应,客户端也能正确解析,其他的步骤完全不用做任何改变,不就可以实现跨语言调用了吗?

客户端和服务端,收发请求响应的工作都是 RPC 框架来实现的,所以,只要 RPC 框架保证在不同的编程语言中,使用相同的序列化协议,就可以实现跨语言的通信。

另外,为了在不同的语言中能描述相同的服务定义,也就是我们上面例子中的 HelloService 接口,跨语言的 RPC 框架还需要提供一套描述服务的语言,称为 IDL(Interface description language)

所有的服务都需要用 IDL 定义,再由 RPC 框架转换为特定编程语言的接口或者抽象类。这样,就可以实现跨语言调用了。


讲到这里,RPC 框架的基本实现原理就很清楚了,可以看到,实现一个简单的 RPC 框架并不是很难,这里面用到的绝大部分技术, 包括:高性能网络传输、序列化和反序列化、服务路由的发现方法等,都和消息队列实现原理类似


RPC 框架的总体结构

下面就一起来实现一个“麻雀虽小但五脏俱全”的 RPC 框架。

对外接口服务

采用 Java 语言来实现这个 RPC 框架。我们把 RPC 框架对外提供的所有服务定义在一个接口 RpcAccessPoint 中

/**
 * RPC框架对外提供的服务接口
 */
public interface RpcAccessPoint extends Closeable{
    /**
     * 客户端获取远程服务的引用
     * @param uri 远程服务地址
     * @param serviceClass 服务的接口类的Class
     * @param <T> 服务接口的类型
     * @return 远程服务引用
     */
    <T> T getRemoteService(URI uri, Class<T> serviceClass);
    /**
     * 服务端注册服务的实现实例
     * @param service 实现实例
     * @param serviceClass 服务的接口类的Class
     * @param <T> 服务接口的类型
     * @return 服务地址
     */
    <T> URI addServiceProvider(T service, Class<T> serviceClass);
    /**
     * 服务端启动RPC框架,监听接口,开始提供远程服务。
     * @return 服务实例,用于程序停止的时候安全关闭服务。
     */
    Closeable startServer() throws Exception;
}

  • 第一个方法 getRemoteService 供客户端来使用,这个方法的作用和我们上面例子中 Dubbo 的 @Reference 注解是一样的,客户端调用这个方法可以获得远程服务的实例。
  • 第二个方法 addServiceProvider 供服务端来使用,这个方法的作用和 Dubbo 的 @Service 注解是一样的,服务端通过调用这个方法来注册服务的实现。
  • 方法 startServer 和 close(在父接口 Closeable 中定义)用于服务端启动和停止服务。

注册中心

另外,我们还需要定一个注册中心的接口 NameService


/**
 * 注册中心
 */
public interface NameService {
    /**
     * 注册服务
     * @param serviceName 服务名称
     * @param uri 服务地址
     */
    void registerService(String serviceName, URI uri) throws IOException;
    /**
     * 查询服务地址
     * @param serviceName 服务名称
     * @return 服务地址
     */
    URI lookupService(String serviceName) throws IOException;
}

这个注册中心只有两个方法,分别是注册服务地址 registerService 和查询服务地址 lookupService。

在这里插入图片描述


如何使用

以上,就是我们要实现的这个 RPC 框架的全部功能了。然后,我们通过一个例子看一下这个 RPC 框架如何来使用。同样,

业务服务接口

需要先定义一个服务接口:

public interface HelloService {
    String hello(String name);
}

然后我们分别看一下服务端和客户端是如何使用这个 RPC 框架的

客户端

客户端

URI uri = nameService.lookupService(serviceName);
HelloService helloService = rpcAccessPoint.getRemoteService(uri, HelloService.class);
String response = helloService.hello(name);
logger.info("收到响应: {}.", response);
  • 客户端首先调用注册中心 NameService 的 lookupService 方法,查询服务地址,
  • 然后调用 rpcAccessPoint 的 getRemoteService 方法,获得远程服务的本地实例,也就是我们刚刚讲的“桩”helloService。
  • 最后,调用 helloService 的 hello 方法,获得返回值并打印出来

服务端

服务端

  • 首先我们需要有一个 HelloService 的实现:
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        String ret = "Hello, " + name;
        return ret;
    }
}
  • 然后,我们将这个实现注册到 RPC 框架上,并启动 RPC 服务:
rpcAccessPoint.startServer();
URI uri = rpcAccessPoint.addServiceProvider(helloService, HelloService.class);
nameService.registerService(serviceName, uri);
  • 首先启动 RPC 框架的服务,
  • 然后调用 rpcAccessPoint.addServiceProvider 方法注册 helloService 服务,
  • 然后我们再调用 nameServer.registerService 方法,在注册中心注册服务的地址。

可以看到,我们将要实现的这个 RPC 框架的使用方式,总体上和上面使用 Dubbo 和 Spring 的例子是一样的,唯一的一点区别是,由于我们没有使用 Spring 和注解,所以需要用代码的方式实现同样的功能。

模块介绍

整个项目分为如下 5 个 Module:

在这里插入图片描述

  • 其中,R PC 框架提供的服务 RpcAccessPoint 和注册中心服务 NameService,这两个接口的定义在 Module rpc-api 中。

使用框架的例子,

  • HelloService 接口定义在 Module hello-service-api 中,
  • 例子中的客户端和服务端分别在 client 和 server 这两个 Module 中。

小结

在实现 RPC 框架之前,需要先掌握 RPC 框架的实现原理。

在 RPC 框架中,最关键的就是理解“桩”的实现原理,桩是 RPC 框架在客户端的服务代理,它和远程服务具有相同的方法签名,或者说是实现了相同的接口。

客户端在调用 RPC 框架提供的服务时,实际调用的就是“桩”提供的方法,在桩的实现方法中,它会发请求的服务名和参数到服务端,

服务端的 RPC 框架收到请求后,解析出服务名和参数后,调用在 RPC 框架中注册的“真正的服务提供者”,然后将结果返回给客户端

在这里插入图片描述

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

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

相关文章

【数据结构】:栈的实现

1 栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则 压栈…

Linux实现原理 — I/O 处理流程与优化手段

Linux I/O 接口 Linux I/O 接口可以分为以下几种类型&#xff1a; 文件 I/O 接口&#xff1a;用于对文件进行读写操作的接口&#xff0c;包括 open()、read()、write()、close()、lseek() 等。 网络 I/O 接口&#xff1a;用于网络通信的接口&#xff0c;包括 socket()、conne…

多目标鳟海鞘算法(Multi-objective Salp Swarm Algorithm,MSSA)求解微电网优化MATLAB

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、多目标鳟海鞘算法MSSA 多…

Go持续改进与代码审查

通过Golang提高软件质量 在快节奏的软件开发世界中&#xff0c;保持领先至关重要。在实现软件工程卓越的基石之一是持续改进的实践&#xff0c;而在这个旅程中的一个关键工具是代码审查过程。在本文中&#xff0c;我们将深入探讨持续改进的重要性&#xff0c;并探讨代码审查在…

【数据结构-字符串 三】【栈的应用】字符串解码

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【字符串转换】&#xff0c;使用【字符串】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

Android位置服务和应用权限

Github:https://github.com/MADMAX110/Odometer 一、使用位置服务 之前的Odometer应用是显示一个随机数&#xff0c;现在要使用Android的位置服务返回走过的距离。 修改getDiatance方法使其返回走过的距离&#xff0c;为此要用Android的位置服务。这些服务允许你得到用户的当…

当10年程序员是什么体验?存款几位数?

最近网上一个话题吸引了许多人的讨论&#xff0c;当10年程序员&#xff0c;是一种什么体验&#xff1f; 都说程序员的高收入和工作年限应该成正比&#xff0c;真的是这样吗&#xff1f;工作10年的程序员&#xff0c;工资应该是什么水平&#xff1f;不少网友纷纷“现身说法”..…

qt中json类

目录 QJsonValue QJsonObject QJsonArray QJsonDocument 案例&#xff1a; Qt 5.0开始提供了对Json的支持&#xff0c;我们可以直接使用Qt提供的Json类进行数据的组织和解析&#xff0c;下面介绍4个常用的类。 QJsonValue 该类封装了JSON支持的数据类型。 布尔类型&#xf…

希尔贝壳受邀参加《人工智能开发平台通用能力要求 第4部分:大模型技术要求》标准第一次研讨会

随着大模型技术与经验的不断累积&#xff0c;该方向也逐渐从聚焦技术突破&#xff0c;到关注开发、部署、应用的全流程工程化落地。为完善人工智能平台标准体系建设&#xff0c;满足产业多样化需求&#xff0c;2023年9月7日&#xff0c;中国信通院云大所在线上召开《人工智能开…

C# Thread.Sleep(0)有什么用?

一、理论分析 回答这个要先从线程时间精度&#xff08;时间片&#xff09;开始说起。很多参考书说&#xff0c;默认情况下&#xff0c;时间片为15ms 左右&#xff0c;但是这是已经过时的知识。在老的 Windows 操作系统里&#xff0c;应用程序模式时时间片 15ms 左右&#xff0…

Jwt简介+工具类应用+Jwt集成spa项目

目录 一、Jwt简介 1.1 Jwt是什么 1.2 为什么使用Jwt 1.3 Jwt的工作原理 1.4 Jwt的组成 1.5 Jwt的验证过程 1.6 JWT令牌刷新思路 二、Jwt工具类 2.1 Jwt工具类是什么 2.2 Jwt工具类的使用 2.2.1 生成Jwt 2.2.2 解析Jwt 2.2.3 复制JWT并延时30分钟 2.2.4 测试JWT的有…

修复画笔工具组

修复画笔工具组 修复画笔工具组包括污点修复画笔工具、修复画笔工具、修补工具、内容感知移动工具和红眼工具&#xff0c;主要用于图像的修复或修补。 一、污点修复画笔工具 污点修复画笔工具可以去除图像中的污点、裂痕等不理想的部分&#xff0c;将其用与周围相似的图形来填充…

pc端使用微信扫码登录(思路篇)

我们在PC端网页中登录的时候有些需要微信扫码登录 例如CSDN网站登录 扫描之后 需要关注公众号 关注公众号就登录成功了 如何实现 流程图 步骤 1.pc端点击登录向业务服务器请求要登录二维码 2.业务服务器拿到用户端唯一参数或socketId&#xff08;使用websocket连接&#x…

10款精选的后台管理系统

1.vue2-manage 此项目是 vue element-ui 构建的后台管理系统&#xff0c;是后台项目node-elm 的管理系统&#xff0c;所有的数据都是从服务器实时获取的真实数据&#xff0c;具有真实的注册、登陆、管理数据、权限验证等功能。 项目地址&#xff1a;https://github.com/baili…

NSDT孪生编辑器助力智慧城市

技术有能力改变城市的运作方式&#xff0c;提高效率&#xff0c;为游客和居民提供更好的体验&#xff0c;实现更可持续的运营和更好的决策。 当今城市面临的主要挑战是什么&#xff0c;成为智慧城市如何帮助克服这些挑战&#xff1f; 我们生活在一个日益城市化的世界&#xf…

【Java 进阶篇】JavaScript Array数组详解

当我们编写JavaScript代码时&#xff0c;经常需要处理一组数据。JavaScript中的数组&#xff08;Array&#xff09;是一种用于存储多个值的数据结构&#xff0c;它提供了许多方法和功能&#xff0c;使我们能够方便地操作这些数据。在本篇博客中&#xff0c;我们将详细探讨JavaS…

【Linux初阶】多线程2 | 分离线程,线程库,线程互斥,可重入VS线程安全,锁的常见概念

文章目录 ☀️一、分离线程&#x1f33b;1.pthread_ self - 获取线程ID&#x1f33b;2.线程分离 ☀️二、用户级线程库&#x1f33b;1.pthread_t&#x1f33b;2.理解用户级线程库 - pthread库&#x1f33b;3.局部存储 ☀️三、线程互斥&#x1f33b;1.线程间的互斥相关概念&…

【Kali】简单记录

文章目录 信息收集DNS记录分析hostdigdnsenum 路由信息tcptraceroutetctrace 搜索引擎 目标识别arpingfping 识别操作系统p0f 服务枚举端口扫描nmap识别VPN服务器 漏洞映射exploitdbmsfconsole 提权arpspoofDsniff 信息收集 DNS记录分析 host host www.example.com host -a …

Windows 多媒体编程库 DirectX 介绍

目录 1、什么是DirectX&#xff1f; 2、使用DirectX的好处 2.1、DirectX为软件开发者提供硬件无关性 2.2、为硬件开发提供策略 3、DirectX的主体构成 3.1、Direct3D 3.2、DirectDraw 3.3、DirectPlay 3.4、DirectSound 3.5、DirectMusic 3.6、DirectInput 4、Dire…

Python数据分析实战-实现T检验(附源码和实现效果)

实现功能 T 检验&#xff08;Students t-test&#xff09;是一种常用的统计方法&#xff0c;用于比较两个样本之间的均值是否存在显著差异。它可以应用于许多场景&#xff0c;其中一些常见的应用场景包括&#xff1a; A/B 测试&#xff1a;在市场营销和用户体验研究中&#xf…