Go大师课程系列将学习
为什么选择 gRPC?
让我们从这个简单的问题开始:gRPC 的动机是什么?或者说 gRPC 试图解决的问题是什么?
嗯,答案就是沟通。
应用程序使用不同的编程语言编写。
例如,后端可以用 Go 编写,而前端(例如,Android 应用程序用 Java 编写,iOS 应用程序用 Swift 编写)
则如何相互通信?
当今的趋势是使用微服务架构。因此,即使在后端,我们也可能有许多用不同语言(如 Go、Python 或 Rust)编写的服务,具体取决于业务需求和技术限制。
因此,为了相互通信,他们必须就一组 API 契约达成一致。例如:通信渠道、身份验证机制、有效负载格式、数据模型以及如何处理错误。
有很多事情需要考虑,这就是为什么构建 API 非常困难。
不仅如此,我们希望通信高效,也就是说,快速且轻量。你知道,微服务之间的交换消息数量非常庞大。因此,通信越快越好。
此外,在某些环境中,例如移动应用程序,网络速度和带宽有限,拥有轻量级的通信协议来与服务器交互非常重要。
最后但并非最不重要的一点是,我们希望通信简单。假设我们正在构建一个包含数百甚至数千个微服务的系统。我们肯定不想花费大部分时间编写代码只是为了让它们相互通信,对吧?
我们希望有某种框架,让开发人员专注于实现其服务的核心逻辑。其余一切都交给框架处理。
什么是 gRPC?它是如何工作的?
gRPC 是一个高性能、开源、功能丰富的 RPC 框架,最初由 Google 开发,现在是云原生计算基金会(或 CNCF)的一部分,就像 Kubernetes 或 Prometheus 一样。
好的,那么g
gRPC 中的"什么"代表什么?起初我以为是 Google(是的,你知道为什么)。但事实上,它在每个 gRPC 版本中代表不同的东西,例如"好"、"绿色"、"辉煌",甚至"gRPC"本身。
那么 RPC 怎么样?RPC 代表远程过程调用。它是一种允许程序执行位于其他计算机的另一个程序的过程的协议。
最棒的是,开发人员无需明确编写网络交互的细节。这些都由底层框架自动处理。
因此,在客户端代码中,我们似乎只是直接调用服务器代码的函数。即使客户端和服务器上的代码是用不同的编程语言编写的,它也能正常工作。就像在这个例子中,客户端代码是用 Go 编写的,而服务器代码是用 Rust 编写的。
那么 gRPC 如何做到这一点?基本上,客户端有一个存根,它提供与服务器相同的方法(或功能)。该存根由 gRPC 自动为您生成。
存根将在后台调用 gRPC 框架通过网络与服务器交换信息。
多亏了存根,客户端和服务器现在只需要关心实现其核心服务逻辑。
gRPC 如何生成代码?
代码生成是 gRPC 最重要的功能之一。
为了为服务器和客户端生成存根,我们首先需要编写 API 契约,其中包括协议缓冲区文件中服务及其有效负载消息的描述,如下所示:
在这个文件中,定义了一个 Hello 方法,该方法以 HelloRequest 作为输入并返回 HelloResponse。HelloRequest 只包含一个字符串 name,HelloResponse 有一个字符串greet。
很简单,对吧?我们将在后面的 protobuf 实践讲座中学习更多有关此内容的详细信息。
从此 proto 文件中,协议缓冲区编译器 (或 protoc) 会生成服务器和客户端存根代码。根据编程语言,我们必须告诉编译器为其使用正确的 gRPC 插件。
Rust 和 Go 生成的代码如下所示:
好的,那么你可能想知道为什么 gRPC 使用协议缓冲区?嗯,原因有很多。
首先,它非常容易阅读和理解。
其次,它是一种可互操作的语言,支持多种语言的自动代码生成
第三,它以二进制格式表示数据,与 JSON 或 XML 等某些基于文本的格式相比,二进制格式体积更小、传输速度更快、序列化效率更高。
它在客户端和服务器之间提供了强类型的 API 契约,使用起来非常安全。
并且它有一套完善的 API 演进规则,以确保向后和向前的兼容性。
gRPC 的类型
gRPC 有 4 种类型:
最简单的是 Unary,客户端发送 1 条请求消息,服务器回复 1 条响应。这看起来有点类似于普通的 HTTP REST API。
然后我们有客户端流。在这种情况下,客户端将发送多条消息的流,并期望服务器仅发回 1 个响应。
类似地,我们有服务器流,其中客户端只发送 1 个请求消息,而服务器使用多个响应流进行回复。
最后是双向(或 bidi)流。这是最复杂的,因为客户端和服务器将继续并行地以任意顺序发送和接收多条消息。它非常灵活且无阻塞,这意味着任何一方都无需等待响应即可发送下一条消息。
这是对 gRPC 中 4 种不同通信方式的非常高层次的概述。
gRPC 与 REST
现在,让我们快速比较一下 gRPC 和 REST,看看它们的区别。
首先,gRPC 使用 HTTP/2,众所周知,它比 REST 默认使用的 HTTP/1.1 快得多。请注意,今天我们也可以在 REST 中启用 HTTP/2,但通常它通常与 HTTP/1.1 一起使用。您可以在以下文章中了解有关如何为 REST 启用 HTTP/2 的更多信息:
其次,gRPC 使用 Protocol buffer 来序列化 payload 数据,它是二进制且较小,而 REST 使用 JSON,它是文本且较大。
gRPC 中的 API 契约非常严格,需要在 proto 文件中明确定义。而在 REST 中,它通常是松散且可选的。我们可以通过 OpenAPI 定义它,但这不是强制性的。
gRPC 内置了代码生成功能,借助 protocol buffer 编译器。而在 REST 中,我们必须使用第三方工具,如 OpenAPI 和 Swagger。
gRPC 和 REST 通信均通过 TLS/SSL 保护。
在 gRPC 中,流式传输是双向的,而在 REST 中,从客户端到服务器的请求只有单向。
因此,在我们目前提到的大多数方面,gRPC 都比 REST 更好。不过,REST 仍然有一点更好,
这是浏览器支持。虽然所有浏览器都完全支持 REST,但对 gRPC 的支持有限,并且需要带有代理层的gRPC-web在 HTTP/1 和 HTTP/2 之间进行转换。
在哪里使用 gRPC?
因此,gRPC 有很多优点,但也有自己的弱点。那么,我们应该在何时何地使用 gRPC 才能充分利用它呢?
您可能已经猜到了,微服务是 gRPC 真正大放异彩的地方,因为它可以实现低延迟和高吞吐量通信,以及强大的 API 契约。
gRPC 也适用于多语言环境,因为它为许多编程语言提供了开箱即用的代码生成
点对点实时通信也是 gRPC 的优势所在,因为它对双向流式传输有出色的支持。
最后,由于 gRPC 具有轻量级消息格式,因此对于网络受限的环境(例如移动应用程序(android/ios))来说,它是一个很好的选择。
为什么选择 HTTP/2?
gRPC 使用 HTTP/2 作为其传输协议,因此它继承了 HTTP/2 提供的一些出色功能,例如二进制帧,与其他基于文本的协议相比,它性能高、稳健、传输更轻松、解码更安全。而且由于它是二进制的,因此它与协议缓冲区完美结合。
HTTP/2 还使用 HPACK 压缩标头,这将减少开销并提高性能。
HTTP/2 中可以实现多路复用,这意味着客户端和服务器可以通过单个 TCP 连接并行发送多个请求和响应。这将有助于减少延迟并提高网络利用率。
最后,HTTP/2 允许服务器推送,即客户端只需发出 1 个请求,服务器就可以发回多个响应。这在很多情况下对于减少客户端和服务器之间的往返延迟非常有用,因为服务器确切地知道客户端需要哪些资源,并且在客户端请求之前就发送这些资源。
让我们看看这个演示,看看 HTTP/2 与 HTTP/1.1 相比有多快。基本上在这个演示中,我们将尝试从服务器加载 200 张小图像
HTTP/2 的工作原理
那么,HTTP/2 内部是如何工作的呢?它的逻辑结构可以如下图所示表示:
一条 TCP 连接承载多个双向流。每个流都有唯一标识符 ,并承载多个双向消息。
每条消息(可以是请求或响应)被分解为多个二进制帧。帧是承载不同类型数据的最小单位,例如 HEADERS、SETTINGS、PRIORITY、DATA 等。
实际上,这些流实际上并不是单独流动的,而是它们的帧在连接上交错,并在到达另一端时重新组装。得益于这个二进制帧层,HTTP/2 中可以实现流复用
HTTP/2 与 HTTP/1.1
好的,现在您已经了解了 HTTP/2 的工作原理。让我们与 HTTP/1.1 进行比较,看看它们之间的区别。
- 首先,HTTP/2是二进制协议,而HTTP/1.1是文本协议。
- HTTP/2 中的标头经过压缩,而 HTTP/1.1 中的标头为纯文本
- HTTP/2 允许多路复用,HTTP/1.1 则不允许
- 在 HTTP/2 中,我们可以在单个连接中发送多个请求和响应,而在 HTTP/1.1 中我们只能发送 1 个,这意味着我们必须创建多个 TCP 连接才能发送多个请求。
- HTTP/2 可以实现服务器推送,但 HTTP/1.1 则不行
- HTTP/2 于 2015 年刚刚发布,而 HTTP/1.1 于 1997 年发布。