美文网首页
gRPC学习笔记(三)——底层原理

gRPC学习笔记(三)——底层原理

作者: 简单一点点 | 来源:发表于2022-02-22 09:50 被阅读0次

protocol buffers

报文结构

protocol buffers编码格式是尽量将字段的元信息和字段的值进行压缩存储,其中字段的元信息中包含对这个字段描述的所有信息。

protocol buffers 序列化后的报文结构如下图所示,整个报文是一个二进制流,其中字段按照定义的顺序紧紧相邻,每个字段包含一个字段标识符(tag)和字段的值(value)。

protobuf.jpg

标签部分由两个值构成:

  • 字段索引:字段索引是在proto文件中定义消息时,为每个消息字段所设置的唯一数字。
  • 线路类型(wire type):线路类型基于字段类型,它可以提供信息来确定值的长度。

线路类型和字段类型的映射如下表所示。

线路类型 分类 说明 字段类型
0 VARINT 要用variant编码对所传入的数据做压缩存储 int32,int64,uint32,uint64,sint32,sint64,bool,enum
1 FIXED64 固定64位长度,不压缩 fixed64,sfixed64,double
2 LENGTH_DELIMITED 针对需要存储长度的类型 string,bytes,嵌入式消息, 打包的 repeadted 字段
3 START_GROUP 一个组(该组可以是嵌套类型,也可以是repeated类型)的开始标志 groups(已废弃)
4 END_GROUP 一个组(该组可以是嵌套类型,也可以是repeated类型)的结束标志 groups(已废弃)
5 FIXED32 固定32位长度,不压缩 fixed32,sfixed32,float

线路类型在报文中占3位,因此可以用下面的公式确定标签的值。

tag_value = (field_index << 3) | wire_type

编码技术

protocol buffers 将支持的字段类型分成不同的组(参考线路类型),每组使用不同的技术来编码。

Varint

Varint(可变长度整数)是使用单字节或者多字节来序列化整数的方法。它基于的思想是:整数的值并不均匀分布,每个值所占的字节数量并不固定,而依赖具体的值。

在 Varint 中,除了最后的字节,其它所有字节都会设置最高有效位(most significant bit,MSB)表明后面还有字节。每字节中较低的7位用来存储数字的二进制补码形式,同时最低有效位放在前面。

对于有符号整数sint32和sint64,会首先使用zigzag编码来将有符号整数转换成无符号整数再使用Varint编码。

zigzag编码中有符号整数会将正整数和负整数以“之”字形方式映射位无符号整数,如下表。

原始有符号整数 映射的无符号整数
0 0
-1 1
1 2
-2 3
-2 4

因此,对于负整数,更推荐sint32和sint64而不是int32和int64。因为后者会将负值直接转换成二进制值,比前者占用更多的字节。

FIXED64 和 FIXED32

这两种会被分配固定的字节,字节数和实际值没有关系。

字符串类型

由前文可知,字符串类型属于基于长度分隔(length-delimited)的线路类型。这意味着说先会有一个经过 Varint 编码的长度值,随后才是指定数量的字节数据。字符串值会使用UTF-8字符编码格式来进行编码。

基于长度前缀的消息分帧

消息分帧(message-framing)是通信中一种常用技术,主要用来保证数据传输准确性以及处理数据过长场景。gRPC 中使用了长度前缀分帧(length-prefix framing)的消息分帧技术。

长度前缀分帧是指再写入消息之前先写入长度信息。在 gRPC 中每条消息使用4字节来设置其大小,这意味着 gRPC 可以处理的消息最大不超过 4GB。

如下图所示,消息使用大端(big-endian)格式表示内容大小,另外帧中还有一维的压缩标志,表明消息是否使用了压缩。

grpc1.jpg

基于 HTTP/2 通信的 gRPC

gRPC 基于 HTTP/2 通信,本部分介绍 gRPC 和 HTTP/2 的关系。

HTTP/2

首先介绍一下 HTTP/2 ,HTTP/2 支持 HTTP/1.1 的所有核心特性,只不过实现方式更加高效,下面会介绍 HTTP/2 中的几个基本概念。

流(Stream)

流是服务器和客户端在 HTTP/2 连接内用于交换帧数据的独立双向序列,逻辑上可看做一个较为完整的交互处理单元,即表达一次完整的资源请求-响应数据交换流程;一个业务处理单元,在一个流内进行处理完毕,这个流生命周期完结。

它的特点如下:

  • 一个 HTTP/2 连接可同时保持多个打开的流,任一端点交换帧。
  • 客户端、服务端双方都可以建立流,流也可以被任意一方关闭。
  • 流的标识符用自然数表示,1~2^31-1区间,由创建流的终端分配。
  • 客户端发起的流使用奇数流ID,服务端发起的使用偶数,ID 0、1为保留ID。
  • HTTP/2连接上传输的每个帧都关联到一个流,一个连接上可以同时有多个流
    同一个流的帧按序传输,不同流的帧交错混合传输。

流是为了实现多路复用而提出的逻辑概念,在一个连接上可以同时存在多个流。而流是由一个个的帧组成,在一个流里面的帧是有序的,多个流之间的帧可以混杂在一起传输。

帧(frame)

HTTP/2抛弃HTTP/1的文本协议改为二进制协议,HTTP2的基本传输单元为帧,每个帧都从属于某个流。帧中标记着这个帧所属的流。

消息(message)

消息是指完整的帧序列,映射为一条逻辑上的HTTP消息,由一帧或多帧组成。客户端和服务端可以将消息分解成独立的帧并交叉发送,然后在另一端重新组合。

HTTP/2中的 gRPC

gRPC 在 HTTP/2 的基础上定义了请求消息(request)和返回消息(response)的规范。使用 header 帧描述元数据信息如超时时间、payload的压缩算法等等。使用 data 帧传输具体的参数和返回结果。

请求消息

请求信息主要包含3部分:请求头、带长度前缀的消息和流结束标记(end of stream)

请求头信息格式如下:

HEADERS (flags = END_HEADERS)
:method = POST // HTTP方法,gRPC中固定为POST
:scheme = http // HTTP模式,如果允许TLS,则使用 https,否则为http
:path = /xxx/xxx // 端点的路径:/{service name}/{method name}
:authority = abc.com // 定义目标 URI 的虚拟主机名
te = trailers // 定义不兼容代理的检测,gRPC 中必须是 trailers
grpc-timeout = 1S // 定义超时时间,如果不定义,则默认为无穷大
content-type = application/grpc // 定义content-type,gRPC 必须以 application/grpc 为前缀,否则的话,gRPC 服务器会返回一个 415 响应码
grpc-encoding = gzip // 定义消息压缩类型
authorization = Bearer xxxxxx</pre> // 可选元数据,authorization用来访问安全的端点

其中包含一些需要注意的点:

  • 以冒号 : 开头的字段称为保留头,即 HTTP/1.1 中包含的报文头字段,HTTP/2 要求保留头必须写在其他头部字段之前。

  • gRPC 的头部字段可以分为两类:调用定义的报文头(call-definition headers)和自定义元数据(custom metadata)。

    • 调用定义的报文头是由 HTTP/2 预定义的报文头,必须设置在自定义元数据之前。

    • 自定义元数据由应用层定义的任意键值对,注意自定义时不要使用grpc-开头的键名,这些是保留的键名。

请求消息通过在最后一个DATA帧上加上 END_STREAM 标志来结束。

    DATA (flags = END_STREAM)
    <Length-Prefixed Message></pre>

响应消息

响应消息同样包含3部分:响应头、带长度前缀的消息和trailer,其中消息部分不是必须的。

响应头格式如下:

    HEADERS (flags = END_HEADERS)
    :status = 200
    grpc-encoding = gzip
    content-type = application/grpc</pre>

发送完响应头后,如果包含消息,就会按数据帧发送带长度前缀的消息。END_STREAM 标记不会和数据帧一起发送,而是作为单独的头信息发送,名为trailer。

    HEADERS (flags = END_STREAM, END_HEADERS)
    grpc-status = 0 // 返回状态码
    grpc-message = xxxxxx</pre> // 对返回错误的描述信息,可选

如果调用请求直接失败,返回不包含数据帧,服务端只发送 trailer,它会以 HTTP/2 报文头帧的形式返回,并包含 END_STREAM 标记。

gRPC 通信模式中的消息流

前文已经讲过 gRPC通信的 4 种模式:一元 RPC 模式、服务端流 RPC 模式、客户端流 RPC 模式以及双向流 RPC 模式。本部分结合本文内容讲述每种模式的运行方式。

一元 RPC 模式

一元 RPC 模式中,gRPC 服务端和客户端之间通信只涉及一个请求和一个响应,其中以长度为前缀的消息中可以包含一个或多个数据帧。

simple-grpc.jpg

服务端流 RPC 模式

请求信息流和 一元 RPC 模式相同,区别是服务端不再向客户端发送一条响应消息,而是多条响应消息流。服务端持续等待到接收到完整请求消息后,就会发送响应头消息和多条以长度为前缀的消息,最后发送 trailer 头信息后通信关闭。

server-streaming-grpc.jpg

客户端流 RPC 模式

在客户端流 RPC 模式中,客户端向服务器发送多条消息,即先发送头信息建立连接,然后发送多条带长度前缀的消息,最后在末尾的数据帧会发送 EOS 标记,接下来会将连接设置为半关(只接收不发送)状态。服务端在接收到客户端的所有消息后发送一条响应消息。

client-streaming-grpc.jpg

双向流 RPC 模式

双向流 RPC 模式中,客户端发送头信息帧与服务端建立连接,然后它们互发带长度前缀的消息,无需等待对方结束。发送完消息后关闭己方一侧的连接。

bidirectional-streaming-grpc.jpg

gRPC 实现架构

gRPC 实现架构如下所示。

grpc-architecture.jpg

在通信层之上的 gRPC 核心层用来抽象网络操作,并对认证等核心功能进行扩展。

代码生成API在应用层,主要处理应用程序逻辑和数据编码逻辑,各个语言会提供不同的API。

相关文章

网友评论

      本文标题:gRPC学习笔记(三)——底层原理

      本文链接:https://www.haomeiwen.com/subject/exjwlrtx.html