如何在 Python 和 Flask 中使用 IP API 查找地理位置?
gRPC 与 REST:API 开发方法的对比分析
在今天的文章中,将深入探讨 gRPC 和 REST,这可能是当今最常用的两种创建 API 的方法。
首先,将简要介绍这两种工具的特点——它们是什么以及能够提供哪些功能。随后,将根据七个现代系统中最关键的类别进行比较。
这些类别包括:
- 底层 HTTP 协议
- 支持的数据格式
- 数据大小
- 吞吐量
- 定义
- 易于采用
- 工具支持
API 的多样性与 gRPC 的优势
当提到 “API” 时,通常会首先想到 REST API。然而,REST 仅是构建 API 的众多方法之一,并不是适用于所有用例的灵丹妙药。还有其他方法,其中 RPC(远程过程调用)便是一个重要选项,而 gRPC 可能是使用 RPC 的最成功框架。
尽管 gRPC 是一种相对成熟且高效的技术,但仍然被视为新兴技术。因此,尽管在某些用例中非常实用,其采用程度仍不及 REST。
推广 gRPC 的主要目的在于强调它在特定用例中的巨大潜力。
什么是 REST?
REST,即 Representational State Transfer,是构建任何类型 API 应用程序的最常用方法之一。它以 HTTP 作为底层通信媒介,因此能够充分利用 HTTP 的优势,例如缓存功能。
REST 从一开始便是无状态的,允许客户端与服务器之间轻松分离。客户端只需了解服务器公开的接口即可进行有效通信,并不依赖于服务器的具体实现。客户端与服务器的通信基于请求和响应,每个请求都是标准的 HTTP 请求。
REST 不是一种协议或工具,而是一种构建应用程序的架构方法。遵循 REST 方法的服务称为 RESTFul 服务。这种架构对用户施加了几个约束,主要包括:
- 客户端-服务器通信
- 无状态通信
- 缓存
- 统一接口
- 分层系统
- 按需编码
REST 的两个关键概念是:
- 终端节点:表示特定资源的唯一 URL(统一资源定位器),可以视为通过 Internet 访问特定操作或数据元素的方式。
- 资源:特定 URL 下可用的具体数据。
此外,还有一个称为 Richardson 成熟模型的框架,该模型描述了 REST API 的“专业性”程度。根据特定 API 的特征集,REST API 被分为 3 个级别(或 4 个级别,视是否计算级别 0 而定)。
其中一个特征是 REST 端点应在 URL 中使用名词和适当的 HTTP 请求方法来管理其资源。例如,使用 DELETE user/1
而不是 GET user/deleteById/1
。
HTTP 方法及其关联的操作如下:
- GET — 检索特定资源或资源集合
- POST — 创建新资源
- PUT — 更新或插入整个资源
- PATCH — 对特定资源进行部分更新
- DELETE — 按 ID 删除特定资源
成熟度模型还有更多的特征,例如 HyperMedia 概念,它将数据的表示与客户端可以执行的操作控制结合在一起。
关于成熟度模型的详细描述超出了本博客的范围,相关信息可以在此处找到。
需要注意的是,本段中提到的许多内容比这里描述的更为复杂。REST 是一个广泛的主题,值得撰写更为详细的文章。尽管如此,文中所有内容都符合公认的最佳 REST 实践。
什么是 gRPC?
gRPC 是对较早 Remote Procedure Call 概念的现代实现,由 Google 开发,因此名称中带有 “g”。它是使用 RPC 的一种高效工具,且是 CNCF 孵化项目。
gRPC 使用 Google 的协议缓冲区作为序列化格式,同时采用 HTTP/2 作为数据传输媒介,尽管也可以使用 JSON 作为数据层。
gRPC 的基本构建块包括:
- 方法:gRPC 的基本单元,每个方法都是一个远程过程调用,接受输入并返回输出。当前,gRPC 支持四种方法:
- 一元:经典的请求-响应模型,接受输入并返回输出。
- Server Streaming:方法接受输入消息,同时返回消息流作为输出,保证单个 RPC 调用中的消息排序。
- 客户端流式处理:接受消息流作为输入,处理完所有消息后返回一条输出消息,同样保证消息排序。
- 双向流式处理:同时处理输入和输出流,两个流独立运行,保持流级别的消息排序。
- Service:表示一组方法,每个方法在服务中必须具有唯一名称,并描述安全性、超时或重试等功能。
- 消息:表示方法的输入或输出对象。
gRPC API 定义以 .proto 文件的形式编写,包含上述所有基本构建块。此外,gRPC 提供了协议缓冲区编译器,可以从 .proto 文件生成客户端和服务代码。
在服务器端方法的实现上,灵活性很高,但必须遵循 API 的输入输出约定。
客户端有一个称为 client(或存根)的对象,类似于 HTTP 客户端,能够识别来自服务器的所有方法,并处理远程过程调用及其响应。
gRPC 与 REST 的比较
底层 HTTP 协议
底层协议是比较 REST 和 gRPC 的重要方面,它的影响贯穿于其他类别。
REST 通常基于请求-响应模型,使用 HTTP/1.1 作为传输媒介。这要求每个请求-响应交换都需要独立的连接,对于长时间运行的请求或网络条件较差的情况,这可能导致性能问题。尽管可以使用 WebSocket 或其他持久连接来改善这种情况,但这需要额外的实现工作。此外,尽管 HTTP/2 能为 REST API 提供更好的性能,但并非所有服务器和库都支持。
与此相对,gRPC 专门基于 HTTP/2,允许通过单一 TCP 连接发送多个请求-响应对,这显著提升了应用程序的性能,尤其是在高并发场景下。
结果:gRPC 略胜。
支持的数据格式
在数据格式的支持方面,REST API 提供了更大的灵活性。
REST API 通常不限制消息格式,允许多种序列化为纯文本的格式,如 JSON 和 XML。JSON 是最流行的数据交换格式,XML 则因遗留应用的存在而仍然被广泛使用。当与 HTTP/2 一起使用时,REST 也可以采用二进制格式,如 Protobuf 或 Avro,这虽然有效,但可能会引入一些复杂性。
相较之下,gRPC 主要支持两种数据格式:默认的 Protobuf 和 JSON。虽然 JSON 支持使得与旧 API 的集成变得可能,但这需要更多的配置工作。
结果:REST 胜出,因为它支持更多格式。
数据大小
默认情况下,gRPC 使用二进制数据交换格式,这大大减小了通过网络发送的消息的大小:研究表明,字节大小减少了大约 40-50%,我之前的一个项目的经验表明,字节大小甚至减少了 50-70%。
上面的文章提供了 JSON 和 Protobuf 之间相对深入的大小比较。作者还提供了一个用于生成 JSON 和二进制文件的工具,因此,您可以重新运行他的实验并比较结果。
文章中的对象相当简单。尽管如此,一般规则是 — JSON 的嵌入对象越多,结构越复杂,与 Protobuf 相比,它就越重。有利于 Protobuf 的 50% 大小差异是一个很好的基线。
在使用 REST 的二进制交换格式时,可以最小化或消除差异。然而,它不是执行 RESTful API 的最常见也不是最受支持的方式,因此可能会出现其他问题。
结果:在默认情况下,gRPC 获胜;如果两者都使用二进制数据格式,则为平局。
吞吐量
同样,对于 REST,一切都取决于底层 HTTP 协议和服务器。
在默认情况下,基于 HTTP/1.1 的 REST,即使是性能最高的服务器也无法击败 gRPC 性能,尤其是当我们在使用 JSON 时增加序列化和反序列化开销时。尽管当我们切换到 HTTP/2 时,差异似乎会减小。
至于最大吞吐量,在这两种情况下,HTTP 都是一种传输媒介,因此它有可能扩展到无穷大。因此,一切都取决于我们使用的工具以及我们对应用程序的确切操作,因为设计没有限制。
结果:默认情况下,gRPC 胜出;在同时使用二进制数据和 HTTP/2 的情况下,gRPC 获胜或略胜。
定义
在这一部分中,我将描述我们如何在这两种方法中定义我们的消息和服务。
在大多数 REST 应用程序中,我们只是将请求和响应声明为类、对象或特定语言支持的任何结构。然后我们依靠提供的库来序列化和反序列化 JSON/XML/YAML,或我们需要的任何格式。
此外,目前正在努力创建能够基于 Swagger 的 REST API 定义以所选编程语言生成代码的工具。但是,它们似乎处于 alpha 版本中,因此仍然可能会出现一些错误和小问题,这将使它们难以使用。
REST 应用程序的二进制和非二进制格式之间几乎没有区别,因为在这两种情况下的规则或多或少相同。对于二进制格式,我们只需以特定格式所需的方式定义所有内容。
此外,我们还通过底层库或框架中的方法或注释定义了我们的 REST 服务。该工具还负责将其与其他配置一起公开给外部世界。
对于 gRPC,我们将 Protobuf 作为默认方法,实际上是编写定义的唯一方法。我们必须在 .proto 文件中声明所有内容:消息、服务和方法,因此问题非常简单。
然后我们使用 gRPC 提供的工具为我们生成代码,我们只需要实现我们的方法。之后,一切都应该按预期工作。
此外,Protobuf 支持导入,因此我们可以以相当简单的方式将设置分散到多个文件中。
结果:这里没有赢家,只有我的描述和提示:选择最适合您的方法。
易于采用
在这一部分中,我将比较现代编程语言中每种方法的库/框架支持。
一般来说,在我作为软件工程师的短暂职业生涯中,我遇到的每种编程语言(Java、Scala、Python)都至少有 3 个主要库/框架用于创建类似 REST 的应用程序,更不用说用于将 JSON 解析为对象/类的类似数量的库了。
此外,由于 REST 默认使用人类可读的格式,因此对于新手来说,它更容易调试和使用。这还会影响交付新功能的便利性,并帮助您解决代码中出现的错误。
长话短说,对 REST 风格的应用程序的支持至少非常好。
在 Scala 中,我们甚至有一个叫做 tapir 的工具 — 我有幸成为它的维护者之一。Tapir 允许我们抽象我们的 HTTP 服务器并编写适用于多个服务器的端点。
gRPC 本身为超过 8 种流行的编程语言提供了客户端库。这通常就足够了,因为这些库包含制作 gRPC API 所需的一切。此外,我知道一些库为 Java(通过 Spring Boot Starter)和 Scala 提供了更高的抽象。
另一件事是,REST 今天被认为是一个全球标准和构建服务的切入点,而 RPC 和 gRPC,尽管在这一点上有些过时,但仍然被视为一种新奇事物。
结果:REST,因为它被更广泛地采用,并且有更多的库和框架。
工具支持
上面介绍了库、框架和一般市场份额,因此在这一部分中,我想介绍围绕这两种风格的工具。它意味着用于测试、性能/压力测试和文档的工具。
自动化测试/测试
首先,在 REST 的情况下,用于构建自动化测试的工具内置于不同的库和框架中,或者是专为此目的构建的独立工具,例如 REST-assured。
在 gRPC 的情况下,我们可以生成一个存根并将其用于测试。如果我们想要更严格,我们可以将生成的客户端用作单独的应用程序,并将其用作我们在真实服务上进行测试的基础。
至于 gRPC 的外部工具支持,我知道:
- Postman 应用程序对 gRPC 的支持
- 在其 IDE 中使用的 JetBrains HTTP 客户端也可以通过一些最少的配置支持 gRPC
结果一:REST 的胜利;然而,gRPC 的情况似乎有所改善。
性能测试
在这里,REST 具有显著的优势,因为 JMeter 或 Gatling 等工具使 REST API 的压力测试成为一项相当简单的工作。
不幸的是,gRPC 没有这样的支持。我知道 Gatling 的人在当前的 Gatling 版本中包含 gRPC 插件,所以情况似乎越来越好。
然而,到目前为止,我们只有一个名为 ghz 的非官方插件和库。所有这些都是好的;它只是与 REST 的支持级别不同。
结果二:REST 获胜;然而,gRPC 的情况似乎有所改善。
文档
就 API 文档而言,REST 再次取得了胜利,OpenAPI 和 Swagger 在整个行业中被广泛采用,并成为事实上的标准。几乎所有的 REST 库都可以以最少的工作量或开箱即用的方式公开 swagger 文档。
不幸的是,gRPC 没有这样的功能。
但是,问题是 gRPC 是否需要这样的工具。gRPC 的设计比 REST 更具描述性,因此其他文档工具可能会。
一般来说,带有 API 描述的 .proto 文件比负责制作 REST API 代码的代码更具声明性和紧凑性,因此可能不需要来自 gRPC 的更多文档。答案留给你。
结果三:REST 获胜;但是,gRPC 文档的问题是开放的。
总体结果:REST 的重大胜利。
总结
最终的比分表如下所示。
两种风格的比较结果平分秋色:每种风格各赢得了三个类别,还有一个类别没有明确的胜者。
没有一种技术是万能的,关键在于根据应用的需求来选择。在做决定时,建议优先考虑哪些类别对应用程序最重要,然后选择在这些类别中表现最优的方法。
关于个人偏好,如果有机会,可以倾向于尝试 gRPC,因为在之前的项目中它表现得非常出色,可能比传统的 REST 更适合某些场景。
如果在选择 REST 还是 gRPC 时需要帮助,或者在其他技术问题上遇到困难,随时可以提供帮助。