五Dubbo服务引用源码分析--3服务引用-下

五Dubbo服务引用源码分析--3服务引用-下

紧接上文,对服务引用源码分析:

TAG 1.1.2 FailOverClusterInvoker.doInvoke()--cluster中调用服务

返回AbstractClusterInvoker.invoke()

上面挑选出了可以正常执行的invokers,但是多个做集群,此时该执行哪一个。需要如下:

这里仍然采用模板方法模式,doInvoke由子类实现。

image-20211027193552630

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。集群对应的容错方案,可以生成对应的Invoker类。

image-20230313131516831

跟入FailOverClusterInvoker.doInvoke

 public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { //0
    	List<Invoker<T>> copyinvokers = invokers;
    	checkInvokers(copyinvokers, invocation);
   		//获取url中invocation.getMethodName方法的参数中,关于retries重试次数
      int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;//默认重试次数为3
        if (len <= 0) {
            len = 1;
        }
   
        RpcException le = null; // 记录loop循环中上一次exception内容
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // 记录已经被invoked的invokers.
        Set<String> providers = new HashSet<String>(len);//根据重试次数,每次获取不同的提供者provider
   
/** …………………………………………………………………………………………………失败重试failover的for循环……………………………………………………………………………………………………… */
        for (int i = 0; i < len; i++) { //1
        	//重试时,进行重新选择,避免重试时invoker列表已发生变化.
        	//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
        	if (i > 0) { //2 
        		checkWheatherDestoried();
//DOI1 list(invocation)-失败重试时重新获取invokers
            //重新获取invokers列表
        		copyinvokers = list(invocation);
        		//重新检查一下
        		checkInvokers(copyinvokers, invocation);
        	} //2 
//DOI2 select(loadbalance, invocation, copyinvokers, invoked)
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
          	//将select选出来的invoker,加入invoked队列,表示选出来过,调用过(无论invoker.invoke是否成功)
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List)invoked);
            try { //2 
//DOI3 invoker.invoke(invocation)--实际调用
              	//实际调用
                Result result = invoker.invoke(invocation);
                  	//如果le不为null,表示发生过失败重试,此时log记录前一次失败信息
                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; 
            }
          //如果invoker.invoke异常,不执行return,继续loop重试 
          catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
              //loop执行中,记录每次调用服务的provider的address地址
                providers.add(invoker.getUrl().getAddress());
            } //2 
        } //1 
        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);
    }//0

注意,这里实现的是failover策略,失败重试,设置重试次数len。失败重试时候,需要从新list进行选择,防止重试时候invokers列表(invokers列表,是从directory.list获得的)发生变化。

doInvoke()主要逻辑:

1 从invocation中获取重试次数参数retries=len;
2 for循环,进行失败重试逻辑:
​	a 判断循环是否是第一次,如果不是第一次执行,表示失败重试过,避免list获取的invokers列表变化,需要重新list获取;
​	b 执行select逻辑,从invokers中依据loadbanlance规则,选择一个可执行的invoker。然后记录选出来的invoker到invoked,避免再次选到;
​	c log(error) 失败重试时,此处log记录上次失败的信息
​	d invoker.invoke调用服务

上述是failover失败重试的主要逻辑。

DOI1 list(invocation)-失败重试时重新获取invokers

注意,在失败重试时,需要重新根据rpcInvocation

image-20230313131546301
AbstractClusterInvoker
    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
    //这里,调用registryDirectory.list重新获取invokers
        List<Invoker<T>> invokers = directory.list(invocation);
        return invokers;
    }

image-20230104132957251

详见[TAG1.1.1 registryDirectory.list(invocation)](#TAG1.1.1 registryDirectory.list(invocation))

DOI2 AbstractClusterInvoker.select(loadbalance, invocation, copyinvokers, invoked)

然后select,通过loadbalance选择invoker

AbstractClusterInvoker.select
  
    protected Invoker<T> 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);//默认为false,非持久的
        {
            //ignore overloaded method
            if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
                stickyInvoker = null;
            }
            //ignore concurrency problem
            if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
                if (availablecheck && stickyInvoker.isAvailable()) {
                    return stickyInvoker;
                }
            }
        }
			//执行负载均衡策略,选择出可执行的invoker
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);

        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

select是AbstraceClusterInvoker内的方法。

接下来就是LoadBalance模块出现。

image-20211028205330799

AbstraceClusterInvoker
  //参数:randomLoadBalance
    private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
  //如果invokers只有一个invoker,直接返回
        if (invokers.size() == 1)
            return invokers.get(0);
        if (loadbalance == null) {
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
        }
//DOI2.1 loadbalance.select(invokers, getUrl(), invocation)
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

        //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
  			//如果选出的invoker已经被执行过(在selected中)、不可用时,重新选择reselect
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {
//DOI2.2 AbstraceClusterInvoker.reselect
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rinvoker != null) {
                  //重新选择的部位null,直接返回
                    invoker = rinvoker;
                } else {
     //如果reselect选择的invoker为null,判断之前选择的invoker在invokers中的index,如果不是最后一个,就选择下一个;否则,返回第一个
                    int index = invokers.indexOf(invoker);
                    try {
                        //避免碰撞
                        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;
    }

doSelect逻辑如下:

1 Dubbo提供了多种均衡策略,缺省为随机调用,但是如果集群的数量为1,那么将直接返回。
2 当传入invokers参数大于一个,执行负载均衡选择出一个invoker;
3 如果invoked包含该invoker,执行重选。如果重选成功,赋值;如果重选出为null,则根据上一次选择invoker的位置,选择invokers列表中的下一个节点。
DOI2.1 loadbalance.select(invokers, getUrl(), invocation)

这时候进入loadBalance选择出Invoker。(相关具体算法看后面的负载均衡分析中)

image-20211028210515937

此时先执行抽象类中的select方法

image-20211028210547504

当invokers大于两个,执行LoadBalance实现类的doSelect方法。

仍旧是模板方法,doselect由子类实现

image-20230104135118547
public class RandomLoadBalance extends AbstractLoadBalance {
 private final Random random = new Random();//随机

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); //invokers总个数
        int totalWeight = 0; // 总权重
        boolean sameWeight = true; // 权重是否都一样
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // 累计总权重
            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,按总权重随机选择
            int offset = random.nextInt(totalWeight);
            // 确定随机值落在哪个invoker的权重区间片断上
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // 如果权重相同、或者为0,则均等随机地获取invoker
        return invokers.get(random.nextInt(length));
    }

总结:

在集群容错的整体架构中,consumer端invoker.invoke()的执行过程,dubbo做了如下三件事:

image-20211028210800309

1 在 Directory中找出本次集群中的全部 invokers

2 在 Router中,将上一步的全部 invokers挑选出能正常执行的 invokers(即invoker.getUrl的协议头不是mock://的);

3 在 LoadBalance中,将上一步的能正常的执行 invokers中,根据配置的负载均衡策略,挑选出需要执行的 invoker。

注意:

到此为止,只是loadbalance选出一个可以执行的invoker。具体的执行invoker,继续看下面。

DOI3 invoker.invoke(invocation)--实际调用(核心)--filter链式调用

经过router路由、loadbalance负载均衡返回的invoker如下:

image-20230313131649481

从返回的invoker可知,由外到内的包裹层次为:RegistryDirectory$InvokerDelegate->ProtocolFilterWrapper$1->ListenerInvokerWrapper->DubboInvoker。

RegistryDirectory$InvokerDelegate---consumer端初始化过程创建的invoker的代理类;//DOI3.1
		ProtocolFilterWrapper$1--filter过滤的invoker类,包含filter链;//DOI3.2 
				ListenerInvokerWrapper--包裹了listener,在服务refer时执行listener.refer;//DOI3.3 
						dubboInvoker--核心的invoker,内部包含了远程调用的nettyClient客户端 //DOI3.4
DOI3.1 RegistryDirectory$InvokerDelegate.invoke
//RegistryDirectory$InvokerDelegate父类
public class InvokerWrapper<T> implements Invoker<T> {

   @Override
    public Result invoke(Invocation invocation) throws RpcException {
      //代理给内部invoker.invoke处理
        return invoker.invoke(invocation);
    }

image-20230104140731859

DOI3.2 ProtocolFilterWrapper$1.invoke(调用时,consumer端filter链式处理)

在对invoker构造的filter拦截器包装类为ProtocolFilterWrapper,其以链式结构存在。

ProtocolFilterWrapper

    public Result invoke(Invocation invocation) throws RpcException {
  			//参数next,为filter链上的下一个invoker对象
          return filter.invoke(next, invocation);
    }

image-20230104151256701

此时,执行filter链。

中间的ConsumerContextFilter、FutureFilter、MonitorFilter拦截器的filter.invoke(next,invocation)拦截过程,省略

以futureFilter为例:

@Activate(group = Constants.CONSUMER)
public class FutureFilter implements Filter {

    protected static final Logger logger = LoggerFactory.getLogger(FutureFilter.class);

    @Override
    public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
        final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);

        fireInvokeCallback(invoker, invocation);
        // need to configure if there's return value before the invocation in order to help invoker to judge if it's
        // necessary to return future.
      //链式调用,传递给下一个protocolFilterWrapper
        Result result = invoker.invoke(invocation);
        if (isAsync) {
            asyncCallback(invoker, invocation);
        } else {
            syncCallback(invoker, invocation, result);
        }
        return result;
DOI3.3 DubboInvoker.invoke--(AbstractInvoker.invoke)

从DubboInvoker.invoke的调用开始,进入到consumer的请求发送阶段。

image-20230111171502780

涉及到consumer---->provider端,Exchange、Transport、Serialize层级的处理逻辑。核心在于底层Netty通信框架对消息的构造、同步转异步、编解码、Netty通道上注册channelHandler的处理流程的内容,以及provider接受到请求对信息的处理。

消息的发送详情见5.3章节。

posted @ 2023-03-13 14:50  LeasonXue  阅读(26)  评论(0编辑  收藏  举报