阐述Dubbo的编码原理

本文详细解析了Netty在处理网络传输时如何通过DubboCodec进行请求和响应的编码,包括设置Dubbo协议头、序列化过程,以及服务消费方和服务提供方在请求和响应编码中的角色和操作。

1 概述

当网络传输使用 Netty 时,实际上是把请求转换成任务并投递到异步队列中,Netty会使用I/O线程去异步执行该任务。上述所说的异步队列指 NettyClient 对应的 Channel 管理的异步队列。

在 Netty 执行异步任务时,会通过 DubboCodec 对请求信息进行编码,此处即服务消费方对请求信息进行编码的地方。同理,服务提供方也通过 DubboCodec 对响应信息进行编码。

由上可知,Dubbo的编码主要指服务消费方对请求信息进行编码服务提供方对响应信息进行编码而Dubbo 的编码过程实际上包含了两部分内容:设置 Dubbo 协议头(header) 的内容,以及对协议 body 数据进行编码。

2 编码原理解析

编码的入口为 DubboCodec 的父类 ExchangeCodec 的 encode 方法,代码如下所示。

@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);
    }
}

2.1 对请求信息进行编码

上述代码中的 encodeRequest 即服务消费方对请求信息进行编码,具体实现如下所示。

protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
    Serialization serialization = getSerialization(channel, req);
    // header. -16个字节
    byte[] header = new byte[HEADER_LENGTH];
    // 1、set magic number.
    Bytes.short2bytes(MAGIC, header);

    // 2、set request and serialization flag.
    header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());

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

    // 3 请求数据不用设置响应结果码

    // 4、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);

    if (req.isHeartbeat()) {
        // heartbeat request data is always null
        bos.write(CodecSupport.getNullBytesOf(serialization));
    } else {
        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();
    // 检查协议body(即协议数据)大小是否超过设置的限定值(payload-默认8M)
    checkPayload(channel, req.getPayload(), len);
    // 5、在header中设置协议body的大小(最后四个字节)
    Bytes.int2bytes(len, header, 12);

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

通过上述方法设置了 Dubbo 协议头(header)的内容,以及对请求数据进行了编码

通过获取到的数据序列化方式对请求数据进行编码,其中默认的序列化方式为 hessian2(即 Hessian2Serialization),最终的获取方法如下所示。

public class DefaultSerializationSelector {

    private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY = "DUBBO_DEFAULT_SERIALIZATION";

    private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY = "hessian2";

    private final static String DEFAULT_REMOTING_SERIALIZATION;

    static {
        String fromProperty = System.getProperty(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
        if (fromProperty != null) {
            DEFAULT_REMOTING_SERIALIZATION = fromProperty;
        } else {
            String fromEnv = System.getenv(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
            if (fromEnv != null) {
                DEFAULT_REMOTING_SERIALIZATION = fromEnv;
            } else {
                DEFAULT_REMOTING_SERIALIZATION = DEFAULT_REMOTING_SERIALIZATION_PROPERTY;
            }
        }
    }

    public static String getDefaultRemotingSerialization() {
        return DEFAULT_REMOTING_SERIALIZATION;
    }
}

对请求数据进行编码的实现是通过 DubboCodec 的 encodeRequestData 方法 ,具体如下所示。

protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
    RpcInvocation inv = (RpcInvocation) data;

    out.writeUTF(version);
    // https://github.com/apache/dubbo/issues/6138
    String serviceName = inv.getAttachment(INTERFACE_KEY);
    if (serviceName == null) {
        serviceName = inv.getAttachment(PATH_KEY);
    }
    out.writeUTF(serviceName);
    out.writeUTF(inv.getAttachment(VERSION_KEY));

    out.writeUTF(inv.getMethodName());
    out.writeUTF(inv.getParameterTypesDesc());
    Object[] args = inv.getArguments();
    if (args != null) {
        for (int i = 0; i < args.length; i++) {
            out.writeObject(callbackServiceCodec.encodeInvocationArgument(channel, inv, i));
        }
    }
    out.writeAttachments(inv.getObjectAttachments());
}

2.2 对响应信息进行编码

上述代码中的 encodeResponse 即服务提供方对响应信息进行编码,具体实现如下所示。

protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
    int savedWriteIndex = buffer.writerIndex();
    try {
        Serialization serialization = getSerialization(channel, res);
        // header.
        byte[] header = new byte[HEADER_LENGTH];
        // 1、set magic number.
        Bytes.short2bytes(MAGIC, header);
        // 2、set request and serialization flag.
        header[2] = serialization.getContentTypeId();
        if (res.isHeartbeat()) {
            header[2] |= FLAG_EVENT;
        }
        // 3、set response status.
        byte status = res.getStatus();
        header[3] = status;
        // 4、set request id.
        Bytes.long2bytes(res.getId(), header, 4);

        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);

        // encode response data or error message.
        if (status == Response.OK) {
            if (res.isHeartbeat()) {
                // heartbeat response data is always null
                bos.write(CodecSupport.getNullBytesOf(serialization));
            } else {
                ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
                if (res.isEvent()) {
                    encodeEventData(channel, out, res.getResult());
                } else {
                    encodeResponseData(channel, out, res.getResult(), res.getVersion());
                }
                out.flushBuffer();
                if (out instanceof Cleanable) {
                    ((Cleanable) out).cleanup();
                }
            }
        } else {
            ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
            out.writeUTF(res.getErrorMessage());
            out.flushBuffer();
            if (out instanceof Cleanable) {
                ((Cleanable) out).cleanup();
            }
        }

        bos.flush();
        bos.close();

        int len = bos.writtenBytes();
        // 检查协议body(即协议数据)大小是否超过设置的限定值(payload-默认8M)
        checkPayload(channel, len);
        // 5、设置协议body-响应数据的大小
        Bytes.int2bytes(len, header, 12);
        // write
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    } catch (Throwable t) {
        ...
    }
}

通过上述方法设置了 Dubbo 协议头(header)的内容,以及对响应数据进行了编码

对响应数据进行编码的实现是通过 DubboCodec 的 encodeResponseData 方法 ,具体如下所示。

protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
    Result result = (Result) data;
    // currently, the version value in Response records the version of Request
    boolean attach = Version.isSupportResponseAttachment(version);
    Throwable th = result.getException();
    if (th == null) {
        Object ret = result.getValue();
        if (ret == null) {
            out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
        } else {
            out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
            out.writeObject(ret);
        }
    } else {
        out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
        out.writeThrowable(th);
    }

    if (attach) {
        // returns current version of Response to consumer side.
        result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());
        out.writeAttachments(result.getObjectAttachments());
    }
}

3 总结

由上可知,不管是服务消费方对请求信息进行编码,还是服务提供方对响应信息进行编码,Dubbo的编码过程本质上就是将要发送的数据封装为 Dubbo 协议帧,并对数据进行序列化处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ability Liao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值