理解RESTful API和gRPC:原理、技术、风格及应用对比

网络API的概念伴随着C/S和分布式程序设计的发展而出现,早期RPC协议可以追溯到1974年的RFC674,它尝试定义在网络中共享资源的通用方法,拉开了RFC的序幕。随着Web应用的发展,REST以更加灵活的架构风格出现,它以资源为核心,将操作映射到HTTP方法,通过URL定位资源,以极强的兼容性和易于实现的特性成为了Web API的实施标准。

侧重于状态转换的REST在处理复杂数据查询和前端需求频繁变化时表现出了明显的不足:REST接口通常是固定的,前端需要多个接口来获取不同的数据,这就可能导致多次请求和数据冗余。面对这一问题,Facebook在2015年推出了API查询语言GraphQL,允许客户端只请求所需的数据,从而优化数据检索并提高效率。

而gRPC由Google于2015年开发并开源,正式版本1.0发布于2016年8月,为了解决在微服务架构和分布式系统中服务间通信的性能问题,使用了Protocol Buffers作为序列化机制,基于HTTP/2支持多路复用,相对于RESTful API使用的JSON/XML格式和HTTP/1.1拥有更小的数据体积和更低的处理延迟。

网上有很多关于API风格的介绍和对比,出于近期的学习需求,本文着重关注RESTful API和gRPC在原理、技术、风格和应用场景等方面的实现和比较。

RESTful API

定义

REST全称Representational State Transfer,表述性状态转移。2000 年,Roy T. Fielding 在博士论文Architectural Styles and the Design of Network-based Software Architectures中正式引入这一概念。

首先需要明确的是REST并没有提出新的技术,而是一种用于构建网络应用程序的软件架构风格,即RESTful是一种风格而不是一种标准

在现代网络实践中,REST将网络中的一切事物都视为资源,每个资源都有唯一的标识符(通常是URL)。客户端通过 HTTP 请求方法(如 GET 用于获取资源、POST 用于创建资源、PUT 用于更新资源、DELETE 用于删除资源)对这些资源进行操作,服务器根据客户端的请求返回相应的资源表述,通常以 JSON、XML 等格式呈现。

(准确的说REST并不基于HTTP,它可以是任意的实现和URI,不过在我们的互联网中HTTP是其唯一的实例)

核心规范

1. 资源

  • 资源抽象:把所有事物抽象成资源,每个资源对应一个唯一的标识符,URI应当具有层级结构
  • 资源命名:使用名词来表示资源而不是动词,例如使用GET /system/users/1​而非GET /system/getUsers/1

2. 接口

  • 操作表示:使用HTTP method表示操作,确保资源操作的表示和限制

    GET /articles/1 获取资源
    POST /articles 创建资源
    DELETE /articles/1 删除资源
    
  • 请求结果:使用HTTP状态码表示请求结果

  • 数据载体:使用JSON或XML表示数据,大多数时候是JSON

  • 版本管理:应使用版本号来管理API以支持旧版API兼容性,例如GET /v1/users/1

3. 分层

  • 系统分层架构:RESTful API 可以采用分层的架构,每一层都有特定的职责
  • 透明性:客户端无需关心其他系统层次是如何处理业务的

特性

  • 无状态:服务器不会保存客户端的状态信息,每个请求都要包含足够的信息以便服务器能独立处理该请求
  • 可扩展:当需要添加新功能或资源时,只需按照统一的接口规范增加新的URI和对应的操作方法
  • 可读性:通过使用直观的 URI 命名和标准的 HTTP 方法,开发人员可以很容易地理解每个请求的目的和作用

gRPC

定义

gRPC 是一个高性能、开源的远程过程调用(RPC)框架。2015 年,Google 将其内部长期使用的 RPC 框架 Stubby 进行了开源和改进,正式推出了 gRPC,由云原生计算基金会(CNCF)管理。

gRPC 基于 HTTP/2 协议,客户端和服务器可以在各种环境中运行并相互通信。客户端和服务器可以使用 gRPC 支持的任何语言编写,如 C++、Python、Java、C# 等。gRPC 通过 Protocol Buffers 进行数据的序列化和结构化,Protocol Buffers 是一种二进制格式,相比 JSON/XML 等序列化文本,具有更高的编码和解码效率,且序列化后的数据体积更小,传输速度更快,因此更加高效。

gRPC概述

核心原理

1. HTTP/2

  • gRPC 基于 HTTP/2 协议进行通信

2. 服务定义与序列化

  • 使用 Protocol Buffers(protobuf)来定义服务接口和消息类型。在.proto 文件中,开发者可以清晰地定义服务的方法、输入参数和输出结果

    syntax = "proto3";
    
    service Greeter {
        rpc SayHello (HelloRequest) returns (HelloResponse) {}
    }
    
    message HelloRequest {
        string name = 1;
    }
    
    message HelloResponse {
        string message = 1;
    }
    
  • Protocol Buffers 用于数据的序列化和反序列化,它将结构化的数据转换为二进制格式,在网络上进行传输

3. 远程过程调用

  • 客户端通过生成的 gRPC 客户端 stub 来调用服务器端的方法,就像调用本地方法一样。当客户端调用方法时,gRPC 框架会将参数序列化为二进制数据,通过 HTTP/2 连接发送到服务器
  • 服务器接收到请求后,进行反序列化,执行相应的方法,并将结果序列化后返回给客户端。客户端再将返回的结果反序列化,提供给应用程序使用

4. 服务端与客户端流

gRPC 支持多种交互模式,允许客户端和服务器之间进行双向的数据流传输:

  • 一元:客户端请求 - 服务端响应 模式
  • 服务器流:客户端发起请求,服务器响应一个消息流,全部数据发送完毕后服务器再发送一条状态消息表示完成
  • 客户端流:客户端向服务器发送一个消息流,并接受单个响应消息
  • 双向流:客户端和服务器建立两个独立的流,他们可以任何顺序传输消息。客户端负责发起并结束双向流

gRPC流类型

5. 拦截器与中间件

  • gRPC 提供了拦截器机制,允许开发者在客户端和服务器端插入自定义的逻辑,例如日志记录、认证授权、数据校验等。拦截器可以在方法调用前后执行特定的操作,对请求和响应进行处理,从而实现通用的功能逻辑,而无需在每个具体的服务方法中重复编写代码。

特性

  • 高性能:HTTP/2 具有多路复用、头部压缩、二进制分帧等特性,能够在一个连接上同时处理多个请求和响应,提高了通信效率,减少了延迟,并且可以更好地利用网络带宽。同时,使用二进制格式的 Protocol Buffers 进行数据序列化,比 JSON、XML 等文本格式序列化和反序列化速度更快,数据体积更小
  • 跨语言支持:Protocol Buffers定义具有语言无关性,支持多种编程语言,使得不同语言开发的客户端和服务器能够基于相同的接口定义进行通信
  • 强类型定义:通过 Protocol Buffers 清晰地定义服务的方法、输入参数和输出结果,提供了强类型的接口定义
  • 易于使用和集成:gRPC 提供了简洁的 API 和工具,使得开发者能够相对容易地定义服务和实现客户端与服务器端的代码

技术对比

  • gRPC通常性能更高,适合高效通信和严格的接口定义场景,常用于软件客户端内部通信
  • RESTful往往更易于理解和更广泛兼容(浏览器都支持HTTP),适合简单的HTTP通信和公共API,特别是与第三方程序进行交互的API
特性gRPCRESTful
文件格式Protocol Buffers(.proto)JSON、XML等
性能较低
通信方式二进制文本
支持的协议HTTP/2、gRPC自身协议HTTP/1.1、HTTP/2
类型安全
语言支持多种编程语言(Java、C++、Go、Python等)所有支持HTTP的语言
服务发现和负载均衡内置支持需要额外工具(如Consul、Eureka等)
序列化速度较慢
流式支持内置支持服务端和客户端流式通信不直接支持,需要使用WebSocket等
描述方式强类型定义的接口(.proto文件)自由结构化,不强制类型
集成与兼容容易与Google生态系统中的其他工具集成广泛兼容和集成,可以与大多数系统和服务协作
版本控制通过提升.proto文件版本号通过URL路径管理或使用头信息进行版本控制

参考阅读