Java序列化的目的主要有兩個:
1.網絡傳輸
2.對象持久化
當選行遠程跨迸程服務調用時,需要把被傳輸的Java對象編碼為字節數組或者ByteBuffer對象。而當遠程服務讀取到ByteBuffer對象或者字節數組時,需要將其解碼為發送時的Java 對象。這被稱為Java對象編解碼技術。
Java序列化僅僅是Java編解碼技術的一種,由於它的種種缺陷,衍生出了多種編解碼技術和框架
Java序列化的缺點
Java序列化從JDK1.1版本就已經提供,它不需要添加額外的類庫,只需實現java.io.Serializable並生成序列ID即可,因此,它從誕生之初就得到了廣泛的應用。
但是在遠程服務調用(RPC)時,很少直接使用Java序列化進行消息的編解碼和傳輸,這又是什么原因呢?下面通過分析.Tava序列化的缺點來找出答案。
1 無法跨語言
對於跨進程的服務調用,服務提供者可能會使用C十+或者其他語言開發,當我們需要和異構語言進程交互時Java序列化就難以勝任。由於Java序列化技術是Java語言內部的私有協議,其他語言並不支持,對於用戶來說它完全是黑盒。對於Java序列化后的字節數組,別的語言無法進行反序列化,這就嚴重阻礙了它的應用。
2 序列化后的碼流太大
通過一個實例看下Java序列化后的字節數組大小。
3序列化性能太低
無論是序列化后的碼流大小,還是序列化的性能,JDK默認的序列化機制表現得都很差。因此,我們邊常不會選擇Java序列化作為遠程跨節點調用的編解碼框架。
序列化 – 內置和第三方的MessagePack實戰
內置
Netty內置了對JBoss Marshalling和Protocol Buffers的支持
集成第三方MessagePack實戰(LengthFieldBasedFrame詳解)
LengthFieldBasedFrame詳解
maxFrameLength:表示的是包的最大長度,
lengthFieldOffset:指的是長度域的偏移量,表示跳過指定個數字節之后的才是長度域;
lengthFieldLength:記錄該幀數據長度的字段,也就是長度域本身的長度;
lengthAdjustment:長度的一個修正值,可正可負;
initialBytesToStrip:從數據幀中跳過的字節數,表示得到一個完整的數據包之后,忽略多少字節,開始讀取實際我要的數據
failFast:如果為true,則表示讀取到長度域,TA的值的超過maxFrameLength,就拋出一個 TooLongFrameException,而為false表示只有當真正讀取完長度域的值表示的字節之后,才會拋出 TooLongFrameException,默認情況下設置為true,建議不要修改,否則可能會造成內存溢出。
數據包大小: 14B = 長度域2B + "HELLO, WORLD"(單詞HELLO+一個逗號+一個空格+單詞WORLD)
長度域的值為12B(0x000c)。希望解碼后保持一樣,根據上面的公式,參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment 無需調整
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
數據包大小: 14B = 長度域2B + "HELLO, WORLD"
長度域的值為12B(0x000c)。解碼后,希望丟棄長度域2B字段,所以,只要initialBytesToStrip = 2即可。
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment 無需調整
4. initialBytesToStrip = 2 解碼過程中,丟棄2個字節的數據
數據包大小: 14B = 長度域2B + "HELLO, WORLD"。長度域的值為14(0x000E)
長度域的值為14(0x000E),包含了長度域本身的長度。希望解碼后保持一樣,根據上面的公式,參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment = -2 因為長度域為14,而報文內容為12,為了防止讀取報文超出報文本體,和將長度字段一起讀取進來,需要告訴netty,實際讀取的報文長度比長度域中的要少2(12-14=-2)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
在長度域前添加2個字節的Header。長度域的值(0x00000C) = 12。總數據包長度: 17=Header(2B) + 長度域(3B) + "HELLO, WORLD"
長度域的值為12B(0x000c)。編碼解碼后,長度保持一致,所以initialBytesToStrip = 0。參數應該為:
1. lengthFieldOffset = 2
2. lengthFieldLength = 3
3. lengthAdjustment = 0無需調整
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
Header與長度域的位置換了。總數據包長度: 17=長度域(3B) + Header(2B) + "HELLO, WORLD"
長度域的值為12B(0x000c)。編碼解碼后,長度保持一致,所以initialBytesToStrip = 0。參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 3
3. lengthAdjustment = 2 因為長度域為12,而報文內容為12,但是我們需要把Header的值一起讀取進來,需要告訴netty,實際讀取的報文內容長度比長度域中的要多2(12+2=14)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
帶有兩個header。HDR1 丟棄,長度域丟棄,只剩下第二個header和有效包體,這種協議中,一般HDR1可以表示magicNumber,表示應用只接受以該magicNumber開頭的二進制數據,rpc里面用的比較多。總數據包長度: 16=HDR1(1B)+長度域(2B) +HDR2(1B) + "HELLO, WORLD"
長度域的值為12B(0x000c)
1. lengthFieldOffset = 1 (HDR1的長度)
2. lengthFieldLength = 2
3. lengthAdjustment =1 因為長度域為12,而報文內容為12,但是我們需要把HDR2的值一起讀取進來,需要告訴netty,實際讀取的報文內容長度比長度域中的要多1(12+1=13)
4. initialBytesToStrip = 3 丟棄了HDR1和長度字段
帶有兩個header,HDR1 丟棄,長度域丟棄,只剩下第二個header和有效包體。總數據包長度: 16=HDR1(1B)+長度域(2B) +HDR2(1B) + "HELLO, WORLD"
長度域的值為16B(0x0010),長度為2,HDR1的長度為1,HDR2的長度為1,包體的長度為12,1+1+2+12=16。
1. lengthFieldOffset = 1
2. lengthFieldLength = 2
3. lengthAdjustment = -3因為長度域為16,需要告訴netty,實際讀取的報文內容長度比長度域中的要 少3(13-16= -3)
4. initialBytesToStrip = 3丟棄了HDR1和長度字段
MessagePack集成