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修饰来添加.