午夜精品人妻久久久-成年美女很黄的网站-在线看片免费人成视久网app-国产精品美女无遮挡一区二区-91精品国产综合久久久久-国产的免费视频又猛又爽又刺激-在线看片免费人成视久网app-久久香蕉国产精品视频-av一区二区三区高清

常見(jiàn)Serialize技術(shù)探秘(XML、JSON、JDBC byte編碼、Pro

目前業(yè)界有各種各樣的網(wǎng)絡(luò)輸出傳輸時(shí)的序列化和反序列化方案,它們?cè)诩夹g(shù)上的實(shí)現(xiàn)的初衷和背景有較大的區(qū)別 , 因此在設(shè)計(jì)的架構(gòu)也會(huì)有很大的區(qū)別,最終在落地后的:解析速度、對(duì)系統(tǒng)的影響、傳輸數(shù)據(jù)的大小、可維護(hù)性及可閱讀性等方面有著較大的區(qū)別,本文分享一些我在一些常見(jiàn)序列化技術(shù)的分析和理解:
文章分成3個(gè)部分:
1、列舉常見(jiàn)的序列化和反序列化方案(、XML、JSON)
2、MySQL JDBC結(jié)果集的處理方案
3、處理方案
【一、常見(jiàn)的在API及消息通信調(diào)的用中方案】:
方案1、基于Java原生的.write()和.read()來(lái)進(jìn)行對(duì)象序列化和反序列化 。
方案2、基于JSON進(jìn)行序列化和反序列化 。
方案3、基于XML進(jìn)行序列化和反序列化 。
【方案1淺析,】:
優(yōu)點(diǎn):
(1)、由Java自帶API序列化,簡(jiǎn)單、方便、無(wú)第三方依賴(lài) 。
(2)、不用擔(dān)心其中的數(shù)據(jù)解析會(huì)丟失精度、丟失字段、的反序列化類(lèi)型不確定等問(wèn)題 。
缺點(diǎn):
(1)、雙方調(diào)試麻煩,發(fā)送方和接收方最好是同版本的對(duì)象描述 , 否則會(huì)有奇怪的問(wèn)題,調(diào)試周期相對(duì)長(zhǎng),跨團(tuán)隊(duì)合作升級(jí)問(wèn)題很多 。
(2)、傳遞的對(duì)象中包含了元數(shù)據(jù)信息 , 占用空間較大 。
【方案2淺析,JSON序列化】:
優(yōu)點(diǎn):
(1)、簡(jiǎn)單、方便,無(wú)需要序列化的對(duì)象格式 。
(2)、開(kāi)源界有較多的組件可以支持,例如性能非常好 。
(3)、在現(xiàn)在很多RPC的框架中,基本都支持這樣的方案 。
缺點(diǎn):
(1)、對(duì)象屬性中如果包含類(lèi)型 , 在反序列化的時(shí)候如果業(yè)務(wù)也本身也不明確數(shù)據(jù)類(lèi)型 , 處理起來(lái)會(huì)很麻煩 。
(2)、由于文本類(lèi)型,所以一定會(huì)占用較大的數(shù)據(jù)空間,例如下圖 。
(3)、比較比較依賴(lài)于JSON的解析包的兼容性和性能 , 在JSON的一些細(xì)節(jié)處理上(例如一些非標(biāo)的JSON),各自處理方式可能不一樣 。
(4)、序列化無(wú)論任何數(shù)據(jù)類(lèi)型先要轉(zhuǎn)換為,轉(zhuǎn)成byte[] , 會(huì)增加內(nèi)存拷貝的次數(shù) 。
(5)、反序列化的時(shí)候,必須將整個(gè)JSON反序列化成對(duì)象后才能進(jìn)行讀?。?蠹矣Ω彌?潰琂ava對(duì)象尤其是層次嵌套較多的對(duì)象,占用的內(nèi)存空間將會(huì)遠(yuǎn)遠(yuǎn)大于數(shù)據(jù)本身的空間 。
數(shù)據(jù)放大的極端案例1:
傳遞數(shù)據(jù)描述信息為:
class PP {
long= 2133L;
int= ;
}
此時(shí)傳遞JSON的格式為:
{
【常見(jiàn)Serialize技術(shù)探秘(XML、JSON、JDBC byte編碼、Pro】“”:2133,
“”:
}
我們要傳遞的數(shù)據(jù)是1個(gè)long、1個(gè)int,也就是12個(gè)字節(jié)的數(shù)據(jù) , 這個(gè)JSON的字符串長(zhǎng)度將是實(shí)際的字節(jié)數(shù)(不包含回車(chē)、空格,這里只是為了可讀性,同時(shí)注意,這里的long在JSON里面是字符串了),這個(gè)字符串有:51個(gè)字節(jié) , 也就是數(shù)據(jù)放到了4.25倍左右 。
數(shù)據(jù)放大極端案例2:
當(dāng)你的對(duì)象內(nèi)部有數(shù)據(jù)是byte[]類(lèi)型,JSON是文本格式的數(shù)據(jù),是無(wú)法存儲(chǔ)byte[]的,那么要序列化這樣的數(shù)據(jù),只有一個(gè)辦法就是把byte轉(zhuǎn)成字符,通常的做法有兩種:
(1)使用編碼,目前JSON中比較常用的做法 。
(2)按照字節(jié)進(jìn)行16進(jìn)制字符編碼,例如字符串:“FF”代表的是0xFF這個(gè)字節(jié) 。
不論上面兩種做法的那一種,1個(gè)字節(jié)都會(huì)變成2個(gè)字符來(lái)傳遞,也就是byte[]數(shù)據(jù)會(huì)被放大2倍以上 。為什么不用ISO-8859-1的字符來(lái)編碼呢?因?yàn)檫@樣編碼后,在最終序列化成網(wǎng)絡(luò)byte[]數(shù)據(jù)后,對(duì)應(yīng)的byte[]雖然沒(méi)變大,但是在反序列化成文本的時(shí)候,接收方并不知道是ISO-8859-1,還會(huì)用例如GBK、UTF-8這樣比較常見(jiàn)的字符集解析成,才能進(jìn)一步解析JSON對(duì)象,這樣這個(gè)byte[]可能在編碼的過(guò)程中被改變了,要處理這個(gè)問(wèn)題會(huì)非常麻煩 。
【方案2淺析,XML序列化】:
優(yōu)點(diǎn):
(1)、使用簡(jiǎn)單、方便,無(wú)需要序列化的對(duì)象格式
(2)、可讀性較好,XML在業(yè)界比較通用,大家也習(xí)慣性在配置文件中看到XML的樣子
(3)、大量RPC框架都支持 , 通過(guò)XML可以直接形成文檔進(jìn)行傳閱
缺點(diǎn):
(1)、在序列化和反序列化的性能上一直不是太好 。
(2)、也有與JSON同樣的數(shù)據(jù)類(lèi)型問(wèn)題,和數(shù)據(jù)放大的問(wèn)題,同時(shí)數(shù)據(jù)放大的問(wèn)題更為嚴(yán)重,同時(shí)內(nèi)存拷貝次數(shù)也和JSON類(lèi)型 , 不可避免 。
XML數(shù)據(jù)放大說(shuō)明:
XML的數(shù)據(jù)放大通常比JSON更為嚴(yán)重,以上面的JSON案例來(lái)講,XML傳遞這份數(shù)據(jù)通常會(huì)這樣傳:
2133
這個(gè)消息就有80+以上的字節(jié)了,如果XML里面再搞一些屬性,對(duì)象再嵌套嵌套,那么這個(gè)放大的比例有可能會(huì)達(dá)到10倍都是有可能的 , 因此它的放大比JSON更為嚴(yán)重,這也是為什么現(xiàn)在越來(lái)越多的API更加喜歡用JSON,而不是XML的原因 。
【放大的問(wèn)題是什么】:
(1)、花費(fèi)更多的時(shí)間去拼接字符串和拷貝內(nèi)存,占用更多的Java內(nèi)存,產(chǎn)生更多的碎片 。
(2)、產(chǎn)生的JSON對(duì)象要轉(zhuǎn)為byte[]需要先轉(zhuǎn)成文本再進(jìn)行byte[]編碼,因?yàn)檫@本身是文本協(xié)議,那么自然再多一次內(nèi)存全量的拷貝 。
(3)、傳輸過(guò)程由于數(shù)據(jù)被放大,占用更大的網(wǎng)絡(luò)流量 。
(4)、由于網(wǎng)絡(luò)的變多了,所以TCP的ACK也會(huì)變多,自然系統(tǒng)也會(huì)更大,同等丟包率的情況下丟包數(shù)量會(huì)增加,整體傳輸時(shí)間會(huì)更長(zhǎng),如果這個(gè)數(shù)據(jù)傳送的網(wǎng)絡(luò)延遲很大且丟包率很高,我們要盡量降低大?。謊顧跏且惶跬揪叮?但是壓縮會(huì)帶來(lái)巨大的CPU負(fù)載提高,在壓縮前盡量降低數(shù)據(jù)的放大是我們所期望的 , 然后傳送數(shù)據(jù)時(shí)根據(jù)RT和數(shù)據(jù)大小再判定是否壓縮,有必要的時(shí)候,壓縮前如果數(shù)據(jù)過(guò)大還可以進(jìn)行部分采樣數(shù)據(jù)壓縮測(cè)試壓縮率 。
(5)、接收方要處理數(shù)據(jù)也會(huì)花費(fèi)更多的時(shí)間來(lái)處理 。
(6)、由于是文本協(xié)議,在處理過(guò)程中會(huì)增加開(kāi)銷(xiāo),例如數(shù)字轉(zhuǎn)字符串,字符串轉(zhuǎn)數(shù)字;byte[]轉(zhuǎn)字符串,字符串轉(zhuǎn)byte[]都會(huì)增加額外的內(nèi)存和計(jì)算開(kāi)銷(xiāo) 。
不過(guò)由于在平時(shí)大量的應(yīng)用程序中,這個(gè)開(kāi)銷(xiāo)相對(duì)業(yè)務(wù)邏輯來(lái)講簡(jiǎn)直微不足道,所以?xún)?yōu)化方面,這并不是我們的重點(diǎn),但面臨一些特定的數(shù)據(jù)處理較多的場(chǎng)景 , 即核心業(yè)務(wù)在數(shù)據(jù)序列化和反序列化的時(shí)候,就要考慮這個(gè)問(wèn)題了,那么下面我繼續(xù)討論問(wèn)題 。
此時(shí)提出點(diǎn)問(wèn)題:
(1)、網(wǎng)絡(luò)傳遞是不是有更好的方案,如果有,為什么現(xiàn)在沒(méi)有大面積采用?
(2)、相對(duì)底層的數(shù)據(jù)通信,例如JDBC是如何做的,如果它像上面3種方案?jìng)鬟f結(jié)果集,會(huì)怎么樣?
【二、MySQL JDBC數(shù)據(jù)傳遞方案】:
在前文中提到數(shù)據(jù)在序列化過(guò)程被放大數(shù)倍的問(wèn)題,我們是否想看看一些相對(duì)底層的通信是否也是如此呢?那么我們以MySQL JDBC為例子來(lái)看看它與JDBC之間進(jìn)行通信是否也是如此 。
JDBC驅(qū)動(dòng)程序根據(jù)數(shù)據(jù)庫(kù)不同有很多實(shí)現(xiàn),每一種數(shù)據(jù)庫(kù)實(shí)現(xiàn)細(xì)節(jié)上都有巨大的區(qū)別,本文以MySQL JDBC的數(shù)據(jù)解析為例(MySQL 8.0以前),給大家說(shuō)明它是如何傳遞數(shù)據(jù)的,而傳遞數(shù)據(jù)的過(guò)程中,相信大家最為的就是的數(shù)據(jù)是如何傳遞的 。
拋開(kāi)結(jié)果集中的等基本信息,單看數(shù)據(jù)本身:
(1)JDBC會(huì)讀取數(shù)據(jù)行的時(shí)候,首先會(huì)從緩沖區(qū)讀取一個(gè)row ,row 就是從網(wǎng)絡(luò)中拿到的,根據(jù)協(xié)議中傳遞過(guò)來(lái)的的頭部判定大小,然后從網(wǎng)絡(luò)緩沖中讀取對(duì)應(yīng)大小的內(nèi)容,下圖想表達(dá)網(wǎng)絡(luò)傳遞的和業(yè)務(wù)數(shù)據(jù)中的之間可能并不是完全對(duì)應(yīng)的 。另外,網(wǎng)絡(luò)中的如果都到了本地緩沖區(qū),邏輯上講它們是連續(xù)的(圖中故意分開(kāi)是讓大家了解到網(wǎng)絡(luò)中傳遞是分不同的傳遞到本地的),JDBC從本地讀取row 這個(gè)過(guò)程就是內(nèi)核到JVM的拷貝過(guò)程,對(duì)于我們Java來(lái)講,我們主要row (JDBC中可能存在一些特殊情況讀取過(guò)來(lái)的并不是行級(jí)別的 , 這種特殊情況請(qǐng)有興趣的同學(xué)自行查閱源碼) 。
(2)、單行數(shù)據(jù)除頭部外 , 就是body了,body部分包含各種各樣不同的數(shù)據(jù)類(lèi)型,此時(shí)在body上放數(shù)據(jù)類(lèi)型顯然是占空間的,所以數(shù)據(jù)類(lèi)型是從中提取的,body中數(shù)據(jù)列的順序?qū)?huì)和中的列的順序保持一致 。
(3)、MySQL詳細(xì)解析數(shù)據(jù)類(lèi)型:
3.1、如果對(duì)應(yīng)數(shù)據(jù)發(fā)現(xiàn)是int、、、year、float、等數(shù)據(jù)類(lèi)型會(huì)按照定長(zhǎng)字節(jié)數(shù)讀取,例如int自然按照4字節(jié)讀??,long會(huì)按照8字節(jié)讀取 。
3.2、如果發(fā)現(xiàn)是其它的類(lèi)型 , 例如、、text等等會(huì)按照變長(zhǎng)讀取 。
3.3、變長(zhǎng)字符串首先讀取1個(gè)字節(jié)標(biāo)志位 。
3.4、如果這個(gè)標(biāo)志位的值小于等于250,則直接代表后續(xù)字節(jié)的長(zhǎng)度(注意字符串在這里是算轉(zhuǎn)換為字節(jié)的長(zhǎng)度),這樣確保大部分業(yè)務(wù)中存放的變長(zhǎng)字符串,在網(wǎng)絡(luò)傳遞過(guò)程中只需要1個(gè)字節(jié)的放大 。
3.5、如果這個(gè)標(biāo)志位是:251 , 代表這個(gè)字段為NULL

常見(jiàn)Serialize技術(shù)探秘(XML、JSON、JDBC byte編碼、Pro

文章插圖
常見(jiàn)Serialize技術(shù)探秘(XML、JSON、JDBC byte編碼、Pro

文章插圖
3.6、如果標(biāo)志位是:252,代表需要2個(gè)字節(jié)代表字段的長(zhǎng)度,此時(shí)加上標(biāo)志位就是3個(gè)字節(jié),在65536以?xún)?nèi)的長(zhǎng)度的數(shù)據(jù)(64KB) , 注意,這里會(huì)在轉(zhuǎn)成long的時(shí)候高位補(bǔ)0 , 所以2個(gè)字節(jié)可以填滿到65536 , 只需要放大3個(gè)字節(jié)來(lái)表示這個(gè)數(shù)據(jù) 。
3.7、如果標(biāo)志位是:253,代表需要4個(gè)自己大表字段的長(zhǎng)度 , 可以表示4GB(同上高位補(bǔ)0),這樣的數(shù)據(jù)幾乎不會(huì)出現(xiàn)在數(shù)據(jù)庫(kù)里面 , 即使出現(xiàn),只會(huì)出現(xiàn)5個(gè)字節(jié)的放大 。
3.8、如果標(biāo)志位是:254 , 8個(gè)字節(jié)代表長(zhǎng)度,此時(shí)沒(méi)有高位補(bǔ)0,最多可以讀取Long.的長(zhǎng)度 , 但是這個(gè)空間目前不可能有內(nèi)存放得下,所以無(wú)需擔(dān)心使用問(wèn)題,此時(shí)9個(gè)字節(jié)的放大,源碼如下圖:
(4)、我們先按照這個(gè)理解,MySQL在傳遞數(shù)據(jù)的過(guò)程中,對(duì)數(shù)據(jù)的放大是很小很小的,是不是真的這樣呢?請(qǐng)下面第5點(diǎn)說(shuō)明 。
補(bǔ)充說(shuō)明:
a、在MySQL JDBC中對(duì)于數(shù)據(jù)的解析(除對(duì)MySQL 8.0以上JDBC版本)有2個(gè)實(shí)現(xiàn)類(lèi):、 , 這兩種方式在讀取單行數(shù)據(jù)在解析這個(gè)階段是一樣的邏輯,只不過(guò)解析存放數(shù)據(jù)的方式有所不同,一個(gè)會(huì)解析成數(shù)據(jù)行的byte[] , 會(huì)解析成byte[][]二維數(shù)組,第二維就是每1個(gè)列的信息 , 這都是客戶(hù)端行為,與網(wǎng)絡(luò)傳遞數(shù)據(jù)的格式無(wú)關(guān) 。(兩者在不同場(chǎng)景下使用,例如其中一種場(chǎng)景是:在游標(biāo)開(kāi)啟模式的時(shí)候會(huì)啟用,但這不是本文的重點(diǎn),這里提到主要告知大家 , 無(wú)論哪一種方式,讀取數(shù)據(jù)的方式是一致的)
b、在MySQL JDBC中的是里面數(shù)據(jù)處理的入口,其實(shí)現(xiàn)類(lèi)有3個(gè):、、 , 這雖然有3個(gè)實(shí)現(xiàn)類(lèi),但是同樣不會(huì)影響數(shù)據(jù)的格式,它們只是從緩沖區(qū)讀取數(shù)據(jù)的方式有所不同:、會(huì)每次將緩沖區(qū)的數(shù)據(jù)全部讀取到JDBC當(dāng)中形成數(shù)組,在處理上有一個(gè)區(qū)別在于數(shù)據(jù)庫(kù)每次返回的是大小的數(shù)據(jù)內(nèi)容(實(shí)現(xiàn)的細(xì)節(jié)在上一篇文章中有提到);是需要行的時(shí)候再?gòu)闹腥プx,讀取完成后就嘗試讀取下一個(gè) 。這些都不會(huì)影響數(shù)據(jù)本身在網(wǎng)絡(luò)上的傳遞格式,所以文本提到的解析是目前MySQL JDBC比較通用的解析,與它內(nèi)部的執(zhí)行路徑無(wú)關(guān) 。
(5)、以為例,當(dāng)你發(fā)起(‘xxx’)、(int)的時(shí)候,首先它需要在內(nèi)部找到是第幾個(gè)列(傳數(shù)字省略該動(dòng)作),然后其內(nèi)部會(huì)有一個(gè)、分別記錄最后讀取的第幾個(gè)字段和所在字節(jié)的位置,如果你傳入的index比這個(gè)index大,則從當(dāng)前位置開(kāi)始 , 向后掃描 , 掃描規(guī)則和上面的數(shù)據(jù)庫(kù)寬度一致,找到對(duì)應(yīng)位置,拷貝出對(duì)應(yīng)的byte[]數(shù)組,轉(zhuǎn)換你要的對(duì)象類(lèi)型 。
PS:、這種其實(shí)就是JDBC認(rèn)為你絕大部分情況是從前向后讀取的,因此這樣讀取對(duì)JDBC程序也是最友好的方案 , 否則指針向前移動(dòng) , 需要從0開(kāi)始,理由很簡(jiǎn)單(數(shù)據(jù)的長(zhǎng)度不是在尾部 , 而是在頭部) , 因此指針來(lái)回來(lái)回移動(dòng)的時(shí)候 , 這樣會(huì)產(chǎn)生很多開(kāi)銷(xiāo) , 同時(shí)會(huì)產(chǎn)生更多的內(nèi)存拷貝出來(lái)的碎片 。雖然可以解決這個(gè)問(wèn)題,但是其本身會(huì)占用相對(duì)較大的空間另外,其內(nèi)部的二維數(shù)組返回的byte[]字節(jié)是可以被外部所修改的(因?yàn)闆](méi)有拷貝) 。
另外,按照這種讀取數(shù)據(jù)的方式,如果單行數(shù)據(jù)過(guò)大(例如有大字段100MB+),讀取到Java內(nèi)存里面來(lái) , 即使使用和讀取 , 讀取幾十條數(shù)據(jù),就能把JVM內(nèi)存干掛掉 。到目前為止,我還沒(méi)看到MySQL里面可以“設(shè)置限制單行數(shù)據(jù)長(zhǎng)度”的參數(shù),后續(xù)估計(jì)官方支持這類(lèi)特殊需求的可能性很小,大多也只能自己改源碼來(lái)實(shí)現(xiàn) 。
【回到話題本身:MySQL和JDBC之間的通信似乎放大很?。俊?
其實(shí)不然 , MySQL傳遞數(shù)據(jù)給JDBC默認(rèn)是走文本協(xié)議的 , 而不是協(xié)議,雖然說(shuō)它的byte[]數(shù)組不會(huì)像JSON那樣放大,并不算真正意義上的文本協(xié)議,但是它很多種數(shù)據(jù)類(lèi)型默認(rèn)情況下,都是文本傳輸,例如一個(gè)上面提到的賬號(hào):2133在數(shù)據(jù)庫(kù)中是8個(gè)自己,但是網(wǎng)絡(luò)傳遞的時(shí)候如果有文本格式傳遞將會(huì)是:15個(gè)字節(jié),如果是數(shù)據(jù)在數(shù)據(jù)庫(kù)中可以用8個(gè)字節(jié)存放,但是網(wǎng)絡(luò)傳遞如果按照YYYY-MM-DD HH:MI:SS傳遞,可以達(dá)到19個(gè)字節(jié),而當(dāng)他們用在網(wǎng)絡(luò)傳遞的時(shí)候 , 按照我們前面提到的,MySQL會(huì)將其當(dāng)成變長(zhǎng)字符,因此會(huì)在數(shù)據(jù)頭部加上最少1個(gè)自己的標(biāo)志位 。另外,這里增加不僅僅是幾個(gè)字節(jié),而是你要取到真正的數(shù)據(jù),接收方還需要進(jìn)一步計(jì)算處理才能得到,例如2133用文本傳送后,接收方是需要將這個(gè)字符串轉(zhuǎn)換為long類(lèi)型才能可以得到long的 , 大家試想一下你處理500萬(wàn)數(shù)據(jù),每一行數(shù)據(jù)有20個(gè)列,有大量的類(lèi)似的處理不是開(kāi)銷(xiāo)增加了特別多呢?
JDBC和MySQL之間可以通過(guò)協(xié)議來(lái)進(jìn)行通信的,也就是按照實(shí)際數(shù)字占用的空間大小來(lái)進(jìn)行通信 , 但是比較坑的時(shí),MySQL目前開(kāi)啟協(xié)議的方案是:“開(kāi)啟服務(wù)端”,這個(gè)一旦開(kāi)啟,會(huì)有一大堆的坑出來(lái),尤其是在互聯(lián)網(wǎng)的編程中,我會(huì)在后續(xù)的文章中逐步闡述 。
拋開(kāi)“開(kāi)啟協(xié)議的坑” , 我們認(rèn)為MySQL JDBC在通信的過(guò)程中對(duì)數(shù)據(jù)的編碼還是很不錯(cuò)的,非常緊湊的編碼(當(dāng)然,如果你開(kāi)啟了SSL訪問(wèn),那么數(shù)據(jù)又會(huì)被放大 , 而且加密后的數(shù)據(jù)基本很難壓縮) 。
對(duì)比傳統(tǒng)的數(shù)據(jù)序列化優(yōu)劣勢(shì)匯總:
優(yōu)勢(shì):
(1)、數(shù)據(jù)全部按照byte[]編碼后,由于緊湊編碼,所以對(duì)數(shù)據(jù)本身的放大很小 。
(2)、由于編碼和解碼都沒(méi)有解析的過(guò)程java中xml怎么提示,都是向的尾部順序地寫(xiě),也就是說(shuō)不用找位置,讀取的時(shí)候根據(jù)設(shè)計(jì)也可以減少找位置 , 即使找位置也是移動(dòng)偏移量 , 非常高效 。
(3)、如果傳遞多行數(shù)據(jù),反序列化的過(guò)程不用像XML或JSON那樣一次要將整個(gè)傳遞過(guò)來(lái)的數(shù)據(jù)全部解析后再處理,試想一下,如果5000行、20列的結(jié)果集,會(huì)產(chǎn)生多少Java對(duì)象,每一個(gè)Java對(duì)象對(duì)數(shù)據(jù)本身的放大又是多少,采用字節(jié)傳遞后可以按需轉(zhuǎn)變?yōu)镴ava對(duì)象,使用完的Java對(duì)象可以釋放,這樣就不用同時(shí)占用那樣大的JVM內(nèi)存,而byte[]數(shù)組也只是數(shù)據(jù)本身的大小,也可以按需釋放 。
(4)、相對(duì)前面提到的3種方式,例如JSON,它不需要在序列化和反序列化的時(shí)候要經(jīng)歷一次的轉(zhuǎn)換,這樣會(huì)減少一次內(nèi)存拷貝 。
(5)、自己寫(xiě)代碼用類(lèi)似的通信方案,可以在網(wǎng)絡(luò)優(yōu)化上做到極致 。
劣勢(shì):
(1)、編碼是MySQL和MySQL JDBC之間自定義的,別人沒(méi)法用(我們可以參考別人的思路)
(2)、byte編碼和解碼過(guò)程程序員自己寫(xiě),對(duì)程序員水平和嚴(yán)謹(jǐn)性要求都很高,前期需要大量的測(cè)試,后期在網(wǎng)絡(luò)問(wèn)題上考慮稍有偏差就可能出現(xiàn)不可預(yù)期的Bug 。(所以在公司內(nèi)部需要把這些內(nèi)容進(jìn)行封裝,大部分程序員無(wú)需這個(gè)內(nèi)容) 。
(3)、從內(nèi)存拷貝上來(lái)講,從到應(yīng)用中的數(shù)據(jù),這一層內(nèi)存拷貝是無(wú)法避免的,如果你寫(xiě)自定義程序,在必要的條件下,這個(gè)地方可以進(jìn)一步減少內(nèi)存拷貝,但無(wú)法杜絕;同上文中提到,這點(diǎn)開(kāi)銷(xiāo),對(duì)于整個(gè)應(yīng)用程序的業(yè)務(wù)處理來(lái)講,簡(jiǎn)直微不足道 。
為什么傳統(tǒng)通信協(xié)議不選擇這樣做:
(1)、參考劣勢(shì)中的3點(diǎn) 。
(2)、傳統(tǒng)API通信,我們更講究快速、通用,也就是會(huì)經(jīng)常和不同團(tuán)隊(duì)乃至不同公司調(diào)試代碼,要設(shè)計(jì)協(xié)議,開(kāi)發(fā)成本和調(diào)試成本非常高 。
(3)、可讀性,對(duì)于業(yè)務(wù)代碼來(lái)講,byte[]的可讀性較差,尤其是對(duì)象嵌套的時(shí)候,byte[]表達(dá)的方式是很復(fù)雜的 。
MySQL JDBC如果用協(xié)議后,數(shù)據(jù)的緊湊性是不是達(dá)到極致了呢?
按照一般的理解 , 就是達(dá)到極致了 , 所有數(shù)據(jù)都不會(huì)進(jìn)一步放大 , int就只用4個(gè)字節(jié)傳遞 , long就只用8個(gè)字節(jié)傳遞,那么還能繼續(xù)變小,難道壓縮來(lái)做?
非也、非也,在二進(jìn)制的世界里,如果你探究細(xì)節(jié),還有更多比較神奇的東西,下面我們來(lái)探討一下:
例如:
long id = 1L;
此時(shí)網(wǎng)絡(luò)傳遞的時(shí)候會(huì)使用8個(gè)字節(jié)來(lái)方,大家可以看下8個(gè)字節(jié)的排布:
我們先不考慮按照bit有31個(gè)bit是0,先按照字節(jié)來(lái)看有7個(gè)0,代表字節(jié)沒(méi)有數(shù)據(jù),只有1個(gè)字節(jié)是有值的,大家可以去看一下自己的數(shù)據(jù)庫(kù)中大量的自動(dòng)增長(zhǎng)列,在id小于之前,前面5個(gè)字節(jié)是浪費(fèi)掉的 , 在增長(zhǎng)到4個(gè)字節(jié)(2的32次方-1)之前前面4個(gè)自己都是0,浪費(fèi)掉的 。另外,即使8個(gè)字節(jié)中的第一個(gè)字節(jié)開(kāi)始使用 , 也會(huì)有大量的數(shù)據(jù),中間字節(jié)是為:0的概率極高,就像十進(jìn)制中進(jìn)入1億 , 那么1億下面最多會(huì)有8個(gè)0java中xml怎么提示,越高位的0約難補(bǔ)充上去 。
如果真的想去嘗試,可以用這個(gè)辦法:用1個(gè)字節(jié)來(lái)做標(biāo)志,但會(huì)占用一定的計(jì)算開(kāi)銷(xiāo),所以是否為了這個(gè)空間去做這個(gè)事情,由你決定,本文僅僅是技術(shù)性探討:
方法1:表達(dá)目前有幾個(gè)低位被使用的字節(jié)數(shù) , 由于long只有8個(gè)字節(jié) , 所以用3個(gè)bit就夠了,另外5個(gè)bit是浪費(fèi)掉的,也無(wú)所謂了,反序列化的時(shí)候按照高位數(shù)量補(bǔ)充0x00即可 。
方法2:相對(duì)方法1,更徹底,但處理起來(lái)更復(fù)雜,用1這個(gè)字節(jié)的8個(gè)bit的0、1分別代表long的8個(gè)字節(jié)是被使用,序列化和反序列化過(guò)程根據(jù)標(biāo)志位和數(shù)據(jù)本身進(jìn)行字節(jié)補(bǔ)0x00操作,補(bǔ)充完整8個(gè)字節(jié)就是long的值了,最壞情況是9個(gè)字節(jié)代表long , 最佳情況0是1個(gè)字節(jié),字節(jié)中只占用了2個(gè)字節(jié)的時(shí)候,即使數(shù)據(jù)變得相當(dāng)大,也會(huì)有大量的數(shù)據(jù)的字節(jié)存在空位的情況,在這些情況下,就通常可以用少于8個(gè)字節(jié)的情況來(lái)表達(dá),要用滿7個(gè)字節(jié)才能夠與原數(shù)字long的占用空間一樣,此時(shí)的數(shù)據(jù)已經(jīng)是比2的48次方-1更大的數(shù)據(jù)了 。
【三、技術(shù)方案】:
這個(gè)對(duì)于很多人來(lái)講未必用過(guò),也不知道它是用來(lái)干什么的 , 不過(guò)我不得不說(shuō),它是目前數(shù)據(jù)序列化和反序列化的一個(gè)神器,這個(gè)東西是在谷歌內(nèi)部為了約定好自己內(nèi)部的數(shù)據(jù)通信設(shè)計(jì)出來(lái)的,大家都知道谷歌的全球網(wǎng)絡(luò)非常牛逼,那么自然在數(shù)據(jù)傳輸方面做得那是相當(dāng)極致,在這里我會(huì)講解下它的原理 , 就本身其使用請(qǐng)大家查閱其它人的博客 , 本文篇幅所限沒(méi)法step by step進(jìn)行講解 。
看到這個(gè)名字 , 應(yīng)該知道是協(xié)議 , 或者是協(xié)議編碼,其目的和上文中提到的用JSON、XML用來(lái)進(jìn)行RPC調(diào)用類(lèi)似,就是系統(tǒng)之間傳遞消息或調(diào)用API 。但是谷歌一方面為了達(dá)到類(lèi)似于XML、JSON的可讀性和跨語(yǔ)言的通用性,另一方面又希望達(dá)到較高的序列化和反序列化性能,數(shù)據(jù)放大能夠進(jìn)行控制,所以它又希望有一種比底層編碼更容易使用,而又可以使用底層編碼的方式 , 又具備文檔的可讀性能力 。
它首先需要定義一個(gè)格式文件,如下:
= “”;
com.xxx.proto..test;
{
int32 id = 2;
int64= 1;
bool= 3;
name = 4;
bytes= 5;
int32 id2 = 6;
}
這個(gè)文件不是Java文件,也不是C文件,和語(yǔ)言無(wú)關(guān),通常把它的后綴命名為proto(文件中1、2、3數(shù)字代表序列化的順序,反序列化也會(huì)按照這個(gè)順序來(lái)做),然后本地安裝了后(不同OS安裝方式不同,在官方有下載和說(shuō)明),會(huì)產(chǎn)生一個(gè)運(yùn)行文件 , 將其加入環(huán)境變量后,運(yùn)行命令指定一個(gè)目標(biāo)目錄:
–=~/temp/ .proto
此時(shí)會(huì)在指定的目錄下,產(chǎn)生所描述的目錄 , 在其目錄內(nèi)部有1個(gè)Java源文件(其它語(yǔ)言的會(huì)產(chǎn)生其它語(yǔ)言),這部分代碼是谷歌幫你生成的,你自己寫(xiě)的話太費(fèi)勁 , 所以谷歌就幫你干了;本地的Java 里面要引入包,maven引用(版本自行選擇):
com..
-java
3.6.1
此時(shí)生成的代碼會(huì)調(diào)用這個(gè)谷歌包里面提供的方法庫(kù)來(lái)做序列化動(dòng)作,我們的代碼只需要調(diào)用生成的這個(gè)類(lèi)里面的API就可以做序列化和反序列化操作了 , 將這些生成的文件放在一個(gè)模塊里面發(fā)布到maven倉(cāng)庫(kù)別人就可以引用了,關(guān)于測(cè)試代碼本身,大家可以參考目前有很多博客有提供測(cè)試代碼,還是很好用的 。
谷歌編碼比較神奇的是 , 你可以按照對(duì)象的方式定義傳輸數(shù)據(jù)的格式,可讀性極高,甚至于相對(duì)XML和JSON更適合程序員閱讀,也可以作為交流文檔,不同語(yǔ)言都通用 , 定義的對(duì)象還是可以嵌套的,但是它序列化出來(lái)的字節(jié)比原始數(shù)據(jù)只大一點(diǎn)點(diǎn),這尼瑪太厲害了吧 。
經(jīng)過(guò)測(cè)試不同的數(shù)據(jù)類(lèi)型,故意制造數(shù)據(jù)嵌套的層數(shù) , 進(jìn)行二進(jìn)制數(shù)組多層嵌套,發(fā)現(xiàn)其數(shù)據(jù)放大的比例非常非常小 , 幾乎可以等價(jià)于二進(jìn)制傳輸,于是我把序列化后的數(shù)據(jù)其二進(jìn)制進(jìn)行了輸出 , 發(fā)現(xiàn)其編碼方式非常接近于上面的JDBC,雖然有一些細(xì)節(jié)上的區(qū)別,但是非常接近,除此之外,它在序列化的時(shí)候有幾大特征:
(1)、如果字段為空,它不會(huì)產(chǎn)生任何字節(jié),如果整合對(duì)象的屬性都為null , 產(chǎn)生的字節(jié)將是0
(2)、對(duì)int32、int64這些數(shù)據(jù)采用了變長(zhǎng)編碼,其思路和我們上面描述有一些共通之處,就是一個(gè)int64值在比較小的時(shí)候用比較少的字節(jié)就可以表達(dá)了,其內(nèi)部有一套字節(jié)的移位和異或算法來(lái)處理這個(gè)事情 。
(3)、它對(duì)字符串、byte[]沒(méi)有做任何轉(zhuǎn)換 , 直接放入字節(jié)數(shù)組,和二進(jìn)制編碼是差不多的道理 。
(4)、由于字段為空它都可以不做任何字節(jié),它的做法是有數(shù)據(jù)的地方會(huì)有一個(gè)位置編碼信息 , 大家可以嘗試通過(guò)調(diào)整數(shù)字順序看看生成出來(lái)的byte是否會(huì)發(fā)生改變;那么此時(shí)它就有了很強(qiáng)兼容性,也就是普通的加字段是沒(méi)問(wèn)題的,這個(gè)對(duì)于普通的二進(jìn)制編碼來(lái)講很難做到 。
(5)、序列化過(guò)程沒(méi)有產(chǎn)生信息 , 也就是它不會(huì)把對(duì)象的結(jié)構(gòu)寫(xiě)在字節(jié)里面,而是反序列化的接收方有同一個(gè)對(duì)象,就可以反解析出來(lái)了 。
這與我自己寫(xiě)編碼有何區(qū)別?
(1)、自己寫(xiě)編碼有很多不確定性,寫(xiě)不好的話,數(shù)據(jù)可能放得更大,也容易出錯(cuò) 。
(2)、的工程師把內(nèi)部規(guī)范后 , 谷歌開(kāi)源的產(chǎn)品也大量使用這樣的通信協(xié)議,越來(lái)越多的業(yè)界中間件產(chǎn)品開(kāi)始使用該方案,就連MySQL數(shù)據(jù)庫(kù)最新版本在數(shù)據(jù)傳輸方面也會(huì)開(kāi)始兼容 。
(3)、谷歌相當(dāng)于開(kāi)始在定義一個(gè)業(yè)界的新的數(shù)據(jù)傳輸方案,即有性能又降低代碼研發(fā)的難度,也有跨語(yǔ)言訪問(wèn)的能力 , 所以才會(huì)有越來(lái)越多的人喜歡使用這個(gè)東西 。
那么它有什么缺點(diǎn)呢?還真不多,它基本兼顧了很多序列化和反序列化中你需要考慮的所有的事情,達(dá)到了一種非常良好的平衡,但是硬要挑缺陷,我們就得找場(chǎng)景才行:
(1)、需要雙方明確數(shù)據(jù)類(lèi)型 , 且定義的文件中每一個(gè)對(duì)象要明確數(shù)據(jù)類(lèi)型,對(duì)于類(lèi)型的表達(dá)沒(méi)有方案,你自己必須提前預(yù)知這個(gè)到底是什么類(lèi)型 。
(2)、使用可以表達(dá)數(shù)組,但是只能表達(dá)相同類(lèi)型的數(shù)據(jù),例如上面提到的JDBC一行數(shù)據(jù)的多個(gè)列數(shù)據(jù)類(lèi)型不同的時(shí)候 , 要用這個(gè)表達(dá),會(huì)比較麻煩;另外,默認(rèn)情況下數(shù)組只能表達(dá)1維數(shù)組,要表達(dá)二維數(shù)組,需要使用對(duì)象嵌套來(lái)間接完成 。
(3)、它提供的數(shù)據(jù)類(lèi)型都是基本數(shù)據(jù)類(lèi)型 , 如果不是普通類(lèi)型,要自己想辦法轉(zhuǎn)換為普通類(lèi)型進(jìn)行傳輸,例如從查處一個(gè)對(duì)象,這個(gè)對(duì)象序列化是需要自己先通過(guò)別的方式轉(zhuǎn)換為byte[]或放進(jìn)去的,而相對(duì)XML、JSON普通是提供了遞歸的功能,但是如果要提供這個(gè)功能,必然會(huì)面臨數(shù)據(jù)放大的問(wèn)題,通用和性能永遠(yuǎn)是矛盾的 。
(4)、相對(duì)于自定義byte的話,序列化和反序列化是一次性完成,不能逐步完成,這樣如果傳遞數(shù)組嵌套,在反序列化的時(shí)候會(huì)產(chǎn)生大量的Java對(duì)象 , 另外自定義byte的話可以進(jìn)一步減少內(nèi)存拷貝,不過(guò)谷歌這個(gè)相對(duì)文本協(xié)議來(lái)講內(nèi)存拷貝已經(jīng)少很多了 。
補(bǔ)充說(shuō)明:
在第2點(diǎn)中提到表達(dá)的數(shù)組,每一個(gè)元素必須是同類(lèi)型的 , 無(wú)法直接表達(dá)不同類(lèi)型的元素,因?yàn)樗鼪](méi)有像Java那樣[]這樣的數(shù)組,這樣它即使通過(guò)本地判定的類(lèi)型傳遞了 , 反序列化會(huì)很麻煩,因?yàn)榻邮辗揭膊恢罃?shù)據(jù)是什么類(lèi)型,而網(wǎng)絡(luò)傳遞數(shù)據(jù)是沒(méi)有傳遞的 , 那么判定唯一的地方就是在客戶(hù)端自己根據(jù)業(yè)務(wù)需要進(jìn)行傳遞 。
因此,如果真的有必要的話,可以用List表達(dá)一行數(shù)據(jù)的方案,也就是里面的每1個(gè)byte[]元素通過(guò)其它地方獲得 , 例如數(shù)據(jù)庫(kù)的得到,然后再自己去轉(zhuǎn)換,但是傳遞過(guò)程全是byte[];就JDBC來(lái)講,我個(gè)人更加推薦于一行數(shù)據(jù)使用一個(gè)byte[]來(lái)傳遞,而不是每一項(xiàng)數(shù)據(jù)用一個(gè)byte[],因?yàn)樵谛蛄谢头葱蛄谢^(guò)程中,每一個(gè)數(shù)組元素都會(huì)在會(huì)被包裝成對(duì)象,此時(shí)產(chǎn)生的Java對(duì)象數(shù)量是列的倍數(shù) , 例如有40個(gè)列,會(huì)產(chǎn)生40倍的Java對(duì)象,很夸張吧 。
總之,每一種序列化和反序列化方案目前都有應(yīng)用場(chǎng)景,它們?cè)谠O(shè)計(jì)之初決定了架構(gòu),也將決定了最終的性能、穩(wěn)定性、系統(tǒng)開(kāi)銷(xiāo)、網(wǎng)絡(luò)傳輸大小等等 。
本文到此結(jié)束,希望對(duì)大家有所幫助 。