一、序列化協(xié)議
? ? ? ?Thrift可以讓你選擇客戶端與服務(wù)端之間傳輸通信協(xié)議的類別,在傳輸協(xié)議上總體上劃分為文本(text)和二進(jìn)制(binary)傳輸協(xié)議, 為節(jié)約帶寬宛琅,提供傳輸效率,一般情況下使用二進(jìn)制類型的傳輸協(xié)議為多數(shù),但有時(shí)會(huì)還是會(huì)使用基于文本類型的協(xié)議任连,這需要根據(jù)項(xiàng)目/產(chǎn)品中的實(shí)際需求(例如:調(diào)試的時(shí)候)。
序列化協(xié)議類型:
TBinaryProtocol:二進(jìn)制編碼格式進(jìn)行數(shù)據(jù)傳輸例诀。
TCompactProtocol:高效密集型的二進(jìn)制序列化協(xié)議随抠,使用Variable-Length Quantity (VLQ) 編碼對數(shù)據(jù)進(jìn)行壓縮。
TJSONProtocol:使用JSON的數(shù)據(jù)編碼協(xié)議進(jìn)行數(shù)據(jù)傳輸繁涂。
TSimpleJSONProtocol:這種節(jié)約只提供JSON只寫的協(xié)議拱她,適用于通過腳本語言解析。
TTupleProtocol(繼承自TCompactProtocol)
TDebugProtocol:在開發(fā)的過程中幫助開發(fā)人員調(diào)試用的扔罪,以文本的形式展現(xiàn)方便閱讀秉沼。
RPC框架中一般使用 TCompactProtocol。
二矿酵、傳輸層
Thrift中所有的傳輸層協(xié)議的基類是TTransport唬复。另外,需要說明的一點(diǎn)是全肮,thrift是基于TCP協(xié)議的敞咧。
傳輸協(xié)議類型:
TSocket:使用堵塞式I/O進(jìn)行傳輸,也是最常見的模式倔矾。
TFramedTransport:使用非阻塞方式妄均,以frame為單位進(jìn)行傳輸,類似于Java中的NIO哪自。
TFileTransport:以文件形式進(jìn)行傳輸丰包,雖然這種方式不提供Java的實(shí)現(xiàn),但是實(shí)現(xiàn)起來非常簡單壤巷。
TMemoryTransport:使用內(nèi)存I/O邑彪,如Java中的ByteArrayOutputStream實(shí)現(xiàn)。
TZlibTransport:使用執(zhí)行zlib壓縮胧华,與其他傳輸方式聯(lián)合使用寄症,不提供Java的實(shí)現(xiàn)。
TNonblockingTransport:使用非阻塞方式矩动,用于構(gòu)建異步客戶端有巧。
RPC框架中一般使用 TFramedTransport。
三悲没、Thrift的序列化和反序列化方式
步驟:
使用IDL創(chuàng)建thrift接口定義文件篮迎;
將thrift的定義文件轉(zhuǎn)換為對應(yīng)語言的源代碼;
選擇相應(yīng)的protocol,進(jìn)行序列化和反序列化甜橱;
四逊笆、TCompactProtocol與TBinaryProtocol的原理和區(qū)別
Thrift二進(jìn)制序列化協(xié)議中,默認(rèn)為TBinaryProtocol岂傲,關(guān)于TCompactProtocol的說明难裆,為高效密集型的二進(jìn)制序列化(varint)。
那么TCompactProtocol相對于TBinaryProtocol是怎樣做到高效密集的呢镊掖?TCompactProtocol是否一定比TBinaryProtocol高效乃戈?
我們以比較常用的i32類型為例,來解釋一下兩種方式各自的原理:
TBinaryProtocol
處理i32整型數(shù)據(jù)類型時(shí)亩进,定義的是4個(gè)字節(jié)的數(shù)組偏化,32位的長度正好可以保存到這4個(gè)字節(jié)組當(dāng)中。如果我們分別以n1~n32來表示第1位到第32位镐侯,那么這個(gè)數(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}
這樣的實(shí)現(xiàn)很簡單.
對于其它類型,比如i16驶冒,也是類似的原理苟翻,不過是以2個(gè)字節(jié)的數(shù)組保存,在此不再說明了骗污。
因?yàn)槲覀兗軜?gòu)中使用的是TCompactProtocol崇猫,所以我們需要重點(diǎn)了解一下該協(xié)議的序列化方式。
TCompactProtocol
在處理i32整型數(shù)據(jù)類型時(shí)需忿,與TBinaryProtocol完全不同诅炉,采用的是1~5個(gè)字節(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個(gè)字節(jié)全部占滿。
很顯然,這樣做比TBinaryProtocol復(fù)雜得多汗洒,而且還多了1個(gè)字節(jié)议纯,并沒有達(dá)到密集的目的。那是不是說明TBinaryProtocol更好?
先不急著下結(jié)論溢谤,舉個(gè)具體一點(diǎn)的例子來說明兩種實(shí)現(xiàn)的區(qū)別瞻凤。
假如我們需要序列化一個(gè)十進(jìn)制數(shù)值'10',那么它的二進(jìn)制表示方式應(yīng)該為'1010'世杀,只用了4位阀参,但i32會(huì)在前面自動(dòng)補(bǔ)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}
這樣有什么問題呢?那就是大量補(bǔ)足的0占用了寶貴空間。
接著我們再來看看TCompactProtocol會(huì)怎樣保存'10'這個(gè)數(shù)值:
i32out[0] {0 , 0 , 0 , 0 , 1 , 0 , 1 , 0}
TCompactProtocol只用了1個(gè)字節(jié)炕吸,而TBinaryProtocol依然用了4個(gè)字節(jié)伐憾。這就是為什么說TCompactProtocol高效密集型的二進(jìn)制序列化的原因。
TCompactProtocol的保存規(guī)則
TCompactProtocol每個(gè)字節(jié)的第1位是狀態(tài)位赫模,第2位到第8位保存具體的數(shù)據(jù).
這有別于TBinaryProtocol的1到8位全部保存具體數(shù)據(jù)树肃。這也是為什么極端情況下TCompactProtocol比TBinaryProtocol多占1個(gè)字節(jié)的原因。
TCompactProtocol的字節(jié)中第1位狀態(tài)位的意思是標(biāo)記此字節(jié)后是否還有數(shù)據(jù)瀑罗。1為有數(shù)據(jù)胸嘴,0為沒有數(shù)據(jù).
為了更容易理解,我們再舉一個(gè)例子,用TCompactProtocol來序列化十進(jìn)制數(shù)值'300'斩祭,二進(jìn)制應(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位)補(bǔ)0诬像;
所以i32out[0]為'10000010'屋群,在i32out[1]中保存了后面的'0101100',并在第1位記為'0'來表示后面沒有了坏挠,則第1位為'0'芍躏,所以i32out[1]為'00101100'。
TCompactProtocol以這樣的原理來達(dá)到壓縮的目的降狠。
thrift 文件:
namespace java mmxf.thrift;
struct Pair {
1: required string key
2: required string value
}
"1", "2" 這些數(shù)字標(biāo)識(shí)符究竟有何含義? 它在序列化機(jī)制中究竟扮演什么樣的角色?
thrift官網(wǎng)描述:thrift的向后兼容性(Version)借助屬性標(biāo)識(shí)(數(shù)字編號(hào)id + 屬性類型type)來實(shí)現(xiàn), 可以理解為在序列化后(屬性數(shù)據(jù)存儲(chǔ)由?field_name:field_value => id+type:field_value)对竣。
所以,id很重要榜配,一旦id順序混淆或者有變化否纬,value值與name的對應(yīng)也會(huì)變換,name在其中并沒有映射作用芥牌。
注意:RPC服務(wù)數(shù)據(jù)發(fā)送方(生產(chǎn)者)和讀取方(消費(fèi)者)如果同樣的字段name烦味,id不同,獲取到的value是不一致的壁拉,會(huì)出現(xiàn)不同name的value互換的奇怪現(xiàn)象谬俄。
數(shù)據(jù)交換格式分類
當(dāng)前的數(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實(shí)現(xiàn)算灸,就是借助該種方式來實(shí)現(xiàn)扼劈,丟棄了很多有效信息,性能/壓縮比最好菲驴,不過向后兼容需要開發(fā)做一定的工作荐吵,詳情不知。