QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

我们为什么从 REST 转向 gRPC

2019 年 6 月 23 日

我们为什么从REST转向gRPC

服务间的通信方式是在采用微服务架构时需要做出一个最基本的决策。默认的选项是通过 HTTP 发送 JSON,也就是所谓的 REST API。我们也是从 REST 开始的,但最近我们决定改用 gRPC。


gRPC是谷歌开发的一个远程调用框架,现在已开源。尽管它已经出现了多年,但网上关于人们为什么要用它或者为什么不用它的信息并不多。于是,我决定写这篇文章分享一下我们为什么要使用 gRPC。


gPRC 的一个很明显的优势是它使用了二进制编码,所以它比 JSON/HTTP 更快。虽然说速度越快越好,但我们也要考虑另外两个因素:清晰的接口规范和对流式传输的支持。


gRPC 的接口规范

创建 gRPC 服务的第一步是在.proto 文件中定义好接口。下面的代码是一个接口的定义,它定义了一个简单的远程过程调用”Lookup“以及相应的输入和输出类型。


syntax = "proto3";
package fromatob;
// FromAtoB is a simplified version of fromAtoB’s backend API.service FromAtoB { rpc Lookup(LookupRequest) returns (Coordinate) {}}
// A LookupRequest is a request to look up the coordinates for a city by name.message LookupRequest { string name = 1;}
// A Coordinate identifies a location on Earth by latitude and longitude.message Coordinate { // Latitude is the degrees latitude of the location, in the range [-90, 90]. double latitude = 1;
// Longitude is the degrees longitude of the location, in the range [-180, 180]. double longitude = 2;}
复制代码


你可以使用 protoc 编译器编译这个文件,生成客户端和服务器端代码,然后就可以开始编写调用这个 API 或提供 API 服务的代码。


那么,为什么说这个接口定义其实不算是额外的工作量反而是件好事?看看上面的代码,即使你之前从来没有使用过 gRPC 或者 Protocol Buffer,也能轻松读懂它。比如,如果要发送一个 Lookup 请求,你需要发送 name 字符串,然后会接收到由 latitude 和 longitude 组成的 Coordinate 对象。实际上,因为你已经在.proto 文件中加入了一些简单的注释,所以它也可以作为服务的 API 文档来使用。


当然,真正的服务定义规范比这个要长得多,但也不会太复杂,只是会多一些用于定义方法的 rpc 语句和一些用于定义数据类型的 message 语句。


通过 protoc 编译器生成的代码可以确保客户端发送或服务器端接收到的数据是遵循规范的,这样非常有助于调试。我记得有两次我开发的服务因为格式没有经过验证而生成了错误的 JSON 数据,这些问题只会在用户界面上表现出来。要想找出问题的根源,我们只能调试前端 JavaScript 代码,而这对于一个不太熟悉 JavaScript 框架的后端开发人员来说这并不容易!


Swagger/OpenAPI

当然,如果使用的是 JSON/HTTP,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 的定义更难懂,也更啰嗦,结构也更复杂(8 层的缩进)。


OpenAPI 的规范验证比 gRPC 要困难一些,至少对于内部服务来说。随着 API 的不断演化,如果不去更新规范,它就会变得毫无用处。


流式传输

今年早些时候,我开始为我们的搜索服务设计一个新的 API。在我使用 JSON/HTTP 设计了第一版 API 之后,我的一个同事告诉我说,在某些情况下,我们需要流式传输搜索结果,也就是在有第一批结果时就开始传输。而我之前设计的 API 只返回一个单独的 JSON 数组,在服务器端收集到所有结果之前是不会向客户端发送任何数据的。


我们的 API 要求客户端轮询搜索结果,先是发送一个 POST 请求发起搜索,然后再不断发送 GET 请求获取搜索结果。响应消息中包含了一个用于表示搜索是否已完成的字段。这种方式虽然没有什么问题,但还不够优雅,而且要求服务器端将中间结果保存在数据存储(如 Redis)中。


这个时候,我们决定试一试 gRPC。要通过 gRPC 发送结果,只需要在.proto 文件中加入 stream 关键字。下面是我们的 Search 函数定义:


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


使用 protoc 编译器生成的代码中包含了一个对象,这个对象有一个 Send 函数,我们的服务器端代码将调用这个函数将 Trip 对象一个接一个地发送出去。代码中还包含了一个 Recv 函数,客户端代码通过调用这个函数来接收 Trip 对象。从开发者的角度来看,这比实现轮询 API 要简单得多。


注意事项

gRPC 也有一些不足之处,不过它们都与相关的开发工具有关,并不是 gRPC 本身的问题。


如果我们使用 JSON/HTTP 开发 API,就可以使用 curl、httpie 或者 Postman 进行简单的手动测试。gRPC 也有一个类似的工具叫作grpcurl,不过它使用起来并不是很方便,你要么需要在服务器端添加gRPC服务器反射插件,要么需要在每个命令后面附上.proto 文件。


另一个是 Kubernetes 负载均衡器问题,负载均衡器可以支持 HTTP,但对 gPRC 支持得并不好。gPRC 要求应用层的负载均衡,而不是在 TCP 连接层。为了解决这个问题,我们参考了这篇文章搭建了 Linkerd。


结论

尽管开发 gRPC API 在前期需要做更多的工作,但拥有清晰的 API 定义和对流式传输的支持对我们来说更重要。在构建新的内部服务时,gRPC 将会是我们的首选。


英文原文:


http://url)https://eng.fromatob.com/post/2019/05/why-were-switching-to-grpc/




我们还邀请了 《Netty 权威指南》、《分布式服务框架原理与实践》作者李林锋撰写了一个系列文章“深入浅出 gRPC",戳此了解更多!


2019 年 6 月 23 日 10:0036730
用户头像
小智 前 InfoQ 主编

发布了 400 篇内容, 共 318.4 次阅读, 收获喜欢 1762 次。

关注

评论 18 条评论

发布
用户头像
这文章太水了吧,《2019 年 InfoQ 最受欢迎的文章排行榜 》推的文章质量太低了吧
2019 年 12 月 16 日 12:44
回复
用户头像
为什么二进制编码比 JSON/HTTP 更快?
JSON压缩后数据量也很小啊
2019 年 07 月 18 日 15:12
回复
二进制序列化反序列化比json快很多
2019 年 12 月 22 日 23:54
回复
用户头像
写得好,继续努力,加油
2019 年 07 月 05 日 13:15
回复
用户头像
只是翻译而已,大家不用当真
2019 年 07 月 02 日 12:58
回复
给大家提供一些同行们的场景化选型作参考
2019 年 07 月 04 日 17:40
回复
用户头像
gRPC 和 HTTP REST 在大多数场景下,并没有太大的优势,两者用起来其实是差不多的。
内部通信推送消息,其实比较适合用MQ,分片返回确实用gRPC的Stream 更容易些。
HTTP/1.1 + JSON 还是 HTTP/2.0+ Protocol Buf 的gRPC ,只需要按团队的需求选择就可以。不过,一般内部用gRPC,对外服务用HTTP Rest 是个比较不错的选择之一。
2019 年 07 月 01 日 10:25
回复
堪称文章评论的典范
2019 年 07 月 04 日 17:38
回复
用户头像
感觉你们换通信只是为了方便服务器主动推点信息而已,这是没毛病
2019 年 07 月 01 日 09:49
回复
用户头像
确实没啥内容,不清不白
2019 年 06 月 30 日 23:31
回复
用户头像
我觉得作者没有谈到事情的本质,给人感觉是在盲目跟风。
2019 年 06 月 30 日 10:24
回复
可以展开谈谈本质应该是啥样的
2019 年 07 月 04 日 17:40
回复
用户头像
不知道为什么用是做主要的,二进制,http传输数据最后还不是二进制,dubbo的rpc直接走tcp,是网络层上比较近一些,你这个二进制的是哪一层?不说清楚就二进制,谁不是二进制呢?
2019 年 06 月 25 日 15:06
回复
该评论已删除
2019 年 06 月 30 日 00:23
回复
二进制是指序列化的方式。比如一个只包含int32元素的对象,二进制序列化出来基本上只消耗4个字节,而且基本没有序列化成本。但是序列化成json,不算字段名,光值就要占用8到72个字节,另外序列化的计算开销相对也高了很多
2019 年 12 月 23 日 00:01
回复
用户头像
没有营养
2019 年 06 月 24 日 16:28
回复
原因何在呢?
2019 年 06 月 24 日 20:30
回复
用户头像
没有营养
2019 年 06 月 24 日 11:08
回复
用户头像
乍一看就是serverless。跟微软的 azure functions 那套差不多。
2019 年 06 月 24 日 11:07
回复
没有更多了
发现更多内容

架构师如何做架构

Safufu

极客大学架构师训练营

架构师训练营第一周总结

人世间

Java 极客大学架构师训练营 UML

食堂就餐卡系统 UML 练习

薛定谔的🐴

UML 部署图 系统用例图 组件图 组件时序图

食堂就餐卡系统架构设计

wei

第一周学习笔记

测试

第一周学习总结

刘卓

软件建模与设计文档学习总结

qihuajun

架构师训练营作业1:食堂就餐卡系统设计

a晖

架构师作业

老姜

极客大学架构师训练营

架构师训练营第一周学习总结

James-Pang

极客大学架构师训练营

程序员陪娃系列——学语趣事

孙苏勇

程序员人生 陪伴

Week1

架构师

通过食堂就餐卡系统学习文档编写

薛腾

极客大学架构师训练营

简易就餐卡管理系统设计

elfkingw

极客大学架构师训练营

架构师训练营-第一章-课程总结

而立

极客大学架构师训练营

食堂就餐卡架构设计

水蒸蛋

第一周:架构师训练营作业

Bruce Xiong

架构师训练营 - 第 1 周作业 - 食堂就餐卡系统设计

yanghao

第一周总结

娄江国

极客大学架构师训练营

做产品少走弯路系列一:上帝视角(1)

我是IT民工

产品 方法论 知识体系 全局观

就餐卡系统设计文档

qihuajun

【架构师训练营】食堂就餐卡系统设计文档

张明森

架构师第一周学习心得

水蒸蛋

第01周 编写架构设计文档 学习总结

Jaye

极客大学架构师训练营

食堂就餐卡系统设计

刘卓

架构第一课作业学习总结

老姜

架构师

wei

架构师训练营 第一周 个人感想

且听且吟

极客大学架构师训练营

架构师训练营-第一周作业

Eric

极客大学架构师训练营

架构师第一周

Tulane

极客大学架构师训练营

第一周学习总结:架构师的产出物

Bruce Xiong

架构设计

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

我们为什么从REST转向gRPC-InfoQ