久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

常見Serialize技術有哪些

177次閱讀
沒有評論

共計 6670 個字符,預計需要花費 17 分鐘才能閱讀完成。

本篇內容主要講解“常見 Serialize 技術有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓丸趣 TV 小編來帶大家學習“常見 Serialize 技術有哪些”吧!

【一、常見的在 API 及消息通信調的用中 Serialize 方案】:

方案 1、基于 Java 原生的 ObjectOutputStream.write() 和 ObjectInputStream.read() 來進行對象序列化和反序列化。

方案 2、基于 JSON 進行序列化和反序列化。

方案 3、基于 XML 進行序列化和反序列化。

【方案 1 淺析,ObjectXXXStream】:

優點:

(1)、由 Java 自帶 API 序列化,簡單、方便、無第三方依賴。

(2)、不用擔心其中的數據解析會丟失精度、丟失字段、Object 的反序列化類型不確定等問題。

缺點:

(1)、雙方調試麻煩,發送方和接收方最好是同版本的對象描述,否則會有奇怪的問題,調試周期相對長,跨團隊合作升級問題很多。

(2)、傳遞的對象中包含了元數據信息,占用空間較大。

【方案 2 淺析,JSON 序列化】:

優點:

(1)、簡單、方便,無需關注要序列化的對象格式。

(2)、開源界有較多的組件可以支持,例如 FastJSON 性能非常好。

(3)、在現在很多 RPC 的框架中,基本都支持這樣的方案。

缺點:

(1)、對象屬性中如果包含 Object 類型,在反序列化的時候如果業務也本身也不明確數據類型,處理起來會很麻煩。

(2)、由于文本類型,所以一定會占用較大的數據空間,例如下圖。

(3)、比較比較依賴于 JSON 的解析包的兼容性和性能,在 JSON 的一些細節處理上(例如一些非標的 JSON),各自處理方式可能不一樣。

(4)、序列化無論任何數據類型先要轉換為 String,轉成 byte[],會增加內存拷貝的次數。

(5)、反序列化的時候,必須將整個 JSON 反序列化成對象后才能進行讀取,大家應該知道,Java 對象尤其是層次嵌套較多的對象,占用的內存空間將會遠遠大于數據本身的空間。

數據放大的極端案例 1:

傳遞數據描述信息為:

class PP {

    long userId                       = 102333320132133L;

    int    passportNumber      = 123456;

}

此時傳遞 JSON 的格式為:

{

      userId :102333320132133,

      passportNumber :123456

}

我們要傳遞的數據是 1 個 long、1 個 int,也就是 12 個字節的數據,這個 JSON 的字符串長度將是實際的字節數(不包含回車、空格,這里只是為了可讀性,同時注意,這里的 long 在 JSON 里面是字符串了),這個字符串有:51 個字節,也就是數據放到了 4.25 倍左右。

數據放大極端案例 2:

當你的對象內部有數據是 byte[] 類型,JSON 是文本格式的數據,是無法存儲 byte[] 的,那么要序列化這樣的數據,只有一個辦法就是把 byte 轉成字符,通常的做法有兩種:

(1)使用 BASE64 編碼,目前 JSON 中比較常用的做法。

(2)按照字節進行 16 進制字符編碼,例如字符串:“FF”代表的是 0xFF 這個字節。

不論上面兩種做法的那一種,1 個字節都會變成 2 個字符來傳遞,也就是 byte[] 數據會被放大 2 倍以上。為什么不用 ISO-8859- 1 的字符來編碼呢?因為這樣編碼后,在最終序列化成網絡 byte[] 數據后,對應的 byte[] 雖然沒變大,但是在反序列化成文本的時候,接收方并不知道是 ISO-8859-1,還會用例如 GBK、UTF- 8 這樣比較常見的字符集解析成 String,才能進一步解析 JSON 對象,這樣這個 byte[] 可能在編碼的過程中被改變了,要處理這個問題會非常麻煩。

【方案 2 淺析,XML 序列化】:

優點:

(1)、使用簡單、方便,無需關注要序列化的對象格式

(2)、可讀性較好,XML 在業界比較通用,大家也習慣性在配置文件中看到 XML 的樣子

(3)、大量 RPC 框架都支持,通過 XML 可以直接形成文檔進行傳閱

缺點:

(1)、在序列化和反序列化的性能上一直不是太好。

(2)、也有與 JSON 同樣的數據類型問題,和數據放大的問題,同時數據放大的問題更為嚴重,同時內存拷貝次數也和 JSON 類型,不可避免。

XML 數據放大說明:

XML 的數據放大通常比 JSON 更為嚴重,以上面的 JSON 案例來講,XML 傳遞這份數據通常會這樣傳:

Msg

      userId 102333320132133 /userId

      passportNumber 123456 passportNumber

Msg

這個消息就有 80+ 以上的字節了,如果 XML 里面再搞一些 Property 屬性,對象再嵌套嵌套,那么這個放大的比例有可能會達到 10 倍都是有可能的,因此它的放大比 JSON 更為嚴重,這也是為什么現在越來越多的 API 更加喜歡用 JSON,而不是 XML 的原因。

【放大的問題是什么】:

(1)、花費更多的時間去拼接字符串和拷貝內存,占用更多的 Java 內存,產生更多的碎片。

(2)、產生的 JSON 對象要轉為 byte[] 需要先轉成 String 文本再進行 byte[] 編碼,因為這本身是文本協議,那么自然再多一次內存全量的拷貝。

(3)、傳輸過程由于數據被放大,占用更大的網絡流量。

(4)、由于網絡的 package 變多了,所以 TCP 的 ACK 也會變多,自然系統也會更大,同等丟包率的情況下丟包數量會增加,整體傳輸時間會更長,如果這個數據傳送的網絡延遲很大且丟包率很高,我們要盡量降低大小;壓縮是一條途徑,但是壓縮會帶來巨大的 CPU 負載提高,在壓縮前盡量降低數據的放大是我們所期望的,然后傳送數據時根據 RT 和數據大小再判定是否壓縮,有必要的時候,壓縮前如果數據過大還可以進行部分采樣數據壓縮測試壓縮率。

(5)、接收方要處理數據也會花費更多的時間來處理。

(6)、由于是文本協議,在處理過程中會增加開銷,例如數字轉字符串,字符串轉數字;byte[] 轉字符串,字符串轉 byte[] 都會增加額外的內存和計算開銷。

不過由于在平時大量的應用程序中,這個開銷相對業務邏輯來講簡直微不足道,所以優化方面,這并不是我們關注的重點,但面臨一些特定的數據處理較多的場景,即核心業務在數據序列化和反序列化的時候,就要考慮這個問題了,那么下面我繼續討論問題。

此時提出點問題:

(1)、網絡傳遞是不是有更好的方案,如果有,為什么現在沒有大面積采用?

(2)、相對底層的數據通信,例如 JDBC 是如何做的,如果它像上面 3 種方案傳遞結果集,會怎么樣?

【二、MySQL JDBC 數據傳遞方案】:

在前文中提到數據在序列化過程被放大數倍的問題,我們是否想看看一些相對底層的通信是否也是如此呢?那么我們以 MySQL JDBC 為例子來看看它與 JDBC 之間進行通信是否也是如此。

JDBC 驅動程序根據數據庫不同有很多實現,每一種數據庫實現細節上都有巨大的區別,本文以 MySQL JDBC 的數據解析為例(MySQL 8.0 以前),給大家說明它是如何傳遞數據的,而傳遞數據的過程中,相信大家最為關注的就是 ResultSet 的數據是如何傳遞的。

拋開結果集中的 MetaData 等基本信息,單看數據本身:

(1)JDBC 會讀取數據行的時候,首先會從緩沖區讀取一個 row packege,row package 就是從網絡 package 中拿到的,根據協議中傳遞過來的 package 的頭部判定 package 大小,然后從網絡緩沖中讀取對應大小的內容,下圖想表達網絡傳遞的 package 和業務數據中的 package 之間可能并不是完全對應的。另外,網絡中的 package 如果都到了本地緩沖區,邏輯上講它們是連續的(圖中故意分開是讓大家了解到網絡中傳遞是分不同的 package 傳遞到本地的),JDBC 從本地 buffer 讀取 row package 這個過程就是內核 package 到 JVM 的 package 拷貝過程,對于我們 Java 來講,我們主要關注 row package(JDBC 中可能存在一些特殊情況讀取過來的 package 并不是行級別的,這種特殊情況請有興趣的同學自行查閱源碼)。

我們先不考慮按照 bit 有 31 個 bit 是 0,先按照字節來看有 7 個 0,代表字節沒有數據,只有 1 個字節是有值的,大家可以去看一下自己的數據庫中大量的自動增長列,在 id 小于 4194303 之前,前面 5 個字節是浪費掉的,在增長到 4 個字節(2 的 32 次方 -1)之前前面 4 個自己都是 0,浪費掉的。另外,即使 8 個字節中的第一個字節開始使用,也會有大量的數據,中間字節是為:0 的概率極高,就像十進制中進入 1 億,那么 1 億下面最多會有 8 個 0,越高位的 0 約難補充上去。

如果真的想去嘗試,可以用這個辦法:用 1 個字節來做標志,但會占用一定的計算開銷,所以是否為了這個空間去做這個事情,由你決定,本文僅僅是技術性探討:

方法 1:表達目前有幾個低位被使用的字節數,由于 long 只有 8 個字節,所以用 3 個 bit 就夠了,另外 5 個 bit 是浪費掉的,也無所謂了,反序列化的時候按照高位數量補充 0x00 即可。

方法 2:相對方法 1,更徹底,但處理起來更復雜,用 1 這個字節的 8 個 bit 的 0、1 分別代表 long 的 8 個字節是被使用,序列化和反序列化過程根據標志位和數據本身進行字節補 0x00 操作,補充完整 8 個字節就是 long 的值了,最壞情況是 9 個字節代表 long,最佳情況 0 是 1 個字節,字節中只占用了 2 個字節的時候,即使數據變得相當大,也會有大量的數據的字節存在空位的情況,在這些情況下,就通常可以用少于 8 個字節的情況來表達,要用滿 7 個字節才能夠與原數字 long 的占用空間一樣,此時的數據已經是比 2 的 48 次方 - 1 更大的數據了。

【三、Google Protocol Buffer 技術方案】:

這個對于很多人來講未必用過,也不知道它是用來干什么的,不過我不得不說,它是目前數據序列化和反序列化的一個神器,這個東西是在谷歌內部為了約定好自己內部的數據通信設計出來的,大家都知道谷歌的全球網絡非常牛逼,那么自然在數據傳輸方面做得那是相當極致,在這里我會講解下它的原理,就本身其使用請大家查閱其它人的博客,本文篇幅所限沒法 step by step 進行講解。

看到這個名字,應該知道是協議 Buffer,或者是協議編碼,其目的和上文中提到的用 JSON、XML 用來進行 RPC 調用類似,就是系統之間傳遞消息或調用 API。但是谷歌一方面為了達到類似于 XML、JSON 的可讀性和跨語言的通用性,另一方面又希望達到較高的序列化和反序列化性能,數據放大能夠進行控制,所以它又希望有一種比底層編碼更容易使用,而又可以使用底層編碼的方式,又具備文檔的可讀性能力。

它首先需要定義一個格式文件,如下:

syntax = proto2

package com.xxx.proto.buffer.test;

message TestData2 {
   optional int32 id = 2;
   optional int64 longId = 1;
   optional bool  boolValue = 3;
   optional string name = 4;
   optional bytes bytesValue = 5;
   optional int32 id2 = 6;
}

這個文件不是 Java 文件,也不是 C 文件,和語言無關,通常把它的后綴命名為 proto(文件中 1、2、3 數字代表序列化的順序,反序列化也會按照這個順序來做),然后本地安裝了 protobuf 后(不同 OS 安裝方式不同,在官方有下載和說明),會產生一個 protoc 運行文件,將其加入環境變量后,運行命令指定一個目標目錄:

protoc –java_out=~/temp/ TestData2.proto

此時會在指定的目錄下,產生 package 所描述的目錄,在其目錄內部有 1 個 Java 源文件(其它語言的會產生其它語言),這部分代碼是谷歌幫你生成的,你自己寫的話太費勁,所以谷歌就幫你干了;本地的 Java project 里面要引入 protobuf 包,maven 引用(版本自行選擇):

dependency

      groupId com.google.protobuf /groupId

    artifactId protobuf-java /artifactId

    version 3.6.1 /version

/dependency

此時生成的代碼會調用這個谷歌包里面提供的方法庫來做序列化動作,我們的代碼只需要調用生成的這個類里面的 API 就可以做序列化和反序列化操作了,將這些生成的文件放在一個模塊里面發布到 maven 倉庫別人就可以引用了,關于測試代碼本身,大家可以參考目前有很多博客有提供測試代碼,還是很好用的。

谷歌編碼比較神奇的是,你可以按照對象的方式定義傳輸數據的格式,可讀性極高,甚至于相對 XML 和 JSON 更適合程序員閱讀,也可以作為交流文檔,不同語言都通用,定義的對象還是可以嵌套的,但是它序列化出來的字節比原始數據只大一點點,這尼瑪太厲害了吧。

經過測試不同的數據類型,故意制造數據嵌套的層數,進行二進制數組多層嵌套,發現其數據放大的比例非常非常小,幾乎可以等價于二進制傳輸,于是我把序列化后的數據其二進制進行了輸出,發現其編碼方式非常接近于上面的 JDBC,雖然有一些細節上的區別,但是非常接近,除此之外,它在序列化的時候有幾大特征:

(1)、如果字段為空,它不會產生任何字節,如果整合對象的屬性都為 null,產生的字節將是 0

(2)、對 int32、int64 這些數據采用了變長編碼,其思路和我們上面描述有一些共通之處,就是一個 int64 值在比較小的時候用比較少的字節就可以表達了,其內部有一套字節的移位和異或算法來處理這個事情。

(3)、它對字符串、byte[] 沒有做任何轉換,直接放入字節數組,和二進制編碼是差不多的道理。

(4)、由于字段為空它都可以不做任何字節,它的做法是有數據的地方會有一個位置編碼信息,大家可以嘗試通過調整數字順序看看生成出來的 byte 是否會發生改變;那么此時它就有了很強兼容性,也就是普通的加字段是沒問題的,這個對于普通的二進制編碼來講很難做到。

(5)、序列化過程沒有產生 metadata 信息,也就是它不會把對象的結構寫在字節里面,而是反序列化的接收方有同一個對象,就可以反解析出來了。

這與我自己寫編碼有何區別?

(1)、自己寫編碼有很多不確定性,寫不好的話,數據可能放得更大,也容易出錯。

(2)、google 的工程師把內部規范后,谷歌開源的產品也大量使用這樣的通信協議,越來越多的業界中間件產品開始使用該方案,就連 MySQL 數據庫最新版本在數據傳輸方面也會開始兼容 protobuf。

(3)、谷歌相當于開始在定義一個業界的新的數據傳輸方案,即有性能又降低代碼研發的難度,也有跨語言訪問的能力,所以才會有越來越多的人喜歡使用這個東西。

那么它有什么缺點呢?還真不多,它基本兼顧了很多序列化和反序列化中你需要考慮的所有的事情,達到了一種非常良好的平衡,但是硬要挑缺陷,我們就得找場景才行:

(1)、protobuf 需要雙方明確數據類型,且定義的文件中每一個對象要明確數據類型,對于 Object 類型的表達沒有方案,你自己必須提前預知這個 Object 到底是什么類型。

(2)、使用 repeated 可以表達數組,但是只能表達相同類型的數據,例如上面提到的 JDBC 一行數據的多個列數據類型不同的時候,要用這個表達,會比較麻煩;另外,默認情況下數組只能表達 1 維數組,要表達二維數組,需要使用對象嵌套來間接完成。

(3)、它提供的數據類型都是基本數據類型,如果不是普通類型,要自己想辦法轉換為普通類型進行傳輸,例如從 MongoDB 查處一個 Docment 對象,這個對象序列化是需要自己先通過別的方式轉換為 byte[] 或 String 放進去的,而相對 XML、JSON 普通是提供了遞歸的功能,但是如果 protobuf 要提供這個功能,必然會面臨數據放大的問題,通用和性能永遠是矛盾的。

(4)、相對于自定義 byte 的話,序列化和反序列化是一次性完成,不能逐步完成,這樣如果傳遞數組嵌套,在反序列化的時候會產生大量的 Java 對象,另外自定義 byte 的話可以進一步減少內存拷貝,不過谷歌這個相對文本協議來講內存拷貝已經少很多了。

到此,相信大家對“常見 Serialize 技術有哪些”有了更深的了解,不妨來實際操作一番吧!這里是丸趣 TV 網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-16發表,共計6670字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 榆林市| 彭阳县| 丹寨县| 犍为县| 绥芬河市| 牡丹江市| 通渭县| 大新县| 星子县| 原平市| 洛阳市| 玉门市| 芜湖县| 梧州市| 昭觉县| 察哈| 苍南县| 水城县| 岗巴县| 高雄市| 和静县| 丹寨县| 汤阴县| 涟水县| 汉寿县| 慈溪市| 开封县| 涿鹿县| 安新县| 毕节市| 株洲市| 辽中县| 清苑县| 松潘县| 龙海市| 渭南市| 正镶白旗| 卢氏县| 聂荣县| 沁源县| 应城市|