什么是RPC?
- RPC全称Remote Procedure Call译为远程过程调用
- RPC是一个计算机通信协议,允许调用不同进程空间的程序。
- RPC允许跨机器、跨语言调用计算机程序方法。
- RPC的客户端和服务器可以在一台机器上,也可以在不同的机器上。
程序员使用RPC时,就像调用本地程序一样,无需关注内部的实现细节。运行于一台计算机的程序调用另一台计算机的子程序,程序员无需额外地为交互作用编程。RPC允许开发者直接调用另一台计算机上的程序,开发者无需额外地为这个调用过程编写网络通信相关代码,使得开发包括网络分布式程序在内的应用程序更加容易。
为什么需要RPC框架?
- 分布式系统中服务之间的调用问题
- 远程过程调用要能够像本地调用一样,让调用者感知不到远程调用的逻辑。
如何实现客户端到服务端的调用,想要在网络中任意两台计算机上实现远程过程调用,需要解决很多问题。
- 两台物理机在网络中需要建立稳定且可靠的通信连接
- 两台服务器的通信协议定义问题,两台服务器程序如何识别对方的请求和返回结果。
- 两台计算机必须都能够识别对方发送过来的信息,并且能够识别出其中的请求含义和返回含义,才能进行处理。
RPC框架
RPC框架需要解决什么问题?
- 传输协议
- 报文编码格式
- 可用性问题
在两台机器上两个应用程序之间需要通信时,首先需要确定采用的传输协议是什么?如果两个应用程序位于不同的机器,一般会选择TCP或HTTP。如果两个应用程序位于相同的机器,也可选择UNIX Socket。
传输协议确定后,还需确定报文的编码格式,比如采用最常用的JSON或XML,若文本较大可能会选择Protobuf等其它编码方式,甚至编码后再进行压缩。接收方获取报文则需要先解压再解码。
解决了传输协议和报文编码问题后,接下来需要解决一系列的可用性问题。比如,连接超时了怎么办?是否支持异步请求和并发?
如果服务端实例很多,而客户端并不关心这些实例的地址和部署位置,只关心自己能够获取到期的结果,那就引出了注册中心(registery)和负载均衡(load balance)的问题。简单地说,即客户端和服务端相互不感知对方的存在,服务端启动时将自己注册到注册中心,客户端调用时,从注册中心获取到所有可用的实例,选择一个来调用。这样服务端和客户端只需要感知注册中心的存在就够了。注册中心通常还需要实现服务动态添加、删除,使用心跳确保服务处于可用状态等功能。
再进一步,假设服务端是不同的团队提供的,如果没有统一的RPC框架,各个团队的服务提供方就需要各自实现一套消息编码解码、连接池、收发线程、超时处理等“业务之外”的重复技术劳动,造成整体的抵消。因此“业务之外”的这部分公共的能力,即RPC框架所需具备的能力。
传输协议
RPC本质上是一种协议,允许一台计算机程序通过网络从远程计算机程序上请求服务,无需使用者了解底层网络技术。RPC协议构建在TCP、UDP、HTTP之上,在OSI七层网络通信模型中,RPC跨域了传输层和应用层,使网络分布式程序开发更为容易。
RPC采用C/S(客户端/服务器)的工作模式,请求程序为客户端(client),服务提供程序为服务端(server)。当执行一个远程过程调用时,客户端程序首先会发送一个带有参数的调用信息服务端,然后等待服务端响应。
- 客户端调用句柄发送参数,调用本地系统内核,发送网络消息到服务端等待应答。
- 服务端句柄接收到调用信息,获得进程参数,执行远程过程,计算结果,返回服务端句柄。
- 服务端句柄返回响应,调用远程系统内核,将消息传回本地。
- 客户端句柄从本地主机内核中接收数据后,客户端接收句柄返回的数据。
在服务端,服务进程保持休眠状态直到客户端调用信息到达为止,当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息,然后等待下一个调用。最后,客户端接收来自服务端的应答信息,获得进程结果,然后调用执行并继续。
RPC架构
RPC在架构上由四部分组成:客户端(client)、客户端存根(client stub)、服务端(server)、服务端存根(server stub)
- 客户端
服务调用发起方,又称为服务消费者。 - 客户端存根
客户端存根程序运行在客户端所在的计算机上,主要用来存储要调用的服务端的地址。
客户端存根程序负责将客户端请求远端服务端程序的数据打包成为数据包,通过网络发送给服务端存根程序。
客户端存根接收服务端存根程序发送的调用结果数据包,并解析返回给客户端。 - 服务端
远端的计算机机器上运行的程序,拥有客户端需要调用的方法。 - 服务端存根
服务端存根接受客户端存根程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用。
服务端存根将服务端执行调用的结果进行数据处理打包后发送给客户端存根程序。
RPC是如何实现远程过程调用的的呢?
1.当客户端发起一个远程过程调用时,首先调用本地客户端存根程序的方式调用想要使用的功能方法名。
2.客户端存根程序接收到客户端的功能调用请求后,将客户端请求调用的方法名、携带的参数等信息进行序列化,然后打包成数据包。
3.客户端存根查找到远程服务端程序的IP地址后,调用Socket通信协议,通过网络发送给服务端。
4.服务端存根程序接收到客户端发送的数据包信息,通过约定好的协议将数据反序列化,得到请求的方法名和请求参数等参数。
5.服务端存根程序准备相关数据后,调用本地服务端对应的功能方法,传入对应参数。
6.服务端程序根据已有业务逻辑执行调用过程,等到业务执行结束后,将执行结果返回给服务端存根程序。
7.服务端存根程序将程序调用结果按照约定的协议进行序列化后,通过网络发送给客户端存根程序。
8.客户端存根程序接收到服务端存根程序发送的返回数据后,对数据进行反序列化操作,并将调用返回的数据传递给客户端请求发起者。
9.客户端请求发起者得到调用结果,整个RPC调用过程结束。
RPC相关技术
1.动态代理技术
服务端存根程序和客户端存根程序,在具体编码实现中会使用动态代理技术自动生成一段程序。
2.序列化和反序列化
RPC调用过程中,数据需从一台机器上传递给另外一台机器上。互联网上所有数据都是以字节的形式进行传输,在编码过程中往往使用的都是数据对象,要在互联网上传递数据对象和变量,需要对数据对象做序列化和反序列化处理。
- 序列化:将对象转换为字节序列的过程成为对象的序列化,即编码的过程。
- 反序列化:将字节序列恢复为对象的过程称为对象的反序列化,即解码的过程。
报文编码格式
Golang中实现RPC会使用已经封装好的官方库和第三方库。Go RPC可以利用TCP或HTTP来传递数据,对要传递的数据使用多种类型的编码解码方案。
- net/rpc
Golang官方提供的net/rpc
库使用encoding/gob
进行编码和解码,支持TCP和HTTP数据传输方式,由于其他语言不支持Golang特有的gob
编码方式,因此使用net/rpc
库实现的RPC无法跨语言调用。
- net/rpc/jsonrpc
Golang官方还提供了net/rpc/jsonrpc
库实现RPC方式,JSON RPC采用JSON进行数据编码和解码,因而支持跨语言调用,但目前jsonrpc
库是基于TCP协议实现的,不支持HTTP。
- protobuf
除了Golang官方提供的RPC库,第三方库大部分会采用protobuf
进行数据编码和解码,根据protobuf
声明文件可自动生成RPC方法与服务注册代码,在Golang中可以很方便地进行RPC服务调用。