Thrift IDL
最近在复习 DDIA, 本来打算把一些序列化相关的内容写一篇很长的文章,预期内容从可读的 json 到 thrift/pb 到 avro, 再到 MySQL 的 Row format,到 Apache Arrow。结果最近被自己写的 bug 坑到想死,然后今天13机兵又到了…那我们从简吧,写完了赶紧打游戏。
Thrift 是一个提供了代码生成、服务端的库,允许你在上面编程:
Facebook 内部有各种语言编写的系统,而 thrift 需要为所有的语言进行服务。他需要实现 C/S 协议,实现对应的应用层的传输,然后给用户一层透明的编写的抽象。
抽象很重要的一部分是 IDL. 相对于 JSON 来说,thrift 给我们提供了编写 idl 的能力,我们可以以此来生成特定语言的 stub 代码。但是一个很重要的事情是:
- 假设我使用的是 JSON + REST 我可以通过 REST 的
/v
来标注对应的版本,同时小的版本修改,我们可以在服务端嵌入对应的逻辑,比如我们变更了一个字段的类型,或者添加了一个字段,我们只要在服务端处理了对应的逻辑即可。
而 Thrift 相对来说是不可读的:我们编写可读的 idl, 生成我们自己都懒得看的静态的 idl 代码,然后我们在 idl 代码上瞎搞。相对于 Json 那种谁看了都明白的结构,thrift 可能优化了空间,但是我们要面对一些问题:
- 他生成的 binary 是怎么样的(其实不那么重要,但是理解这个才能理解下面重要的东西)
- 变更的时候,怎么样修改 idl 是合理的
第一个问题本身没那么重要,但是他对理解“怎么样修改 idl 是合理的”很重要。实际上,对于这点,我们可以看到,thrift 会支持:
- Types
- Versioning
Types in Thrift
https://thrift.apache.org/docs/types.html
Base types
Thrift 支持:
bool
A boolean value, true or falsebyte
A signed bytei16
A 16-bit signed integeri32
A 32-bit signed integeri64
A 64-bit signed integerdouble
A 64-bit floating point numberstring
An encoding-agnostic text or binary string
这里需要注意:
- 不支持
float
, 因为部分语言没有。 - 不支持
unsigned
, 如果需要unsigned
, 你得 cast 了。
需要说明的时候,在传输的时候,他是按网络序传输的。同时,我们之前谈大小端的时候说到 double
的问题。这里在传输的时候,会把 double
给 reinterpret_cast
成 i64
, 然后按网络序发送。
而 bool
会被按 i8
传输。string
会被组织成 prefix_length
+ data
的形式。
containers
同时,除了上面的基础类型, 它还支持容器,对应 list
set
和 map
. 这些类型要求是 iterable 的。
structs
表示结构,需要
1 | struct Example { |
你甚至可以设置 default 值,同时,可以显式设置 tag。
Thrift 还支持了 message 和 service,但是今天略过不表。
Interface
内存结构和二进制表示是分开的,实际上,thrift 甚至可以指定 JSON,xml。不过我们今天就介绍他的 IDL,所以别的不表。
在生成的时候我们有如下接口:
我相信没耐心看完…有几个重要的是:
read
对应的复合类型的readBegin
+readEnd
write
对应的复合类型的readBegin
+readEnd
write
对应的writeFieldStop
The procedure for reading a struct is to readFieldBegin() until the stop field is encountered, and then to readStructEnd(). The generated code relies upon this call sequence to ensure that everything written by a protocol encoder can be read by a matching protocol decoder.
上述的内容会在代码中被生成,然后按照类型被写入。
这个同时跟 idl 中的 optional
required
和 default 是挂钩的,可以看看这个 SO:
这里显示,idl 生成的代码,写入的时候、读取的时候会生成字段,而optional
会影响对应的写入。
Binary
binary 可以详见 Compact 和 Binary 的文档:
- https://github.com/apache/thrift/blob/master/doc/specs/thrift-binary-protocol.md
- https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md
我们介绍 binary:
实际上,你会知道一点:
- Stop field 能 explicit 的表示结束,而且别的编码不会影响他的正确性
- 这里没有 name,只有 field id
- field 可能是乱序的
那么我们实际上知道,field id 是不能乱设的!我们也可以读读 spec 里面的: https://github.com/apache/thrift/blob/master/doc/specs/SequenceNumbers.md
编程 & Version
以 C++ 为例,用户实际上除了数据,还能看到一个 __isset
,这个字段是 public
的。这个被用来识别版本:
- 不认识的 tag 字段被丢弃
- 有的字段设置
__isset
上述功能可以实现版本。具体可以看 whitepaper 5.3 的 case analysis: