註冊中心可以是zookeeper、redis和dubbo
zookeeper的路徑如上圖所示,root下面有接口,接口下面有providers和consumers。
首先會註冊節點.
消費者會訂閱接口下的providers的所有子節點。一旦providers下的子節點發生改變,就會通知消息給消費者。
而監控中心訂閱的是接口。
其中接口下會有四個子節點providers, consumers, routers, configurators
其中dubbo、接口、providers都是持久化節點,只有url是臨時節點。當會話消失(服務器斷開與zookeeper的連接),對應的臨時節點會被刪除。(利用zookeeper中的臨時節點特性以及watch)
AbstractRegistryFactory
AbstractRegistryFactory 這個主要的目的是在獲取註冊中心時
- 加鎖
- 記錄註冊中心集合.
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
×tamp=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下的子節點發生改變(提供者的服務器增加或者刪除),會通知到消費者。消費者會把提供者的集羣地址緩存到本地。
主要做了以下幾步操作(以具體接口爲例)
- 將訂閱信息記錄到集合中
- 將路徑轉變成/dubbo/xxService/providers,/dubbo/xxService/configurators
,/dubbo/xxService/routers 循環這三個路徑
-
如果消費者的接口沒有創建過子節點監聽器,那麼就創建子節點監聽器
-
創建路徑節點,並將子節點監聽器放入到節點上。(一旦子節點發生改變,就通知)
-
獲取到當前路徑節點下的所有子節點(提供者),將這些子節點組裝成集合,如果沒有節點,那麼就將消費者的地址的協議變成empty
empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators
-
通知
- 出現異常,根據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
接口是 (對所有的接口進行訂閱,有點類似於遞歸訂閱)*- 如果在集合中沒有創建過*的子節點監聽器,那麼就創建子節點監聽器,一旦root下的子節點(service)發生改變,那麼就對這個節點就行訂閱NotifyListener 。(這時就有具體的接口了)
- 創建root節點,將子節點監聽器放入到root上。並返回root下的所有的接口,對這些接口訂閱NotifyListener
接口是具體 (以providers爲例)
1)將接口名稱轉變成/dubbo/com.test.ITestService/providers,集合中沒有沒有providers的子節點監聽器,就創建子節點監聽器。一旦子節點發生改變,那麼就通知- 創建 /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…-
configurators(configurators下的子節點爲空,將消費者的url變成empty )
empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators&default.check=false… -
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;
}