序列化與反序列化
我們在進行網(wǎng)絡通信調(diào)用的時候桩了,總是需要將內(nèi)存的數(shù)據(jù)塊經(jīng)過序列化促绵,轉(zhuǎn)換成為一種可以通過網(wǎng)絡流進行傳輸?shù)母袷奖愎蟆6@種格式在經(jīng)過了傳輸之后再經(jīng)過序列化,能還原成我們預想中的數(shù)據(jù)結(jié)構(gòu)稻励。
那么我們對于這種用于中間網(wǎng)絡傳輸?shù)臄?shù)據(jù)格式就有一定的要求。首先它可以準確地描述數(shù)據(jù)內(nèi)容愈涩,在此基礎(chǔ)上我們則希望它盡量的小望抽。
最開始流行起來的是XML,可擴展標記語言履婉。由于它可以用來標記數(shù)據(jù)煤篙、定義數(shù)據(jù)類型,所以用戶可以自己定義數(shù)據(jù)自己的語言毁腿,從而讓對不同的數(shù)據(jù)結(jié)構(gòu)化成統(tǒng)一的格式稱為了可能辑奈。
而另外一個我們熟知的則是JSON(JavaScript Object Notation, JS 對象簡譜)苛茂。盡管JSON中缺少了XML中的標簽屬性等描述方式,但是足夠簡介和清晰的層次結(jié)構(gòu)使得其成為了必XML更受歡迎的數(shù)據(jù)交換格式鸠窗。
同一份數(shù)據(jù)顯然JSON的數(shù)據(jù)量比XML所使用的空間更少妓羊。那么空間省略在哪里呢?一方面是json使用更簡單的字符來定義數(shù)據(jù)間的關(guān)聯(lián)關(guān)系稍计;另一方面是JSON減少了對數(shù)據(jù)類型的描述躁绸。但是丟少的數(shù)據(jù)類型再哪里呢?
以Java中的 OpenFeign 舉例臣嚣,JSON中缺少的類型定義被定義道程序中的接口中了净刮。當進行序列化與反序列化時,JSON格式并不記錄數(shù)據(jù)的類型硅则,具體的數(shù)據(jù)類型在序列化方與反序列化方通過事先約定的接口來進行定義庭瑰。這樣就減少了信息傳輸過程中的信息量,從而讓數(shù)據(jù)得以壓縮抢埋。
但是JSON由于沒有定義數(shù)據(jù)類型弹灭,所以在傳輸?shù)倪^程中實際上就都是文本流,那么這種方法還可以進一步壓縮嗎揪垄?
ProtoBuf的原理概要
結(jié)合上文的討論穷吮,我們先說結(jié)論:方法是有的,并寫當前的實現(xiàn)方式是ProtoBuf饥努。但在此之前我們先來了解一下ProtoBuf捡鱼。
我們可以先看看官方給出的定義與描述:
protocol buffers 是一種與語言無關(guān)、平臺無關(guān)酷愧、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)的方法驾诈,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲等溶浴。 Protocol Buffers 是一種靈活乍迄,高效,自動化機制的結(jié)構(gòu)數(shù)據(jù)序列化方法-可類比 XML士败,但是比 XML 更写沉健(3 ~ 10倍)、更快(20 ~ 100倍)谅将、更為簡單漾狼。 你可以定義數(shù)據(jù)的結(jié)構(gòu),然后使用特殊生成的源代碼輕松地在各種數(shù)據(jù)流中使用各種語言進行編寫和讀取結(jié)構(gòu)數(shù)據(jù)饥臂。你甚至可以更新數(shù)據(jù)結(jié)構(gòu)逊躁,而不破壞由舊數(shù)據(jù)結(jié)構(gòu)編譯的已部署程序。
同樣的隅熙,ProtoBuf也是一種支持序列化反序列化的方法稽煤,并且他具有很多優(yōu)點:
- 多語言
- 多平臺
- 體積小
- 擴展性好
實際上核芽,ProtoBuf提供了一種通用的數(shù)據(jù)描述方式,這種定義數(shù)據(jù)的方式是通用的念脯,就如同JSON或者XML一樣狞洋。
接下來我們來來回答本節(jié)一開始的問題,針對JSON來說绿店,ProtoBuf是如何將體積變得更小的呢吉懊?答案很簡單,就是為數(shù)據(jù)序列化反序列化提供更多的先驗知識假勿。
本文暫不過度深入ProtoBuf原理借嗽,但是可以通過一張圖來進行簡要說明(圖片來自網(wǎng)絡):
ProtoBuf中的數(shù)據(jù)是按順序進行排列,而整體的結(jié)構(gòu)為若干個field转培,每一個field中由Tag-[Length]-Value組成恶导。Length是可選的,而是否存在Length是通過Tag的類型來決定的浸须。也就是說如果是指定的類型惨寿,比如int64,那我們就可以知道Value的長度删窒,也就不用在依靠Length來對其空間進行描述(redis中的壓縮列表也是這個思想)裂垦。
那么field應該對應的是什么字段呢?這個則是在序列化與反序列化時在ProtoBuf的服務端與客戶端之間進行預先定義的肌索。而因為提前定義了field的類型蕉拢、排序,所以field本身可以不用對字段名诚亚、字段位置進行描述晕换,只需要根據(jù)字段類型選用合適的二進制序列化方法,將字段本身的value值進行序列化傳輸即可站宗。
稍微總結(jié)一下:
ProtoBuf通過對傳輸字段的名稱闸准、順序進行預定義,從而在傳輸結(jié)構(gòu)中只需要順序的記錄每個字段的類型標簽和二進制值份乒。
二進制序列化
盡管上文和官方中都是以XML或者JSON來對ProtoBuf進行對比恕汇。但是因為ProtoBuf本身就是二進制序列化方式,所以從壓縮比上比較感覺有點欺負人或辖。
對應的在Java中二進制常用的序列化器有Kryo和Hessian。但事實上枣接,由于Kryo和Hessian中都需要對Java類名和字段信息進行存儲颂暇。而ProtoBuf則只有Tag-Length-Value的數(shù)據(jù)對,且Value更是有針對性的特殊編碼但惶,所以空間占用小的很多耳鸯。
Kryo是專門針對Java進行優(yōu)化了的湿蛔。所以在使用的便捷性上來說Kryo則更加方便。但ProtoBuf是跨平臺的县爬,且由于進行了字段的順序定義阳啥,所以似的ProtoBuf定義后的接口是可以向前兼容的(只向后追加字段),而這種優(yōu)勢是Kryo所沒有的财喳。
使用ProtoBuf
ProtoBuf是跨語言的察迟,使用ProtoBuf的第一步是先定一個proto 文件,而由于ProtoBuf 2和3語言版本的不同耳高,其定義格式會有所不同扎瓶,具體的細節(jié)還是得參考官方文檔:https://developers.google.cn/protocol-buffers/docs/proto3
對于ProtoBuf 3 的定義文檔我們可以按如下方法定義:
syntax = "proto3";//指定版本為proto3,默認為proto2message SearchRequest { string query = 1; int32 page_number = 2; repeated int32 result = 3;}其中message關(guān)鍵字是定義的文件名,而 string泌枪、int32則是預定的字段類型概荷,repeated則是描述字段為可重復任意多次的字段。
ProtoBuf通過這種形式的文件定義了傳輸信息的文件結(jié)構(gòu)碌燕。
但是之前小節(jié)中我們知道了ProtoBuf是通過Tag-[Length]-Value組成的數(shù)據(jù)組來進行信息傳輸?shù)奈笾ぃ敲磒roto文件中定義的內(nèi)容如何轉(zhuǎn)換為實際傳輸?shù)膶ο竽兀?/p>
ProtoBuf的做法是,為每一種語言提供一個生成器protoc修壕。通過使用protoc則可以根據(jù).proto文件生成為一組java文件愈捅。對應的官方語法演示樣例為:
protoc --proto_path=src --java_out=build/gen src/foo.proto官方的生成參考為:https://developers.google.com/protocol-buffers/docs/reference/java-generated
生成后的java文件將提供對應的實體以及數(shù)據(jù)的構(gòu)造方法等文件,從而支持后續(xù)的使用叠殷。
需要注意的是改鲫,ProtoBuf是本質(zhì)上是序列化方法,具體是通過Spring Cloud 的OpenFeign進行接口調(diào)用林束,還是通過grpc進行接口調(diào)用像棘,都是可以的。
最后
本文對ProtoBuff進行了概念的整理壶冒,并沒有對每個細節(jié)都進行深入的梳理缕题,可以當作概念科普來進行閱讀。