你好,我是 Guide。今天分享一道讀者面試招銀網(wǎng)絡(luò)科技遇到的面試真題。
下面是正文。
序列化和反序列化相關(guān)概念
什么是序列化?什么是反序列化?
如果我們需要持久化 Java 對(duì)象比如將 Java 對(duì)象保存在文件中,或者在網(wǎng)絡(luò)上傳輸 Java 對(duì)象,這些場(chǎng)景都需要用到序列化。
簡單來說:
序列化
:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制字節(jié)流的過程
反序列化
:將在序列化過程中所生成的二進(jìn)制字節(jié)流的過程轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過程
對(duì)于 Java 這種面向?qū)ο缶幊陶Z言來說,我們序列化的都是對(duì)象(Object)也就是實(shí)例化后的類(Class),但是在 C++這種半面向?qū)ο蟮恼Z言中,struct(結(jié)構(gòu)體)定義的是數(shù)據(jù)結(jié)構(gòu)類型,而 class 對(duì)應(yīng)的是對(duì)象類型。
維基百科是如是介紹序列化的:
序列化
(serialization)在計(jì)算機(jī)科學(xué)的數(shù)據(jù)處理中,是指將數(shù)據(jù)結(jié)構(gòu)或?qū)ο鬆顟B(tài)轉(zhuǎn)換成可取用格式(例如存成文件,存于緩沖,或經(jīng)由網(wǎng)絡(luò)中發(fā)送),以留待后續(xù)在相同或另一臺(tái)計(jì)算機(jī)環(huán)境中,能恢復(fù)原先狀態(tài)的過程。依照序列化格式重新獲取字節(jié)的結(jié)果時(shí),可以利用它來產(chǎn)生與原始對(duì)象相同語義的副本。對(duì)于許多對(duì)象,像是使用大量引用的復(fù)雜對(duì)象,這種序列化重建的過程并不容易。面向?qū)ο笾械膶?duì)象序列化,并不概括之前原始對(duì)象所關(guān)系的函數(shù)。這種過程也稱為對(duì)象編組(marshalling)。從一系列字節(jié)提取數(shù)據(jù)結(jié)構(gòu)的反向操作,是反序列化(也稱為解編組、deserialization、unmarshalling)。
綜上:
序列化的主要目的是通過網(wǎng)絡(luò)傳輸對(duì)象或者說是將對(duì)象存儲(chǔ)到文件系統(tǒng)、數(shù)據(jù)庫、內(nèi)存中。
實(shí)際開發(fā)中有哪些用到序列化和反序列化的場(chǎng)景?
對(duì)象在進(jìn)行網(wǎng)絡(luò)傳輸(比如遠(yuǎn)程方法調(diào)用 RPC 的時(shí)候)之前需要先被序列化,接收到序列化的對(duì)象之后需要再進(jìn)行反序列化;
將對(duì)象存儲(chǔ)到文件中的時(shí)候需要進(jìn)行序列化,將對(duì)象從文件中讀取出來需要進(jìn)行序列化。
將對(duì)象存儲(chǔ)到緩存數(shù)據(jù)庫(如 Redis)時(shí)需要用到序列化,將對(duì)象從緩存數(shù)據(jù)庫中讀取出來需要反序列化。
序列化協(xié)議對(duì)應(yīng)于 TCP/IP 4 層模型的哪一層?
我們知道網(wǎng)絡(luò)通信的雙方必須要采用和遵守相同的協(xié)議。TCP/IP 四層模型是下面這樣的,序列化協(xié)議屬于哪一層呢?
應(yīng)用層
傳輸層
網(wǎng)絡(luò)層
網(wǎng)絡(luò)接口層
TCP/IP 4層模型
如上圖所示,OSI 七層協(xié)議模型中,表示層做的事情主要就是對(duì)應(yīng)用層的用戶數(shù)據(jù)進(jìn)行處理轉(zhuǎn)換為二進(jìn)制流。反過來的話,就是將二進(jìn)制流量轉(zhuǎn)換成應(yīng)用層的用戶數(shù)據(jù)。這不就對(duì)應(yīng)的是序列化和反序列化么?
因?yàn)椋琌SI 七層協(xié)議模型中的應(yīng)用層、表示層和會(huì)話層對(duì)應(yīng)的都是 TCP/IP 四層模型中的應(yīng)用層,所以序列化協(xié)議屬于 TCP/IP 協(xié)議應(yīng)用層的一部分。
常見序列化協(xié)議對(duì)比
JDK 自帶的序列化方式一般不會(huì)用 ,因?yàn)樾蛄谢实筒⑶也糠职姹居邪踩┒?。比較常用的序列化協(xié)議有 hessian、kyro、protostuff。
下面提到的都是基于二進(jìn)制的序列化協(xié)議,像 JSON 和 XML 這種屬于文本類序列化方式。雖然 JSON 和 XML 可讀性比較好,但是性能較差,一般不會(huì)選擇。
JDK 自帶的序列化方式
JDK 自帶的序列化,只需實(shí)現(xiàn)
java.io.Serializable
接口即可。
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 1905122041950251207L;
private String requestId;
private String interfaceName;
private String methodName;
private Object[] parameters;
private Class>[] paramTypes;
private RpcMessageTypeEnum rpcMessageTypeEnum;
}
序列化號(hào) serialVersionUID 屬于版本控制的作用。序列化的時(shí)候 serialVersionUID 也會(huì)被寫入二級(jí)制序列,當(dāng)反序列化時(shí)會(huì)檢查 serialVersionUID 是否和當(dāng)前類的 serialVersionUID 一致。如果 serialVersionUID 不一致則會(huì)拋出
InvalidClassException
異常。強(qiáng)烈推薦每個(gè)序列化類都手動(dòng)指定其
serialVersionUID
,如果不手動(dòng)指定,那么編譯器會(huì)動(dòng)態(tài)生成默認(rèn)的序列化號(hào)
我們很少或者說幾乎不會(huì)直接使用這個(gè)序列化方式,主要原因有兩個(gè):
不支持跨語言調(diào)用
: 如果調(diào)用的是其他語言開發(fā)的服務(wù)的時(shí)候就不支持了。
性能差
:相比于其他序列化框架性能更低,主要原因是序列化之后的字節(jié)數(shù)組體積較大,導(dǎo)致傳輸成本加大。
Kryo
Kryo 是一個(gè)高性能的序列化/反序列化工具,由于其變長存儲(chǔ)特性并使用了字節(jié)碼生成機(jī)制,擁有較高的運(yùn)行速度和較小的字節(jié)碼體積。
另外,Kryo 已經(jīng)是一種非常成熟的序列化實(shí)現(xiàn)了,已經(jīng)在 Twitter、Groupon、Yahoo 以及多個(gè)著名開源項(xiàng)目(如 Hive、Storm)中廣泛的使用。
guide-rpc-framework[1]
就是使用的 kyro 進(jìn)行序列化,序列化和反序列化相關(guān)的代碼如下:
/**
* Kryo serialization class, Kryo serialization efficiency is very high, but only compatible with Java language
*
* @author shuang.kou
* @createTime 2020年05月13日 19:29:00
*/
@Slf4j
public class KryoSerializer implements Serializer {
/**
* Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
*/
private final ThreadLocal
Kryo kryo = new Kryo();
kryo.register(RpcResponse.class);
kryo.register(RpcRequest.class);
return kryo;
});
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream)) {
Kryo kryo = kryoThreadLocal.get();
// Object->byte:將對(duì)象序列化為byte數(shù)組
kryo.writeObject(output, obj);
kryoThreadLocal.remove();
return output.toBytes();
} catch (Exception e) {
throw new SerializeException("Serialization failed");
}
}
@Override
public
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream)) {
Kryo kryo = kryoThreadLocal.get();
// byte->Object:從byte數(shù)組中反序列化出對(duì)對(duì)象
Object o = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return clazz.cast(o);
} catch (Exception e) {
throw new SerializeException("Deserialization failed");
}
}
}
Github 地址:
。
Protobuf
Protobuf 出自于 Google,性能還比較優(yōu)秀,也支持多種語言,同時(shí)還是跨平臺(tái)的。就是在使用中過于繁瑣,因?yàn)槟阈枰约憾x IDL 文件和生成對(duì)應(yīng)的序列化代碼。這樣雖然不然靈活,但是,另一方面導(dǎo)致 protobuf 沒有序列化漏洞的風(fēng)險(xiǎn)。
Protobuf 包含序列化格式的定義、各種語言的庫以及一個(gè) IDL 編譯器。正常情況下你需要定義 proto 文件,然后使用 IDL 編譯器編譯成你需要的語言
一個(gè)簡單的 proto 文件如下:
// protobuf的版本
syntax = "proto3";
// SearchRequest會(huì)被編譯成不同的編程語言的相應(yīng)對(duì)象,比如Java中的class、Go中的struct
message Person {
//string類型字段
string name = 1;
// int 類型字段
int32 age = 2;
}
Github 地址:
。
ProtoStuff
由于 Protobuf 的易用性,它的哥哥 Protostuff 誕生了。
protostuff 基于 Google protobuf,但是提供了更多的功能和更簡易的用法。雖然更加易用,但是不代表 ProtoStuff 性能更差。
Github 地址:
。
hession
hessian 是一個(gè)輕量級(jí)的,自定義描述的二進(jìn)制 RPC 協(xié)議。hessian 是一個(gè)比較老的序列化實(shí)現(xiàn)了,并且同樣也是跨語言的。
dubbo RPC 默認(rèn)啟用的序列化方式是 hession2 ,但是,Dubbo 對(duì) hessian2 進(jìn)行了修改,不過大體結(jié)構(gòu)還是差不多。
總結(jié)
Kryo 是專門針對(duì) Java 語言序列化方式并且性能非常好,如果你的應(yīng)用是專門針對(duì) Java 語言的話可以考慮使用,并且 Dubbo 官網(wǎng)的一篇文章中提到說推薦使用 Kryo 作為生產(chǎn)環(huán)境的序列化方式。(文章地址:
)
像 Protobuf、 ProtoStuff、hession 這類都是跨語言的序列化方式,如果有跨語言需求的話可以考慮使用。
除了我上面介紹到的序列化方式的話,還有像 Thrift,Avro 這些。
其他推薦閱讀
美團(tuán)技術(shù)團(tuán)隊(duì)-序列化和反序列化:
在 Dubbo 中使用高效的 Java 序列化(Kryo 和 FST):
參考資料
[1]
guide-rpc-framework:
[2]
[3]
[4]
[5]
[6]
[7]
·········· END ··············
責(zé)任編輯:Rex_08