dubbo源码:dubbo配置集群容群优先级配置原理

1 集群容错模式配置优先级配置
  • 在服务端和消费端都配置集群容错模式时,以消费端配置优先级最高
  • 若消费端没有配置集群容错模式而服务端配置了集群容错模式,则使用服务端的集群容错模式

以如下配置为例,主要探究一下上面优先级配置,如服务端和消费端都配置了相同的参数dubbo怎么做相应的处理。

<dubbo:service interface="service.DemoService" ref="demoService" protocol="dubbo" group="dubbo_test" cluster="mergeable">

<dubbo:reference id="permissionService" interface="service.DemoService" group="dubbo_test">
        <dubbo:parameter key="invoker.listener" value="invokerListenerTest"/>
</dubbo:reference>
2.配置原理

消费端启动过程在refer服务过程中会创建RegistryDirectory实例directory,同时directory向注册中心发起订阅,即向服务器端发送订阅请求,此时Zookeeper会创建订阅服务节点,然后进行通知并刷新重建invoker实例。

2.1 原理初探:Directory实例的overrideDirectoryUrl属性

此时服务端启动成功,消费端debug启动,直接看RegistryProtocol的doRefer方法:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
        if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
                && url.getParameter(Constants.REGISTER_KEY, true)) {
            registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                    Constants.CHECK_KEY, String.valueOf(false)));
        }
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                Constants.PROVIDERS_CATEGORY 
                + "," + Constants.CONFIGURATORS_CATEGORY 
                + "," + Constants.ROUTERS_CATEGORY));
        // 这里会调用cluster的适配器code
        return cluster.join(directory);
}

其中Cluster的适配器code如下:

package com.alibaba.dubbo.rpc.cluster;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Cluster$Adpative implements com.alibaba.dubbo.rpc.cluster.Cluster {
  
  public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.cluster.Directory {
    
    if (arg0 == null) 
      throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
    
    if (arg0.getUrl() == null) 
      throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
    
    com.alibaba.dubbo.common.URL url = arg0.getUrl();
    String extName = url.getParameter("cluster", "failover");
    
    if(extName == null) 
      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
    
    com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
    return extension.join(arg0);
  }
}

doRefer方法中会调用Cluster的适配器code,即会最终会调用code中的下面一行代码:

directory.getUrl().getParameter("cluster")

再看 directory.getUrl() 返回的是Directory实例的overrideDirectoryUrl属性:

public URL getUrl() {
    	return this.overrideDirectoryUrl;
}
2.2 ClusterUtils进行消费端和远程参数合并,同时更新providerUrl,并对Directory实例的overrideDirectoryUrl属性进行更新

重点分析一下RegistryProtocol的doRefer方法中的一行,

directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                Constants.PROVIDERS_CATEGORY 
                + "," + Constants.CONFIGURATORS_CATEGORY 
                + "," + Constants.ROUTERS_CATEGORY));

即directory进行订阅subscribeUrl,此时subscribeUrl的值是:

consumer://192.168.1.102/service.DemoService?application=dubbo-consumer&category=providers,configurators,routers&dubbo=2.5.0&group=dubbo_test&interface=service.DemoService&invoker.listener=invokerListenerTest&methods=getPermission&organization=dubbox&owner=programmer&pid=34308&side=consumer&timestamp=1558369013207

接下来的调用链:

RegistryDirectory的subscribe方法 --> FailbackRegistry的subscribe方法 --> ZookeeperRegistry的doSubscribe方法 --> FailbackRegistry的notify方法 --> RegistryDirectory的notify方法

这里直接看RegistryDirectory的notify方法:

public synchronized void notify(List<URL> urls) {
  // 省略大部分代码......
  // providers
  refreshInvoker(invokerUrls);
}

/**
**invokerUrls size是1,值:dubbo://10.88.97.9:20880/service.DemoService?anyhost=true&application=debbo-provider&cluster=mergeable&dubbo=2.8.4&generic=false&group=dubbo_test&interface=service.DemoService&methods=getPermission&organization=dubbox&owner=programmer&pid=34787&side=provider&timestamp=1558403615130
**/
private void refreshInvoker(List<URL> invokerUrls){
  //......
  Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 将URL列表转成Invoker列表
  Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
  //......
}

// 将urls转成invokers,如果url已经被refer过,不再重新引用。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
  Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
  if(urls == null || urls.size() == 0){
    return newUrlInvokerMap;
  }
  
  Set<String> keys = new HashSet<String>();
  // 获取消费端的protocol key,其中queryMap存储的是消费端配置的参数,此时无"protocol" key,即queryProtocols=null
  String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); // "protocol" key
  for (URL providerUrl : urls) {
    //如果reference端配置了protocol,则只选择匹配的protocol
    if (queryProtocols != null && queryProtocols.length() >0) {
      boolean accept = false;
      String[] acceptProtocols = queryProtocols.split(",");
      for (String acceptProtocol : acceptProtocols) {
        if (providerUrl.getProtocol().equals(acceptProtocol)) {
          accept = true;
          break;
        }
      } // for
      if (!accept) {
        continue;
      }
    }
    
    // 若协议头是"empty"开头,则进入到下一次for循环
    if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
      continue;
    }
    
    if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
      logger.error(......);
      continue;
    }
    
    // 合并URL,这里是重点
    URL url = mergeUrl(providerUrl);
    
    String key = url.toFullString(); // URL参数是排序的
    if (keys.contains(key)) { // 重复URL
      continue;
    }
    keys.add(key);
    // 缓存key为没有合并消费端参数的URL,不管消费端如何合并参数,如果服务端URL发生变化,则重新refer
    Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
    Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
    
    if (invoker == null) { // 缓存中没有,重新refer
      boolean enabled = true;
      if (url.hasParameter(Constants.DISABLED_KEY)) {
        enabled = ! url.getParameter(Constants.DISABLED_KEY, false);
      } else {
        enabled = url.getParameter(Constants.ENABLED_KEY, true);
      }
      if (enabled) {
        // 重新refer
        invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url,providerUrl);
      }
      if (invoker != null) { // 将新的引用放入缓存
        newUrlInvokerMap.put(key, invoker);
      }
    }else {
      newUrlInvokerMap.put(key, invoker);
    }
  } // for
  keys.clear();
  return newUrlInvokerMap;
}

// 合并url参数 顺序为override > -D >Consumer > Provider
private URL mergeUrl(URL providerUrl){
  // 合并消费端参数
  providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); 
  
  List<Configurator> localConfigurators = this.configurators; // local reference
  if (localConfigurators != null && localConfigurators.size() > 0) {
    for (Configurator configurator : localConfigurators) {
      providerUrl = configurator.configure(providerUrl);
    }
  }
  // 不检查连接是否成功,总是创建Invoker!设置check key为false,该值表示启动时检查提供者是否存在,true报错,false忽略
  providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); 
  
  //合并提供者参数。这里更新了overrideDirectoryUrl,另外directoryUrl 与 override 合并是在notify的最后,这里不能够处理
  this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters());
  
  // 设置path
  if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0) && "dubbo".equals(providerUrl.getProtocol())) { // 兼容1.0
    String path = directoryUrl.getParameter(Constants.INTERFACE_KEY); // interface key
    if (path != null) {
      int i = path.indexOf('/');
      if (i >= 0) {
        path = path.substring(i + 1);
      }
      i = path.lastIndexOf(':');
      if (i >= 0) {
        path = path.substring(0, i);
      }
      providerUrl = providerUrl.setPath(path);
    }
  }
  return providerUrl;
}

上面进行完消费端参数合并之后,providerUrl的值是:

dubbo://10.88.97.9:20880/service.DemoService?anyhost=true&application=dubbo-consumer&cluster=mergeable&dubbo=2.8.4&generic=false&group=dubbo_test&interface=service.DemoService&invoker.listener=invokerListenerTest&methods=getPermission&organization=dubbox&owner=programmer&pid=35779&side=consumer&timestamp=1558414726755

这里providerUrl已经包含了服务端配置的"cluster"的key,其value是"mergeable",下面再看一下ClusterUtils.mergeUrl方法,这里做了合并消费端参数:

public class ClusterUtils {
    
    public static URL mergeUrl(URL remoteUrl, Map<String, String> localMap) {
        Map<String, String> map = new HashMap<String, String>();
        Map<String, String> remoteMap = remoteUrl.getParameters();
        
        
        if (remoteMap != null && remoteMap.size() > 0) {
            // map中首先放置远程参数
            map.putAll(remoteMap);
            
            //线程池配置不使用提供者的
            map.remove(Constants.THREAD_NAME_KEY);
            map.remove(Constants.CORE_THREADS_KEY);
            map.remove(Constants.THREADS_KEY);
            map.remove(Constants.QUEUES_KEY);
            map.remove(Constants.ALIVE_KEY);
        }
        
        if (localMap != null && localMap.size() > 0) {
           // map中再放置本地参数,若存在和远程冲突的key值,则覆盖
            map.putAll(localMap);
        }
        if (remoteMap != null && remoteMap.size() > 0) { 
            // 版本号使用提供者的
            String dubbo = remoteMap.get(Constants.DUBBO_VERSION_KEY);
            if (dubbo != null && dubbo.length() > 0) {
                map.put(Constants.DUBBO_VERSION_KEY, dubbo);
            }
            String version = remoteMap.get(Constants.VERSION_KEY);
            if (version != null && version.length() > 0) {
                map.put(Constants.VERSION_KEY, version);
            }
            String group = remoteMap.get(Constants.GROUP_KEY);
            if (group != null && group.length() > 0) {
                map.put(Constants.GROUP_KEY, group);
            }
            String methods = remoteMap.get(Constants.METHODS_KEY);
            if (methods != null && methods.length() > 0) {
                map.put(Constants.METHODS_KEY, methods);
            }
            // 合并filter和listener
            String remoteFilter = remoteMap.get(Constants.REFERENCE_FILTER_KEY);
            String localFilter = localMap.get(Constants.REFERENCE_FILTER_KEY);
            if (remoteFilter != null && remoteFilter.length() > 0
                    && localFilter != null && localFilter.length() > 0) {
                localMap.put(Constants.REFERENCE_FILTER_KEY, remoteFilter + "," + localFilter);
            }
            String remoteListener = remoteMap.get(Constants.INVOKER_LISTENER_KEY);
            String localListener = localMap.get(Constants.INVOKER_LISTENER_KEY);
            if (remoteListener != null && remoteListener.length() > 0
                    && localListener != null && localListener.length() > 0) {
                localMap.put(Constants.INVOKER_LISTENER_KEY, remoteListener + "," + localListener);
            }
        }
        return remoteUrl.addParameters(map);
    }

    private ClusterUtils() {}
    
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bboyzqh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值