dubbo源码解析(9)dubbo集群容错

56 篇文章 2 订阅

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

引用dubbo官方的一个图

 

  从consumer的调用开始追踪源码

由于consumer端的注入是一个动态代理对象,所以会进入InvokerInvocationHandler#invoke方法

关注

return invoker.invoke(new RpcInvocation(method, args)).recreate();

这里的invoker是一个MockClusterInvoker  进入他的invoke方法

public Result invoke(Invocation invocation) throws RpcException {
   Result result = null;
       
       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);

。。。。。。

这里面Invocation中封装了调用的信息

invoker对象=failoverClusterInvoker

failoverClusterInvoker的invoker方法在abstractClusterInvoker中

public Result invoke(final Invocation invocation) throws RpcException {

    checkWheatherDestoried();

    LoadBalance loadbalance;
    
    List<Invoker<T>> invokers = list(invocation);
    if (invokers != null && invokers.size() > 0) {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
    } else {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    return doInvoke(invocation, invokers, loadbalance);
}
List<Invoker<T>> invokers = list(invocation);这句话进入
。。。。
   List<Invoker<T>> invokers = directory.list(invocation);
   。。。
这里的directory要重点说明下,从从第一张图上

public List<Invoker<T>> list(Invocation invocation) throws RpcException {
    if (destroyed){
        throw new RpcException("Directory already destroyed .url: "+ getUrl());
    }
    List<Invoker<T>> invokers = doList(invocation);
    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)) {
                    invokers = router.route(invokers, getConsumerUrl(), invocation);
                }
            } catch (Throwable t) {
                logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
            }
        }
    }
    return invokers;
}

Directory接口有两个实现StaticDirectory和RegistryDirectory

前者和静态注册有关系,我们重点关注RegistryDirectory

RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener

RegistryDirectory实现了NotifyListener接口,我们就可以知道RegistryDirectory会在zookeeper的服务发生变化时进行通知

methodInvokerMap这个成员变量中封装了所有的服务提供者

RegistryDirectory#notify中有一行代码

refreshInvoker(invokerUrls);这部分代码之前讲过,会根据提供者得变化更新提供者

-------------------------

回到list方法List<Router> localRouters = this.routers; 是路由相关的

Router是一个路由组件

这个routers中装的是一个MockInvokersSelector 实现了Router接口

MockInvokersSelector中的
return getNormalInvokers(invokers);

这个方法会根据路由配置(通常是控制台配置)返回可用的invokers

回到list方法
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
        .getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));

这个loadbalance是 RoundRobinBalance

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

   /**
    * select one invoker in list.
    * 
    * @param invokers invokers.
    * @param url refer url
    * @param invocation invocation.
    * @return selected invoker.
    */
    @Adaptive("loadbalance")
   <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

LoadBalance是一个组件,默认是RandomLoadBalance

进入FailoverClusterInvoker的doinvoker方法

这里说明一下Cluster

@SPI(FailoverCluster.NAME)
public interface Cluster {

    /**
     * Merge the directory invokers to a virtual invoker.
     * 
     * @param <T>
     * @param directory
     * @return cluster invoker
     * @throws RpcException
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

这是一个集群容错模式,默认是FailoverCluster,上一篇讲到RegistryProtocol#doRefer中有一个

 cluster.join(directory);

FailoverCluster#join方法默认返回 new FailoverClusterInvoker<T>(directory);

我们进入FailoverClusterInvoker的doInvoke方法

List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
   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++) {
       //重试时,进行重新选择,避免重试时invoker列表已发生变化.
       //注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
       if (i > 0) {
          checkWheatherDestoried();
          copyinvokers = list(invocation);
          //重新检查一下
          checkInvokers(copyinvokers, invocation);
       }
       Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
       invoked.add(invoker);
       RpcContext.getContext().setInvokers((List)invoked);
       try {
           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;

for循环中会进行重试。。

看到Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);这句话

Invoker<T> invoker = doselect(loadbalance, invocation, invokers, selected);
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

由于我们的loadBalance是RoundRobinLoadBalance

select 方法在abstractLoadBalance中

public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
  。。。。
    return doSelect(invokers, url, invocation);
}
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    int length = invokers.size(); // 总个数
    int maxWeight = 0; // 最大权重
    int minWeight = Integer.MAX_VALUE; // 最小权重
    for (int i = 0; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        maxWeight = Math.max(maxWeight, weight); // 累计最大权重
        minWeight = Math.min(minWeight, weight); // 累计最小权重
    }
    if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样
        AtomicPositiveInteger weightSequence = weightSequences.get(key);
        if (weightSequence == null) {
            weightSequences.putIfAbsent(key, new AtomicPositiveInteger());
            weightSequence = weightSequences.get(key);
        }
        int currentWeight = weightSequence.getAndIncrement() % maxWeight;
        List<Invoker<T>> weightInvokers = new ArrayList<Invoker<T>>();
        for (Invoker<T> invoker : invokers) { // 筛选权重大于当前权重基数的Invoker
            if (getWeight(invoker, invocation) > currentWeight) {
                weightInvokers.add(invoker);
            }
        }
        int weightLength = weightInvokers.size();
        if (weightLength == 1) {
            return weightInvokers.get(0);
        } else if (weightLength > 1) {
            invokers = weightInvokers;
            length = invokers.size();
        }
    }
    AtomicPositiveInteger sequence = sequences.get(key);
    if (sequence == null) {
        sequences.putIfAbsent(key, new AtomicPositiveInteger());
        sequence = sequences.get(key);
    }
    // 取模轮循
    return invokers.get(sequence.getAndIncrement() % length);
}

以上是RoundRobinLoadBalance的逻辑

回到FailoverClusterInvoker#doInvoke方法的

Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);

这里就会返回一个合适的invoker

从上面分析我们就把Direcotry,LoadBalance,Cluster,Router四个组件串起来了

总结下来就是在Cluster中的通过找到Direcotry中有效的invokers,然后根据Router规则过滤去一部分invokers,然后在根据loadBalance从过滤后的invokers找到一个合适invoker

 

 

----------------------------------------------------------

上述分析我们都是针对正常调用的情况,dubbo提供了容错和屏蔽两种服务降级得方式

在MockClusterInvoker#invoke中

首先看第二个else if 分支
else {
       //fail-mock
       try {
          result = this.invoker.invoke(invocation);
       }catch (RpcException e) {
   if (e.isBiz()) {
      throw e;
   } else {
      if (logger.isWarnEnabled()) {
              logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " +  directory.getUrl(), e);
           }
      result = doMockInvoke(invocation, e);
   }
}

这是一个服务容错,如果调用抛异常了就会进入catch分支

result = doMockInvoke(invocation, e);

看doMockInvoke中的

List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);

进入List<Invoker<T>> invokers = directory.list(invocation);

invokers = router.route(invokers, getConsumerUrl(), invocation);
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
      URL url, final Invocation invocation) throws RpcException {
   if (invocation.getAttachments() == null) {
      return getNormalInvokers(invokers);
   } else {
      String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
      if (value == null) 
         return getNormalInvokers(invokers);
      else if (Boolean.TRUE.toString().equalsIgnoreCase(value)){
         return getMockedInvokers(invokers);
      } 
   }
   return invokers;
}

这里因为是容错,所以走else分支

看这行代码

else if (Boolean.TRUE.toString().equalsIgnoreCase(value)){
   return getMockedInvokers(invokers);
} 
private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
   if (! hasMockProviders(invokers)){
      return null;
   }
   List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
   for (Invoker<T> invoker : invokers){
      if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)){
         sInvokers.add(invoker);
      }
   }
   return sInvokers;
}

这里会走 if (! hasMockProviders(invokers)){ return null; }分支,所以返回的invokers = null

所以回到MockClusterInvoker#doMockInvoke中

mockInvokers =null

所以

if (mockInvokers == null || mockInvokers.size() == 0){
   minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());

minvoker是自己创建的一个MockInvoker

然后进入MockInvoker#invoke

关注这句话

if (StringUtils.isBlank(mock)){
   mock = getUrl().getParameter(Constants.MOCK_KEY);
}

这个是在控制台上配置的

mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
mock = mock.replace('`', '"');
try {
    Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
    Object value = parseMockValue(mock, returnTypes);
    return new RpcResult(value);

这里就会直接返回这个配置的值

-----------------------------------

再看下屏蔽

else if (value.startsWith("force")) {
   if (logger.isWarnEnabled()) {
      logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " +  directory.getUrl());
   }
   //force:direct mock
   result = doMockInvoke(invocation, null);

屏蔽没有去调用服务端代码,而是直接执行doMockInvoke,这部分我们在容错已经分析过了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值