Dubbo协议

539 阅读4分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

在服务消费者与服务提供者进行信息交换的过程中,需要使用到一些通信协议,以便于通信双方能够识别对方的信息。Dubbo框架支持多种协议,包括dubbo、rmi、hessian、http等,每种协议都有着各自适用的场景。

dubbo协议是Dubbo框架缺省和推荐的协议,采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

本文主要介绍dubbo协议报文格式和编解码细节。

报文格式

在网络七层模型中,TCP协议是传输层协议。dubbo协议是基于TCP协议建立的一种应用层协议,它参考了TCP协议,协议内容由header和body两部分组成。

其中,协议header格式如下:

Bit类型描述
0 - 15magic魔数,用于标识这是一个dubbo协议报文,固定为0xdabb。报文解码时会对魔数进行正确性校验。
16 - 23request flag | serializationId请求类型和序列化类型id的组合结果,高4位表示请求类型、低4位表示序列化方式。
24 - 31response status响应状态结果码,只有响应报文中才会设置该内容。
32 - 95request id请求id,作为一次请求的唯一标识,由于tcp传输内容有限,一次请求内容可能会分多次传输。
96 - 127body length请求body部分的内容长度,可以用来判断传输的内容是否完善。

编解码细节

在服务消费者与服务提供者进行信息交换的过程中,Dubbo需要对传输的数据进行编码和解码。Codec2接口声明了encode、decode两个方法,对应编码和解码规范;AbstractCodec中定义了一些公共方法,如获取序列化方式的方法;TransportCodec对应传输层的编解码;ExchangeCodec对应交换层的编解码;DubboCodec为应用层的编解码。

dubbo-编解码.png

编码阶段

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encode

    @Override
    public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
        // 1.对请求信息编码
        if (msg instanceof Request) {
            encodeRequest(channel, buffer, (Request) msg);
        // 2.对响应信息编码
        } else if (msg instanceof Response) {
            encodeResponse(channel, buffer, (Response) msg);
        // 3.对其它信息编码
        } else {
            super.encode(channel, buffer, msg);
        }
    }

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest

protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
        // 1.获取序列化扩展实现
        Serialization serialization = getSerialization(channel, req);
        // 2.构建dubbo协议头数组,长度16字节
        byte[] header = new byte[HEADER_LENGTH];
        // 3.设置魔数
        Bytes.short2bytes(MAGIC, header);
​
        // 4.设置请求类型和序列化标识
        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
        if (req.isTwoWay()) {
            header[2] |= FLAG_TWOWAY;
        }
        if (req.isEvent()) {
            header[2] |= FLAG_EVENT;
        }
​
        // 5.设置请求id
        Bytes.long2bytes(req.getId(), header, 4);
​
        // encode request data.
        int savedWriteIndex = buffer.writerIndex();
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
​
        if (req.isHeartbeat()) {
            // heartbeat request data is always null
            bos.write(CodecSupport.getNullBytesOf(serialization));
        } else {
            // 6.将请求数据序列化,写入缓存
            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();
    
        // 7.检查payload部分数据是否合法
        int len = bos.writtenBytes();
        checkPayload(channel, len);
        Bytes.int2bytes(len, header, 12);
​
        // 8.将协议头写入缓存
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    }

对响应信息的编码和encodeRequest类似,区别主要是响应信息的编码,还需要设置响应状态结果码信息。

解码阶段

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decode(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.buffer.ChannelBuffer, int, byte[])

protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
        // 1.检查魔数信息,判断是不是dubbo协议
        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);
        }
    
        // 2.判断是否读取了一个完整的协议头 
        if (readable < HEADER_LENGTH) {
            return DecodeResult.NEED_MORE_INPUT;
        }
​
        // 3.获取协议体数据长度
        int len = Bytes.bytes2int(header, 12);
​
        Object obj = finishRespWhenOverPayload(channel, len, header);
        if (null != obj) {
            return obj;
        }
        checkPayload(channel, len);
​
        // 4.判断是否遇到了半包问题
        int tt = len + HEADER_LENGTH;
        if (readable < tt) {
            return DecodeResult.NEED_MORE_INPUT;
        }
​
        // limit input stream.
        ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
​
        try {
            // 5.解析协议体数据部分
            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);
                }
            }
        }
    }

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decodeBody

protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
        byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
        // 1.获取请求id
        long id = Bytes.bytes2long(header, 4);
        if ((flag & FLAG_REQUEST) == 0) {
            // 2.对响应信息进行解码
            Response res = new Response(id);
            if ((flag & FLAG_EVENT) != 0) {
                res.setEvent(true);
            }
            // 3.获取响应结果码
            byte status = header[3];
            res.setStatus(status);
            try {
                if (status == Response.OK) {
                    Object data;
                    if (res.isEvent()) {
                        byte[] eventPayload = CodecSupport.getPayload(is);
                        if (CodecSupport.isHeartBeat(eventPayload, proto)) {
                            // heart beat response data is always null;
                            data = null;
                        } else {
                            data = decodeEventData(channel, CodecSupport.deserialize(channel.getUrl(), new ByteArrayInputStream(eventPayload), proto), eventPayload);
                        }
                    } else {
                        // 4.对响应协议体数据进行解码
                        data = decodeResponseData(channel, CodecSupport.deserialize(channel.getUrl(), is, proto), getRequestData(id));
                    }
                    res.setResult(data);
                } else {
                    res.setErrorMessage(CodecSupport.deserialize(channel.getUrl(), is, proto).readUTF());
                }
            } catch (Throwable t) {
                res.setStatus(Response.CLIENT_ERROR);
                res.setErrorMessage(StringUtils.toString(t));
            }
            return res;
        } else {
            // 5.对请求信息进行解码
            Request req = new Request(id);
            req.setVersion(Version.getProtocolVersion());
            req.setTwoWay((flag & FLAG_TWOWAY) != 0);
            if ((flag & FLAG_EVENT) != 0) {
                req.setEvent(true);
            }
            try {
                Object data;
                if (req.isEvent()) {
                    byte[] eventPayload = CodecSupport.getPayload(is);
                    if (CodecSupport.isHeartBeat(eventPayload, proto)) {
                        // heart beat response data is always null;
                        data = null;
                    } else {
                        data = decodeEventData(channel, CodecSupport.deserialize(channel.getUrl(), new ByteArrayInputStream(eventPayload), proto), eventPayload);
                    }
                } else {
                    // 6.对请求协议体数据进行解码
                    data = decodeRequestData(channel, CodecSupport.deserialize(channel.getUrl(), is, proto));
                }
                req.setData(data);
            } catch (Throwable t) {
                // bad request
                req.setBroken(true);
                req.setData(t);
            }
            return req;
        }
    }

由于TCP传输层,不知道应用层协议业务包的概念,所以会出现半包及粘包问题。dubbo协议通过自定义协议header+body的形式,其中协议header的body length信息判断一次请求是否能够读取到一个完整的业务包,以此来解决半包及粘包问题。