@
Rpc基础篇
1. Rpc入门知识
1.1 什么是Rpc
在分布式计算中,远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议,该协议允许运行一个于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
1.2 Rpc能做什么
Rpc是解决分布式系统通信的利器,Rpc最大的特点就是可以让我们像调用本地方法一样发起远程调用。Rpc对网络通信进行了完整包装,在我们搭建分布式系统的时候,可以让网络通信的逻辑变的简单,也可以让网络通信变的更安全。
1.3 如何去学习Rpc
1.3.1 Rpc流程
下图就是一个rpc的功能流程图:
- 客户端调用客户端存根(client stub)。这个调用是在本地,并将调用参数push到栈(stack)中。
- 客户端存根(client stub)将这些参数打包,并通过系统调用发送到服务端机器。(常见方式:XML、JSON、二进制编码)
- 客户端本地操作系统发送信息至服务器。(可通过自定义TCP协议或HTTP传输)
- 服务器系统将信息传送至服务端存根(server stub)。
- 服务端存根(server stub)拆包解析信息。
- 服务端存根(server stub)调用程序,并通过类似的方式返回给客户端。
1.3.2 开源的Rpc框架有哪些
- 特定语言的
- Dubbo:阿里开源的rpc框架,仅支持 Java 语言
- Motan:微博开源的rpc框架,仅支持 Java 语言
- Tars:腾讯开源的rpc框架,仅支持 C++ 语言
- Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言
- 跨语言的
- gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言
- Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言
2.设计一个基础的Rpc框架
我们在上面讨论的Rpc流程图,下面我们在讨论下一个Rpc具体要做哪些步骤呢?
如图所示:
2.1协议
Rpc协议属于应用层协议,协议的作用就是我们在发送请求时设定的一个边界,然后收到这个请求的时候按这个这个设定的边界进行数据分割。
http协议同属应用层协议,那我们不直接用http协议的原因主要是有两个:
- 性能问题,http协议的数据包大小相对于请求还要大的多,不符合高性能
- 无状态问题,http协议是无状态协议,客户端无法将请求和响应关联起来,每次请求都会重新建立连接
那既然我们用不了http协议,我们自己怎么设计一个协议呢?
一个协议可以分为协议头和协议体,协议头可以再分为固定协议头和扩展协议头,其中固定协议头中我们会放协议长度、序列化方式、消息ID、消息类型等,而扩展协议头则是留作备用
2.2通信
一次Rpc调用的本质就是调用方通过网路IO发送一条请求,提供方解析进行处理后,在通过一条响应消息返回给提供方,提供方接收解析响应数据。网络通信是Rpc的基础。
->名词解释:IO多路复用、Reactor模式(可以参考我的文章I/O数据模型和Reactor模式)
Rpc的使用场景一般都是高并发调用的,考虑内核支持,编程语言的支持以及IO模型的特点,在网络通信的处理上,我们一般都会使用IO多路复用的方式;在开发语言的网络通信框架的选择上,我们一般选择的实现了Reactor模式的的框架。如JAVA,我们就一般选择Netty。
我们可以通过零拷贝技术(可以参考我的文章IO处理时的零拷贝)来提高我们的通信效率。
2.3序列化
在网络传输中必须是二进制数据,但调用方请求和接收的时候都是对象,对象是不能直接在网络中传输的,所以我们就需要将对象序列化后进行传输,服务提供方根据请求类型和序列化方式正确的将二进制转为对象叫反序列化。
那序列化和反序列化方式有哪些呢?我们列举几个常用的:
方式 | 优点 | 缺点 |
---|---|---|
json | 应用广泛 | 序列化开销比较大 没有类型通过反射解决的时候性能不太好 |
hessian | 高效,比json生成的字节数更小,兼容性更高 | 对java一些常见对象的类型不支持,例如byte/short反序列化时会变成integer |
protobuf | 序列化后的体积比上述两种更小 反序列化速度快 | 不支持null 不支持单纯的map,list对象,需要包在对象里 |
那我们如何在rpc框架里选择序列化呢?
推荐按照下列顺序进行选择:安全性->通用性->性能->效率->空间开销
我们首选的还是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中 Hessian 在使用上更加方便,在对象的兼容性上更好;Protobuf 则更加高效,通用性上更有优势。
2.4代理
如何屏蔽复杂的Rpc调用细节呢?
要解决的是被调用的服务本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。我们就需要自动的给接口生成一个代理类,当我们在项目中注入接口的时候,实际绑定的是代理类,我们通过代理类来处理远程调用。
可以使用jdk提供的动态代理来实现,或者使用Byte Buddy、javassist等实现
2.5服务实例化
登记的服务有可能在我们的系统中就是一个名字,怎么变成实际执行的对象实例,就需要使用反射机制来获取请求的消息。