JAVA序列化和反序列化的常見格式

dubbo RPC是dubbo體系中最核心的一種高性能、高吞吐量的遠程調用方式,我喜歡稱之爲多路複用的TCP長連接調用,簡單的說:

長連接:避免了每次調用新建TCP連接,提高了調用的響應速度

多路複用:單個TCP連接可交替傳輸多個請求和響應的消息,降低了連接的等待閒置時間,從而減少了同樣併發數下的網絡連接數,提高了系統吞吐量。

dubbo RPC主要用於兩個dubbo系統之間作遠程調用,特別適合高併發、小數據的互聯網場景。

而序列化對於遠程調用的響應速度、吞吐量、網絡帶寬消耗等同樣也起着至關重要的作用,是我們提升分佈式系統性能的最關鍵因素之一。

在dubbo RPC中,同時支持多種序列化方式,例如:

dubbo序列化:阿里尚未開發成熟的高效java序列化實現,阿里不建議在生產環境使用它

hessian2序列化:hessian是一種跨語言的高效二進制序列化方式。但這裏實際不是原生的hessian2序列化,而是阿里修改過的hessian lite,它是dubbo RPC默認啓用的序列化方式

json序列化:目前有兩種實現,一種是採用的阿里的fastjson庫,另一種是採用dubbo中自己實現的簡單json庫,但其實現都不是特別成熟,而且json這種文本序列化性能一般不如上面兩種二進制序列化。

java序列化:主要是採用JDK自帶的Java序列化實現,性能很不理想。


通常情況下,這四種主要序列化方式的性能從上到下依次遞減。對於dubbo
RPC這種追求高性能的遠程調用方式來說,實際上只有1、2兩種高效序列化方式比較般配,而第1個dubbo序列化由於還不成熟,所以實際只剩下2可用,
所以dubbo RPC默認採用hessian2序列化。

但hessian是一個比較老的序列化實現了,而且它是跨語言的,所以不是單獨針對java進行優化的。而dubbo RPC實際上完全是一種Java to Java的遠程調用,其實沒有必要採用跨語言的序列化方式(當然肯定也不排斥跨語言的序列化)。

最近幾年,各種新的高效序列化方式層出不窮,不斷刷新序列化性能的上限,最典型的包括:

專門針對Java語言的:Kryo,FST等等

跨語言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack等等

這些序列化方式的性能多數都顯著優於hessian2(甚至包括尚未成熟的dubbo序列化)。

有鑑於此,我們爲dubbo引入Kryo和FST這兩種高效Java序列化實現,來逐步取代hessian2。

其中,Kryo是一種非常成熟的序列化實現,已經在Twitter、Groupon、Yahoo以及多個著名開源項目(如Hive、Storm)中廣泛的使用。而FST是一種較新的序列化實現,目前還缺乏足夠多的成熟使用案例,但我認爲它還是非常有前途的。

在面向生產環境的應用中,我建議目前更優先選擇Kryo。

啓用Kryo和FST

使用Kryo和FST非常簡單,只需要在dubbo RPC的XML配置中添加一個屬性即可:



註冊被序列化類

要讓Kryo和FST完全發揮出高性能,最好將那些需要被序列化的類註冊到dubbo系統中,例如,我們可以實現如下回調接口:

public class SerializationOptimizerImpl implements SerializationOptimizer { public Collection getSerializableClasses() { List classes = new LinkedList();
classes.add(BidRequest.class);
classes.add(BidResponse.class);
classes.add(Device.class);
classes.add(Geo.class);
classes.add(Impression.class);
classes.add(SeatBid.class); return classes;
}
}
然後在XML配置中添加:


在註冊這些類後,序列化的性能可能被大大提升,特別針對小數量的嵌套對象的時候。

當然,在對一個類做序列化的時候,可能還級聯引用到很多類,比如Java集合類。針對這種情況,我們已經自動將JDK中的常用類進行了註冊,所以你不需要重複註冊它們(當然你重複註冊了也沒有任何影響),包括:

GregorianCalendar
InvocationHandler
BigDecimal
BigInteger
Pattern
BitSet
URI
UUID
HashMap
ArrayList
LinkedList
HashSet
TreeSet
Hashtable
Date
Calendar
ConcurrentHashMap
SimpleDateFormat
Vector
BitSet
StringBuffer
StringBuilder
Object
Object[]
String[]
byte[]
char[]
int[]
float[]
double[]
由於註冊被序列化的類僅僅是出於性能優化的目的,所以即使你忘記註冊某些類也沒有關係。事實上,即使不註冊任何類,Kryo和FST的性能依然普遍優於hessian和dubbo序列化。

當然,有人可能會問爲什麼不用配置文件來註冊這些類?這是因爲要註冊的類往往數量較多,導致配置文件冗長;而且在沒有好的IDE支持的情況下,配置文件的編寫和重構都比java類麻煩得多;最後,這些註冊的類一般是不需要在項目編譯打包後還需要做動態修改的。

外,有人也會覺得手工註冊被序列化的類是一種相對繁瑣的工作,是不是可以用annotation來標註,然後系統來自動發現並註冊。但這裏
annotation的侷限是,它只能用來標註你可以修改的類,而很多序列化中引用的類很可能是你沒法做修改的(比如第三方庫或者JDK系統類或者其他項
目的類)。另外,添加annotation畢竟稍微的“污染”了一下代碼,使應用代碼對框架增加了一點點的依賴性。
除了
annotation,我們還可以考慮用其它方式來自動註冊被序列化的類,例如掃描類路徑,自動發現實現Serializable接口(甚至包括
Externalizable)的類並將它們註冊。當然,我們知道類路徑上能找到Serializable類可能是非常多的,所以也可以考慮用
package前綴之類來一定程度限定掃描範圍。
當然,在自動註冊機制中,特別需要考慮如何保證服務提供端和消費端都以同樣的順序(或者ID)來註冊類,避免錯位,畢竟兩端可被發現然後註冊的類的數量可能都是不一樣的。
無參構造函數和Serializable接口


果被序列化的類中不包含無參的構造函數,則在Kryo的序列化中,性能將會大打折扣,因爲此時我們在底層將用Java的序列化來透明的取代Kryo序列
化。所以,儘可能爲每一個被序列化的類添加無參構造函數是一種最佳實踐(當然一個java類如果不自定義構造函數,默認就有無參構造函數)。

另外,Kryo和FST本來都不需要被序列化都類實現Serializable接口,但我們還是建議每個被序列化類都去實現它,因爲這樣可以保持和Java序列化以及dubbo序列化的兼容性,另外也使我們未來採用上述某些自動註冊機制帶來可能。

序列化性能分析與測試

本文我們主要討論的是序列化,但在做性能分析和測試的時候我們並不單獨處理每種序列化方式,而是把它們放到dubbo RPC中加以對比,因爲這樣更有現實意義。

測試環境
粗略如下:

兩臺獨立服務器

4核Intel(R) Xeon(R) CPU E5-2603 0 @ 1.80GHz

8G內存

虛擬機之間網絡通過百兆交換機

CentOS 5

JDK 7

Tomcat 7

JVM參數-server -Xms1g -Xmx1g -XX:PermSize=64M -XX:+UseConcMarkSweepGC

當然這個測試環境較有侷限,故當前測試結果未必有非常權威的代表性。

測試腳本
和dubbo自身的基準測試保持接近:

10個併發客戶端持續不斷髮出請求:

傳入嵌套複雜對象(但單個數據量很小),不做任何處理,原樣返回

傳入50K字符串,不做任何處理,原樣返回(TODO:結果尚未列出)

進行5分鐘性能測試。(引用dubbo自身測試的考慮:“主要考察序列化和網絡IO的性能,因此服務端無任何業務邏輯。取10併發是考慮到http協議在高併發下對CPU的使用率較高可能會先打到瓶頸。”)

Dubbo RPC中不同序列化生成字節大小比較
序列化生成字節碼的大小是一個比較有確定性的指標,它決定了遠程調用的網絡傳輸時間和帶寬佔用。

針對複雜對象的結果如下(數值越小越好):

序列化實現 請求字節數 響應字節數
Kryo 272 90
FST 288 96
Dubbo Serialization 430 186
Hessian 546 329
FastJson 461 218
Json 657 409
Java Serialization 963 630
no image found

Dubbo RPC中不同序列化響應時間和吞吐量對比
遠程調用方式 平均響應時間 平均TPS(每秒事務數)
REST: Jetty + JSON 7.806 1280
REST: Jetty + JSON + GZIP TODO TODO
REST: Jetty + XML TODO TODO
REST: Jetty + XML + GZIP TODO TODO
REST: Tomcat + JSON 2.082 4796
REST: Netty + JSON 2.182 4576
Dubbo: FST 1.211 8244
Dubbo: kyro 1.182 8444
Dubbo: dubbo serialization 1.43 6982
Dubbo: hessian2 1.49 6701
Dubbo: fastjson 1.572 6352
no image found

no image found

測試總結
就目前結果而言,我們可以看到不管從生成字節的大小,還是平均響應時間和平均TPS,Kryo和FST相比Dubbo RPC中原有的序列化方式都有非常顯著的改進。

未來

未來,當Kryo或者FST在dubbo中當應用足夠成熟之後,我們很可能會將dubbo RPC的默認序列化從hessian2改爲它們中間的某一個。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章