Thrift 個人實戰--Thrift 的序列化機制


 

前言:
  Thrift作為Facebook開源的RPC框架, 通過IDL中間語言, 並借助代碼生成引擎生成各種主流語言的rpc框架服務端/客戶端代碼. 不過Thrift的實現, 簡單使用離實際生產環境還是有一定距離, 本系列將對Thrift作代碼解讀和框架擴充, 使得它更加貼近生產環境. 本文主要講解Thrift的序列化機制, 看看thrift作為數據交換格式是如何工作的?

構造應用場景:
1). 首先我們先來定義下thrift的簡單結構.

namespace java mmxf.thrift;

struct Pair {
  1: required string key
  2: required string value
}

required修飾符你肯定能猜測到它的意義, 但是你是否有沒有這樣的疑惑, "1", "2" 這些數字標識符究竟有何含義? 它在序列化機制中究竟扮演什么樣的角色?
編譯並進行
thrift -gen java <your thrift file>

2). 編寫測試代碼

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中

3). 如果我重新定義pair結構, 調整數字編號數序

struct Pair {
  2: required string key
  1: required string value
}

評注: 這邊2對應key, 1對應value.
重新編譯thrift -gen java <your thrift file>

4). 然后讀取該數據

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

Versioning in Thrift is implemented via field identifiers. The field header for every member of a struct in Thrift is encoded with a unique field identifier. The combination of this field identifier and its type specifier is used to uniquely identify the field. The Thrift definition language supports automatic assignment of field identifiers, but it is good programming practice to always explicitly specify field identifiers. 

翻譯: 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)的有組織有序調用.

數據交換格式分類
當前的數據交換格式可以分為如下幾類:
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修飾來添加.

后續
  后續會講解基於thrift進行服務化的專題, 首先會講解client的封裝改造, 敬請期待.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM