4. Dubbo源码解析之服务调用过程

Dubbo服务调用过程,分为三部分,一部分为消费者发送请求部分,一部分为提供者处理消息过程,一部分消费者接受并解析结果部分。

4.1 消费者发送请求

服务引入完成后,会创建一个接口代理类实例,并注入到接口应用处,dubbo服务调用发送请求就是用这个代理来处理的,从上一节解析中得到,最终会经过 InvokerInvocationHandler 处理器去处理.
在这里插入图片描述

4.1.1 Invoker 准备工作(降级,列举,负载均衡策略,异步幂等)

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

public class MockClusterInvoker<T> implements Invoker<T> {
    
    private final Invoker<T> 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;
    }
    
    // 省略其他方法
}

如果配置了Mock,且不是强制要求mock的话,服务会先调用其他的invoker进行服务调用,如果没有成功时,不会报错,只会mock掉,mock处理逻辑相当于服务降级处理。

这里会调用FailfastClusterInvoker.invoker() 进行处理,这里用到了模板模式,来看看它的处理逻辑:

public Result invoke(final Invocation invocation) throws RpcException {
    checkWhetherDestroyed();

    // binding attachments into invocation.
    Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
    if (contextAttachments != null && contextAttachments.size() != 0) {
        ((RpcInvocation) invocation).addAttachments(contextAttachments);
    }
	// 列举invokers
    List<Invoker<T>> invokers = list(invocation);
    // 初始化负载均衡器
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    // 如果异步时生成InvocationId
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    return doInvoke(invocation, invokers, loadbalance);
}
  • 列举invokers

    invokers = routerChain.route(getConsumerUrl(), invocation); 会通过在服务引入的订阅逻辑时设置的路由链拿到 invokers

  • 初始化负载均衡

    没有配置时,会获取到 RandomLoadBalance

  • 异步时生成InvocationId

    只为幂等操作

4.1.2 负载均衡选择提供者

public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    checkInvokers(invokers, invocation);
    // 根据负载均衡策略,筛选出所要执行的invoker
    Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
    try {
        return invoker.invoke(invocation);
    } catch (Throwable e) {
        ...
    }
}

这个集群策略是,失败后不做任何补救措施。

这里最关键的一步是,根据负载均衡策略选出其中一个invoker,做后续处理,即从多个提供者中选出一个提供者进行服务调用。Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation); ,关于负载均衡策略,其他章节介绍。

4.1.3 DubboInvoker

最终选择出来的invoker对象如下图:

在这里插入图片描述

调用链如下:

FailfastClusterInvoker -> InvokerWrapper -> ListenerInvokerWrapper
-> ProtocolFilterWrapper(这里是一个过滤器链,其他filter不写了) -> FutureFilter -> DubboInvoker

最终会调用DubboInvoker,具体注入过程前面已经介绍过。看看DubboInvoker做了什么?

首先看个比较重要的实体类 Invocation :
在这里插入图片描述

protected Result doInvoke(final Invocation invocation) throws Throwable {
    // ${装配 invocation}
    
    // 获取客户端
    ExchangeClient currentClient;
    if (clients.length == 1) {
        currentClient = clients[0];
    } else {
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
        // ${获取异步配置}
        // 异步无返回值
        if (isOneway) {
            ...
        }
        // 异步有返回值
        else if (isAsync) {
            ...
        }
        // 同步调用
        else {
            RpcContext.getContext().setFuture(null);
            // DefaultFuture 调用 get(time) 方法阻塞
            return (Result) currentClient.request(inv, timeout).get();
        }
    } catch (TimeoutException e) {
        ...
    }
}

获取客户端,如下图:
在这里插入图片描述

4.1.4 客户端发送请求

构造request对象,将invocation进行封装,最终通过netty的channel发送消息。

ChannelFuture future = channel.writeAndFlush(message);

发送过程中,会经过编解码、序列化处理,解决粘包和半包问题。这里不做过多解析。
在这里插入图片描述

4.2 提供者处理请求

消费者发送完消息后,提供者会通过处理器接收消息,具体调用步骤如下:

NettyHandler#messageReceived
	|- MultiMessageHandler#received
  		|- HeartbeatHandler#received
    		|- AllChannelHandler#received
    			|- ChannelEventRunnable#run
    				|- DecodeHandler#received
            			|- HeaderExchangeHandler#received
              				|- HeaderExchangeHandler#handleRequest
                				|- DubboProtocol.requestHandler#reply
                					|- DubboProtocol#getInvoker
                					|- ProtocolFilterWrapper.filter#invoker
                						|- AbstractProxyInvoker#invoker
                							|- AbstractProxyInvoker#doInvoker
                								|- Wrapper#invokeMethod
                									|- DemoFacade#sayHello
                		|- HeaderExchangeHandler#send

4.2.1 线程池相关

在创建WrappedChannelHandler 处理器时,会初始化线程操作,具体如下:

public WrappedChannelHandler(ChannelHandler handler, URL url) {
    ...
    executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class)
        			.getAdaptiveExtension().getExecutor(url);
    ...
}

默认是fixed,看看线程池里具体运行些什么任务?

public void run() {
    if (state == ChannelState.RECEIVED) {
        try {
            // 接收消息
            handler.received(channel, message);
        } catch (Exception e) {
            ...
        }
    } else {
        switch (state) {
            case CONNECTED:
                ...
                break;
            case DISCONNECTED:
                ...
                break;
            case SENT:
                ...
                break;
            case CAUGHT:
                ...
                break;
            default:
                logger.warn("unknown state: " + state + ", message is " + message);
        }
    }

4.2.2 获取invoker

String serviceKey = serviceKey(port, path, 
                               inv.getAttachments().get(Constants.VERSION_KEY), 
                               inv.getAttachments().get(Constants.GROUP_KEY));
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
exporter.getInvoker();

4.2.3 过滤器调用链

ProtocolFilterWrapper#buildInvokerChain 该方法会创建 invoker 调用链,如下:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    // 真正的invoker
    Invoker<T> last = invoker;
    // 获取过滤器
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
        	.getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
				...
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    Result result = filter.invoke(next, invocation);
                    if (result instanceof AsyncRpcResult) {
                        AsyncRpcResult asyncResult = (AsyncRpcResult) result;
                        asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
                        return asyncResult;
                    } else {
                        return filter.onResponse(result, invoker, invocation);
                    }
                }
                ...
            };
        }
    }
    return last;
}

4.2.4 获取结果

Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoFacadeImpl 为例,Javassist 为其生成的代理类如下。

public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    // 省略其他方法

    public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
        DemoService demoService;
        try {
            // 类型转换
            demoService = (DemoService)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            // 根据方法名调用指定的方法
            if ("sayHello".equals(string) && arrclass.length == 1) {
                return demoService.sayHello((String)arrobject[0]);
            }
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
    }
}

4.2.5 发送结果到消费者

具体发送过程和请求过程一样,不做解析。

4.3 消费者解析结果

4.3.1 结果接收和处理

编码、序列化处理过程,不做解析

4.3.2 传递结果到用户线程

服务消费方在收到响应数据后,首先要做的事情是对响应数据进行解码,得到 Response 对象。然后再将该对象传递给下一个入站处理器,这个入站处理器就是 NettyHandler。接下来 NettyHandler 会将这个对象继续向下传递,最后 AllChannelHandler 的 received 方法会收到这个对象,并将这个对象派发到线程池中。这个过程和服务提供方接收请求的过程是一样的,因此这里就不重复分析了。本节我们重点分析两个方面的内容,一是响应数据的解码过程,二是 Dubbo 如何将调用结果传递给用户线程的。下面先来分析响应数据的解码过程。

响应数据解码完成后,Dubbo 会将响应对象派发到线程池上。要注意的是,线程池中的线程并非用户的调用线程,所以要想办法将响应对象从线程池线程传递到用户线程上。分析过用户线程在发送完请求后的动作,即调用 DefaultFuture 的 get 方法等待响应对象的到来。当响应对象到来后,用户线程会被唤醒,并通过调用编号获取属于自己的响应对象。下面我们来看一下整个过程对应的代码。

public void received(Channel channel, Object message) throws RemotingException {
    channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
    final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
    try {
        if (message instanceof Request) {
            ...
        } else if (message instanceof Response) {
            // 处理响应
            handleResponse(channel, (Response) message);
        } else if (message instanceof String) {
            ...
        } else {
            handler.received(exchangeChannel, message);
        }
    } finally {
        HeaderExchangeChannel.removeChannelIfDisconnected(channel);
    }
}
static void handleResponse(Channel channel, Response response) throws RemotingException {
    if (response != null && !response.isHeartbeat()) {
        // 继续向下调用
        DefaultFuture.received(channel, response);
    }
}

看看DefaultFuture阻塞和唤醒怎么实现的?

private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private volatile ResponseCallback callback;

4.3.2.1 DefultFuture.get()

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

利用锁的信号量阻塞的,使用await()

4.3.2.2 DefultFuture.received()

private void doReceived(Response res) {
    lock.lock();
    try {
        response = res;
        if (done != null) {
            // 唤醒用户线程
            done.signal();
        }
    } finally {
        lock.unlock();
    }
    if (callback != null) {
        invokeCallback(callback);
    }
}

利用锁的信号量唤醒的,使用signal()

本篇文章在多个地方都强调过调用编号很重要,但一直没有解释原因,这里简单说明一下。一般情况下,服务消费方会并发调用多个服务,每个用户线程发送请求后,会调用不同 DefaultFuture 对象的 get 方法进行等待。 一段时间后,服务消费方的线程池会收到多个响应对象。这个时候要考虑一个问题,如何将每个响应对象传递给相应的 DefaultFuture 对象,且不出错。答案是通过调用编号。DefaultFuture 被创建时,会要求传入一个 Request 对象。此时 DefaultFuture 可从 Request 对象中获取调用编号,并将 <调用编号, DefaultFuture 对象> 映射关系存入到静态 Map 中,即 FUTURES。线程池中的线程在收到 Response 对象后,会根据 Response 对象中的调用编号到 FUTURES 集合中取出相应的 DefaultFuture 对象,然后再将 Response 对象设置到 DefaultFuture 对象中。最后再唤醒用户线程,这样用户线程即可从 DefaultFuture 对象中获取调用结果了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值