序列化與反序列化:通過網絡傳輸結構化的數據


一、前言

在TCP的連接上,它傳輸數據的基本形式就是二進制流,也就是一段一段的1和0。在一般編程語言或者網絡框架提供的API中,傳輸數據的基本形式是字節,也就是Byte。一個字節就是8個二進制位,8個Bit,所以在這里,二進制流和字節流本質上是一樣的。對於我們編寫的程序來說,它需要通過網絡傳輸的數據是結構化的數據,比如,一條命令、一段文本或者一條消息。對應代碼中,這些結構化的數據都可以用一個類或者一個結構體來表示。

 

要想使用網絡框架的API來傳輸結構化的數據,必須得先實現結構化的數據與字節流之間的雙向轉換。這種將結構化數據轉換成字節流的過程,稱為序列化,反過來轉換,就是反序列化。序列化的用途除了用於在網絡上傳輸數據以外,另外一個重要用途是,將結構化數據保存在文件中,因為文件內保存數據的形式也是二進制序列。

 

二、選擇哪種序列化實現

Java和Go語言都內置了序列化實現,也有一些流行的開源序列化實現,比如,Googel的Protobuf、Kryo、Hessian等;此外,像JSON、XML這些標准的數據格式,也可以作為一種序列化實現來使用。當然,也可以自己來實現私有的序列化實現。

 

面對這么多種序列化實現,需要權衡幾個因素:

  • 序列化后的數據最好是易於人類閱讀的
  • 實現的復雜度是否足夠低
  • 序列化和反序列化的速度越快越好
  • 序列化后的信息密度越大越好,也就是說,同樣的一個結構化數據,序列化之后占用的存儲空間越小越好

不存在一種序列化實現在這幾個方面都是最優的,像JSON、XML可讀性最好,但信息密度也最低。像Kryo、Hessian這些通用的二進制序列化,適用范圍廣,適用簡單,性能比JSON、XML要好一些,但肯定不如專用的序列化實現。

對於一些強業務類系統,比如電商、社交應用系統,特點是業務復雜,需求變化快,但是對性能的要求沒有那么苛刻。推薦使用JSON這種實現簡單,數據可讀性好的序列化實現,無論是接口調試還是排查問題都非常方便,付出的代價就是多一點點CPU時間和存儲空間。

 

如果JSON序列化的性能達不到系統的要求,可以采用性能更好的二進制序列化實現,實現的復雜度和JSON序列化差不多,但是序列化性能更好,信息密度更高,代價就是失去了可讀性。

 

三、實現高性能的序列化和反序列化

絕大部分系統,使用上面兩種通用的序列化實現都可以滿足需求,而像消息隊列這種用於解決通信問題的中間件,對性能要求非常高,通用的序列化實現達不到性能要求,所以,很多消息隊列選擇自己實現高性能的專用序列化和反序列化

 

使用專用的序列化方法,可以提高序列化性能,並有效減小序列化后的字節長度。不必考慮通用性,比如,可以固定字段的順序,這樣在序列化后的字節里面就不必包含字段名,只要字段值就可,不同類型的數據也可以做針對性的優化:

03   | 08 7a 68 61 6e 67 73 61 6e | 17 | 01
User |    z  h  a  n  g  s  a  n  | 23 | true

1.首先我們需要標識一下這個對象的類型,這里面我們用一個字節來表示類型,比如用 03 表示這是一個 User 類型的對象。
2.我們約定,按照 name、age、married 這個固定順序來序列化這三個屬性。按照順序,第一個字段是 name,我們不存字段名,直接存字段值“zhangsan”就可以了,由於名字的長度不固定,我們用第一個字節 08 表示這個名字的長度是 8 個字節,后面的 8 個字節就是 zhangsan。
3.第二個字段是年齡,我們直接用一個字節表示就可以了,23 的 16 進制是 17 。
4.最后一個字段是婚姻狀態,我們用一個字節來表示,01 表示已婚,00 表示未婚,這里面保存一個 01。

 

可以看到,同樣的一個User對象,JSON序列化后({"name":"zhangsan","age":"23","married":"true"})需要47個字節,這里只要12個字節就夠了。

專用的序列化方法顯然更高效,序列化出來的字節更少,在網絡傳輸過程中的速度也更快。但缺點是,需要為每種對象類型定義專門的序列化和反序列化方法,實現起來太復雜了,大部分情況下是不划算的。

 

四、總結

  • 進程之間要通過網絡傳輸結構化的數據,需要通過序列化和反序列化來實現結構化數據和二進制數據的雙向轉換。在選擇序列化實現的時候,需要綜合考慮數據可讀性,實現復雜度,性能和信息密度這四個因素。
  • 大多數情況下,選擇一個高性能的通用序列化框架都可以滿足要求,在性能可以滿足需求的前提下,推薦優先選擇 JSON 這種可讀性好的序列化方法。
  • 如果說我們需要超高的性能,或者是帶寬有限的情況下,可以使用專用的序列化方法,來提升序列化性能,節省傳輸流量。不過實現起來很復雜,大部分情況下並不划算。

 

問題:在內存里存放的任何數據,它最基礎的存儲單元也是二進制比特,也就是說,我們應用程序操作的對象,它在內存中也是使用二進制存儲的,既然都是二進制,為什么不能直接把內存中,對象對應的二進制數據直接通過網絡發送出去,或者保存在文件中呢?為什么還需要序列化和反序列化呢?

  • 內存里存的東西,不通用, 不同系統, 不同語言的組織可能都是不一樣的, 而且還存在很多引用, 指針,並不是直接數據塊。內存中的對象數據應該具有語言獨特性,例如表達相同業務的User對象(id/name/age字段),Java和PHP在內存中的數據格式應該不一樣的,如果直接用內存中的數據,可能會造成語言不通。通常兩個服務之間沒有嚴格要求語言必須一致,只要對序列化的數據格式進行了協商,任何2個語言直接都可以進行序列化傳輸、接收。
  • 序列化, 反序列化, 其實是約定一種標准吧, 大家都按這個標准去弄, 就能跨平台 , 跨語言。
  • 雖然都是二進制的數據,但是序列化的二進制數據是通過一定的協議將數據字段進行拼接。第一個優勢是:不同的語言都可以遵循這種協議進行解析,實現了跨語言。第二個優勢是:這種數據可以直接持久化到磁盤,從磁盤讀取后也可以通過這個協議解析出來。如果是內存中的數據不能直接存盤的,直接存盤后再讀出來我們根本無法辨識這是個什么數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM