• 第三章 dubbo源码解析目录


    7.6 服务远程暴露 - 注册服务到zookeeper

    为了安全:服务启动的ip全部使用10.10.10.10

    远程服务的暴露总体步骤:

    • 将ref封装为invoker
    • 将invoker转换为exporter
    • 启动netty
    • 注册服务到zookeeper
    • 订阅
    • 返回新的exporter实例

    7.4 服务远程暴露 - 创建Exporter与启动netty服务端中,实现了前三步,本节实现第四步:注册服务到zk。总体代码如下:RegistryProtocol.export(final Invoker<T> originInvoker)

    1         final Registry registry = getRegistry(originInvoker);//创建ZookeeperRegistry实例:创建CuratorClient,并启动会话。
    2         final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);//获取真正要注册在zk上的url
    3         registry.register(registedProviderUrl);//创建节点(即注册服务到zk上)

    说明:

    • 第一句代码用来创建ZookeeperRegistry实例:创建CuratorClient,并启动会话。
    • 第二句代码获取真正要注册在zk上的url
    • 第三句代码实现创建节点(即注册服务到zk上)

    一  创建ZookeeperRegistry实例

    1  RegistryProtocol.getRegistry(final Invoker<?> originInvoker)

    复制代码
     1     /**
     2      * 根据invoker的地址获取registry实例
     3      */
     4     private Registry getRegistry(final Invoker<?> originInvoker) {
     5         URL registryUrl = originInvoker.getUrl();
     6         if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
     7             String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);//zookeeper
     8             registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
     9         }
    10         return registryFactory.getRegistry(registryUrl);
    11     }
    复制代码

    首先对originInvoker中的url进行处理:

    • 将协议换成zookeeper
    • 去掉registry=zookeeper的参数

    来看一下originInvoker的url:(解码后的)

    registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&export=dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2791&side=provider&timestamp=1507262031554&pid=2791&registry=zookeeper&timestamp=1507262031521

    说明:

    • 第一个红色部分代表协议:zookeeper
    • 第二个红色部分是export参数
    • 第三个红色部分是registry=zookeeper

    经过处理之后的registryUrl为:

    zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&export=dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2791&side=provider&timestamp=1507262031554&pid=2791&timestamp=1507262031521

    之后使用注册工厂来创建注册中心。

    2  RegistryFactory$Adaptive.getRegistry(com.alibaba.dubbo.common.URL registryUrl)

    复制代码
     1 public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
     2     public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
     3         if (arg0 == null)
     4             throw new IllegalArgumentException("url == null");
     5         com.alibaba.dubbo.common.URL url = arg0;
     6         String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );//zookeeper
     7         if(extName == null)
     8             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
     9         com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
    10         return extension.getRegistry(arg0);
    11     }
    12 }
    复制代码

    这里获取到的extension是ZookeeperRegistryFactory,之后,使用ZookeeperRegistryFactory进行Registry的创建。首先来看一下ZookeeperRegistryFactory的继承图:

    getRegistry方法在ZookeeperRegistryFactory的父类AbstractRegistryFactory中。

    3  AbstractRegistryFactory.getRegistry(URL registryUrl)

    复制代码
     1     public Registry getRegistry(URL url) {
     2         url = url.setPath(RegistryService.class.getName())
     3                 .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
     4                 .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
     5         String key = url.toServiceString();
     6         // 锁定注册中心获取过程,保证注册中心单一实例
     7         LOCK.lock();
     8         try {
     9             Registry registry = REGISTRIES.get(key);
    10             if (registry != null) {
    11                 return registry;
    12             }
    13             registry = createRegistry(url);
    14             if (registry == null) {
    15                 throw new IllegalStateException("Can not create registry " + url);
    16             }
    17             REGISTRIES.put(key, registry);
    18             return registry;
    19         } finally {
    20             // 释放锁
    21             LOCK.unlock();
    22         }
    23     }
    复制代码

    流程:

    • 先处理url,之后获取Registry的key,然后根据该key从Map<String, Registry> REGISTRIES注册中心集合缓存中获取Registry,如果有,直接返回,如果没有,创建Registry,之后存入缓存,最后返回。

    首先处理传入的registryUrl:

    • 设置:path=com.alibaba.dubbo.registry.RegistryService
    • 添加参数:interface=com.alibaba.dubbo.registry.RegistryService
    • 去除export参数

    最终得到的registryUrl如下:

    zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=2791&timestamp=1507262031521

    之后,很具上述的registryUrl创建Registry的key,该{ key : Registry }最终会被存储在Map<String, Registry> REGISTRIES注册中心集合(该属性是ZookeeperRegistryFactory父类AbstractRegistryFactory的一个属性)中。

    根据registryUrl创建Registry的key:url.toServiceString()

    复制代码
     1     public String toServiceString() {
     2         return buildString(true, false, true, true);
     3     }
     4 
     5     private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, boolean useService, String... parameters) {
     6         StringBuilder buf = new StringBuilder();
     7         if (protocol != null && protocol.length() > 0) {  //protocol://
     8             buf.append(protocol);
     9             buf.append("://");
    10         }
    11         if (appendUser && username != null && username.length() > 0) {  //protocol://username:password@host:port/group/interface{path}:version/parameters
    12             buf.append(username);
    13             if (password != null && password.length() > 0) {
    14                 buf.append(":");
    15                 buf.append(password);
    16             }
    17             buf.append("@");
    18         }
    19         String host;
    20         if (useIP) {
    21             host = getIp();
    22         } else {
    23             host = getHost();
    24         }
    25         if (host != null && host.length() > 0) {
    26             buf.append(host);
    27             if (port > 0) {
    28                 buf.append(":");
    29                 buf.append(port);
    30             }
    31         }
    32         String path;
    33         if (useService) {
    34             path = getServiceKey();
    35         } else {
    36             path = getPath();
    37         }
    38         if (path != null && path.length() > 0) {
    39             buf.append("/");
    40             buf.append(path);
    41         }
    42         if (appendParameter) {
    43             buildParameters(buf, true, parameters);
    44         }
    45         return buf.toString();
    46     }
    47 
    48     public String getServiceKey() {
    49         String inf = getServiceInterface();//先获取interface参数,如果没有的话,取path的值,这里都是com.alibaba.dubbo.registry.RegistryService
    50         if (inf == null) return null;
    51         StringBuilder buf = new StringBuilder();
    52         String group = getParameter(Constants.GROUP_KEY);
    53         if (group != null && group.length() > 0) {
    54             buf.append(group).append("/"); //interfacegroup
    55         }
    56         buf.append(inf);
    57         String version = getParameter(Constants.VERSION_KEY);
    58         if (version != null && version.length() > 0) {
    59             buf.append(":").append(version);
    60         }
    61         return buf.toString();
    62     }
    复制代码

    最终得到的应该是这样的形式:protocol://username:password@host:port/group/interface{path}:version?key1=value1&key2=value2...。

    这里key=zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService

    之后来到了真正创建Registry的地方。

    复制代码
     1 public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
     2     private ZookeeperTransporter zookeeperTransporter;
     3 
     4     public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
     5         this.zookeeperTransporter = zookeeperTransporter;
     6     }
     7 
     8     public Registry createRegistry(URL url) {
     9         return new ZookeeperRegistry(url, zookeeperTransporter);
    10     }
    11 }
    复制代码

    这里的zookeeperTransporter对象是一个com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter$Adaptive对象。

    在创建ZookeeperRegistry之前来看一下其继承图:

    new ZookeeperRegistry(registryUrl, ZookeeperTransporter$Adaptive对象)

    复制代码
     1     private final static int DEFAULT_ZOOKEEPER_PORT = 2181;
     2     private final static String DEFAULT_ROOT = "dubbo";
     3     private final String root;
     4     private final Set<String> anyServices = new ConcurrentHashSet<String>();
     5     private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>();
     6     private final ZookeeperClient zkClient;
     7 
     8     public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
     9         super(url);
    10         if (url.isAnyHost()) {
    11             throw new IllegalStateException("registry address == null");
    12         }
    13         String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);//dubbo
    14         if (!group.startsWith(Constants.PATH_SEPARATOR)) {
    15             group = Constants.PATH_SEPARATOR + group;
    16         }
    17         this.root = group;// /dubbo
    18         zkClient = zookeeperTransporter.connect(url);//创建zk客户端,启动会话
    19         zkClient.addStateListener(new StateListener() {//监听重新连接成功事件,重新连接成功后,之前已经完成注册和订阅的url要重新进行注册和订阅(因为临时节点可能已经跪了)
    20             public void stateChanged(int state) {
    21                 if (state == RECONNECTED) {
    22                     try {
    23                         recover();
    24                     } catch (Exception e) {
    25                         logger.error(e.getMessage(), e);
    26                     }
    27                 }
    28             }
    29         });
    30     }
    复制代码

    new FailbackRegistry(registryUrl)

    复制代码
     1     private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));
     2     // 失败重试定时器,定时检查是否有请求失败,如有,无限次重试
     3     private final ScheduledFuture<?> retryFuture;
     4     private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
     5     private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();
     6     private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
     7     private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
     8     private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();
     9     private AtomicBoolean destroyed = new AtomicBoolean(false);
    10 
    11     public FailbackRegistry(URL url) {
    12         super(url);
    13         int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);//5*1000
    14         this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
    15             public void run() {
    16                 // 检测并连接注册中心
    17                 try {
    18                     retry();
    19                 } catch (Throwable t) { // 防御性容错
    20                     logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
    21                 }
    22             }
    23         }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
    24     }
    复制代码

    new AbstractRegistry(registryUrl)

    复制代码
     1     // URL地址分隔符,用于文件缓存中,服务提供者URL分隔
     2     private static final char URL_SEPARATOR = ' ';
     3     // URL地址分隔正则表达式,用于解析文件缓存中服务提供者URL列表
     4     private static final String URL_SPLIT = "\\s+";
     5     // 本地磁盘缓存,其中特殊的key值.registies记录注册中心列表,其它均为notified服务提供者列表
     6     private final Properties properties = new Properties();
     7     // 文件缓存定时写入
     8     private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
     9     //是否是同步保存文件
    10     private final boolean syncSaveFile;
    11     // 本地磁盘缓存文件
    12     private File file;
    13     private final AtomicLong lastCacheChanged = new AtomicLong();
    14     private final Set<URL> registered = new ConcurrentHashSet<URL>();//已经注册的url集合
    15     private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();//已经订阅的<URL, Set<NotifyListener>>
    16     private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();//已经通知的<URL, Map<String, List<URL>>>
    17     private URL registryUrl;//注册url
    18     private AtomicBoolean destroyed = new AtomicBoolean(false);
    19 
    20     public AbstractRegistry(URL url) {
    21         setUrl(url);
    22         // 启动文件保存定时器
    23         syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
    24         String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");
    25         File file = null;
    26         if (ConfigUtils.isNotEmpty(filename)) {
    27             file = new File(filename);
    28             if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
    29                 if (!file.getParentFile().mkdirs()) {//创建文件所在的文件夹 /Users/jigangzhao/.dubbo/
    30                     throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
    31                 }
    32             }
    33         }
    34         this.file = file;
    35         loadProperties();
    36         notify(url.getBackupUrls());
    37     }
    复制代码

    先简单的总结一下:父子三代分别做的事情:

    • AbstractRegistry主要用来维护缓存文件。
    • FailbackRegistry主要用来做失败重试操作(包括:注册失败/反注册失败/订阅失败/反订阅失败/通知失败的重试);也提供了供ZookeeperRegistry使用的zk重连后的恢复工作的方法。
    • ZookeeperRegistry创建zk客户端,启动会话;并且调用FailbackRegistry实现zk重连后的恢复工作。

    先看AbstractRegistry

    • 设置属性registryUrl=url:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=4685&timestamp=1507286468150
    • 创建文件/Users/jigangzhao/.dubbo/dubbo-registry-10.211.55.5.cache的文件夹/Users/jigangzhao/.dubbo
    • 设置属性file:/Users/jigangzhao/.dubbo/dubbo-registry-10.211.55.5.cache文件,该文件存储信息将是这样的:

      com.alibaba.dubbo.demo.DemoService=empty\://10.10.10.10\:20880/com.alibaba.dubbo.demo.DemoService?anyhost\=true&application\=demo-provider&category\=configurators&check\=false&dubbo\=2.0.0&generic\=false&interface\=com.alibaba.dubbo.demo.DemoService&methods\=sayHello&pid\=5259&side\=provider&timestamp\=1507294508053

    • 如果file存在,将file中的内容写入properties属性;既然有读file,那么是什么时候写入file的呢?AbstractRegistry创建了一个含有一个名字为DubboSaveRegistryCache的后台线程的FixedThreadPool,只在在notify(URL url, NotifyListener listener, List<URL> urls)方法中会被调用,我们此处由于ConcurrentMap<URL, Set<NotifyListener>> subscribed为空,所以AbstractRegistry(URL url)中的notify(url.getBackupUrls())不会执行,此处也不会创建文件。
    • 最后是notify(url.getBackupUrls())(TODO 这里后续会写)

    再来看FailbackRegistry:

    只做了一件事,启动了一个含有一个名为DubboRegistryFailedRetryTimer的后台线程的ScheduledThreadPool,线程创建5s后开始第一次执行retry(),之后每隔5s执行一次。来看一下retry()

    复制代码
      1     /**
      2      * 将所有注册失败的url(failedRegistered中的url)进行注册,之后从failedRegistered进行移除;
      3      * 将所有反注册失败的url(failedUnregistered中的url)进行反注册,之后从failedUnregistered进行移除;
      4      * 将所有订阅失败的url(failedSubscribed中的url)进行重新订阅,之后从failedSubscribed进行移除;
      5      * 将所有反订阅失败的url(failedUnsubscribed中的url)进行反订阅,之后从failedUnsubscribed进行移除;
      6      * 将所有通知失败的url(failedNotified中的url)进行通知,之后从failedNotified进行移除;
      7      */
      8     protected void retry() {
      9         if (!failedRegistered.isEmpty()) {
     10             Set<URL> failed = new HashSet<URL>(failedRegistered);
     11             if (failed.size() > 0) {
     12                 if (logger.isInfoEnabled()) {
     13                     logger.info("Retry register " + failed);
     14                 }
     15                 try {
     16                     for (URL url : failed) {
     17                         try {
     18                             doRegister(url);
     19                             failedRegistered.remove(url);
     20                         } catch (Throwable t) { // 忽略所有异常,等待下次重试
     21                             logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     22                         }
     23                     }
     24                 } catch (Throwable t) { // 忽略所有异常,等待下次重试
     25                     logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     26                 }
     27             }
     28         }
     29         if (!failedUnregistered.isEmpty()) {
     30             Set<URL> failed = new HashSet<URL>(failedUnregistered);
     31             if (failed.size() > 0) {
     32                 if (logger.isInfoEnabled()) {
     33                     logger.info("Retry unregister " + failed);
     34                 }
     35                 try {
     36                     for (URL url : failed) {
     37                         try {
     38                             doUnregister(url);
     39                             failedUnregistered.remove(url);
     40                         } catch (Throwable t) { // 忽略所有异常,等待下次重试
     41                             logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     42                         }
     43                     }
     44                 } catch (Throwable t) { // 忽略所有异常,等待下次重试
     45                     logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     46                 }
     47             }
     48         }
     49         if (!failedSubscribed.isEmpty()) {
     50             Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedSubscribed);
     51             for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
     52                 if (entry.getValue() == null || entry.getValue().size() == 0) {
     53                     failed.remove(entry.getKey());
     54                 }
     55             }
     56             if (failed.size() > 0) {
     57                 if (logger.isInfoEnabled()) {
     58                     logger.info("Retry subscribe " + failed);
     59                 }
     60                 try {
     61                     for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
     62                         URL url = entry.getKey();
     63                         Set<NotifyListener> listeners = entry.getValue();
     64                         for (NotifyListener listener : listeners) {
     65                             try {
     66                                 doSubscribe(url, listener);//listener需要一个一个订阅,每订阅一个,就将该listener从当前的url监听列表中移除
     67                                 listeners.remove(listener);
     68                             } catch (Throwable t) { // 忽略所有异常,等待下次重试
     69                                 logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     70                             }
     71                         }
     72                     }
     73                 } catch (Throwable t) { // 忽略所有异常,等待下次重试
     74                     logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     75                 }
     76             }
     77         }
     78         if (!failedUnsubscribed.isEmpty()) {
     79             Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedUnsubscribed);
     80             for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
     81                 if (entry.getValue() == null || entry.getValue().size() == 0) {
     82                     failed.remove(entry.getKey());
     83                 }
     84             }
     85             if (failed.size() > 0) {
     86                 if (logger.isInfoEnabled()) {
     87                     logger.info("Retry unsubscribe " + failed);
     88                 }
     89                 try {
     90                     for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
     91                         URL url = entry.getKey();
     92                         Set<NotifyListener> listeners = entry.getValue();
     93                         for (NotifyListener listener : listeners) {
     94                             try {
     95                                 doUnsubscribe(url, listener);//listener需要一个一个反订阅,每反订阅一个,就将该listener从当前的url监听列表中移除
     96                                 listeners.remove(listener);
     97                             } catch (Throwable t) { // 忽略所有异常,等待下次重试
     98                                 logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
     99                             }
    100                         }
    101                     }
    102                 } catch (Throwable t) { // 忽略所有异常,等待下次重试
    103                     logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
    104                 }
    105             }
    106         }
    107         if (!failedNotified.isEmpty()) {
    108             Map<URL, Map<NotifyListener, List<URL>>> failed = new HashMap<URL, Map<NotifyListener, List<URL>>>(failedNotified);
    109             for (Map.Entry<URL, Map<NotifyListener, List<URL>>> entry : new HashMap<URL, Map<NotifyListener, List<URL>>>(failed).entrySet()) {
    110                 if (entry.getValue() == null || entry.getValue().size() == 0) {
    111                     failed.remove(entry.getKey());
    112                 }
    113             }
    114             if (failed.size() > 0) {
    115                 if (logger.isInfoEnabled()) {
    116                     logger.info("Retry notify " + failed);
    117                 }
    118                 try {
    119                     for (Map<NotifyListener, List<URL>> values : failed.values()) {
    120                         for (Map.Entry<NotifyListener, List<URL>> entry : values.entrySet()) {
    121                             try {
    122                                 NotifyListener listener = entry.getKey();
    123                                 List<URL> urls = entry.getValue();
    124                                 listener.notify(urls);
    125                                 values.remove(listener);
    126                             } catch (Throwable t) { // 忽略所有异常,等待下次重试
    127                                 logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
    128                             }
    129                         }
    130                     }
    131                 } catch (Throwable t) { // 忽略所有异常,等待下次重试
    132                     logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
    133                 }
    134             }
    135         }
    136     }
    复制代码

    最后回到我们的主角:ZookeeperRegistry

    首先是为属性设置root=/dubbo,之后创建zk客户端,启动会话,最后创建了一个StateListener监听器,监听重新连接成功事件,重新连接成功后,之前已经完成注册和订阅的url要重新进行注册和订阅(因为临时节点可能已经跪了)。

    来看创建zk客户端,启动会话的代码,这是此处最核心的部分:

    ZookeeperTransporter$Adaptive.connect(com.alibaba.dubbo.common.URL registryUrl)

    复制代码
     1     public com.alibaba.dubbo.remoting.zookeeper.ZookeeperClient connect(com.alibaba.dubbo.common.URL arg0) {
     2         if (arg0 == null)
     3             throw new IllegalArgumentException("url == null");
     4         com.alibaba.dubbo.common.URL url = arg0;
     5         String extName = url.getParameter("client", url.getParameter("transporter", "zkclient"));//curator
     6         if(extName == null)
     7             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter) name from url(" + url.toString() + ") use keys([client, transporter])");
     8         com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter extension = (com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter.class).getExtension(extName);
     9         return extension.connect(arg0);
    10     }
    复制代码

    这里创建的extension是CuratorZookeeperTransporter实例。

    1 public class CuratorZookeeperTransporter implements ZookeeperTransporter {
    2     public ZookeeperClient connect(URL url) {
    3         return new CuratorZookeeperClient(url);
    4     }
    5 }

    new CuratorZookeeperClient(registryUrl)

    复制代码
     1     private final CuratorFramework client;
     2 
     3     public CuratorZookeeperClient(URL url) {
     4         super(url);
     5         try {
     6             CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
     7                     .connectString(url.getBackupAddress())
     8                     .retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000))
     9                     .connectionTimeoutMs(5000);
    10             String authority = url.getAuthority();
    11             if (authority != null && authority.length() > 0) {
    12                 builder = builder.authorization("digest", authority.getBytes());
    13             }
    14             client = builder.build();
    15             client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
    16                 public void stateChanged(CuratorFramework client, ConnectionState state) {
    17                     if (state == ConnectionState.LOST) {
    18                         CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
    19                     } else if (state == ConnectionState.CONNECTED) {
    20                         CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
    21                     } else if (state == ConnectionState.RECONNECTED) {
    22                         CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
    23                     }
    24                 }
    25             });
    26             client.start();
    27         } catch (Exception e) {
    28             throw new IllegalStateException(e.getMessage(), e);
    29         }
    30     }
    复制代码

    这里首先执行父类AbstractZookeeperClient的构造器来初始化一些参数,之后创建CuratorFramework客户端,然后添加了ConnectionStateListener监听器,监听连接断开/连接成功/重新连接成功事件,之后作出相应的操作(实际上这里只有重新连接成功事件会被处理,而处理器实际上就是ZookeeperRegistry构造器中的那个执行recover()的StateListener),

        protected void stateChanged(int state) {
            for (StateListener sessionListener : getSessionListeners()) {
                sessionListener.stateChanged(state);//此处查找实现类,只有ZookeeperRegistry构造器中的那个StateListener
            }
        }

    最后阻塞,直到创建会话完成。

    来看一下父类AbstractZookeeperClient

    复制代码
    1     private final URL url;
    2     private final Set<StateListener> stateListeners = new CopyOnWriteArraySet<StateListener>();
    3     private final ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners = new ConcurrentHashMap<String, ConcurrentMap<ChildListener, TargetChildListener>>();
    4     private volatile boolean closed = false;
    5 
    6     public AbstractZookeeperClient(URL url) {
    7         this.url = url;
    8     }
    复制代码

    说明:

    • 设置属性url=registryUrl:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=4685&timestamp=1507286468150
    • 创建了一个Set<StateListener> stateListeners,ZookeeperRegistry构造器中的那个执行recover()的StateListener就将会放在这里

    至此,一个完整的ZookeeperRegistry实例就创建完成了,来看一下属性:

    • ZookeeperClient zkClient = CuratorZookeeperClient实例
      • CuratorFramework client:CuratorFrameworkImpl实例
      • String url:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=4685&timestamp=1507286468150
      • Set<StateListener> stateListeners:{ 监听了重连成功事件的执行recover()的StateListener }
    • String root="/dubbo"
    • URL registryUrl = zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=4685&timestamp=1507286468150
    • Set<URL> registered:0//已经注册的url集合,此处为空
    • ConcurrentMap<URL, Set<NotifyListener>> subscribed:0//已经订阅的<URL, Set<NotifyListener>>
    • ConcurrentMap<URL, Map<String, List<URL>>> notified:0//已经通知的<URL, Map<String, List<URL>>>
    • Set<URL> failedRegistered:0//注册失败的url
    • Set<URL> failedUnregistered:0//反注册失败的url
    • ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed:0//订阅失败的url
    • ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed:0//反订阅失败的url
    • ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified:0//通知失败的url
    • ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners:0

    还有一个定时线程:DubboRegistryFailedRetryTimer每隔5s执行一次retry(),进行失败重试。

    最后,该ZookeeperRegistry会存储在ZookeeperRegistry的父类的static属性Map<String, Registry> REGISTRIES中:

    Map<String, Registry> REGISTRIES:{ "zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService" : ZookeeperRegistry实例 }

    二  获取真正要注册到zk的节点url

    1 final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
    复制代码
     1     /**
     2      * 1 获取originInvoker的export参数值:就是providerUrl
     3      * 2 去除providerUrl中所有参数名是"."开头的,然后去除参数monitor
     4      */
     5     private URL getRegistedProviderUrl(final Invoker<?> originInvoker) {
     6         URL providerUrl = getProviderUrl(originInvoker);
     7         //注册中心看到的地址
     8         final URL registedProviderUrl = providerUrl.removeParameters(getFilteredKeys(providerUrl)).removeParameter(Constants.MONITOR_KEY);
     9         return registedProviderUrl;
    10     }
    11 
    12     /**
    13      * 从invoker的URL中的Map<String, String> parameters中获取key为export的地址providerUrl:
    14      */
    15     private URL getProviderUrl(final Invoker<?> origininvoker) {
    16         String export = origininvoker.getUrl().getParameterAndDecoded(Constants.EXPORT_KEY);
    17         if (export == null || export.length() == 0) {
    18             throw new IllegalArgumentException("The registry export url is null! registry: " + origininvoker.getUrl());
    19         }
    20         URL providerUrl = URL.valueOf(export);
    21         return providerUrl;
    22     }
    23 
    24     //过滤URL中不需要输出的参数(以点号开头的)
    25     private static String[] getFilteredKeys(URL url) {
    26         Map<String, String> params = url.getParameters();
    27         if (params != null && !params.isEmpty()) {
    28             List<String> filteredKeys = new ArrayList<String>();
    29             for (Map.Entry<String, String> entry : params.entrySet()) {
    30                 if (entry != null && entry.getKey() != null && entry.getKey().startsWith(Constants.HIDE_KEY_PREFIX)) {
    31                     filteredKeys.add(entry.getKey());
    32                 }
    33             }
    34             return filteredKeys.toArray(new String[filteredKeys.size()]);
    35         } else {
    36             return new String[]{};
    37         }
    38     }
    复制代码

    最后得到的registedProviderUrl是:

    • dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=4758&side=provider&timestamp=1507289961588

    三  注册服务到zk

    registry.register(registedProviderUrl);//创建节点(即注册服务到zk上)

    这里的registry是ZookeeperRegistry。register(registedProviderUrl)方法在ZookeeperRegistry的父类FailbackRegistry中实现。

    1  FailbackRegistry.register(registedProviderUrl)

    复制代码
     1     @Override
     2     public void register(URL url) {
     3         if (destroyed.get()){
     4             return;
     5         }
     6         super.register(url);
     7         failedRegistered.remove(url);
     8         failedUnregistered.remove(url);
     9         try {
    10             // 向服务器端发送注册请求
    11             doRegister(url);
    12         } catch (Exception e) {
    13             Throwable t = e;
    14             // 如果开启了启动时检测check=true,则直接抛出异常,不会加入到failedRegistered中
    15             boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
    16                     && url.getParameter(Constants.CHECK_KEY, true)
    17                     && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
    18             boolean skipFailback = t instanceof SkipFailbackWrapperException;
    19             if (check || skipFailback) {
    20                 if (skipFailback) {
    21                     t = t.getCause();
    22                 }
    23                 throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
    24             } else {
    25                 logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
    26             }
    27             // 将失败的注册请求记录到失败列表,定时重试
    28             failedRegistered.add(url);
    29         }
    30     }
    复制代码

    首先调用父类AbstractRegistry的register(registedProviderUrl)将当前的registeredProviderUrl放到Set<URL> registered属性中,如下:

    复制代码
    1     public void register(URL url) {
    2         if (url == null) {
    3             throw new IllegalArgumentException("register url == null");
    4         }
    5         if (logger.isInfoEnabled()) {
    6             logger.info("Register: " + url);
    7         }
    8         registered.add(url);
    9     }
    复制代码

    之后,从failedRegistered和failedUnregistered两个url集合中删除该url。然后执行真正的服务注册(创建节点,doRegister(url)),如果在创建过程中抛出异常,如果url的协议不是consumer并且开启了check=true的属性并且当前存储的URL registryUrl也有check=true的话,那么直接抛出异常,不会将该url加入到failedRegistered集合;当然抛出的异常如果是SkipFailbackWrapperException,那么也会直接抛出异常,不会将该url加入到failedRegistered集合。否则,会将该url加入到failedRegistered集合,然后DubboRegistryFailedRetryTimer线程会每隔5s执行一次doRegister(url)。

    我们来看真正doRegister(url)。

    2  ZookeeperRegistry.doRegister(registedProviderUrl)

    复制代码
    1     protected void doRegister(URL url) {
    2         try {
    3             zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    4         } catch (Throwable e) {
    5             throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    6         }
    7     }
    复制代码

    首先是对入参registedProviderUrl进行一顿处理,

    复制代码
     1     private String toUrlPath(URL url) {
     2         return toCategoryPath(url) + Constants.PATH_SEPARATOR + URL.encode(url.toFullString());
     3     }
     4 
     5     private String toCategoryPath(URL url) {
     6         return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
     7     }
     8 
     9     private String toServicePath(URL url) {
    10         String name = url.getServiceInterface();
    11         if (Constants.ANY_VALUE.equals(name)) {
    12             return toRootPath();
    13         }
    14         return toRootDir() + URL.encode(name);// /dubbo/com.alibaba.dubbo.demo.DemoService
    15     }
    16 
    17     private String toRootDir() {
    18         if (root.equals(Constants.PATH_SEPARATOR)) {
    19             return root;
    20         }
    21         return root + Constants.PATH_SEPARATOR;// /dubbo/
    22     }
    23 
    24     private String toRootPath() {
    25         return root;
    26     }
    复制代码

    这里就体现了上边的ZookeeperRegistry的root属性的作用。最终实际上得到的是:/dubbo/interface/category/encode过的export,该节点也将是创建在zk上的节点。

    • /dubbo是根节点
    • /interface是服务接口
    • /category是providers/consumers/routers/configurators等

    最终得到的url是:

    • /dubbo/com.alibaba.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F10.10.10.10%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D5148%26side%3Dprovider%26timestamp%3D1507291294629
    • 解码后:/dubbo/com.alibaba.dubbo.demo.DemoService/providers/dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5148&side=provider&timestamp=1507291294629

    最后执行zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true))来创建节点,该方法由CuratorZookeeperClient的父类AbstractZookeeperClient来执行:

    复制代码
     1     public void create(String path, boolean ephemeral) {
     2         int i = path.lastIndexOf('/');
     3         if (i > 0) {
     4             create(path.substring(0, i), false);
     5         }
     6         if (ephemeral) {
     7             createEphemeral(path);
     8         } else {
     9             createPersistent(path);
    10         }
    11     }
    复制代码

    这里实际上是通过递归分别创建持久化的/dubbo,/dubbo/com.alibaba.dubbo.demo.DemoService以及/dubbo/com.alibaba.dubbo.demo.DemoService/providers节点;最后创建临时节点/dubbo/com.alibaba.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F10.10.10.10%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D5148%26side%3Dprovider%26timestamp%3D1507291294629,而实际上,如果使用了curator的话,可以直接使用递归创建节点即可(结合zk的特性,只有最后一个字节点可以是临时节点,父节点一定是持久化节点),这里这样的写法应该是兼容不能递归创建节点的Zkclient客户端。值得注意的是,url.getParameter(Constants.DYNAMIC_KEY, true)为true则最终创建的节点是临时节点,否则是持久化节点。

    创建节点的操作是在CuratorZookeeperClient中进行的。

    复制代码
     1     public void createPersistent(String path) {
     2         try {
     3             client.create().forPath(path);
     4         } catch (NodeExistsException e) {
     5         } catch (Exception e) {
     6             throw new IllegalStateException(e.getMessage(), e);
     7         }
     8     }
     9 
    10     public void createEphemeral(String path) {
    11         try {
    12             client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
    13         } catch (NodeExistsException e) {
    14         } catch (Exception e) {
    15             throw new IllegalStateException(e.getMessage(), e);
    16         }
    17     }
    复制代码

    到此为止,我们去zk上看一下节点的创建情况。

    或者从zkui上看一下:

    隐藏掉的是ip:10.10.10.10。

    到目前为止,我们再来看看ZookeeperRegistry的属性变化。相较于注册前:

    • Set<URL> registered:[ dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5214&side=provider&timestamp=1507293238549 ]

    7.7 服务远程暴露 - 订阅与通知

    为了安全:服务启动的ip全部使用10.10.10.10

    远程服务的暴露总体步骤:

    • 将ref封装为invoker
    • 将invoker转换为exporter
    • 启动netty
    • 注册服务到zookeeper
    • 订阅与通知
    • 返回新的exporter实例

    7.4 服务远程暴露 - 创建Exporter与启动netty服务端中,实现了前三步,在7.6 服务远程暴露 - 注册服务到zookeeper实现了第四步。本节实现第五步:订阅。总体代码如下:RegistryProtocol.export(final Invoker<T> originInvoker)

    1         // 订阅override数据
    2         // FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
    3         final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
    4         final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    5         overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    6         registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    说明:

    • 第一句代码根据registedProviderUrl来获取overrideSubscribeUrl。
    • 第二句代码创建overrideSubscribeListener
    • 第三句代码将{ overrideSubscribeUrl : overrideSubscribeListener放入缓存 }
    • 第四句代码实现真正的订阅与通知

    一  获取overrideSubscribeUrl

    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
    复制代码
    1     /**
    2      * 1 将协议改为provider;
    3      * 2 添加参数:category=configurators和check=false;
    4      */
    5     private URL getSubscribedOverrideUrl(URL registedProviderUrl) {
    6         return registedProviderUrl.setProtocol(Constants.PROVIDER_PROTOCOL)
    7                                   .addParameters(Constants.CATEGORY_KEY, Constants.CONFIGURATORS_CATEGORY, Constants.CHECK_KEY, String.valueOf(false));
    8     }
    复制代码

    开始时的registedProviderUrl如下:

    • dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5259&side=provider&timestamp=1507294508053

    最终的overrideSubscribeUrl如下:

    • provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5259&side=provider&timestamp=1507294508053

    二  创建overrideSubscribeListener

    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);

    overrideSubscribeListener是RegistryProtocol的内部类,来看一下声明和属性:

    复制代码
    1     private class OverrideListener implements NotifyListener {
    2         private final URL subscribeUrl;
    3         private final Invoker originInvoker;
    4 
    5         public OverrideListener(URL subscribeUrl, Invoker originalInvoker) {
    6             this.subscribeUrl = subscribeUrl;
    7             this.originInvoker = originalInvoker;
    8         }
    复制代码

    这里创建出来的OverrideListener实例属性如下:

    • subscribeUrl:provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=1818&side=provider&timestamp=1507366969962
    • originInvoker:该实例还是在ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)创建出来的AbstractProxyInvoker实例(具体见7.4 服务远程暴露 - 创建Exporter与启动netty服务端
      • proxy:DemoServiceImpl实例
      • type:Class<com.alibaba.dubbo.demo.DemoService>
      • url:registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&export=dubbo%3A%2F%2F10.10.10.10%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D993%26side%3Dprovider%26timestamp%3D1507100322516&pid=993&registry=zookeeper&timestamp=1507100319830

    最后,将创建出来的OverrideListener实例存储在RegistryProtocol的属性Map<URL, NotifyListener> overrideListeners中:

    • key: (overrideSubscribeUrl,也就是subscribeUrl) provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=1818&side=provider&timestamp=1507366969962
    • value:  上述的OverrideListener实例

    三  真正的订阅

    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    这里的registry是ZookeeperRegistry实例,subscribe(URL url, NotifyListener listener)方法在其父类FailbackRegistry中,如下:

    复制代码
     1     @Override
     2     public void subscribe(URL url, NotifyListener listener) {
     3         if (destroyed.get()){
     4             return;
     5         }
     6         super.subscribe(url, listener);
     7         removeFailedSubscribed(url, listener);
     8         try {
     9             // 向服务器端发送订阅请求
    10             doSubscribe(url, listener);
    11         } catch (Exception e) {
    12             Throwable t = e;
    13 
    14             List<URL> urls = getCacheUrls(url);
    15             if (urls != null && urls.size() > 0) {
    16                 notify(url, listener, urls);
    17                 logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
    18             } else {
    19                 // 如果开启了启动时检测check=true,则直接抛出异常
    20                 boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
    21                         && url.getParameter(Constants.CHECK_KEY, true);
    22                 boolean skipFailback = t instanceof SkipFailbackWrapperException;
    23                 if (check || skipFailback) {
    24                     if (skipFailback) {
    25                         t = t.getCause();
    26                     }
    27                     throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
    28                 } else {
    29                     logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
    30                 }
    31             }
    32             // 将失败的订阅请求记录到失败列表,定时重试
    33             addFailedSubscribed(url, listener);
    34         }
    35     }
    复制代码

    步骤:

    • 首先调用其父类AbstractRegistry的方法,将之前创建出来的overrideSubscribeListener实例加入到overrideSubscribeUrl所对应的监听器集合中;
    • 然后从failedSubscribed/failedUnsubscribed中overrideSubscribeUrl所对应的监听器集合中删除overrideSubscribeListener实例;从failedNotified获取当前url的通知失败map Map<NotifyListener, List<URL>>,之后从中删除掉该NotifyListener实例以及其需要通知的所有的url。
    • 之后使用具体的子类(这里是ZookeeperRegistry)向服务器端发送订阅请求
    • 如果在订阅的过程中抛出了异常,那么尝试获取缓存url,如果有缓存url,则进行失败通知,之后“将失败的订阅请求记录到失败列表,定时重试”,如果没有缓存url,如果开启了启动时检测或者直接抛出的异常是SkipFailbackWrapperException,则直接抛出异常,不会“将失败的订阅请求记录到失败列表,定时重试”

    将之前创建出来的overrideSubscribeListener实例加入到overrideSubscribeUrl所对应的监听器集合中

    复制代码
     1    private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();//已经订阅的<URL, Set<NotifyListener>>
     2     
     3      /**
     4      * 首先从ConcurrentMap<URL, Set<NotifyListener>> subscribed中获取key为url的集合Set<NotifyListener>,
     5      * 如果该集合存在,直接将当前的NotifyListener实例存入该集合,
     6      * 如果集合不存在,先创建,之后放入subscribed中,并将当前的NotifyListener实例存入刚刚创建的集合
     7      *
     8      * @param url      订阅条件,不允许为空,如:consumer://10.10.10.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     9      * @param listener 变更事件监听器,不允许为空
    10      */
    11     public void subscribe(URL url, NotifyListener listener) {
    12         if (url == null) {
    13             throw new IllegalArgumentException("subscribe url == null");
    14         }
    15         if (listener == null) {
    16             throw new IllegalArgumentException("subscribe listener == null");
    17         }
    18         if (logger.isInfoEnabled()) {
    19             logger.info("Subscribe: " + url);
    20         }
    21         Set<NotifyListener> listeners = subscribed.get(url);
    22         if (listeners == null) {
    23             subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
    24             listeners = subscribed.get(url);
    25         }
    26         listeners.add(listener);
    27     }
    复制代码

    从失败集合移除overrideSubscribeListener实例

    复制代码
     1     /**
     2      * 1 从ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed 中获取当前url的订阅失败列表Set<NotifyListener>,之后从中删除掉该NotifyListener实例;
     3      * 2 从ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed 中获取当前url的反订阅失败列表Set<NotifyListener>,之后从中删除掉该NotifyListener实例;
     4      * 3 从ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified 中获取当前url的通知失败map Map<NotifyListener, List<URL>>,之后从中删除掉该NotifyListener实例以及其需要通知的所有的url。
     5      *
     6      * @param url
     7      * @param listener
     8      */
     9     private void removeFailedSubscribed(URL url, NotifyListener listener) {
    10         Set<NotifyListener> listeners = failedSubscribed.get(url);
    11         if (listeners != null) {
    12             listeners.remove(listener);
    13         }
    14         listeners = failedUnsubscribed.get(url);
    15         if (listeners != null) {
    16             listeners.remove(listener);
    17         }
    18         Map<NotifyListener, List<URL>> notified = failedNotified.get(url);
    19         if (notified != null) {
    20             notified.remove(listener);
    21         }
    22     }
    复制代码

    ZookeeperRegistry.doSubscribe(final URL url, final NotifyListener listener)

    复制代码
     1 protected void doSubscribe(final URL url, final NotifyListener listener) {
     2         try {
     3             if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {//这条分支先不说
     4                 String root = toRootPath();
     5                 ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
     6                 if (listeners == null) {
     7                     zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
     8                     listeners = zkListeners.get(url);
     9                 }
    10                 ChildListener zkListener = listeners.get(listener);
    11                 if (zkListener == null) {
    12                     listeners.putIfAbsent(listener, new ChildListener() {
    13                         public void childChanged(String parentPath, List<String> currentChilds) {
    14                             for (String child : currentChilds) {
    15                                 child = URL.decode(child);
    16                                 if (!anyServices.contains(child)) {
    17                                     anyServices.add(child);
    18                                     subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
    19                                             Constants.CHECK_KEY, String.valueOf(false)), listener);
    20                                 }
    21                             }
    22                         }
    23                     });
    24                     zkListener = listeners.get(listener);
    25                 }
    26                 zkClient.create(root, false);
    27                 List<String> services = zkClient.addChildListener(root, zkListener);
    28                 if (services != null && services.size() > 0) {
    29                     for (String service : services) {
    30                         service = URL.decode(service);
    31                         anyServices.add(service);
    32                         subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
    33                                 Constants.CHECK_KEY, String.valueOf(false)), listener);
    34                     }
    35                 }
    36             } else {
    37                 /**
    38                  * ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners
    39                  * 1 根据url获取ConcurrentMap<NotifyListener, ChildListener>,没有就创建
    40                  * 2 根据listener从ConcurrentMap<NotifyListener, ChildListener>获取ChildListener,没有就创建(创建的ChildListener用来监听子节点的变化)
    41                  * 3 创建path持久化节点
    42                  * 4 创建path子节点监听器
    43                  */
    44                 List<URL> urls = new ArrayList<URL>();
    45                 for (String path : toCategoriesPath(url)) {
    46                     ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
    47                     if (listeners == null) {
    48                         zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
    49                         listeners = zkListeners.get(url);
    50                     }
    51                     ChildListener zkListener = listeners.get(listener);
    52                     if (zkListener == null) {
    53                         listeners.putIfAbsent(listener, new ChildListener() {
    54                             //监听子节点列表的变化
    55                             public void childChanged(String parentPath, List<String> currentChilds) {
    56                                 ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
    57                             }
    58                         });
    59                         zkListener = listeners.get(listener);
    60                     }
    61                     zkClient.create(path, false);//创建持久化节点/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
    62                     List<String> children = zkClient.addChildListener(path, zkListener);
    63                     if (children != null) {
    64                         urls.addAll(toUrlsWithEmpty(url, path, children));
    65                     }
    66                 }
    67                 notify(url, listener, urls);
    68             }
    69         } catch (Throwable e) {
    70             throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    71         }
    72     }
    复制代码

    说明:

    • url(overrideSubscribeUrl):provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076
    • listener:之前创建出来的overrideSubscribeListener实例

    步骤:

    • 首先获取categorypath:实际上就是获取/dubbo/{servicename}/{url中的category参数,默认是providers,这里是final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);这句代码中添加到overrideSubscribeUrl上的category=configurators}
    复制代码
     1     private String[] toCategoriesPath(URL url) {
     2         String[] categroies;
     3         if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
     4             categroies = new String[]{Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY,
     5                     Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
     6         } else {
     7             categroies = url.getParameter(Constants.CATEGORY_KEY, new String[]{Constants.DEFAULT_CATEGORY});
     8         }
     9         String[] paths = new String[categroies.length];
    10         for (int i = 0; i < categroies.length; i++) {
    11             paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categroies[i];
    12         }
    13         return paths; // /dubbo/com.alibaba.dubbo.demo.DemoService/configurators
    14     }
    复制代码
    • 然后就是获取并创建:ConcurrentMap<overrideSubscribeUrl, ConcurrentMap<overrideSubscribeListener实例, ChildListener>> zkListeners,这里创建出来的ChildListener实例中的childChanged(String parentPath, List<String> currentChilds)方法实际上就是最终当parentPath(实际上就是上边的categorypath)下的currentChilds发生变化时,执行的逻辑。
    • 之后创建持久化节点:/dubbo/com.alibaba.dubbo.demo.DemoService/configurators
    • 然后使用AbstractZookeeperClient<TargetChildListener>的addChildListener(String path, final ChildListener listener)方法为path下的子节点添加上边创建出来的内部类ChildListener实例
    • 最后进行通知

    AbstractZookeeperClient<TargetChildListener>.addChildListener(String path, final ChildListener listener)

    复制代码
     1     /**
     2      *  1 根据path从ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners获取ConcurrentMap<ChildListener, TargetChildListener>,没有就创建
     3      *  2 根据ChildListener获取TargetChildListener,没有就创建,TargetChildListener是真正的监听path的子节点变化的监听器
     4      *  createTargetChildListener(String path, final ChildListener listener):创建一个真正的用来执行当path节点的子节点发生变化时的逻辑
     5      *  3 addTargetChildListener(path, targetListener):将刚刚创建出来的子节点监听器订阅path的变化,这样之后,path的子节点发生了变化时,TargetChildListener才会执行相应的逻辑。
     6      *  而实际上TargetChildListener又会调用ChildListener的实现类的childChanged(String parentPath, List<String> currentChilds)方法,而该实现类,正好是ZookeeperRegistry中实现的匿名内部类,
     7      *  在该匿名内部类的childChanged(String parentPath, List<String> currentChilds)方法中,调用了ZookeeperRegistry.notify(URL url, NotifyListener listener, List<URL> urls)
     8      */
     9     public List<String> addChildListener(String path, final ChildListener listener) {
    10         ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.get(path);
    11         if (listeners == null) {
    12             childListeners.putIfAbsent(path, new ConcurrentHashMap<ChildListener, TargetChildListener>());
    13             listeners = childListeners.get(path);
    14         }
    15         TargetChildListener targetListener = listeners.get(listener);
    16         if (targetListener == null) {
    17             listeners.putIfAbsent(listener, createTargetChildListener(path, listener));
    18             targetListener = listeners.get(listener);
    19         }
    20         return addTargetChildListener(path, targetListener);
    21     }
    复制代码

    步骤:

    • 首先是一顿获取和创建:ConcurrentMap<categorypath, ConcurrentMap<ZookeeperRegistry的内部类ChildListener实例, TargetChildListener>> childListeners,这里主要是创建TargetChildListener;
    • 之后是真正的为path添加TargetChildListener实例。

    CuratorZookeeperClient.createTargetChildListener(path, listener)

    复制代码
     1     public CuratorWatcher createTargetChildListener(String path, ChildListener listener) {
     2         return new CuratorWatcherImpl(listener);
     3     }
     4 
     5     private class CuratorWatcherImpl implements CuratorWatcher {
     6 
     7         private volatile ChildListener listener;
     8 
     9         public CuratorWatcherImpl(ChildListener listener) {
    10             this.listener = listener;
    11         }
    12 
    13         public void unwatch() {
    14             this.listener = null;
    15         }
    16 
    17         public void process(WatchedEvent event) throws Exception {
    18             if (listener != null) {
    19                 listener.childChanged(event.getPath(), client.getChildren().usingWatcher(this).forPath(event.getPath()));
    20             }
    21         }
    22     }
    复制代码

    很简单,就是创建一个监听path子节点的watcher,当path下有子节点变化时,调用listener(即传入的ZookeeperRegistry的内部类ChildListener实例的childChanged(String parentPath, List<String> currentChilds)方法)。

    CuratorZookeeperClient.addTargetChildListener(String path, CuratorWatcher targetChildListener)

    复制代码
    1     public List<String> addTargetChildListener(String path, CuratorWatcher listener) {
    2         try {
    3             return client.getChildren().usingWatcher(listener).forPath(path);
    4         } catch (NoNodeException e) {
    5             return null;
    6         } catch (Exception e) {
    7             throw new IllegalStateException(e.getMessage(), e);
    8         }
    9     }
    复制代码

    从上边的分析我们可以看出,当path节点下的子节点发生变化的时候,会首先调用TargetChildListener的process(WatchedEvent event)方法,在该方法中又会调用ChildListener实例的childChanged(String parentPath, List<String> currentChilds)方法,那么我们来分析一下该方法:

    1                             //监听子节点列表的变化
    2                             public void childChanged(String parentPath, List<String> currentChilds) {
    3                                 ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
    4                             }

    步骤:

    • 首先获取子节点urls或者是一个consumer的empty协议的url
      复制代码
       1     /**
       2      * 过滤出providers中与consumer匹配的url集合
       3      */
       4     private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
       5         List<URL> urls = new ArrayList<URL>();
       6         if (providers != null && providers.size() > 0) {
       7             for (String provider : providers) {
       8                 provider = URL.decode(provider);
       9                 if (provider.contains("://")) {
      10                     URL url = URL.valueOf(provider);
      11                     if (UrlUtils.isMatch(consumer, url)) {
      12                         urls.add(url);
      13                     }
      14                 }
      15             }
      16         }
      17         return urls;
      18     }
      19 
      20     /**
      21      * 1 首先过滤出providers中与consumer匹配的providerUrl集合
      22      * 2 如果providerUrl集合不为空,直接返回这个集合
      23      * 3 如果为空,首先从path中获取category,然后将consumer的协议换成empty,添加参数category=configurators
      24      * @param consumer provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076
      25      * @param path /dubbo/com.alibaba.dubbo.demo.DemoService/configurators
      26      * @param providers 
      27      */
      28     private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
      29         List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
      30         if (urls == null || urls.isEmpty()) {
      31             int i = path.lastIndexOf('/');
      32             String category = i < 0 ? path : path.substring(i + 1);//configurators
      33             URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);
      34             urls.add(empty);
      35         }
      36         return urls; // empty://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=1237&side=provider&timestamp=1507352638483
      37     }
      复制代码
    • 之后调用ZookeeperRegistry的父类FailbackRegistry.notify(URL url, NotifyListener listener, List<URL> urls)
      复制代码
       1     @Override
       2     protected void notify(URL url, NotifyListener listener, List<URL> urls) {
       3         if (url == null) {
       4             throw new IllegalArgumentException("notify url == null");
       5         }
       6         if (listener == null) {
       7             throw new IllegalArgumentException("notify listener == null");
       8         }
       9         try {
      10             doNotify(url, listener, urls);
      11         } catch (Exception t) {
      12             // 将失败的通知请求记录到失败列表,定时重试
      13             Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
      14             if (listeners == null) {
      15                 failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
      16                 listeners = failedNotified.get(url);
      17             }
      18             listeners.put(listener, urls);
      19             logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
      20         }
      21     }
      22 
      23     protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
      24         super.notify(url, listener, urls);
      25     }
      复制代码

      说明:这里传入的

      • url(overrideSubscribeUrl):provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076

      • listener:之前创建出来的overrideSubscribeListener实例
      • urls:[ empty://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076 ]
      • 这里首先执行父类的AbstractRegistry.notify(URL url, NotifyListener listener, List<URL> urls),如果失败,则获取或创建ConcurrentMap<overrideSubscribeUrl, Map<overrideSubscribeListener实例, urls>> failedNotified,后续做重试

    来看一下通知的最核心部分:

    AbstractRegistry.notify(URL url, NotifyListener listener, List<URL> urls)

    复制代码
     1     /**
     2      * 1 首先遍历List<URL> urls,将urls按照category进行分类,存储在Map<"categoryName", List<URL>> result中;
     3      * 2 之后遍历result:(每遍历一次,都是一个新的category)
     4      * (1)将Map<"categoryName", List<URL>>存储在ConcurrentMap<URL, Map<String, List<URL>>> notified的Map<String, List<URL>>中
     5      * (2)进行properties设置和文件保存
     6      * (3)调用传入放入listener的notify()方法。
     7      * @param url
     8      * @param listener
     9      * @param urls
    10      */
    11     protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    12         if (url == null) {
    13             throw new IllegalArgumentException("notify url == null");
    14         }
    15         if (listener == null) {
    16             throw new IllegalArgumentException("notify listener == null");
    17         }
    18         if ((urls == null || urls.size() == 0)
    19                 && !Constants.ANY_VALUE.equals(url.getServiceInterface())) {
    20             logger.warn("Ignore empty notify urls for subscribe url " + url);
    21             return;
    22         }
    23         if (logger.isInfoEnabled()) {
    24             logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
    25         }
    26         /**
    27          * 遍历List<URL> urls,将urls按照category进行分类
    28          */
    29         Map<String, List<URL>> result = new HashMap<String, List<URL>>(); //{ "categoryName" : List<URL> }
    30         for (URL u : urls) {
    31             if (UrlUtils.isMatch(url, u)) {
    32                 String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
    33                 List<URL> categoryList = result.get(category);
    34                 if (categoryList == null) {
    35                     categoryList = new ArrayList<URL>();
    36                     result.put(category, categoryList);
    37                 }
    38                 categoryList.add(u);
    39             }
    40         }
    41         if (result.size() == 0) {
    42             return;
    43         }
    44         Map<String, List<URL>> categoryNotified = notified.get(url);
    45         if (categoryNotified == null) {
    46             notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
    47             categoryNotified = notified.get(url);
    48         }
    49         for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
    50             String category = entry.getKey();
    51             List<URL> categoryList = entry.getValue();
    52             categoryNotified.put(category, categoryList);//填充notified集合
    53             saveProperties(url);//该行代码为什么不写在循环体外边
    54             listener.notify(categoryList);
    55         }
    56     }
    复制代码

    说明:这里传入的

    • url(overrideSubscribeUrl):provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076

    • listener:之前创建出来的overrideSubscribeListener实例
    • urls:[ empty://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9544&side=provider&timestamp=1507643800076 ]

    步骤:

    • 首先遍历List<URL> urls,将urls按照category进行分类,存储在Map<"categoryName", List<URL>> result中;
    • 然后获取或创建ConcurrentMap<overrideSubscribeUrl, Map<"categoryName", subList(urls)>> notified
    • 最后遍历Map<"categoryName", List<URL>> result
      • 去填充notified集合
      • 保存传入的url到Properties properties(本地磁盘缓存中)
      • 调用传入的listener的notify方法(注意:这里调用的正是文章开头创建的overrideSubscribeListener实例的notify方法)

    AbstractRegistry.saveProperties(URL url)

    复制代码
     1     /**
     2      * 1 按照url从将ConcurrentMap<URL, Map<String, List<URL>>> notified中将Map<String, List<URL>>拿出来,之后将所有category的list组成一串buf(以空格分隔)
     3      * 2 将< serviceKey<->buf >写入本地磁盘缓存中:Properties properties
     4      * 3 将AtomicLong lastCacheChanged加1
     5      * 4 之后根据syncSaveFile判断时同步保存properties到文件,还是异步保存properties到文件
     6      * @param url
     7      */
     8     private void saveProperties(URL url) {
     9         if (file == null) {
    10             return;
    11         }
    12 
    13         try {
    14             StringBuilder buf = new StringBuilder();
    15             Map<String, List<URL>> categoryNotified = notified.get(url);
    16             if (categoryNotified != null) {
    17                 for (List<URL> us : categoryNotified.values()) {
    18                     for (URL u : us) {
    19                         if (buf.length() > 0) {
    20                             buf.append(URL_SEPARATOR);
    21                         }
    22                         buf.append(u.toFullString());
    23                     }
    24                 }
    25             }
    26             properties.setProperty(url.getServiceKey(), buf.toString());
    27             long version = lastCacheChanged.incrementAndGet();
    28             if (syncSaveFile) {
    29                 doSaveProperties(version);
    30             } else {
    31                 registryCacheExecutor.execute(new SaveProperties(version));
    32             }
    33         } catch (Throwable t) {
    34             logger.warn(t.getMessage(), t);
    35         }
    36     }
    复制代码

    说明:

    • 入参:url:provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5033&side=provider&timestamp=1507720343596
    • properties:{ "com.alibaba.dubbo.demo.DemoService" -> "empty://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5033&side=provider&timestamp=1507720343596" }
    • 最后采用异步线程将properties中的内容写入到文件中

    AbstractRegistry$SaveProperties

    复制代码
     1     private class SaveProperties implements Runnable {
     2         private long version;
     3 
     4         private SaveProperties(long version) {
     5             this.version = version;
     6         }
     7 
     8         public void run() {
     9             doSaveProperties(version);
    10         }
    11     }
    复制代码

    AbstractRegistry.doSaveProperties(long version)

    复制代码
     1     /**
     2      * 1 先将文件中的内容读取到一个新的Properties newProperties中;
     3      * 2 之后将properties中的信息写入这个newProperties中;
     4      * 3 之后创建dubbo-registry-10.211.55.5.cache.lock文件;
     5      * 4 最后将这个newProperties中的内容写入到文件中
     6      */
     7     public void doSaveProperties(long version) {
     8         if (version < lastCacheChanged.get()) {
     9             return;
    10         }
    11         if (file == null) {
    12             return;
    13         }
    14         Properties newProperties = new Properties();
    15         // 保存之前先读取一遍,防止多个注册中心之间冲突
    16         InputStream in = null;
    17         try {
    18             if (file.exists()) {
    19                 in = new FileInputStream(file);
    20                 newProperties.load(in);
    21             }
    22         } catch (Throwable e) {
    23             logger.warn("Failed to load registry store file, cause: " + e.getMessage(), e);
    24         } finally {
    25             if (in != null) {
    26                 try {
    27                     in.close();
    28                 } catch (IOException e) {
    29                     logger.warn(e.getMessage(), e);
    30                 }
    31             }
    32         }
    33         // 保存
    34         try {
    35             newProperties.putAll(properties);
    36             File lockfile = new File(file.getAbsolutePath() + ".lock");
    37             if (!lockfile.exists()) {
    38                 lockfile.createNewFile();//创建lock文件
    39             }
    40             RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
    41             try {
    42                 FileChannel channel = raf.getChannel();
    43                 try {
    44                     FileLock lock = channel.tryLock();
    45                     if (lock == null) {
    46                         throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");
    47                     }
    48                     // 保存
    49                     try {
    50                         if (!file.exists()) {
    51                             file.createNewFile();
    52                         }
    53                         FileOutputStream outputFile = new FileOutputStream(file);
    54                         try {
    55                             newProperties.store(outputFile, "Dubbo Registry Cache");
    56                         } finally {
    57                             outputFile.close();
    58                         }
    59                     } finally {
    60                         lock.release();
    61                     }
    62                 } finally {
    63                     channel.close();
    64                 }
    65             } finally {
    66                 raf.close();
    67             }
    68         } catch (Throwable e) {
    69             if (version < lastCacheChanged.get()) {
    70                 return;
    71             } else {
    72                 registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));
    73             }
    74             logger.warn("Failed to save registry store file, cause: " + e.getMessage(), e);
    75         }
    76     }
    复制代码

    步骤见注释。这里有一个version,实际上是一个CAS判断,我们在saveProperties(URL url)方法中执行了long version = lastCacheChanged.incrementAndGet();之后在doSaveProperties(long version)进行if (version < lastCacheChanged.get())判断,如果满足这个条件,说明当前线程在进行doSaveProperties(long version)时,已经有其他线程执行了saveProperties(URL url),马上就要执行doSaveProperties(long version),所以当前线程放弃操作,让后边的这个线程来做保存操作。

    保存操作执行之后,会在文件夹/Users/jigangzhao/.dubbo下生成两个文件:

    • dubbo-registry-10.211.55.5.cache
    • dubbo-registry-10.211.55.5.cache.lock

    前者的内容:

    1
    2
    #Wed Oct 11 19:42:29 CST 2017
    com.alibaba.dubbo.demo.DemoService=empty\://10.10.10.10\:20880/com.alibaba.dubbo.demo.DemoService?anyhost\=true&application\=demo-provider&category\=configurators&check\=false&dubbo\=2.0.0&generic\=false&interface\=com.alibaba.dubbo.demo.DemoService&methods\=sayHello&pid\=5165&side\=provider&timestamp\=1507722024953

    最后就是OverrideListener.notify(List<URL> urls)

    复制代码
     1     /**
     2      * 重新export
     3      * 1.protocol中的exporter destroy问题
     4      * 1.要求registryprotocol返回的exporter可以正常destroy
     5      * 2.notify后不需要重新向注册中心注册
     6      * 3.export 方法传入的invoker最好能一直作为exporter的invoker.
     7      */
     8     private class OverrideListener implements NotifyListener {
     9         private final URL subscribeUrl;
    10         private final Invoker originInvoker;
    11 
    12         public OverrideListener(URL subscribeUrl, Invoker originalInvoker) {
    13             this.subscribeUrl = subscribeUrl;
    14             this.originInvoker = originalInvoker;
    15         }
    16 
    17         /**
    18          * 目的:
    19          * 对原本注册了的providerUrl进行校验,如果url发生了变化,那么要重新export
    20          *
    21          * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
    22          */
    23         public synchronized void notify(List<URL> urls) {
    24             logger.debug("original override urls: " + urls);
    25             List<URL> matchedUrls = getMatchedUrls(urls, subscribeUrl);
    26             logger.debug("subscribe url: " + subscribeUrl + ", override urls: " + matchedUrls);
    27             //没有匹配的
    28             if (matchedUrls.isEmpty()) {
    29                 return;
    30             }
    31 
    32             List<Configurator> configurators = RegistryDirectory.toConfigurators(matchedUrls);//这里是一个空列表
    33 
    34             final Invoker<?> invoker;
    35             if (originInvoker instanceof InvokerDelegete) {
    36                 invoker = ((InvokerDelegete<?>) originInvoker).getInvoker();
    37             } else {
    38                 invoker = originInvoker;
    39             }
    40             //最原始的invoker
    41             URL originUrl = RegistryProtocol.this.getProviderUrl(invoker);//dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5279&side=provider&timestamp=1507723571451
    42             String key = getCacheKey(originInvoker);//dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5279&side=provider&timestamp=1507723571451
    43             ExporterChangeableWrapper<?> exporter = bounds.get(key);//在doLocalExport方法中已经存放在这里了
    44             if (exporter == null) {
    45                 logger.warn(new IllegalStateException("error state, exporter should not be null"));
    46                 return;
    47             }
    48             //当前的,可能经过了多次merge
    49             URL currentUrl = exporter.getInvoker().getUrl();//dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5279&side=provider&timestamp=1507723571451
    50             //与本次配置merge的
    51             URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
    52             if (!currentUrl.equals(newUrl)) {
    53                 RegistryProtocol.this.doChangeLocalExport(originInvoker, newUrl);//重新将invoker暴露为exporter
    54                 logger.info("exported provider url changed, origin url: " + originUrl + ", old export url: " + currentUrl + ", new export url: " + newUrl);
    55             }
    56         }
    57 
    58         private List<URL> getMatchedUrls(List<URL> configuratorUrls, URL currentSubscribe) {
    59             List<URL> result = new ArrayList<URL>();
    60             for (URL url : configuratorUrls) {
    61                 URL overrideUrl = url;
    62                 // 兼容旧版本
    63                 if (url.getParameter(Constants.CATEGORY_KEY) == null
    64                         && Constants.OVERRIDE_PROTOCOL.equals(url.getProtocol())) {
    65                     overrideUrl = url.addParameter(Constants.CATEGORY_KEY, Constants.CONFIGURATORS_CATEGORY);
    66                 }
    67 
    68                 //检查是不是要应用到当前服务上
    69                 if (UrlUtils.isMatch(currentSubscribe, overrideUrl)) {
    70                     result.add(url);
    71                 }
    72             }
    73             return result;
    74         }
    75 
    76         //合并配置的url
    77         private URL getConfigedInvokerUrl(List<Configurator> configurators, URL url) {
    78             for (Configurator configurator : configurators) {
    79                 url = configurator.configure(url);
    80             }
    81             return url;
    82         }
    83     }
    复制代码

    最后:总结一下:

    当前的provider订阅了/dubbo/com.alibaba.dubbo.demo.DemoService/configurators,当其下的子节点发生变化时,如果其下的子节点的url或者当前的providerUrl发生了变化,需要重新暴露

    重新暴露:

    复制代码
     1     /**
     2      * 对修改了url的invoker重新export
     3      *
     4      * @param originInvoker
     5      * @param newInvokerUrl
     6      */
     7     @SuppressWarnings("unchecked")
     8     private <T> void doChangeLocalExport(final Invoker<T> originInvoker, URL newInvokerUrl) {
     9         String key = getCacheKey(originInvoker);
    10         final ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    11         if (exporter == null) {
    12             logger.warn(new IllegalStateException("error state, exporter should not be null"));
    13         } else {
    14             final Invoker<T> invokerDelegete = new InvokerDelegete<T>(originInvoker, newInvokerUrl);
    15             exporter.setExporter(protocol.export(invokerDelegete));
    16         }
    17     }

    7.8 服务暴露总结

    服务提供端配置:

     1     <!-- 提供方应用信息,用于计算依赖关系 -->
     2     <dubbo:application name="demo-provider"/>
     3 
     4     <!-- 使用zookeeper注册中心,并使用curator客户端 -->
     5     <dubbo:registry protocol="zookeeper" address="10.211.55.5:2181" client="curator"/>
     6 
     7     <!-- 用dubbo协议在20880端口暴露服务 -->
     8     <dubbo:protocol name="dubbo" port="20880"/>
     9 
    10     <!-- 和本地bean一样实现服务 -->
    11     <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
    12 
    13     <!-- 声明需要暴露的服务接口 -->
    14     <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

    一 ServiceBean

    1 继承实现关系

    2 最终的ServiceBean实例

    复制代码
    -->String id: "com.alibaba.dubbo.demo.DemoService"
    -->String beanName: "com.alibaba.dubbo.demo.DemoService"
    -->ApplicationContext applicationContext: ClassPathXmlApplicationContext实例
    -->supportedApplicationListener:true
    -->List<URL> urls: ["dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461"]
    -->List<Exporter<?>> exporters:[
      -->InjvmExporter实例
        -->String key:com.alibaba.dubbo.demo.DemoService
        -->Map<String, Exporter<?>> exporterMap: {"com.alibaba.dubbo.demo.DemoService ", 当前的JvmExporter实例}
        -->Invoker invoker:经过filter包装的AbstractProxyInvoker实例
      -->RegistryProtocol返回的新的Exporter实例
        -->Exporter exporter: ExporterChangeableWrapper<T> exporter实例
          -->Invoker originInvoker:经过filter包装的AbstractProxyInvoker实例
          -->Exporter exporter: DubboExporter
        -->Registry registry: 上边的ZookeeperRegistry实例
    ]
    -->String interfaceName:"com.alibaba.dubbo.demo.DemoService"
    -->Class<?> interfaceClass:interface com.alibaba.dubbo.demo.DemoService
    -->T ref: DemoServiceImpl实例
    -->String path:"com.alibaba.dubbo.demo.DemoService"
    -->List<ProtocolConfig> protocols:[解析:<dubbo:protocol name="dubbo" port="20880" id="dubbo" />]
    -->ApplicationConfig application:[解析:<dubbo:application name="demo-provider" id="demo-provider" />]
    -->List<RegistryConfig> registries:[解析:<dubbo:registry address="10.211.55.5:2181" protocol="zookeeper" id="com.alibaba.dubbo.config.RegistryConfig" />]
    复制代码

    二 调用简图

    三 代码调用链

    复制代码
    ServiceBean.onApplicationEvent(ApplicationEvent event)
    -->ServiceConfig.export()
      -->doExport()
        -->doExportUrls()
          -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)
             protocolConfig:<dubbo:protocol name="dubbo" port="20880" id="dubbo" /> 
             registryURLs:registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&pid=3141&registry=zookeeper&timestamp=1510021313960
          	<!-- 一 本地暴露 -->
            -->exportLocal(url)
               url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3141&side=provider&timestamp=1510021401013
              //1.1 将实现类ref封装成Invoker
              -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
                 proxy:DemoServiceImpl实例(即ref实例) 
                 type:interface com.alibaba.dubbo.demo.DemoService  
                 url:injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3141&side=provider&timestamp=1510021401013
                -->Wrapper.getWrapper(Class DemoServiceImpl)
                -->new AbstractProxyInvoker<T>(proxy, type, url)
              //1.2 将实现类Invoker暴露为Exporter
              -->ProtocolFilterWrapper.buildInvokerChain(final Invoker<T> invoker, String key, String group)
                 组建invoker链,实际上只有最后一个是真正的AbstractProxyInvoker实例,前边的都是filter。
                 invoker:AbstractProxyInvoker实例 
                 key:service.filter 
                 group:provider
              -->InjvmProtocol.export(Invoker<T> invoker)
                 invoker:经过filter包装的invoker
                -->new InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap)
                   invoker:经过filter包装的invoker 
                   key:com.alibaba.dubbo.demo.DemoService 
                   exporterMap:传入时为空,构造器执行后为{"com.alibaba.dubbo.demo.DemoService ", 当前的JvmExporter实例}
              -->List<Exporter<?>> exporters.add(上述的exporter)
            <!-- 二 远程暴露 -->
            //2.1 将实现类ref封装成Invoker
            -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
               proxy:DemoServiceImpl实例(即ref实例) 
               type:interface com.alibaba.dubbo.demo.DemoService  
               url:registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&export=dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3141&side=provider&timestamp=1510021401013&pid=3141&registry=zookeeper&timestamp=1510021313960
              -->Wrapper.getWrapper(Class DemoServiceImpl)
              -->new AbstractProxyInvoker<T>(proxy, type, url)
            -->RegistryProtocol.export(final Invoker<T> originInvoker)
               originInvoker:上述的AbstractProxyInvoker实例
              //2.2 将invoker转化为exporter
              -->doLocalExport(originInvoker)
                -->new InvokerDelegete(Invoker<T> invoker, URL url)
                   invoker:原始的AbstractProxyInvoker实例
                   url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461
                -->ProtocolFilterWrapper.buildInvokerChain(final Invoker<T> invoker, String key, String group)
                   组建invoker链,实际上只有最后一个是真正的InvokerDelegete实例,前边的都是filter
                   invoker:InvokerDelegete实例 
                   key:service.filter 
                   group:provider
                -->DubboProtocol.export(Invoker<T> invoker)
                   invoker:经过filter包装的InvokerDelegete实例
                  -->new DubboExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap)
                     invoker:经过filter包装的InvokerDelegete实例 
                     key:com.alibaba.dubbo.demo.DemoService:20880 (group/servicename:version:port)
                     exporterMap:传入时为空,构造器执行后又执行了put,为{"com.alibaba.dubbo.demo.DemoService:20880", 当前的DubboExporter实例}
                  //2.3 开启netty服务端监听客户端请求
                  -->openServer(URL url)
                     url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461
                    -->createServer(URL url)
                        -->HeaderExchanger.bind(URL url, ExchangeHandler handler)
                           url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&channel.readonly.sent=true&codec=dubbo&dubbo=2.0.0&generic=false&heartbeat=60000&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461 handler:DubboProtocol.requestHandler
                          -->new DecodeHandler(new HeaderExchangeHandler(handler)))
                            -->NettyTransporter.bind(URL url, ChannelHandler listener)
                               url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&channel.readonly.sent=true&codec=dubbo&dubbo=2.0.0&generic=false&heartbeat=60000&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461  listener:上边的DecodeHandler实例
                              -->new NettyServer(URL url, ChannelHandler handler)
                                -->ChannelHandler.wrapInternal(ChannelHandler handler, URL url)
                                   handler:上边的DecodeHandler实例 
                                   url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&channel.readonly.sent=true&codec=dubbo&dubbo=2.0.0&generic=false&heartbeat=60000&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&threadname=DubboServerHandler-10.10.10.10:20880&timestamp=1510023456461
                                  -->new MultiMessageHandler(HeartbeatHandler(AllChannelHandler(handler)))
                                -->getChannelCodec(url)//获取Codec2,这里是DubboCountCodec实例
                                -->doOpen()//开启netty服务
                          -->new HeaderExchangeServer(Server server)
                             server:上述的NettyServer
                            -->startHeatbeatTimer()
                -->new ExporterChangeableWrapper(Exporter<T> exporter, Invoker<T> originInvoker)
                   exporter:上述的DubboExporter实例  
                   originInvoker:原始的AbstractProxyInvoker实例
              //2.4 创建Registry:创建zkclient,连接zk
              -->getRegistry(final Invoker<?> originInvoker)
                -->AbstractRegistryFactory.getRegistry(URL url)
                   url:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461&pid=3508&timestamp=1510023439825
                  -->ZookeeperRegistryFactory.createRegistry(URL url)
                    -->new ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter)
                       url:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=3508&timestamp=1510023439825
                      -->ZkclientZookeeperTransporter.connect(URL url)
                        -->new ZkclientZookeeperClient(URL url)
                           url:zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=3508&timestamp=1510023439825
                          -->new ZkClient(url.getBackupAddress())//这里是10.211.55.5:2181
                -->AbstractRegistryFactory.Map<String, Registry> REGISTRIES.put("zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService", 上边的ZookeeperRegistry实例)
              //2.5 向注册中心注册服务
              -->registry.register(registedProviderUrl)
                -->ZookeeperRegistry.doRegister(URL url)
                   url:dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461
                  -->AbstractZookeeperClient.create(String path, boolean ephemeral)
                     path:/dubbo/com.alibaba.dubbo.demo.DemoService/providers/dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461  
                     ephemeral=true
              //2.6 订阅override数据
              -->ZookeeperRegistry.doSubscribe(final URL url, final NotifyListener listener)
                 url:provider://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&category=configurators&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3508&side=provider&timestamp=1510023456461  
                 listener:RegistryProtocol.OverrideListener实例
              //2.7 创建新的Exporter实例
              -->new Exporter<T>()//包含了上边的ExporterChangeableWrapper<T> exporter实例 + ZookeeperRegistry实例

    8.1 构建客户端总体流程

    一 示例

    1 配置文件:

    复制代码
     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3        xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
     4        xmlns="http://www.springframework.org/schema/beans"
     5        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     6     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
     7     <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
     8     <dubbo:application name="demo-consumer"/>
     9     <!-- 使用zookeeper注册中心 -->
    10     <dubbo:registry protocol="zookeeper" address="10.211.55.5:2181"/>
    11     <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
    12     <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>
    13 </beans>
    复制代码

    2 Consumer

    复制代码
     1 package com.alibaba.dubbo.demo.consumer;
     2 
     3 import com.alibaba.dubbo.demo.DemoService;
     4 import org.springframework.context.support.ClassPathXmlApplicationContext;
     5 
     6 public class Consumer {
     7     public static void main(String[] args) {
     8         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
     9         context.start();
    10 
    11         DemoService demoService = (DemoService) context.getBean("demoService"); // 获取远程服务代理
    12         String hello = demoService.sayHello("world"); // 执行远程方法
    13 
    14         System.out.println(hello); // 显示调用结果
    15     }
    16 }
    复制代码

    先来看DemoService demoService = (DemoService) context.getBean("demoService"); // 获取远程服务代理。

    二 调用简图

    三 总体代码调用链

    复制代码
    ReferenceConfig.init()
    -->createProxy(Map<String, String> map)
      //一 获取Invoker
      -->RegistryProtocol.refer(Class<T> type, URL url)
        //1 获取注册中心:创建ZkClient实例,连接zk
        -->Registry registry = registryFactory.getRegistry(url)
          -->AbstractRegistryFactory.getRegistry(URL url)
            -->ZookeeperRegistryFactory.createRegistry(URL url)
              -->new ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter)
                -->ZkclientZookeeperTransporter.connect(URL url)
                  -->new ZkclientZookeeperClient(URL url)
                    -->new ZkClient(url.getBackupAddress())
                -->AbstractRegistryFactory.Map<String, Registry> REGISTRIES.put("zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService", 上边的ZookeeperRegistry实例)
        -->doRefer(Cluster cluster, Registry registry, Class<T> type, URL url)
          -->new RegistryDirectory<T>(type, url)
          //2 向注册中心注册服务
          -->registry.register(url)
            -->ZookeeperRegistry.doRegister(URL url)
              -->AbstractZookeeperClient.create(String path, boolean ephemeral)
        //3 订阅providers、configurators、routers -->RegistryDirectory.subscribe(URL url) -->ZookeeperRegistry.doSubscribe(final URL url, final NotifyListener listener) //3.1 会获取当前节点下已经存在的字节点(第一次服务发现发生在这里),添加子节点变化监听器 -->List<String> children = zkClient.addChildListener(path, zkListener) -->AbstractRegistry.notify(URL url, NotifyListener listener, List<URL> urls) -->saveProperties(url) -->RegistryDirectory.notify(List<URL> urls) //仅仅针对的是providers -->refreshInvoker(List<URL> invokerUrls) -->toInvokers(List<URL> urls -->ProtocolFilterWrapper.refer(Class<T> type, URL url) -->DubboProtocol.refer(Class<T> serviceType, URL url) //3.1.1 创建ExchangeClient,对第一次服务发现providers路径下的相关url建立长连接 -->getClients(URL url) -->getSharedClient(URL url) -->ExchangeClient exchangeClient = initClient(url) -->Exchangers.connect(url, requestHandler) -->HeaderExchanger.connect(URL url, ExchangeHandler handler) -->new DecodeHandler(new HeaderExchangeHandler(handler))) -->Transporters.connect(URL url, ChannelHandler... handlers) -->NettyTransporter.connect(URL url, ChannelHandler listener) -->new NettyClient(url, listener) -->new MultiMessageHandler(HeartbeatHandler(AllChannelHandler(handler))) -->getChannelCodec(url)//获取Codec2,这里是DubboCountCodec实例 -->doOpen()//开启netty客户端 -->doConnect()//连接服务端,建立长连接 -->new HeaderExchangeClient(Client client, boolean needHeartbeat)//上述的NettyClient实例,needHeartbeat:true -->startHeatbeatTimer()//启动心跳计数器 -->ReferenceCountExchangeClient(ExchangeClient client, ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap)/ -->Map<String, ReferenceCountExchangeClient> referenceClientMap.put("10.10.10.10:20880", 上边的ReferenceCountExchangeClient实例) //3.2 创建DubboInvoker -->new DubboInvoker(Class<T> serviceType, URL url, ExchangeClient[] clients, Set<Invoker<?>> invokers) -->DubboProtocol.Set<Invoker<?>> invokers.add(上边的DubboInvoker实例) -->ProtocolFilterWrapper.buildInvokerChain(final Invoker<T> invoker, String key, String group) -->new InvokerDelegete(Invoker<T> invoker, URL url, URL providerUrl) //3.3 将创建出来的Invoker缓存起来 -->newUrlInvokerMap.put("dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=16001&register.ip=10.10.10.10&remote.timestamp=1510127991625&side=consumer&timestamp=1510128022123", 上边的InvokerDelegate实例) -->toMethodInvokers(newUrlInvokerMap) -->Map<String, List<Invoker<T>>> newMethodInvokerMap:{sayHello=[InvokerDelegete实例], *=[InvokerDelegete实例]} //4 将directory封装成一个ClusterInvoker(MockClusterInvoker) -->cluster.join(directory) -->Cluster$Adaptive.join(directory) -->ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("failover")//MockClusterWrapper包装FailoverCluster -->MockClusterWrapper.join(Directory<T> directory) -->FailoverCluster.join(Directory<T> directory) -->new FailoverClusterInvoker<T>(directory) -->MockClusterInvoker(Directory<T> directory, Invoker<T> invoker)//invoker:上边的FailoverClusterInvoker实例 //二 获取代理 -->JavassistProxyFactory.getProxy(Invoker<T> invoker, Class<?>[] interfaces)//invoker:上边的MockClusterInvoker实例, interfaces:[interface com.alibaba.dubbo.demo.DemoService, interface com.alibaba.dubbo.rpc.service.EchoService] -->Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)) -->Proxy.getProxy(ClassLoader cl, Class<?>... ics)//使用javassist获取一个动态类 -->new InvokerInvocationHandler(invoker)//invoker:上边的MockClusterInvoker实例
    复制代码

    极简版流程图:

    复制代码
    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)
    复制代码

    在服务端会根据RpcInvocation中的attachments中的path、group、version以及从channel中获取的port拼接一个serviceKey:group/path:version:port,例如:dev/com.alibaba.dubbo.demo.DemoService:2.0.0:20880,之后根据这个serviceKey从服务端获取DubboExporter。

    注意:

    1、每一个代理都会有自己的一个MockClusterInvoker,也就有自己的一个RegistryDirectory,所以,假设A引用了服务B和C,B和C中都有sayHello(String name)方法时,B的RegistryDirectory存储自己的sayHello=[Invoker对象],C的RegistryDirectory存储自己的sayHello=[Invoker对象]。

    2、假设A调用了服务B,B中有方法String sayHello(String name)/String sayHello(String name, Integer age)时,在B的RegistryDirectory只存储一份的sayHello=[Invoker对象],注意这里一个Invoker对象其实对应一个Provider实例,根据从serviceKey:group/path:version:port获取出DubboExporter,DubboExporter获取到AbstractProxyInvoker,AbstractProxyInvoker中的wrapper类中就有String sayHello(String name)/String sayHello(String name, Integer age)两个方法,根据Request中的方法名、参数类型和参数值就可以找出执行哪一个方法。

    准备工作:

    先启动两个provider:

    • dubbo://10.211.55.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.5.7&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=318&revision=2.5.7&side=provider&timestamp=1510225244315
    • dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25215&side=provider&timestamp=1510225334486

    来看一下ReferenceBean的继承实现关系图:

    在执行DemoService demoService = (DemoService) context.getBean("demoService")时,由于ReferenceBean是一个FactoryBean,所以这里会通过FactoryBean.getObject方法获取Bean。

    来看一下ReferenceBean的核心代码:

    复制代码
        public Object getObject() throws Exception {
            return get();
        }
    
        public synchronized T get() {
            if (destroyed) {
                throw new IllegalStateException("Already destroyed!");
            }
            if (ref == null) {
                init();
            }
            return ref;
        }
    
        private void init() {
            ...
            ref = createProxy(map);
        }
    
        private T createProxy(Map<String, String> map) {
            ...
            if (urls.size() == 1) {
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } 
            ...
            // 创建服务代理
            return (T) proxyFactory.getProxy(invoker);
        }
    复制代码

    最核心的两行代码如上红色。

    一 使用Protocol将interfaceClass转化为Invoker

    1 invoker = refprotocol.refer(interfaceClass, urls.get(0))

    这里的refprotocol是Protocol$Adaptive实例。

    复制代码
    public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
        ...
        public com.alibaba.dubbo.rpc.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
            if (arg1 == null)
                throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg1;
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if(extName == null)
                throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
        }
        ...
    }
    复制代码

    这里extName="registry"。之后经过ProtocolListenerWrapper.refer->ProtocolFilterWrapper.refer->RegistryProtocol.refer,前两步什么都不做(registry协议)。来看RegistryProtocol.refer方法核心代码:

    1     public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    2         url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    3         Registry registry = registryFactory.getRegistry(url);
    4         ...
    5         return doRefer(cluster, registry, type, url);
    6     }

    参数:

    • url:registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=25267&refer=application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509&registry=zookeeper&timestamp=1510225984358
    • type: interface com.alibaba.dubbo.demo.DemoService

    第一行代码执行完成之后,替换了协议,此时的url为:

    zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=25267&refer=application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509&timestamp=1510225984358

    之后开始获取Registry。这里的registryFactory是RegistryFactory$Adaptive实例。

    复制代码
     1 public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
     2     public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
     3         if (arg0 == null)
     4             throw new IllegalArgumentException("url == null");
     5         com.alibaba.dubbo.common.URL url = arg0;
     6         String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );//zookeeper
     7         if(extName == null)
     8             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
     9         com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
    10         return extension.getRegistry(arg0);
    11     }
    12 }
    复制代码

    这里的extName是zookeeper。之后执行ZookeeperRegistryFactory的父类AbstractRegistryFactory.getRegistry,如下:

    复制代码
     1     public Registry getRegistry(URL url) {
     2         url = url.setPath(RegistryService.class.getName())
     3                 .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
     4                 .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
     5         String key = url.toServiceString();
     6         // 锁定注册中心获取过程,保证注册中心单一实例
     7         LOCK.lock();
     8         try {
     9             Registry registry = REGISTRIES.get(key);
    10             if (registry != null) {
    11                 return registry;
    12             }
    13             registry = createRegistry(url);
    14             if (registry == null) {
    15                 throw new IllegalStateException("Can not create registry " + url);
    16             }
    17             REGISTRIES.put(key, registry);
    18             return registry;
    19         } finally {
    20             // 释放锁
    21             LOCK.unlock();
    22         }
    23     }
    复制代码

    经过处理的url为:

    zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=25267&timestamp=1510225984358

    之后调用ZookeeperRegistryFactory.createRegistry(URL url):

    1     public Registry createRegistry(URL url) {
    2         return new ZookeeperRegistry(url, zookeeperTransporter);
    3     }

    这里的zookeeperTransporter为ZookeeperTransporter$Adaptive实例。

    复制代码
     1     public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
     2         super(url);
     3         if (url.isAnyHost()) {
     4             throw new IllegalStateException("registry address == null");
     5         }
     6         String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
     7         if (!group.startsWith(Constants.PATH_SEPARATOR)) {
     8             group = Constants.PATH_SEPARATOR + group;
     9         }
    10         this.root = group;
    11         zkClient = zookeeperTransporter.connect(url);
    12         zkClient.addStateListener(new StateListener() {
    13             public void stateChanged(int state) {
    14                 if (state == RECONNECTED) {
    15                     try {
    16                         recover();
    17                     } catch (Exception e) {
    18                         logger.error(e.getMessage(), e);
    19                     }
    20                 }
    21             }
    22         });
    23     }
    复制代码

    通过super(url)这句代码,调用了ZookeeperRegistry的父类FailbackRegistry(启动失败处理器:注册失败/注销失败/订阅失败/反订阅失败/通知失败)和AbstractRegistry(将信息写入properties文件,进行相应通知-这里没有url的订阅器,所以没做什么事)。

    然后获取ZkClient客户端,最后添加失败重连监听器。

    执行zookeeperTransporter.connect(url),该类中的extName是"zkClient"(我们在provider部分使用了curator)。之后执行ZkclientZookeeperTransporter.connect:

    1     public ZookeeperClient connect(URL url) {
    2         return new ZkclientZookeeperClient(url);
    3     }
    复制代码
     1     public ZkclientZookeeperClient(URL url) {
     2         super(url);
     3         client = new ZkClientWrapper(url.getBackupAddress(), 30000);
     4         client.addListener(new IZkStateListener() {
     5             public void handleStateChanged(KeeperState state) throws Exception {
     6                 ZkclientZookeeperClient.this.state = state;
     7                 if (state == KeeperState.Disconnected) {
     8                     stateChanged(StateListener.DISCONNECTED);
     9                 } else if (state == KeeperState.SyncConnected) {
    10                     stateChanged(StateListener.CONNECTED);
    11                 }
    12             }
    13 
    14             public void handleNewSession() throws Exception {
    15                 stateChanged(StateListener.RECONNECTED);
    16             }
    17         });
    18         client.start();
    19     }
    复制代码

    此处的client是ZkClientWrapper实例,来看ZkClientWrapper.start():

    复制代码
     1     private ListenableFutureTask<ZkClient> listenableFutureTask;
     2 
     3     public ZkClientWrapper(final String serverAddr, long timeout) {
     4         this.timeout = timeout;
     5         listenableFutureTask = ListenableFutureTask.create(new Callable<ZkClient>() {
     6             @Override
     7             public ZkClient call() throws Exception {
     8                 return new ZkClient(serverAddr, Integer.MAX_VALUE);
     9             }
    10         });
    11     }
    12 
    13     public void start() {
    14         if (!started) {
    15             Thread connectThread = new Thread(listenableFutureTask);
    16             connectThread.setName("DubboZkclientConnector");
    17             connectThread.setDaemon(true);
    18             connectThread.start();
    19             try {
    20                 client = listenableFutureTask.get(timeout, TimeUnit.MILLISECONDS);
    21             } catch (Throwable t) {
    22                 logger.error("Timeout! zookeeper server can not be connected in : " + timeout + "ms!", t);
    23             }
    24             started = true;
    25         } else {
    26             logger.warn("Zkclient has already been started!");
    27         }
    28     }
    复制代码

    此处会new ZkClient,连接zookeeper。

    之后设置失败重连监听器。到此为止,创建Registry就完成了!再回到RegistryProtocol.refer方法核心代码:

    1     public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    2         url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    3         Registry registry = registryFactory.getRegistry(url);
    4         ...
    5         return doRefer(cluster, registry, type, url);
    6     }

    之后执行最后一行代码:

    复制代码
     1     private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
     2         RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
     3         directory.setRegistry(registry);
     4         directory.setProtocol(protocol);
     5         // REFER_KEY的所有属性
     6         Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
     7         URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
     8         if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
     9                 && url.getParameter(Constants.REGISTER_KEY, true)) {
    10             registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
    11                     Constants.CHECK_KEY, String.valueOf(false)));
    12         }
    13         directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
    14                 Constants.PROVIDERS_CATEGORY
    15                         + "," + Constants.CONFIGURATORS_CATEGORY
    16                         + "," + Constants.ROUTERS_CATEGORY));
    17         return cluster.join(directory);
    18     }
    复制代码

    总体步骤:

    • 首先创建RegistryDirectory实例;
    • 之后向zk注册消费者
    • 然后开启监听器(此处发生了第一次服务发现/长连接的建立/netty客户端的建立)
    • 最后使用将RegistryDirectory实例

     首先是创建RegistryDirectory,创建完成的实例:

    复制代码
    -->List<Router> routers: [MockInvokersSelector实例]
    -->Registry registry: 上述的ZookeeperRegistry实例(zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&interface=com.alibaba.dubbo.registry.RegistryService&pid=25267&timestamp=1510225984358)
    -->String serviceKey: com.alibaba.dubbo.registry.RegistryService
    -->String[] serviceMethods: [sayHello]
    -->Class<T> serviceType: interface com.alibaba.dubbo.demo.DemoService
    -->URL url: zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=25267&refer=application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509&timestamp=1510225984358
    -->URL consumerUrl: zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=25267&refer=application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509&timestamp=1510225984358
    -->URL directoryUrl: zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509
    -->URL overrideDirectoryUrl: zookeeper://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&side=consumer&timestamp=1510225913509
    -->Map<String, String> queryMap: {side=consumer, application=demo-consumer, register.ip=10.10.10.10, methods=sayHello, dubbo=2.0.0, pid=25267, check=false, interface=com.alibaba.dubbo.demo.DemoService, timestamp=1510225913509}
    复制代码

    其中Router是在RegistryDirectory的父类AbstractDirectory中创建的,代码如下:

    复制代码
        public AbstractDirectory(URL url, URL consumerUrl, List<Router> routers) {
            if (url == null)
                throw new IllegalArgumentException("url == null");
            this.url = url;
            this.consumerUrl = consumerUrl;
            setRouters(routers);
        }
    
        protected void setRouters(List<Router> routers) {
            // copy list
            routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
            // append url router
            String routerkey = url.getParameter(Constants.ROUTER_KEY);
            if (routerkey != null && routerkey.length() > 0) {
                RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
                routers.add(routerFactory.getRouter(url));
            }
            // append mock invoker selector
            routers.add(new MockInvokersSelector());
            Collections.sort(routers);
            this.routers = routers;
        }
    复制代码

    之后向注册中心注册消费者,注册的方式与服务提供者一样。先是通过FailbackRegistry.register,内部调用子类ZookeeperRegistry的doRegister(),如果失败,加入注册失败列表(会被修复线程后台重新注册)。

    复制代码
     1     public void register(URL url) {
     2         if (destroyed.get()){
     3             return;
     4         }
     5         super.register(url);
     6         failedRegistered.remove(url);
     7         failedUnregistered.remove(url);
     8         try {
     9             // 向服务器端发送注册请求
    10             doRegister(url);
    11         } catch (Exception e) {
    12             Throwable t = e;
    13 
    14             // 如果开启了启动时检测,则直接抛出异常
    15             boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
    16                     && url.getParameter(Constants.CHECK_KEY, true)
    17                     && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
    18             boolean skipFailback = t instanceof SkipFailbackWrapperException;
    19             if (check || skipFailback) {
    20                 if (skipFailback) {
    21                     t = t.getCause();
    22                 }
    23                 throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
    24             } else {
    25                 logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
    26             }
    27 
    28             // 将失败的注册请求记录到失败列表,定时重试
    29             failedRegistered.add(url);
    30         }
    31     }
    复制代码

    最后来看ZookeeperRegistry的doRegister方法:

    复制代码
    1     protected void doRegister(URL url) {
    2         try {
    3             zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    4         } catch (Throwable e) {
    5             throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    6         }
    7     }
    复制代码

    在zk上创建临时节点:/dubbo/com.alibaba.dubbo.demo.DemoService/consumers/consumer://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509

    到此,消费者注册完成!之后directory.subscribe进行订阅。RegistryDirectory.subscribe(URL url):

    1     public void subscribe(URL url) {
    2         setConsumerUrl(url);
    3         registry.subscribe(url, this);
    4     }

    FailbackRegistry.subscribe(URL url, NotifyListener listener)核心代码:

    复制代码
     1     public void subscribe(URL url, NotifyListener listener) {
     2         ...
     3         super.subscribe(url, listener);
     4         removeFailedSubscribed(url, listener);
     5         try {
     6             // 向服务器端发送订阅请求
     7             doSubscribe(url, listener);
     8         } catch (Exception e) {
     9             ...
    10             // 将失败的订阅请求记录到失败列表,定时重试
    11             addFailedSubscribed(url, listener);
    12         }
    13     }
    复制代码

    ZookeeperRegistry.doSubscribe(final URL url, final NotifyListener listener)

    复制代码
     1     protected void doSubscribe(final URL url, final NotifyListener listener) {
     2         try {
     3             ...
     4             List<URL> urls = new ArrayList<URL>();
     5             for (String path : toCategoriesPath(url)) {
     6                 ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
     7                 if (listeners == null) {
     8                     zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
     9                     listeners = zkListeners.get(url);
    10                 }
    11                 ChildListener zkListener = listeners.get(listener);
    12                 if (zkListener == null) {
    13                     listeners.putIfAbsent(listener, new ChildListener() {
    14                         public void childChanged(String parentPath, List<String> currentChilds) {
    15                             ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
    16                         }
    17                     });
    18                     zkListener = listeners.get(listener);
    19                 }
    20                 zkClient.create(path, false);
    21                 List<String> children = zkClient.addChildListener(path, zkListener);
    22                 if (children != null) {
    23                     urls.addAll(toUrlsWithEmpty(url, path, children));
    24                 }
    25             }
    26             notify(url, listener, urls);
    27         } catch (Throwable e) {
    28             throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    29         }
    30     }
    复制代码

    这里的for循环是3次:

    • /dubbo/com.alibaba.dubbo.demo.DemoService/providers
    • /dubbo/com.alibaba.dubbo.demo.DemoService/configurators
    • /dubbo/com.alibaba.dubbo.demo.DemoService/routers

    执行完上述for循环后,来看此时的:

    ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners:

    1 {
    2 consumer://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509
    3 =
    4 {RegistryDirectory实例=ZookeeperRegistry中的匿名内部类ChildListener实例}
    5 }

    List<URL> urls:(4个元素)

    复制代码
    [
    dubbo://10.211.55.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.5.7&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=318&revision=2.5.7&side=provider&timestamp=1510225244315, 
    
    dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25215&side=provider&timestamp=1510225334486, 
    
    empty://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=configurators&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509, 
    
    empty://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=routers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509
    ]
    复制代码

    注意:前边两个元素是在执行List<String> children = zkClient.addChildListener(path, zkListener)代码时,会返回当前path下的节点(实际上就是第一次服务发现)。

    之后一路执行到AbstractRegistry.notify(URL url, NotifyListener listener, List<URL> urls)

    复制代码
     1     protected void notify(URL url, NotifyListener listener, List<URL> urls) {
     2         ...
     3         Map<String, List<URL>> result = new HashMap<String, List<URL>>();
     4         for (URL u : urls) {
     5             if (UrlUtils.isMatch(url, u)) {
     6                 String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
     7                 List<URL> categoryList = result.get(category);
     8                 if (categoryList == null) {
     9                     categoryList = new ArrayList<URL>();
    10                     result.put(category, categoryList);
    11                 }
    12                 categoryList.add(u);
    13             }
    14         }
    15         if (result.size() == 0) {
    16             return;
    17         }
    18         Map<String, List<URL>> categoryNotified = notified.get(url);
    19         if (categoryNotified == null) {
    20             notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
    21             categoryNotified = notified.get(url);
    22         }
    23         for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
    24             String category = entry.getKey();
    25             List<URL> categoryList = entry.getValue();
    26             categoryNotified.put(category, categoryList);
    27             saveProperties(url);
    28             listener.notify(categoryList);
    29         }
    30     }
    复制代码

    首先是一个for循环对传入的url列表进行分类,分类结果如下:

    Map<String, List<URL>> result:

    复制代码
    {
    configurators=[
    empty://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=configurators&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509
    ], 
    
    routers=[
    empty://10.10.10.10/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=routers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&side=consumer&timestamp=1510225913509
    ], 
    
    providers=[
    dubbo://10.211.55.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.5.7&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=318&revision=2.5.7&side=provider&timestamp=1510225244315, 
    
    dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25215&side=provider&timestamp=1510225334486
    ]
    }
    复制代码

    之后执行第二个for循环,对上述的result进行遍历,分别进行保存文件和通知。其中前两个entry没做什么核心事,直接来看providers的entry的通知。代码RegistryDirectory.

    notify(List<URL> urls)。这里的urls就是上边的providers的两个value值。

    复制代码
     1     public synchronized void notify(List<URL> urls) {
     2         List<URL> invokerUrls = new ArrayList<URL>();
     3         List<URL> routerUrls = new ArrayList<URL>();
     4         List<URL> configuratorUrls = new ArrayList<URL>();
     5         for (URL url : urls) {
     6             String protocol = url.getProtocol();
     7             String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
     8             if (Constants.ROUTERS_CATEGORY.equals(category)
     9                     || Constants.ROUTE_PROTOCOL.equals(protocol)) {
    10                 routerUrls.add(url);
    11             } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
    12                     || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
    13                 configuratorUrls.add(url);
    14             } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
    15                 invokerUrls.add(url);
    16             } else {
    17                 logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
    18             }
    19         }
    20         // configurators
    21         if (configuratorUrls != null && configuratorUrls.size() > 0) {
    22             this.configurators = toConfigurators(configuratorUrls);
    23         }
    24         // routers
    25         if (routerUrls != null && routerUrls.size() > 0) {
    26             List<Router> routers = toRouters(routerUrls);
    27             if (routers != null) { // null - do nothing
    28                 setRouters(routers);
    29             }
    30         }
    31         List<Configurator> localConfigurators = this.configurators; // local reference
    32         // 合并override参数
    33         this.overrideDirectoryUrl = directoryUrl;
    34         if (localConfigurators != null && localConfigurators.size() > 0) {
    35             for (Configurator configurator : localConfigurators) {
    36                 this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
    37             }
    38         }
    39         // providers
    40         refreshInvoker(invokerUrls);
    41     }
    复制代码

    这里首先将输入的两个provider的url存放在invokerUrls列表中,之后调用refreshInvoker(invokerUrls)。

    复制代码
     1     private void refreshInvoker(List<URL> invokerUrls) {
     2             ...
     3             Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
     4             ...
     5             this.cachedInvokerUrls = new HashSet<URL>();
     6             this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比
     7             ...
     8             Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// 将URL列表转成Invoker列表
     9             Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
    10             this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
    11             this.urlInvokerMap = newUrlInvokerMap;
    12             ...
    13     }
    复制代码
    复制代码
     1     private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
     2         Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
     3         ...
     4         for (URL providerUrl : urls) {
     5             String key = url.toFullString(); // URL参数是排序的
     6             ...
     7             Invoker<T> invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
     8 
     9             newUrlInvokerMap.put(key, invoker);
    10         }
    11         ...
    12         return newUrlInvokerMap;
    13     }
    复制代码

    这里会遍历两个providerUrl:protocol是Protocol$Adaptive实例,依旧是走listener->filter->DubboProtocol,看一下filter部分:

    1     public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    2         if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
    3             return protocol.refer(type, url);
    4         }
    5         return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    6     }

    两个常量是:reference.filter和consumer。最后来看DubboProtocol.refer

    1     public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    2         // create rpc invoker.
    3         DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    4         invokers.add(invoker);
    5         return invoker;
    6     }

    这里首先执行getClients创建Netty客户端,创建客户端与服务端的长连接,之后封装为DubboInvoker,最后返回。返回之后进行filter链包装该DubboInvoker实例。最后又会使用InvokerDelegete包装带有filter链的DubboInvoker实例。在最后,将该InvokerDelegete实例放置到newUrlInvokerMap缓存中,这就是整个toInvokers(List<URL> urls)的逻辑。最后再将newUrlInvokerMap转换封装到Map<String, List<Invoker<T>>> newMethodInvokerMap缓存中。这就是整个refreshInvoker(List<URL> invokerUrls)的逻辑。执行完成之后,订阅通知就执行完了。

    来看一下getClients(url):

    复制代码
     1     private ExchangeClient[] getClients(URL url) {
     2         //是否共享连接
     3         boolean service_share_connect = false;
     4         int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
     5         //如果connections不配置,则共享连接,否则每服务每连接
     6         if (connections == 0) {
     7             service_share_connect = true;
     8             connections = 1;
     9         }
    10 
    11         ExchangeClient[] clients = new ExchangeClient[connections];
    12         for (int i = 0; i < clients.length; i++) {
    13             if (service_share_connect) {
    14                 clients[i] = getSharedClient(url);
    15             } else {
    16                 clients[i] = initClient(url);
    17             }
    18         }
    19         return clients;
    20     }
    21 
    22     /**
    23      * 获取共享连接
    24      */
    25     private ExchangeClient getSharedClient(URL url) {
    26         String key = url.getAddress();
    27         ReferenceCountExchangeClient client = referenceClientMap.get(key);
    28         if (client != null) {
    29             if (!client.isClosed()) {
    30                 client.incrementAndGetCount();
    31                 return client;
    32             } else {
    33                 referenceClientMap.remove(key);
    34             }
    35         }
    36         synchronized (key.intern()) {
    37             ExchangeClient exchangeClient = initClient(url);
    38             client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
    39             referenceClientMap.put(key, client);
    40             ghostClientMap.remove(key);
    41             return client;
    42         }
    43     }
    44 
    45 
    46     /**
    47      * 创建新连接.
    48      */
    49     private ExchangeClient initClient(URL url) {
    50 
    51         // client type setting.
    52         String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
    53 
    54         String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
    55         boolean compatible = (version != null && version.startsWith("1.0."));
    56         url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    57         //默认开启heartbeat
    58         url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
    59 
    60         // BIO存在严重性能问题,暂时不允许使用
    61         if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
    62             throw new RpcException("Unsupported client type: " + str + "," +
    63                     " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
    64         }
    65 
    66         ExchangeClient client;
    67         try {
    68             //设置连接应该是lazy的 
    69             if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
    70                 client = new LazyConnectExchangeClient(url, requestHandler);
    71             } else {
    72                 client = Exchangers.connect(url, requestHandler);
    73             }
    74         } catch (RemotingException e) {
    75             throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
    76         }
    77         return client;
    78     }
    复制代码

    注意:这里由于使用了共享链接,实际上就是在一个消费者机器和一个服务提供者机器之间只建立一条nio长连接,也可以指定连接数,那样就会建立多条连接。

    最后执行到HeaderExchanger.connect(URL url, ExchangeHandler handler)

    1     public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    2         return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    3     }

    执行Transporters.connect:

    复制代码
     1     public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
     2         if (url == null) {
     3             throw new IllegalArgumentException("url == null");
     4         }
     5         ChannelHandler handler;
     6         if (handlers == null || handlers.length == 0) {
     7             handler = new ChannelHandlerAdapter();
     8         } else if (handlers.length == 1) {
     9             handler = handlers[0];
    10         } else {
    11             handler = new ChannelHandlerDispatcher(handlers);
    12         }
    13         return getTransporter().connect(url, handler);
    14     }
    复制代码

    执行NettyTransporter.connect:

    1     public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    2         return new NettyClient(url, listener);
    3     }
    1     public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
    2         super(url, wrapChannelHandler(url, handler));
    3     }

    这里继续包装handler。和provider一样,6层。之后进行一系列的赋值后,打开netty客户端:

    复制代码
     1     protected void doOpen() throws Throwable {
     2         NettyHelper.setNettyLoggerFactory();
     3         bootstrap = new ClientBootstrap(channelFactory);
     4         // config
     5         // @see org.jboss.netty.channel.socket.SocketChannelConfig
     6         bootstrap.setOption("keepAlive", true);
     7         bootstrap.setOption("tcpNoDelay", true);
     8         bootstrap.setOption("connectTimeoutMillis", getTimeout());
     9         final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    10         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
    11             public ChannelPipeline getPipeline() {
    12                 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
    13                 ChannelPipeline pipeline = Channels.pipeline();
    14                 pipeline.addLast("decoder", adapter.getDecoder());
    15                 pipeline.addLast("encoder", adapter.getEncoder());
    16                 pipeline.addLast("handler", nettyHandler);
    17                 return pipeline;
    18             }
    19         });
    20     }
    复制代码

    之后进行连接netty服务端:

    复制代码
     1     protected void doConnect() throws Throwable {
     2         long start = System.currentTimeMillis();
     3         ChannelFuture future = bootstrap.connect(getConnectAddress());
     4         try {
     5             boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS);
     6 
     7             if (ret && future.isSuccess()) {
     8                 Channel newChannel = future.getChannel();
     9                 newChannel.setInterestOps(Channel.OP_READ_WRITE);
    10                 try {
    11                     // 关闭旧的连接
    12                     Channel oldChannel = NettyClient.this.channel; // copy reference
    13                     if (oldChannel != null) {
    14                         try {
    15                             if (logger.isInfoEnabled()) {
    16                                 logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel);
    17                             }
    18                             oldChannel.close();
    19                         } finally {
    20                             NettyChannel.removeChannelIfDisconnected(oldChannel);
    21                         }
    22                     }
    23                 } finally {
    24                     if (NettyClient.this.isClosed()) {
    25                         try {
    26                             if (logger.isInfoEnabled()) {
    27                                 logger.info("Close new netty channel " + newChannel + ", because the client closed.");
    28                             }
    29                             newChannel.close();
    30                         } finally {
    31                             NettyClient.this.channel = null;
    32                             NettyChannel.removeChannelIfDisconnected(newChannel);
    33                         }
    34                     } else {
    35                         NettyClient.this.channel = newChannel;
    36                     }
    37                 }
    38             } else if (future.getCause() != null) {
    39                 throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
    40                         + getRemoteAddress() + ", error message is:" + future.getCause().getMessage(), future.getCause());
    41             } else {
    42                 throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
    43                         + getRemoteAddress() + " client-side timeout "
    44                         + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
    45                         + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
    46             }
    47         } finally {
    48             if (!isConnected()) {
    49                 future.cancel();
    50             }
    51         }
    52     }
    复制代码

    到此为止NettyClient就创建好了,之后将该client封装为HeaderExchangeClient中。

    复制代码
     1     public HeaderExchangeClient(Client client, boolean needHeartbeat) {
     2         if (client == null) {
     3             throw new IllegalArgumentException("client == null");
     4         }
     5         this.client = client;
     6         this.channel = new HeaderExchangeChannel(client);
     7         String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
     8         this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);
     9         this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
    10         if (heartbeatTimeout < heartbeat * 2) {
    11             throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
    12         }
    13         if (needHeartbeat) {
    14             startHeatbeatTimer();
    15         }
    16     }
    复制代码

    启动心跳。

    最后将HeaderExchangeClient实例封装为ReferenceCountExchangeClient:

    复制代码
    1     public ReferenceCountExchangeClient(ExchangeClient client, ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap) {
    2         this.client = client;
    3         refenceCount.incrementAndGet();
    4         this.url = client.getUrl();
    5         if (ghostClientMap == null) {
    6             throw new IllegalStateException("ghostClientMap can not be null, url: " + url);
    7         }
    8         this.ghostClientMap = ghostClientMap;
    9     }
    复制代码

    最后放到缓存Map<String, ReferenceCountExchangeClient> referenceClientMap中。最后将ReferenceCountExchangeClient封装到DubboInvoker中。我们来看此时的DubboInvoker:

    -->Map<String, String> attachment: {interface=com.alibaba.dubbo.demo.DemoService}
    -->ExchangeClient[] clients:[ReferenceCountExchangeClient实例]//如果设置了多条连接,此处有多个client
    -->Class<T> type: interface com.alibaba.dubbo.demo.DemoService
    -->Url url: dubbo://10.211.55.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.5.7&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&remote.timestamp=1510225244315&revision=2.5.7&side=consumer&timestamp=1510225913509

    之后对DubboInvoker实例进行filter链的包装。

    ConsumerContextFilter->FutureFilter->MonitorFilter->DubboInvoker.

    最后将包装后的Invoker实例包装为InvokerDelegete实例。最后的最后,我们的终极目的:初始化RegistryDirectory的两个属性:

    Map<String, List<Invoker<T>>> methodInvokerMap={
    sayHello=[provider1的RegistryDirectory$InvokerDelegete实例, provider2的RegistryDirectory$InvokerDelegete实例], *=[provider1的RegistryDirectory$InvokerDelegete实例, provider2的RegistryDirectory$InvokerDelegete实例]}
    
    Map<String, Invoker<T>> urlInvokerMap={dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&remote.timestamp=1510225334486&side=consumer&timestamp=1510225913509
    =
    provider1的RegistryDirectory$InvokerDelegete实例, dubbo://10.211.55.5:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.5.7&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=25267&register.ip=10.10.10.10&remote.timestamp=1510225244315&revision=2.5.7&side=consumer&timestamp=1510225913509=provider2的RegistryDirectory$InvokerDelegete实例}

    到此为止,订阅就完成了。现在来看RegistryProtocol.doRefer的最后一行代码:return cluster.join(directory)

    这里的cluster是Cluster$Adaptive实例:

    复制代码
     1 public class Cluster$Adaptive implements com.alibaba.dubbo.rpc.cluster.Cluster {
     2     public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.RpcException {
     3         if (arg0 == null)
     4             throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
     5         if (arg0.getUrl() == null)
     6             throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
     7         com.alibaba.dubbo.common.URL url = arg0.getUrl();
     8         String extName = url.getParameter("cluster", "failover");
     9         if (extName == null)
    10             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
    11         com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
    12         return extension.join(arg0);
    13     }
    14 }
    复制代码

    这里的extName="failover",这里会进行aop:MockClusterWrapper包装FailoverCluster。

    复制代码
     1 public class MockClusterWrapper implements Cluster {
     2     private Cluster cluster;
     3 
     4     public MockClusterWrapper(Cluster cluster) {
     5         this.cluster = cluster;
     6     }
     7 
     8     public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
     9         return new MockClusterInvoker<T>(directory,
    10                 this.cluster.join(directory));
    11     }
    12 }
    复制代码

    这里的cluster是FailoverCluster实例。

    复制代码
     1 /**
     2  * 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟。
     3  */
     4 public class FailoverCluster implements Cluster {
     5     public final static String NAME = "failover";
     6 
     7     public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
     8         return new FailoverClusterInvoker<T>(directory);
     9     }
    10 }
    复制代码
    1     public FailoverClusterInvoker(Directory<T> directory) {
    2         super(directory);
    3     }

    这里实际上就是创建一个FailoverClusterInvokers实例,通过其父类AbstractClusterInvoker存储属性。

    最后创建一个MockClusterInvoker实例:

    复制代码
    1     private final Directory<T> directory;
    2     private final Invoker<T> invoker;
    3 
    4     public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
    5         this.directory = directory;
    6         this.invoker = invoker;
    7     }
    复制代码

    到此为止,下边的第一行代码就结束了!最终得到一个MockClusterInvoker实例:

    • directory=RegistryDirectory实例:
    • invoker=FailoverClusterInvokers实例(该实例中又包含一个Directory<T> directory属性,值为上述的RegistryDirectory实例)
    复制代码
    1     private T createProxy(Map<String, String> map) {
    2         ...
    3         if (urls.size() == 1) {
    4             invoker = refprotocol.refer(interfaceClass, urls.get(0));
    5         } 
    6         ...
    7         // 创建服务代理
    8         return (T) proxyFactory.getProxy(invoker);
    9     }
    复制代码

    二 使用ProxyFactory创建代理

    1 (T) proxyFactory.getProxy(invoker)

    上述的proxyFactory是ProxyFactory$Adaptive实例,其getProxy内部最终得到是一个被StubProxyFactoryWrapper包装后的JavassistProxyFactory。直接来看JavassistProxyFactory.getProxy方法

    1     public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    2         return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    3     }
    • invoker:MockClusterInvoker实例
    • interfaces:[interface com.alibaba.dubbo.demo.DemoService, interface com.alibaba.dubbo.rpc.service.EchoService]

    注意这里的Proxy不是jdk的,而是dubbo的。

    Proxy.getProxy(interfaces)

    复制代码
     1     public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
     2         ...
     3         Proxy proxy = null;
     4         ...
     5         // create ProxyInstance class.
     6         String pcn = pkg + ".proxy" + id;
     7         ccp.setClassName(pcn);
     8         ccp.addField("public static java.lang.reflect.Method[] methods;");
     9         ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
    10         ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
    11         ccp.addDefaultConstructor();
    12         Class<?> clazz = ccp.toClass();
    13         clazz.getField("methods").set(null, methods.toArray(new Method[0]));
    14             
    15         // create Proxy class.
    16         String fcn = Proxy.class.getName() + id;
    17         ccm = ClassGenerator.newInstance(cl);
    18         ccm.setClassName(fcn);
    19         ccm.addDefaultConstructor();
    20         ccm.setSuperClass(Proxy.class);
    21         ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
    22         Class<?> pc = ccm.toClass();
    23         proxy = (Proxy) pc.newInstance();
    24         ...
    25         return proxy;
    26     }
    复制代码

    从代码来看,会生成两个Class对象:pc是创建代理类的工厂类;clazz是真实对象的代理类。最终返回的proxy是如下Proxy0对象;之后调用了Proxy0.newInstance(InvocationHandler paramInvocationHandler)方法:创建出了proxy0对象,并初始化了其中的InvocationHandler handler对象为InvokerInvocationHandler。

    最终会生成两个类:(这两个类都是笔者都是直接导出.class文件之后通过jd-gui反编译出来的)。

    工厂类

    复制代码
    1 package com.alibaba.dubbo.common.bytecode;
    2 
    3 import java.lang.reflect.InvocationHandler;
    4 
    5 public class Proxy0 extends Proxy {
    6     public Object newInstance(InvocationHandler paramInvocationHandler) {
    7         return new proxy0(paramInvocationHandler);
    8     }
    9 }
    复制代码

    真实对象代理类

    复制代码
     1 package com.alibaba.dubbo.common.bytecode;
     2 
     3 import com.alibaba.dubbo.demo.DemoService;
     4 import com.alibaba.dubbo.rpc.service.EchoService;
     5 import java.lang.reflect.InvocationHandler;
     6 import java.lang.reflect.Method;
     7 
     8 public class proxy0 implements EchoService, DemoService {
     9     public static Method[] methods;
    10     private InvocationHandler handler;
    11 
    12     public String sayHello(String paramString) {
    13         Object[] arrayOfObject = new Object[1];
    14         arrayOfObject[0] = paramString;
    15         Object localObject = this.handler.invoke(this, methods[0], arrayOfObject);
    16         return (String) localObject;
    17     }
    18 
    19     public Object $echo(Object paramObject) {
    20         Object[] arrayOfObject = new Object[1];
    21         arrayOfObject[0] = paramObject;
    22         Object localObject = this.handler.invoke(this, methods[1], arrayOfObject);
    23         return (Object) localObject;
    24     }
    25 
    26     public proxy0() {
    27     }
    28 
    29     public proxy0(InvocationHandler paramInvocationHandler) {
    30         this.handler = paramInvocationHandler;
    31     }
    32 }
    复制代码

    上边的methods数组实际上已经包含了两个元素:

    [public abstract java.lang.String com.alibaba.dubbo.demo.DemoService.sayHello(java.lang.String),

    public abstract java.lang.Object com.alibaba.dubbo.rpc.service.EchoService.$echo(java.lang.Object)]

    如上所示,我们最终返回的代理对象其实是一个proxy0对象,当我们调用其sayHello方法时,其调用内部的handler.invoke方法。

    复制代码
     1 public class InvokerInvocationHandler implements InvocationHandler {
     2     private final Invoker<?> invoker;
     3 
     4     public InvokerInvocationHandler(Invoker<?> handler) {
     5         this.invoker = handler;
     6     }
     7 
     8     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     9         String methodName = method.getName();
    10         Class<?>[] parameterTypes = method.getParameterTypes();
    11         if (method.getDeclaringClass() == Object.class) {
    12             return method.invoke(invoker, args);
    13         }
    14         if ("toString".equals(methodName) && parameterTypes.length == 0) {
    15             return invoker.toString();
    16         }
    17         if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
    18             return invoker.hashCode();
    19         }
    20         if ("equals".equals(methodName) && parameterTypes.length == 1) {
    21             return invoker.equals(args[0]);
    22         }
    23         return invoker.invoke(new RpcInvocation(method, args)).recreate();
    24     }
    25 
    26 }
    复制代码

    这里的invoke是上述的MockClusterInvoker实例。

    到此为止,DemoService demoService = (DemoService) context.getBean("demoService"); 该行代码就结束了。最终得到的demoService是一个proxy0实例(是一个代理)!

  • 相关阅读:
    Oracle学习 第19天 SqlServer数据迁移Oracle
    Oracle学习 第18天 用户、表空间
    Oracle学习 第18天 PL/SQL切换用户/表空间
    Oracle学习 第18天 .NET连接Oracle
    Oracle学习 第18天 RowID与RowNum
    Bat脚本学习-2:查找文件是否存在
    Bat脚本学习-1:合并Sql存储过程
    展望新的一年
    Oracle学习 第17天
    UEditor练习(JSP版)20190603
  • 原文地址:https://www.cnblogs.com/hanease/p/16295197.html
Copyright © 2020-2023  润新知