本文介绍: 其实我早就很想写这篇文章了,RPC是一切现代计算机应用中非常重要的思想。也是微服务分布式的总体设计思想。只能说是非常中要,远的不说,就说进的这个是面试必问的。不管用的上不,但是就是非常重要。

其实我早就很想写这篇文章了,RPC是一切现代计算机应用中非常重要的思想。也是微服务分布式的总体设计思想。只能说是非常中要,远的不说,就说进的这个是面试必问的。不管用的上不,但是就是非常重要。

RPC的原理

RPC(Remote Procedure Call Protocol远程过程调用协议一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在远程计算机上的某个对象,就像调用本地应用程序中的对象一样。

一种通过网络远程计算机程序请求服务,而不需要了解底层网络技术协议

这里我做一个解释:是不是有人会将这个与TCP等传输协议放在一起比较,而我想说这么比较是对的。这里献上一篇文章tcp、http、rpc和grpc总结,但是要注意,这其中的区别

不过这里我做一个强调,这篇文章必须将这些协议弄清楚了,才能看,他不是雪中送碳,而是锦上添花。

RPC是一种服务器客户端Client/Server模式,经典实现是一个通过发送请求-接受回应进行信息交互系统

首先与RPC远程调用相对应的是本地调用。

本地调用

先看一个go文件

package main

import "fmt"

func add(x, y int)int{
	return x + y
}

func main(){
	// 调用本地函数add
	a := 10
	b := 20
	ret := add(a, x)
	fmt.Println(ret)
}

程序本地调用add函数执行流程可以理解为以下四个步骤

  1. 变量 a b 的值分别压入堆栈上
  2. 执行 add 函数,从堆栈中获取 ab 的值,并将它们分配x y
  3. 计算 x + y 的值并将其保存到堆栈中
  4. 退出 add 函数并将x + y的值赋给 ret

在这里插入图片描述

定义add函数代码和调用add函数代码共享同一个内存空间,所以调用能够正常执行

但是我们无法直接在另一个程序——中调用add函数,因为它们是两个程序——内存空间是相互隔离的。(两个进程共享内存

(两个程序可能在一个主机上,也可能在不同的主机上)

在这里插入图片描述

所以RPC就诞生了

RPC就是为了解决类似远程、跨内存空间、的函数或者方法调用的

RPC调用

为什么要用RPC

如果我们开发简单的单一应用逻辑简单用户不多、流量不大,那我们用不着。

我们的系统访问量增大、业务增多时,我们发现一台单机运行此系统已经无法承受。此时,我们可以业务拆分成几个互不关联的应用,分别部署在各自机器上,以划清逻辑并减小压力。

此时,我们也可以需要RPC,因为应用之间是互不关联的。

当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。

此时,可以公共业务逻辑抽离出来,将之组成独立服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整业务功能

所以此时,我们急需一种高效的应用程序之间的通讯手段来完成这种需求.

描述的场景也是服务化 、微服务分布式系统架构的基础场景

实现RPC就需要解决以下三个问题

  1. 如何确定要执行的函数?

  2. 如何表达参数

  3. 如何进行网络传输

以往实现服务调用的时候,我们会采用RESTful API方式,被调用方会对外提供一个HTTP接口,调用方按要求发起HTTP请求接收API接口返回响应数据

server服务

// server/main.go

package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
)

type addParam struct {
	X int `json:"x"`
	Y int `json:"y"`
}

type addResult struct {
	Code int `json:"code"`
	Data int `json:"data"`
}

func add(x, y int) int {
	return x + y
}

func addHandler(w http.ResponseWriter, r *http.Request) {
	// 解析参数
	b, _ := ioutil.ReadAll(r.Body)
	var param addParam
	json.Unmarshal(b, &param)
	// 业务逻辑
	ret := add(param.X, param.Y)
	// 返回响应
	respBytes , _ := json.Marshal(addResult{Code: 0, Data: ret})
	w.Write(respBytes)
}

func main() {
	http.HandleFunc("/add", addHandler)
	log.Fatal(http.ListenAndServe(":9090", nil))
}

客户端请求上述HTTP服务

// client/main.go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type addParam struct {
	X int `json:"x"`
	Y int `json:"y"`
}

type addResult struct {
	Code int `json:"code"`
	Data int `json:"data"`
}

func main() {
	// 通过HTTP请求调用其他服务器上的add服务
	url := "http://127.0.0.1:9090/add"
	param := addParam{
		X: 10,
		Y: 20,
	}
	paramBytes, _ := json.Marshal(param)
	resp, _ := http.Post(url, "application/json", bytes.NewReader(paramBytes))
	defer resp.Body.Close()

	respBytes, _ := ioutil.ReadAll(resp.Body)
	var respData addResult
	json.Unmarshal(respBytes, &respData)
	fmt.Println(respData.Data) // 30
}

这中交互模式,应该是非常常见了。

这种模式是我们目前比较常见的跨服务或跨语言之间基于RESTful API的服务调用模式。

既然使用API调用也能实现类似远程调用的目的,为什么还要用RPC呢?

使用 RPC 的目的是让我们调用远程方法像调用本地方法一样无差别

并且基于RESTful API通常是基于HTTP协议传输数据采用JSON文本协议,相较于RPC 直接使用TCP协议传输数据采用二进制协议来说,RPC通常相比RESTful API性能更好。(在我的rabbitMQ文章中有体现)

RESTful API用于前后端之间的数据传输,而目前微服务架构各个微服务之间多采用RPC调用

使用场景

  1. 服务化/微服务
  2. 分布式系统架构
  3. 服务可重用
  4. 系统间交互调用

总结一下:

  1. 内部子系统较多
  2. 接口访问量巨大
  3. 接口非常多

RPC原理

在这里插入图片描述

  1. 服务调用方(client以本地调用方式调用服务
  2. client stub收到调用后负责将方法、参数等组装成能够进行网络传输消息体,
  3. client stub找到服务地址,并将消息发送服务端
  4. server端接收到消息
  5. server stub收到消息后进行解码
  6. server stub根据解码结果调用本地的服务
  7. 本地服务执行并将结果返回server stub
  8. server stub返回结果打包成能够进行网络传输的消息
  9. 按地址将消息发送至调用方
  10. client 端接收到消息
  11. client stub收到消息并进行解码
  12. 调用方得到最终结果

使用RPC框架目标是只需要关心第1步第12步,中间的其他步骤统统封装起来,让使用者无需关心。

例如社区中各式RPC框架grpcthrift等)就是为了让RPC调用更方便。

RPC的目标就是要2~8这些步骤封装起来,让用户对这些细节透明

如何做到透明化(封装)远程服务调用

怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?

部分有点像我用java实现的MQ一样。

就是定义一个结构体。

如果我们需要从A调用B的方法,那么意味着B需要A传来的参数和方法名等,然后通过反射解析

所以我们就可以这么做。

看到这里的人相信对如何设置数据结构已经了熟与心了,所以就不写代码了。(用web实现数据结构的肯定没少做这个事)

如果还是知道。可以看我的这篇文章,并找到创建请求响应协议参数一节,当然看完也可。

消息进行编码解码

客户端请求消息结构一般需要包括以下内容

服务端返回的消息结构一般包括以下内容:

一旦确定了消息的数据结构后,下一步就是要考虑序列化与反序列化了。

序列

什么序列化?

什么是反序列化?

为什么需要序列化?

为什么需要反序列化?

现如今序列化的方案越来越多,每种序列化方案都有优点和缺点,它们在设计之初有自己独特的应用场景,那到底选择哪种呢?

从RPC的角度上看,主要看三点:

目前互联网公司广泛使用ProtobufThriftAvro等成熟的序列化解决方案搭建RPC框架,这些都是久经考验的解决方案

这里就不介绍了。

如果想要知道如何创建搭建一个框架的,个人建议可以去这里极客兔兔的文章

下一章介绍如何使用go中的RPC的包

原文地址:https://blog.csdn.net/Cheer_RIO/article/details/134734444

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25982.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注