掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流
網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)須是二進(jìn)制數(shù)據(jù),但調(diào)用方請(qǐng)求的出入?yún)?shù)都是對(duì)象:

網(wǎng)站建設(shè)公司,為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及定制網(wǎng)站建設(shè)服務(wù),專(zhuān)注于成都企業(yè)網(wǎng)站建設(shè),高端網(wǎng)頁(yè)制作,對(duì)成都玻璃隔斷等多個(gè)行業(yè)擁有豐富的網(wǎng)站建設(shè)經(jīng)驗(yàn)的網(wǎng)站建設(shè)公司。專(zhuān)業(yè)網(wǎng)站設(shè)計(jì),網(wǎng)站優(yōu)化推廣哪家好,專(zhuān)業(yè)seo優(yōu)化優(yōu)化,H5建站,響應(yīng)式網(wǎng)站。
序列化與反序列化
RPC框架為何需要序列化?
回想RPC通信流程:
RPC通信流程圖
案例:
import java.io.*;
public class Student implements Serializable {
//學(xué)號(hào)
private int no;
//姓名
private String name;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
String home = System.getProperty("user.home");
String basePath = home + "/Desktop";
FileOutputStream fos = new FileOutputStream(basePath + "student.dat");
Student student = new Student();
student.setNo(100);
student.setName("TEST_STUDENT");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(student);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(basePath + "student.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Student deStudent = (Student) ois.readObject();
ois.close();
System.out.println(deStudent);
}
}
JDK序列化過(guò)程:
ObjectOutputStream序列化過(guò)程圖
序列化過(guò)程就是在讀取對(duì)象數(shù)據(jù)的時(shí)候,不斷加入一些特殊分隔符,這些特殊分隔符用于在反序列化過(guò)程中截?cái)嘤谩?/p>
將對(duì)象的類(lèi)型、屬性類(lèi)型、屬性值按固定格式寫(xiě)到二進(jìn)制字節(jié)流中來(lái)完成序列化,再按固定格式讀出對(duì)象的類(lèi)型、屬性類(lèi)型、屬性值,通過(guò)這些信息重建一個(gè)新的對(duì)象,完成反序列化。
典型KV方式,沒(méi)有數(shù)據(jù)類(lèi)型,是一種文本型序列化框架。
所以如果RPC框架選用JSON序列化,服務(wù)提供者與服務(wù)調(diào)用者之間傳輸?shù)臄?shù)據(jù)量要相對(duì)較小。
動(dòng)態(tài)類(lèi)型、二進(jìn)制、緊湊的,并且可跨語(yǔ)言移植的一種序列化框架。比JDK、JSON更加緊湊,性能上要比JDK、JSON序列化高效很多,而且生成的字節(jié)數(shù)更小。
使用代碼示例如下:
Student student = new Student();
student.setNo(101);
student.setName("HESSIAN");
//把student對(duì)象轉(zhuǎn)化為byte數(shù)組
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(student);
output.flushBuffer();
byte[] data = bos.toByteArray();
bos.close();
//把剛才序列化出來(lái)的byte數(shù)組轉(zhuǎn)化為student對(duì)象
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
Student deStudent = (Student) input.readObject();
input.close();
System.out.println(deStudent);
相對(duì)于JDK、JSON,由于Hessian更加高效,生成的字節(jié)數(shù)更小,有非常好的兼容性和穩(wěn)定性,所以Hessian更加適合作為RPC框架遠(yuǎn)程通信的序列化協(xié)議。
但Hessian本身也有問(wèn)題,官方版本對(duì)Java里面一些常見(jiàn)對(duì)象的類(lèi)型不支持,比如:
Protobuf 是 Google 公司內(nèi)部的混合語(yǔ)言數(shù)據(jù)標(biāo)準(zhǔn),是一種輕便、高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,可以用于結(jié)構(gòu)化數(shù)據(jù)序列化,支持Java、Python、C++、Go等語(yǔ)言。Protobuf使用的時(shí)候需要定義IDL(Interface description language),然后使用不同語(yǔ)言的IDL編譯器,生成序列化工具類(lèi),它的優(yōu)點(diǎn)是:
使用代碼示例如下:
/**
*
* // IDl 文件格式
* synax = "proto3";
* option java_package = "com.test";
* option java_outer_classname = "StudentProtobuf";
*
* message StudentMsg {
* //序號(hào)
* int32 no = 1;
* //姓名
* string name = 2;
* }
*
*/
StudentProtobuf.StudentMsg.Builder builder = StudentProtobuf.StudentMsg.newBuilder();
builder.setNo(103);
builder.setName("protobuf");
//把student對(duì)象轉(zhuǎn)化為byte數(shù)組
StudentProtobuf.StudentMsg msg = builder.build();
byte[] data = msg.toByteArray();
//把剛才序列化出來(lái)的byte數(shù)組轉(zhuǎn)化為student對(duì)象
StudentProtobuf.StudentMsg deStudent = StudentProtobuf.StudentMsg.parseFrom(data);
System.out.println(deStudent);
Protobuf 非常高效,但是對(duì)于具有反射和動(dòng)態(tài)能力的語(yǔ)言來(lái)說(shuō),這樣用起來(lái)很費(fèi)勁,這一點(diǎn)就不如Hessian,比如用Java的話(huà),這個(gè)預(yù)編譯過(guò)程不是必須的,可以考慮使用Protostuff。
Protostuff不需要依賴(lài)IDL文件,可以直接對(duì)Java領(lǐng)域?qū)ο筮M(jìn)行反/序列化操作,在效率上跟Protobuf差不多,生成的二進(jìn)制格式和Protobuf是完全相同的,可以說(shuō)是一個(gè)Java版本的Protobuf序列化框架。但在使用過(guò)程中,我遇到過(guò)一些不支持的情況,也同步給你:
即序列化之后的二進(jìn)制數(shù)據(jù)的體積大小。序列化后的字節(jié)數(shù)據(jù)體積越小,網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量就越小,傳輸數(shù)據(jù)的速度也就越快,由于RPC是遠(yuǎn)程調(diào)用,那么網(wǎng)絡(luò)傳輸?shù)乃俣葘⒅苯雨P(guān)系到請(qǐng)求響應(yīng)的耗時(shí)。
某類(lèi)型為集合類(lèi)的入?yún)⒎?wù)調(diào)用者不能解析了,服務(wù)提供方將入?yún)㈩?lèi)加一個(gè)屬性之后服務(wù)調(diào)用方不能正常調(diào)用,升級(jí)了RPC版本后發(fā)起調(diào)用時(shí)報(bào)序列化異?!?/p>
通用性和兼容性的優(yōu)先級(jí)考慮很高,直接關(guān)系到服務(wù)調(diào)用穩(wěn)定性和可用率。看重這種序列化協(xié)議在版本升級(jí)后的兼容性,是否支持更多的對(duì)象類(lèi)型,是否跨平臺(tái)、跨語(yǔ)言,是否有很多人已用過(guò)并踩過(guò)很多坑,其次考慮性能、效率和空間開(kāi)銷(xiāo)。
JDK原生序列化存在漏洞。如果序列化存在安全漏洞,線(xiàn)上服務(wù)可能被入侵:
首選Hessian與Protobuf,性能、時(shí)間開(kāi)銷(xiāo)、空間開(kāi)銷(xiāo)、通用性、兼容性和安全性上,都滿(mǎn)足要求:
屬性很多,并且存在多層的嵌套,比如A對(duì)象關(guān)聯(lián)B對(duì)象,B對(duì)象又聚合C對(duì)象,C對(duì)象又關(guān)聯(lián)聚合很多其他對(duì)象,對(duì)象依賴(lài)關(guān)系過(guò)于復(fù)雜。
序列化框架在序列化與反序列化對(duì)象時(shí),對(duì)象越復(fù)雜就越浪費(fèi)性能,消耗CPU,這會(huì)嚴(yán)重影響RPC框架整體的性能。
RPC請(qǐng)求經(jīng)常超時(shí),排查后發(fā)現(xiàn)他們的入?yún)?duì)象非常得大,比如為一個(gè)大List或者大Map,序列化之后字節(jié)長(zhǎng)度達(dá)到了上兆字節(jié)。這種情況同樣會(huì)嚴(yán)重地浪費(fèi)性能、CPU,并且序列化一個(gè)如此大的對(duì)象是很耗費(fèi)時(shí)間的,這肯定會(huì)直接影響到請(qǐng)求耗時(shí)。
如Hessian天然不支持LinkHashMap、LinkedHashSet等,而且大多數(shù)情況下最好不要使用第三方集合類(lèi),如Guava中的集合類(lèi),很多開(kāi)源的序列化框架都是優(yōu)先支持編程語(yǔ)言原生的對(duì)象。因此如果入?yún)⑹羌项?lèi),應(yīng)盡量選用原生的、最為常用的集合類(lèi),如HashMap、ArrayList。
序列化對(duì)象時(shí)會(huì)將對(duì)象屬性一一序列化,當(dāng)有繼承關(guān)系時(shí),會(huì)不停尋找父類(lèi),遍歷屬性。就像問(wèn)題1,對(duì)象關(guān)系越復(fù)雜,越浪費(fèi)性能。
在RPC框架的使用過(guò)程中,盡量構(gòu)建簡(jiǎn)單的對(duì)象作為入?yún)⒑头祷刂祵?duì)象,避免上述問(wèn)題。
使用RPC框架的過(guò)程中,我們構(gòu)造入?yún)?、返回值?duì)象,主要記住以下幾點(diǎn):
實(shí)際上,雖然RPC框架可以讓我們發(fā)起遠(yuǎn)程調(diào)用就像調(diào)用本地一樣,但在RPC框架的傳輸過(guò)程中,入?yún)⑴c返回值的根本作用就是用來(lái)傳遞信息的,為了提高RPC調(diào)用整體的性能和穩(wěn)定性,我們的入?yún)⑴c返回值對(duì)象要構(gòu)造得盡量簡(jiǎn)單。
RPC框架在序列化框架的選型上,你認(rèn)為還需要考慮哪些因素?你還知道哪些優(yōu)秀的序列化框架,它們又是否適合在RPC調(diào)用中使用?
序列化一般用在協(xié)議里面的payload里。
Redis使用的RESP,在做序列化時(shí)也是會(huì)增加很多冗余的字符,但它勝在實(shí)現(xiàn)簡(jiǎn)單、可讀性強(qiáng)易于理解。
JSON和XML使用字符串表示所有的數(shù)據(jù),對(duì)于非字符數(shù)據(jù)來(lái)說(shuō),字面量表達(dá)會(huì)占用很多額外的存儲(chǔ)空間,并且會(huì)嚴(yán)重受到數(shù)值大小和精度的影響。一個(gè)32位浮點(diǎn)數(shù) 1234.5678 在內(nèi)存中占用 4 bytes 空間,如果存儲(chǔ)為 utf8 ,則需要占用 9 bytes空間,在JS這樣使用utf16表達(dá)字符串的環(huán)境中,需要占用 18 bytes空間。使用正則表達(dá)式進(jìn)行數(shù)據(jù)解析,在面對(duì)非字符數(shù)據(jù)時(shí)顯得十分低效,不僅要耗費(fèi)大量的運(yùn)算解析數(shù)據(jù)結(jié)構(gòu),還要將字面量轉(zhuǎn)換成對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。
在面對(duì)海量數(shù)據(jù)時(shí),這種格式本身就能夠成為整個(gè)系統(tǒng)的IO與計(jì)算瓶頸,甚至直接overflow。
常見(jiàn)的序列化協(xié)議有:xml json protobuf jdk等 xml和json可讀性好,序列化后空間大,性能差,而且json序列化后無(wú)類(lèi)型,需要反射獲取對(duì)象類(lèi)型。而protobuf則是可讀性差點(diǎn),序列化后占用空間小,性能好,不需要反序列化獲取屬性類(lèi)型等優(yōu)點(diǎn)。對(duì)性能要求高的原則protobuf比較好點(diǎn)
最明顯的就是你說(shuō)的數(shù)據(jù)包大,因?yàn)樽址鄬?duì)二進(jìn)制更占空間。
json需要內(nèi)存去解析能理解,但為什么json序列化還需要磁盤(pán)開(kāi)銷(xiāo)啊。json序列化的二進(jìn)制數(shù)據(jù)在體量比其他序列化方法小一些吧,可以減少帶寬和流量?
說(shuō)的如果json數(shù)據(jù)存儲(chǔ)在磁盤(pán)上,json字節(jié)數(shù)相對(duì)其他數(shù)據(jù)都偏大。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流