背景
随着微服务架构和云原生架构的出现,传统的单体应用程序被分解为一组细粒度的、自治的和面向业务能力的“微服务”,网络通信链路的数量激增,进程间(或服务间/应用程序间)通信技术也因此成为了现代分布式系统中至关重要的一个环节。
目前,最常见最传统的进程间通信方式是构建一个Restful服务,将应用程序建模为一个可访问的资源集合,然后通过http协议进行服务调用,获取资源或者变更资源状态。然而,在比较多的场景下Restful服务对于构建进程间通信来说过于庞大、低效且容易出错,需要一个比Restful服务更高效的高可扩展、松耦合的进程间通信技术。因此,诞生了gRPC,一种用于构建分布式应用程序和微服务的现代进程间通信方式。
《深入了解grpc》系列文章从以下几个方面来讲解grpc技术:
- grpc介绍。包括什么是grpc、进程间通信的演进、grpc的优缺点、grpc与其他协议的对比。
- grpc原理。包括框架架构、通信流程、编解码以及支持的四种通信方式。
- grpc-go实现细节。包括客户端端与服务端建立RPC连接、发送/接收消息的过程,以及超时、拦截器、keepalive、metadata、加解密、认证等高级功能的实现方式。
本篇文章讲述第一个方面,即“grpc介绍”。后续会用4-5篇文章讲述另外两方面。
什么是gRPC?
gRPC是一种进程间通信技术。在 gRPC 中,客户端可以直接调用不同机器上的服务端的方法,就像调用本地函数一样。
与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的接口及其参数和返回类型。服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。而客户端有一个stub(在某些语言中也称为client),它提供与服务器相同的方法。客户端通过调用stub的方法来与服务端进行通信,获取响应结果。
下图为开发gRPC应用的一个示例:
- 首先,你需要定义一个服务接口。服务接口定义包含有关客户端如何使用你的服务、允许客户端远程调用哪些方法、调用这些方法时需要传递哪些参数、返回格式是怎么样的等信息。一般通过protocol buffer来定义服务接口。
- 使用protocol buffer定义好服务接口之后,你可以使用它提供的protoc工具生成被称为服务器骨架(Server Skeleton)的服务端代码,它通过提供低级通信抽象来简化服务器端逻辑。此外,你还可以生成客户端代码,称为客户端存根(client stub),它通过抽象来简化客户端通信,以隐藏不同编程语言的低级通信。
- 客户端使用你在服务接口定义中指定的方法来进行远程调用,就像进行本地函数调用一样简单。底层 gRPC 框架会自动帮你处理数据序列化、网络通信、身份验证、访问控制、可观察性等远程通信相关的所有复杂的工作。
RPC技术的演进
RPC技术随着时间的推移出现了翻天覆地的变化,出现了各种RPC技术的实现来满足现代需求,并提供更好、更高效的开发体验。本节主要是了解RPC技术是如何演化成如今的gRPC的。
常规的RPC
RPC 是一种流行的进程间通信技术,用于构建客户端-服务器应用程序。使用 RPC,客户端可以像调用本地方法一样远程调用方法的功能。早期有几个比较流行的RPC实现,例如CORBA、RMI等,它们用于构建和连接服务或应用程序。然而,大多数此类传统的RPC实现都非常复杂,因为它们建立在TCP等通信协议之上,互操作性较差,定义的协议规范也很繁琐。
SOAP
由于 CORBA 等传统 RPC 实现的局限性,简单对象访问协议 (SOAP) 被微软、IBM 等大型企业设计并大力推广。SOAP 是面向服务架构 (SOA) 中的标准通信技术,用于在服务(在 SOA 的上下文中通常称为 Web 服务)之间交换基于 XML 的结构化数据,并通过任何底层通信协议(例如 ,HTTP)进行通信。
使用 SOAP,你可以定义服务接口、该服务的操作以及用于调用这些操作的关联 XML 消息格式。SOAP 是一种相当流行的技术,但消息格式的复杂性以及围绕 SOAP 构建的规范的复杂性降低了构建分布式应用程序的敏捷性。因此,在现代分布式应用程序开发中,SOAP Web 服务被认为是一种遗留技术。大多数现有的分布式应用程序现在都不是使用 SOAP,而是使用 REST 架构风格开发的。
REST
Representational State Transfer (REST) 是一种源自 Roy Fielding 博士论文的架构风格。Fielding 是 HTTP 规范的主要作者之一,也是 REST 架构风格的鼻祖。REST 是面向资源架构 (ROA) 的基础,你可以将分布式应用程序建模为资源的集合,访问这些资源的客户端可以更改这些资源的状态(创建、读取、更新或删除)。
REST 的实际实现是 HTTP,在 HTTP 中,你可以将 RESTful Web 应用程序建模为可使用唯一标识符 (URL) 访问的资源集合,可通过HTTP方法(GET、POST、PUT、DELETE、PATCH 等)来变更这些资源的状态。资源状态以文本格式表示,例如 JSON、XML、HTML、YAML 等。
使用带有 HTTP 和 JSON 的 REST 架构风格构建应用程序已成为构建微服务的常见方式。然而,随着微服务数量的激增及网络交互的愈发复杂,RESTful 服务已经无法满足预期的现代需求。RESTful 服务有几个关键限制,使它难以成为现代基于微服务的应用程序的消息传递协议:
- 低效的基于文本的消息协议。本质上,RESTful 服务构建在基于文本的传输协议(如 HTTP 1.x)之上,并使用人类可读的文本格式(如 JSON)进行传输。当涉及到服务到服务的通信时,使用 JSON 等文本格式是非常低效的,因为通信的过程中会涉及到文本格式和二进制格式的转换,而且同样的内容,使用文本格式编码与使用其他简单编码方式相比,占用的空间更多,需要传输的数据也就更大。
- 应用程序之间缺乏强类型接口。当你开发 RESTful 服务时,不需要对应用程序之间共享的信息进行服务定义和类型定义。这会导致服务之间通信时容易出现不兼容、运行时错误和交互问题。
- REST 架构风格难以执行。作为一种架构风格,REST 有很多“良好实践”,你需要遵循这些“良好实践”来实现真正的 RESTful 服务。但是它们没有作为实现协议(例如 HTTP)的一部分强制执行,这使得在实现阶段很难强制执行它们。
gRPC
谷歌一直在使用一个名为Stubby的通用 RPC 框架来连接数千个在多个数据中心运行并使用不同技术构建的微服务。其核心 RPC 层被设计成可以处理每天数百亿规模的请求。Stubby 有很多很棒的特性,但是它与 Google 的内部基础架构耦合得太紧密了,没有标准化,不能用作通用框架。
2015 年,Google发布了开源 RPC 框架gRPC,它是一个标准化的、通用的、跨平台的 RPC 基础设施。gRPC 旨在提供与 Stubby 相同的可扩展性、性能和功能。
从那时起,随着 Netflix、Square、Lyft、Docker、Cisco 和 CoreOS 等大公司的大规模采用,gRPC 的受欢迎程度在过去几年中急剧增长。后来,gRPC 加入了云原生计算基金会 (CNCF,最受欢迎的开源软件基金会之一,致力于使云原生计算变得普遍和可持续),并从 CNCF 生态系统项目中获得了巨大的关注度。
为什么选择 gRPC?
gRPC的优势
gRPC带来的优势是越来越多地公司采用 gRPC 的关键。这些优势包括:
- 实现的进程间通信方式高效。gRPC 不使用 JSON 或 XML 等文本格式,而是使用基于二进制协议的protocol buffer与 gRPC 服务、客户端进行通信。此外,gRPC 是在 HTTP/2 之上实现的protocol buffer,这使得进程间通信更快。
- 具有简单、定义良好的服务接口和协议。你首先定义服务接口,然后处理实现细节。因此,与用于RESTful服务定义的 OpenAPI/Swagger 和用于 SOAP Web 服务的 WSDL 不同,gRPC 提供了简单但一致、可靠且可扩展的应用程序开发体验。
- 强类型。protocol buffer清楚地定义了应用程序之间通信的数据类型,这使得分布式应用程序开发更加稳定。因为静态类型有助于减少你在构建跨多个团队和技术的云原生应用程序时遇到的大多数运行时和交互错误。
- 支持多语言。gRPC被设计成支持多种编程语言。使用protocol buffer的服务定义与语言无关。因此,你可以选择grpc支持的任意语言,并与任何现有的 gRPC 服务或客户端进行通信。
- 支持双向流式传输。gRPC 对客户端或服务器端流式传输具有原生支持,这使得开发流媒体服务或流媒体客户端变得更加容易。
- 内置多种高级特性。gRPC 提供对高级特性的内置支持,例如身份验证、加密、元数据交换、压缩、负载平衡、服务发现等。
- 与云原生生态系统高度集成。gRPC 是 CNCF 的一部分,大多数现代框架和技术都为 gRPC 提供了开箱即用的原生支持。例如,Envoy等 CNCF 下的许多项目都支持使用 gRPC 作为通信协议。
gRPC的缺点
与任何技术一样,gRPC 也有一些缺点:
- 它可能不适合面向外部的服务。当你想将应用程序或服务提供给外部客户端使用时,gRPC 可能不是最合适的协议,因为大多数外部使用者对 gRPC 还很陌生。而且,gRPC 服务的协议驱动、强类型化特性可能会降低你向外部提供的服务的灵活性,因为外部使用者可以控制的东西要少得多。
- 生态系统相对较小。与传统的 REST/HTTP 协议相比,gRPC 生态系统仍然相对较小。浏览器和移动应用程序对 gRPC 的支持仍处于初级阶段。
gRPC与其他协议:Apache Thrift和GraphQL
Apache Thrift
Apache Thrift是一个类似于 gRPC 的 RPC 框架(最初由 Facebook 开发,后来捐赠给 Apache)。它使用自己的接口定义语言并提供对多种编程语言的支持。Thrift 允许你在定义文件中定义数据类型和服务接口,并根据你定义的文件为客户端和服务器端生成代码。Thrift 传输层为网络 I/O 提供抽象,并将 Thrift 与系统的其余部分解耦,这意味着它可以在任何传输实现上运行,例如 TCP、HTTP 等。
如果将 Thrift 与 gRPC 进行比较,你会发现两者几乎都遵循相同的设计和使用目标。但是,两者之间有几个重要的区别:
- 传输。gRPC并未给网络 I/O 提供抽象,而是强依赖于 HTTP/2 。它基于HTTP/2实现了传输的高效,并支持流式传输等消息传递模式。
- 流式传输。gRPC 原生支持双向流(客户端和服务器)。
- 性能。虽然没有 gRPC 与 Thrift 的官方比较结果,但有一些在线资源对两者进行了性能比较,显示 Thrift 的数据更好。然而,gRPC 在几乎所有版本中都做过严格的性能基准测试,与Thrift相比,性能相差较小。因此,在选择使用Thrift还是gRPC时,性能不太可能成为决定因素。
GraphQL
GraphQL是另一种技术(由Facebook最先创建并标准化),在构建进程间通信方面非常流行。GraphQL 为传统的客户端-服务器通信提供了一种完全不一样的实现,它是 API 的一种查询语言,允许客户端来决定他们想要什么数据、他们想要怎么获取数据以及他们想要什么格式的数据。而gRPC对于客户端和服务器之间的通信方式有一个固定的协议。
GraphQL 更适合直接面向外部的服务或 API,其中客户端需要对从服务器获取的数据进行更多控制。
在 GraphQL 和 gRPC 的大多数现实例子中,GraphQL 被用于面向外部的服务/API,而面向内部的服务则使用 gRPC 实现。
总结
gRPC 是一种可扩展、松耦合且类型安全的解决方案,与传统的基于 REST/HTTP 的通信相比,它实现了更高效的进程间通信。它允许你像本地方法调用一样调用、调试分布式应用程序。
gRPC 也可以被认为是传统 RPC 的演变,并且已经设法克服了它们的局限性。它被各种互联网公司广泛采用,以满足其进程间通信需求,最常用于构建内部服务到服务的通信。
参考文档
- Microservices for the Enterprise: Designing, Developing, and Deploying
- gRPC: Up and Running