dubbo cluster--订阅

先复习下架构篇,对概念,类结构以及交互过程有个直观性认识。
《dubbo cluster–架构》

相关概念

  1. Category Category就类似是一个订阅主题,主要包括providers, consumers, routers, configurators四类。每个服务都会有这四类事件,分别指服务提供者,服务消费者,服务路由规则,服务配置变更四类事件。
  2. Provider 服务提供商,服务的不同实现。
  3. Consumer 服务消费者,使用服务的各种客户端。Consumer会指定消费哪个Provider(对provider的各种维度侧写,主要是group,methods两个配置项),由dubbo完成对指定Provider的调用。

订阅和发现过程


上图是一个订阅发布过程的概念模型,3个步骤并不是严格串行的,其实3个过程是异步解耦的,只是协作图上必须要有顺序。

客户端启动时会向Registry订阅服务事件,服务端启动时会向Registry注册服务,相应的Registry在注册完成后会通知客户端,客户端会在本地做provider(invoker)映射。

订阅过程

客户端Reference通过Protocol对象引用Invoker。
《dubbo spi》

//ReferenceConfig#loadRegistry方法。这个方法用来获取注册中心相关信息
//并对注册中心协议做隐藏处理,这里注意下,会面会解释
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);

//ReferenceConfig#createProxy方法。该方法生成一个暴露给消费者的远程服务引用(dubbo proxy),proxy里持有invoker对象,客户端与服务端的远程交互细节就是通过invoker来完成。
invoker = refprotocol.refer(interfaceClass, url);

loadRegistry方法将注册中心url的实际协议隐藏,暴露协议值为‘registry’,所以扩展点基于动态规则将会适配RegistryProtocol。

以下抽取RegistryProtocol#refer的关键逻辑,使用“图步骤x”映射架构篇里协作图上的具体步骤

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { //暴露真正的注册协议,以动态匹配具体的注册中心实现 url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); //架构篇图步骤1,后面所有的图都是指架构篇协作图 Registry registry = registryFactory.getRegistry(url); // group="a,b" or group="*" if (//reference指定多个组别) { return doRefer( getMergeableCluster(), registry, type, url ); }
        return doRefer(cluster, registry, type, url);
    }

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) { //图步骤2 RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url); //protocolRegistryProtocol的实例变量,也是通过扩展点注入进来,其实就是Adaptive directory.setRegistry(registry); directory.setProtocol(protocol); URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters()); //图步骤3 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY)); //图步骤6 return cluster.join(directory); }

RegistryFactory也是个spi服务,依据具体的注册中心协议初始化一个Registry,并且set给RegistryDirectory。最后通过RegistryDirectory#subscribe订阅事件推送。

public void subscribe(URL url) {
    this.subscribeUrl = url;
    registry.subscribe(url, this);
}

Registry#subscribe方法,先看下接口定义

void subscribe(URL url, NotifyListener listener);

该方法第二个参数是一个NotifyListener,RegistryDirectory继承了它,在RegistryDirectory#subscribe中将this对象就直接传递进去,由RegistryDirectory接受事件推送。

Zookeeper注册中心

以下以zookeeper注册中心举例说明Registry#subscribe。

Zookeeper对每个provider的每类主题都单独写一个文件,路径名:group/interface name/category/url(protocol://username:password@host/path),category后的url为每个provider的具体地址信息,由provider上报时提供。

dubbo在zookeeper生成的文件结构如下图
dubbo cluster--订阅_第1张图片

每个consumer会watch自己使用服务的对应目录的childChnaged时间,一般是providers,configurators和routers。目录下有写入或者删除事件时会通知监听者,也就是RegistryDirectory,通知事件中包含当前监听目录下所有的path信息,这些path信息由provider在初始化时写入。

protected void doSubscribe(final URL url, final NotifyListener listener) {
      //省略了订阅所有provider事件部分逻辑
      List<URL> urls = new ArrayList<URL>();
      for (String path : toCategoriesPath(url)) {
          ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
          //省略部分非主要逻辑
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                 listeners.putIfAbsent(listener, new ChildListener() {
                     public void childChanged(String parentPath, List<String> currentChilds) {

                          ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                     }
                });
                zkListener = listeners.get(listener);
            }
            //zookeeper下创建目录
            zkClient.create(path, true);
            //图步骤4
            List<String> children = zkClient.addChildListener(path, zkListener);
            if (children != null) {
                 urls.addAll(toUrlsWithEmpty(url, path, children));
            }
       }
       notify(url, listener, urls);

    }

private String[] toCategoriesPath(URL url) {
        String[] categroies;
        //省略了订阅所有事件部分逻辑
        else {
            categroies = url.getParameter(Constants.CATEGORY_KEY, new String[] {Constants.DEFAULT_CATEGORY});
        }
        String[] paths = new String[categroies.length];
        for (int i = 0; i < categroies.length; i ++) {
            paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categroies[i];
        }
        return paths;
    }

一个directory可以同时订阅多个主题,toCategoriesPath会为每个主题(category)都生成一个路径。客户端会在zookeeper下写入目录,并订阅目录下的写入及变更事件。

发布过程

最后看一下RegistryDirectory#notify方法,Zookeeper推送的事件最终会由这个方法接受。

public synchronized void notify(List<URL> urls) {
//精简了router和configurator逻辑
        List<URL> invokerUrls = new ArrayList<URL>();
        for (URL url : urls) {
            String protocol = url.getProtocol();
            String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            if (Constants.PROVIDERS_CATEGORY.equals(category)) {
                invokerUrls.add(url);
            } 
        }
//刷新客户端的provider(invoker)数据
        refreshInvoker(invokerUrls);
    }

refreshInvoker方法会刷新客户端的invoker数据,移除失效的,加入新增的。

RegistryDirectory里有两种invoker map,一种是urlInvokerMap,另一种是methodInvokerMap。前者主要是用来每次推送时和最新invoker列表做对比,清除下线的invoker信息。

另一个,methodInvokerMap,它里面保存的数据最终会作为directory对外暴露的invoker。methodInvokerMap是将invoker按照method分组,可以通过methods配置项声明provider指定给哪个方法使用,没有指定methods的provider则对这个接口的所有消费者可见。

你可能感兴趣的:(源码,DUBBO,服务治理)