Dubbo进阶(十二)- Dubbo 请求调用过程(二)

由于深入源码,分析的较为细,因而调用过程分为两篇文章进行。>…<

Dubbo 请求调用过程(一)

上篇文章从 InvokerInvocationHandler 调用,到 MockClusterInvoker 调用,再到AbstractClusterInvoker,而后进入 FailoverClusterInvoker
最后就开始调用 invoker.invoke(invocation) ,即负载均衡后由具体invoker 进行调用操作。
在这里插入图片描述

上篇文章最后,细致的分析了 ProtocolFilterWrapper 中 filter 的构成。

本文以 ProtocolFilterWrapperinvokeResult asyncResult = filterInvoker.invoke(invocation); 开始。

  1. ConsumerContextFilter
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 设置基本invoker 等参数
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
        // 将invoker 设置到invocation
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            RpcContext.removeServerContext();
            return invoker.invoke(invocation);
        } finally {
            RpcContext.removeContext();
        }
    }

第一个链式调用的为ConsumerContextFilter,它主要功能是往当前执行上下文中设置 invokerinvocationlocalAddressremoteAddressremotePort等。
而后,将 链式的下一个 Filter 放入 invocation中,标识为当前invocation执行的 invoker,并将当前上下文的serverLocal清楚
最后调用invoker.invoke(invocation); 执行下一个Filter调用

  1. FutureFilter
    FutureFilter 主要是用来实现callback功能的,Dubbo 中支持callback,服务端可以调用客户端定义的一些callback方法,可以看官方文档:
    http://dubbo.apache.org/zh-cn/blog/dubbo-invoke.html
    @Override
    public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
        // 检测是否有callback,执行callback
        fireInvokeCallback(invoker, invocation);
        return invoker.invoke(invocation);
    }

具体callback在 fireInvokeCallback 执行,具体分析看博主以后分析 Callback 文章。

  1. MonitorFilter
    MonitorFilter 是一个 在 consumer 和 provider 都有的 Filter。
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 判断是否有配置 monitor
        if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
            invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis()));
            getConcurrent(invoker, invocation).incrementAndGet(); // count up
        }
        return invoker.invoke(invocation); // proceed invocation chain
    }

Monitor 主要通过 ConcurrentMap<String, AtomicInteger>, 来记录 interface.method 的调用次数。
最后就往下执行。

ListenerInvokerWrapper

当执行完 ListenerFilterWrapper后,就会执行 Protocol 的下一个 Wrapper,即 ProtocolListenerWrapper
具体ProtocolWrapper 装配 原理,可以看这篇文章:
@Reference或ReferenceConfig.get代理对象如何产生(一):SPI模式中 Wrapper和 SPI 类组装逻辑

ProtocolListenerWrapper 的refer 方法中,返回了 ListenerInvokerWrapper 的包装类:

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, INVOKER_LISTENER_KEY)));
    }

所以下一步则会进入到 ListenerInvokerWrapperinvoke方法。
ListenerInvokerWrapperinvoke方法 则是简单的调用invoke的invoker方法:

而往后,则是包着一层 AsyncToSyncInvokerDubboInvoker (如果使用的是默认的Dubbo协议)。

AsyncToSyncInvoker 的封装过程同样可以参看这篇文章 https://blog.csdn.net/anLA_/article/details/99773768,跟着源码读一遍就会有大概了解了。

AsyncToSyncInvoker

这个Invoker,名字上看是由异步到同步的Invoker,那里面具体是怎样一种逻辑呢?

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
       // 执行 Invoker 
        Result asyncResult = invoker.invoke(invocation);

        try {
        	// 如果是同步,则需要同步等待
            if (InvokeMode.SYNC == ((RpcInvocation)invocation).getInvokeMode()) {
                asyncResult.get();
            }
        } catch (InterruptedException e) {
            throw new RpcException("Interrupted unexpectedly while waiting for remoting result to return!  method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (ExecutionException e) {
            Throwable t = e.getCause();
            if (t instanceof TimeoutException) {
                throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
            } else if (t instanceof RemotingException) {
                throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
            }
        } catch (Throwable e) {
            throw new RpcException(e.getMessage(), e);
        }
        return asyncResult;
    }

上文中,执行了 invoker.invoke(invocation);,执行DubboInvokerinvoke 方法,但是DubboInvoker 没有 invoke 方法,所以最终走到 AbstractInvokerinvoke 方法。
另一个方面, 会判断当前 invocationInvokeMode 方式,如果为 SYNC 即同步调用,则直接会调用get 进行阻塞等待。

AsyncToSyncInvoker

   @Override
    public Result invoke(Invocation inv) throws RpcException {
        // 如果此时 当前invoker 已经destroy ,那么只是warn 以下,还是让本次通过。
        if (destroyed.get()) {
            logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
                    + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
        }
        RpcInvocation invocation = (RpcInvocation) inv;
        invocation.setInvoker(this);
        if (CollectionUtils.isNotEmptyMap(attachment)) {
            invocation.addAttachmentsIfAbsent(attachment);
        }
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
            /**
             * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
             * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
             * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
             * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
             */
            invocation.addAttachments(contextAttachments);
        }

        invocation.setInvokeMode(RpcUtils.getInvokeMode(url, invocation));
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

        try {
            return doInvoke(invocation);
        } catch (InvocationTargetException e) { // biz exception
            Throwable te = e.getTargetException();
            if (te == null) {
                return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
            } else {
                if (te instanceof RpcException) {
                    ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
                }
                return AsyncRpcResult.newDefaultAsyncResult(null, te, invocation);
            }
        } catch (RpcException e) {
            if (e.isBiz()) {
                return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
            } else {
                throw e;
            }
        } catch (Throwable e) {
            return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
        }
    }

上段代码主要目的主要是检查参数,初始化 invocationattachment、初始化 方法调用类型,如果是async 类型,则需要填充一个标识id。
具体上述代码详解:

  1. 检测当前Invoker是否已经destory,如果 destory ,则只是输出warn 信息,而不失败,需要让当前invoker 继续执行。
  2. 将当前 invoker 改为 this,即将 ProtocolFilterWrapper 改为 DubboInvoker。更换当前的 invoker
  3. 将 当前 attachment,放入 invocation 中,attachment 最少会有一个 : interface
  4. 将当前上下文的 Map<String,String>类型也放入invocation 中。
  5. 判断当前调用类型,主要以下集中判断:
    public static InvokeMode getInvokeMode(URL url, Invocation inv) {
        // 如果 配置了方法的返回值 是`CompletableFuture` 类型,则是 FUTURE
        if (isReturnTypeFuture(inv)) {
            return InvokeMode.FUTURE;
        } else if (isAsync(url, inv)) {
        // 如果 invocation 的 attachment 中 的 `async` 配置为 true,则为async,而 attachment 来源,上文代码中也有说明。
            return InvokeMode.ASYNC;
        } else {
        // 否则为同步调用
            return InvokeMode.SYNC;
        }
    }
  1. 这段代码: RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);,如果是异步调用,则以一个全局id标识,并放入该invoker 的 attachment中,而后该全局id自增。这个id就是异步化的关键。

DubboInvoker

DubboInvoker 是 网络传输层上面一层,里面使用 ExchangeClient 进行网络传输。

    @Override
    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        // 填充 path 和 version 参数
        inv.setAttachment(PATH_KEY, getUrl().getPath());
        inv.setAttachment(VERSION_KEY, version);
		// 选出一个ExchangeClient
        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
        // 判断是否为oneWay 方式
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
            if (isOneway) {
            // oneWay 就不需要timeout
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return AsyncRpcResult.newDefaultAsyncResult(invocation);
            } else {
            // 否则需要timeout,即异步调用。
                AsyncRpcResult asyncRpcResult = new AsyncRpcResult(inv);
                CompletableFuture<Object> responseFuture = currentClient.request(inv, timeout);
                responseFuture.whenComplete((obj, t) -> {
                    if (t != null) {
                        asyncRpcResult.completeExceptionally(t);
                    } else {
                        asyncRpcResult.complete((AppResponse) obj);
                    }
                });
                RpcContext.getContext().setFuture(new FutureAdapter(asyncRpcResult));
                return asyncRpcResult;
            }
        } 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);
        }
    }

DubboInvoker 中主要为了

  1. invocationattachment 中加入了 path、version 等信息。
  2. 选出一个 ExchangeClient,如果有多个 ExchangeClient,则循环使用。
  3. 判断该 invocation 是否为oneWayoneWay 有以下情况时调用: 当配置return 为false时,即不需要返回
  4. 获取timeout 参数,用于超时控制。而后执行调用。

上述在判断完是否为 oneWay ,就直接进行了调用,只有oneWay不需要超时功能。
使用 Java 自带的 CompletableFuture 来获取 ExchangeClient 的执行结果。

  1. ReferenceCountExchangeClient
    首先会进入 ReferenceCountExchangeClient,包装了一层 ExchangeClient 以及一个 AtonmicInteger 来进行计数。
    @Override
    public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
        return client.request(request, timeout);
    }
  1. HeaderExchangeClient
    HeaderExchangeClient 包装了一层 ExchangeChannel,以及心跳和重试的Task:
    private HeartbeatTimerTask heartBeatTimerTask;
    private ReconnectTimerTask reconnectTimerTask;

以及重试的定时器:

    private static final HashedWheelTimer IDLE_CHECK_TIMER = new HashedWheelTimer(
            new NamedThreadFactory("dubbo-client-idleCheck", true), 1, TimeUnit.SECONDS, TICKS_PER_WHEEL);
  1. HeaderExchangeChannel
    这里面主要是封装了一个 Dubbo 中的 Request dioxin,而后使用Future 框架将其发送出去:
    @Override
    public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // 创建一个Request,里面会自带一个全局id。
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        // 构造一个Future 对象
        DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout);
        try {
        // 走channel 发送
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        // 直接返回future
        return future;
    }

DefaultFuture 中包括了超时检测策略。
newFuture 方法:

    public static DefaultFuture newFuture(Channel channel, Request request, int timeout) {
        final DefaultFuture future = new DefaultFuture(channel, request, timeout);
        // timeout check
        timeoutCheck(future);
        return future;
    }

timeoutCheck(future)方法:

    private static void timeoutCheck(DefaultFuture future) {
        TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
        future.timeoutCheckTask = TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
    }

上述会生成一个以 future 的id为参数的Task,执行时间是 过了timeout后,过30毫秒进行一次扫描。
TimeoutCheckTask 中逻辑:

        @Override
        public void run(Timeout timeout) {
            DefaultFuture future = DefaultFuture.getFuture(requestID);
            // 如果已经完成,那么就返回
            if (future == null || future.isDone()) {
                return;
            }
            // 否则出错了,构建错误消息体。
            Response timeoutResponse = new Response(future.getId());
            // set timeout status.
            timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
            timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
            // 处理后续操作,包括将该Future移除,将定时器干掉等。
            DefaultFuture.received(future.getChannel(), timeoutResponse, true);

        }

Dubbo 中当出错了,会有两种超时错误类型,一种是 CLIENT_TIMEOUT,另一种是 SERVER_TIMEOUT,即如果规定的时间,Client没有发送出去,则为client端超时,否则如果client已发送还是超时,那么就是server端超时错误。

最终,当 Future 完成是,会由 Futrue 判断,是否有错误,有则抛出。

                responseFuture.whenComplete((obj, t) -> {
                    if (t != null) {
                        asyncRpcResult.completeExceptionally(t);
                    } else {
                        asyncRpcResult.complete((AppResponse) obj);
                    }
                });

整个一套调用逻辑就分析完了,当然,缺少了网络传输相关逻辑,如果是Dubbo默认的传输协议,则是Netty 相关应用。
在以后的文章将会介绍。

总结

本文主要探讨了以下几个问题:

  1. 代理对象调用逻辑
  2. Dubbo 几种调用方式分析,Async,Sync,OneWay,Futrue等
  3. 接口超时实现逻辑分析

当然还大概的带过了一些边缘源码,例如ExchangeClientHeaderExchangeClientReferenceCountExchangeClient 封装逻辑、定时器HashedWheelTimer,Dubbo 中 share connection 分析。

当然这些文章都会分析的,且看下篇分解,>…<

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值