RPC核心概念理解

1 RPC概念

  1. RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。
  2. 对应rpc的是本地过程调用,函数调用是最常见的本地过程调用。
  3. 将本地过程调用变成远程过程调用会面临各种问题。

1.1 本地过程调用与远程过程调用

image

函数调用过程:

  1. 将1和2压入add函数的栈;
  2. 进入add函数,从栈中取出1和2分别赋值给a和b;
  3. 执行a + b将结果赋值给局部的total并压栈;
  4. 将栈中的值取出来赋值给全局的total。

原本的本地函数放到另一个服务器上去运行即远程过程调用,会引入很多新问题:

  1. Call的id映射:我们怎么告诉远程机器我们要调用add,而不是sub或者Foo呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用add,编译器就自动帮我们调用它相应的函数指针。

    但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数<-->Call lD} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call lD,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

  2. 序列化和反序列化(编码和解码):客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python),这时候就需要客户端把参数先转成一个字节流(json\xml\protobuf\msgpack),传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。

  3. 网络传输:远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。

1.2 数据编码协议和传输协议

1.2.1 数据编码协议

编码协议:在客户端,远程过程调用数据传输时需要把参数先转成一个字节流(json\xml\protobuf\msgpack),传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化反序列化(也称为编码和解码)。同理,从服务端返回的值也需要序列化反序列化的过程。

package main

import (
   "fmt"
)

type Company struct {
   Name    string
   Address string
}

type Employee struct {
   Name    string
   company Company
}

type PrintResult struct {
   Info string
   Err  error
}

//用于远程调用的函数
func RpcPrintln(employee Employee) PrintResult {
   /*
         客户端的工作:
         1.建立连接,基于tcp、http建立连接
         2.将employee对象序列化成json字符串 - 序列化
         3.发送json字符串 - 网络传输只能使用二进制数据,所以调用成功后实际上接收到的是一个二进制数据
         4.等待服务器发送结果
         5.将服务器返回的数据即二进制数据解析成PrintResult对象,此举是为了方便本地直接能使用对象,二进制数据是不能用的 - 反序列化

         服务端的工作:
         1.监听网络端口 80
         2.读取数据 - 二进制的json数据
         3.对数据进行反序列化成Employee对象 - 反序列化
         4.开始处理业务逻辑
         5.将处理结果的PrintResult对象序列化成json二进制数据 - 序列化
         6.将数据返回
      序列化和反序列化的编码协议是可以选择的,不一定要用json,xml、protobuf、msgpack也可以选择
   */
}

func main() {
   /*
      情景示例:有一个电商系统,有一个减库存的函数reduce,但是库存服务是一个独立的系统,需要远程调用reduce,
      那如何远程调用呢?一定会牵扯到网络,所以可以做成一个web服务(框架:gin、beego、自带的net/httpserver)

      1.函数的调用参数如何传递?
      最常用的是json(json是一种数据格式的协议或者说叫编码协议,但json并不是一个高性能的编码协议)
      除此之外还有其他的协议,比如:xml、protobuf、msgpack
      客户端把参数通过编码协议先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
      现在网络调用有两个端,即客户端和服务端,客户端为main函数,客户端负责将数据传输到gin,gin是服务端,服务端负责解析数据。
   */

   //将这个打印的工作放在另一台服务器上
   //此时需要将本地内存对象struct传到远程的服务器上,当然不能直接传,可行的方式是将struct序列化成json对象(即二进制对象)并进行传输
   fmt.Println(Employee{
      Name: "lee",
      company: Company{
         Name:    "ByteDance",
         Address: "beijing",
      },
   })
   //远程的服务需要将二进制对象反解成struct对象
}

1.2.2 数据传输协议

传输协议:数据在网络中传输需要遵循数据的传输协议,协议有两种,即tcp协议和http协议,常用的是http协议,有两个版本:http1.x和http2.0。

将服务器A的数据传输到服务器B需要在两服务器之间建立连接,可以选择tcp协议或者http协议,http协议是基于tcp协议建立的,其本身也是tcp连接,http协议有一个问题:一次性,一旦对方返回了结果连接会断开,http2.0可以保持长连接;在建立连接时也可以基于tcp协议封装一层应用协议。

posted @ 2022-09-26 15:56  雪碧锅仔饭  阅读(72)  评论(0)    收藏  举报