简介

本篇文章主要对 Dubbo 中的几种服务调用方式,以及从双向通信的角度对整个通信过程进行了详细的分析。按照通信顺序,通信过程包括服务消费方发送请求,服务提供方接收请求,服务提供方返回响应数据,服务消费方接收响应数据等过程。

源码分析

在进行源码分析之前,我们先来通过一张图了解 Dubbo 服务调用过程。




dubbo的底层调用过程 dubbo的整个调用过程_bat 存储过程返回值


首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。至于响应的发送与接收过程,这张图中没有表现出来。对于这两个过程,我们也会进行详细分析。

服务调用方式

Dubbo 支持同步和异步两种调用方式,其中异步调用还可细分为“有返回值”的异步调用和“无返回值”的异步调用。所谓“无返回值”异步调用是指服务消费方只管调用,但不关心调用结果,此时 Dubbo 会直接返回一个空的 RpcResult。若要使用异步特性,需要服务消费方手动进行配置。默认情况下,Dubbo 使用同步调用方式。

本节以及其他章节将会使用 Dubbo 官方提供的 Demo 分析整个调用过程,下面我们从 DemoService 接口的代理类开始进行分析。Dubbo 默认使用 Javassist 框架为服务接口生成动态代理类,因此我们需要先将代理类进行反编译才能看到源码。这里使用阿里开源 Java 应用诊断工具 Arthas 反编译代理类,结果如下:

public class DubboCodec extends ExchangeCodec implements Codec2 {     @Override    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);        // 获取请求编号        long id = Bytes.bytes2long(header, 4);        // 检测消息类型,若下面的条件成立,表明消息类型为 Response        if ((flag & FLAG_REQUEST) == 0) {            // 创建 Response 对象            Response res = new Response(id);            // 检测事件标志位            if ((flag & FLAG_EVENT) != 0) {                // 设置心跳事件                res.setEvent(Response.HEARTBEAT_EVENT);            }            // 获取响应状态            byte status = header[3];            // 设置响应状态            res.setStatus(status);                        // 如果响应状态为 OK,表明调用过程正常            if (status == Response.OK) {                try {                    Object data;                    if (res.isHeartbeat()) {                        // 反序列化心跳数据,已废弃                        data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));                    } else if (res.isEvent()) {                        // 反序列化事件数据                        data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));                    } else {                        DecodeableRpcResult result;                        // 根据 url 参数决定是否在 IO 线程上执行解码逻辑                        if (channel.getUrl().getParameter(                                Constants.DECODE_IN_IO_THREAD_KEY,                                Constants.DEFAULT_DECODE_IN_IO_THREAD)) {                            // 创建 DecodeableRpcResult 对象                            result = new DecodeableRpcResult(channel, res, is,                                    (Invocation) getRequestData(id), proto);                            // 进行后续的解码工作                            result.decode();                        } else {                            // 创建 DecodeableRpcResult 对象                            result = new DecodeableRpcResult(channel, res,                                    new UnsafeByteArrayInputStream(readMessageData(is)),                                    (Invocation) getRequestData(id), proto);                        }                        data = result;                    }                                        // 设置 DecodeableRpcResult 对象到 Response 对象中                    res.setResult(data);                } catch (Throwable t) {                    // 解码过程中出现了错误,此时设置 CLIENT_ERROR 状态码到 Response 对象中                    res.setStatus(Response.CLIENT_ERROR);                    res.setErrorMessage(StringUtils.toString(t));                }            }             // 响应状态非 OK,表明调用过程出现了异常            else {                // 反序列化异常信息,并设置到 Response 对象中                res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF());            }            return res;        } else {            // 对请求数据进行解码,前面已分析过,此处忽略        }    }}/** * Arthas 反编译步骤: * 1. 启动 Arthas *    java -jar arthas-boot.jar * * 2. 输入编号选择进程 *    Arthas 启动后,会打印 Java 应用进程列表,比如: *    [1]: 11232 org.jetbrains.jps.cmdline.Launcher *    [2]: 22370 org.jetbrains.jps.cmdline.Launcher *    [3]: 22371 com.alibaba.dubbo.demo.consumer.Consumer *    [4]: 22362 com.alibaba.dubbo.demo.provider.Provider *    [5]: 2074 org.apache.zookeeper.server.quorum.QuorumPeerMain * 这里输入编号 3,让 Arthas 关联到启动类为 com.....Consumer 的 Java 进程上 * * 3. 由于 Demo 项目中只有一个服务接口,因此此接口的代理类类名为 proxy0,此时使用 sc 命令搜索这个类名。 *    $ sc *.proxy0 *    com.alibaba.dubbo.common.bytecode.proxy0 * * 4. 使用 jad 命令反编译 com.alibaba.dubbo.common.bytecode.proxy0 *    $ jad com.alibaba.dubbo.common.bytecode.proxy0 * * 更多使用方法请参考 Arthas 官方文档: *   https://alibaba.github.io/arthas/quick-start.html */public class proxy0 implements ClassGenerator.DC, EchoService, DemoService {    // 方法数组    public static Method[] methods;    private InvocationHandler handler;     public proxy0(InvocationHandler invocationHandler) {        this.handler = invocationHandler;    }     public proxy0() {    }     public String sayHello(String string) {        // 将参数存储到 Object 数组中        Object[] arrobject = new Object[]{string};        // 调用 InvocationHandler 实现类的 invoke 方法得到调用结果        Object object = this.handler.invoke(this, methods[0], arrobject);        // 返回调用结果        return (String)object;    }     /** 回声测试方法 */    public Object $echo(Object object) {        Object[] arrobject = new Object[]{object};        Object object2 = this.handler.invoke(this, methods[1], arrobject);        return object2;    }}

如上,代理类的逻辑比较简单。首先将运行时参数存储到数组中,然后调用 InvocationHandler 接口实现类的 invoke 方法,得到调用结果,最后将结果转型并返回给调用方。关于代理类的逻辑就说这么多,继续向下分析。

public class InvokerInvocationHandler implements InvocationHandler {     private final Invoker> invoker;     public InvokerInvocationHandler(Invoker> handler) {        this.invoker = handler;    }     @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        String methodName = method.getName();        Class>[] parameterTypes = method.getParameterTypes();                // 拦截定义在 Object 类中的方法(未被子类重写),比如 wait/notify        if (method.getDeclaringClass() == Object.class) {            return method.invoke(invoker, args);        }                // 如果 toString、hashCode 和 equals 等方法被子类重写了,这里也直接调用        if ("toString".equals(methodName) && parameterTypes.length == 0) {            return invoker.toString();        }        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {            return invoker.hashCode();        }        if ("equals".equals(methodName) && parameterTypes.length == 1) {            return invoker.equals(args[0]);        }                // 将 method 和 args 封装到 RpcInvocation 中,并执行后续的调用        return invoker.invoke(new RpcInvocation(method, args)).recreate();    }}

InvokerInvocationHandler 中的 invoker 成员变量类型为 MockClusterInvoker,MockClusterInvoker 内部封装了服务降级逻辑。下面简单看一下:

public class MockClusterInvoker implements Invoker {        private final Invoker invoker;        public Result invoke(Invocation invocation) throws RpcException {        Result result = null;         // 获取 mock 配置值        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();        if (value.length() == 0 || value.equalsIgnoreCase("false")) {            // 无 mock 逻辑,直接调用其他 Invoker 对象的 invoke 方法,            // 比如 FailoverClusterInvoker            result = this.invoker.invoke(invocation);        } else if (value.startsWith("force")) {            // force:xxx 直接执行 mock 逻辑,不发起远程调用            result = doMockInvoke(invocation, null);        } else {            // fail:xxx 表示消费方对调用服务失败后,再执行 mock 逻辑,不抛出异常            try {                // 调用其他 Invoker 对象的 invoke 方法                result = this.invoker.invoke(invocation);            } catch (RpcException e) {                if (e.isBiz()) {                    throw e;                } else {                    // 调用失败,执行 mock 逻辑                    result = doMockInvoke(invocation, e);                }            }        }        return result;    }        // 省略其他方法}

服务降级不是本文重点,因此这里就不分析 doMockInvoke 方法了。考虑到前文已经详细分析过 FailoverClusterInvoker,因此本节略过 FailoverClusterInvoker,直接分析 DubboInvoker。

public abstract class AbstractInvoker implements Invoker {        public Result invoke(Invocation inv) throws RpcException {        if (destroyed.get()) {            throw new RpcException("Rpc invoker for service ...");        }        RpcInvocation invocation = (RpcInvocation) inv;        // 设置 Invoker        invocation.setInvoker(this);        if (attachment != null && attachment.size() > 0) {            // 设置 attachment            invocation.addAttachmentsIfAbsent(attachment);        }        Map contextAttachments = RpcContext.getContext().getAttachments();        if (contextAttachments != null && contextAttachments.size() != 0) {            // 添加 contextAttachments 到 RpcInvocation#attachment 变量中            invocation.addAttachments(contextAttachments);        }        if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {            // 设置异步信息到 RpcInvocation#attachment 中            invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());        }        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);         try {            // 抽象方法,由子类实现            return doInvoke(invocation);        } catch (InvocationTargetException e) {            // ...        } catch (RpcException e) {            // ...        } catch (Throwable e) {            return new RpcResult(e);        }    }     protected abstract Result doInvoke(Invocation invocation) throws Throwable;        // 省略其他方法}

上面的代码来自 AbstractInvoker 类,其中大部分代码用于添加信息到 RpcInvocation#attachment 变量中,添加完毕后,调用 doInvoke 执行后续的调用。doInvoke 是一个抽象方法,需要由子类实现,下面到 DubboInvoker 中看一下。

public class DubboInvoker extends AbstractInvoker {        private final ExchangeClient[] clients;        protected Result doInvoke(final Invocation invocation) throws Throwable {        RpcInvocation inv = (RpcInvocation) invocation;        final String methodName = RpcUtils.getMethodName(invocation);        // 设置 path 和 version 到 attachment 中        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());        inv.setAttachment(Constants.VERSION_KEY, version);         ExchangeClient currentClient;        if (clients.length == 1) {            // 从 clients 数组中获取 ExchangeClient            currentClient = clients[0];        } else {            currentClient = clients[index.getAndIncrement() % clients.length];        }        try {            // 获取异步配置            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);            // isOneway 为 true,表示“单向”通信            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);             // 异步无返回值            if (isOneway) {                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);                // 发送请求                currentClient.send(inv, isSent);                // 设置上下文中的 future 为 null                RpcContext.getContext().setFuture(null);                // 返回一个空的 RpcResult                return new RpcResult();            }              // 异步有返回值            else if (isAsync) {                // 发送请求,获得 ResponseFuture 实例                ResponseFuture future = currentClient.request(inv, timeout);                // 设置 future 到上下文中                RpcContext.getContext().setFuture(new FutureAdapter(future));                // 暂时返回一个空结果                return new RpcResult();            }              // 同步调用            else {                RpcContext.getContext().setFuture(null);                // 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待                return (Result) currentClient.request(inv, timeout).get();            }        } catch (TimeoutException e) {            throw new RpcException(..., "Invoke remote method timeout....");        } catch (RemotingException e) {            throw new RpcException(..., "Failed to invoke remote method: ...");        }    }        // 省略其他方法}

上面的代码包含了 Dubbo 对同步和异步调用的处理逻辑,搞懂了上面的代码,会对 Dubbo 的同步和异步调用方式有更深入的了解。Dubbo 实现同步和异步调用比较关键的一点就在于由谁调用 ResponseFuture 的 get 方法。同步调用模式下,由框架自身调用 ResponseFuture 的 get 方法。异步调用模式下,则由用户调用该方法。ResponseFuture 是一个接口,下面我们来看一下它的默认实现类 DefaultFuture 的源码。

public class DefaultFuture implements ResponseFuture {        private static final Map CHANNELS =         new ConcurrentHashMap();     private static final Map FUTURES =         new ConcurrentHashMap();        private final long id;    private final Channel channel;    private final Request request;    private final int timeout;    private final Lock lock = new ReentrantLock();    private final Condition done = lock.newCondition();    private volatile Response response;        public DefaultFuture(Channel channel, Request request, int timeout) {        this.channel = channel;        this.request = request;                // 获取请求 id,这个 id 很重要,后面还会见到        this.id = request.getId();        this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);        // 存储  映射关系到 FUTURES 中        FUTURES.put(id, this);        CHANNELS.put(id, channel);    }        @Override    public Object get() throws RemotingException {        return get(timeout);    }     @Override    public Object get(int timeout) throws RemotingException {        if (timeout <= 0) {            timeout = Constants.DEFAULT_TIMEOUT;        }        if (!isDone()) {            long start = System.currentTimeMillis();            lock.lock();            try {                // 循环检测服务提供方是否成功返回了调用结果                while (!isDone()) {                    // 如果调用结果尚未返回,这里等待一段时间                    done.await(timeout, TimeUnit.MILLISECONDS);                    // 如果调用结果成功返回,或等待超时,此时跳出 while 循环,执行后续的逻辑                    if (isDone() || System.currentTimeMillis() - start > timeout) {                        break;                    }                }            } catch (InterruptedException e) {                throw new RuntimeException(e);            } finally {                lock.unlock();            }                        // 如果调用结果仍未返回,则抛出超时异常            if (!isDone()) {                throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));            }        }                // 返回调用结果        return returnFromResponse();    }        @Override    public boolean isDone() {        // 通过检测 response 字段为空与否,判断是否收到了调用结果        return response != null;    }        private Object returnFromResponse() throws RemotingException {        Response res = response;        if (res == null) {            throw new IllegalStateException("response cannot be null");        }                // 如果调用结果的状态为 Response.OK,则表示调用过程正常,服务提供方成功返回了调用结果        if (res.getStatus() == Response.OK) {            return res.getResult();        }                // 抛出异常        if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {            throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());        }        throw new RemotingException(channel, res.getErrorMessage());    }        // 省略其他方法}

如上,当服务消费者还未接收到调用结果时,用户线程调用 get 方法会被阻塞住。同步调用模式下,框架获得 DefaultFuture 对象后,会立即调用 get 方法进行等待。而异步模式下则是将该对象封装到 FutureAdapter 实例中,并将 FutureAdapter 实例设置到 RpcContext 中,供用户使用。FutureAdapter 是一个适配器,用于将 Dubbo 中的 ResponseFuture 与 JDK 中的 Future 进行适配。这样当用户线程调用 Future 的 get 方法时,经过 FutureAdapter 适配,最终会调用 ResponseFuture 实现类对象的 get 方法,也就是 DefaultFuture 的 get 方法。

到这里关于 Dubbo 几种调用方式的代码逻辑就分析完了,下面来分析请求数据的发送与接收,以及响应数据的发送与接收过程。

服务消费方发送请求

发送请求

本节我们来看一下同步调用模式下,服务消费方是如何发送调用请求的。在深入分析源码前,我们先来看一张图。


dubbo的底层调用过程 dubbo的整个调用过程_dubbo的底层调用过程_02


这张图展示了服务消费方发送请求过程的部分调用栈,略为复杂。从上图可以看出,经过多次调用后,才将请求数据送至 Netty NioClientSocketChannel。这样做的原因是通过 Exchange 层为框架引入 Request 和 Response 语义,这一点会在接下来的源码分析过程中会看到。其他的就不多说了,下面开始进行分析。首先分析 ReferenceCountExchangeClient 的源码。

final class ReferenceCountExchangeClient implements ExchangeClient {     private final URL url;    private final AtomicInteger referenceCount = new AtomicInteger(0);     public ReferenceCountExchangeClient(ExchangeClient client, ConcurrentMap ghostClientMap) {        this.client = client;        // 引用计数自增        referenceCount.incrementAndGet();        this.url = client.getUrl();                // ...    }     @Override    public ResponseFuture request(Object request) throws RemotingException {        // 直接调用被装饰对象的同签名方法        return client.request(request);    }     @Override    public ResponseFuture request(Object request, int timeout) throws RemotingException {        // 直接调用被装饰对象的同签名方法        return client.request(request, timeout);    }     /** 引用计数自增,该方法由外部调用 */    public void incrementAndGetCount() {        // referenceCount 自增        referenceCount.incrementAndGet();    }            @Override    public void close(int timeout) {        // referenceCount 自减        if (referenceCount.decrementAndGet() <= 0) {            if (timeout == 0) {                client.close();            } else {                client.close(timeout);            }            client = replaceWithLazyClient();        }    }        // 省略部分方法}

ReferenceCountExchangeClient 内部定义了一个引用计数变量 referenceCount,每当该对象被引用一次 referenceCount 都会进行自增。每当 close 方法被调用时,referenceCount 进行自减。ReferenceCountExchangeClient 内部仅实现了一个引用计数的功能,其他方法并无复杂逻辑,均是直接调用被装饰对象的相关方法。所以这里就不多说了,继续向下分析,这次是 HeaderExchangeClient。

public class HeaderExchangeClient implements ExchangeClient {     private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));    private final Client client;    private final ExchangeChannel channel;    private ScheduledFuture> heartbeatTimer;    private int heartbeat;    private int heartbeatTimeout;     public HeaderExchangeClient(Client client, boolean needHeartbeat) {        if (client == null) {            throw new IllegalArgumentException("client == null");        }        this.client = client;                // 创建 HeaderExchangeChannel 对象        this.channel = new HeaderExchangeChannel(client);                // 以下代码均与心跳检测逻辑有关        String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);        this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);        this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);        if (heartbeatTimeout < heartbeat * 2) {            throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");        }        if (needHeartbeat) {            // 开启心跳检测定时器            startHeartbeatTimer();        }    }     @Override    public ResponseFuture request(Object request) throws RemotingException {        // 直接 HeaderExchangeChannel 对象的同签名方法        return channel.request(request);    }     @Override    public ResponseFuture request(Object request, int timeout) throws RemotingException {        // 直接 HeaderExchangeChannel 对象的同签名方法        return channel.request(request, timeout);    }     @Override    public void close() {        doClose();        channel.close();    }        private void doClose() {        // 停止心跳检测定时器        stopHeartbeatTimer();    }     private void startHeartbeatTimer() {        stopHeartbeatTimer();        if (heartbeat > 0) {            heartbeatTimer = scheduled.scheduleWithFixedDelay(                    new HeartBeatTask(new HeartBeatTask.ChannelProvider() {                        @Override                        public Collection getChannels() {                            return Collections.singletonList(HeaderExchangeClient.this);                        }                    }, heartbeat, heartbeatTimeout),                    heartbeat, heartbeat, TimeUnit.MILLISECONDS);        }    }     private void stopHeartbeatTimer() {        if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {            try {                heartbeatTimer.cancel(true);                scheduled.purge();            } catch (Throwable e) {                if (logger.isWarnEnabled()) {                    logger.warn(e.getMessage(), e);                }            }        }        heartbeatTimer = null;    }        // 省略部分方法}

HeaderExchangeClient 中很多方法只有一行代码,即调用 HeaderExchangeChannel 对象的同签名方法。那 HeaderExchangeClient 有什么用处呢?答案是封装了一些关于心跳检测的逻辑。心跳检测并非本文所关注的点,因此就不多说了,继续向下看。

final class HeaderExchangeChannel implements ExchangeChannel {        private final Channel channel;        HeaderExchangeChannel(Channel channel) {        if (channel == null) {            throw new IllegalArgumentException("channel == null");        }                // 这里的 channel 指向的是 NettyClient        this.channel = channel;    }        @Override    public ResponseFuture request(Object request) throws RemotingException {        return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));    }     @Override    public ResponseFuture request(Object request, int timeout) throws RemotingException {        if (closed) {            throw new RemotingException(..., "Failed to send request ...");        }        // 创建 Request 对象        Request req = new Request();        req.setVersion(Version.getProtocolVersion());        // 设置双向通信标志为 true        req.setTwoWay(true);        // 这里的 request 变量类型为 RpcInvocation        req.setData(request);                                                // 创建 DefaultFuture 对象        DefaultFuture future = new DefaultFuture(channel, req, timeout);        try {            // 调用 NettyClient 的 send 方法发送请求            channel.send(req);        } catch (RemotingException e) {            future.cancel();            throw e;        }        // 返回 DefaultFuture 对象        return future;    }}

到这里大家终于看到了 Request 语义了,上面的方法首先定义了一个 Request 对象,然后再将该对象传给 NettyClient 的 send 方法,进行后续的调用。需要说明的是,NettyClient 中并未实现 send 方法,该方法继承自父类 AbstractPeer,下面直接分析 AbstractPeer 的代码。

public abstract class AbstractPeer implements Endpoint, ChannelHandler {        @Override    public void send(Object message) throws RemotingException {        // 该方法由 AbstractClient 类实现        send(message, url.getParameter(Constants.SENT_KEY, false));    }        // 省略其他方法} public abstract class AbstractClient extends AbstractEndpoint implements Client {        @Override    public void send(Object message, boolean sent) throws RemotingException {        if (send_reconnect && !isConnected()) {            connect();        }                // 获取 Channel,getChannel 是一个抽象方法,具体由子类实现        Channel channel = getChannel();        if (channel == null || !channel.isConnected()) {            throw new RemotingException(this, "message can not send ...");        }                // 继续向下调用        channel.send(message, sent);    }        protected abstract Channel getChannel();        // 省略其他方法}

默认情况下,Dubbo 使用 Netty 作为底层的通信框架,因此下面我们到 NettyClient 类中看一下 getChannel 方法的实现逻辑。

public class NettyClient extends AbstractClient {        // 这里的 Channel 全限定名称为 org.jboss.netty.channel.Channel    private volatile Channel channel;     @Override    protected com.alibaba.dubbo.remoting.Channel getChannel() {        Channel c = channel;        if (c == null || !c.isConnected())            return null;        // 获取一个 NettyChannel 类型对象        return NettyChannel.getOrAddChannel(c, getUrl(), this);    }} final class NettyChannel extends AbstractChannel {     private static final ConcurrentMap channelMap =         new ConcurrentHashMap();     private final org.jboss.netty.channel.Channel channel;        /** 私有构造方法 */    private NettyChannel(org.jboss.netty.channel.Channel channel, URL url, ChannelHandler handler) {        super(url, handler);        if (channel == null) {            throw new IllegalArgumentException("netty channel == null;");        }        this.channel = channel;    }     static NettyChannel getOrAddChannel(org.jboss.netty.channel.Channel ch, URL url, ChannelHandler handler) {        if (ch == null) {            return null;        }                // 尝试从集合中获取 NettyChannel 实例        NettyChannel ret = channelMap.get(ch);        if (ret == null) {            // 如果 ret = null,则创建一个新的 NettyChannel 实例            NettyChannel nc = new NettyChannel(ch, url, handler);            if (ch.isConnected()) {                // 将  键值对存入 channelMap 集合中                ret = channelMap.putIfAbsent(ch, nc);            }            if (ret == null) {                ret = nc;            }        }        return ret;    }}

获取到 NettyChannel 实例后,即可进行后续的调用。下面看一下 NettyChannel 的 send 方法。

public void send(Object message, boolean sent) throws RemotingException {    super.send(message, sent);     boolean success = true;    int timeout = 0;    try {        // 发送消息(包含请求和响应消息)        ChannelFuture future = channel.write(message);                // sent 的值源于  中 sent 的配置值,有两种配置值:        //   1. true: 等待消息发出,消息发送失败将抛出异常        //   2. false: 不等待消息发出,将消息放入 IO 队列,即刻返回        // 默认情况下 sent = false;        if (sent) {            timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);            // 等待消息发出,若在规定时间没能发出,success 会被置为 false            success = future.await(timeout);        }        Throwable cause = future.getCause();        if (cause != null) {            throw cause;        }    } catch (Throwable e) {        throw new RemotingException(this, "Failed to send message ...");    }     // 若 success 为 false,这里抛出异常    if (!success) {        throw new RemotingException(this, "Failed to send message ...");    }}

经历多次调用,到这里请求数据的发送过程就结束了,过程漫长。为了便于大家阅读代码,这里以 DemoService 为例,将 sayHello 方法的整个调用路径贴出来。

proxy0#sayHello(String)  —> InvokerInvocationHandler#invoke(Object, Method, Object[])    —> MockClusterInvoker#invoke(Invocation)      —> AbstractClusterInvoker#invoke(Invocation)        —> FailoverClusterInvoker#doInvoke(Invocation, List>, LoadBalance)          —> Filter#invoke(Invoker, Invocation)  // 包含多个 Filter 调用            —> ListenerInvokerWrapper#invoke(Invocation)               —> AbstractInvoker#invoke(Invocation)                 —> DubboInvoker#doInvoke(Invocation)                  —> ReferenceCountExchangeClient#request(Object, int)                    —> HeaderExchangeClient#request(Object, int)                      —> HeaderExchangeChannel#request(Object, int)                        —> AbstractPeer#send(Object)                          —> AbstractClient#send(Object, boolean)                            —> NettyChannel#send(Object, boolean)                              —> NioClientSocketChannel#write(Object)

在 Netty 中,出站数据在发出之前还需要进行编码操作,接下来我们来分析一下请求数据的编码逻辑。

请求编码

在分析请求编码逻辑之前,我们先来看一下 Dubbo 数据包结构。


dubbo的底层调用过程 dubbo的整个调用过程_dubbo的底层调用过程_03


Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容。


dubbo的底层调用过程 dubbo的整个调用过程_dubbo源码_04


了解了 Dubbo 数据包格式,接下来我们就可以探索编码过程了。这次我们开门见山,直接分析编码逻辑所在类。如下:

public class ExchangeCodec extends TelnetCodec {     // 消息头长度    protected static final int HEADER_LENGTH = 16;    // 魔数内容    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];    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) {            // 对 Request 对象进行编码            encodeRequest(channel, buffer, (Request) msg);        } else if (msg instanceof Response) {            // 对 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);         // 创建消息头字节数组,长度为 16        byte[] header = new byte[HEADER_LENGTH];         // 设置魔数        Bytes.short2bytes(MAGIC, header);         // 设置数据包类型(Request/Response)和序列化器编号        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());         // 设置通信方式(单向/双向)        if (req.isTwoWay()) {            header[2] |= FLAG_TWOWAY;        }                // 设置事件标识        if (req.isEvent()) {            header[2] |= FLAG_EVENT;        }         // 设置请求编号,8个字节,从第4个字节开始设置        Bytes.long2bytes(req.getId(), header, 4);         // 获取 buffer 当前的写位置        int savedWriteIndex = buffer.writerIndex();        // 更新 writerIndex,为消息头预留 16 个字节的空间        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);        // 创建序列化器,比如 Hessian2ObjectOutput        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);         // 将 buffer 指针移动到 savedWriteIndex,为写消息头做准备        buffer.writerIndex(savedWriteIndex);        // 从 savedWriteIndex 下标处写入消息头        buffer.writeBytes(header);        // 设置新的 writerIndex,writerIndex = 原写下标 + 消息头长度 + 消息体长度        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);    }        // 省略其他方法}

以上就是请求对象的编码过程,该过程首先会通过位运算将消息头写入到 header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。本节的最后,我们再来看一下 Request 对象的 data 字段序列化过程,也就是 encodeRequestData 方法的逻辑,如下:

public class DubboCodec extends ExchangeCodec implements Codec2 {        protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {        RpcInvocation inv = (RpcInvocation) data;         // 依次序列化 dubbo version、path、version        out.writeUTF(version);        out.writeUTF(inv.getAttachment(Constants.PATH_KEY));        out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));         // 序列化调用方法名        out.writeUTF(inv.getMethodName());        // 将参数类型转换为字符串,并进行序列化        out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));        Object[] args = inv.getArguments();        if (args != null)            for (int i = 0; i < args.length; i++) {                // 对运行时参数进行序列化                out.writeObject(encodeInvocationArgument(channel, inv, i));            }                // 序列化 attachments        out.writeObject(inv.getAttachments());    }}

至此,关于服务消费方发送请求的过程就分析完了,接下来我们来看一下服务提供方是如何接收请求的。

总结

感谢大家看到最后,由于有字数限制本文将分为几个部分发出来,如果喜欢本文记得点个赞哦,若有不对之处还请指正。