13、服务引入之消费者方法调用过程(集群容错与负载均衡)

前一节分析服务引入的提供者目录刷新和消费者与提供者建立连接的逻辑,并分析了以下主要的包装层级,我把它分成了三层

MockClusterInvoker
    invoker -> FailoverClusterInvoker(如果消费者设置了group,那么这个会变成MergeableClusterInvoker)
                    directory -> RegistryDirectory(具有动态更新提供者目录的功能)
                                    urlInvokerMap -> 这是一个Map,key为提供者url,值为InvokeDelegate

        
InvokeDelegate
    invoker -> ProtocolFilterWrapper
                invoker -> ListenerInvokerWrapper
                            invoker -> DubboInvoker
                                        clients -> 这是一个数组,它的元素个数取决于配置服务引入时设置了允许多少连接数,其实例为ReferenceCountExchangeClient
                                        
                                        
ReferenceCountExchangeClient
    client -> HeaderExchangeClient
                client -> NettyClient
                            channel -> NioSocketChannel
                            handler -> MultiMessageHandler
                                            handler -> HeartbeatHandler
                                                           handler -> AllChannelHandler
                                                                            handler -> DecodeHandler
                                                                                            handler -> HeaderExchangeChannel
                                                                                                            handler -> DubboProtocol#requestHandler(内部类)

MockClusterInvoker实例属于invoker的最外层,这个MockClusterInvoker被传入ProxyFactory.getProxy(invoker)形成了消费端的接口代理,所以当消费端接口调用方法的时候就会调用到MockClusterInvoker,可以说这个类是准备调用提供者服务的入口,MockClusterInvoker直接引用的invoker是一个故障转移调用者(在指定了group的情况是可合并调用者),而FailoverClusterInvoker所持有的提供者列表是一个动态列表,可以根据注册中心变化而更新服务提供者列表

InvokeDelegate 这一层就可以代表一个提供者,它在调用提供者服务的道路上设置了一系列的过滤器

ReferenceCountExchangeClient 这一层是表示具体的每个invoker的连接,他们被封装在DubboInvoker的clients属性中,是一个数组,这一层主要会跟netty的通道打交道,然后向提供者发送信息,一旦向netty通道发送消息,那么就会调用com.alibaba.dubbo.remoting.transport.netty4.NettyCodecAdapter.InternalEncoder这个编码器进行参数编码。
并调用dubbo自己的通道处理器,但是对于发送请求,dubbo的通道处理没有做更多的逻辑,如果是同步调用的话,那么只是把请求封装到DefaultFuture中,等待着服务提供者的返回,如果服务提供者返回了数据,那么dubbo的通道就有很大的用处了,它会调用MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeChannel -> DubboProtocol#requestHandler,具体的逻辑请看请求or响应数据的处理系列文章。

下面我们来研究一下:当某个接口调用某个方法时会发生什么?前面已经说了,MockClusterInvoker是开始调用提供者的入口

public Result com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#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")) {
        //no mock
        //无需mock
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        if (logger.isWarnEnabled()) {
            logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
        }
        //force:direct mock
        //强制调用mock服务
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        try {
            result = this.invoker.invoke(invocation);
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                }
                result = doMockInvoke(invocation, e);
            }
        }
    }
    return result;
}

上面一段代码主要是检查是否需要mock服务的调用,从代码上来看,大体分为两种,一种是强制调用,还有一种就是调用服务接口失败之后调用

//实例为FailoverClusterInvoker(故障转移)
public Result com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke(final Invocation invocation) throws RpcException {
    //检查当前invoker是否已经被销毁
    checkWhetherDestroyed();
    LoadBalance loadbalance = null;

    // binding attachments into invocation.
    //从rpc调用上下文中获取附加参数
    Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
    if (contextAttachments != null && contextAttachments.size() != 0) {
        ((RpcInvocation) invocation).addAttachments(contextAttachments);
    }
    //获取可用的提供者
    List<Invoker<T>> invokers = list(invocation);
    if (invokers != null && !invokers.isEmpty()) {
        //获取负载均衡策略,默认是random策略
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
    }
    //如果是异步调用,那么通过原子操作生成调用id
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    return doInvoke(invocation, invokers, loadbalance);
}

上面一段代码最重要就是筛选提供者,现在我们来看下它具体是怎么筛选的吧

//它是RegistryDirectory的实例
public List<Invoker<T>> com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory#list(Invocation invocation) throws RpcException {
    if (destroyed) {
        throw new RpcException("Directory already destroyed .url: " + getUrl());
    }
    //实际筛选提供者的方法,完美继承了spring的编码风格
    List<Invoker<T>> invokers = doList(invocation);
    //获取上次服务订阅时获取的路由
    List<Router> localRouters = this.routers; // local reference
    if (localRouters != null && !localRouters.isEmpty()) {
        for (Router router : localRouters) {
            try {
                //检查是否是运行时筛选路由(订阅时会筛选一波)
                if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                    invokers = router.route(invokers, getConsumerUrl(), invocation);
                }
            } catch (Throwable t) {
                logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
            }
        }
    }
    return invokers;
}

com.alibaba.dubbo.registry.integration.RegistryDirectory#doList

当我把话都说在注释里的时候,我就会贴下一段代码的方法名

public List<Invoker<T>> com.alibaba.dubbo.registry.integration.RegistryDirectory#doList(Invocation invocation) {
    //禁止访问,抛出错误
    if (forbidden) {
        // 1. No service provider 2. Service providers are disabled
        throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
            "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +  NetUtils.getLocalHost()
                    + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
    }
    List<Invoker<T>> invokers = null;
    //这个集合是在服务引入启动时筛选后的结果,methodName -> List<Invoker>
    Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
    if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
        //获取方法名
        String methodName = RpcUtils.getMethodName(invocation);
        //获取我们调用接口时传递进来的参数
        Object[] args = RpcUtils.getArguments(invocation);
        if (args != null && args.length > 0 && args[0] != null
                && (args[0] instanceof String || args[0].getClass().isEnum())) {
            //如果第一个参数是枚举类型的,那么其在localMethodInvokerMap集合中的key是方法名+"."+枚举值,可以根据第一个参数值的变化去调用不同提供者
            invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
        }
        if (invokers == null) {
            //通过方法名获取提供者
            invokers = localMethodInvokerMap.get(methodName);
        }
        if (invokers == null) {
            //通过*获取提供者,这个*是在服务引入启动时经过路由筛选的
            invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
        }
        if (invokers == null) {
            //获取第一组
            Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
            if (iterator.hasNext()) {
                invokers = iterator.next();
            }
        }
    }
    //返回
    return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
}

经过路由筛选后,接下来就是负载均衡的主场了

public Result com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    List<Invoker<T>> copyinvokers = invokers;
    //如果copyinvokers为空,抛出错误
    checkInvokers(copyinvokers, invocation);
    //从url中获取retries参数值,表示失败重试次数,默认是2次,如果把第一次调用也算的话就是3次
    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.
        //i大于零,那就说明发生了失败重试
        if (i > 0) {
            //检查当前invoker是否已经关闭
            checkWhetherDestroyed();
            //重新通过路由筛选,因为提供者随时都有可能下掉
            copyinvokers = list(invocation);
            // check again
            //再次检查copyinvokers有值
            checkInvokers(copyinvokers, invocation);
        }
        //通过负载均衡筛选出一个
        Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
        invoked.add(invoker);
        //记录到rpc上下文中
        RpcContext.getContext().setInvokers((List) invoked);
        try {
            //调用可执行远程提供者的invoker
            Result result = invoker.invoke(invocation);
            //即使重试成功了,但是还是会打印日志提醒
            if (le != null && logger.isWarnEnabled()) {
                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;
        } catch (Throwable e) {
            le = new RpcException(e.getMessage(), e);
        } finally {
            //记录已经调用过的提供者地址,用于在重试成功后打印日志提供,哪些机器调用失败!
            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);
}
                                                    |
                                                    V
//selected:被排除的提供者,一般重试的时候这个会有值,这些值都是上一次调用失败的,invokers:候选的提供者
protected Invoker<T> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
    if (invokers == null || invokers.isEmpty())
        return null;
    //获取方法名
    String methodName = invocation == null ? "" : invocation.getMethodName();
    //判断是否采用粘性策略,所谓粘性就是使用过某个提供者的服务后,后面再来的请求尽量使用相同的这台机器
    boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
    {
        //ignore overloaded method
        //检查上次是否已经筛选过一个提供者了,并且个提供者是否还在提供服务
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            //如果这个提供者已经下线,那么置为null
            stickyInvoker = null;
        }
        //ignore concurrency problem
        //如果服务条件,那么使用上次筛选的提供者
        if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
            if (availablecheck && stickyInvoker.isAvailable()) {
                return stickyInvoker;
            }
        }
    }
    //真正使用负载均衡策略的地方
    Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
    //如果允许粘性,那么记录这次筛选到的结果
    if (sticky) {
        stickyInvoker = invoker;
    }
    return invoker;
}

大部分解释都已经写在注释上了,就过多的总结了,下面我们继续分析dubbo是怎么通过负载策略筛选提供者的

private Invoker<T> com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
    if (invokers == null || invokers.isEmpty())
        return null;
    //如果只有一个提供者,那么直接返回即可
    if (invokers.size() == 1)
        return invokers.get(0);
    if (loadbalance == null) {
        //如果没有指定负载均衡策略,那么获取默认的
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }
    Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

    //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
    //如果这个提供者是一个不可用的提供者,那么需要重新筛选
    if ((selected != null && selected.contains(invoker))
            || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
        try {
            //重新筛选,如果availablecheck为true,会检查每个提供者的是否可用
            Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
            if (rinvoker != null) {
                invoker = rinvoker;
            } else {
                //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                //如果重试都没有获取到合适的提供者,那么获取重新筛选前invoker的下一位,或者第一位
                int index = invokers.indexOf(invoker);
                try {
                    //Avoid collision
                    invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                } catch (Exception e) {
                    logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
                }
            }
        } catch (Throwable t) {
            logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
        }
    }
    return invoker;
}

下面我们选择一个负载策略进行分析,这里就使用随机策略分析,其他的自行分析

protected <T> Invoker<T> com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    //获取提供者数量
    int length = invokers.size(); // Number of invokers
    //权重总和
    int totalWeight = 0; // The sum of weights
    boolean sameWeight = true; // Every invoker has the same weight?
    for (int i = 0; i < length; i++) {
        //循环获取每个提供者的权重
        int weight = getWeight(invokers.get(i), invocation);
        totalWeight += weight; // Sum
        //检查每个提供者的权重是否相同,如果不同,将sameWeight标记为不同,当权重不同时就会触发非等概率取值
        if (sameWeight && i > 0
                && weight != getWeight(invokers.get(i - 1), invocation)) {
            sameWeight = false;
        }
    }
    if (totalWeight > 0 && !sameWeight) {
        // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
        //从0~n个提供者的权重之和获取偏移
        int offset = random.nextInt(totalWeight);
        // Return a invoker based on the random value.
        for (int i = 0; i < length; i++) {
            //偏移减去权重,直到减到小于零,对于这种权重不同的,他们的被选中的概率是不同的,比如我有三个提供者,第一个权重为10,第二个为30,第三个为60
            //他们被选中的概率为10%,30%,60%
            offset -= getWeight(invokers.get(i), invocation);
            if (offset < 0) {
                return invokers.get(i);
            }
        }
    }
    // If all invokers have the same weight value or totalWeight=0, return evenly.
    //如果每个提供者的权重是一样的,那么简单粗暴,直接在这n个提供者中随机一个,他们被选中的概率是一样的
    return invokers.get(random.nextInt(length));
}

对于上面一段代码,有必要在说明一下,假设我们有三个提供者,第一个提供者的权重是10,第二个提供者的权重是60,第三个提供者的权重是30,那么他们被选中的概率是10%,60%,30%

权重总和是100,我们在0 - 100之间随机获取一个数字,这个数字落在0 - 10以内的概率是10%,落在10 - 70的概率是60%,落在70~100的概率是30%

在这里插入图片描述

橙色部分代表权重为10的提供者,蓝色部分代表权重为60的提供者,绿色部分代表权重为30的提供者,总权重是100,在这个100中随机取一个值,假设我随机取了一个值是20(代码中的offset),那么这20落在了蓝色区域,当我用这个20减去第一个提供者的权重时是够减的,只有减去蓝色部分的权重时不够减,所以它选中的权重为60的提供者,它被选中的概率为60%。

筛选好invoker之后,我们就可以开始分析装饰层级的第二层了

//。。。。。。第一层

InvokeDelegate
    invoker -> ProtocolFilterWrapper
                invoker -> ListenerInvokerWrapper
                            invoker -> DubboInvoker
                                        clients -> 这是一个数组,它的元素个数取决于配置服务引入时设置了允许多少连接数,其实例为ReferenceCountExchangeClient
                                        
//。。。。。。第三层

它的入口InvokeDelegate,对于这一层的InvokeDelegate并未做更多的逻辑,直接往后调用了,ProtocolFilterWrapper,ListenerInvokerWrapper此处不再展开分析,具体的操作可查看请求or响应数据处理系列文章,这里主要来分析下不一样的东西,那就是DubboInvoker

protected Result com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke(final Invocation invocation) throws Throwable {
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
    inv.setAttachment(Constants.VERSION_KEY, version);

    ExchangeClient currentClient;
    //如果只有一个连接,直接使用这一个连接
    if (clients.length == 1) {
        currentClient = clients[0];
    } else {
        //轮流获取
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
        //检查是否是异步调用
        boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
        //是否为双向通信,也就是发送消息给对方后,需要对方回应
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        //等待超时时间
        int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        //isOneway为true的话表示单向发送,无需对方回应,通常用于对方是一个消费者的情况,只进不出
        if (isOneway) {
            //是否等待所有数据发送完毕,如果为true,线程会阻塞timeout时间,等待发送的数据全部发送完毕
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            //发送数据
            currentClient.send(inv, isSent);
            //清空future,无需获取对方的回应的结果(对方也不会回应)
            RpcContext.getContext().setFuture(null);
            //直接返回一个空结果集
            return new RpcResult();
            //是否为异步调用
        } else if (isAsync) {
            //请求接口,会将inv参数包装成Request,将Future存储到com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#FUTURES中
            //阻塞timeout,等待提供者返回响应
            ResponseFuture future = currentClient.request(inv, timeout);
            //将用于等待对方回应的ResponseFuture设置到rpc上下文中,用户可以获取到这个future
            RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
            return new RpcResult();
        } else {
            //同步获取接口响应,将rpc上下文的future清除
            RpcContext.getContext().setFuture(null);
            //同步获取接口响应
            return (Result) currentClient.request(inv, timeout).get();
        }
    } 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);
    }
}

后面就是消息的编码,消息的编码请看请求or响应数据处理系列文章,此处不再展开说明

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值