Loading

[计算机网络]RPC

HTTP 和 RPC 有什么区别,我们来看看 RPC 和 HTTP 区别比较明显的几个点。

服务发现

首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道 IP 地址和端口。这个找到服务对应的 IP 端口的过程,其实就是服务发现。

在 HTTP 中,你知道服务的域名,就可以通过 DNS 服务去解析得到它背后的 IP 地址,默认 80 端口。

而 RPC 的话,就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如 Consul 或者 Etcd,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如CoreDNS。

可以看出服务发现这一块,两者是有些区别,但不太能分高低。

底层连接形式

以主流的 HTTP/1.1 协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(Keep Alive),之后的请求和响应都会复用这条连接。

而 RPC 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。

image

由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给 HTTP 加个连接池,比如 Go 就是这么干的。

可以看出这一块两者也没太大区别,所以也不是关键。

传输的内容

基于 TCP 传输的消息,说到底,无非都是消息头 Header 和消息体 Body。

Header 是用于标记一些特殊信息,其中最重要的是消息体长度。

Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 Json,Protobuf。

这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化。

image

对于主流的 HTTP/1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 Json 来序列化结构体数据。

我们可以随便截个图直观看下。

image

可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 Content-Type,就不需要每次都真的把"Content-Type"这个字段都传过来,类似的情况其实在 body 的 Json 结构里也特别明显。

而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。

image

image

当然上面说的 HTTP,其实特指的是现在主流使用的 HTTP/1.1,HTTP/2 在前者的基础上做了很多改进,所以性能可能比很多 RPC 协议还要好,甚至连 gRPC 底层都直接用的 HTTP/2。

那么问题又来了,为什么既然有了 HTTP/2,还要有 RPC 协议?

这个是由于 HTTP/2 是 2015 年出来的。那时候很多公司内部的 RPC 协议都已经跑了好些年了,基于历史原因,一般也没必要去换了。

总结

纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。

RPC 本质上不算是协议,而是一种调用方式,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,不一定非得基于 TCP 协议。

从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。

RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP/1.1 性能要更好,所以大部分公司内部都还在使用 RPC。
HTTP/2.0 在 HTTP/1.1 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。

为什么probuf比json解析快

我认为主要有两个原因:

  • http需要传输的数据量更大:
    • http/1.1 header需要传输很多的键值对,其中有一些是为了浏览器的行为服务的,在rpc中是不需要的
    • http/1.1 header json需要传输很多的键和括号,传输的数据量更大,在RPC中我们可以约定从第几位到第几位表示什么信息,从而减少键的传输
  • http使用了json进行序列化,这种方式在发送数据的时候首先要把结构体转换成json文本,再转换成01数据,接收数据的时候需要先解析文本,再从文本中获取结构体数据。而rpc直接把结构体转换成01数据,再从01数据中获取结构体,这种方式解析起来更快。

给出一个protobuf的例子

下面是一个简单的 .proto 文件示例:

syntax = "proto3";

package example;

// 定义一个简单的消息类型
message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

这个示例定义了一个 Person 消息,其中包含 nameidemail 字段。

下面是对这个 .proto 文件的详细解释:

syntax = "proto3";
  • syntax = "proto3";: 指定使用 Protocol Buffers 的第三版语法。这决定了文件的解析方式和支持的特性。
package example;
  • package example;: 定义了这个协议缓冲区文件的包名称。它帮助避免命名冲突,类似于编程语言中的命名空间。
message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}
  • message Person { ... }: 定义了一个 Person 消息类型。消息是 Protocol Buffers 中的数据结构,相当于类或结构体。

    • string name = 1;: 定义了一个 name 字段,类型为字符串,标识号为 1。标识号用于在序列化时唯一标识字段。

    • int32 id = 2;: 定义了一个 id 字段,类型为 int32,标识号为 2

    • string email = 3;: 定义了一个 email 字段,类型为字符串,标识号为 3

使用场景

  • 数据交换格式: 定义了数据传输时的结构,可以用于客户端和服务器之间的数据交换。

  • 代码生成: 可以通过 Protocol Buffers 的编译器生成多种语言的代码,以便在不同的编程语言中使用相同的数据结构。

优势

  • 高效的序列化: Protocol Buffers 将数据序列化为紧凑的二进制格式,节省带宽和存储空间。

  • 向后兼容: 可以在不破坏现有数据的情况下添加新字段。旧版本的应用程序将忽略未知字段。

这样的 .proto 文件可以被编译器用来生成特定语言的代码,实现数据的序列化和反序列化。

标识号在 Protocol Buffers 中有以下几个重要作用:

  1. 唯一标识字段: 每个字段都有一个唯一的标识号,用于在序列化和反序列化时识别字段。它在传输数据时代替字段名称,节省空间。

  2. 数据兼容性: 允许添加或删除字段时保持向后兼容。旧版本客户端会忽略未知标识号的字段。

  3. 优化性能: 较小的标识号(1到15)在序列化时占用更少的字节,提高效率。

总体来说,标识号是 Protocol Buffers 高效和灵活的关键。

在序列化和反序列化时,识别字段的意思是:

序列化

  • 序列化是将数据结构或对象转换为紧凑的二进制格式。
  • 在这个过程中,每个字段用其标识号表示,而不是用字段名称。这样可以减少数据大小。
  • 例如,字段 name 的值被编码为与标识号 1 相关联的数据。

反序列化

  • 反序列化是将二进制数据转换回数据结构或对象。
  • 读取到标识号后,程序能知道应该将数据填充到哪个字段。
  • 通过标识号,程序可以正确地重建原始数据结构,即使字段顺序不同或存在未知字段。

这种机制使得 Protocol Buffers 高效且灵活,支持版本演化和兼容性。

Protobuf(Protocol Buffers)比JSON解析速度更快,主要原因有以下几个方面:

  1. 二进制格式
    Protobuf是一种二进制格式,而JSON是一种文本格式。二进制格式通常比文本格式更紧凑,因为它不需要存储数据类型、字段名等元数据。这意味着Protobuf消息在传输和解析时的数据量更小,因而速度更快。

  2. 预先定义的结构
    在使用Protobuf时,数据结构是通过.proto文件预先定义的。这种预定义的结构使得序列化和反序列化过程更加高效,因为解析器可以直接按照定义解析数据,而不需要像JSON那样动态地推断数据类型。

  3. 更高效的解析算法
    Protobuf的解析器通常使用了更高效的算法,专门优化了序列化和反序列化过程。由于Protobuf数据的每个字段都有一个唯一的标识符,解析器可以直接跳到相关位置,从而减少了不必要的处理步骤。

  4. 压缩性
    Protobuf消息在序列化时通常比JSON更小,因此需要处理的数据量更少,这也间接提升了解析速度。更小的消息意味着更少的I/O开销和内存占用。

  5. 无需解析字段名
    JSON需要在解析时处理字段名,这可能会导致额外的字符串处理开销。相比之下,Protobuf使用的是字段标签,解析时不需要处理字段名,从而减少了解析时间。

综上所述,Protobuf通过二进制编码、预定义的数据结构、更高效的解析算法以及更少的元数据开销,实现了比JSON更快的解析速度。

为什么二进制解析比文本快

二进制格式比文本格式解析更快,主要原因在于二进制格式更接近计算机底层的数据表示方式,解析过程中可以省去许多中间步骤和不必要的处理。以下是主要原因:

  1. 无需解析和转换
    文本格式(如JSON、XML)在解析时需要将文本数据转换为计算机可以理解的二进制数据。例如,将字符串"123"转换为整数123,这个过程涉及到字符识别和类型转换。而二进制格式直接以机器可读的形式存储数据,解析时可以直接读取,不需要额外的转换步骤。

  2. 固定大小和结构
    二进制格式通常具有固定的大小和结构。例如,整数、浮点数等数据类型在二进制格式中占用固定的字节数。解析器可以根据预定义的格式直接定位和提取数据,而不需要遍历和解析复杂的文本结构。这种固定结构使得解析过程更加简单和高效。

  3. 更少的开销
    文本格式通常包含大量的元数据,如字段名、括号、引号等,这些都会增加解析的复杂性和时间消耗。相比之下,二进制格式没有这些冗余信息,数据紧凑,解析时的内存占用和CPU负载都更低。

  4. 无需字符编码处理
    文本格式需要处理字符编码问题(如UTF-8、UTF-16等),尤其在涉及多语言支持时,解析器需要识别和转换不同的字符编码。而二进制格式通常使用固定的字节表示,无需关心字符编码,因此解析速度更快。

  5. 直接映射到内存结构
    二进制数据通常可以直接映射到内存中的数据结构,例如C语言的结构体(struct),这种直接映射避免了复杂的解析逻辑。文本格式则需要逐字符解析,并动态地生成对应的数据结构,这个过程相对慢且复杂。

综上所述,二进制格式由于其紧凑、高效和与机器数据表示形式的直接对应,解析时省去了大量不必要的步骤和开销,因此解析速度比文本格式更快。

posted @ 2024-07-05 21:50  Duancf  阅读(6)  评论(0编辑  收藏  举报