虾皮面试原题,尽管存在HTTP协议,仍然需要RPC协议的深入分析与比较
分享一位朋友在面试虾皮时遇到的关于RPC的基础问题。
从TCP协议谈起
假设我们是一名程序员,需要将一段数据从电脑A的进程发送到电脑B的进程,通常会在代码中使用socket进行编程。在这种情况下,我们的选择主要是TCP和UDP。TCP协议以其高可靠性而受到广泛使用,而UDP则是相对不太可靠的选项。除非你是像马化腾那样的高级程序员(早期的QQ大量使用UDP),否则一般情况下,大家都选择TCP来确保数据的可靠性。
例如,下面是基本的TCP socket创建代码:
fd = socket(AF_INET, SOCK_STREAM, 0);
这里的SOCK_STREAM
表示采用字节流传输数据,也就是使用TCP协议。
定义了socket后,我们便可以进行各种操作,例如使用bind()
来绑定IP和端口,使用connect()
发起连接。
在连接建立后,我们可以使用send()
方法发送数据,使用recv()
方法接收数据。
然而,单纯依靠一个原生的TCP连接是否足够呢?答案是:不够,这样做会出现一些问题。
关于纯裸TCP的潜在问题
我们经常听到八股文中提到,TCP协议有三个特点:面向连接、可靠性、以及基于字节流。
TCP的特点
这三点确实非常准确地总结了TCP协议的本质。每个特点都可以独立展开讨论,而今天我们主要关注的是基于字节流这一特性。
字节流可以看作是在一个双向通道中流动的二进制数据,即01串。在纯裸TCP中,发送和接收的这些01串之间并没有任何边界,这意味着你无法确定何处才是完整消息的结束。
01二进制字节流
正因为缺乏边界,当我们通过TCP发送**"夏洛"和"特烦恼"时,接收方收到的内容可能会被错误理解为"夏洛特烦恼"。此时,接收端无法分辨这是否是"夏洛"+"特烦恼"还是"夏洛特"+"烦恼"**。
消息对比
这就是所谓的粘包问题。我之前也写过一篇专门讨论该问题的文章。提到这个目的,是为了告诉大家,单纯依靠TCP是无法直接使用的,我们需要在其基础上添加一些自定义规则来区分消息的边界。
因此,我们会将每条要发送的数据进行包装,例如添加消息头,在消息头中明确标示出完整包的长度,以便接收方根据长度继续接收数据,并截取出我们真正想要传输的消息体。
消息边界长度标志
在提到的消息头中,还可以包含多种信息,例如消息体是否被压缩以及消息体的格式等。只要上下游的双方达成一致,互相认可即可,这就是所谓的协议。
每个使用TCP的项目可能会定义一套类似的协议解析标准,它们可能各有不同,但原理却是相似的。
因此,基于TCP协议,衍生出了众多协议,包括HTTP和RPC等。
HTTP与RPC的关联
RPC实际上是一种调用方式
我们回到网络分层图。
四层网络协议
TCP是传输层协议,而基于TCP构建的HTTP和各种RPC协议,实际上只是定义了不同消息格式的应用层协议。
HTTP(Hyper Text Transfer Protocol)协议是我们常用的协议,通常在浏览器中输入网址来访问网页时,就是在用HTTP协议。
HTTP调用
而RPC(Remote Procedure Call)指的是远程过程调用,它本身并不是一个具体的协议,而是一种调用方式。
例如,我们在调用一个本地方法时,通常会像下面这样:
res = localFunc(req)
如果这个方法不是本地的,而是一个远程服务器暴露的remoteFunc
方法,而我们仍然能够像调用本地方法一样去调用它,那么这无疑会减少我们对网络细节的关注,使得调用过程更加方便。
res = remoteFunc(req)
RPC可以像调用本地方法那样调用远端方法
基于这种思路,许多技术大咖们开发出了多种RPC协议,如知名的gRPC
和Thrift
。
值得注意的是,虽然大多数RPC协议底层使用TCP,但实际上并不一定非得使用TCP,使用UDP或HTTP同样可以实现类似的功能。
回到我们开始讨论的问题。
那么,既然有了RPC,为什么仍然需要HTTP呢?
TCP协议是在70年代诞生的,而HTTP协议是在90年代才开始流行。由于直接使用裸TCP会存在问题,想必这几十年间也有很多定制协议,而RPC就是在80年代产生的。
因此,问题应当是:既然有RPC,为什么还需要HTTP协议?
如今的网络软件,如某些管理工具和安全软件,作为客户端(Client),需要与服务器(Server)建立连接以收发消息,通常会使用应用层协议。在这种Client/Server (C/S)架构下,它们可以使用自家设计的RPC协议,因为只需连接自家公司的服务器即可。
但有一种软件例外,那就是浏览器(Browser),无论是Chrome还是IE,它们不仅需要访问自家公司的服务器(Server),还需要访问其他公司的网站服务器,因此必须采用统一的标准,否则无法进行交互。HTTP便是在那个时代为**Browser/Server (B/S)**架构制定的统一协议。
简言之,早期HTTP主要用于B/S架构,而RPC则多用于C/S架构。但随着技术的发展,两者之间的界限逐渐模糊。许多软件现在支持多端,例如某度云盘,既需要支持网页版,也需要支持手机端和PC端,若通信协议统一使用HTTP,服务器便可只需维护一套协议。而RPC则逐渐被用于公司内部集群中的各个微服务之间的通信。
那么,有人可能会问:都用HTTP还要RPC干嘛?
这又回到了文章开头的问题。这就需要从两者之间的区别说起。
HTTP与RPC的区别
我们来看看RPC和HTTP几个明显的区别。
服务发现
首先,要向某个服务器发起请求,需要先建立连接,而建立连接的前提是得知道IP地址和端口。找到服务对应的IP和端口的过程,就是服务发现。
在HTTP中,只需知道服务的域名,就可以通过DNS服务解析出其背后的IP地址,默认使用80端口。
而RPC的情况有所不同,通常会有专门的中间服务来保存服务名和IP信息,比如Consul、Etcd、Nacos、ZooKeeper,甚至是Redis。要访问某个服务,需向这些中间服务请求获取IP和端口信息。由于DNS也是服务发现的一种方式,因此也有基于DNS进行服务发现的组件,比如CoreDNS。
可以看出,在服务发现这一块,二者有一定区别,但并不能简单地分出高低。
底层连接形式
以主流的HTTP1.1协议为例,默认在建立底层TCP连接之后,会保持该连接(keep alive),随后的请求和响应都会复用这条连接。
而RPC协议也通过建立TCP长连接进行数据交互,但与HTTP不同的是,RPC协议还会建立一个连接池,在高请求量时,维护多条连接在池中,发数据时从池中取出一条连接,使用后再放回去,以便下次复用,可以说相当环保。
连接池
由于连接池有助于提升网络请求性能,许多编程语言的网络库里也会给HTTP添加连接池。例如,Go语言就实现了这一点。
可以发现,在这一点上,两者并没有太大区别,因此这也不是关键所在。
传输内容
基于TCP传输的消息,无非是消息头Header和消息体Body。
Header用于标记一些特殊信息,其中最重要的是消息体长度。
Body则是我们真正需要传输的内容,而在计算机中,这些内容只能是二进制的01串,因为计算机只理解这种形式。因此,TCP传输字符串和数字没有问题,因为字符串可以编码为二进制,而数字本身也可以直接转换为二进制。但对于结构体数据,我们需要将其转为二进制01串,这方面现在有很多现成的解决方案,比如JSON和Protocol Buffers (Protobuf)。
将结构体转换为二进制数组的过程称为序列化,反过来再将二进制数组复原为结构体的过程叫反序列化。
序列化与反序列化
对于主流的HTTP1.1,尽管它现在被称为超文本协议,支持音频和视频内容,但HTTP的初衷是为了网页文本的展示,因此它传输的内容以字符串为主,Header和Body的内容也都是如此。在Body部分,它通常使用JSON格式来对结构体数据进行序列化。
我们可以截取一段HTTP报文来直观地观察其内容。
HTTP报文
从中可以看到,内容中存在较多冗余,显得相当繁琐。例如,Header中的一些信息,若我们约定好头部第几位是Content-Type
,便无需每次都重复传递Content-Type
这一字段,Body中的JSON结构同样存在类似的冗余。
而RPC由于定制化程度更高,可以采用体积更小的Protobuf或其他序列化协议来存储结构体数据,同时不需要考虑如HTTP那样的多种浏览器行为,比如302重定向等。这也是在公司内部微服务中,很多技术团队选择抛弃HTTP,转而使用RPC的主要原因。
HTTP原理
RPC原理
当然,前面提到的HTTP是指目前主流的HTTP1.1,而HTTP2在此基础上进行了许多改进,因此其性能可能甚至超过一些RPC协议,甚至连gRPC底层也直接使用HTTP2。
那么问题又来了。
为什么在有了HTTP2后,还需要RPC协议?
这一点主要是由于HTTP2是在2015年发布的,而众多公司内部的RPC协议在那之前便已经运行多年。因此,出于历史原因,通常也没有必要进行更换。
总结
- 纯裸TCP能够收发数据,但它是一个无边界的数据流,上层需定义消息格式以确定消息的边界。因此,HTTP和各种RPC协议是在TCP之上定义的应用层协议。
- RPC本质上并不是一个协议,而是一种调用方式,而像gRPC和Thrift这样的具体实现才算是协议,它们实现了RPC调用的协议,旨在让程序员能像调用本地方法一样调用远程服务方法。RPC存在多种实现方式,并不一定需要基于TCP协议。
- 从发展历史来看,HTTP主要用于B/S架构,而RPC更多用于C/S架构。然而,现在两者的界限正在逐渐模糊,许多软件同时支持多端,因此对外通常使用HTTP协议,而内部微服务之间则采用RPC协议进行通讯。
- RPC实际上比HTTP出现得更早,且在大多数情况下,其性能优于当前主流的HTTP1.1,因此许多公司内部仍在使用RPC。
- HTTP2在HTTP1.1的基础上进行了优化,其性能可能优于许多RPC协议,但由于其发布时间较晚,因此不可能完全取代RPC。