简单谈谈RPC、RestFul和GraphQL

covert

REST和RESTFul

其实无论是RPC、RESTFUL、还是GraphQL,都和REST这个词有千丝万缕的关系的。所以在文章的开篇,先简单介绍下REST和RESTFul。REST和RESTFul这两个词经常会出现在开发者的眼中,我相信很多开发者都不知道它们的区别是什么,也有很多开发者认为REST就是RESTFul。那么它们真的就是一个东西吗?下面我们就来探究下它们之间的关系。

REST

REST的全称是:Representational State Transfer ,它的直接中文翻译是:表现层状态转移。这个词,基本上没什么人能直接看懂它表达的意思。从系统架构的角度上来讲,它表示一种分布式超媒体系统的架构风格。也就是说,它代表一种系统架构的风格。在这里我们不深入讨论REST架构是怎样的,我们只需要知道,现在大部分人接触的前后端分离的架构一般都是REST风格的就行了。

RESTFul

那RESTFul又是什么呢?RESTFul是指:基于REST架构风格的Server端为其它组件(这个其它组件可以是前端应用,也可以是其它后端系统)提供交互的一个统一接口。这样做的目的是,简化整体系统架构,改善交互的可见性。也避免了Server端组件的内部逻辑泄露到其它组件的可能性。实现Server端与它提供的服务解耦的目标,这使组件之间独立演进成为了可能。

从架构角度来看,我们所说的解耦一般被会理解为:前后端解耦,又或者后端服务和后端服务之间的解耦。但是从REST的架构上来说,它其实是指:Server端与它提供的服务解耦。这样带来的好处是:提供的服务是可信任的,不会因为Server的演变而发生改版。这是Rest架构的一个非常重要的特点。

简单来说,我们最常见的基于HTTP协议的服务端接口、API等,就是我们说的RESTFul接口。

RPC

RPC的全称是Remote Procedure Call,即远程过程调用,与之对应的是本地过程调用。为了方面大家理解什么是RPC,我们先通过简单的代码来看看本地过程调用是怎样的。

1
2
3
4
int x = -100;
int y = -99;
int result = Math.max(x, y);
System.out.println("max = " + result);

调用Math.max就是一个本地过程调用。即通俗来讲,我们调用我们本地代码中的提供的一些API的过程,就称为本地过程调用。

远程过程调用,最终调用的API不是运行在本地的。这个提供服务的程序有可能是运行在不同进程,甚至是不同机器上的。RPC的调用过程从代码上来看,和我们调用本地的代码其实是非常像的。我们可以尝试使用伪代码实现一个RPC的max函数。

1
2
3
4
5
int x = -100;
int y = -99;
RPCMath.max(x, y, { result ->
System.out.println("max = " + result);
});

可以看到,RPC的调用过程和调用本地代码其实是有点像的,我们可以像调用本地代码一样调用我们远程服务的代码。唯一的区别就是,本地过程调用可以是同步的,也可以是异步的,但是远程过程调用一般都是异步的。因为本地过程调用我们能确认这个“过程”是否是耗时的,而远程过程调用,这个“过程”一定是耗时的。一般来说,我们耗时的任务都会倾向于使用异步调用。但是这也不是一定的,因为我们也可以阻塞等待结果,这样其实也是可以的。

RPC调用一般是CS模型的,整个调用过程可以概括为:

Client发起调用 -> 通过中转层发送到Server端 -> Server处理 -> 把结果通过中转层发送给Client

图示如下:

RPC

这里的中转层一般来说是通过网络实现(TCP、Socket、也有可能是HTTP),也有可能是夸进程通信中使用的系统服务,如Android的Binder机制其实也是一种RPC调用。

RPC一般是通过约定协议来实现夸端通信的。双方会遵守同一套协议,客户端会通过协议把需要调用的服务告诉服务端,然后服务端运算完毕后,再通过协议把结果回传给客户端。业内比较知名的RPC框架是Google公司的grpc,grpc是使用ProtoBuf(接口描述语言Interface Definition Language,IDL)定义双方的通信协议的。总的来说,RPC调用虽然看起来很像调用我们代码库里面的代码,但是最终的处理单元却是运行在另一个的进程甚至是另一台计算机中的。

HTTP协议下的RESTFul

前面我们简单讨论过REST和RESTFul的关系,现在我们来探讨下,我们经常使用的基于HTTP协议的RESTFul又是如何工作的。

经过上文的讨论,现在我们知道了,RESTFul其实是指REST架构下的一种用于提供资源的接口。在平时访问远程接口时,我们一般会通过统一资源定位符(URI)来定位资源的位置,建立连接后再通过HTTP协议来传输数据。整个过程可
以用下图来展示。

RESTFul

可以看到,在RESET架构下,Client会通过URI来确定自己需要请求的资源。建立连接后,会通过HTTP来发送/接收数据。Client的请求目标通过统一接口访问是资源(Resource),而不是Server本身。如果你理解REST的话,你会大概看懂这句话的意思,如果你不太理解REST也没关系。我们对比下上面讨论到的RPC风格的接口和RESTFul接口就能大概理解了。

RPC和RESTFul的区别

我们回顾下可以发现,RPC是通过双方约定协议来实现通信的。如果协议改变了,那么Client和Server的代码都需要作出改变。不然很容易会导致通信失败,甚至会编译失败。grpc就是一个典型,在proto协议文件改变的情况下,客户端和服务端的代码都需要更新,否则会有可能编译失败。这种编译失败也是一种安全机制,可以避免由于协议更新导致的线上故障。总的来说,RPC是一种强耦合的模型。

而REST架构,是一种松耦合的模型。在调用RESTFul接口的时候,我们不需要关心服务端代码的实现,Client和Server只需要按照接口文档的定义来开发即可。接口文档可以看作是对资源的描述。RESTFul资源增加字段是不会影响其它Client的调用的,但是如果要删减字段的时候则需要更谨慎一些。RESTFul架构通过能帮助我们进一步实现前后端分离,使前端和后端能分别独立演进(和RPC的区别)。

RESTFul的统一接口简化了整体的系统性和也改善了交互的可见性,实现了资源与它们所提供的服务解耦。而RPC由于是建立在双方所约定的协议的技术上的,所以耦合度会更高。

什么是GraphQL?

GraphQL这种技术大部分都是流行在前端领域,后端的同学会比较少接触。那为什么GraphQL会让大部分前端,客户端开发沉迷呢?先看看GraphQL官网自己的介绍:

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

从这句话来看,GraphQL的主要功能是让客户端的同学查询自己想查询的数据。简单来说,就是客户端不用天天求着后端帮忙改接口了。举个例子:

我们现在想请求用户的个人信息,现在和后端约定好,需要name、id、address字段。双方开发完后,产品问:我们能不能(必须)也把用户的年龄也显示出来啊。这个时候,前端就会去请求我们的后端同学帮忙加一下了。

如果GraphQL能实现它介绍的功能的话,下面这个场景会变成这样。

前端想请求用户的个人信息,主要包含name、id、address。现在不需要前端和后端约定的东西了,只需要前端告诉后端的服务器想要什么数据就行了。前端可以写出下面的graph查询语言:

1
2
3
4
5
user(userId:"11111"){
name
id
address
}

这样我们就能得到需要的数据。如果产品现在想把年龄也显示出来,那我们只需要这样写就行了:

1
2
3
4
5
6
user(userId:"11111"){
name
id
address
age
}

GraphQL对自己的定义非常明晰:一种用于 API 的查询语言。它能让客户端脱离后端的接口,实现按需获取数据。

为什么需要GraphQL?

GraphQL更多是用来解决前端的痛点的,为什么这么说呢?因为现代Web系统大部分是基于REST架构风格的,所以各个子系统和前后端之间的独立演进是非常方便的。但是因为前后端的特殊性,这就导致了一个现象:后端的核心业务往往是稳定运行的,但是前端页面的变化是非常迅速的。

这样导致的结果就是,后端的核心业务代码往往是很稳定的。但是为了适应前端页面的各种变化,后端需要频繁的开发变更各种接口,以适应前端业务的快速变化。导致这个现象的原因有很多,其中RESTFul的接口被滥用是非常重要的一点。

被滥用的RESTFul

为什么说RESTFul被滥用了呢?因为在一个大型系统中,在做一个新业务的时候,往往会考虑创建一个新的接口。这样做的目的大部分是为了不让新业务影响到旧业务。但是一旦这种接口多起来,那么接口(这里指RESTFUL接口,下同)的管理会变成一件十分令人抓狂的工作。新增接口很简单,但是什么时候要废弃一个接口就是一个难以决策的事情了。如果不废弃的话,那系统中就会充满各种为“一次性页面(如时限性的活动页面)”服务的接口,各种个样的聚合接口。甚至会出现,接口与接口之间相互耦合的可怕结果。

当然,我们可以根据接口的流量来决定,当接口的流量降到一定的程度的时候,我们就废弃这个接口。但是什么时候流量才算是较低呢?这又是一个问题。

通过分析,我们知道,后端核心系统的接口往往是数量很少并且非常稳定的。后端系统之间能通过少量的接口实现系统与系统之间的交互。后端和客户端/前端之间的交互往往会产生大量不太稳定的接口,而这些接口对后端来说不是必须的,对前端来说是必须的。需要快速增加/变动各种接口这种痛点,其实更多实在前端同学的身上。

所以GraphQL一般都是前端的同学先了解,并且倾向于尝试。

GraphQL的作用

GraphQL和REST架构风格是不冲突的,我们可以看作在REST架构中再介入薄薄的一层,专门用来处理数据查询。如下图:

RESTFul

GraphQL有一个统一的入口,它只有一个接口,但是这个接口非常聪明,能按需返回你想要的数据。它会根据访问者的需要来返回数据,不会缺少,也不会像聚合接口这样,返回一堆你不太需要的数据。

使用GraphQL时,我们一边会有三个步骤:

  • 描述你的数据
  • 请求你的数据
  • 得到可预测的结果

简单来说,搭建完GraphQL体系后,前端就不再需要频繁需要后端增加、更新、聚合接口。当然,这也要求前端工程师更了解后端的业务体系。还需要后端工程师帮我们搭建好能有助于我们构建GraphQL的后端体系。

这是一种前期投入大,产出低,但是后期潜力和价值都非常高的技术。

如何去选择

GraphQL和RPC、RESTFul有什么不同的地方呢?它们最大的不同就是架构上的不同,我们在分析RPC和RESTFul的时候可以发现,它们是和我们Web后端系统的整体架构是息息相关的。但是GraphQL不一样,它是在我们的前端和后端架构中加多薄薄的一层,这一层的作用是让我们能按需查询数据。

那么那一种更好呢?从技术上来说,这几种技术都各有优缺点。

RPC RESTFul GraphQL
优点 速度快 接入简单 查询方便,按需查询
缺点 耦合度高 接口管理问题 会增加架构复杂度

我们又要如何选择呢?这问题的答案是离不开我们的应用场景的。在稳定的系统核心服务的交互中,我们可以用RPC,这样能获取最高的性能。在小型Web系统中,我们使用RESTFul来实现前后端通信也是可行的。而在大型Web系统中,我们可以在整体架构中加多一层GraphQL层,可以达到更高的开发效率。

它们是对立的吗?

看到这里可能有人会有一些疑问,这三种技术看起来都是运行在不同的层级的,那它们是对立的关系吗?是不是选择了其中一种,就意味着必须要放弃其它的方案?

其实它们并不是对立的,甚至如果你想,你可以同时使用它们。举个例子,一般情况下,我们使用了RPC就不会再使用RESTFul。但是上文我们也说过,RESTFul是指REST架构风格的服务器提供的API,只是一般来说,我们会基于HTTP协议去实现它(最佳实践)。但是有一些特殊情况,我们既需要追求高性能的同时,又不想直面RPC的缺点,那么我们可以基于RPC实现一些RESTFul风格的接口。有些追求高性能的移动客户端就是这样做的。一般前端要使用RPC和后台通信的话,比较好的方案是建立一层防腐层,避免RPC定义的一些协议直接侵入到前端的代码中。这样也能达到松耦合的目的。因为REST是一种架构风格,所以它们并不是绝对对立的。

而GraphQL更是能直接使用我们原来的接口,我们可以通过搭建一个GraphQL服务器。这个服务器的作用就是聚合已经上线的RPC,RESTFul接口,最终提供一个GraphQL数据查询的服务,具体细节这里就不细谈了。最后,有一点大家需要注意,GraphQL并不是说直接让前端去查询后端数据库的(当然,你也可以这样做),在技术上,有更多更好的方案。

写在最后

本文主要是介绍性质的,所以就不在这里探讨如何去实施/使用这几种技术了。如果对RPC感兴趣可以看看Google的grpc;如果的GraphQL感兴趣,看FaceBook的官方文档就可以了。GraphQL的社区热度非常高,社区比较优秀的框架是Apollo系列。

如果你希望了解更多

以上项目如果你感兴趣或者对你有用的话,可以点一下star,Thanks。

Tang wechat
如果希望及时收到更新,请长按或扫描上方微信二维码
你的支持,是我坚持的动力👍