一、序列化協(xié)議
? ? ? ?Thrift可以讓你選擇客戶端與服務(wù)端之間傳輸通信協(xié)議的類別,在傳輸協(xié)議上總體上劃分為文本(text)和二進制(binary)傳輸協(xié)議, 為節(jié)約帶寬,提供傳輸效率,一般情況下使用二進制類型的傳輸協(xié)議為多數(shù),但有時會還是會使用基于文本類型的協(xié)議,這需要根據(jù)項目/產(chǎn)品中的實際需求(例如:調(diào)試的時候)。
序列化協(xié)議類型:
TBinaryProtocol:二進制編碼格式進行數(shù)據(jù)傳輸。
TCompactProtocol:高效密集型的二進制序列化協(xié)議,使用Variable-Length Quantity (VLQ) 編碼對數(shù)據(jù)進行壓縮。
TJSONProtocol:使用JSON的數(shù)據(jù)編碼協(xié)議進行數(shù)據(jù)傳輸。
TSimpleJSONProtocol:這種節(jié)約只提供JSON只寫的協(xié)議,適用于通過腳本語言解析。
TTupleProtocol(繼承自TCompactProtocol)
TDebugProtocol:在開發(fā)的過程中幫助開發(fā)人員調(diào)試用的,以文本的形式展現(xiàn)方便閱讀。
RPC框架中一般使用 TCompactProtocol。
二、傳輸層
Thrift中所有的傳輸層協(xié)議的基類是TTransport。另外,需要說明的一點是,thrift是基于TCP協(xié)議的。
傳輸協(xié)議類型:
TSocket:使用堵塞式I/O進行傳輸,也是最常見的模式。
TFramedTransport:使用非阻塞方式,以frame為單位進行傳輸,類似于Java中的NIO。
TFileTransport:以文件形式進行傳輸,雖然這種方式不提供Java的實現(xiàn),但是實現(xiàn)起來非常簡單。
TMemoryTransport:使用內(nèi)存I/O,如Java中的ByteArrayOutputStream實現(xiàn)。
TZlibTransport:使用執(zhí)行zlib壓縮,與其他傳輸方式聯(lián)合使用,不提供Java的實現(xiàn)。
TNonblockingTransport:使用非阻塞方式,用于構(gòu)建異步客戶端。
RPC框架中一般使用 TFramedTransport。
三、Thrift的序列化和反序列化方式
步驟:
使用IDL創(chuàng)建thrift接口定義文件;
將thrift的定義文件轉(zhuǎn)換為對應(yīng)語言的源代碼;
選擇相應(yīng)的protocol,進行序列化和反序列化;
四、TCompactProtocol與TBinaryProtocol的原理和區(qū)別
Thrift二進制序列化協(xié)議中,默認為TBinaryProtocol,關(guān)于TCompactProtocol的說明,為高效密集型的二進制序列化(varint)。
那么TCompactProtocol相對于TBinaryProtocol是怎樣做到高效密集的呢?TCompactProtocol是否一定比TBinaryProtocol高效?
我們以比較常用的i32類型為例,來解釋一下兩種方式各自的原理:
TBinaryProtocol
處理i32整型數(shù)據(jù)類型時,定義的是4個字節(jié)的數(shù)組,32位的長度正好可以保存到這4個字節(jié)組當中。如果我們分別以n1~n32來表示第1位到第32位,那么這個數(shù)組的數(shù)據(jù)結(jié)構(gòu)應(yīng)該為以下結(jié)構(gòu):
i32out[0] {n1 ?~ n8 }
i32out[1] {n9 ?~ n16}
i32out[2] {n17 ~ n24}
i32out[3] {n25 ~ n32}
這樣的實現(xiàn)很簡單.
對于其它類型,比如i16,也是類似的原理,不過是以2個字節(jié)的數(shù)組保存,在此不再說明了。
因為我們架構(gòu)中使用的是TCompactProtocol,所以我們需要重點了解一下該協(xié)議的序列化方式。
TCompactProtocol
在處理i32整型數(shù)據(jù)類型時,與TBinaryProtocol完全不同,采用的是1~5個字節(jié)組來保存。依然以n1~n32來表示第1位到第32位,數(shù)據(jù)結(jié)構(gòu)應(yīng)該為以下結(jié)構(gòu):
i32out[0] {1 , 0 , 0 , 0 , n1 ~ n4}
i32out[1] {1 , n5 ~ n11}
i32out[2] {1 , n12 ~ n18}
i32out[3] {1 , n19 ?~ n25}
i32out[4] {0?, n26 ?~ n32}
這是一種極端情況,5個字節(jié)全部占滿。
很顯然,這樣做比TBinaryProtocol復雜得多,而且還多了1個字節(jié),并沒有達到密集的目的。那是不是說明TBinaryProtocol更好?
先不急著下結(jié)論,舉個具體一點的例子來說明兩種實現(xiàn)的區(qū)別。
假如我們需要序列化一個十進制數(shù)值'10',那么它的二進制表示方式應(yīng)該為'1010',只用了4位,但i32會在前面自動補0,
則是:'00000000000000000000000000001010',那么如果使用TBinaryProtocol方式來保存,則應(yīng)該為以下結(jié)構(gòu):
i32out[0] {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0}
i32out[1] {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0}
i32out[2] {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0}
i32out[3] {0 , 0 , 0 , 0 , 1 , 0 , 1 , 0}
這樣有什么問題呢?那就是大量補足的0占用了寶貴空間。
接著我們再來看看TCompactProtocol會怎樣保存'10'這個數(shù)值:
i32out[0] {0 , 0 , 0 , 0 , 1 , 0 , 1 , 0}
TCompactProtocol只用了1個字節(jié),而TBinaryProtocol依然用了4個字節(jié)。這就是為什么說TCompactProtocol高效密集型的二進制序列化的原因。
TCompactProtocol的保存規(guī)則
TCompactProtocol每個字節(jié)的第1位是狀態(tài)位,第2位到第8位保存具體的數(shù)據(jù).
這有別于TBinaryProtocol的1到8位全部保存具體數(shù)據(jù)。這也是為什么極端情況下TCompactProtocol比TBinaryProtocol多占1個字節(jié)的原因。
TCompactProtocol的字節(jié)中第1位狀態(tài)位的意思是標記此字節(jié)后是否還有數(shù)據(jù)。1為有數(shù)據(jù),0為沒有數(shù)據(jù).
為了更容易理解,我們再舉一個例子,用TCompactProtocol來序列化十進制數(shù)值'300',二進制應(yīng)該為'100101100',用TCompactProtocol方式來保存則為如下結(jié)構(gòu):
i32out[0] {1 , 0 , 0 , 0 , 0 , 0 , 1 , 0}
i32out[1] {0 , 0 , 1 , 0 , 1 , 1 , 0 , 0}
這里將'100101100'拆分為了2部分,'10'和'0101100',在i32out[0]中保存了'10',并在第1位記為'1'來表示后面還有數(shù)據(jù),第7位和第8位保存'10',不足的幾位(第2位到第6位)補0;
所以i32out[0]為'10000010',在i32out[1]中保存了后面的'0101100',并在第1位記為'0'來表示后面沒有了,則第1位為'0',所以i32out[1]為'00101100'。
TCompactProtocol以這樣的原理來達到壓縮的目的。
thrift 文件:
namespace java mmxf.thrift;
struct Pair {
1: required string key
2: required string value
}
"1", "2" 這些數(shù)字標識符究竟有何含義? 它在序列化機制中究竟扮演什么樣的角色?
thrift官網(wǎng)描述:thrift的向后兼容性(Version)借助屬性標識(數(shù)字編號id + 屬性類型type)來實現(xiàn), 可以理解為在序列化后(屬性數(shù)據(jù)存儲由?field_name:field_value => id+type:field_value)。
所以,id很重要,一旦id順序混淆或者有變化,value值與name的對應(yīng)也會變換,name在其中并沒有映射作用。
注意:RPC服務(wù)數(shù)據(jù)發(fā)送方(生產(chǎn)者)和讀取方(消費者)如果同樣的字段name,id不同,獲取到的value是不一致的,會出現(xiàn)不同name的value互換的奇怪現(xiàn)象。
數(shù)據(jù)交換格式分類
當前的數(shù)據(jù)交換格式可以分為如下幾類:
1. 自解析型
序列化的數(shù)據(jù)包含完整的結(jié)構(gòu),包含了field名稱和value值. 比如xml/json/java serizable,百度的mcpack/compack, 都屬于此類. 即調(diào)整不同屬性的順序?qū)π蛄谢?反序列化不影響。
2. 半解析型
? ? ? ? 序列化的數(shù)據(jù),丟棄了部分信息,比如field名稱,但引入了index(常常是id+type的方式)來對應(yīng)具體屬性和值。這方面的代表有g(shù)oogle protobuf,thrift也屬于此類。
3. 無解析型
? ? ? ?傳說中百度的infpack實現(xiàn),就是借助該種方式來實現(xiàn),丟棄了很多有效信息,性能/壓縮比最好,不過向后兼容需要開發(fā)做一定的工作,詳情不知。