dubbo序列化


dubbo序列化

dubbo作為一個rpc框架支持豐富的序列化方式,本文簡單介紹dubbo的序列化。本文結構:

  • 對象序列化是什么意思?

  • dubbo序列化

  • 幾個問題

對象序列化是什意思?

先來思考兩個問題:

  1. 普通的Java對象的生命周期是僅限於一個JVM中的,只要JVM停止,這個對象也就不存在了,下次JVM啟動我們還想使用這個對象怎么辦呢?
  2. 或者我們想要把一個對象傳遞給另外一個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。

參考

dubbo文檔

Understand the serialVersionUID


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM