dubbo序列化
dubbo作為一個rpc框架支持豐富的序列化方式,本文簡單介紹dubbo的序列化。本文結構:
-
對象序列化是什么意思?
-
dubbo序列化
-
幾個問題
對象序列化是什意思?
先來思考兩個問題:
- 普通的Java對象的生命周期是僅限於一個JVM中的,只要JVM停止,這個對象也就不存在了,下次JVM啟動我們還想使用這個對象怎么辦呢?
- 或者我們想要把一個對象傳遞給另外一個JVM的時候,應該怎么做呢?
這兩個問題的答案就是將該對象進行序列化,然后保存在文件中或者進行網絡傳輸到另一個JVM,由另外一個JVM反序列化成一個對象,然后供JVM使用。
對象序列化就是將一個存在內存中的對象,轉換為可存儲或者可傳輸的二進制流,並且根據序列化的規則可以進行反序列化。一般來說需要保存的對象信息包括
- 類的全限定名稱
- 未被transparent修飾的字段值
將這些信息按照一定的規則轉換為二進制之后進行網絡傳輸,在接收到的一端,就可以根據這些信息先實例化這個類對應的對象,然后將對應的屬性值填入新構造的對象,在使用者看來就是使用的原來的對象。
dubbo序列化
dubbo協議
dubbo支持很多種通信協議,其中dubbo協議作為默認的通信協議,dubbo協議的協議格式如下
header | 0-15 | 16 | 17 | 18 | 19-23 | 24-31 | 32-95 | 96-127 |
---|---|---|---|---|---|---|---|---|
MAGIC = (short) 0xdabb | req/resp | two /one way | event,是心跳還是正常消息 | serializationId 指定序列化的類型 | 狀態位, 消息類型為response時,設置請求響應狀態 | 消息的id,long類型 | body的length,int類型 | |
body |
header的字段格式是固定的,所以header的序列化方式也是固定的,header序列化過程如下(以request的encode為例)
// com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
// 根據配置獲取序列化的協議,默認是hessian2
Serialization serialization = getSerialization(channel);
// header.
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
// oneway還是twoway
if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
// event種類
if (req.isEvent()) header[2] |= FLAG_EVENT;
// set request id.
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (req.isEvent()) {
encodeEventData(channel, out, req.getData());
} else {
encodeRequestData(channel, out, req.getData());
}
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
checkPayload(channel, len);
// 消息長度,在消息序列化完成后才能確定消息體body的長度
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
header的序列化方式是固定的,但是消息體body可以指定不同的序列化方式,由於消息體可以由用戶自定義,所以可能是各種類型。一種序列化協議需要支持序列化和反序列化各種類型,包括基礎類型和類類型,比如:null、long、int、String、List、Map、enum、自定義類等。
dubbo中序列化協議都實現了下面的接口
com.alibaba.dubbo.common.serialize.Serialization
通過SPI擴展可以實現不同的協議,默認的SPI擴展是hessian2
com.alibaba.dubbo.common.serialize.support.hessian.Hessian2Serialization
dubbo中的序列化、反序列化都要實現下面的接口
com.alibaba.dubbo.common.serialize.ObjectOutput
com.alibaba.dubbo.common.serialize.ObjectInput
比如hessian序列化和反序列化
com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectOutput
com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput
這兩個類里面的方法是通過調用Hessian2Output、Hessian2Input方法實現的,Hessian2Output和Hessian2Input實現了hessian協議。具體的協議內容可以參考這個
幾個問題
為什么參數對象都要實現Serializable接口(使用dubbo協議,默認的序列化方式hessian的時候)?
因為dubbo使用hessian序列化方式的時候,對象的序列化使用的是JavaSerializer
com.alibaba.com.caucho.hessian.io.SerializerFactory#getDefaultSerializer
com.alibaba.com.caucho.hessian.io.SerializerFactory#getSerializer
com.alibaba.com.caucho.hessian.io.Hessian2Output#writeObject
獲取默認的序列化方式的時候會判斷該參數是否實現了Serializable接口
protected Serializer getDefaultSerializer(Class cl) {
if (_defaultSerializer != null)
return _defaultSerializer;
// 判斷是否實現了Serializable接口
if (!Serializable.class.isAssignableFrom(cl)
&& !_isAllowNonSerializable) {
throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
}
return new JavaSerializer(cl, _loader);
}
如果沒有實現Serializable接口的話就拋出異常,所以在聲明參數的時候,參數是一個類,這個類必須實現Serializable接口。
關於serialVersionUID
serialVersionUID是Java原生序列化時候的一個關鍵屬性,但是在不使用Java原生序列化的時候,這個屬性是沒有被用到的,比如基於hessian協議實現的序列化方式中沒有用到這個屬性。
這里說的Java原生序列化是指使用下面的序列化方式和反序列化方式
java.io.ObjectOutputStream
java.io.ObjectInputStream
在使用原生序列化的時候,serialVersionUID起到了一個類似版本號的作用,在反序列化的時候判斷serialVersionUID如果不相同,會拋出InvalidClassException。
如果在使用原生序列化方式的時候官方是強烈建議指定一個serialVersionUID的,如果沒有指定,在序列化過程中,jvm會自動計算出一個值作為serialVersionUID,由於這種運行時計算serialVersionUID的方式依賴於jvm的實現方式,如果序列化和反序列化的jvm實現方式不一樣可能會導致拋出異常InvalidClassException,所以強烈建議指定serialVersionUID。
參考