Dubbo中編碼和解碼的解析

weixin_34116110發表於2017-04-24

(這裡做的解析不是很詳細,等到走完整個流程再來解析)Dubbo中編解碼的工作由Codec2介面的實現來處理,回想一下第一次接觸到Codec2相關的內容是在服務端暴露服務的時候,根據具體的協議去暴露服務的步驟中,在DubboProtocol的createServer方法中:

private ExchangeServer createServer(URL url) {
    。。。
    //這裡url會新增codec=dubbo
    url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
    ExchangeServer server;
    try {
        server = Exchangers.bind(url, requestHandler);
    }
    。。。
    return server;
}

緊接著進入Exchangers.bind(url, requestHandler);

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    //如果url中沒有codec屬性,就會新增codec=exchange
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).bind(url, handler);
}

然後會繼續進入HeaderExchanger的bind方法:

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

在這裡會建立一個DecodeHandler例項。繼續跟蹤Transporters的bind方法,會發現直接返回一個NettyServer例項,在NettyServer的父類AbstractEndpoint構造方法初始的時候,會根據url獲取一個ChannelCodec,並將其賦值給codec存放到NettyServer的例項中。

我們先看下getChannelCodec(url);方法:

protected static Codec2 getChannelCodec(URL url) {
    //獲取codecName,不存在的話,預設為telnet
    String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
    //先看下是不是Codec2的實現,是的話就根據SPI擴充套件機制獲得Codec2擴充套件的實現
    //我們這裡預設使用的是DubboCountCodec
    if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
        return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
    } else {
        //如果不是Codec2的實現,就去查詢Codec的實現
        //然後使用CodecAdapter介面卡類來轉換成Codec2
        return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
                                           .getExtension(codecName));
    }
}

這裡返回的是Codec2,而Codec這個介面已經被標記為過時。到這裡的話,在NettyServer中就會存在一個Codec2的例項了。

在繼續往下看到NettyServer中的doOpen()方法,這裡是使用Netty的邏輯開啟服務並繫結監聽服務的地方:

protected void doOpen() throws Throwable {
    NettyHelper.setNettyLoggerFactory();
    ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
    ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
    ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
    bootstrap = new ServerBootstrap(channelFactory);

    final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    channels = nettyHandler.getChannels();
    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() {
            //這裡的getCodec方法獲取到的codec就是在AbstractEndpoint中我們獲取到的codec
            //NettyCodecAdapter,介面卡類
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
            ChannelPipeline pipeline = Channels.pipeline();
            pipeline.addLast("decoder", adapter.getDecoder());//SimpleChannelUpstreamHandler
            pipeline.addLast("encoder", adapter.getEncoder());//OneToOneEncoder
            pipeline.addLast("handler", nettyHandler);
            return pipeline;
        }
    });
    // bind
    channel = bootstrap.bind(getBindAddress());
}

這裡就在Netty的pipeline中新增了編解碼器。這裡涉及到Netty的相關流程,可以先了解下Netty3服務端流程簡介

decoder為解碼器,是一個SimpleChannelUpstreamHandler,從Socket到Netty中的時候,需要解碼,也就是服務提供端接收到消費者的請求的時候,需要解碼。

encoder是編碼器,是OneToOneEncoder,這個類實現了ChannelDownstreamHandler,從服務提供端傳送給服務消費者的時候,需要編碼。

nettyHandler實現了ChannelUpstreamHandler, ChannelDownstreamHandler兩個,上下的時候都需要處理。

接收到服務消費者的請求的時候,會先執行decoder,然後執行nettyHandler。

傳送給消費者的時候,會先執行nettyHandler,然後執行encoder。

dubbo協議頭

協議頭是16位元組的定長資料:

  • 2位元組short型別的Magic

  • 1位元組的訊息標誌位

    • 5位序列化id
    • 1位心跳還是正常請求
    • 1位雙向還是單向
    • 1位請求還是響應
  • 1位元組的狀態位

  • 8位元組的訊息id

  • 4位元組資料長度

編碼的過程

首先會判斷是請求還是響應,程式碼在ExchangeCodec的encode方法:

public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
    if (msg instanceof Request) {//Request型別
        encodeRequest(channel, buffer, (Request) msg);
    } else if (msg instanceof Response) {//Response型別
        encodeResponse(channel, buffer, (Response) msg);
    } else {//telenet型別的
        super.encode(channel, buffer, msg);
    }
}

服務提供者對響應資訊編碼

在服務提供者端一般是對響應來做編碼,所以這裡重點看下encodeResponse。

encodeResponse:

protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
    try {
        //序列化方式
        //也是根據SPI擴充套件來獲取,url中沒指定的話預設使用hessian2
        Serialization serialization = getSerialization(channel);
        //長度為16位元組的陣列,協議頭
        byte[] header = new byte[HEADER_LENGTH];
        //魔數0xdabb
        Bytes.short2bytes(MAGIC, header);
        //序列化方式
        header[2] = serialization.getContentTypeId();
        //心跳訊息還是正常訊息
        if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
        //響應狀態
        byte status = res.getStatus();
        header[3] = status;
        //設定請求id
        Bytes.long2bytes(res.getId(), header, 4);
        //buffer為1024位元組的ChannelBuffer
        //獲取buffer的寫入位置
        int savedWriteIndex = buffer.writerIndex();
        //需要再加上協議頭的長度之後,才是正確的寫入位置
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        // 對響應資訊或者錯誤訊息進行編碼
        if (status == Response.OK) {
            if (res.isHeartbeat()) {
                //心跳
                encodeHeartbeatData(channel, out, res.getResult());
            } else {
                //正常響應
                encodeResponseData(channel, out, res.getResult());
            }
        }
        //錯誤訊息
        else out.writeUTF(res.getErrorMessage());
        out.flushBuffer();
        bos.flush();
        bos.close();
        //寫出去的訊息的長度
        int len = bos.writtenBytes();
        //檢視訊息長度是否過長
        checkPayload(channel, len);
        Bytes.int2bytes(len, header, 12);
        //重置寫入的位置
        buffer.writerIndex(savedWriteIndex);
        //向buffer中寫入訊息頭
        buffer.writeBytes(header); // write header.
        //buffer寫出去的位置從writerIndex開始,加上header長度,加上資料長度
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    } catch (Throwable t) {
        // 傳送失敗資訊給Consumer,否則Consumer只能等超時了
        if (! res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {
            try {
                // FIXME 在Codec中列印出錯日誌?在IoHanndler的caught中統一處理?
                logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);

                Response r = new Response(res.getId(), res.getVersion());
                r.setStatus(Response.BAD_RESPONSE);
                r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));
                channel.send(r);

                return;
            } catch (RemotingException e) {
                logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);
            }
        }

        // 重新丟擲收到的異常
        if (t instanceof IOException) {
            throw (IOException) t;
        } else if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else if (t instanceof Error) {
            throw (Error) t;
        } else  {
            throw new RuntimeException(t.getMessage(), t);
        }
    }
}

服務消費者對請求資訊編碼

消費者端暫先不做解析

解碼的過程

服務提供者對請求訊息的解碼

decode方法一次只會解析一個完整的dubbo協議包,但是每次收到的協議包不一定是完整的,或者有可能是多個協議包。看下程式碼解析,首先看NettyCodecAdapter的內部類InternalDecoder的messageReceived方法:

public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
    Object o = event.getMessage();
    if (! (o instanceof ChannelBuffer)) {
        ctx.sendUpstream(event);
        return;
    }

    ChannelBuffer input = (ChannelBuffer) o;
    int readable = input.readableBytes();
    if (readable <= 0) {
        return;
    }

    com.alibaba.dubbo.remoting.buffer.ChannelBuffer message;
    if (buffer.readable()) {
        if (buffer instanceof DynamicChannelBuffer) {
            buffer.writeBytes(input.toByteBuffer());
            message = buffer;
        } else {
            int size = buffer.readableBytes() + input.readableBytes();
            message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer(
                size > bufferSize ? size : bufferSize);
            message.writeBytes(buffer, buffer.readableBytes());
            message.writeBytes(input.toByteBuffer());
        }
    } else {
        message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
            input.toByteBuffer());
    }

    NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
    Object msg;
    //讀索引
    int saveReaderIndex;
    try {
        do {
            saveReaderIndex = message.readerIndex();
            try {
            //解碼
                msg = codec.decode(channel, message);
            } catch (IOException e) {
                buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                throw e;
            }
            //不完整的協議包
            if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
                //重置讀索引
                message.readerIndex(saveReaderIndex);
                //跳出迴圈,之後在finally中把message賦值給buffer儲存起來,等到下次接收到資料包的時候會追加到buffer的後面
                break;
            } else {//有多個協議包,觸發messageReceived事件
                if (saveReaderIndex == message.readerIndex()) {
                    buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
                    throw new IOException("Decode without read data.");
                }
                if (msg != null) {
                    Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress());
                }
            }
        } while (message.readable());
    } finally {
        if (message.readable()) {
            message.discardReadBytes();
            buffer = message;
        } else {
            buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
        }
        NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
    }
}

繼續看codec.decode(channel, message);這裡是DubboCountCodec的decode方法:

public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
    //當前的讀索引記錄下來
    int save = buffer.readerIndex();
    //多訊息
    MultiMessage result = MultiMessage.create();
    do {
        //解碼訊息
        Object obj = codec.decode(channel, buffer);
        //不是完整的協議包
        if (Codec2.DecodeResult.NEED_MORE_INPUT == obj) {
            buffer.readerIndex(save);
            break;
        } else {//多個協議包
            result.addMessage(obj);
            logMessageLength(obj, buffer.readerIndex() - save);
            save = buffer.readerIndex();
        }
    } while (true);
    if (result.isEmpty()) {
        return Codec2.DecodeResult.NEED_MORE_INPUT;
    }
    if (result.size() == 1) {
        return result.get(0);
    }
    return result;
}

繼續看ExchangeCodec的decode方法:

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

解碼decode:

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;
            }
        }
        //telenet
        return super.decode(channel, buffer, readable, header);
    }
    //不完整的包
    if (readable < HEADER_LENGTH) {
        return DecodeResult.NEED_MORE_INPUT;
    }

    //資料長度
    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 {
                StreamUtils.skipUnusedStream(is);
            } catch (IOException e) { }
        }
    }
}

decodeBody解析資料部分:

protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
    byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
    //獲取序列化方式
    Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);
    //反序列化
    ObjectInput in = s.deserialize(channel.getUrl(), is);
    //獲取請求id
    long id = Bytes.bytes2long(header, 4);
    //這裡是解碼響應資料
    if ((flag & FLAG_REQUEST) == 0) {
        //response的id設為來時候的Request的id,這樣才能對上暗號
        Response res = new Response(id);
        //判斷是什麼型別請求
        if ((flag & FLAG_EVENT) != 0) {
            res.setEvent(Response.HEARTBEAT_EVENT);
        }
        //獲取狀態
        byte status = header[3];
        res.setStatus(status);
        if (status == Response.OK) {
            try {
                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);
            } catch (Throwable t) {
                res.setStatus(Response.CLIENT_ERROR);
                res.setErrorMessage(StringUtils.toString(t));
            }
        } else {
            res.setErrorMessage(in.readUTF());
        }
        return res;
    } else {//這是解碼請求資料
        // request的id
        Request req = new Request(id);
        req.setVersion("2.0.0");
        req.setTwoWay((flag & FLAG_TWOWAY) != 0);
        if ((flag & FLAG_EVENT) != 0) {
            req.setEvent(Request.HEARTBEAT_EVENT);
        }
        try {
            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;
    }
}

具體的解碼細節交給底層解碼器,這裡是使用的hessian2。

服務消費者對響應訊息的解碼

暫先不做解釋。

相關文章