一Dubbo架构设计

一Dubbo架构设计

1.1结构设计

官网用户手册的介绍如下:

https://dubbo.gitbooks.io/dubbo-user-book/content/demos/fault-tolerent-strategy.html

image-20210924194915954

上述过程是服务调用call过程,consumer端invoker.invoke,调用provider端的invoker对象的方法,就是RPC调用主干流程。

各节点关系:

  • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
  • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

1.2 调用的代码流程

上述过程主要是技术设计角度绘出,不是实际调用。该过程只是调用proxy的方法的容错架构设计。不包含refer的初始化过程(初始化过程对directory、registry分别进行初始化)

源码中具体调用过程如下:

image-20210927022757025

代码流程:

proxy0.xxxMethod()
-->InvokerInvocationHandler.invoke
  // RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={}]
  -->MockClusterInvoker.invoke(Invocation invocation)
    -->FailoverClusterInvoker.invoke(final Invocation invocation)
      -->RegistryDirectory.list(Invocation invocation) //根据RpcInvocation中的methodName获取Invoker
        -->router过滤
        -->loadBalancer选取一个Invoker
      -->执行filter链
        // RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={path=com.alibaba.dubbo.demo.DemoService, interface=com.alibaba.dubbo.demo.DemoService, version=2.0.0, timeout=60000, group=dev}]
        -->DubboInvoker.invoke(Invocation inv)

1.3 源码追踪

本文代码使用DatatablesDemo代码。

用applicationContext启动spring,并通过interface加载服务端实现的bean。看出实例对象为dubbo的代理生成的proxy对象

image-20210927154208438

看出该proxy被传入一个invocationHaldler实例InvokerInvocationHandler(该过程就是java的动态代理)

image-20210927154449975

动态代理实现(InvocationHandlerImpl其内含有被代理的实例对象,这里,就是Invoker实例。通过调用invoke方法,调用引用服务端的方法)

	// 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
		InvocationHandler handler = new InvocationHandlerImpl(car);
		/*
		  4.根据上面提供的信息,创建代理对象 在这个过程中, 
             a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
		         b.然后根据相应的字节码转换成对应的class, 
             c.然后调用newInstance()创建实例
		 */
		Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);

A1 接下来进入dubbo的consumer调用过程,来看容错架构设计

//该类为动态代理的handler对象
public class InvokerInvocationHandler implements InvocationHandler {

// 该处为MockClusterInvoker对象,就是动态代理invocationhandler中实际代理的对象car
    private final Invoker<?> invoker;
    
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
     	//调用Invoker实现代理
             return invoker.invoke(new RpcInvocation(method, args)).recreate();

image-20211018165456240

B1 此时,进入MockClusterInvoker类

public class MockClusterInvoker<T> implements Invoker<T> {
     
    private final Directory<T> directory;

    private final Invoker<T> invoker;
     
    public Result invoke(Invocation invocation) throws RpcException {
        Result result = null;

         //获取directory中注册的URL内,方法参数是否有mock
        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
        if (value.length() == 0 || value.equalsIgnoreCase("false")) {
         //no mock
            result = this.invoker.invoke(invocation);
        }
    }

image-20211018170032542

MockClusterInvoker是针对集群部署的注册中心,提供的可以根据URL中mock参数,支持服务降级的集群管理功能的invoker。(是cluster.join返回的逻辑整合为一个invoker)。mockClusterInvoker内部持有的invoker类型为FailoverClusterInvoker类。

FailoverClusterInvoker采用模板方法,基本实现由抽象类AbstractClusterInvoker实现。

image-20211018170625379

进入AbstractClusterInvoker(此时就进入到集群,cluster)

TAG1 AbstractClusterInvoker.invoke()
public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
         protected final Directory<T> directory;
     
//TAG1 AbstractClusterInvoker.invoke(invocation)
     public Result invoke(final Invocation invocation) throws RpcException {

        checkWhetherDestroyed();

          //
        LoadBalance loadbalance;

//TAG1.1 list(invocation)
          //从directory中获取可以正常执行的invoker
        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && invokers.size() > 0) {
          //如果有可用invoker,根据invoker.url的loadbalance参数,决定负载均衡的自适应类
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        } else {
          //如果没有invoker服务,采用默认的负载均衡类
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
       
//TAG1.2 doInvoke(invocation, invokers, loadbalance)
        return doInvoke(invocation, invokers, loadbalance);
    }
     
      protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        //从directory中获取invoker服务
        List<Invoker<T>> invokers = directory.list(invocation);
        return invokers;
    }

跟入debug代码,分析执行中的数据

image-20211018190153432

image-20211018190124472

TAG1.1 list(invocation)

list内,RegistryDirectory.list(invocation)获取可用服务invokers

image-20211018190359683

image-20211018190542932

进入模板设计模式的AbstractDirectory中

public abstract class AbstractDirectory<T> implements Directory<T> {
         private final URL url;
     private volatile URL consumerUrl;
         private volatile List<Router> routers;

从这里看出,Router在Directory内

image-20211027183905622

image-20211018191657309

image-20211018191804491

分析directory的抽象类内模板方法list

    public abstract class AbstractDirectory<T> implements Directory<T> {

			public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) {
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }
        
//TAG1.1.1 AbstractDirectory.doList(invocation)
         //先通过directory的模板方法获取可以正常执行的Invoker
        List<Invoker<T>> invokers = doList(invocation);
         
         //router位于directory内
        List<Router> localRouters = this.routers; // local reference
        if (localRouters != null && localRouters.size() > 0) {
            for (Router router : localRouters) {
                try {
                    if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, true)) {
                      
//TAG1.1.2 router.route(invokers, getConsumerUrl(), invocation)
                         //通过router路由过滤invoker
                        invokers = router.route(invokers, getConsumerUrl(), invocation);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
                }
            }
        }
        return invokers;
    }
TAG1.1.1 AbstractDirectory.doList()

doList由RegistryDirectory实现(主要作用就是从directory查找正常执行的所有Invoker)

image-20230310174136402 image-20211018192101396

RegistryDirectory内各属性的意义,见[模块详解章节](#4.3 zk订阅)

image-20230310174208549

上面过程是从methodInvokerMap中取出invokers。以kv存储,如下图所示

image-20211027190145816

dolist就是从RegistryDirectory的methodInvokerMap中取出invokers。

methodInvokerMap内是以方法名为key,以List为value的map。从map中取出对应的方法的invokers服务,如果没有取到,根据methodname+arg、methodName、ANY、iterator遍历的次序逐个获取。(map中invoker实例,是RegistryDirectory$InvokerDelegate类型的invoker)

image-20230310174233163

RegistryDirectory.doList执行逻辑,是从registryDirectory的属性methodInvokerMap的属性中,根据methodName,获取invokers集合。获取的invokers类型为RegistryDirectory\(InvokerDelegate类型,详见[invoker章节内容](#1 RegistryDirectory\)InvokerDelegate)。

image-20230310174245377

该invokerDelegate有三个参数,provideUrl存储,注册中心下发的provider的服务地址。可以在refer调用服务时候,根据provideUrl、methodparm等参数,重新构造调用服务地址。

三个参数,providerUrl代表当前服务,注册在注册中心中,服务提供者provider的真实地址。

其中,delegate代理类内传入的invoker是[ListenerInvokerWrapper](#2 ListenerInvokerWrapper)的invoker,其内添加了invoker调用和销毁监听器listener。

然后,返回InvokerDelegate类型的invoker实例

TAG1.1.2 router.route()

此时,将invokers返回后,从AbstractDirectory的list继续执行list,到达Router进入路由。

image-20211019152006323

image-20211019152122754

此时的router为MockInvokerSelector(也是router)

image-20211019151826741

public interface Router extends Comparable<Router> {

 //
    URL getUrl();

//route路由方法,根据dubbo中配置的路由规则(如读写分离等),获取对应的可用的invokers服务
    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

进入route方法

image-20211019152234240

url为consumer的url

image-20211027192242828

MockInvokerSelector.route,根据参数invocation,是否有mock,选择创建normal还是mocked的invoker。

getNormalInvokers,创建正常的invokers,

image-20211019152311565

判断invokers中是否有mock过的提供者,如果没有,直接返回。

回到AbstarctClusterInvoker类,此时从directory执行list获取可用的服务类,已经完成,然后需要执行对应的调用invoke。

总结:

directory中找出本次集群中的全部Invokers;

router中,将上一步的Invokers挑选出正常执行的invokers。(getNomalInvokers)。

TAG 1.2 doInvoke()

AbstractClusterInvoker.invoke()

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

image-20211027193130264

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

image-20211027193552630

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

跟踪debug,查看源码走到该步骤,数据情况。

image-20211019152508778

image-20230310174329441

跟入FailOverClusterInvoker.doInvoke

 public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    	List<Invoker<T>> copyinvokers = invokers;
    	checkInvokers(copyinvokers, invocation);
   		//获取invocation调用中,关于retries重试次数的参数
      int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        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);
   		//失败重试failover的for循环
        for (int i = 0; i < len; i++) {
        	//重试时,进行重新选择,避免重试时invoker列表已发生变化.
        	//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
        	if (i > 0) {
        		checkWheatherDestoried();
            //重新获取invokers列表
        		copyinvokers = list(invocation);
        		//重新检查一下
        		checkInvokers(copyinvokers, invocation);
        	}
//TAG 1.2.1 select
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
          	//将select选出来的invoker,加入invoked队列,表示选出来过,调用过(无论invoker.invoke是否成功)
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List)invoked);
            try {
              	//实际调用
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                  	//当重试时,ls不为null,此时log记录,前一次失败信息记录
                    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());
            }
        }
        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);
    }

注意,这里实现的是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失败重试的主要逻辑

TAG1.2.1 select

然后select,通过loadbalance选择invoker

image-20230310174400032

select是AbstraceClusterInvoker内的方法,doselect也是其内的方法。

接下来就是LoadBalance模块出现。

image-20211028205330799

image-20230310174416169

  public abstract class AbstractClusterInvoker<T> implements Invoker<T> {

	private Invoker<T> doselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.size() == 0)
            return null;
     			//当invokers有一个,直接返回
        if (invokers.size() == 1)
            return invokers.get(0);
        // 如果只有两个invoker,退化成轮循
        if (invokers.size() == 2 && selected != null && selected.size() > 0) {
            return selected.get(0) == invokers.get(0) ? invokers.get(1) : invokers.get(0);
        }
    
//TAG1.2.1.1 loadbalance.select
    		//当invokers数目,超过两个,负载均衡获取(默认负载均衡为random)
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
        
        //如果 selected中包含(优先判断) 或者 不可用&&availablecheck=true 则重试.
        if( (selected != null && selected.contains(invoker))
                ||(!invoker.isAvailable() && getUrl()!=null && availablecheck)){
            try{
              //重新选择reselect
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if(rinvoker != null){
                  //当重新选择出invoker
                    invoker =  rinvoker;
                }else{
                  //当重新未选择出invoker,看下第一次选的位置,如果不是最后,选+1位置.
                    int index = invokers.indexOf(invoker);
                    try{
                        //最后在避免碰撞
                        invoker = index <invokers.size()-1?invokers.get(index+1) :invoker;
                    }catch (Exception e) {
                        logger.warn(e.getMessage()+" may because invokers list dynamic change, ignore.",e);
                    }
                }
            }catch (Throwable t){
                logger.error("clustor relselect fail reason is :"+t.getMessage() +" if can not slove ,you can set cluster.availablecheck=false in url",t);
            }
        }
        return invoker;
    } 

doSelect逻辑如下:

1 Dubbo提供了多种均衡策略,缺省为随机调用,但是如果集群的数量为2,那么将退化成轮询。——(面试题:dubbo的负载均衡策略是怎么样的?)

2 当传入invokers参数大于两个,执行负载均衡选择出一个invoker;

3 如果invoked包含该invoker,执行重选。如果重选成功,赋值;如果重选出为null,则根据上一次选择invoker的位置,选择invokers列表中的下一个节点。
TAG 1.2.1.1 loadBalance.select

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

image-20211028210515937

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

image-20211028210547504

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

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

image-20211028210646717

总结:

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

image-20211028210800309

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

2 在 Router中,将上一步的全部 invokers挑选出能正常执行的 invokers

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

注意:

到此为止,只是loadbalance选出一个可以执行的invoker。具体的执行invoker,其中涉及的通信、连接等,详细见[第五章]

posted @ 2023-03-13 14:33  LeasonXue  阅读(91)  评论(0)    收藏  举报