Thrift 与 Google Protobuf 类似, 是一种 RPC 序列化协议, 严谨地说, Thrift 本身具有多层含义, 大范围上讲, Thrift 是一种生态, 包含 Thrift 代码生成器、序列化框架以及 RPC 框架, 类似于 Google Protobuf 生态的 Protoc + Protobuf + gRPC, 狭义上 Thrift 也可以用来单独指代序列化协议, 序列化协议统一了不同编程语言在诸如数据类型、语法上的差异, 由不同语言编写的服务之间只需按照 Thrift 的编码将数据序列为 Thrift 定义的格式便可以相互通信, Thrift 最初由 Facebook 开发后捐赠给 Apache 基金会, Thrift 的序列化协议比较多, 对于 RPC 场景主要有 Thrift Binary Protocol 和 Thrift Compact Protocol, 后者使用了与 Google Protobuf 相同的 Varints 编码和 Zigzag 编码来优化整数的存储 (关于这两种编码的原理可参考之前的文章 - Blog01 - Google Protobuf 编码原理), 相比前者后者的编码长度短, 从而传输效率更优, 目前在头条内部主要使用 Binary Protocol, 本文讨论 Thrift Binary Protocol 的编码原理

27.1 整数和枚举编码

在 Thrift Binary Protocol 中, 整数都使用大端字节序 (网络字节序) 来编码, 目前 Thrift 共有 4 种整数类型, 分别是 i8, i16, i32, i64, 对 Binary Protocol 来说, 整数存储没有做相应的优化, 因此它们所占的字节数分别为 1, 2, 4, 8 个字节 (其中 i8 对于部分编程语言没有实现, 具体是否可用需要看相应语言的 Thrift 编译器实现)

对于枚举类型的数据, Thrift 则以 int32 类型来存储对应的枚举序号值

27.2 二进制编码

二进制数据 (即 binary 类型) 的序列化结构如下所示

即二进制数据由 length 和 value 两部分构成, 前者为大端字节序的 32 比特有符号整数, 指示其后所跟的数据的长度 (以字节为单位), 由于 length 字段的长度是固定的, 所以这里我们也可以知道 Thrift binary 类型的数据是有长度上限的, 除去最高的符号位, length 字段的最大值为 , 即在 Thrift Binary Protocol 中, binary 类型的字段最大可传输的字节流上限为 个字节

27.3 字符串和布尔类型编码

在 Thrift Binary Protocol 中, 字符串会首先转为 UTF-8 编码, 然后以上面所述的 binary 格式 (即 length + value) 传输

布尔类型则是以 int8 类型存储, True 对应 1, False 对应 0

27.4 浮点型编码

Double 类型按照 IEEE-754 标准规定的浮点数描述方式以 i64 类型的数据传输

27.5 结构体编码

Thrift 使用 field-id 来标识每一个字段, 字段名称本身不会被序列化进字节流中, 因此修改 IDL 中定义的结构体的字段名称不会产生任何兼容性问题 (但这显然不是好的做法), Thrift 结构体的每一个字段都由 field-header 和 field-value 来表征, field-header 又可进一步拆分为两部分, 分别是 field-type 和 field-id, field-type 占 1 字节, 指示字段类型, 目前 Thrift 字段类型与 field-type 值之间的对应关系如下表所示

field-id 为 16 比特的有符号数, 因此 Thrift Binary Protocol 中, 结构体字段编号的协议上限为 , field-value 顾名思义便是字段的值, 根据字段类型的不同, 字段值具有如上所述的相应的编码格式, 在结构体的末尾, Thrift 拥有一个 stop-field 用以定界结构体的结束, stop-filed 长度为 1 字节, 其值固定为 00000000, Thrift 对于结构体的编码可由下图形象地表示

27.5 List 和 Set 编码

List 和 Set 编码是用 TLV 格式来描述, 即类型 - 长度 - 值, 它们的编码格式相同, 结构如下图所示

其中类型字段长度为 1 字节, 如上节的结构体编码中的图片所示, List 对应的 type 值为 15, Set 对应的 type 值为 14, size 为 i32 类型的整数, 指示其后所跟的元素的个数, 最后的 elements 便是元素的值列表

27.6 Map 编码

Map 编码也是用 TLV 格式来描述, 它的格式如下图所示

其中前两个字节分为指示 Map 中 Key 和 Value 的数据类型, 类型值在上面已提到过, size 占 4 字节, 以 i32 类型指示 Map 中的 K-V 对的数量, 因此 Thrift Map 的最大元素个数为 个, key value pairs 则是 Map 中 K-V 对的实际值, 它们的结构根据其相应的数据类型使用如上所描述的对应类型的 layout 来编码

27.7 总结

Thrift Binary Protocol 的编码方式比较简洁, 数据均采用 TLV 格式来描述, 相比于 Thrift Compact Protocol 没有做过多的优化, 以后的文章中会再讨论 Thrift Compact Protocol 的编码细节