前言:

Dubbo提供了多种协议来进行服务消费者和提供者之间的交互。

从Dubbo框架层次来看(参考:https://dubbo.apache.org/zh/docsv2.7/dev/design/),Dubbo协议位于以下位置:

dubbo自定义filter atachments为空 dubbo自定义协议_java

 

如何理解这些协议呢?我觉得可以按照HTTP协议的方式来理解,他们都是位于TCP协议之上的应用层协议。

可以把他们理解为一份交流的说明书,每个字节都有其特定含义,服务消费者和提供者按照这份说明书来发送数据、接收数据,按照说明书的指示,解析出每个字节的含义,最终拼接出一个完整的请求体、响应体即可。

那么自定义的Dubbo协议,是如何设定每个字节的含义?跟随笔者一起来看下吧。

1.Dubbo的那些协议

先来概述一下Dubbo提供的那些协议。首先从Protocol接口出发,展示下其实现类。

dubbo自定义filter atachments为空 dubbo自定义协议_java_02

 

每种协议所用来表示请求、响应的方式都有所不同,性能和适用场景也有所不同。

具体的使用场景和不同之处,可以参考Dubbo官网: 协议参考手册 | Apache Dubbo 

里面的相关介绍还是非常详细的。    

Dubbo官网中也有一张表来对比各协议,但是不够全,笔者从网上找到另一篇(),具体如下:

协议名称

实现描述

连接

使用场景

dubbo

传输:mina、netty、grizzly


序列化:dubbo、hessian2、java、json

    dubbo缺省采用单一长连接和NIO异步通讯   

1.传入传出参数数据包较小


2.消费者 比提供者多


3.常规远程服务方法调用


4.不适合传送大数据量的服务,比如文件、传视频

rmi

传输:java  rmi


序列化:java 标准序列化

    


连接个数:多连接


连接方式:短连接


传输协议:TCP/IP


传输方式:BIO


1.常规RPC调用


2.与原RMI客户端互操作


3.可传文件


4.不支持防火墙穿透

hessian


传输:Serverlet容器


序列化:hessian二进制序列化

   


    连接个数:多连接

    连接方式:短连接

    传输协议:HTTP

    传输方式:同步传输


   


1.提供者比消费者多


2.可传文件


3.跨语言传输

http


传输:servlet容器


序列化:表单序列化

    连接个数:多连接

    连接方式:短连接

    传输协议:HTTP

    传输方式:同步传输

1.提供者多余消费者


2.数据包混合

webservice


传输:HTTP


序列化:SOAP文件序列化

    连接个数:多连接

    连接方式:短连接

    传输协议:HTTP

    传输方式:同步传输


1.系统集成


2.跨语言调用

thrift


    与thrift RPC实现集成,并在基础上修改了报文头   


长连接、NIO异步传输   

2.Dubbo协议解析

针对Dubbo协议的学习,笔者觉得需要分成几个方向:

传输:数据传输的方式有mina、netty、grizzly这么几种,默认使用netty4的方式来传输。这个之前我们有介绍过

序列化:对象在网络间以二进制的方式来传输,所以,在真正传输之前,需要将对象序列化为字节数组。这个在下一篇博客中会有说明

协议:这就是本文的重点。协议表明了消费者发送请求的方式,服务提供者接收请求的方式。

在下面的分析中,我们主要分析下消费者依据Dubbo协议发送请求的过程和提供者依据Dubbo协议接收请求的过程。

传输方式默认使用Netty4,序列化方式默认使用Hessian2(这个不是本文重点)。

消费者发送请求默认使用NettyClient

提供者接收请求默认使用NettyServer

后续的分析主要围绕这两个类来完成。

3.消费者依据Dubbo协议发送请求全过程

3.1 NettyClient的相关创建

public class NettyClient extends AbstractClient {
 
    protected void doOpen() throws Throwable {
        final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
        bootstrap = new Bootstrap();
        bootstrap.group(NIO_EVENT_LOOP_GROUP)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
                .channel(socketChannelClass());

        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.max(3000, getConnectTimeout()));
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());

                if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
                    ch.pipeline().addLast("negotiation", SslHandlerInitializer.sslClientHandler(getUrl(), nettyClientHandler));
                }

                NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
                ch.pipeline()
                        // 添加decode和encode的相关Handler
                        .addLast("decoder", adapter.getDecoder())
                        .addLast("encoder", adapter.getEncoder())
                        .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
                        .addLast("handler", nettyClientHandler);

                String socksProxyHost = ConfigUtils.getProperty(SOCKS_PROXY_HOST);
                if(socksProxyHost != null) {
                    int socksProxyPort = Integer.parseInt(ConfigUtils.getProperty(SOCKS_PROXY_PORT, DEFAULT_SOCKS_PROXY_PORT));
                    Socks5ProxyHandler socks5ProxyHandler = new Socks5ProxyHandler(new InetSocketAddress(socksProxyHost, socksProxyPort));
                    ch.pipeline().addFirst(socks5ProxyHandler);
                }
            }
        });
    }

}

有关Netty的相关知识非本文重点(后续笔者会有Netty相关的专题博客),我们只需要知道,当消费者发送请求时必须会经过Encoder 相关的Handler即可。

这个EncoderHandler就是我们分析的重点。

3.2 NettyCodecAdapter下的encoder

final public class NettyCodecAdapter {

    // Encoder默认为InternalEncoder对象
    private final ChannelHandler encoder = new InternalEncoder();

    private final ChannelHandler decoder = new InternalDecoder();

    private final Codec2 codec;

    private final URL url;

    private final org.apache.dubbo.remoting.ChannelHandler handler;

    public NettyCodecAdapter(Codec2 codec, URL url, org.apache.dubbo.remoting.ChannelHandler handler) {
        this.codec = codec;
        this.url = url;
        this.handler = handler;
    }

    public ChannelHandler getEncoder() {
        return encoder;
    }

    public ChannelHandler getDecoder() {
        return decoder;
    }

    // Encoder对象
    private class InternalEncoder extends MessageToByteEncoder {

        @Override
        protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
            org.apache.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
            Channel ch = ctx.channel();
            NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
            // 最终交由codec来完成encode操作
            codec.encode(channel, buffer, msg);
        }
    }
    ...
}

通过分析可知:最终的encode工作还是交由codec对象来完成了,这个对象是哪里来的呢?

回过头,到3.1中看下NettyCodecAdapter的创建,就知道codec是传递过来的。创建过程如下:

NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);

public abstract class AbstractEndpoint extends AbstractPeer implements Resetable {
    private static final Logger logger = LoggerFactory.getLogger(AbstractEndpoint.class);

    private Codec2 codec;

    private int timeout;

    private int connectTimeout;

    public AbstractEndpoint(URL url, ChannelHandler handler) {
        super(url, handler);
        // 在这里
        this.codec = getChannelCodec(url);
        this.timeout = url.getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
        this.connectTimeout = url.getPositiveParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT);
    }
    
    protected static Codec2 getChannelCodec(URL url) {
        String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
        // 所以,最终还是通过SPI的方式加载到Codec2的实现类
        if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
            return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
        } else {
            return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
                    .getExtension(codecName));
        }
    }
}

此时通过SPI的方式来加载Codec2的实现类,默认使用DubboCodec来完成

3.3 DubboCodec

经历了上面的一系列分析,最终encode操作交由了DubboCodec.encode()方法来操作

3.3.1 DubboCodec.encode() 请求编码

// DubboCodec继承了ExchangeCodec,encode()方法在ExchangeCodec中执行
public class ExchangeCodec extends TelnetCodec {

    // header length.
    protected static final int HEADER_LENGTH = 16;
    // magic header.
    protected static final short MAGIC = (short) 0xdabb;
    protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
    protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
    // message flag.
    protected static final byte FLAG_REQUEST = (byte) 0x80;
    protected static final byte FLAG_TWOWAY = (byte) 0x40;
    protected static final byte FLAG_EVENT = (byte) 0x20;
    protected static final int SERIALIZATION_MASK = 0x1f;
    private static final Logger logger = LoggerFactory.getLogger(ExchangeCodec.class);

    public Short getMagicCode() {
        return MAGIC;
    }

    @Override
    public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
        if (msg instanceof Request) {
            // 请求信息包装
            encodeRequest(channel, buffer, (Request) msg);
        } else if (msg instanceof Response) {
            encodeResponse(channel, buffer, (Response) msg);
        } else {
            super.encode(channel, buffer, msg);
        }
    }
    
    protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
        // 获取序列化方式,下一篇文章来分析
        Serialization serialization = getSerialization(channel);
        // header头默认16字节
        byte[] header = new byte[HEADER_LENGTH];
        // 设置魔数
        Bytes.short2bytes(MAGIC, header);

        // 设置FLAG_REQUEST和序列化类型信息
        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());

        if (req.isTwoWay()) {
            header[2] |= FLAG_TWOWAY;
        }
        if (req.isEvent()) {
            header[2] |= FLAG_EVENT;
        }

        // 设置requestId
        Bytes.long2bytes(req.getId(), header, 4);

        // 序列化请求体
        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(), req.getVersion());
        }
        out.flushBuffer();
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }
        bos.flush();
        bos.close();
        int len = bos.writtenBytes();
        checkPayload(channel, len);
        // 设置请求体的length到header中
        Bytes.int2bytes(len, header, 12);

        // write
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    }
}

我们可以总结Dubbo协议的请求体如下图:

dubbo自定义filter atachments为空 dubbo自定义协议_HTTP_03

 

3.3.2 DubboCodec.decode() 请求解码

对该请求进行解码是NettyServer的事情,而参考NettyServer的代码,最终还是交由DubboCodec.decode()方法来完成解码

public class ExchangeCodec extends TelnetCodec {
    @Override
    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
        int readable = buffer.readableBytes();
        byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
        buffer.readBytes(header);
        return decode(channel, buffer, readable, header);
    }
    
    // 按照编码的方式进行解码即可
    protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
        // 检查首两字节的魔数信息
        if (readable > 0 && header[0] != MAGIC_HIGH
                || readable > 1 && header[1] != MAGIC_LOW) {
            int length = header.length;
            if (header.length < readable) {
                header = Bytes.copyOf(header, readable);
                buffer.readBytes(header, length, readable - length);
            }
            for (int i = 1; i < header.length - 1; i++) {
                if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                    buffer.readerIndex(buffer.readerIndex() - header.length + i);
                    header = Bytes.copyOf(header, i);
                    break;
                }
            }
            return super.decode(channel, buffer, readable, header);
        }
        // 如果发生拆包,则等待后续的字节返回
        if (readable < HEADER_LENGTH) {
            return DecodeResult.NEED_MORE_INPUT;
        }

        // 获取请求体信息,12-15字节,共4字节,也就是一个int类型的字节长度信息
        int len = Bytes.bytes2int(header, 12);
        checkPayload(channel, len);

        int tt = len + HEADER_LENGTH;
        if (readable < tt) {
            return DecodeResult.NEED_MORE_INPUT;
        }

        // limit input stream.
        ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);

        try {
            return decodeBody(channel, is, header);
        } finally {
            if (is.available() > 0) {
                try {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Skip input stream " + is.available());
                    }
                    StreamUtils.skipUnusedStream(is);
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
        }
    }
    
    // 对请求体信息进行解码
    protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
        byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
        // 获取request_id
        long id = Bytes.bytes2long(header, 4);
        // 如果是response信息,则执行如下
        if ((flag & FLAG_REQUEST) == 0) {
            // decode response.
            Response res = new Response(id);
            if ((flag & FLAG_EVENT) != 0) {
                res.setEvent(true);
            }
            // get status.
            byte status = header[3];
            res.setStatus(status);
            try {
                ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
                if (status == Response.OK) {
                    Object data;
                    if (res.isHeartbeat()) {
                        data = decodeHeartbeatData(channel, in);
                    } else if (res.isEvent()) {
                        data = decodeEventData(channel, in);
                    } else {
                        data = decodeResponseData(channel, in, getRequestData(id));
                    }
                    res.setResult(data);
                } else {
                    res.setErrorMessage(in.readUTF());
                }
            } catch (Throwable t) {
                res.setStatus(Response.CLIENT_ERROR);
                res.setErrorMessage(StringUtils.toString(t));
            }
            return res;
        } else {
            // 解码请求信息
            Request req = new Request(id);
            req.setVersion(Version.getProtocolVersion());
            req.setTwoWay((flag & FLAG_TWOWAY) != 0);
            if ((flag & FLAG_EVENT) != 0) {
                req.setEvent(true);
            }
            try {
                // 根据序列化的方式进行反序列化即可
                ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
                Object data;
                if (req.isHeartbeat()) {
                    data = decodeHeartbeatData(channel, in);
                } else if (req.isEvent()) {
                    data = decodeEventData(channel, in);
                } else {
                    data = decodeRequestData(channel, in);
                }
                req.setData(data);
            } catch (Throwable t) {
                // bad request
                req.setBroken(true);
                req.setData(t);
            }
            return req;
        }
    }
}

总结:

Dubbo协议主要是请求头的设置,设置序列化的方式,请求体的长度后,可以在对应字节后的信息都作为请求体,按照设置的Serialization进行反序列化出完整的Request对象即可。

源码看多了,就对协议本身没那么感冒了。就是自定义设置请求头和请求体信息即可,根据请求头获取请求体的基本信息,按照指定的方式将请求体(字节数组)反序列化为具体的对象。