RPC 无非是做两件事情:一是数据编码,二是请求映射。

1. 数据编码–protobuf vs json

数据编码顾名思义就是在将请求的内存对像转化成可以传输的字节流发给服务端,并将收到的字节流再转化成内存对像。方法有很多,常见的有 XML、JSON、Protobuf。XML 已经日薄西山,JSON 风头正盛,Protobuf 则方兴未艾。gRPC 默认选用 Protobuf,早期貌似只支持 Protobuf,现在号称也支持 JSON 了,但不知道有多少人在用。

为什么选 Protobuf 呢?Protobuf 同样也是谷歌的产品,我想这是其中的一个原因。另外一个原因应该是 Protobuf 在某些场景下的效率要比 JSON 高一些。请大家牢记,天下没有免费的午餐,所有的优化都是有代价的。我们在考虑问题的时候一定要思考选择什么和放弃什么。

要理解 Protobuf 的优化,我们就需要回过头来看 JSON 有什么缺点。

{ "int":12345, "str": "hello", "bool": true }
{ "int":67890, "str": "hello", "bool": false }
  • 头一个缺点是非字符串的编码低效。比如 int 字段的值是 12345,内存表示只占两个字节,转成 JSON 却要五个字节。 bool 字段则占了四或五个字节。
  • 再一个缺点就是信息冗余。同一个接口同一个对像,只是 int 字段的值不同,每次都还要传输“int”这个字段名。

JSON 在可读性和编码效率之间选择了可读性,所以效率方面做了一定的牺牲。

Protobuf 提供了一系列工具,为 proto 描述的 message 生成各种语言的代码。传输效率上去了,工具链也更加复杂了。如果你给 gRPC 通信抓过包,你一定会怀念 JSON 的。

message Demo {int32 i = 1;string s = 2;bool b = 3;
}

2. 请求映射–.proto文件

因为有 .proto 作为 IDL,Protobuf 确实可以做很多 JSON 不方便做的事情。其中最重的就是 RPC 描述!

package demo.hello;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {string name = 1;
}message HelloReply {string message = 1;
}

上面的 .proto 文件定义了一个 Greeter 服务,其中有一个 SayHello 的方法,接受 HelloRequest 消息并返回 HelloReply 消息。如何实现这个 Greeter 则是语言无关的,所以叫 IDL。gRPC 就是用了 Protobuf 的 service 来描述 RPC 接口的。

那问题来了,gRPC 如何映射请求呢?要回答这个问题,首先要回答 gRPC 在底层使用什么传输协议。答案是 HTTP 协议,准确的说,gRPC 使用的是 HTTP/2 协议。

现在你可以简单认为一个 gRPC 请求就是一个 HTTP 请求(不严格)。这个 HTTP 请求用的是 POST 方法,对应的资源路径则是根据 .proto 定义确定的。我们前文提到的 Greeter 服务对应的路径是/demo.hello.Greeter/SayHello 。

gRPC 协议规定Content-Typeheader 的取值为application/grpc,当然也可以写成application/grpc+proto。如果你想使用 JSON 编码,也可以设成application/grpc+json,只要服务支持都行。

最后就要确定请求 body 的定义了。如果用的 Protobuf 编码,那 body 肯定是编码后的字节流。那 gRPC 的 HTTP 请求是不是这样呢?

POST /demo.hello.Greeter/SayHello HTTP/1
Host: grpc.demo.com
Content-Type: application/grpc
Content-Length: 1234<protobuf bytes>

答案是否定的!简单来说,gRPC 要求在 Protobuf 字节流前面加一个五字节的前缀,第一个字节表示字节流是否被压缩,后四个字节存储数据长度,并取名叫作 Length-Prefixed Message。

熟悉 HTTP 协议的同学都清楚,HTTP 协议本身可以通过 Content-Encoding 表示压缩算法,使用 Content-Length 指定数据长度。gRPC 为什么要重新定义一套机制呢?

答案在于 gRPC 支持的另一特性 stream rpc!为方便行文,我们称之为流式接口。所谓流式,就是可以源源不断收发消息。这个跟 HTTP 的一收一发有着显著的差别。

service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}rpc SayHello (stream HelloRequest) returns (HelloReply) {}rpc SayHello (HelloRequest) returns (stream HelloReply) {}rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}

gRPC 持三种流式接口,定义的办法就是在参数前加上 stream 关键字,分别是:请求流、响应流和双向流。

  • 第一种叫请求流,可以在 RPC 发起之后不断发送新的请求消息。此类接口最典型的使用场景是发推送或者短信。
  • 第二种叫响应流,可以在 RPC 发起之后不断接收新的响应消息。此类接口最典型的使用场景是订阅消息通知。
  • 最后一种是双向流。可以在 RPC 发起之后同时收发消息。此类接口最典型的使用场景是实时语音转字幕。

为了实现流式传输,gRPC 不得不引入所谓的 Length-Prefixed Message。同一个 gRPC 请求的不同消息共用 HTTP 头信息,所以只能给每个消息单独加一个五字节的前缀来表示压缩和长度信息了。

就是因为这五个字节,不管你是 Protobuf 还是 JSON,都注定了 gRPC 只能是二进制协议,UNIX 下常用的文本工具都无法很好地处理 gRPC 的通信内容。

gRPC 还定义了自己的返回状态和消息,分别用 grpc-status 和 grpc-message 头传输。所以最简单的 gRPC 通信(非流式调用,unary)内容长成这个样子

POST /demo.hello.Greeter/SayHello HTTP/1.1
Host: grpc.demo.com
Content-Type: application/grpc
Content-Length: 1234<Length-Prefixed Message>
HTTP/1.1 200 OK
Content-Length: 5678
Content-Type: application/grpc<Length-Prefixed Message>

3. 流式接口与HTTP/2

如果单看非流式调用,也就是 unary call,gRPC 并不复杂,跟普通的 HTTP 请求也没有太大区别。我们甚至可以使用 HTTP/1.1 来承载 gRPC 流量。但是(凡事都有但是),gRPC 支持流式接口,这就有点难办了。

我们知道,HTTP/1.1 也是支持复用 TCP 连接的。但这种复用有一个明显的缺陷,所有请求必须排队。也就是说一定要按照请求、等待、响应、请求、等待、响应这样的顺序进行。先到先服务。而在实际的业务场景中肯定会有一些请求响应时间很长,客户端在收到响应之前会一直霸占着TCP连接。在这段时间里别的请求要么等待,要么发起新的 TCP 连接。在效率上确实有优化的余地。一言以蔽之,HTTP/1.1 不能充分地复用 TCP 连接。

后来,HTTP/2 横空出世!通过引入 stream 的概念,解决了 TCP 连接复用的问题(注意,这里同样有取舍问题,不展开了)。你可以把 HTTP/2 的 stream 简单理解为逻辑上的 TCP 连接,可以在一条 TCP 连接上并行收发 HTTP 消息,而无需像 HTTP/1.1 那样等待。

所以 gRPC 为了实现流式特性,选择使用 HTTP/2 进行通信。所以,前文的 Greeter 调用的实际通信内容长这个样子。

HEADERS (flags = END_HEADERS) # header frame
:method = POST
:scheme = http
:path = /demo.hello.Greeter/SayHello
:authority = grpc.demo.com
content-type = application/grpc+protoDATA (flags = END_STREAM) # data frame
<Length-Prefixed Message>
HEADERS (flags = END_HEADERS) # header frame
:status = 200
content-type = application/grpc+protoDATA # data frame
<Length-Prefixed Message>HEADERS (flags = END_STREAM, END_HEADERS) # header frame
grpc-status = 0`在这里插入代码片`

参考

https://taoshu.in/grpc.html

深入理解 gRPC 协议--理解protobuf/.proto/http2相关推荐

  1. python爬虫入门教程--快速理解HTTP协议(一)

    http协议是互联网里面最重要,最基础的协议之一,我们的爬虫需要经常和http协议打交道.下面这篇文章主要给大家介绍了关于python爬虫入门之快速理解HTTP协议的相关资料,文中介绍的非常详细,需要 ...

  2. 在实践中深入理解IP协议

    本文为我个人计划撰写的博客专题<在实践中深入理解常见网络协议>中关于IP协议的一篇,有兴趣的朋友可以继续关注我的博客,我将会陆续撰写各种协议的实践分析文章. TCP/IP协议栈其实当然不止 ...

  3. 网络编程懒人入门(三):快速理解TCP协议一篇就够

    1.前言 本系列文章的前两篇<网络编程懒人入门(一):快速理解网络通信协议(上篇)>.<网络编程懒人入门(二):快速理解网络通信协议(下篇)>快速介绍了网络基本通信协议及理论基 ...

  4. 【Http协议】深入理解HTTP协议

    来源:http://www.blogjava.net/zjusuyong/articles/304788.html 深入理解HTTP协议 1. 基础概念篇 1.1 介绍 HTTP是Hyper Text ...

  5. Zookeeper理解---ZAB协议

    ZAB协议 Zookeeper并不是完全采用Paxos算法,而是使用了一种称为Zookeeper Atomic Broadcast(ZAB,Zookeeper原子消息广播协议)作为数据一致性的核心算法 ...

  6. [通俗易懂]深入理解TCP协议(下):RTT、滑动窗口、拥塞处理

    转自即时通讯网:http://www.52im.net/ 前言 此文为系列文章的下篇,如果你对TCP不熟悉的话,请先看看上篇<[通俗易懂]深入理解TCP协议(上):理论基础> . 上篇中, ...

  7. 【数字IC】深入浅出理解I2C协议

    深入浅出理解I2C协议 一.什么是I2C协议 二.I2C,SPI,UART协议的区别 三.I2C的信号线 四.I2C的连接方式 4.1 单主设备,单从设备 4.2 单主设备,多从设备 4.3 多主设备 ...

  8. rrpp协议如何修改_《技术进阶:理解RRPP协议.ppt

    理解RRPP协议 RRPP 协议的背景 RRPP协议是由EAPS协议发展来的, EAPS (Ethernet Automatic Protect Switching) EAPS协议:rfc3619 对 ...

  9. 分布式一致性协议三部曲-深入理解一致性协议Paxos

    在理解分析分布式一致性协议前,我们必须先看下CAP理论 CAP CAP是指在一个分布式系统中,一致性(Consistency).可用性(Availability).分区容错性(Partition to ...

最新文章

  1. STM32使用GPIO_WriteBit()函数使LED灯闪烁
  2. ftl不存在为真_LTL和FTL货运之间有什么区别?
  3. matlab 颜色图名称
  4. 【前沿】何恺明大神ICCV2017最佳论文Mask R-CNN的Keras/TensorFlow/Pytorch 代码实现
  5. 使用帆软finereport实现跳转的一点心得
  6. python综合管理系统_Python-20 (信息系统-框架/循环/增删/综合应用)
  7. mpu6050 重力加速度_2021年高考物理一轮复习学与练 重力、弹力 、摩擦力 高中物理知识总结大全...
  8. java讲对象放在常量池的方法_java的常量池里面都放了些神马东西
  9. 《Python核心编程》第二版第209页第八章练习 -Python核心编程答案-自己做的-
  10. 磁盘的成组与分解技术
  11. vscode插件之Vetur
  12. bootstrap 复选框及单选按钮
  13. css半透明渐变过渡效果
  14. 在线ICO转换器--网站专用
  15. 关于4418开发和6818开发
  16. 笔记本实现共享wifi上网
  17. Python之NumPy(axis=0/1/2...)的透彻理解——通过np.sum(axis=?)实例进行说明
  18. 深度学习-各类梯度下降优化算法回顾
  19. ARP与RARP协议详解 (三)
  20. 杂七杂八(5): 文件图标变白纸 解决方法(在Windows 10中修复损坏或丢失的图标和缩略图)

热门文章

  1. python解不定积分_python快速求解不定积分和定积分
  2. 3-8Tensor的算术运算编程实例
  3. Python机器学习:KNN算法06网格搜索
  4. 数据科学入门与实战:玩转pandas之三
  5. tomcat java_opts 最大_tomcat性能优化(JAVA_OPTS)
  6. 管家婆辉煌版软件的使用方法_管家婆软件进销存的使用方法,管家婆软件使用教程_双全科技...
  7. 鸿蒙硬件HI3861开发环境搭建
  8. pymysql使用变化的变量,构造SQL语句
  9. python根据年月日计算天数_「每日一练」Python实现输入年月日计算第几天
  10. 多选题spss相关分析_【医学问卷分析】使用SPSS多重响应对医学问卷多选题进行统计分析——【杏花开医学统计】...