logo头像

小时候,哭是我们解决问题的绝招。长大后,笑是我们面对现实的武器。

RPC框架

前言

大概是三年前我开始接触RPC,那时作为一个刚入职场三年的新兵,在公司技术架构决策层上还没有什么发言权。一直沿用前辈搭建的技术架构来开发应用系统,当时还没有前后端分离,采用的框架是Spring MVC + JSP,每个应用服务都是一个war,服务间的调用是通过共享访问数据库的代码包(jar)来实现,现在想想那时的技术是真的Low,导致现在欠了很多的技术债,直到现在我们也还在努力的还债。

后来随着服务不断增加,而且服务之前还需要互相调用,这种方式导致维护的成本越来越高。这时领导才下定决心要重构技术架构,要引入微服务并进行前后端分离,这才拉开了服务化的进程,从那时起我们开始引入RPC框架。

本片文章我会带领大家来了解这几个方面的内容:什么是RPC,为什么要使用RPC以及常见的RPC框架。

RPC简介

RPC 是远程过程调用(Remote Procedure Call)的缩写形式,Birrell 和 Nelson 在 1984 发表于 ACM Transactions on Computer Systems 的论文《Implementing remote procedure calls》对 RPC 做了经典的诠释。RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行。调用方可以通过使用参数将信息传送给被调用方,而后可以通过传回的结果得到信息。而这一过程,对于开发人员来说是透明的。
RPC框架
上图描述了数据报在一个简单的RPC传递的过程

当两个物理分离的子系统需要建立逻辑上的关联时,RPC 是牵线搭桥的常见技术手段之一。除 RPC 之外,常见的多系统数据交互方案还有分布式消息队列、HTTP 请求调用、数据库和分布式缓存等。
image

其中 RPC 和 HTTP 调用是没有经过中间件的,它们是端到端系统的直接数据交互。HTTP 调用其实也可以看成是一种特殊的 RPC,只不过传统意义上的 RPC 是指长连接数据交互,而 HTTP 一般是指即用即走的短链接。

RPC 在我们熟知的各种中间件中都有它的身影。Nginx/Redis/MySQL/Dubbo/Hadoop/Spark/Tensorflow 等重量级开源产品都是在 RPC 技术的基础上构建出来的,我们这里说的 RPC 指的是广义的 RPC,也就是分布式系统的通信技术。RPC 在技术中的地位好比我们身边的空气,它无处不在,但是又有很多人根本不知道它的存在。

为什么要使用RPC

  1. 首先要明确一点:RPC可以用HTTP协议实现,并且用HTTP是建立在TCP之上最广泛使用的RPC,但是互联网公司往往用自己的私有协议,比如鹅厂的JCE协议,私有协议不具备通用性为什么还要用呢?因为相比于HTTP协议,RPC采用二进制字节码传输,更加高效也更加安全。
  2. 现在业界提倡“微服务”的概念,而服务之间通信目前有两种方式,RPC就是其中一种。RPC可以保证不同服务之间的互相调用。即使是跨语言跨平台也不是问题,让构建分布式系统更加容易。
  3. RPC框架都会有服务降级,流程控制的功能,保证服务的高可用。

RPC框架

要想了解一个RPC框架是如何实现的,首先要明白以下几点:

Call ID映射

我们怎么告诉远程机器我们要调用Multiply,而不是Add或者FooBar呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用Multiply,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <--> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

序列化和反序列化

客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。

网络传输

远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西

常用的RPC框架

常用的RPC框架在语言上支持Java的最多,golang次之

  1. Netty - Netty框架不局限于RPC,更多的是作为一种网络协议的实现框架,比如HTTP,由于RPC需要高效的网络通信,就可能选择以Netty作为基础。
  2. brpc是一个基于protobuf接口的RPC框架,在百度内部称为“baidu-rpc”,它囊括了百度内部所有RPC协议,并支持多种第三方协议,从目前的性能测试数据来看,brpc的性能领跑于其他同类RPC产品。
  3. Dubbo是Alibaba开发的一个RPC框架,远程接口基于Java Interface, 依托于Spring框架。
  4. gRPC的Java实现的底层网络库是基于Netty开发而来,其Go实现是基于net库。
  5. Thrift是Apache的一个项目(http://thrift.apache.org),前身是Facebook开发的一个RPC框架,采用thrift作为IDL (Interface description language)。
  6. jsonrpc

END

后面我会继续发文深入介绍gRPC框架和在工作中是如何应用gRPC框架的