天天看點

Dubbo + Kryo 實作高速序列化

dubbo rpc 是 dubbo 體系中最核心的一種高性能、高吞吐量的遠端調用方式,可以稱之為多路複用的 tcp 長連接配接調用:

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

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

dubbo rpc 主要用于兩個 dubbo 系統之間的遠端調用,特别适合高并發、小資料的網際網路場景。而序列化對于遠端調用的響應速度、吞吐量、網絡帶寬消耗等同樣也起着至關重要的作用,是我們提升分布式系統性能的最關鍵因素之一。

dubbo 中支援的序列化方式:

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。

在 provider 和 consumer 項目啟用 kryo 高速序列化功能,兩個項目的配置方式相同

Dubbo + Kryo 實作高速序列化
Dubbo + Kryo 實作高速序列化

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

Dubbo + Kryo 實作高速序列化

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

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

Dubbo + Kryo 實作高速序列化

由于注冊被序列化的類僅僅是出于性能優化的目的,是以即使你忘記注冊某些類也沒有關系。事實上,即使不注冊任何類,kryo 和 fst 的性能依然普遍優于 hessian 和 dubbo 序列化。

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

另外,有人也會覺得手工注冊被序列化的類是一種相對繁瑣的工作,是不是可以用 annotation 來标注,然後系統來自動發現并注冊。但這裡 annotation 的局限是,它隻能用來标注你可以修改的類,而很多序列化中引用的類很可能是你沒法做修改的(比如第三方庫或者 jdk 系統類或者其他項目的類)。另外,添加 annotation 畢竟稍微的“污染”了一下代碼,使應用代碼對架構增加了一點點的依賴性。

除了 annotation,我們還可以考慮用其它方式來自動注冊被序列化的類,例如掃描類路徑,自動發現實作 serializable 接口(甚至包括 externalizable)的類并将它們注冊。當然,我們知道類路徑上能找到 serializable 類可能是非常多的,是以也可以考慮用 package 字首之類來一定程度限定掃描範圍。

當然,在自動注冊機制中,特别需要考慮如何保證服務提供端和消費端都以同樣的順序(或者 id)來注冊類,避免錯位,畢竟兩端可被發現然後注冊的類的數量可能都是不一樣的。

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

另外,kryo 和 fst 都不需要被序列化類實作 serializable 接口,但我們還是建議每個被序列化類都去實作 serializable 接口,因為這樣可以保持和 java 序列化以及 dubbo 序列化的相容性,另外也使我們未來采用上述某些自動注冊機制帶來可能。

兩台獨立伺服器

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 + Kryo 實作高速序列化
Dubbo + Kryo 實作高速序列化
Dubbo + Kryo 實作高速序列化
Dubbo + Kryo 實作高速序列化
Dubbo + Kryo 實作高速序列化

就目前結果而言,我們可以看到不管從生成位元組的大小,還是平均響應時間和平均 tps,kryo 和 fst 相比 dubbo rpc 中原有的序列化方式都有非常顯著的改進。