[译] 为何咱们要切换到 gRPC

若是你在使用微服务式架构,那么你须要做出的一个基本决策就是:服务之间应该如何交换信息?默认的方法彷佛是使用 HTTP 协议发送 JSON 信息 —— 也就是使用所谓的 REST API,可是大多数人并无认真执行 REST 的原则。使用 REST API 的 fromAtoB 就是咱们最开始使用的方法,然而最近咱们决定将 gRPC 做为咱们新的标准。前端

gRPC 是谷歌研发,而且已经开源的远程过程调用系统。虽然它已经存在数年之久,但我并无在网上找到关于人们为何使用或者不使用它的信息,因此我决定写一篇文章,阐述咱们选择了 gRPC 的缘由。android

gRPC 最大的优点就是它使用的是高效二进制编码,这让它比 JSON/HTTP 这种模式快了不少。虽然更快的速度每每是很受欢迎的,这里还有两个对于咱们而言更重要的方面:清晰的接口规范,以及对流的支持。ios

gRPC 接口规范

当你建立要新的 gRPC 服务时,第一步一般是在 .proto 文件中定义接口。下面这段代码就是 .proto 文件大体的格式 —— 它是咱们本身的 API 中一小部分的简化版。在这个例子中,定义了一个远程过程调用“Lookup”,以及它的输入和输出类型。git

syntax = "proto3";

package fromatob;

// FromAtoB 是 fromAtoB 后台 API 的简化版本。
service FromAtoB {
	rpc Lookup(LookupRequest) returns (Coordinate) {}
}

// LookupRequest 是按照名称查找城市坐标的请求
message LookupRequest {
	string name = 1;
}

// Coordinate 使用经度和纬度定义了地球上的坐标
message Coordinate {
  // Latitude 是坐标的纬度,范围是 [-90, 90]。
	double latitude = 1;

  // Longitude 是坐标的经度,范围是 [-180, 180]。
	double longitude = 2;
}
复制代码

使用了这个文件,接下来你就可使用 protoc 编译器生成客户端和服务端代码,同时你也能够开始编写提供或者使用 API 的代码了。github

因此,为何这个文件能为咱们带来优点,而不是冗余的工做呢?让咱们再看一遍上面的代码样例。就算你历来没有用过 gRPC 或者协议缓冲(Protocol Buffer),这段代码也很是易读:例如,很容易看出,若是想要发出 Lookup 请求,你必须发送一个 string 类型的 name 参数,这个请求将会返回给你一个 Coordinate 类型的结果,它包含了参数 latitudelongitude。事实上,一旦你像例子中的那样添加了一些简单的注释,.proto 文件就能够做为你的服务的 API 文档了。json

固然,一个真正的服务规范的内容应该要多得多,可是却不会更加复杂。只是会有更多对于方法的 rpc 声明和对于类型的 message 声明。后端

经过 protoc 生成的代码也会确保客户端或者服务端发送的数据都合乎规范。这对于调试是大有帮助的。我记得以前就曾有过两次,我负责维护的服务生成了错误格式的 JSON 数据,而且因为这个格式并无被验证,错误仅会在用户界面出现。发现错误的惟一方法就是调试前端的 JavaScript 代码 —— 这对于一个历来没有使用过前端 JavaScript 框架的后端开发者并不容易!api

Swagger / OpenAPI

原则上来讲,若是你同时使用了 HTTP/JSON API 和 Swagger 或者它的继承者 OpenAPI,你也能够得到一样的优点。下面这段代码范例也能够和 gRPC API 媲美:数组

openapi: 3.0.0

info:
  title: A simplified version of fromAtoB’s backend API
  version: '1.0'

paths:
  /lookup:
    get:
      description: Look up the coordinates for a city by name.
      parameters:
        - in: query
          name: name
          schema:
            type: string
          description: City name.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Coordinate'
        '404':
          description: Not Found
          content:
            text/plain:
              schema:
                type: string

components:
  schemas:
    Coordinate:
      type: object
      description: A Coordinate identifies a location on Earth by latitude and longitude.
      properties:
        latitude:
          type: number
          description: Latitude is the degrees latitude of the location, in the range [-90, 90].
        longitude:
          type: number
          description: Longitude is the degrees longitude of the location, in the range [-180, 180].
复制代码

将这段代码和 gRPC 规范相比。OpenAPI 的代码就显得很是难以读懂!它更加冗长,结构也更复杂(有八级缩进,不像 gRPC 的只有一级)。缓存

使用 OpenAPI 规范进行验证也要比 gRPC 难不少。至少对于内部服务,这一切都意味着规范要么没有被写入,要么随着 API 的迭代,它们因为没有更新而变得无用了。

今年更早些的时候,我开始为咱们的搜索引擎设计新的 API(想象一下我要搜索“请给我全部 2019 年 6 月 1 日从柏林到巴黎的航线”)。在我构建了初版使用 HTTP 和 JSON 的 API 以后,个人一个同事指出,在某些状况下,我须要流式的请求结果,意味着我获取到第一个请求结果的时候,我应该开始再发送这些结果。而我设计的 API 只是返回一个简单的 JSON 数组,因此服务在获取到全部结果以前,没法发送任何请求。

前端使用这样的 API 就须要客户端发起轮询请求结果。前端发起 POST 请求来设置搜索条件,而后反复发送 GET 请求来获取结果。返回结果会包含一个能够确认搜索是否已经完成的字段。这种方式能够正常运行,可是不够优雅,而且还须要服务端使用像 Redis 这样的数据存储来缓存中间结果。新的 API 可能会被大量的更小的服务来实现,我并不但愿强制它们都实现这样的逻辑。

因而,咱们就决定要试一试采用 gRPC。若是你想要发送远程调用的结果,使用了 gRPC,你只须要将 stream 关键字添加到 .proto 文件中。这就是 Search 函数的定义:

rpc Search (SearchRequest) returns (stream Trip) {}
复制代码

protoc 编译器生成的代码包含了一个带有 Send 函数的对象,咱们的服务代码将会调用这个函数,来一个接一个的发送 Trip 对象,还会包含一个带有 Recv 函数的对象,而客户端代码将会调用这个函数来获取结果。从一个开发者的角度来看,这比应用轮询要简单不少

注意事项

另外我还想提一下,gRPC 也有些缺点。它们都与工具备关,而不是协议自己的问题。

当你使用 HTTP/JSON 构建 API 的时候,你可使用 curl、httpie 或 Postman 来作简单的测试。对于 gRPC 也有相似的工具,即 grpcurl,可是它和 gRPC 并非那么无缝衔接的:你必须在服务端添加 gRPC 服务映射扩展,或者在每一个命令中指定对应的 .proto 文件。咱们认为在服务端添加一个小小的能够发送简单请求的命令行工具更为简便。而 protoc 生成的客户端代码已经让发送请求很是简单了。

另外一个更大的问题则是 Kubernete 的负载均衡,咱们曾经用于 HTTP 服务的负载均衡并不很是适用于 gRPC。基本上来讲,gRPC 须要的负载均衡是在应用层面而不是 TCP 链接的层面。为了解决这个问题,咱们参考教程:gRPC Load Balancing on Kubernetes without Tears,建立了 Linkerd

总结

尽管构建 gRPC API 会须要更多的前期工做,然而所以可以拥有清晰的 API 规范以及对流的支持,咱们发现这些获益彻底可以弥补这些前期的工做量。对于咱们而言,gRPC 将会是全部咱们将构建的新的内部服务的默认选择。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索