Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码.
1、构造应用场景:
1)首先我们先来定义下thrift的简单结构.
namespace java mmxf.thrift;
struct Pair {
1: required string key
2: required string value
}
required修饰符你肯定能猜测到它的意义, 但是你是否 有没有这样的疑惑, "1", "2" 这些数字标识符究竟有何含义? 它在序列化机制中究竟扮演什么样的角色?
2)编译并进行
thrift -gen java <your thrift file>
3)编写测试代码
private String datafile = "1.dat";
// *) 把对象写入文件
public void writeData() throws IOException, TException {
Pair pair = new Pair();
pair.setKey("rowkey").setValue("column-family");
FileOutputStream fos = new FileOutputStream(new File(datafile));
pair.write(new TBinaryProtocol(new TIOStreamTransport(fos)));
fos.close();
}
调用writeData(), 把 pair{key=> rowkey, value=> column-family} 写入文件1.dat中
4)如果我重新定义pair结构, 调整数字编号数序
struct Pair {
2: required string key
1: required string value
}
评注: 这边2对应key, 1对应value. 重新编译thrift -gen java <your thrift file>
5)然后读取该数据
private String datafile = "1.dat";
// *) 从文件恢复对象
public void readData() throws TException, IOException {
FileInputStream fis = new FileInputStream(new File(datafile));
Pair pair = new Pair();
pair.read(new TBinaryProtocol(new TIOStreamTransport(fis)));
System.out.println("key => " + pair.getKey());
System.out.println("value => " + pair.getValue());
fis.close();
}
调用readData(), 从文件1.dat中恢复Pair对象来 结果:
key => column-family
value => rowkey
是不是和你预期的相反, 看来属性名称并没有发挥作用, 而id标识在thrift的序列化/反序列化扮演非常重要的角色。带着这些疑惑, 我们进一步的详细解读序列化机制。
thrift 数据格式描述:(官网文档描述: http://thrift.apache.org/static/files/thrift-20070401.pdf)
thrift的向后兼容性(Version)借助属性标识(数字编号id + 属性类型type)来实现, 可以理解为在序列化后(属性数据存储由 field_name:field_value => id+type:field_value) , 这也解释了上述提到的场景的原因了.
对之前定义的Pair结构体, 进行代码解读:
public void read(org.apache.thrift.protocol.TProtocol iprot, Pair struct) {
// *) 读取结构结束标记
iprot.readStructBegin();
while ( iprot is stop) {
// *) 读取Field属性开始标记
schemeField = iprot.readFieldBegin();
// *) field标记包含 id + type, switch根据(id+type)来分配相关的值
switch (schemeField.id) {
case <id>: // <field_name>
if (schemeField.type == thrift.TType.<type>) {
struct.<field_name> = iprot.read<type>();
struct.set<field_name>IsSet(true);
}
}
// *) 读取Field属性结束标记
iprot.readFieldEnd();
}
// *) 读取结构体结束标记
iprot.readStructEnd();
}
代码评注:
从恢复对象的函数中, 我们也可以对thrift定义的序列化对象有个初步的认识, 庖丁解牛,最终会被细化为readStructBegin, readFieldBegin, read<type>(readString, readI32, readI64)的有组织有序调用.
2、数据交换格式分类
当前的数据交换格式可以分为如下几类:
1.)自解析型
序列化的数据包含完整的结构, 包含了field名称和value值. 比如xml/json/java serizable, 大百度的mcpack/compack, 都属于此类. 即调整不同属性的顺序对序列化/反序列化不影响.
2)半解析型
序列化的数据,丢弃了部分信息, 比如field名称, 但引入了index(常常是id+type的方式)来对应具体属性和值. 这方面的代表有google protobuf, thrift也属于此类.
3)无解析型
传说中大百度的infpack实现, 就是借助该种方式来实现, 丢弃了很多有效信息, 性能/压缩比最好, 不过向后兼容需要开发做一定的工作, 详情不知.
thrift与常见数据交换格式的对比
交换格式 | 类型 | 优点 | 缺点 |
Xml | 文本 | 易读 | 臃肿, 不支持二进制数据类型 |
Json | 文本 | 易读 | 丢弃了类型信息, 比如"score":100, 对score类型是int/double解析有二义性, 不支持二进制数据类型 |
Java serizable | 二进制 | 使用简单 | 臃肿, 只限制在java领域 |
Thrift | 二进制 | 高效 | 不宜读, 向后兼容有一定的约定限制 |
Google Protobuf | 二进制 | 高效 | 不宜读, 向后兼容有一定的约定限制 |
向后兼容实践:
Thrift官方文档, 也提到对新增的字段属性, 采用id递增的方式标识并以optional修饰来添加.