1.RPC的基础知识

什么是RPC

RPC的全称是Remote ProcedureCall,即远程过程调用,RPC屏蔽了底层网络通信相关的细节,实现调用远程方法和调用本地方法一样

RPC通信流程

1.RPC的基础知识

RPC协议

RPC协议设计

RPC协议的设计可以分为协议头和协议体
1.RPC的基础知识
协议头包含协议长度、序列化方式、协议标示、消息 ID、 消息类型等
协议体包含请求接口方法、请求的业务参数值、扩展属 性等

可扩展的协议设计

上面设计的协议都是固定的协议,如果想在这个协议中增加新的参数,那么就会有兼容性问题,那么就要设计一个可扩展的协议,如下所示:
1.RPC的基础知识
协议头里面增加了不固定部分

序列化

为什么需要序列化

从前面看到,RPC要通过网络传输数据,网路中的数据传输必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”。这时,服务提供方就可以正确地从二进制数据中分割出不同的请求,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象,这个过程我们称之为“反序列化”。
1.RPC的基础知识

JDK原生序列化

JDK自带的序列化机制对使用者而言是非常简单的,但是性能不好

JSON

JSON是典型的Key-Value方式,没有数据类型,是一种文本型序列化框架,但是JSON进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销

Hessian

Hessian是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian协议要比JDK、JSON更加紧凑,性能上要比JDK、JSON序列化高效很多,而且生成的字节数也更小,有非常好的兼容性和稳定性,所以Hessian更加适合作为 RPC 框架远程通信的序列化协议,但 Hessian 本身也有问题,官方版本对 Java 里面一些常见对象的类型不支持,比如:Linked系列、Locale类等等

Protobuf

Protobuf是Google公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf使用的时候需要定义IDL(Interfacedescriptionlanguage),然后使用不同语言的IDL编译器,生成序列化工具类,它的优点是列化后体积相比JSON、Hessian小很多;IDL能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似XML解析器;序列化反序列化速度很快,不需要通过反射获取类型;消息格式升级和兼容性不错,可以做到向后兼容。Protostuff 不需要依赖IDL文件,可以直接对Java领域对象进行反/序列化操作,在效率上跟 Protobuf差不多,生成的二进制格式和Protobuf 是完全相同的,可以说是一个Java版本的Protobuf序列化框架,当然,这个框架也有一定的问题不支持null,ProtoStuff不支持单纯的Map、List集合对象,需要包在对象里面。

RPC如何选择序列化

前面我们知道,RPC框架都要经过序列化和反序列化,那么性能就是我们要考虑的,数据包要在网络中传输,所以序列化后数据包的大小也是要考虑的,除了这些,序列化协议的通用性和兼容性是很重要的考虑因素,因为首先要支持很多类型的序列化,以及协议升级后,也不会出现问题,除了序列化协议的通用性和兼容性,序列化协议的安全性也是非常重要的一个参考因素,以 JDK 原生序列化为例,它就存在漏洞。如果序列化存在安全漏洞,那么线上的服务就很可能被入侵。
1.RPC的基础知识
通过上面的比较,首选的还是Hessian与Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中Hessian在使用上更加方便,在对象的兼容性上更好;Protobuf则更加高效,通用性上更有优势

RPC使用时,序列化常见的问题
对象构造得过于复杂

属性很多,并且存在多层的嵌套,对象依赖关系过于复杂.序列化框架在序列化与反序列化对象时,对象越复杂就越浪费性能,消耗CPU,这会严重响RPC框架整体的性能;另外,对象越复杂,在序列化与反序列化的过程中,出现问题的概率就越高.

对象过于庞大

比如为一个大List 或者大Map,序列化之后字节长度达到了上兆字节。这种情况同样会严重地浪费了性能、CPU,并且序列化一个如此大的对象是很耗费时间的,这肯定会直接影响到请求的耗时

使用序列化框架不支持的类作为入参类

比如Hessian框架,他天然是不支持LinkHashMap、LinkedHashSet等,而且大多数情况下最好不要使用第三方集合类

对象有复杂的继承关系

大多数序列化框架在序列化对象时都会将对象的属性一一进行序列化,当有继承关系时,会不停地寻找父类,遍历属性,这样会很容易出现性能上的问题

RPC网络模型选择

RPC网络IO模型选择

RPC调用在大多数的情况下,是一个高并发调用的场景,考虑到系统内核的支持、编程语言的支持以及IO模型本身的特点,在RPC框架的实现中,在网络通信的处理上,我们会选择IO多路复用的方式。开发语言的网络通信框架的选型上,我们最优的选择是基于Reactor模式实现的框架,如Java语言,首选的框架便是Netty框架

RPC中使用动态代理

我们在使用RPC的时候,直接是通过接口来调用的,通过接口调用就可以获取到想要的结果,接口是没法进行网络通信,解析结果等,那么对于实现这个的主要就是动态代理。RPC会自动给接口生成一个代理类,当我们在项目中注入接口的时候,运行过程中实际绑定的是这个接口生成的代理类。这样在接口方法被调用的时候,它实际上是被生成代理类拦截到了,这样我们就可以在生成的代理类里面,加入远程调用逻辑
1.RPC的基础知识