RPC 初识
- 进程间通信(IPC)是在多任务操作系统或联网的计算机之间运行的程序和进程所采用的通信技术,IPC有两种类型的进程间通信的方式分别是本地过程调用(LPC)和远程过程调用(RPC)。
# 本地过程调用LPC
本地过程调用(LPC)用于多任务操作系统中,使同时运行的任务能够相互会话,任务共享内存空间以实现任务同步和互相发送消息。
例如:完成一个本地函数的调用
- add()函数的执行流程
- 分别将变量a和b的值压入栈
- 执行add()函数,从栈中取出a和b的值,赋值给x和y。
- 计算x加y的值并保存到栈中
- 退出add()函数将x加y的值赋值给result
- add()函数的执行流程
int add(int x, int y){
return x + y;
}
int a = 1;
int b = 2;
int result = add(a, b);
2
3
4
5
6
- RPC通信则需要跨域不同机器、不同进程,因此需要解决三个问题:
- 函数ID(服务寻址)
- 数据流的序列化和反序列化
- 网络传输
# 远程过程调用RPC
RPC(Romote Procedure Call)远程过程调用,RPC是通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,开发人员无需额外为交互作用编程。若软件采用面向对象编程,那么RPC又称为远程调用或远程方法调用。简单来说,远程调用协议是一种通过网络从远程计算机程序上请求服务,同时无需了解底层网络技术的协议。
RPC是一种用于构建基于客户端/服务器(C/S)的分布式应用程序技术,调用者与被调用者可能在同一台服务器上,也可能在由网络连接的不同服务器上。对客户端和服务器而言,使用RPC时网络通信是透明的,远程调用如何本地调用一样简单。简单来说,RPC就是要像调用本地函数一样去调用远程函数。
# RPC核心
RPC核心功能是指实现一个RPC最重要的功能模块即RPC协议部分,一个RPC的核心功能主要由5部分组成分别是:客户端、客户端存根、网络传输模式、服务器、服务器存根。
组成 | 名称 | 描述 |
---|---|---|
客户端 | client | 服务调用方 |
客户端存根 | client stub | 存放服务器地址信息,将客户端请求参数数据打包成网络消息,在通过网路传输发送给服务器。 |
网络传输模式 | transfer | 底层传输,可以是TCP或HTTP。 |
服务器存根 | server stub | 接受客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。 |
服务器 | server | 服务提供者 |
# RPC原理
- 实现一个RCP框架只需要实现三点:
- Call ID映射:可以直接使用函数字符串,也可以使用整数ID,映射表通常是要一个哈希表。
- 序列化和反序列化:可使用Protobuf、FlatBuffers等之类
- 网络传输库:可使用Socket、Asio、ZeroMQ、Netty等
客户端client(caller 服务调用者)
- 通过本地服务调用的方式调用远程服务
客户端存根 client stub
接收到调用请求后负责将方法和参数等信息序列化(组装)为能够在网络中传输的消息体。
调用客户端句柄,执行传送参数。
客户端存根 client stub
寻找到远程服务器地址并将消息通过网路发送给服务器
调用本地系统内核发送网络消息,消息传递到远程主机。
服务器存根 server stub
接收到客户端消息后进行解码即反序列化操作
服务器句柄得到消息并获取参数
服务器存根 server stub
根据解码结果调用本地服务进行处理
执行远程过程
服务器 server (callee 服务提供方)
- 本地业务处理
服务器 server
服务器处理结果返回给服务器存根
执行过程将结果返回给服务器句柄
服务器句柄返回结果并调用远程系统内核
消息传回本地主机
服务器存根 server stub
- 序列化结果
服务器存根 server stub
- 将序列化结果通过网络发送至客户端消费方
客户端存根 client stub
接收到服务器返回的消息并进行反序列化解码
客户端接收句柄并返回数据
客户端句柄由内核接收消息
客户端 client
- 服务器消费方获得最终结果
# RPC技术点
- 一个典型的RPC使用场景中,包含服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议指明了程序如何进行网络传输和序列化。
实现RPC重点需要实现三个技术,分别是:服务寻址、数据流的序列化和反序列化、网络传输
# 1.服务器寻址
远程过程调用中包含三个角色的节点分别是服务调用方、服务提供方、注册中心
可靠的服务寻址方式主要是为了提供服务的发现,是RPC实现的基石。从服务提供方的角度来看,**当提供方服务启动时需要自动向注册中心注册机器IP、端口和提供的服务列表,当提供方服务停止时需要向注册中心注销服务。提供方需定时向注册中心发送心跳,当一段时间未收到来自提供方的心跳后,会认为提供方已经停止服务,此时会从注册中心上摘掉对应的服务。**服务调用方启动时会向注册中心获取服务提供方地址列表。
服务寻址可以使用Call ID映射,在本地调用中,函数体是直接通过函数指针来指定的,但在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在RPC中所有的函数都必须具有自己的ID,这个ID在所有进程中都是唯一的。
客户端在做远程过程调用时,必须附上这个函数ID,还需要在客户端和服务端分别维护函数和Call ID的对应表。当客户端需要进行远程调用时,会差这个对应表找出对应的Call ID,然后将其传递给服务器,服务器也通过查对应表来确定客户端需要调用的函数,最终执行相对应函数的代码。
# 2.数据流的序列化和反序列化
远程过程调用最重要的是序列化与反序列化,因为传输的数据库必须是二进制的,因此客户端必须将数据序列化为二进制格式传递给服务器。为什么需要使用二进制,可直接使用文本吗?
**采用原始的二进制可以省掉中间转换环节,而且数据量会大大减少,效率更高。**采用文本方式则需要将整数转换为字符串后发送,服务端接收时又需要再次进行数据转换。
客户端如何将参数传递给远程的函数呢?在本地过程调用中,只需要将参数压入栈,让函数自己去栈中读取即可。但在远程过程调用中,客户端跟服务器是不同的进程,不能通过内存来传递参数。此时就需要客户端将参数先转换为字节流再传递给服务器,服务器接收后再将字节流转换为自己能读取的格式。而在网络中只有二进制数据才能传输,因此序列化与反序列化的定义也就是将对象转换成二进制流的过程是序列化,将二进制转换为对象的过程则是反序列化。
# 3.网络传输
远程调用往往使用在网络环境中,客户端和服务器是通过网络连接的,所有的数据都需要通过网络传输,因此需要一个网络传输层。网络传输层需要将Call ID和序列化后的参数字节流传递给服务器,然后再将序列化后的调用结果传回给客户端。只要能完成这两个操作,都可以作为传输层使用。因此它所使用的网络传输协议是不限的,只要能完成传输即可。尽管大部分RPC框架都是用TCP协议,其实UDP也可以。
TCP连接是最常见的,通常TCP连接可以是按需连接,即需要调用时先建立调用后断开。也可以是长连接,客户端和服务器建立连接后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制和定期检测建立的连接是否存活有效。另外,多个远程过程调用可以共享同一个连接。
# RPC网络传输协议
- 在RPC中可选的网络传输方式有很多种,可选的包括TCP、UDP、HTTP,每种协议对整体的性能和效率都有不同的影响。如何选择一个正确的网络传输协议呢?
# 1.基于TCP的RPC
基于TCP协议的RPC调用是由服务调用方和服务提供方建立Socket连接,并由服务调用方通过Socket将需要调用的接口名称、方法名称、参数序列化后传递给服务提供方,服务提供方反序列化后再利用反射调用相关的方法。最后将结果返回给服务调用方,整个基于TCP协议的RPC调用大致如此。
# 2.基于HTTP的RPC
基于HTTP的RPC累加类似于访问网页,只是它返回的结果更为单一简单。首先由服务调用者向服务提供者发送请求,这种请求可能是GET、POST、PUT、DELETE等其中的一种,服务提供者可能会根据不同请求方式做出不同处理,或者某个方法只允许某种请求方式。调用的具体方法则根据URL进行方法调用,方法所需参数可能是对服务器调用方传输过去的XML或JSON数据解析后的结果,最后返回JSON或XML的数据结果。
# 3.两种方式对比
基于TCP实现的RPC由于TCP处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网路开销提高性能,实现更大的吞吐量和并发数。但需要更多关注底层复杂的细节,实现代价更高。同时对不同平台比如安卓、iOS等需要重新开发不同的工具包来进行请求发送和相应的解析,因此工作量大且难以快速响应和满足用户需求。
基于HTTP实现的RPC则可以使用JSON和XML格式的请求或响应数据,JSON和XML作为通用的格式开源解析工具已经相当成熟。但由于HTTP是上层协议,发送包含同等内容信息传输所占用的字节数会比TCP更高。
# rpc、http以及restful 之间的区
# 1、RPC 和 REST 区别是什么?
你一定会觉得这个问题很奇怪,是的,包括我,但是你在网络上一搜,会发现类似对比的文章比比皆是,我在想可能很多初学者由于基础不牢固,才会将不相干的二者拿出来对比吧。既然是这样,那为了让你更加了解陌生的RPC,就从你熟悉得不能再熟悉的 REST 入手吧。
REST,是Representational State Transfer 的简写,中文描述表述性状态传递(是指某个瞬间状态的资源数据的快照,包括资源数据的内容、表述格式(XML、JSON)等信息。)
REST 是一种软件架构风格。这种风格的典型应用,就是HTTP。其因为简单、扩展性强的特点而广受开发者的青睐。
而RPC 呢,是 Remote Procedure Call Protocol 的简写,中文描述是远程过程调用,它可以实现客户端像调用本地服务(方法)一样调用服务器的服务(方法)。
而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 协议进行传输的,按理说它和REST不是一个层面意义上的东西,不应该放在一起讨论,但是谁让REST这么流行呢,它是目前最流行的一套互联网应用程序的API设计标准,某种意义下,我们说 REST 可以其实就是指代 HTTP 协议。
# 2、使用方式不同
从使用上来看,HTTP 接口只关注服务提供方,对于客户端怎么调用并不关心。接口只要保证有客户端调用时,返回对应的数据就行了。而RPC则要求客户端接口保持和服务端的一致。
REST 是服务端把方法写好,客户端并不知道具体方法。客户端只想获取资源,所以发起HTTP请求,而服务端接收到请求后根据URI经过一系列的路由才定位到方法上面去RPC是服务端提供好方法给客户端调用,客户端需要知道服务端的具体类,具体方法,然后像调用本地方法一样直接调用它。
# 3、面向对象不同
从设计上来看,RPC,所谓的远程过程调用 ,是面向方法的 ,REST:所谓的 Representational state transfer ,是面向资源的,除此之外,还有一种叫做 SOA,所谓的面向服务的架构,它是面向消息的,这个接触不多,就不多说了。
# 4、序列化协议不同
接口调用通常包含两个部分,序列化和通信协议。
通信协议,上面已经提及了,REST 是 基于 HTTP 协议,而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 协议进行传输的。
常见的序列化协议,有:json、xml、hession、protobuf、thrift、text、bytes等,REST 通常使用的是 JSON或者XML,而 RPC 使用的是 JSON-RPC,或者 XML-RPC。
通过以上几点,我们知道了 REST 和 RPC 之间有很明显的差异。
然后第二个问题:为什么要采用RPC呢?
那到底为何要使用 RPC,单纯的依靠RESTful API不可以吗?为什么要搞这么多复杂的协议,渣渣表示真的学不过来了。
关于这一点,以下几点仅是我的个人猜想,仅供交流哈:
RPC 和 REST 两者的定位不同,REST 面向资源,更注重接口的规范,因为要保证通用性更强,所以对外最好通过 REST。而 RPC 面向方法,主要用于函数方法的调用,可以适合更复杂通信需求的场景。RESTful API客户端与服务端之间采用的是同步机制,当发送HTTP请求时,客户端需要等待服务端的响应。当然对于这一点是可以通过一些技术来实现异步的机制的。采用RESTful API,客户端与服务端之间虽然可以独立开发,但还是存在耦合。比如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常工作。而 rpc + ralbbimq中间件可以实现低耦合的分布式集群架构。说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考:
REST 接口更加规范,通用适配性要求高,建议对外的接口都统一成 REST。而组件内部的各个模块,可以选择 RPC,一个是不用耗费太多精力去开发和维护多套的HTTP接口,一个RPC的调用性能更高(见下条)从性能角度看,由于HTTP本身提供了丰富的状态功能与扩展功能,但也正由于HTTP提供的功能过多,导致在网络传输时,需要携带的信息更多,从性能角度上讲,较为低效。而RPC服务网络传输上仅传输与业务内容相关的数据,传输数据更小,性能更高
# rpc解决的问题
基于RPC模式,一个 RPC 框架基本需要解决 协议约定、网络传输、服务发现这三个问题。
协议约定问题(Stub) 指的是怎么规定远程调用的语法,怎么传参数等。用上面的类比,你怎么告诉你的朋友要玩什么游戏?是直接说游戏的名字,王者荣耀,绝地求生,还是说简称,王者,吃鸡,或者用 1 代表王者,2 代表吃鸡,只说 1 或 2。
传输协议问题(RPCRuntime) 指的是在网络发生错误、重传、丢包或者有性能问题时怎么办?用上面的类比,你打电话时,刚说了打什么游戏,但是还没有收到对方回复,电话信号不好断了,这时候怎么处理?
服务发现问题(插件比如:etcd) 指的是如何知道服务端有哪些服务可以调用,从哪个端口访问?服务端可能实现多个远程调用,在不同的进程上,随机监听端口,客户端要怎么才能知道这些端口呢?
# 为什么一定要rpc
rpc可以基于tcp直接开发自己的协议,这个是可以保持长连接的,tcp的传输效率高,并且可以一直维持链接
自定义协议可以优化数据的传输
如果我们只是开发web网站或者一些服务的使用者, 那么我们用restful看起来已经足够了,但是rpc的这种模式在大量的服务中都有,比如redis协议, rabbitmq的AMQP协议, 聊天软件的协议,也就是说我们想要开发一个redis的客户端,我们只需要用我们喜欢的语言实现redis定义的协议就行了,这对于开发服务来说非常有用,一般这种协议的价值在于我们自己开发的服务之间需要通信的时候 - 那你会问了,自己开发的组件之间协作,直接调用函数不就行了吗? - 对了,有些人已经反映过来了 -- 分布式系统,分布式系统中非常常用, 比如openstack中。 还有就是微服务!
所以掌握rpc开发,对于进阶和分布式开发就变得非常重要。
http协议1.x一般情况下一个来回就关闭连接,虽然提供了keep-alive可以保持长连接,但是依然不方便,所以就出现了http2.0, http2.0基本上可以当做tcp协议使用了。所以后面讲解到的grpc就会使用http2.0开发