Table of Contents:

开篇词 | 别老想着怎么用好RPC框架,你得多花时间琢磨原理

只要涉及到网络通信,我们就可能用到 RPC
在我看来,RPC 是解决分布式系统通信问题的一大利器。
这个系列的文章是有java代码讲解的。

01 | 核心原理:能否画张图解释下RPC的通信流程?

我理解的 RPC 是帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地(同一个项
目中的方法)一样的体验,我们不需要因为这个方法是远程调用就需要编写很多与业务无关 的代码。
我认为,RPC 的作用就是体现在这样两个方面:
屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法;
隐藏底层网络通信的复杂性,让我们更专注于业务逻辑

围绕 RPC 我们讲了这么多,那 RPC 在架构中究竟处于什么位置呢?
如刚才所讲,RPC 是解决应用间通信的一种方式,而无论是在一个大型的分布式应用系统
还是中小型系统中,应用架构最终都会从“单体”演进成“微服务化”,整个应用系统会被
拆分为多个不同功能的应用,并将它们部署在不同的服务器中,而应用之间会通过 RPC 进
行通信,可以说 RPC 对应的是整个分布式应用系统,就像是“经络”一样的存在。

RPC 框架能够帮助我们解决系统拆分后的通信问题,并且能让我们像调用本地一样去调用
远程方法。利用 RPC 我们不仅可以很方便地将应用架构从“单体”演进成“微服务化”,
而且还能解决实际开发过程中的效率低下、系统耦合等问题,这样可以使得我们的系统架构
整体清晰、健壮,应用可运维度增强。

适用场景:和服务器的交互比较多的情况

02 | 协议:怎么设计可扩展且向后兼容的协议?

这还要从 RPC 的作用说起,相对于 HTTP 的用处,RPC 更多的是负责应用间的通信,所以 性能要求相对更高。

那怎么设计一个私有 RPC 协议呢?

在协议头里面,我们除了会放协议长度、序列化方式,还会放一些像协议标示、消息 ID、
消息类型这样的参数,而协议体一般只放请求接口方法、请求的业务参数值和一些扩展属
性。这样一个完整的 RPC 协议大概就出来了,协议头是由一堆固定的长度参数组成,而协
议体是根据请求接口和参数构造的,长度属于可变的,具体协议如下图所示:

可扩展的协议:
刚才讲的协议属于定长协议头,那也就是说往后就不能再往协议头里加新参数了,如果加参
数就会导致线上兼容问题。
决绝方法就是消息头也是长度可变。

设计一个简单的 RPC 协议并不难,难的就是怎么去设计一个可“升级”的
协议。不仅要让我们在扩展新特性的时候能做到向下兼容,而且要尽可能地减少资源损耗,
所以我们协议的结构不仅要支持协议体的扩展,还要做到协议头也能扩展。

03 | 序列化:对象怎么在网络中传输?

有哪些常用的序列化?

RPC 框架中如何选择序列化?

序列化与反序列化过程是 RPC 调用的一个必须过程,那么序列化与反序列化的性能、效率、和序列化后的大小
势必将直接关系到 RPC 框架整体的性能和效率。

04 | 网络通信:RPC框架在网络通信上更倾向于哪种网络IO模型?

常见的网络 IO 模型

那说到网络通信,就不得不提一下网络 IO 模型。为什么要讲网络 IO 模型呢?因为所谓的
两台 PC 机之间的网络通信,实际上就是两台 PC 机对网络 IO 的操作。
常见的网络 IO 模型分为四种:同步阻塞 IO(BIO)、同步非阻塞 IO(NIO)、IO 多路复
用和异步非阻塞 IO(AIO)。在这四种 IO 模型中,只有 AIO 为异步 IO,其他都是同步 IO

RPC 调用在大多数的情况下,是一个高并发调用的场景,考虑到系统内核的支持、编程语
言的支持以及 IO 模型本身的特点,在 RPC 框架的实现中,在网络通信的处理上,我们会
选择 IO 多路复用的方式。开发语言的网络通信框架的选型上,我们最优的选择是基于
Reactor 模式实现的框架,如 Java 语言,首选的框架便是 Netty 框架(Java 还有很多其
他 NIO 框架,但目前 Netty 应用得最为广泛),并且在 Linux 环境下,也要开启 epoll 来
提升系统性能(Windows 环境下是无法开启 epoll 的,因为系统内核不支持)。

什么是零拷贝?

应用进程的每一次写操作,都会把数据写到用户空间的缓冲区中,再由 CPU 将数据拷贝到
系统内核的缓冲区中,之后再由 DMA 将这份数据拷贝到网卡中,最后由网卡发送出去。
这里我们可以看到,一次写操作数据要拷贝两次才能通过网卡发送出去,而用户进程的读操
作则是将整个流程反过来,数据同样会拷贝两次才能让应用程序读取到数据。

那怎么做到零拷贝?你想一下是不是用户空间与内核空间都将数据写到一个地方,就不需要
拷贝了?此时你有没有想到虚拟内存?
零拷贝有两种解决方式,分别是 mmap+write 方式和 sendfile 方式,其核心原理都是
通过虚拟内存来解决的。

05 | 动态代理:面向接口编程,屏蔽RPC处理流程

06 | RPC实战:剖析gRPC源码,动手实现一个完整的RPC