[计算机网络]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 协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。
由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给 HTTP 加个连接池,比如 Go 就是这么干的。
可以看出这一块两者也没太大区别,所以也不是关键。
传输的内容
基于 TCP 传输的消息,说到底,无非都是消息头 Header 和消息体 Body。
Header 是用于标记一些特殊信息,其中最重要的是消息体长度。
Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 Json,Protobuf。
这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化。
对于主流的 HTTP/1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 Json 来序列化结构体数据。
我们可以随便截个图直观看下。
可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 Content-Type,就不需要每次都真的把"Content-Type"这个字段都传过来,类似的情况其实在 body 的 Json 结构里也特别明显。
而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。
当然上面说的 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
消息,其中包含 name
、id
和 email
字段。
下面是对这个 .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到15)在序列化时占用更少的字节,提高效率。
总体来说,标识号是 Protocol Buffers 高效和灵活的关键。
在序列化和反序列化时,识别字段的意思是:
序列化
- 序列化是将数据结构或对象转换为紧凑的二进制格式。
- 在这个过程中,每个字段用其标识号表示,而不是用字段名称。这样可以减少数据大小。
- 例如,字段
name
的值被编码为与标识号1
相关联的数据。
反序列化
- 反序列化是将二进制数据转换回数据结构或对象。
- 读取到标识号后,程序能知道应该将数据填充到哪个字段。
- 通过标识号,程序可以正确地重建原始数据结构,即使字段顺序不同或存在未知字段。
这种机制使得 Protocol Buffers 高效且灵活,支持版本演化和兼容性。
Protobuf(Protocol Buffers)比JSON解析速度更快,主要原因有以下几个方面:
-
二进制格式:
Protobuf是一种二进制格式,而JSON是一种文本格式。二进制格式通常比文本格式更紧凑,因为它不需要存储数据类型、字段名等元数据。这意味着Protobuf消息在传输和解析时的数据量更小,因而速度更快。 -
预先定义的结构:
在使用Protobuf时,数据结构是通过.proto
文件预先定义的。这种预定义的结构使得序列化和反序列化过程更加高效,因为解析器可以直接按照定义解析数据,而不需要像JSON那样动态地推断数据类型。 -
更高效的解析算法:
Protobuf的解析器通常使用了更高效的算法,专门优化了序列化和反序列化过程。由于Protobuf数据的每个字段都有一个唯一的标识符,解析器可以直接跳到相关位置,从而减少了不必要的处理步骤。 -
压缩性:
Protobuf消息在序列化时通常比JSON更小,因此需要处理的数据量更少,这也间接提升了解析速度。更小的消息意味着更少的I/O开销和内存占用。 -
无需解析字段名:
JSON需要在解析时处理字段名,这可能会导致额外的字符串处理开销。相比之下,Protobuf使用的是字段标签,解析时不需要处理字段名,从而减少了解析时间。
综上所述,Protobuf通过二进制编码、预定义的数据结构、更高效的解析算法以及更少的元数据开销,实现了比JSON更快的解析速度。
为什么二进制解析比文本快
二进制格式比文本格式解析更快,主要原因在于二进制格式更接近计算机底层的数据表示方式,解析过程中可以省去许多中间步骤和不必要的处理。以下是主要原因:
-
无需解析和转换:
文本格式(如JSON、XML)在解析时需要将文本数据转换为计算机可以理解的二进制数据。例如,将字符串"123"转换为整数123,这个过程涉及到字符识别和类型转换。而二进制格式直接以机器可读的形式存储数据,解析时可以直接读取,不需要额外的转换步骤。 -
固定大小和结构:
二进制格式通常具有固定的大小和结构。例如,整数、浮点数等数据类型在二进制格式中占用固定的字节数。解析器可以根据预定义的格式直接定位和提取数据,而不需要遍历和解析复杂的文本结构。这种固定结构使得解析过程更加简单和高效。 -
更少的开销:
文本格式通常包含大量的元数据,如字段名、括号、引号等,这些都会增加解析的复杂性和时间消耗。相比之下,二进制格式没有这些冗余信息,数据紧凑,解析时的内存占用和CPU负载都更低。 -
无需字符编码处理:
文本格式需要处理字符编码问题(如UTF-8、UTF-16等),尤其在涉及多语言支持时,解析器需要识别和转换不同的字符编码。而二进制格式通常使用固定的字节表示,无需关心字符编码,因此解析速度更快。 -
直接映射到内存结构:
二进制数据通常可以直接映射到内存中的数据结构,例如C语言的结构体(struct),这种直接映射避免了复杂的解析逻辑。文本格式则需要逐字符解析,并动态地生成对应的数据结构,这个过程相对慢且复杂。
综上所述,二进制格式由于其紧凑、高效和与机器数据表示形式的直接对应,解析时省去了大量不必要的步骤和开销,因此解析速度比文本格式更快。