RPC 原理详解

文章目录

    • 什么是 RPC
    • RPC 基本原理
      • RPC核心功能
      • 服务寻址
      • 数据编解码
      • 网络传输
      • 一次RPC的调用过程
    • 实践
      • 基于HTTP协议的RPC
      • 基于TCP协议的RPC

什么是 RPC

RPC(Remote Procedure Call),即远程过程调用,它允许像调用本地服务一样调用远程服务。是一种服务器-客户端(Client/Server)模式。

  • 远程:指的是需要经过网络的,而不是应用内部、机器内部进行的。
  • 过程:也就是方法。

那“远程过程调用”,就是:可以跨过一段网络,调用另外一个网络节点上的方法。以上就是对远程过程调用的简单理解。

RPC 调用分以下两种:

  1. 同步调用:客户方等待调用执行完成并返回结果。
  2. 异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。 若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。

异步和同步的区分在于是否等待服务端执行完成并返回结果。

RPC 基本原理

RPC核心功能

知道什么是RPC以后就会发现,RPC需要解决一些问题:

  1. 既然是远程调用,那么客户端如何知道服务端的地址?
  2. 如果客户端和服务端使用的是不同语言写的程序,那么参数该如何表达和解析?
  3. 如何进行网络传输?

这三个问题的解决方案也是RPC的核心功能:服务寻址、数据编解码和网络传输。

服务寻址

如果是本地调用,被调用的方法在同一个进程内,操作系统或者是虚拟机可以去地址空间去找;但是在远程调用中,这是行不通的,因为两个进程的地址空间是完全不一样的,肯定也无法知道远端的进程在那。

如果要想实现远程调用,我们需要对服务消费者和服务提供者两者进行约束:在远程过程调用中所有的函数都必须有一个 ID,这个 ID 在整套系统中是唯一存在确定的。服务消费者在做远程过程调用时,发送的消息体中必须要携带这个 ID。服务消费者和服务提供者分别维护一个函数和 ID 的对应表。当服务消费者需要进行远程调用时,它就查一下这个表,找出对应的 ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码就行。

服务寻址的实现方式有很多种,常见的是:服务注册中心。要调用服务,首先你需要一个服务注册中心去查询对方服务都有哪些实例,然后根据负载均衡策略择优选一。

服务注册
  1. 从服务提供者的角度看:当提供者服务启动时,需要自动向注册中心注册服务;当提供者服务停止时,需要向注册中心注销服务;提供者需要定时向注册中心发送心跳。如果一段时间未收到来自提供者的心跳后,注册中心会判定提供者已经停止服务,并从注册中心下架对应的服务。
  2. 从调用者的角度看:调用者启动时订阅注册中心的消息并从注册中心获取提供者的地址;当有提供者上线或者下线时,注册中心会告知到调用者;调用者下线时,取消订阅。

数据编解码

对计算机网络稍微有一点熟悉的同学都知道,数据在网络中传输都是二进制的:01010101010101010,类似这种,只有二进制数据才能在网络间传。选择好的序列化协议特别重要,一个好的序列化协议能减少序列化数据带来的性能损耗。常见的RPC序列化协议如下:

  • XML(Extensible Markup Language)是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。狭义web service就是基于SOAP消息传递协议(一个基于XML的可扩展消息信封格式)来进行数据交换的。

  • JSON(Javascript Object Notation)起源于弱类型语言Javascript, 是采用"Attribute-value"的方式来描述对象协议。与XML相比,其协议比较简单,解析速度比较快。

  • Protocol Buffers 是google提供的一个开源序列化框架,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。同 XML 相比, Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。

以上每种协议都有其优点和适用场景,需要根据具体的需求和环境来选择合适的协议。

网络传输

提起网络传输大家脑海里肯定马上就能想到 TCP/IP四层模型、OSI 七层模型,那通常 RPC 会选择那一层作为传输协议呢?

在回答这个问题前,先来看下 RPC 需要网络传输实现什么样的功能。客户端的数据经过序列化后,就需要通过网络传输到服务端。网络传输层需要把前面说的函数 ID 和序列化后的参数字节流传给服务端,服务端处理完然后再把序列化后的调用结果传回客户端。

原则上只要能实现上面这个功能的都可以作为传输层来使用,具体协议没有限制。我们先来看下 TCP 协议,TCP 连接可以是按需连接,需要调用的时候就先建立连接,调用结束后就立马断掉,也可以是长连接,客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效。

由此可见 TCP 的性能确实很好,因此市面上大部分 RPC 框架都使用 TCP 协议,但也有少部分框架使用其他协议,比如 gRPC 用的是 HTTP2 来实现。

一次RPC的调用过程

忽略服务端向注册中心注册服务的流程,下面是客户端和服务端之间进行一次RPC调用的完整过程。

image-20231102193528606
  1. 客户端(Client)通过本地调用的方式调用服务(以接口方式调用);
  2. 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息进行组装序列化成能够进行网络传输的消息体(将消息体对象序列化为二进制流);
  3. 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端(通过sockets发送消息);
  4. 服务端存根(Server Stub)收到消息后进行反序列化操作,即解码(将二进制流反序列化为消息对象);
  5. 服务端存根(Server Stub)通过解码结果调用本地的服务进行相关处理;
  6. 服务端(Server)将处理结果返回给服务端存根;
  7. 服务端存根(Server Stub)序列化处理结果(将结果消息对象序列化为二进制流);
  8. 服务端存根(Server Stub)将序列化结果通过网络发送至客户端(通过sockets发送消息);
  9. 客户端存根(Server Stub)接收到消息,进行反序列化解码(将结果二进制流反序列化为消息对象);
  10. 客户端得到最终的结果。

这里面有一个词语:存根(Stub)。这里存根的作用我认为和Linux内核里面的库打桩机制有点类似。在Linux中,一个"桩"(stub)就是一个程序或函数的临时替代品,"桩"可以模拟出类似于真实的程序或函数的行为。所以,在RPC中,客户端存根和服务器存根的作用是隐藏RPC底层机制的复杂性,让开发者可以像调用本地函数一样调用远程函数。

实践

基于HTTP协议的RPC

服务端代码:

type Args struct {
	A, B int
}

type Compute int

func (c *Compute) Add(args *Args, reply *int) error {
	*reply = args.A + args.B
	return nil
}

func main() {
	compute := new(Compute)
	rpc.HandleHTTP() // 注册 HTTP 路由
    // 注册 RPC 服务
	if err := rpc.Register(compute); err != nil {
		log.Fatal("Register error:", err)
	}

	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal("Listen error:", err)
	}
	if err = http.Serve(listen, nil); err != nil {
		log.Fatal("Serve error:", err)
	}
}

rpc库对注册的方法有一定的限制,方法必须满足签名func (t *T) MethodName(argType T1, replyType *T2) error{}

  1. 方法名必需是可导出的。
  2. 方法接收两个参数,这两个参数都是可导出的,且第二个参数必需为指针类型。
  3. 方法必需返回一个error类型的参数。

客户端代码:

type Args struct {
	A, B int
}

func main() {
	client, err := rpc.DialHTTP("tcp", "localhost:8080")
	if err != nil {
		log.Fatal("dialing:", err)
	}

	args := &Args{3, 5}
	// 同步调用
	var reply1 int
	if err = client.Call("Compute.Add", args, &reply1); err != nil {
		log.Fatal("Compute error:", err)
	}
	fmt.Printf("同步调用的sum: %d\n", reply1)

	// 异步调用
	var reply2 int
	divCall := client.Go("Compute.Add", args, &reply2, nil)
	_ = <-divCall.Done // 接收调用结果
	fmt.Printf("异步调用的sum: %d\n", reply2)
}

运行结果如下:

PS D:\GolandProjects\RPC\client> go run .\client.go
同步调用的sum: 8
异步调用的sum: 8

基于TCP协议的RPC

服务端代码:

type Args struct {
	A, B int
}

type Compute int

func (c *Compute) Add(args *Args, reply *int) error {
	*reply = args.A + args.B
	return nil
}

func main() {
	compute := new(Compute)
	if err := rpc.Register(compute); err != nil {
		log.Fatal("Register error:", err)
	}

	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal("Listen error:", err)
	}
	rpc.Accept(listen)
}

客户端代码:

type Args struct {
	A, B int
}

func main() {
	client, err := rpc.Dial("tcp", "localhost:8080")
	if err != nil {
		log.Fatal("dialing:", err)
	}

	args := &Args{6, 8}
	// 同步调用
	var reply1 int
	if err = client.Call("Compute.Add", args, &reply1); err != nil {
		log.Fatal("Compute error:", err)
	}
	fmt.Printf("同步调用的sum: %d\n", reply1)

	// 异步调用
	var reply2 int
	divCall := client.Go("Compute.Add", args, &reply2, nil)
	_ = <-divCall.Done // 接收调用结果
	fmt.Printf("异步调用的sum: %d\n", reply2)
}

运行结果:

PS D:\GolandProjects\RPC\client> go run .\client.go
同步调用的sum: 14
异步调用的sum: 14

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

内涝积水监测仪怎么样?万宾科技城市内涝积水监测的作用

在城市建设发展过程中&#xff0c;道路基础设施的建设永远都占据着重要一席&#xff0c;因为人们出行一旦受阻便会影响城市进展&#xff0c;也会影响经济发展。在城市之中有隧道&#xff0c;下穿式立交桥等容易存积水的地方&#xff0c;一旦出现恶劣暴雨天气&#xff0c;这些地…

腾讯云CVM服务器操作系统镜像大全

腾讯云CVM服务器的公共镜像是由腾讯云官方提供的镜像&#xff0c;公共镜像包含基础操作系统和腾讯云提供的初始化组件&#xff0c;公共镜像分为Windows和Linux两大类操作系统&#xff0c;如TencentOS Server、Windows Server、OpenCloudOS、CentOS Stream、CentOS、Ubuntu、Deb…

chroot

1.chroot技术 在Linux系统中&#xff0c;系统默认的目录结构都是以/&#xff0c;即根(root)开始的。而在使用chroot之后&#xff0c;进程的系统目录结构将以指定的位置作为根(/)位置。chroot实际作用就是将进程描述符中struct fs_struct中的root的值设置为选定的目录。 在经过…

品牌如何长期占领小红书市场,小红书投放复盘怎么规划?

想要实现产品种草与品牌营销&#xff0c;达人投放成了很多品牌的选择。然而随着达人协助成本的水涨船高&#xff0c;提高达人投放结果&#xff0c;就变得迫在眉睫。今天我们将为大家分享下&#xff0c;品牌如何长期占领小红书市场&#xff0c;小红书投放复盘怎么规划&#xff1…

el-select 搜索无选项时 请求接口添加输入的值

el-select 搜索无选项时 请求接口添加输入的值 <template><div class"flex"><el-select class"w250" v-model"state.brand.id" placeholder"请选择" clearable filterable :filter-method"handleQu…

随时随地时时刻刻使用GPT类应用

疑问 很多人说GPT的广泛使用可能会使人们失业&#xff0c;会对一些互联网公司的存活造成挑战&#xff0c;那么这个说法是真的吗&#xff1f; 这个说法并不完全准确。虽然GPT等AI技术的广泛应用可能会对某些行业和职业产生影响&#xff0c;但并不意味着它会导致人们失业或互联网…

【Qt之事件过滤器】使用

介绍 事件过滤器是Qt中一种重要的机制&#xff0c;用于拦截并处理窗口和其他对象的事件。 它可以在不修改已有代码的情况下&#xff0c;动态地增加、删除一些处理事件的代码&#xff0c;并能够对特定对象的事件进行拦截和处理。 在Qt中&#xff0c;事件处理经过以下几个阶段&…

.NET Core 中插件式开发实现

在 .NET Framework 中&#xff0c;通过AppDomain实现动态加载和卸载程序集的效果&#xff1b;但是.NET Core 仅支持单个默认应用域&#xff0c;那么在.NET Core中如何实现【插件式】开发呢&#xff1f; 一、.NET Core 中 AssemblyLoadContext的使用 1、AssemblyLoadContext简…

CodeWhisperer 的安装及体验

文章作者&#xff1a;Pony CodeWhisperer 是亚马逊出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。类似 Cursor 和 Github Copilot 编码工具。 官网&#xff1a;https://aws.amazon.com/cn/codewhisperer/?trkcndc-detail 在编写代码时&#xff0c…

pg14-sql基础(二)-排序与统计

排序 SELECT employee_id, first_name, last_name, hire_date, salary FROM employees ORDER BY first_name; --按字母&#xff0c;默认升序 ORDER BY hire_date ASC; --升序 ORDER BY hire_date DESC; --降序SELECT employee_id, first_name, last_name, hire_date, salary F…

chinese_llama_aplaca训练和代码分析

训练细节 ymcui/Chinese-LLaMA-Alpaca Wiki GitHub中文LLaMA&Alpaca大语言模型本地CPU/GPU训练部署 (Chinese LLaMA & Alpaca LLMs) - 训练细节 ymcui/Chinese-LLaMA-Alpaca Wikihttps://github.com/ymcui/Chinese-LLaMA-Alpaca/wiki/%E8%AE%AD%E7%BB%83%E7%BB%86%E…

【实战Flask API项目指南】之一 概述

实战Flask API项目指南之 概述 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握Flask在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 小菜是一个Python编程爱好者&#xff0c;他目前…

华为OD机试 - 高效的任务规划 - 逻辑分析(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

挑战100天 AI In LeetCode Day02(1)

挑战100天 AI In LeetCode Day02&#xff08;1&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-32.1 题目2.2 题解 三、面试经典 150 题-33.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&#xff0c;面向程序…

Linux中的高级IO

文章目录 1.IO1.1基本介绍1.2基础io的低效性1.3如何提高IO效率1.4五种IO模型1.5非阻塞模式的设置 2.IO多路转接之Select2.1函数的基本了解2.2fd_set理解2.3完整例子代码&#xff08;会在代码中进行讲解&#xff09;2.4优缺点 3.多路转接之poll3.1poll函数的介绍3.2poll服务器3.…

4.网络之TCP

TCP协议(传输层) 文章目录 TCP协议(传输层)1. TCP报文格式2. TCP相关机制2.1 确认应答机制2.2 超时重传机制2.3 连接管理机制&#xff08;重点&#xff09;2.3.1 三次握手2.3.2 四次挥手 2.4 滑动窗口机制2.5 流量控制机制2.6 拥塞控制机制2.7 延迟应答机制2.8 捎带应答机制 3.…

Linux工具git版本控制器介绍

git介绍 ​ git就是一个版本控制器&#xff0c;是由Linux之父写的开源软件&#xff0c;功能就是保存每个版本的内容。将被管理的内容&#xff08;文本&#xff09;&#xff0c;按照变化来进行管理的软件&#xff0c;你需要哪一个变化的版本都可以找到。 git是一个软件&#x…

ATE新能源汽车充电桩自动负载测试系统

随着新能源汽车的普及&#xff0c;充电桩的需求也在不断增加&#xff0c;为了确保充电桩的性能和安全性&#xff0c;对其进行负载测试是非常重要的。ATE新能源汽车充电桩自动负载测试系统是一种专门用于检测充电桩性能的设备&#xff0c;它可以模拟各种实际使用场景&#xff0c…

打造高效运营底座,极智嘉一体化软件系统彰显科技威能

在仓储成本和物流需求日益增加的今天&#xff0c;创新且高效的物流机器人解决方案能够显著提升物流运营效率&#xff0c;降低物流成本&#xff0c;实现智能化、精益化、一体化的物流管理。全球仓储机器人引领者极智嘉(Geek)以「一套系统&#xff0c;天生全能」为准则&#xff0…

数据集:机器人理解世界的关键

原创 | 文 BFT机器人 传统的机器人和工业自动化解决方案已经颇有成效。在工厂中入驻自动化机器人可以快速地帮助工人们完成长时间重复劳动的任务。随着用工成本上涨、技能人才短缺、工作环境恶劣等问题的凸显&#xff0c;社会更迫切地需要采用自动化设备代替人工来完成该类操作…
最新文章