目录
问题二:为啥传统HTTP有了RPC的需求?为什么有了HTTP,还需要RPC?
一、HTTP
对于HTTP协议的详细介绍,可以移步到我的另一篇博客HTTP协议简单介绍-CSDN博客
HTTP最早版本在1989年提出,经过多年发展,到1996年发布的HTTP/1.1版本一直沿用至今,而在2015年也出现了HTTP/2.0。
二、RPC
介绍
RPC,全称Remote Procedure Call(远程过程调用),是一种允许程序调用另一个地址空间(通常是在网络中的另一台计算机上)的程序的协议。它使得程序能够像调用本地函数一样调用远程函数,而无需显式编码这些远程交互的细节。
它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。通俗来说,客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样,即允许像调用本地服务一样调用远程服务。
RPC框架的目的就是让远程服务调用更简单、透明,由RPC框架负责屏蔽底层的序列化、传输方式和通信细节,开发者在使用时只需要了解谁在什么位置提供了什么样的远程服务接口即可,并不需要关心底层通信细节和调用过程。
RPC的概念可以追溯到1970年代,但真正流行起来是1980年代中后期,随着分布式计算的兴起。1984年发布的ONC RPC和1986年的DCE RPC是最早的RPC实现。
(1)基本概念
服务提供者(Server):对外提供后台服务,将自己的服务信息,注册到注册中心。
注册中心(Registry):用于服务端注册远程服务以及客户端发现服务。目前主要的注册中心可以借由 zookeeper,eureka,consul,etcd 等开源框架实现。比如:阿里的Dubbo就是采用zookeeper实现注册中心。
服务消费者(Client):从注册中心获取远程服务的注册信息,然后进行远程过程调用。
接口定义语言IDL(Interface Definition Language):用于定义客户端和服务器之间的接口,描述可调用的过程、参数和返回值等信息。
调用过程:在 RPC 中,客户端程序通过调用远程服务器上的过程(函数)来执行某个任务。这些调用过程的执行看起来像是本地过程的调用。
通信协议和传输方式:通常使用类似于 HTTP、TCP 或 UDP 的协议进行通信。RPC可以基于不同的通信协议和传输方式,如TCP/IP、HTTP、消息队列等。RPC 在客户端和服务器之间传输数据,这包括调用参数和返回值。序列化和反序列化技术用于在网络上传输数据。选择合适的协议和传输方式取决于应用的需求和环境。
序列化与反序列化:在请求和响应的传输过程中,数据需要被转换为字节流进行传输,这个过程称为序列化。接收方将接收到的字节流转换回原始数据,这个过程称为反序列化。
(2)在一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件。
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端存根、网络传输模块、服务端存根、服务端等。
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
工作原理
RPC的工作原理大致如下,
- 调用请求:客户端程序调用一个函数,但该函数实际上是位于远程服务器上。这个调用请求通常包括函数名和传递的参数。
- 序列化:调用请求通过RPC框架被序列化为一个标准格式的数据包。这个过程被称为“序列化”或“编码”。
- 网络传输:序列化后的数据包通过网络发送到远程服务器。
- 反序列化:服务器接收到数据包后,RPC框架将其反序列化(解码)为函数名和参数,并调用对应的远程函数。
- 执行函数:服务器执行被调用的函数,将结果返回给客户端。
- 结果返回:返回的数据通过同样的方式序列化、传输,并在客户端进行反序列化,最终作为函数调用的返回值被处理。
这个过程对于开发者来说是透明的,RPC框架隐藏了底层的网络通信细节,使得远程调用看起来像是本地调用。
具体来说,一次 RPC 调用流程为:
- 服务消费者(Client 客户端)通过本地调用的方式调用服务。
- 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
- 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
- 服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。
- 服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理。
- 服务端Server本地服务业务处理。
- 处理结果返回给服务端存根(Server Stub)。
- 服务端存根(Server Stub)序列化结果。
- 服务端存根(Server Stub)将结果通过网络发送至消费方。
- 客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。
- 服务消费方得到最终结果。
RPC框架的目标是将上面2-10步完好的封装起来,也就是把调用、编码/解码的过程给封装起来,让用户感觉上像调用本地服务一样的调用远程服务。
核心功能
RPC 的核心功能主要由 5 个模块组成。如果想要自己实现一个 RPC,最简单的方式要实现三个技术点,分别是:
- 服务寻址
- 数据流的序列化和反序列化
- 网络传输
如何服务寻址
在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。因此,在远程调用中,客户端和服务端需要分别维护一个【ID->函数】的映射表,ID在所有进程中都是唯一确定的。
客户端在做远程过程调用时,必须附上这个ID,它就查一下这个表,找出相应的 Call ID,然后把它传给服务端。服务端也通过查表,来确定客户但需要调用的函数,然后执行相应函数的代码。寻址方式的具体实现方式,可以通过注册中心,服务提供者完成后,对外暴露相应的功能并将其注册到注册中心上,客户端从注册中心寻找服务,然后调用该服务对应的方法完成远程调用。
如何进行序列化和反序列化
客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。
这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。
只有二进制数据才能在网络中传输,序列化和反序列化的定义是:
- 将对象转换成二进制流的过程叫做序列化
- 将二进制流转换成对象的过程叫做反序列化
这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
如何网络传输
远程调用往往用在网络上,客户端和服务端的通讯是通过网络连接的:首先建立通信连接,通过把这个连接把请求信息的字节流传给服务端,然后再把序列化后的响应结果传回给客户端。
所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。
网络协议可以选择的协议有:TCP、UDP、HTTP等。尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2。
TCP 的连接是最常见的,简要分析基于 TCP 的连接:通常 TCP 连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。
基于 TCP 协议的 RPC 调用
大致流程为:由服务的调用方与服务的提供方建立 Socket 连接,并由服务的调用方通过 Socket 将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法。将结果返回给服务的调用方。
基于 HTTP 协议的 RPC 调用
该方法更像是访问网页一样,只是它的返回结果更加单一简单。
大致流程为:由服务的调用者向服务的提供者发送请求,这种请求的方式可能是 GET、POST、PUT、DELETE 等中的一种。服务的提供者可能会根据不同的请求方式做出不同的处理,或者某个方法只允许某种请求方式。
而调用的具体方法则是根据 URL 进行方法调用,方法所需要的参数可能是对服务调用方传输过去的 XML 数据或者 JSON 数据解析后的结果,返回 JOSN 或者 XML 的数据结果。
由于目前有很多开源的 Web 服务器,如 Tomcat,所以其实现起来更加容易,就像做 Web 项目一样。
两种方式对比
基于 TCP 协议实现的 RPC 调用由于 TCP 协议处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发数。但是需要更多关注底层复杂的细节,实现的代价更高。同时对不同平台,如安卓,iOS 等,需要重新开发出不同的工具包来进行请求发送和相应解析,工作量大,难以快速响应和满足用户需求。
基于 HTTP 协议实现的 RPC 则可以使用 JSON 和 XML 格式的请求或响应数据。而 JSON 和 XML 作为通用的格式标准(使用 HTTP 协议也需要序列化和反序列化,不过这不是该协议下关心的内容,成熟的 Web 程序已经做好了序列化内容),开源的解析工具已经相当成熟,在其上进行二次开发会非常便捷和简单。
但是由于 HTTP 协议是上层协议,发送包含同等内容的信息,使用 HTTP 协议传输所占用的字节数会比使用 TCP 协议传输所占用的字节数更高。
因此在同等网络下,通过 HTTP 协议传输相同内容,效率会比基于 TCP 协议的数据效率要低,信息传输所占用的时间也会更长,当然压缩数据能够缩小这一差距。
实现方式
同步 RPC:调用方发送请求后,会一直等待服务器返回结果,直到结果返回或超时。这种方式简单直接,但可能导致调用方长时间阻塞。
异步 RPC:调用方发送请求后不等待结果,而是继续执行其他任务。一般通过回调函数、Future/Promise 或者消息队列来处理异步 RPC。
优点和缺点
优点:
- 透明性:RPC 隐藏了网络通信的底层细节,使得分布式系统的通信看起来像是本地调用。
- 封装性:RPC 允许远程过程调用,提高了代码的封装性和复用性。
- 跨语言性:RPC 框架通常支持多种编程语言,使得不同语言的应用能够进行通信。
缺点:
- 复杂性:RPC 通常需要定义接口,使用 IDL 进行描述,这增加了开发的复杂性。
- 性能开销:与本地调用相比,RPC 通信涉及序列化、网络传输和反序列化等操作,可能引入一定的性能开销。
- 网络不稳定性:分布式环境中,网络故障或不稳定性可能导致 RPC 失败,需要额外的处理机制。
使用场景
RPC适用于需要分布式架构的系统,其中组件需要跨网络边界进行通信。常见的使用场景包括:
- 微服务架构:RPC广泛应用于分布式系统中,尤其是在微服务架构中。
- 在微服务架构中,各个服务通常需要相互通信。RPC可以用于服务之间的通信,使得不同的服务可以通过调用彼此的函数来完成任务。
- 跨平台应用:RPC可以用于跨平台调用,例如一个Java程序可以通过RPC调用一个用Python编写的函数。
- 分布式计算:如大数据处理和计算密集型任务,可以通过RPC在多台机器间分配和管理计算任务。
-
客户端-服务器架构:传统的客户端-服务器应用可以通过RPC实现,例如前端应用程序通过RPC调用后端的服务。
常见框架
gRPC:由 Google 开发的高性能 RPC 框架,使用 Protocol Buffers 作为接口定义语言。阿里巴巴开源的高性能RPC框架,支持服务治理和多种协议,适用于Java生态系统。
Apache Thrift:与Dubbo类似,最初由Facebook开发,后面进入Apache开源项目。由 Apache 软件基金会开发的跨语言的 RPC 框架,支持多种语言。
Dubbo:阿里巴巴开源的分布式服务框架,提供高性能的RPC调用能力,以及服务动态寻址、负载均衡等特性。
Spring Cloud:基于Spring Boot构建的微服务架构生态,提供了丰富的RPC相关组件,如Spring Cloud OpenFeign等。
gRPC:由Google开发的高性能RPC框架,使用Protocol Buffers进行序列化,支持多种编程语言。提供了多种调用方式,包括简单RPC、服务器流式RPC、客户端流式RPC和双向流式RPC。
示例
以下是一个简化的 gRPC 的示例,这里仅给出gRPC服务定义和简单实现的概要:
1)服务定义(Proto文件)
// 指定了该文件使用的protobuf语法版本为proto3
syntax = "proto3";
// 定义了这个.proto文件所属的包名
package example;
// 定义服务。服务是一组可以远程调用的方法集合。
service Greeter {
// 定义一个RPC方法
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// 请求消息
message HelloRequest {
string name = 1;
}
// 响应消息
message HelloReply {
string message = 1;
}
这段代码是一个使用Protocol Buffers(简称protobuf)定义的gRPC服务示例。protobuf是由Google开发的一种语言无关、平台无关的数据交换格式,而gRPC是一个高性能、开源和通用的RPC框架。
在Greeter
服务中定义了一个名为SayHello
的RPC方法。该方法接收一个类型为HelloRequest
的消息作为请求参数,并返回一个类型为HelloReply
的消息作为响应结果。
定义了一个名为HelloRequest
的消息类型,用于封装传递给SayHello
方法的请求数据。这里只有一个字段name
,其类型为字符串,序号为1。定义了一个名为HelloReply
的消息类型,用于封装从SayHello
方法返回的数据。这里只有一个字段message
,其类型也为字符串,序号同样为1。
2)服务器端实现(简化)
import grpc // 用于创建和管理gRPC服务
from concurrent import futures // 用于线程池管理,以便在同一时间处理多个请求。
import example_pb2, example_pb2_grpc // 这些是由Protocol Buffers定义的消息和服务生成的模块。
// 服务实现类
class Greeter(example_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return example_pb2.HelloReply(message='Hello, %s!' % request.name)
// 用于启动gRPC服务器
def serve():
// 创建了一个新的gRPC服务器实例,并且指定了最大工作线程数为10。
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
// 将Greeter类添加到服务器中,使得服务器能够处理来自客户端的请求。
example_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
// 设置服务器监听的端口。
server.add_insecure_port('[::]:50051')
// 启动服务器并等待其完成。
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
定义了一个名为Greeter
的服务实现类,它继承自example_pb2_grpc.GreeterServicer
。这个类实现了SayHello
方法,该方法接收一个包含name
字段的request
对象,并返回一个带有问候消息的HelloReply
对象。
通过serve()
函数来启动gRPC服务器。
3)客户端实现(简化)
这段代码实现了一个简单的gRPC客户端,它连接到本地的gRPC服务,并向其发送一个SayHello
请求,然后打印出服务返回的消息。
import grpc
import example_pb2
import example_pb2_grpc
def run():
// 创建一个到localhost地址上运行在50051端口的gRPC服务的不安全通道(即不进行SSL/TLS加密)。
with grpc.insecure_channel('localhost:50051') as channel:
// 使用之前创建的通道来实例化一个Greeter服务的存根对象,用来发送RPC请求。
stub = example_pb2_grpc.GreeterStub(channel)
// 发送一个名为SayHello的方法调用,并传递一个HelloRequest请求对象,其中包含name字段设置为'world'。该行代码会阻塞直到收到服务器响应。
response = stub.SayHello(example_pb2.HelloRequest(name='world'))
// 打印出从服务器接收到的消息内容
print("Greeter client received: " + response.message)
if __name__ == '__main__':
run()
以上代码仅作为gRPC实现RPC的示例,展示了服务定义、服务器端和客户端的基本实现框架。在实际应用中,RPC的实现会更加复杂,涉及到服务的注册与发现、负载均衡、容错处理等多个方面。
三、问题
在面试题中可能会看到为什么有了HTTP还需要RPC。但由上述介绍可知,RPC的提出及使用时间均比HTTP要早,故首先需要思考第一个问题——为什么有RPC还要有HTTP协议?
问题一:是先有HTTP还是先有RPC?
(1)首先,我们需要先了解 B/S 和 C/S 指的是两种常见的软件架构模型。
C/S(Client/Server)即客户端/服务器架构中,客户端和服务器是独立的两个程序,通过某种网络协议进行通信。
- 客户端需要安装专用的软件来访问服务器提供的服务。服务器提供各种功能给客户端调用。
- 典型的 C/S 架构应用如QQ、迅雷、游戏客户端等各种需要安装对应软件的服务。
C/S架构的概念可以追溯到20世纪60年代随着分布式计算的兴起开始出现,但真正流行开来是80年代中后期。
B/S(Browser/Server)浏览器/服务器架构中,客户端只需要使用 web 浏览器来访问 web 服务器提供的服务。
- web 服务器提供 HTML、JavaScript、CSS 等页面,浏览器负责展现。
- 典型的 B/S 架构应用就是 web 应用。用户只需要一个浏览器就可以访问服务,而不需要安装任何客户端软件。
B/S架构是与万维网、HTTP协议同时出现的,大约出现于1989年。
因此,由上可知,在刚开始C/S架构流行时,对于C/S架构下的软件,如聊天软件、办公软件等,它们只需要与自己公司的服务器通信,所以可以使用自家定制的RPC协议进行远程调用即可。但随着万维网与B/S架构的出现,浏览器产生了,而浏览器需要访问来自不同公司的很多网站,这不能通过RPC进行访问,所以需要一个统一的标准来与这些网站服务器通信。这就是HTTP协议发挥作用的地方。HTTP为B/S架构提供了一个统一的标准,让不同网站的服务器能够与浏览器交互。
(2)由此,分析可知,在多年前,RPC与HTTP有对应的主流应用场景。
RPC 更多用于 C/S 架构,即客户端和服务器之间的通信。由于 C/S 架构通常是 within an organization(一个组织内部),所以可以使用自家定制的 RPC 协议来实现客户端和服务器之间的通信。
HTTP 主要用于 B/S 架构,即浏览器和服务器之间的通信。因为浏览器需要一个统一的标准来访问不同网站的服务器,所以 HTTP 成为了 B/S 架构的标准协议。
而HTTP发明的场景是用于web架构,而不是分布式系统间通信,这导致了在很长一段时间内,HTTP是浏览器程序和后端web系统通信用的东西,传输的文档格式是繁琐的HTML格式,因此没有人把HTTP作为分布式系统通信的协议。
但是随着用户需要,许多软件同时支持多端,现在情况已经变化,B/S 架构和 C/S 架构在慢慢融合。越来越多的应用同时支持 Web 端、手机端和 PC 端。
- 随着前端技术的发展,AJAX技术和JSON文档在前端界逐渐成为主流,HTTP调用摆脱HTML,开始使用JSON这一相对简洁的文档格式。之后随着RESTFUL思潮的兴起,越来越多系统用HTTP来提供服务。因此为了简化架构,现在很多应用选择使用 HTTP 作为统一的通信协议,来支持多端通信。这使服务器端只需要实现一次 HTTP 接口,就可以支持所有客户端。
- 而 RPC 协议则主要用于 within an organization(一个组织内部),比如公司内部的微服务(Microservices)之间的通信。
所以总的趋势是:HTTP 作为公用的标准协议不断普及,而自家定制的 RPC 协议则主要用于内部集群(Cluster)。
而由上介绍,我们可以知道HTTP既可以用于B/S端,也可以用于C/S端,那这样为什么不全部使用HTTP协议呢,即引出了接下来的经典面试问题——为什么有了HTTP,还需要RPC。
问题二:为啥传统HTTP有了RPC的需求?为什么有了HTTP,还需要RPC?
HTTP通常指的是HTTP1.1版本,在对HTTP1.1与RPC进行分析时,一般会从以下方面进行分析。
总结来说,HTTP/1.1相对于RPC在传输协议和性能方面有一些不足,但是随着HTTP/2和HTTP/3的出现,HTTP协议在这些方面得到了很大的改进,使得它在某些场景下可以替代RPC来进行高效的数据传输。如当前流行的gRPC框架底层就使用HTTP2.0 协议进行通信。
当然2015年才出现HTTP 2.0,因此在2015年之前使用RPC可以从效率方面进行考虑,但如今已经出现了效率一样的HTTP2.0,为什么还需要继续使用RPC呢?
根据上述对比分析,可以发现HTTP2.0协议已经优化编码效率问题,像 grpc 这种 rpc 库使用的就是 http2.0协议,那么为什么还需要使用RPC进行远程调用呢?
首先是历史原因,HTTP2.0在2015年才出现,因此许多公司内部已经使用了很久的PPC,更换需要很大的成本。
而之前说过,RPC并不是一个具体的协议,只是一种协议的规范,明确的说是概念、机制或者思想,它并没有具体实现,只有按照RPC通信协议规范实现的通信框架,也就是RPC框架,才是协议的具体实现,它包括了:接口规范+序列化反序列化规范+通信协议等。现在狭义的RPC一般指一些用IDL(Inteface Description Language)描述接口, 然后生成stub的框架,比如grpc,thrift,dubbo等,其中grpc,dubbo3.0的传输用的都是HTTP2.0,也已经属于RPC和HTTP的融合体了,这种融合体的设计使得开发人员可以享受到HTTP的广泛支持,同时获得更好的性能和功能。
还有,我们要确定的一件事情是,业务是通过需求量级去转化的,肯定是HTTP本身已经不满足有的业务的需求了,主要是性能和安全的问题。
- 同步和异步问题:量级很大的情况下,如果有多个项目,多个接口,大家一起调的时候,同步的链路会很长。如果请求失败的话,需要每个接口再重新访问。所以连接数量迅速上升。
- 安全问题:比如说我们HTTP的访问失败,超时了,这种情况很难去追踪,很多时候我们需要通过定时任务做主动的补偿。
- 复用问题:在软件开发过程中,很多项目采用了分层架构,这种架构通常包括Web层和Service层。Web层主要负责处理用户界面相关的逻辑,而Service层则包含了更多的业务逻辑,这些逻辑可以被多个项目或模块复用。然而,当其他项目想要直接调用Service层的服务时,可能会面临一个问题:Service层的设计初衷是为了被Web层或其他高层模块调用,而不是直接暴露给其他项目。如果为了使其他项目能够直接调用Service层,而不得不在Controller层再次封装Service层的服务,这实际上违背了设计的初衷,增加了不必要的复杂性。换句话说,这样做会导致架构上的混乱,即原本应该简单直接的调用变得复杂,反而降低了系统的清晰度和可维护性。
当我们使用比较成熟的RPC库时,RPC库通常提供了更多面向服务的高级特性,如服务发现、负载均衡和熔断降级等。
- 服务发现:RPC库可以提供服务注册和发现的机制,使得服务之间的通信更加灵活和可靠。通过服务发现,服务可以自动注册自己的地址和状态,并且客户端可以动态地发现可用的服务实例。这样,当服务实例发生变化时,客户端可以自动适应并调用可用的服务。
建立连接的前提是:你得知道IP地址和端口。
这个找到服务对应的IP端口的过程,其实就是服务发现。在HTTP中,你知道服务的域名,就可以通过DNS服务去解析得到它背后的IP地址,默认80端口。
而RPC的话就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如consul或者etcd,甚至是redis。想要访问某个服务,就去这些中间服务去获得IP和端口信息。由于dns也是服务发现的一种,所以也有基于dns去做服务发现的组件,比如CoreDNS。
- 负载均衡:RPC库可以支持负载均衡算法,用于将请求动态地分配到不同的服务实例上。负载均衡可以根据实际情况,如服务实例的负载情况、网络延迟等,来决定将请求发送到哪个服务实例上,以实现请求的均衡分配和提高系统的性能和可伸缩性。
- 熔断降级:RPC库可以实现熔断降级机制,用于处理服务不可用或响应时间过长的情况。当某个服务出现故障或性能下降时,熔断降级可以自动切换到备用服务或返回默认值,以保证系统的稳定性和可用性。熔断降级还可以防止故障的扩散,避免整个系统崩溃。
总的来说,RPC框架对比HTTP协议多了更高级的封装,它提供了服务发现、负载均衡和熔断降级等面向服务的高级特性。这些特性使得RPC框架在构建分布式微服务系统时更加方便和可靠,能够提供更好的性能、可伸缩性和容错能力。
举例说明:电商平台中的应用场景
考虑一个典型的电商平台,这个平台包括多个服务模块,如用户管理、商品管理、订单处理、库存管理和支付服务等。我们来看看这些模块之间如何通信,选择 HTTP 还是 RPC。
在这个系统中,用户管理服务可能需要提供给外部应用访问的接口,例如前端应用程序、移动客户端,甚至是合作伙伴的系统。由于这些外部系统可能是不同的编程语言和技术栈,为了实现最大的兼容性,用户管理服务通常采用 HTTP 作为通信协议。这不仅方便开发人员调试和测试,也确保了跨平台、跨语言的可访问性。例如,用户管理服务暴露一个 GET /users/{userId}
接口来获取用户信息,前端应用可以轻松地调用这个接口并渲染用户界面。
另一方面,订单处理服务和库存管理服务之间的通信需要频繁、低延迟,并且服务之间的接口是受控的、不对外部暴露。在订单创建时,订单处理服务需要立即检查库存,这种情况下,使用 RPC 通信可以大大提高效率,因为 RPC 的二进制序列化数据传输占用的网络资源更少,并且调用更类似于本地函数调用,这种方式的低延迟、高性能特点非常适合这种高频内部服务调用的场景。
支付服务通常也是一个对外暴露的服务模块,涉及到与银行、支付网关等第三方系统的交互。这些第三方系统的接口可能也是基于 HTTP 的,因此支付服务内部可能会使用 RPC 与订单服务、用户服务通信,但与外部银行接口交互时则使用 HTTP。这种组合使用可以达到内部通信的高效性和外部交互的兼容性。
问题三:同步RPC比HTTP好在哪儿?
同步RPC(远程过程调用)相比于HTTP协议,在某些场景下确实具有一定的优势。以下是同步RPC的一些优点:
- 性能更高:
- 更低的延迟:RPC通常使用二进制协议(如Protocol Buffers、Thrift等),相比HTTP使用的文本协议(如JSON),数据传输效率更高,因此延迟更低。
- 更少的开销:RPC框架通常会有更高效的序列化和反序列化机制,减少了网络传输中的额外开销。
- 更简单的编程模型:
- 透明的远程调用:RPC允许开发者像调用本地函数一样调用远程函数,简化了编程模型,减少了网络编程的复杂度。
- 更好的抽象:RPC提供了更强的功能抽象,使得开发者可以专注于业务逻辑,而不需要过多关注底层通信细节。
- 更强的类型检查:
-
静态类型检查:许多RPC框架支持静态类型检查,可以在编译期发现潜在的类型错误,从而提高代码质量。
-
-
更好的错误处理:
-
详细的错误信息:RPC框架通常提供更丰富的错误处理机制,能够传递更详细的错误信息,便于调试和维护。
-
-
支持双向通信:
-
流式通信:部分RPC框架支持双向通信,可以实现流式传输,适用于大数据量传输的场景。
-
-
高效的服务治理:
-
服务注册与发现:许多RPC框架内置了服务注册与发现机制,可以自动管理服务实例,简化服务间通信的配置。
-
问题四:微服务出来前,大流量业务是不是RPC多?
在微服务架构出现之前,大流量业务确实更多依赖于RPC来处理分布式系统的通信问题。但随着技术的发展,微服务架构结合现代RPC机制和其他先进的技术手段,为处理大流量业务提供了更加灵活、高效和可靠的解决方案。
在微服务架构出现之前,尤其是在早期的分布式系统中,RPC确实是一种常见的技术手段,用于处理大流量业务。虽然早期的RPC框架可能没有现代微服务架构中的那些高级服务治理功能,但它们仍然提供了一定程度上的服务发现和管理能力,帮助开发者更好地组织和管理分布式系统。
随着互联网应用规模的不断增长,对系统灵活性、可扩展性和容错性的要求也越来越高。微服务架构在这种背景下应运而生,它通过将单一应用程序拆分为多个小型服务,每个服务运行在其独立的进程中,并通过轻量级通信机制(如HTTP、消息队列)互相协作。
微服务架构并不排斥RPC,事实上,许多微服务框架(如Spring Cloud、Dubbo)都集成了各种RPC机制。但是,微服务架构更强调服务的独立性、松耦合以及自动化运维能力。因此,微服务通常还会结合其他技术手段(如服务注册与发现、负载均衡、熔断限流等)来进一步提升系统的稳定性和性能。
问题五:为什么微服务默认HTTP,而且现在RPC用的少了?
微服务架构在现代软件开发中变得越来越流行,它将一个单体应用程序分割为多个相对独立的小服务,这些服务可以独立开发、部署和维护。为了让这些分布在不同地方的服务协同工作,服务之间需要通过通信协议进行交互。通常,HTTP 和 RPC(Remote Procedure Call,远程过程调用)是两个常见的微服务通信方式。
微服务架构中,默认使用HTTP协议的原因主要是因为它简单易用,并且具有良好的跨平台兼容性。HTTP协议是互联网的标准协议之一,几乎所有的编程语言和框架都支持HTTP通信,这使得它成为一种非常通用的通信方式。此外,HTTP协议支持RESTful风格的服务设计,这种设计模式可以简化服务之间的交互逻辑,提高系统的可维护性和扩展性。
至于为什么现在RPC(远程过程调用)的使用相对减少,这并不是说RPC技术本身过时了,而是随着技术的发展,特别是对于性能要求较高的场景,人们开始更多地关注如何提高通信效率和降低延迟。HTTP协议虽然功能强大,但在某些情况下可能不如RPC那样高效,特别是在需要频繁进行二进制数据交换或实时性要求较高的应用场景中。因此,在一些对性能有更高需求的微服务架构设计中,开发者可能会选择使用更高效的通信协议,如gRPC等基于RPC的框架,这些框架通常提供更好的性能表现和更强大的功能集,比如流式通信、双向流式通信等。
微服务架构默认使用HTTP的主要原因是其易用性、标准化、跨平台性和丰富的生态系统。而RPC的使用减少主要是由于其复杂性、维护成本和调试困难等因素。当然,RPC在某些特定场景下仍然有其独特的优势,但总体而言,HTTP已成为微服务架构中的主流选择。
总之,选择哪种通信方式取决于具体的应用场景和技术需求。HTTP适合大多数通用场景,而RPC则在特定领域内展现出更高的性能优势。随着技术的进步,新的通信协议和技术不断涌现,开发者可以根据项目特点灵活选择最合适的方案。