星辰计划07-深入解析源码(逐行debug)dubbo服务调用数据传输层

133 阅读9分钟

前言

前面我们分析的dubbo的服务引用,和调用,但是没有更深入底层数据传输和编码,没有深入到网络传输当中去,所以这一篇我们主要分析服务调用的数据传输层。

源码分析

分析DubboInvoker#invoke

步骤分析
  1. 轮询选择一个ExchangeClient
  2. 通过ExchangeClient 发送请求 返回一个异步结果,如果是单向的返回一个默认的已经成功了的结果
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    inv.setAttachment(PATH_KEY, getUrl().getPath());
    inv.setAttachment(VERSION_KEY, version);

    ExchangeClient currentClient;
    if (clients.length == 1) {
        currentClient = clients[0];
    } else {
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        int timeout = calculateTimeout(invocation, methodName);
        invocation.put(TIMEOUT_KEY, timeout);
        //是否是单向=不带返回值
        if (isOneway) {
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            currentClient.send(inv, isSent);
            return AsyncRpcResult.newDefaultAsyncResult(invocation);
        } else {
            ExecutorService executor = getCallbackExecutor(getUrl(), inv);
            CompletableFuture<AppResponse> appResponseFuture =
            currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
            // save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
            FutureContext.getContext().setCompatibleFuture(appResponseFuture);
            AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
            result.setExecutor(executor);
            return result;
        }
    } catch (TimeoutException e) {
        throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    } catch (RemotingException e) {
        throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

所以 可以看到 **ExchangeClient 是一个非常重要的媒介,它真实将 rpc请求发送出去,它负责着具体的请求编解码和对象序列化。**来我们来分析一下这个 ExchangeClient 的类图,可以看到他继承了远程客户端(Client)和交换Channel(ExchangeChannel)

ExchangeChannel是做什么的?它是**将rpc请求转化为Request对象,并且提供异步返回的包装实现,**他主要干了这两件事。它的底层最终还是调用channel来将请求发送出去

ExchangeClient实现分析

来我们继续分析,既然我们看到了他的类图,那我们现在的DubboInvoker到底使用的ExchangeClient是哪个呢?他是怎么实现的ExchangeChannel呢?我们可以看到他有三个实现。我们可以在这里debug来看他是哪个实现。

在这里debug

最终定位到 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getClients 协议这里

Exchangers.connect(url, requestHandler) 我们可以着重看到这个

private ExchangeClient initClient(URL url) {
        ...
        ExchangeClient client;
        try {
            // connection should be lazy
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
                client = new LazyConnectExchangeClient(url, requestHandler);
            } else {
                client = Exchangers.connect(url, requestHandler);
            }
        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
        }
        return client;
    }

可以看到又通过dubbo spi 加载** Exchanger** 实现 来 获取最终的**ExchangeClient,**可以看到默认的实现 DEFAULT_EXCHANGER="header",org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    //        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    return getExchanger(url).connect(url, handler);
}

public static Exchanger getExchanger(URL url) {
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}

public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}

最终我们可以看到 ExchangeClient的实现是这个 HeaderExchangeClient,现在我们以他为入口来分析,他是如何发送Request,和如何接收的呢?

HeaderExchangeClient#request分析

可以看到非常简单,最终是通过 **HeaderExchangeChannel发送出去的.**

可以看到HeaderExchangeChannel是通过Client将数据发送出去的,它是将rpc请求包装成Request,然后发送出去。

@Override
    public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null,
                    "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // create request.
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
        try {
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }

初步总结

这个时候我们暂停一下 我们来初步的总结一下画个图理一下

大概得主要类如上所示,但是我觉得还是不够细,还是可以结合我们分析的源码再画一个,上面的还是比较粗浅。

打完手工,可以看到完整的链路如上。但是上面的分析还是不够底层,还没到编解码和具体的网络传输。接下来我们继续分析 Channel.send 具体的网络传输

继续分析 Channel.send

这个Channel怎么构建的?我们debug可以看到这里 org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#connect,构建 **HeaderExchangeClient**的时候 **Transporters.connect **可以看到来构建传输数据的Client(它本身就实现了channel,所以是通过他来发送数据的)客户端

Transporter的默认实现是 org.apache.dubbo.remoting.transport.netty.NettyTransporter,通过他来构造org.apache.dubbo.remoting.transport.netty4.NettyClient

public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    ChannelHandler handler;
    if (handlers == null || handlers.length == 0) {
        handler = new ChannelHandlerAdapter();
    } else if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        handler = new ChannelHandlerDispatcher(handlers);
    }
    return getTransporter().connect(url, handler);
}

public static Transporter getTransporter() {
    return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}

学过netty的都知道 有两个点非常重要

1.编解码

2.编解码后的对象,怎么处理

让我们来分析分析 NettyClient的上面两点

可以看到我们能够找到编解码处理类** NettyCodecAdapter和 编解码后的对象处理类NettyClientHandler**

来我们一个一个分析

编解码分析

我们可以看到这个编解码适配器 中的 getCodec(),他最终是通过SPI来加载 指定协议的 编解码实现
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);

可以看到这个获取协议,通过协议类型找到指定的编解码器,这里我们可以看下dubbo协议的编解码器 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec

protected static Codec2 getChannelCodec(URL url) {
String codecName = url.getProtocol(); // codec extension name must stay the same with protocol name
if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
    return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
} else {
    return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
                            .getExtension(codecName));
}
}

看看org.apache.dubbo.rpc.protocol.dubbo.DubboCodec的类图,可以看到它继承了ExchangeCodec,可以看到这个命名可以看到 ExchangeCodec 肯定是对Request,Response进行编解码,但是他不真正的去做编解码,而是通过DubboCodec去做真实的dubbo协议的编码与解码

果然可以看到这个验证了我们的想法,但是ExchangeCodec它还是做了dubbo协议的基础编码,在下面我们将看到

我们继续跟踪 org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest 编码请求的方法,发现这个就是完整的dubbo协议

protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
        //获取指定的序列化协议
        Serialization serialization = getSerialization(channel, req);
        // 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());

        if (req.isTwoWay()) {
            header[2] |= FLAG_TWOWAY;
        }
        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);
        //1.将rpc请求 通过序列化协议将请求的对象进行序列化成二进制 
        //2.添加dubbo协议的具体详情信息 例如哪个接口 哪个方法 版本号
        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();
        checkPayload(channel, len);
        Bytes.int2bytes(len, header, 12);

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

所以真实的dubbo请求协议内容如下

有没有发现请求头当中第3个字节浪费了,没有使用,

当为Response时,这个字节是用来存储 响应状态的

画板

来我们继续看 Response是如何编码的

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

这里我就不贴出来直接画出这样的编码协议

画板

终于我们探究到了他的底层编码信息,这个时候来梳理一下核心代码

编解码核心代码梳理

简单层级核心代码

更加细致的dubbo协议代码梳理

client 发送请求和响应分析

NettyClientHandler分析

可以看到这个客户端是 包装了ChannelHandler最终处理的还是这个包装了的ChannelHandler

final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);

通过 NettyClient也是 ChannelHandler的实现类,并且他自己内部有个ChannelHandler,最终调用的是这个传过来了的 ChannelHandler

public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
    	// you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
    	// the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
    	super(url, wrapChannelHandler(url, handler));
    }

但是到底发送请求是哪个呢?处理Server响应是哪个呢?

我们继续debug 可以看到我们发送请求 使用的NettyClient的父类的AbstractClient#send方法 构建的是NettyChannel,通过他包装的Netty的Channel将数据发送出去。

那NettyClient的构造函数 ChannelHandler 这个有什么用呢?我们继续debug

我们可以知道这个外面包装是 DecodeHandler,可以看到最终处理的还是这个HeaderExchangeHandler handler

return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);

我们来详细分析一下 HeaderExchangeHandler的源码

这个就是接收服务端的响应,根据请求id,将结果塞入到 DefaultFuture当中

@Override
    public void received(Channel channel, Object message) throws RemotingException {
        final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
        if (message instanceof Request) {
            // handle request.
            Request request = (Request) message;
            if (request.isEvent()) {
                handlerEvent(channel, request);
            } else {
                if (request.isTwoWay()) {
                    handleRequest(exchangeChannel, request);
                } else {
                    handler.received(exchangeChannel, request.getData());
                }
            }
        } else if (message instanceof Response) {
            //将结果塞入到 DefaultFuture当中,根据请求id
            handleResponse(channel, (Response) message);
        } else if (message instanceof String) {
            if (isClientSide(channel)) {
                Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
                logger.error(e.getMessage(), e);
            } else {
                String echo = handler.telnet(channel, (String) message);
                if (echo != null && echo.length() > 0) {
                    channel.send(echo);
                }
            }
        } else {
            handler.received(exchangeChannel, message);
        }
    }
static void handleResponse(Channel channel, Response response) throws RemotingException {
        if (response != null && !response.isHeartbeat()) {
            DefaultFuture.received(channel, response);
        }
    }


public static void received(Channel channel, Response response) {
        received(channel, response, false);
    }

    public static void received(Channel channel, Response response, boolean timeout) {
        try {
            //拿到这个请求的 DefaultFuture
            DefaultFuture future = FUTURES.remove(response.getId());
            if (future != null) {
                Timeout t = future.timeoutCheckTask;
                if (!timeout) {
                    // decrease Time
                    t.cancel();
                }
                //将response塞入DefaultFuture当中
                future.doReceived(response);
            } else {
                logger.warn("The timeout response finally returned at "
                        + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
                        + ", response status is " + response.getStatus()
                        + (channel == null ? "" : ", channel: " + channel.getLocalAddress()
                        + " -> " + channel.getRemoteAddress()) + ", please check provider side for detailed result.");
            }
        } finally {
            CHANNELS.remove(response.getId());
        }
    }

还有一个问题 我debug的时候发现 这个handler是包装了多层的 是一个完整的链路

**MultiMessageHandler=>HeartbeatHandler=>AllChannelHandler=>DecodeHandler=HeaderExchangeHandler ,**这个是在哪里包装的呢?

我们来看到这个NettyClient 原来是在这里又包装了一下,我来猜一下他这个是干嘛了,肯定是为了异步处理,当接收到多个服务端的响应时,应该采用多线程的方式来高效的处理响应

public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
    // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
    // the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
    super(url, wrapChannelHandler(url, handler));
}

Dispatcher 果然所有的响应都是通过线程池调度来处理,可以看到默认的AllDispatcher(当然还有其他策略 可以自行摸索)是这个,所以所有的响应都先放到队列里,多线程并发的来处理这个响应。最终调到这个 HeaderExchangeHandler

public static ChannelHandler wrap(ChannelHandler handler, URL url) {
        return ChannelHandlers.getInstance().wrapInternal(handler, url);
    }

    protected static ChannelHandlers getInstance() {
        return INSTANCE;
    }

    static void setTestingChannelHandlers(ChannelHandlers instance) {
        INSTANCE = instance;
    }

    protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
                .getAdaptiveExtension().dispatch(handler, url)));
    }

client 发送请求和响应分析核心代码梳理

发送请求核心代码流程

响应服务端返回Response

可以看到管道链路非常解耦 ,dubbo有很多地方都是用了这个 Invoker也使用了,这个也是我们可以学习的地方。

总结

现在我们来总结一下如何构建client和底层的编解码,Client发送请求和接收响应

如何构建Client

更详细的完整的client的角色

编解码整合流程分析

Client发送请求核心代码流程

Client 响应服务端返回Response