目前業界有各類各樣的網絡輸出傳輸時的序列化和反序列化方案,它們在技術上的實現的初衷和背景有較大的區別,所以在設計的架構也會有很大的區別,最終在落地後的:解析速度、對系統的影響、傳輸數據的大小、可維護性及可閱讀性等方面有着較大的區別,本文分享一些我在一些常見序列化技術的分析和理解:
java
文章分紅3個部分:程序員
一、列舉常見的序列化和反序列化方案(ObjectXXStream、XML、JSON)算法
二、MySQL JDBC結果集的處理方案數據庫
三、Google Protocol Buffer處理方案編程
【1、常見的在API及消息通訊調的用中Serialize方案】:數組
方案一、基於Java原生的ObjectOutputStream.write()和ObjectInputStream.read()來進行對象序列化和反序列化。服務器
方案二、基於JSON進行序列化和反序列化。網絡
方案三、基於XML進行序列化和反序列化。架構
【方案1淺析,ObjectXXXStream】:框架
優勢:
(1)、由Java自帶API序列化,簡單、方便、無第三方依賴。
(2)、不用擔憂其中的數據解析會丟失精度、丟失字段、Object的反序列化類型不肯定等問題。
缺點:
(1)、雙方調試麻煩,發送方和接收方最好是同版本的對象描述,不然會有奇怪的問題,調試周期相對長,跨團隊合做升級問題不少。
(2)、傳遞的對象中包含了元數據信息,佔用空間較大。
【方案2淺析,JSON序列化】:
優勢:
(1)、簡單、方便,無需關注要序列化的對象格式。
(2)、開源界有較多的組件能夠支持,例如FastJSON性能很是好。
(3)、在如今不少RPC的框架中,基本都支持這樣的方案。
缺點:
(1)、對象屬性中若是包含Object類型,在反序列化的時候若是業務也自己也不明確數據類型,處理起來會很麻煩。
(2)、因爲文本類型,因此必定會佔用較大的數據空間,例以下圖。
(3)、比較比較依賴於JSON的解析包的兼容性和性能,在JSON的一些細節處理上(例如一些非標的JSON),各自處理方式可能不同。
(4)、序列化不管任何數據類型先要轉換爲String,轉成byte[],會增長內存拷貝的次數。
(5)、反序列化的時候,必須將整個JSON反序列化成對象後才能進行讀取,你們應該知道,Java對象尤爲是層次嵌套較多的對象,佔用的內存空間將會遠遠大於數據自己的空間。
數據放大的極端案例1:
傳遞數據描述信息爲:
class PP {
long userId = 102333320132133L;
int passportNumber = 123456;
}
此時傳遞JSON的格式爲:
{
"userId":102333320132133,
"passportNumber":123456
}
咱們要傳遞的數據是1個long、1個int,也就是12個字節的數據,這個JSON的字符串長度將是實際的字節數(不包含回車、空格,這裏只是爲了可讀性,同時注意,這裏的long在JSON裏面是字符串了),這個字符串有:51個字節,也就是數據放到了4.25倍左右。
數據放大極端案例2:
當你的對象內部有數據是byte[]類型,JSON是文本格式的數據,是沒法存儲byte[]的,那麼要序列化這樣的數據,只有一個辦法就是把byte轉成字符,一般的作法有兩種:
(1)使用BASE64編碼,目前JSON中比較經常使用的作法。
(2)按照字節進行16進制字符編碼,例如字符串:「FF」表明的是0xFF這個字節。
不論上面兩種作法的那一種,1個字節都會變成2個字符來傳遞,也就是byte[]數據會被放大2倍以上。爲何不用ISO-8859-1的字符來編碼呢?由於這樣編碼後,在最終序列化成網絡byte[]數據後,對應的byte[]雖然沒變大,可是在反序列化成文本的時候,接收方並不知道是ISO-8859-1,還會用例如GBK、UTF-8這樣比較常見的字符集解析成String,才能進一步解析JSON對象,這樣這個byte[]可能在編碼的過程當中被改變了,要處理這個問題會很是麻煩。
【方案2淺析,XML序列化】:
優勢:
(1)、使用簡單、方便,無需關注要序列化的對象格式
(2)、可讀性較好,XML在業界比較通用,你們也習慣性在配置文件中看到XML的樣子
(3)、大量RPC框架都支持,經過XML能夠直接造成文檔進行傳閱
缺點:
(1)、在序列化和反序列化的性能上一直不是太好。
(2)、也有與JSON一樣的數據類型問題,和數據放大的問題,同時數據放大的問題更爲嚴重,同時內存拷貝次數也和JSON類型,不可避免。
XML數據放大說明:
XML的數據放大一般比JSON更爲嚴重,以上面的JSON案例來說,XML傳遞這份數據一般會這樣傳:
<Msg>
<userId>102333320132133</userId>
<passportNumber>123456<passportNumber>
<Msg>
這個消息就有80+以上的字節了,若是XML裏面再搞一些Property屬性,對象再嵌套嵌套,那麼這個放大的比例有可能會達到10倍都是有可能的,所以它的放大比JSON更爲嚴重,這也是爲何如今愈來愈多的API更加喜歡用JSON,而不是XML的緣由。
【放大的問題是什麼】:
(1)、花費更多的時間去拼接字符串和拷貝內存,佔用更多的Java內存,產生更多的碎片。
(2)、產生的JSON對象要轉爲byte[]須要先轉成String文本再進行byte[]編碼,由於這自己是文本協議,那麼天然再多一次內存全量的拷貝。
(3)、傳輸過程因爲數據被放大,佔用更大的網絡流量。
(4)、因爲網絡的package變多了,因此TCP的ACK也會變多,天然系統也會更大,同等丟包率的狀況下丟包數量會增長,總體傳輸時間會更長,若是這個數據傳送的網絡延遲很大且丟包率很高,咱們要儘可能下降大小;壓縮是一條途徑,可是壓縮會帶來巨大的CPU負載提升,在壓縮前儘可能下降數據的放大是咱們所指望的,而後傳送數據時根據RT和數據大小再斷定是否壓縮,有必要的時候,壓縮前若是數據過大還能夠進行部分採樣數據壓縮測試壓縮率。
(5)、接收方要處理數據也會花費更多的時間來處理。
(6)、因爲是文本協議,在處理過程當中會增長開銷,例如數字轉字符串,字符串轉數字;byte[]轉字符串,字符串轉byte[]都會增長額外的內存和計算開銷。
不過因爲在平時大量的應用程序中,這個開銷相對業務邏輯來說簡直微不足道,因此優化方面,這並非咱們關注的重點,但面臨一些特定的數據處理較多的場景,即核心業務在數據序列化和反序列化的時候,就要考慮這個問題了,那麼下面我繼續討論問題。
此時提出點問題:
(1)、網絡傳遞是否是有更好的方案,若是有,爲何如今沒有大面積採用?
(2)、相對底層的數據通訊,例如JDBC是如何作的,若是它像上面3種方案傳遞結果集,會怎麼樣?
【2、MySQL JDBC數據傳遞方案】:
在前文中提到數據在序列化過程被放大數倍的問題,咱們是否想看看一些相對底層的通訊是否也是如此呢?那麼咱們以MySQL JDBC爲例子來看看它與JDBC之間進行通訊是否也是如此。
JDBC驅動程序根據數據庫不一樣有不少實現,每一種數據庫實現細節上都有巨大的區別,本文以MySQL JDBC的數據解析爲例(MySQL 8.0之前),給你們說明它是如何傳遞數據的,而傳遞數據的過程當中,相信你們最爲關注的就是ResultSet的數據是如何傳遞的。
拋開結果集中的MetaData等基本信息,單看數據自己:
(1)JDBC會讀取數據行的時候,首先會從緩衝區讀取一個row packege,row package就是從網絡package中拿到的,根據協議中傳遞過來的package的頭部斷定package大小,而後從網絡緩衝中讀取對應大小的內容,下圖想表達網絡傳遞的package和業務數據中的package之間可能並非徹底對應的。另外,網絡中的package若是都到了本地緩衝區,邏輯上講它們是連續的(圖中故意分開是讓你們瞭解到網絡中傳遞是分不一樣的package傳遞到本地的),JDBC從本地buffer讀取row package這個過程就是內核package到JVM的package拷貝過程,對於咱們Java來說,咱們主要關注row package(JDBC中可能存在一些特殊狀況讀取過來的package並非行級別的,這種特殊狀況請有興趣的同窗自行查閱源碼)。
(2)、單行數據除頭部外,就是body了,body部分包含各類各樣不一樣的數據類型,此時在body上放數據類型顯然是佔空間的,因此數據類型是從metadata中提取的,body中數據列的順序將會和metdata中的列的順序保持一致。
(3)、MySQL詳細解析數據類型:
3.一、若是Metadata對應數據發現是int、longint、tinyint、year、float、double等數據類型會按照定長字節數讀取,例如int天然按照4字節讀取,long會按照8字節讀取。
3.二、若是發現是其它的類型,例如varchar、binary、text等等會按照變長讀取。
3.三、變長字符串首先讀取1個字節標誌位。
3.四、若是這個標誌位的值小於等於250,則直接表明後續字節的長度(注意字符串在這裏是算轉換爲字節的長度),這樣確保大部分業務中存放的變長字符串,在網絡傳遞過程當中只須要1個字節的放大。
3.五、若是這個標誌位是:251,表明這個字段爲NULL
3.六、若是標誌位是:252,表明須要2個字節表明字段的長度,此時加上標誌位就是3個字節,在65536之內的長度的數據(64KB),注意,這裏會在轉成long的時候高位補0,因此2個字節能夠填滿到65536,只須要放大3個字節來表示這個數據。
3.七、若是標誌位是:253,表明須要4個本身大表字段的長度,能夠表示4GB(同上高位補0),這樣的數據幾乎不會出如今數據庫裏面,即便出現,只會出現5個字節的放大。
3.八、若是標誌位是:254,8個字節表明長度,此時沒有高位補0,最多能夠讀取Long.MAX_VALUE的長度,可是這個空間目前不可能有內存放得下,因此無需擔憂使用問題,此時9個字節的放大,源碼以下圖:
(4)、咱們先按照這個理解,MySQL在傳遞數據的過程當中,對數據的放大是很小很小的,是否是真的這樣呢?請下面第5點說明。
補充說明:
a、在MySQL JDBC中對於ResultSetRow數據的解析(除對MySQL 8.0以上JDBC版本)有2個實現類:BufferRow、ByteArrayRow,這兩種方式在讀取單行數據在解析這個階段是同樣的邏輯,只不過解析存放數據的方式有所不一樣,BufferRow一個會解析成數據行的byte[],ByteArrayRow會解析成byte[][]二維數組,第二維就是每1個列的信息,這都是客戶端行爲,與網絡傳遞數據的格式無關。(二者在不一樣場景下使用,例如其中一種場景是:ByteArrayRow在遊標開啓UPDATE模式的時候會啓用,但這不是本文的重點,這裏提到主要告知你們,不管哪種方式,讀取數據的方式是一致的)
b、在MySQL JDBC中的RowData是ResultSet裏面數據處理的入口,其實現類有3個:RowStatStatic、RowDataCursor、RowDataDynamic,這雖然有3個實現類,可是一樣不會影響數據的格式,它們只是從緩衝區讀取數據的方式有所不一樣:RowStatStatic、RowDataCursor會每次將緩衝區的數據所有讀取到JDBC當中造成數組,RowDataCursor在處理上有一個區別在於數據庫每次返回的是FetchSize大小的數據內容(實現的細節在上一篇文章中有提到);RowDataDynamic是須要行的時候再從pakcege中去讀,package讀取完成後就嘗試讀取下一個package。這些都不會影響數據自己在網絡上的傳遞格式,因此文本提到的解析是目前MySQL JDBC比較通用的解析,與它內部的執行路徑無關。
(5)、以BufferRow爲例,當你發起getString('xxx')、getDate(int)的時候,首先它須要在內部找到是第幾個列(傳數字省略該動做),而後其內部會有一個lastRequestedIndex、lastRequestedPos分別記錄最後讀取的第幾個字段和所在字節的位置,若是你傳入的index比這個index大,則從當前位置開始,向後掃描,掃描規則和上面的數據庫寬度一致,找到對應位置,拷貝出對應的byte[]數組,轉換你要的對象類型。
PS:lastRequestedIndex、lastRequestedPos這種其實就是JDBC認爲你絕大部分狀況是從前向後讀取的,所以這樣讀取對JDBC程序也是最友好的方案,不然指針向前移動,須要從0開始,理由很簡單(數據的長度不是在尾部,而是在頭部),所以指針來回來回移動的時候,這樣會產生不少開銷,同時會產生更多的內存拷貝出來的碎片。ByteArrayRow雖然能夠解決這個問題,可是其自己會佔用相對較大的空間另外,其內部的二維數組返回的byte[]字節是能夠被外部所修改的(由於沒有拷貝)。
另外,按照這種讀取數據的方式,若是單行數據過大(例若有大字段100MB+),讀取到Java內存裏面來,即便使用CursorFetch和Stream讀取,讀取幾十條數據,就能把JVM內存幹掛掉。到目前爲止,我還沒看到MySQL裏面能夠「設置限制單行數據長度」的參數,後續估計官方支持這類特殊需求的可能性很小,大多也只能本身改源碼來實現。
【回到話題自己:MySQL和JDBC之間的通訊彷佛放大很小?】
其實否則,MySQL傳遞數據給JDBC默認是走文本協議的,而不是Binary協議,雖說它的byte[]數組不會像JSON那樣放大,並不算真正意義上的文本協議,可是它不少種數據類型默認狀況下,都是文本傳輸,例如一個上面提到的帳號:102333320132133在數據庫中是8個本身,可是網絡傳遞的時候若是有文本格式傳遞將會是:15個字節,若是是DateTime數據在數據庫中能夠用8個字節存放,可是網絡傳遞若是按照YYYY-MM-DD HH:MI:SS傳遞,能夠達到19個字節,而當他們用String在網絡傳遞的時候,按照咱們前面提到的,MySQL會將其當成變長字符,所以會在數據頭部加上最少1個本身的標誌位。另外,這裏增長不只僅是幾個字節,而是你要取到真正的數據,接收方還須要進一步計算處理才能獲得,例如102333320132133用文本傳送後,接收方是須要將這個字符串轉換爲long類型才能能夠獲得long的,你們試想一下你處理500萬數據,每一行數據有20個列,有大量的相似的處理不是開銷增長了特別多呢?
JDBC和MySQL之間能夠經過binary協議來進行通訊的,也就是按照實際數字佔用的空間大小來進行通訊,可是比較坑的時,MySQL目前開啓Binary協議的方案是:「開啓服務端prepareStatemet」,這個一旦開啓,會有一大堆的坑出來,尤爲是在互聯網的編程中,我會在後續的文章中逐步闡述。
拋開「開啓binary協議的坑」,咱們認爲MySQL JDBC在通訊的過程當中對數據的編碼仍是很不錯的,很是緊湊的編碼(固然,若是你開啓了SSL訪問,那麼數據又會被放大,並且加密後的數據基本很難壓縮)。
對比傳統的數據序列化優劣勢彙總:
優點:
(1)、數據所有按照byte[]編碼後,因爲緊湊編碼,因此對數據自己的放大很小。
(2)、因爲編碼和解碼都沒有解析的過程,都是向ByteBuffer的尾部順序地寫,也就是說不用找位置,讀取的時候根據設計也能夠減小找位置,即便找位置也是移動偏移量,很是高效。
(3)、若是傳遞多行數據,反序列化的過程不用像XML或JSON那樣一次要將整個傳遞過來的數據所有解析後再處理,試想一下,若是5000行、20列的結果集,會產生多少Java對象,每個Java對象對數據自己的放大又是多少,採用字節傳遞後能夠按需轉變爲Java對象,使用完的Java對象能夠釋放,這樣就不用同時佔用那樣大的JVM內存,而byte[]數組也只是數據自己的大小,也能夠按需釋放。
(4)、相對前面提到的3種方式,例如JSON,它不須要在序列化和反序列化的時候要經歷一次String的轉換,這樣會減小一次內存拷貝。
(5)、本身寫代碼用相似的通訊方案,能夠在網絡優化上作到極致。
劣勢:
(1)、編碼是MySQL和MySQL JDBC之間自定義的,別人無法用(咱們能夠參考別人的思路)
(2)、byte編碼和解碼過程程序員本身寫,對程序員水平和嚴謹性要求都很高,前期須要大量的測試,後期在網絡問題上考慮稍有誤差就可能出現不可預期的Bug。(因此在公司內部須要把這些內容進行封裝,大部分程序員無需關注這個內容)。
(3)、從內存拷貝上來說,從rowBuffer到應用中的數據,這一層內存拷貝是沒法避免的,若是你寫自定義程序,在必要的條件下,這個地方能夠進一步減小內存拷貝,但沒法杜絕;同上文中提到,這點開銷,對於整個應用程序的業務處理來說,簡直微不足道。
爲何傳統通訊協議不選擇這樣作:
(1)、參考劣勢中的3點。
(2)、傳統API通訊,咱們更講究快速、通用,也就是會常常和不一樣團隊乃至不一樣公司調試代碼,要設計binary協議,開發成本和調試成本很是高。
(3)、可讀性,對於業務代碼來說,byte[]的可讀性較差,尤爲是對象嵌套的時候,byte[]表達的方式是很複雜的。
MySQL JDBC若是用binary協議後,數據的緊湊性是否是達到極致了呢?
按照通常的理解,就是達到極致了,全部數據都不會進一步放大,int就只用4個字節傳遞,long就只用8個字節傳遞,那麼還能繼續變小,難道壓縮來作?
非也、非也,在二進制的世界裏,若是你探究細節,還有更多比較神奇的東西,下面咱們來探討一下:
例如:
long id = 1L;
此時網絡傳遞的時候會使用8個字節來方,你們能夠看下8個字節的排布:
咱們先不考慮按照bit有31個bit是0,先按照字節來看有7個0,表明字節沒有數據,只有1個字節是有值的,你們能夠去看一下本身的數據庫中大量的自動增加列,在id小於4194303以前,前面5個字節是浪費掉的,在增加到4個字節(2的32次方-1)以前前面4個本身都是0,浪費掉的。另外,即便8個字節中的第一個字節開始使用,也會有大量的數據,中間字節是爲:0的機率極高,就像十進制中進入1億,那麼1億下面最多會有8個0,越高位的0約難補充上去。
若是真的想去嘗試,能夠用這個辦法:用1個字節來作標誌,但會佔用必定的計算開銷,因此是否爲了這個空間去作這個事情,由你決定,本文僅僅是技術性探討:
方法1:表達目前有幾個低位被使用的字節數,因爲long只有8個字節,因此用3個bit就夠了,另外5個bit是浪費掉的,也無所謂了,反序列化的時候按照高位數量補充0x00便可。
方法2:相對方法1,更完全,但處理起來更復雜,用1這個字節的8個bit的0、1分別表明long的8個字節是被使用,序列化和反序列化過程根據標誌位和數據自己進行字節補0x00操做,補充完整8個字節就是long的值了,最壞狀況是9個字節表明long,最佳狀況0是1個字節,字節中只佔用了2個字節的時候,即便數據變得至關大,也會有大量的數據的字節存在空位的狀況,在這些狀況下,就一般能夠用少於8個字節的狀況來表達,要用滿7個字節纔可以與原數字long的佔用空間同樣,此時的數據已是比2的48次方-1更大的數據了。
【3、Google Protocol Buffer技術方案】:
這個對於不少人來說未必用過,也不知道它是用來幹什麼的,不過我不得不說,它是目前數據序列化和反序列化的一個神器,這個東西是在谷歌內部爲了約定好本身內部的數據通訊設計出來的,你們都知道谷歌的全球網絡很是牛逼,那麼天然在數據傳輸方面作得那是至關極致,在這裏我會講解下它的原理,就自己其使用請你們查閱其它人的博客,本文篇幅所限無法step by step進行講解。
看到這個名字,應該知道是協議Buffer,或者是協議編碼,其目的和上文中提到的用JSON、XML用來進行RPC調用相似,就是系統之間傳遞消息或調用API。可是谷歌一方面爲了達到相似於XML、JSON的可讀性和跨語言的通用性,另外一方面又但願達到較高的序列化和反序列化性能,數據放大可以進行控制,因此它又但願有一種比底層編碼更容易使用,而又可使用底層編碼的方式,又具有文檔的可讀性能力。
它首先須要定義一個格式文件,以下:
syntax = "proto2";
package com.xxx.proto.buffer.test;
message TestData2 {
optional int32 id = 2;
optional int64 longId = 1;
optional bool boolValue = 3;
optional string name = 4;
optional bytes bytesValue = 5;
optional int32 id2 = 6;
}
這個文件不是Java文件,也不是C文件,和語言無關,一般把它的後綴命名爲proto(文件中一、二、3數字表明序列化的順序,反序列化也會按照這個順序來作),而後本地安裝了protobuf後(不一樣OS安裝方式不一樣,在官方有下載和說明),會產生一個protoc運行文件,將其加入環境變量後,運行命令指定一個目標目錄:
protoc --java_out=~/temp/ TestData2.proto
此時會在指定的目錄下,產生package所描述的目錄,在其目錄內部有1個Java源文件(其它語言的會產生其它語言),這部分代碼是谷歌幫你生成的,你本身寫的話太費勁,因此谷歌就幫你幹了;本地的Java project裏面要引入protobuf包,maven引用(版本自行選擇):
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
此時生成的代碼會調用這個谷歌包裏面提供的方法庫來作序列化動做,咱們的代碼只須要調用生成的這個類裏面的API就能夠作序列化和反序列化操做了,將這些生成的文件放在一個模塊裏面發佈到maven倉庫別人就能夠引用了,關於測試代碼自己,你們能夠參考目前有不少博客有提供測試代碼,仍是很好用的。
谷歌編碼比較神奇的是,你能夠按照對象的方式定義傳輸數據的格式,可讀性極高,甚至於相對XML和JSON更適合程序員閱讀,也能夠做爲交流文檔,不一樣語言都通用,定義的對象仍是能夠嵌套的,可是它序列化出來的字節比原始數據只大一點點,這尼瑪太厲害了吧。
通過測試不一樣的數據類型,故意製造數據嵌套的層數,進行二進制數組多層嵌套,發現其數據放大的比例很是很是小,幾乎能夠等價於二進制傳輸,因而我把序列化後的數據其二進制進行了輸出,發現其編碼方式很是接近於上面的JDBC,雖然有一些細節上的區別,可是很是接近,除此以外,它在序列化的時候有幾大特徵:
(1)、若是字段爲空,它不會產生任何字節,若是整合對象的屬性都爲null,產生的字節將是0
(2)、對int3二、int64這些數據採用了變長編碼,其思路和咱們上面描述有一些共通之處,就是一個int64值在比較小的時候用比較少的字節就能夠表達了,其內部有一套字節的移位和異或算法來處理這個事情。
(3)、它對字符串、byte[]沒有作任何轉換,直接放入字節數組,和二進制編碼是差很少的道理。
(4)、因爲字段爲空它均可以不作任何字節,它的作法是有數據的地方會有一個位置編碼信息,你們能夠嘗試經過調整數字順序看看生成出來的byte是否會發生改變;那麼此時它就有了很強兼容性,也就是普通的加字段是沒問題的,這個對於普通的二進制編碼來說很難作到。
(5)、序列化過程沒有產生metadata信息,也就是它不會把對象的結構寫在字節裏面,而是反序列化的接收方有同一個對象,就能夠反解析出來了。
這與我本身寫編碼有何區別?
(1)、本身寫編碼有不少不肯定性,寫很差的話,數據可能放得更大,也容易出錯。
(2)、google的工程師把內部規範後,谷歌開源的產品也大量使用這樣的通訊協議,愈來愈多的業界中間件產品開始使用該方案,就連MySQL數據庫最新版本在數據傳輸方面也會開始兼容protobuf。
(3)、谷歌至關於開始在定義一個業界的新的數據傳輸方案,即有性能又下降代碼研發的難度,也有跨語言訪問的能力,因此纔會有愈來愈多的人喜歡使用這個東西。
那麼它有什麼缺點呢?還真很少,它基本兼顧了不少序列化和反序列化中你須要考慮的全部的事情,達到了一種很是良好的平衡,可是硬要挑缺陷,咱們就得找場景才行:
(1)、protobuf須要雙方明確數據類型,且定義的文件中每個對象要明確數據類型,對於Object類型的表達沒有方案,你本身必須提早預知這個Object究竟是什麼類型。
(2)、使用repeated能夠表達數組,可是隻能表達相同類型的數據,例如上面提到的JDBC一行數據的多個列數據類型不一樣的時候,要用這個表達,會比較麻煩;另外,默認狀況下數組只能表達1維數組,要表達二維數組,須要使用對象嵌套來間接完成。
(3)、它提供的數據類型都是基本數據類型,若是不是普通類型,要本身想辦法轉換爲普通類型進行傳輸,例如從MongoDB查處一個Docment對象,這個對象序列化是須要本身先經過別的方式轉換爲byte[]或String放進去的,而相對XML、JSON普通是提供了遞歸的功能,可是若是protobuf要提供這個功能,必然會面臨數據放大的問題,通用和性能永遠是矛盾的。
(4)、相對於自定義byte的話,序列化和反序列化是一次性完成,不能逐步完成,這樣若是傳遞數組嵌套,在反序列化的時候會產生大量的Java對象,另外自定義byte的話能夠進一步減小內存拷貝,不過谷歌這個相對文本協議來說內存拷貝已經少不少了。
補充說明:
在第2點中提到repeated表達的數組,每個元素必須是同類型的,沒法直接表達不一樣類型的元素,由於它沒有像Java那樣Object[]這樣的數組,這樣它即便經過本地斷定Object的類型傳遞了,反序列化會很麻煩,由於接收方也不知道數據是什麼類型,而protobuf網絡傳遞數據是沒有metadata傳遞的,那麼斷定惟一的地方就是在客戶端本身根據業務須要進行傳遞。
所以,若是真的有必要的話,能夠用List<byte[]>表達一行數據的方案,也就是裏面的每1個byte[]元素經過其它地方得到metadata,例如數據庫的Metadata獲得,而後再本身去轉換,可是傳遞過程全是byte[];就JDBC來說,我我的更加推薦於一行數據使用一個byte[]來傳遞,而不是每一項數據用一個byte[],由於在序列化和反序列化過程當中,每個數組元素都會在protobuf會被包裝成對象,此時產生的Java對象數量是列的倍數,例若有40個列,會產生40倍的Java對象,很誇張吧。
總之,每一種序列化和反序列化方案目前都有應用場景,它們在設計之初決定了架構,也將決定了最終的性能、穩定性、系統開銷、網絡傳輸大小等等。
阿里雲雙十一1折拼團活動:已滿6人,都是最低折扣了
【滿6人】1核2G雲服務器99.5元一年298.5元三年 2核4G雲服務器545元一年 1227元三年
【滿6人】1核1G MySQL數據庫 119.5元一年
【滿6人】3000條國內短信包 60元每6月
參團地址:click.aliyun.com/m/100002029…