RPC入门
简介
RPC(Remote Procedure Call,远程过程调用)是一种通信协议,允许一个程序(客户端)调用另一个地址空间(通常是网络上的另一台机器)的过程或函数,而就像本地调用一样,使调用者无需关心底层的网络细节。
RPC的基本原理是,客户端调用远程服务器上的函数时,就像调用本地函数一样,但是函数的执行实际上发生在远程服务器上。RPC框架负责将函数调用、参数传递、执行结果等信息在客户端和服务器之间进行传输和协调。
核心组成部分
- 通信协议:定义了客户端和服务器之间通信的规则和格式。常见的RPC通信协议包括HTTP、TCP、UDP等。
- 序列化协议:将函数调用和参数序列化为字节流,以便在网络上传输。常见的序列化协议包括JSON、XML、Protocol Buffers等。
- Stub(存根):客户端使用的本地代理,用于代表远程服务器上的函数,并负责将函数调用转换为网络消息发送到服务器。
- Skeleton(骨架):服务器端的本地代理,用于接收来自客户端的网络消息,并调用真正的函数执行请求。
- 注册中心:用于注册和发现RPC服务的中心化组件,客户端和服务器可以通过注册中心找到彼此。
组件职责
服务调用方(Consumer)
服务调用方也叫服务消费者,它的职责之一是提供需要调用的接口相关信息给调用端的本地存根,比如全限定名和方法以及调用方法的参数等;职责之二是从调用方的本地存根中接收执行结果。
服务提供方(Provider)
服务提供方就是服务端,它的职责就是提供服务,执行接口实现的方法逻辑,也就是为服务提供方的本地存根提供方法的具体实现。
本地存根(stub)
远程调用中,Provider如何精确地确定自己应该执行哪个函数,以及如何处理Consumer发起的函数调用,需要借助于 Stub。Stub的作用是让远程调用看起来像本地调用一样直接进行函数调用,无需关心地址空间隔离、函数不匹配等问题。Stub的职责包括进行类型和参数转化,并在服务提供方和服务调用方之间充当桥梁的角色,使得远程调用的过程对于服务调用方和服务提供方来说都像是在本地进行函数调用一样。
- 服务调用方的本地存根(Consumer端):
- 服务调用方的本地存根存在于服务调用方(Consumer)的机器上。
- 它会接收Consumer发起的函数调用,并解析函数名、参数等信息。
- 本地存根会将这些信息整理并组装成可传输的消息,按照定义好的协议进行序列化,然后交给RPCRuntime(RPC通信者)。
- 当服务调用方收到服务提供方返回的结果后,本地存根会将数据反序列化成服务调用方所需要的数据结果,并传递给服务消费方。
- 服务提供方的本地存根(Provider端):
- 服务提供方的本地存根与服务提供方一起存在于服务提供方(Provider)的机器上。
- 当RPCRuntime收到请求包后,会交由服务提供方的本地存根进行参数等数据的转化。
- 本地存根会重新转换客户端传递的数据,以便在Provider端的机器上找到对应的函数,并传递正确的参数数据。
- 最终,服务提供方的本地存根会将执行结果返回给RPCRuntime。
RPC通信者(RPCRuntime)
RPCRuntime负责数据包的重传,数据包的确认、数据包路由和加密等。
在Consumer端和Provider端都会有一个RPCRuntime实例,负责双方之间的通信,可靠地将存根数据包传输到另一端。
调用过程
服务暴露、服务发现、服务引用和方法调用这四个阶段组成了整个RPC的执行过程。
服务暴露
服务暴露发生在Provider端。根据服务是否暴露到远程可以分为两种,一种是服务只暴露到本地,另一种则是暴露到远程。
- 本地暴露(Local Export):
- 在本地暴露中,服务只暴露给本地的调用方,不对外提供远程访问能力。
- 这种方式适用于在同一台物理机或者同一台虚拟机上的不同进程间通信,通常使用基于进程内通信(IPC)的方式实现,例如通过共享内存、套接字等方式。
- 本地暴露的优点是通信效率高、安全性好,但缺点是不能实现跨网络的远程调用。
- 远程暴露(Remote Export):
- 在远程暴露中,服务可以被远程调用方访问,提供了跨网络的调用能力。
- 这种方式通常涉及网络通信,可以通过各种网络协议(如HTTP、TCP、UDP等)来实现跨网络的调用。
- 远程暴露的优点是可以实现分布式系统中不同节点之间的通信和协作,但缺点是通信开销相对较高,可能存在网络延迟和安全风险。
服务发现
服务发现的方式有两种,分别是直连式和注册中心式,对应的是Provider端的两种服务暴露方式。
- 直连式(Direct Connection)服务发现:
- 在直连式服务发现中,服务的消费方(Consumer)直接与服务的提供方(Provider)建立连接,不依赖于任何中间组件。
- Consumer通过配置服务提供方的地址(IP地址和端口号),直接向提供方发送请求。
- 这种方式简单直接,适用于小型系统或者对服务调用的可控性要求较高的场景。
- 注册中心式(Service Registry)服务发现:
- 在注册中心式服务发现中,服务的提供方将自己注册到一个中心化的服务注册中心(Service Registry)中,而服务的消费方通过查询注册中心获取服务提供方的信息,然后再进行调用。
- 注册中心负责管理服务的注册、注销和查询等操作,消费方通过与注册中心交互来获取服务提供方的地址信息。
- 这种方式通常用于大型分布式系统中,具有良好的扩展性和灵活性,但同时也引入了额外的中间件依赖和网络开销。
服务引用
服务引用的过程发生在服务发现之后,当Consumer端通过服务发现获取所有服务提供者的地址后,通过负载均衡策略选择其中一个服务提供著的节点进行服务引用。服务引用的过程就是与某一个服务节点建立连接,以及在Consumer端创建接口的代理的过程其中建立连接也就是两端的RPCRuntime 建立连接的过程。
服务调用
- 服务消费者以本地调用方式(即以接口的方式)调用服务,它会将需要调用的方法、参数类型、参数传递给服务消费方的本地存根。
- 服务消费方的本地存根收到调用后,负责将方法、参数等数据组装成能够进行网络传输的消息体(将消息体对象序列化为二进制数据),并将该消息体传输给RPC通信者。
- Consumer 端的RPC通信者通过sockets 将消息发送到Provider端,由Provider端的RPC通信者接收。Provider端将收到的消息传递给服务提供方的本地存根。
- 服务提供方的本地存根收到消息后将消息对象反序列化。
- 服务提供方的本地存根根据反序列化的结果解析出服务调用的方法、参数类型、参数等信息,并调用服务提供方的服务。
- 服务提供方执行对应的方法后,将执行结果返回给服务提供方的本地存根。
- 服务提供方的本地存根将返回结果序列化,并且打包成可传输的消息体,传递给Provider端的RPC通信者。
- Provider端的RPC通信者通过sockets将消息发送到Consumer端,由Consumer端的RPC通信者接收。Consumer端将收到的消息传递给服务消费方的本地存根。
- 服务消费方的本地存根收到消息后将消息对象反序列化。反序列化出来的是方法执行的结果,并将结果传递给服务消费者。
- 服务消费者得到最终执行结果。
常见RPC协议
- gRPC:gRPC 是由 Google 开发的高性能、开源的 RPC 框架,基于 HTTP/2 标准设计。它使用 Protocol Buffers(protobuf)作为默认的序列化协议,支持多种编程语言。
- Apache Dubbo:Apache Dubbo 是阿里巴巴开源的分布式服务框架,支持面向接口的远程调用,提供了丰富的功能如负载均衡、服务注册与发现、容错机制等。
- Apache Thrift:Apache Thrift 是 Facebook 开源的跨语言的服务框架,支持多种编程语言,使用 IDL(接口定义语言)来定义接口和数据类型,可以生成对应语言的客户端和服务器端代码。
- RSocket:RSocket 是一个跨语言的异步通信协议,支持多种传输模型(如TCP、WebSocket),提供了请求-响应、请求-流、流-响应等多种交互模式,适用于高性能、实时性要求较高的场景。
- XML-RPC:XML-RPC 是一种使用 XML 格式进行数据交换的远程过程调用协议,它基于 HTTP 协议,简单易用,但相对于其他协议来说性能较低。
- JSON-RPC:JSON-RPC 是一种使用 JSON 格式进行数据交换的远程过程调用协议,与 XML-RPC 类似,但使用 JSON 格式使得数据更加紧凑,适用于 Web 开发中的前后端交互。
protobuf
Protocol Buffers(protobuf)是一种轻量级、高效的序列化框架,由 Google 开发并开源。它可以用于结构化数据的序列化和反序列化,支持多种编程语言,并且提供了简单的接口定义语言(IDL)用于定义数据结构和服务接口。
protobuf 的主要特点包括:
- 高效性:protobuf 使用二进制编码,相比于文本格式(如 XML、JSON),序列化后的数据更加紧凑,传输效率更高。
- 跨语言支持:protobuf 提供了多种编程语言的支持,包括 Java、C++、Python、Go 等,这使得不同语言之间的数据交换变得更加方便。
- 可扩展性:protobuf 的消息格式是可以扩展的,可以向已有的消息类型中添加新的字段而不会破坏现有的兼容性。
- 自描述性:protobuf 支持自描述,即在序列化后的数据中包含了消息的字段标识和类型信息,使得消息的解析更加容易。
- 快速:相比于其他序列化框架,protobuf 的序列化和反序列化速度更快,适用于对性能要求较高的场景。
1 | syntax = "proto3"; |