dubbo-- 註冊中心Zookeeper (註冊、訂閱、通知)

註冊中心可以是zookeeper、redis和dubbo

這裏寫圖片描述

zookeeper的路徑如上圖所示,root下面有接口,接口下面有providers和consumers。
首先會註冊節點.

消費者會訂閱接口下的providers的所有子節點。一旦providers下的子節點發生改變,就會通知消息給消費者。
而監控中心訂閱的是接口。

其中接口下會有四個子節點providers, consumers, routers, configurators

其中dubbo、接口、providers都是持久化節點,只有url是臨時節點。當會話消失(服務器斷開與zookeeper的連接),對應的臨時節點會被刪除。(利用zookeeper中的臨時節點特性以及watch)

AbstractRegistryFactory

AbstractRegistryFactory 這個主要的目的是在獲取註冊中心時

  1. 加鎖
  2. 記錄註冊中心集合.
public abstract class AbstractRegistryFactory implements RegistryFactory {

   // 註冊中心獲取過程鎖
   private static final ReentrantLock LOCK = new ReentrantLock();

   // 註冊中心集合  註冊中心地址-- Registry 
   private static final Map<String, Registry> REGISTRIES = new 
	   ConcurrentHashMap<String, Registry>();

   /**
    * 獲取所有註冊中心
    * 
    * @return 所有註冊中心
    */
   public static Collection<Registry> getRegistries() {
       return Collections.unmodifiableCollection(REGISTRIES.values());
   }

  
   public Registry getRegistry(URL url) {
   /**
   zookeeper://10.118.22.25:2181/com.alibaba.dubbo.registry.RegistryService?
   application=testservice&dubbo=2.8.4&interface=
   com.alibaba.dubbo.registry.RegistryService&logger=slf4j&pid=6668
   &timestamp=1524045346142
**/
   	url = url.setPath(RegistryService.class.getName())
   			.addParameter(Constants.INTERFACE_KEY,
   			 RegistryService.class.getName())
   			.removeParameters(Constants.EXPORT_KEY, 
   			Constants.REFER_KEY);
   /**zookeeper://10.118.22.25:2181/com.alibaba.dubbo.registry.
    RegistryService**/
   	String key = url.toServiceString();
       // 鎖定註冊中心獲取過程,保證註冊中心單一實例
       LOCK.lock();
       try {
           Registry registry = REGISTRIES.get(key);
           if (registry != null) {
               return registry;
           }
           registry = createRegistry(url);
           if (registry == null) {
               throw new IllegalStateException("...");
           }
           REGISTRIES.put(key, registry);
           return registry;
       } finally {
           // 釋放鎖
           LOCK.unlock();
       }
   }
 /**
    * 關閉所有已創建註冊中心
    */
   public static void destroyAll() {
       // 鎖定註冊中心關閉過程
       LOCK.lock();
       try {
           for (Registry registry : getRegistries()) {
               try {
                   registry.destroy();
               } catch (Throwable e) {
                   LOGGER.error(e.getMessage(), e);
               }
           }
           REGISTRIES.clear();
       } finally {
           // 釋放鎖
           LOCK.unlock();
       }
   }

//創建註冊中心
   protected abstract Registry createRegistry(URL url);

}

ZookeeperRegistry##

ZookeeperRegistryFactory提供生成ZookeeperRegistry對象.
屬於多繼承
ZookeeperRegistry – >FailbackRegistry --> AbstractRegistry

初始化:
1) 創建本地緩存文件,從緩存中讀取到property中
2) 啓動定時器,去重新註冊、訂閱等操作
3) 初始化zookeeper的客戶端(zkClient)


AbstractRegistry構造函數
AbstractRegistry:主要是緩存註冊中心裏的地址到本地文件中

public abstract class AbstractRegistry implements Registry {
	 //本地磁盤緩
	private final Properties properties = new Properties();
 // 本地磁盤緩存文件
    private File file;
	public AbstractRegistry(URL url) {
	   this.registryUrl = url;
	  // 啓動文件保存定時器
	   syncSaveFile = url.getParameter("save.file", false);
	     /**C:\Users\pc/.dubbo/dubbo-registry-10.118.22.25.cache
	      主機名+.dubbo/dubbo-registry-註冊中心ip+.cache
	     **/
	   String filename = url.getParameter("file", 
		      System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + 
		       url.getHost() + ".cache");
	   File file = null;
       if (ConfigUtils.isNotEmpty(filename)) {
          file = new File(filename);
          //如果目錄不存在,就創建目錄 ..
       }
	   this.file = file;      
	}
	  //將緩存文件載入到properties(註冊中心zookeeper中的提供者接口)
	 
   
}

FailbackRegistry
定時對失敗的進行重試,主要針對於以下幾種:
1) 註冊失敗
2)取消註冊失敗
3)訂閱失敗
4)取消訂閱失敗
5)通知失敗

public abstract class FailbackRegistry extends AbstractRegistry {
 // 失敗重試定時器,定時檢查是否有請求失敗,如有,無限次重試
    private final ScheduledFuture<?> retryFuture;
    //註冊失敗
    private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
	//取消註冊失敗
    private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();
	//訂閱失敗
    private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed
      = new ConcurrentHashMap<URL, Set<NotifyListener>>();
	//取消訂閱失敗
    private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed 
    = new ConcurrentHashMap<URL, Set<NotifyListener>>();
	//通知失敗
    private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> 
      failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, 
      List<URL>>>();
      
	 public FailbackRegistry(URL url) {
	    super(url);
	    int retryPeriod = url.getParameter("retry.period",5 * 1000);
	    //啓動定時器進行重連註冊中心
	    this.retryFuture=retryExecutor.scheduleWithFixedDelay(new 
	        Runnable() {
	        public void run() {
	            // 檢測並連接註冊中心
	            retry();
	        }
	    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
	}

     //重試失敗的動作 (如果中間出現異常,忽略等待重試)
    protected void retry() {
	  //註冊    
      if (! failedRegistered.isEmpty()) {
           //遍歷failedRegistered並且從中刪除
           doRegister(url);
      }
      //取消註冊
      if(! failedUnregistered.isEmpty()) {
	      doUnregister(url);
      }
      //訂閱
      if (! failedSubscribed.isEmpty()) {
	       doSubscribe(url, listener);
      }
      //取消訂閱
      if (! failedUnsubscribed.isEmpty()) {
		   doUnsubscribe(url, listener);
      }
      //通知
      if (! failedNotified.isEmpty()) {
		   NotifyListener listener = entry.getKey();
           List<URL> urls = entry.getValue();
           listener.notify(urls);
      }
      
    }        
}

**ZookeeperRegistry **
主要處理Zookeeper,客戶端有兩種,Curator和ZkClient,默認是ZkClient

public class ZookeeperRegistry extends FailbackRegistry {
	
    private final String        root;
    
    private final Set<String> anyServices = new ConcurrentHashSet<String>();

    private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, 
	    ChildListener>> zkListeners = new 
	    ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, 
	    ChildListener>>();
    //zoookeeper客戶端(默認是zkclient)
    private final ZookeeperClient zkClient;

	public ZookeeperRegistry(URL url, ZookeeperTransporter
		 zookeeperTransporter) {
        super(url);
		//根節點,默認是/dubbo
        this.root = group;
        //zookeeperTransporter客戶端有Curator和ZkClient,默認是ZkClient
        zkClient = zookeeperTransporter.connect(url);
        //監聽如果重連了,那麼
        zkClient.addStateListener(new StateListener() {
            public void stateChanged(int state) {
            	if (state == RECONNECTED) {
	            	try {
	            	    //將所有註冊的地址放入到註冊失敗的集合中
	            	    //將所有訂閱的地址放入到訂閱失敗的集合中
						recover();
					} catch (Exception e) {
						logger.error(e.getMessage(), e);
					}
            	}
            }
        });
    }
}

註冊##

消費者或者提供者在暴露服務或者引用服務時,會往zookeeper上註冊節點。

主要做了以下幾步:
1)記錄註冊註冊地址
2) 註冊節點到zookeeper上
3) 捕捉錯誤信息,出錯則記錄下來,等待定期器去重新執行

  • AbstractRegistry
    記錄地址
private final Set<URL> registered = new ConcurrentHashSet<URL>();
 public void register(URL url) {
    registered.add(url);
}


  • FailbackRegistry
    主要是爲了捕捉註冊時是否失敗,失敗記錄到集合中
public abstract class FailbackRegistry extends AbstractRegistry {
	public void register(URL url) {
        super.register(url);
        //從註冊失敗集合中刪除
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        try {
            // 向服務器端發送註冊請求
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;
            // 如果開啓了啓動時檢測,則直接拋出異常....
            // 將失敗的註冊請求記錄到失敗列表,定時重試
            failedRegistered.add(url);
        }
    }
}
  • ZookeeperRegistry
public class ZookeeperRegistry extends FailbackRegistry {
   protected void doRegister(URL url) {
      /**
      /consumer://10.118.14.204/com.test.ITest?application=ec-service-impl..
      
      向zookeeper中註冊臨時文件節點/dubbo/com.test.ITest/consumers,
      值就是consumer://10.118.14.204/com.test.ITest?application=..
      /dubbo/com.test.ITest/consumers
      */
      zkClient.create(toUrlPath(url), url.getParameter("dynamic", true));
   }
}

訂閱

消費者在引用服務時,會訂閱接口下的providers的節點。一旦providers下的子節點發生改變(提供者的服務器增加或者刪除),會通知到消費者。消費者會把提供者的集羣地址緩存到本地。

主要做了以下幾步操作(以具體接口爲例)

  1. 將訂閱信息記錄到集合中
  2. 將路徑轉變成/dubbo/xxService/providers,/dubbo/xxService/configurators
    ,/dubbo/xxService/routers 循環這三個路徑
  • 如果消費者的接口沒有創建過子節點監聽器,那麼就創建子節點監聽器

  • 創建路徑節點,並將子節點監聽器放入到節點上。(一旦子節點發生改變,就通知)

  • 獲取到當前路徑節點下的所有子節點(提供者),將這些子節點組裝成集合,如果沒有節點,那麼就將消費者的地址的協議變成empty

    empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators

  • 通知

  1. 出現異常,根據url從本地緩存文件中獲取到提供者的地址,通知
  • AbstractRegistry
    保存訂閱信息到集合中
//訂閱
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = 
new ConcurrentHashMap<URL, Set<NotifyListener>>();

 public void subscribe(URL url, NotifyListener listener) {
    Set<NotifyListener> listeners = subscribed.get(url);
     if (listeners == null) {
         subscribed.putIfAbsent(url, 
         new ConcurrentHashSet<NotifyListener>());
         listeners = subscribed.get(url);
     }
     listeners.add(listener);
 }
  • FailbackRegistry
    出現異常時,會根據本地緩存文件取的url並通知.
public void subscribe(URL url, NotifyListener listener) {
        super.subscribe(url, listener);
        removeFailedSubscribed(url, listener);
        try {
            // 向服務器端發送訂閱請求
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;
            List<URL> urls = getCacheUrls(url);
            if (urls != null && urls.size() > 0) {
                notify(url, listener, urls);
            } 
            // 將失敗的訂閱請求記錄到失敗列表,定時重試
            addFailedSubscribed(url, listener);
        }
    }
  • ZookeeperRegistry
    接口是 (對所有的接口進行訂閱,有點類似於遞歸訂閱)*

    1. 如果在集合中沒有創建過*的子節點監聽器,那麼就創建子節點監聽器,一旦root下的子節點(service)發生改變,那麼就對這個節點就行訂閱NotifyListener 。(這時就有具體的接口了)
    2. 創建root節點,將子節點監聽器放入到root上。並返回root下的所有的接口,對這些接口訂閱NotifyListener

    接口是具體 (以providers爲例)
    1)將接口名稱轉變成/dubbo/com.test.ITestService/providers,集合中沒有沒有providers的子節點監聽器,就創建子節點監聽器。一旦子節點發生改變,那麼就通知

    1. 創建 /dubbo/com.test.ITestService/providers,並且將子節點監聽器放入到這個節點上,並返回所有的子節點(提供者),通知。

總結
當消費者要訂閱接口中的提供者時
會監聽/dubbo/xxService/providers下的所有提供者。一旦提供者的節點刪除或增加時,都會通知到消費者的url(consumer://10.118.14.204/com.test.ITestService…)

它會監聽以下三個節點的子節點
1) /dubbo/xxService/providers
2)/dubbo/xxService/configurators
3)/dubbo/xxService/routers

組裝的url集合(即提供者的子節點providers,configurators,routers下的子節點)。如果沒有子節點(沒有提供者),那麼就將消費者的協議變成empty作爲url。

 //存放子節點的監聽器
 private final ConcurrentMap<URL, ConcurrentMap<NotifyListener,
  ChildListener>> zkListeners = new ConcurrentHashMap<URL,
   ConcurrentMap<NotifyListener, ChildListener>>();
   

protected void doSubscribe(final URL url, final NotifyListener listener) {
	//接口名稱(*代表需要監聽root下面的所有節點)
    if ("*".equals(url.getServiceInterface())) {
        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.
        get(url);
        //如果listeners爲空創建並放入到map中...
        ChildListener zkListener = listeners.get(listener);
        /**
           root下的子節點是service接口
           創建子節點監聽器,對root下的子節點做監聽,一旦有子節點發生改變,
           那麼就對這個節點進行訂閱.
        **/
        if (zkListener == null) {
            listeners.putIfAbsent(listener, new ChildListener() {
                public void childChanged(String parentPath, List<String> 
	                currentChilds) {
                    for (String child : currentChilds) {
						//如果不存在,才訂閱
                        if (! anyServices.contains(child)) {
                            anyServices.add(child);
                            //訂閱
                            subscribe(url.setPath(child).addParameters(
                            "interface", child,"check", "false"), listener);
                        }
                    }
                }
            });
            zkListener = listeners.get(listener);
        }
        //創建root節點
        zkClient.create(root, false);
        //添加root節點的子節點監聽器,並返回當前的services
        List<String> services = zkClient.addChildListener(root, zkListener);
        if (services != null && services.size() > 0) {
		    //對root下的所有service節點進行訂閱
            for (String service : services) {
				service = URL.decode(service);
				anyServices.add(service);
                subscribe(url.setPath(service).addParameters("interface", 
                service, "check", "false"), listener);
            }
        }
    } else {
        List<URL> urls = new ArrayList<URL>();
        /**將url轉變成
	        /dubbo/com.test.ITestService/providers
	        /dubbo/com.test.ITestService/configurators
	        /dubbo/com.test.ITestService/routers
        **/
        for (String path : toCategoriesPath(url)) {
            ConcurrentMap<NotifyListener, ChildListener> listeners = 
	            zkListeners.get(url);
	        //如果listeners爲空就創建並放入盜map中
            ChildListener zkListener = listeners.get(listener);
            /**
              對接口下的providers的子節點進行監聽,一旦發生改變,就通知
            **/
            if (zkListener == null) {
                listeners.putIfAbsent(listener, new ChildListener() {
                    public void childChanged(String parentPath, List<String> 
	                    currentChilds) {
	                    //通知
                    	ZookeeperRegistry.this.notify(url, listener, 
	                    	toUrlsWithEmpty(url, parentPath, currentChilds));
                    }
                });
                zkListener = listeners.get(listener);
            }
            //創建/dubbo/com.test.ITestService/providers
            zkClient.create(path, false);
            //獲取到providers的所有子節點(提供者)
            List<String> children = zkClient.addChildListener(path, 
	            zkListener);
	        //獲取到所有的提供者,組裝起來
            if (children != null) {
                //有子節點組裝,沒有那麼就將消費者的協議變成empty作爲url。
            	urls.addAll(toUrlsWithEmpty(url, path, children));
            }
        }
        //通知/dubbo/com.test.ITestService/providers的所有子節點
        notify(url, listener, urls);
    }
       
 }

/**
根據url獲取到哪些類型
consumer://10.118.14.204/com.test.ITestService?application=testservice
&category=providers,configurators,routers&...
這裏的category是重點
**/
private String[] toCategoriesPath(URL url) {
    String[] categroies;
    //如果是*
    if ("*".equals(url.getParameter(Constants.CATEGORY_KEY))) 
        categroies = new String[] {"providers", "consumers", 
            "routers", "configurators"}; 
    else
    //從url獲取到category的值,沒有的話就默認providers
        categroies = url.getParameter("category", 
	        new String[] {"providers"});
    String[] paths = new String[categroies.length];
    //將格式轉變成/dubbo/xxService/類型
    for (int i = 0; i < categroies.length; i ++) {
        paths[i] = toServicePath(url) + "/" + categroies[i];
    }
    return paths;
}

/**
組裝providers、routers、configurators下的url。
如果有提供者那麼就組裝;沒有的話,就將消費者的協議變成empty
**/
private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
        List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
        if (urls.isEmpty()) {
        	int i = path.lastIndexOf('/');
        	String category = i < 0 ? path : path.substring(i + 1);
        	URL empty = consumer.setProtocol("empty").addParameter(
	        	"category", category);
            urls.add(empty);
        }
        return urls;
    }



通知

有三個參數
url: 消費者的地址 consumer://10.118.14.204/com…
listener: 監聽器
urls: providers,configurators和routers

1)寫入到本地緩存文件中
文件名稱:
2) 監聽器通知

  • FailbackRegistry
    主要是鋪捉到異常時放入到集合中,定時重試
 protected void notify(URL url, NotifyListener listener, List<URL> urls) {
     try {
     	doNotify(url, listener, urls);
     } catch (Exception t) {
         // 將失敗的通知請求記錄到失敗列表,定時重試..
         Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
     }
 }
  • AbstractRegistry

    urls三種
    1) providers(providers下的子節點)
    dubbo://10.118.22.29:20710/com.test.ITestService?anyhost=true&application=testservice&default.cluster=failfast…

    1. configurators(configurators下的子節點爲空,將消費者的url變成empty )
      empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators&default.check=false…

    2. routers(routers下的子節點爲空,將消費者的url變成empty)
      empty://10.118.14.204/com.test.ITestService?application=testservice&category=routers&default.check=false…

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    //根據url中的category分割    
    Map<String, List<URL>> categoryNotified = notified.get(url);
	//空的話就創建並且放入到map
	
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
		//將url寫入到本地緩存中
        saveProperties(url);
        //監聽器通知url
        listener.notify(categoryList);
    }
}

保存到本地緩存文件

組裝url保存到properties中,如果是同步,直接保存到本地緩存文件中,否則文件緩存定時寫入

private void saveProperties(URL url) {
    try {
	    //根據url取出所有的地址
        StringBuilder buf = new StringBuilder();
        Map<String, List<URL>> categoryNotified = notified.get(url);
        if (categoryNotified != null) {
            for (List<URL> us : categoryNotified.values()) {
                for (URL u : us) {
                    if (buf.length() > 0) {
                        buf.append(URL_SEPARATOR);
                    }
                    buf.append(u.toFullString());
                }
            }
        }
        //放入到properties中
        properties.setProperty(url.getServiceKey(), buf.toString());
        long version = lastCacheChanged.incrementAndGet();
        if (syncSaveFile) {
	        //直接保存文件緩存
            doSaveProperties(version);
        } else {
	        //文件緩存定時寫入
            registryCacheExecutor.execute(new SaveProperties(version));
        }
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

通過 AtomicLong 來控制鎖
首先會有個dubbo-registry-10.118.22.25.cache.lock,會獲取這個文件的鎖,然後保存dubbo-registry-10.118.22.25.cache文件,再釋放鎖。

public void doSaveProperties(long version) {
   if(version < lastCacheChanged.get()){
       return;
   }
   Properties newProperties = new Properties();
   // 保存之前先讀取一遍file,防止多個註冊中心之間衝突...
       
// 保存
   try {
	 newProperties.putAll(properties);
     //首先會有個空文件dubbo-registry-10.118.22.25.cache.lock
     File lockfile = new File(file.getAbsolutePath() + ".lock");
     if (!lockfile.exists()) {
     	 lockfile.createNewFile();
     }
     RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
     try {
         FileChannel channel = raf.getChannel();
         try {
          //獲取到lock文件的鎖  
          FileLock lock = channel.tryLock();
          try {
          	// 將Properties保存到file中
          } finally {
          	lock.release();
          }
         } finally {
             channel.close();
         }
     } finally {
         raf.close();
     }
 } catch (Throwable e) {
	 //出錯了再重新保存
     if (version < lastCacheChanged.get()) {
         return;
     } else {
         registryCacheExecutor.execute(new SaveProperties
         (lastCacheChanged.incrementAndGet()));
     }
 }
}

監聽器通知 (當收到服務變更通知時觸發。)##

當收到提供者的地址發生改變時,這時刷新緩存中的invoker,如果url不存在,那麼重新refer(根據dubbo協議)

通知需處理契約:
1. 總是以服務接口和數據類型爲維度全量通知,即不會通知一個服務的同類型的部分數據,用戶不需要對比上一次通知結果。
2. 訂閱時的第一次通知,必須是一個服務的所有類型數據的全量通知。
3. 中途變更時,允許不同類型的數據分開通知,比如:providers, consumers, routers, overrides,允許只通知其中一種類型,但該類型的數據必須是全量的,不是增量的。
4. 如果一種類型的數據爲空,需通知一個empty協議並帶category參數的標識性URL數據。
5. 通知者(即註冊中心實現)需保證通知的順序,比如:單線程推送,隊列串行化,帶版本對比。

  • RegistryDirectory
 public synchronized void notify(List<URL> urls) {
	//providers
    List<URL> invokerUrls = new ArrayList<URL>();
    //router
    List<URL> routerUrls = new ArrayList<URL>();
    //configurator
    List<URL> configuratorUrls = new ArrayList<URL>();
    //循環url,根據category放入到對應的集合中...
    
    // 處理configurators,去掉empty
    if (configuratorUrls != null && configuratorUrls.size() >0 ){
        this.configurators = toConfigurators(configuratorUrls);
    }
    // 處理routers,去掉empty
    if (routerUrls != null && routerUrls.size() >0 ){
        List<Router> routers = toRouters(routerUrls);
        if(routers != null){ // null - do nothing
            setRouters(routers);
        }
    }
    
    List<Configurator> localConfigurators = this.configurators;
    // 合併override參數
    this.overrideDirectoryUrl = directoryUrl;
    if (localConfigurators != null && localConfigurators.size() > 0) {
        for (Configurator configurator : localConfigurators) {
            this.overrideDirectoryUrl = configurator.configure(
            overrideDirectoryUrl);
        }
    }
    // providers
    refreshInvoker(invokerUrls);
}

private void refreshInvoker(List<URL> invokerUrls){
	//如果只有一個empty,那麼就禁止訪問
   if (invokerUrls != null && invokerUrls.size() == 1 
    && invokerUrls.get(0) != null
    && "empty".equals(invokerUrls.get(0).getProtocol())) {
       this.forbidden = true; // 禁止訪問
       this.methodInvokerMap = null; // 置空列表
       destroyAllInvokers(); // 關閉所有Invoker
   } else {
       this.forbidden = false; // 允許訪問
       //...
       this.cachedInvokerUrls.addAll(invokerUrls);
       if (invokerUrls.size() ==0 ){
	       	return;
       }
       Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap;
       // 將URL列表轉成Invoker列表
       Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;
       // 換方法名映射Invoker列表
       Map<String, List<Invoker<T>>> newMethodInvokerMap = 
        toMethodInvokers(newUrlInvokerMap); 
       // state change
       //如果計算錯誤,則不進行處理.
       if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){
           return ;
       }
       this.methodInvokerMap = multiGroup ? 
       toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
       this.urlInvokerMap = newUrlInvokerMap;
       // 關閉未使用的Invoker
       destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap);
   }
}
/**
 * 將urls轉成invokers,如果url已經被refer過,不再重新引用。
 * 如果沒有那麼需要refer
 * protocol.refer(serviceType, url)
	根據協議dubbo,所以他是DubboProtocol
	 DubboInvoker<T> invoker = new DubboInvoker<T>
	 (serviceType, url, getClients(url), invokers);
 */
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
    Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, 
	    Invoker<T>>();
    if(urls == null || urls.size() == 0){
        return newUrlInvokerMap;
    }
    Set<String> keys = new HashSet<String>();
    String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
    for (URL providerUrl : urls) {
    	//如果reference端配置了protocol,則只選擇匹配的protocol
        if ("empty".equals(providerUrl.getProtocol())) {
            continue;
        }
        //沒有這個指定的協議,那麼報錯
        if (! ExtensionLoader.getExtensionLoader(Protocol.class).
	        hasExtension(providerUrl.getProtocol())) {
            continue;
        }
        //合併url參數 順序爲override > -D >Consumer > Provider
        URL url = mergeUrl(providerUrl);
        // URL參數是排序的
        String key = url.toFullString(); 
        // 重複URL,過濾
        
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; 
        Invoker<T> invoker = localUrlInvokerMap == null ? null : 
	        localUrlInvokerMap.get(key);
	    // 緩存中沒有,重新refer  
        if (invoker == null) { 
           	boolean enabled = true;
           	//根據url獲取參數disabled或enabled
           	if (enabled) {
	           	//重新refer
           		invoker = new InvokerDelegete<T>(
	           		protocol.refer(serviceType, url), url, providerUrl);
           	}
            if (invoker != null) { // 將新的引用放入緩存
                newUrlInvokerMap.put(key, invoker);
            }
        }else {
            newUrlInvokerMap.put(key, invoker);
        }
    }
    keys.clear();
    return newUrlInvokerMap;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章