標籤:turn inpu manual bind 效能 超過 關閉 網路 iat
1. Java序列化工具技術原理比較
- Binary Formats & language-specific ones
JavaBuiltIn(java原生)、JavaManual(根據成員變數類型,手工寫)、FstSerliazation、Kryo
- Binary formats-generic language-unspecific ones
Protobuf(Google)、Thrift(Facebook)、 AvroGeneric、Hessian
- JSON Format
Jackson、Gson、FastJSON
- JSON-like:
CKS (textual JSON-like format)、BSON(JSON-like format with extended datatypes)、JacksonBson、MongoDB
- XML-based formats
XmlXStream
java的序列化工具大致就可以分為以上幾類,簡單概括就分為二進位binary和文字格式設定(json、xml)兩大類。
在速度的對比上一般有如下規律:
binary > textual
language-specific > language-unspecific
而textual中,由json相比xml冗餘度更低因此速度上更勝一籌,而json又bson這類textual serialization技術上更成熟,架構的選擇上更豐富和優秀。下面重點介紹下Kryo、fast-serialiation、fastjson、protocol-buffer
2. 典型Java序列化工具分析
目前互連網公司廣泛使用Protobuf、Thrift、Avro等成熟的序列化解決方案來搭建RPC架構,這些都是久經考驗的解決方案。
2.1 Java原生序列化工具
Java本身提供的序列化工具基本上能勝任大多數情境下的序列化任務,關於其序列化機制,這篇文章很細緻的解釋了(7820018),值得一讀。Java內建的序列化工具在序列化過程中需要不僅需要將對象的完整的class name記錄下來,還需要把該類的定義也都記錄下,包括所有其他引用的類,這會是一筆很大的開銷,尤其是僅僅序列化單個對象的時候。正因為java序列化機制會把所有meta-data記錄下來,因此當修改了類的所在的包名後,還原序列化則會報錯。Java內建序列化工具的效能問題總結如下:
一個single object的序列化會 遞迴地,連同所有成員變數(instsnce variables)一起序列化了,這種預設機制很容易造成不必要的序列化開銷。
序列化和還原序列化過程需要上面的這種機制去遞迴並用反射機制去尋找所有成員變數的資訊,另外如果沒定義自己serialVersionUID的話,那麼對象及其他變數都必須自己產生一個。上述過程開銷很大。
使用預設序列化機制,所有序列化類別定義完整資訊都會被記錄下來,包括所有包名、父類資訊、以及成員變數
2.2 最佳化過的Java序列化工具
- kryo
kryo根據上述Java原生序列化機制的一些問題,對了很多最佳化工作,而且提供了很多serializer,甚至封裝了Unsafe類型的序列化方式,更多關於Unsafe類型的序列化方式,請參考這裡,需要注意的是,jdk1.7以後,預設關閉unsafe的類(sun.misc.Unsafe)包。更多kryo介紹參考kryo的wiki.
- fast-serialization
fst-serialozation相對來說是一個很新的序列化工具,雖然從2-1的評測上來看,速度於kryo有一些差距,但根據本人在生產環境上的情境上測試,效果幾乎於kryo一致,都能瞬間還原序列化出內容並渲染
2.3 JSON
比較優秀的JSON解析工具的表現還是比較好的,有些json解析工具甚至速度超過了一些二進位的序列化方式。
2.4 Protocol-Buffer
Protocol buffers是一個用來序列化結構化資料的技術,支援多種語言諸如C++、Java以及Python語言,可以使用該技術來持久化資料或者序列化成網路傳輸的資料。相比較一些其他的XML技術而言,該技術的一個明顯特點就是更加節省空間的(以二進位流儲存)、速度更快以及更加靈活。
另外Protobuf支援的資料類型相對較少,不支援常量類型。由於其設計的理念是純粹的展現層協議(Presentation Layer),目前並沒有一個專門支援Protobuf的RPC架構。
2.5 Thrift
Thrift是Facebook開源提供的一個高效能,輕量級RPC服務架構,其產生正是為了滿足當前大資料量、分布式、跨語言、跨平台資料通訊的需求。 但是,Thrift並不僅僅是序列化協議,而是一個RPC架構。 相對於JSON和XML而言,Thrift在空間開銷和解析效能上有了比較大的提升,對於對效能要求比較高的分布式系統,它是一個優秀的RPC解決方案;但是由於Thrift的序列化被嵌入到Thrift架構裡面, Thrift架構本身並沒有透出序列化和還原序列化介面,這導致其很難和其他傳輸層協議共同使用(例如HTTP)。
2.6 Avro
Avro解析效能高並且序列化之後的資料非常簡潔,比較適合於高效能的序列化服務。
Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開銷和解析效能方面可以和Protobuf媲美, JSON格式方便測試階段的調試。 Avro支援的資料類型非常豐富,包括C++語言裡面的union類型。Avro支援JSON格式的IDL和類似於Thrift和Protobuf的IDL(實驗階段),這兩者之間可以互轉。Schema可以在傳輸資料的同時發送,加上JSON的自我描述屬性,這使得Avro非常適合動態類型語言。 Avro在做檔案持久化的時候,一般會和Schema一起儲存,所以Avro序列化檔案自身具有自我描述屬性,所以非常適合於做Hive、Pig和MapReduce的持久化資料格式。對於不同版本的Schema,在進行RPC調用的時候,服務端和用戶端可以在握手階段對Schema進行互相確認,大大提高了最終的資料解析速度。
3.下面介紹幾種常用的Java序列化技術使用樣本
KryoRegister、FST、Kryo、Gson、Fastjson、JDK
3.1 JDK
public static byte[] serialize(Object obj) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); byte[] bs = baos.toByteArray(); baos.close(); oos.close(); return bs; } catch (IOException e) { throw new RuntimeException(e); }}public static Object deserialize(byte[] bits) { try { ByteArrayInputStream bais = new ByteArrayInputStream(bits); ObjectInputStream ois = new ObjectInputStream(bais); Object obj = ois.readObject(); bais.close(); ois.close(); return obj; } catch (Exception e) { throw new RuntimeException(e); }}
3.2 Fastjson
一個JSON庫涉及的最準系統就是序列化和還原序列化。Fastjson支援java bean的直接序列化。 使用com.alibaba.fastjson.JSON這個類進行序列化和還原序列化。
public static String serialize(Object obj){ String json = JSON.toJSONString(obj); return json;}public static Object deserialize(String json, Class<?> clazz){ Object obj = JSON.parseObject(json, clazz); return obj;}
3.3 FST
FST fast-serialization 是重新實現的 Java 快速對象序列化的開發包。序列化速度更快(2-10倍)、體積更小,而且相容 JDK 原生的序列化。
Java 快速序列化庫 FST 已經發布了 2.0 版本,該版本的包名已經更改,無法平滑升級。另外官方建議為了穩定性考慮還是使用最新的 1.58 版本為好
static FSTConfiguration configuration = FSTConfiguration .createDefaultConfiguration();public static byte[] serialize(Object obj){ return configuration.asByteArray((Serializable)obj);}public static Object deserialize(byte[] sec){ return configuration.asObject(sec);}
3.4 Gson
這裡採用JSON格式同時使用採用Google的gson進行轉義.
static Gson gson = new Gson();public static String serialize(Object obj){ String json = gson.toJson(obj); return json;}public static Object deserialize(String json, Class<?> clazz){ Object obj = gson.fromJson(json, clazz); return obj;}
3.5 Jackson
Jackson庫(http://jackson.codehaus.org),是基於java語言的開源json格式解析工具,整個庫(使用最新的2.2版本)包含3個jar包:
jackson-core.jar——核心包(必須),提供基於“流模式”解析的API。
jackson-databind——資料繫結包(可選),提供基於“對象綁定”和“樹模型”相關API。
jackson-annotations——註解包(可選),提供註解功能。
相對於java json解析的其他庫,諸如json-lib、gson包,Jackson具有以下優點:
功能全面,提供多種模式的json解析方式,“對象綁定”使用方便,利用註解包能為我們開發提供很多便利。
效能較高,“流模式”的解析效率超過絕大多數類似的json包。
核心包:JsonPaser(json流讀取),JsonGenerator(json流輸出)。
資料繫結包:ObjectMapper(構建樹模式和對象繫結模式),JsonNode(樹節點)
public static String serialize(Object obj){ ObjectMapper mapper = new ObjectMapper(); String json = null; try { json = mapper.writeValueAsString(obj); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return json;}public static Object deserialize(String json, Class<?> clazz){ ObjectMapper mapper = new ObjectMapper(); Object obj = null; try { obj = mapper.readValue(json, clazz); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return obj;}
3.6 Kryo 和 KryoRegister
Kryo的運行速度是java Serializable 的20倍左右
Kryo的檔案大小是java Serializable的一半左右
Kryo有兩種模式:
一種是先註冊(regist),再寫對象,即writeObject函數,實際上如果不先註冊,在寫對象時也會註冊,並為class分配一個id。
注意,如果是rpc,則必須兩端都按同樣的順序註冊,否則會出錯,因為必須要明確類對應的唯一id。
另一種是寫類名及對象,即writeClassAndObject函數。
writeClassAndObject函數是先寫入(-1 + 2)(一個約定的數字),再寫入類ID(第一次要先寫-1,再寫類ID + 類名),寫入參考關聯性(見引用的實現),最後才寫真正的資料)。
注意每一次writeClassAndObject調用後資訊都會清空,所以不用擔心和client互動時會出錯。
static Kryo kryo = new Kryo();public static byte[] serialize(Object obj) { byte[] buffer = new byte[2048]; Output output = new Output(buffer); kryo.writeClassAndObject(output, obj); byte[] bs = output.toBytes(); output.close(); return bs;}public static Object deserialize(byte[] src) { Input input = new Input(src); Object obj = kryo.readClassAndObject(input); input.close(); return obj;}
register
static Kryo kryo = null;static{ kryo = new Kryo(); kryo.setReferences(false); kryo.setRegistrationRequired(false); kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());}public static byte[] serialize(Object obj) { kryo.register(obj.getClass()); byte[] buffer = new byte[2048]; Output output = new Output(buffer); kryo.writeObject(output, obj); byte[] bs = output.toBytes(); output.close(); return bs;}public static Object deserialize(byte[] src, Class<?> clazz) { kryo.register(clazz); Input input = new Input(src); Object obj = kryo.readObject(input, clazz); input.close(); return obj;}
Object Serializalbe 優點:java原生支援,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,位元組數佔用比較大,某些情況下對於對象屬性的變化比較敏感。
對象在進行序列化和還原序列化的時候,必須實現Serializable介面,但並不強制聲明唯一的serialVersionUID,是否聲明serialVersionUID對於對象序列化的向上向下的相容性有很大的影響。
4. 小結
就已有原先使用Java原生序列化方案的系統來說,kryo於fst-serializer是良好的java原生序列化方案替代者,不僅體現再編程簡單,而且速度與效能上會有大幅提升,尤其是fst-serializer ,只需替代output/inputstream 即可,效能的提升上也很可觀,目前該工具剛出來,穩定性還需要多測測。
如果程式本身就用json格式序列化,則可以考慮引入一個效能優異的json解析庫,一般再服務端jackson是廣受歡迎的解析庫。
protobuffer更多的是一種取代xml的誇語言的訊息交換格式,儘快速度很快,但是編程上需要定義訊息格式,對成員變數多、業務複雜的javabean來說代價是較為複雜的,對穩定的已有系統來說總體代價較高。
下表是幾種方案的各項指標的一個對比
| 序列化工具 |
序列化速度 |
序列化檔案大小 |
編程模型複雜度 |
社區活躍度 |
jar包大小 |
| kryo |
極快 |
小 |
簡單 |
高 |
132kb |
| fst-serializer |
快 |
小 |
非常簡單 |
高 |
246kb |
| protobuffer |
快 |
較大 |
較複雜 |
穩定 |
329kb |
| fastjson |
較快 |
較大 |
簡單 |
一般 |
338kb |
| jackson |
一般 |
較大 |
簡單 |
穩定 |
1.1mb |
| gson |
較慢 |
較大 |
簡單 |
穩定 |
189kb |
參考:
http://blog.51cto.com/zlfwmm/1761401
44495549
https://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
http://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
7820018
http://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
Java序列化工具對比