深度解析dubbo服务调用invoke流程(调用者端)

注:本文基于dubbo v2.6.1
当我们在调用某个服务提供者暴露出来的接口的时候,实际上调用的是dubbo框架帮我们生成的代理类,例如我们现在有个服务提供者暴露的接口

public interface IHelloProviderService {
    String getName(Integer id);
}

我们在启动服务的时候,dubbo会帮我们生成一个Proxy代理类,我们可以看下这个代理类的样子(之前我们在《深度解析dubbo服务远程引用(创建Proxy流程)》这篇文章中末尾曾经说过,现在我们在把之前代理类的图拿过来)
在这里插入图片描述
注意: 这个$echo这个方法是dubbo框架回声方法,我们可以不用管他
我们调用getName方法的时候,实际上调用的就是这个proxy里面的getName方法,因为这个代理类实现了IHelloProviderService 接口,并重写了getName方法,我们可以看到方法里面是执行了handler.invoke()方法,然后method对象与我们传入的参数值 传了进去。
这个handler 其实就是封装了我们之前 服务引用的时候生成的 FailoverClusterInvoker 对象。
这里这个handler 还不是FailoverClusterInvoker对象, 而是InvokerInvocationHandler这个类在外面又包了一层,大约是这个样子的层级。
在这里插入图片描述

1. InvokerInvocationHandler

我们先来看下这个InvokerInvocationHandler 类,它实现了接口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类型的方法的时候
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        /// 下面都是调用了些Object的方法
        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]);
        }
        //组装rpcinvocation对象 执行调用
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }
}

他这里就一个作用,就是把Object的一些方法过滤去,当调用Object方法的时候直接调用invoker自己对象的方法,就不用远程调用了。
接下来就是看看这个FailoverClusterInvoker 类了

2.FailoverClusterInvoker

FailoverClusterInvoker 这个类是干什么的呢,可以从类名看出来,这个是一个失败重试的类。
先看下继承关系
在这里插入图片描述
我们可以看到FailoverClusterInvoker 继承AbstractClusterInvoker,AbstractClusterInvoker这个抽象类其实还有很多子类,我们可以看到这些都是,
在这里插入图片描述
然后这个invoke方法是在父类里面的,然后由子类实现doInvoke方法。我们先来看下这个AbstractClusterInvoker 的invoke方法。

 // 实现接口的invoker方法
    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();   // 检验是否销毁
        LoadBalance loadbalance = null;  //定义负载均衡

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {// 就是将一些公共的kv 设置到invocation中
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        // 获取所有服务提供者的集合
        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && !invokers.isEmpty()) {

            // invokers 不是空  根据dubbo spi 获取负载均衡策略  默认是random
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }

        // 如果是异步调用,就设置调用编号
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

        // 具体实现是由子类实现的
        return doInvoke(invocation, invokers, loadbalance);
    }
    // 检测是否已经销毁
    protected void checkWhetherDestroyed() {

        if (destroyed.get()) {
            throw new RpcException("Rpc cluster invoker for " + getInterface() + " on consumer " + NetUtils.getLocalHost()
                    + " use dubbo version " + Version.getVersion()
                    + " is now destroyed! Can not invoke any more.");
        }
    }

1.首先检查是否销毁
2.将上下文中的附加信息添加到invocation 中。
3.获取invoker集合,获取负载均衡策略(这个后面讲)
4.调用子类的doInvoke(invocation, invokers, loadbalance) 方法。
我们来看下 FailoverClusterInvoker 的doInvoke 方法。

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);// 检验参数

        // 获取重试次数,如果没有设置,就是用默认2+1  加1 操作是本身要调用一次,然后如果失败了 再重试调用1次
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception. 异常
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();  //检验是否已经销毁
                copyinvokers = list(invocation);  // 获取所有的invokers
                // check again
                checkInvokers(copyinvokers, invocation);
            }

            // 选择一个invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
            invoked.add(invoker);  // 添加到已经选择的列表中

            // 将已经选择过的invoker列表设置到context中
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                // 调用 获得结果
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {//重试过程中,将最后一次调用的异常信息以 warn 级别日志输出
                    logger.warn("Although retry the method " + invocation.getMethodName()
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyinvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.   // 如果是业务性质的异常,不再重试,直接抛出
                    throw e;
                }
                le = e;  // 其他性质的异常统一封装成RpcException
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {

                // 将提供者的地址添加到providers
                providers.add(invoker.getUrl().getAddress());
            }
        }
        // 最后抛出异常
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyinvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

其实 FailoverClusterInvoker 的doInvoke 方法中就是失败重试的逻辑,
首先获取重试次数默认情况是调用一次,然后重试2次。
我们看for循环中,不是第一次的时候,要重新获取invoker列表,这里就是为了保证invoker列表是最新的,能够跟注册中心的保持一致。
接着就是调用select()方法,这个方法其实就是根据负载均衡算法获取一个invoker,然后将这个invoker放到一个调用过的列表中,然后将这个列表塞到context中,之后就是调用invoker的invoke方法,返回Result 结果。异常处理是如果是业务异常 直接抛出不再重试,其他异常接着重试。
这个负载均衡获取invoker咱们这里就先不讲了,这个后面会有文章。
其实我们获取的这个invoker 还不直接是DubboInvoker,而是经过一堆FilterInvoker包装过的。先调用到FilterInvoker,最后才到这个DubboInvoker,这dubboInvoker我们这里就不看了,可以自己研究一下,然后下面这两张流程图是服务消费者调用一次要经过的invoker。
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值