• 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

  • 相关阅读:
    多线程学习笔记第一篇
    当Visual Studio中win32控制台应用程序的注释也会生产代码时……
    博客行文及排版技法
    Debian Linux下的Python学习——安装python,vim
    onhashchange事件
    MyEclipse9 Maven开发Web工程 详细配置
    Java面向对象(上)
    lucene 的分析器(analyzer)与分词器(tokenizer)和过滤器(tokenfilter)
    java编程陷阱
    solr中文分词(mmseg4j)
  • 原文地址:https://www.cnblogs.com/sunshine-2015/p/10891813.html
Copyright © 2020-2023  润新知