Dubbo/Dubbox的服務暴露(三)- 服務的註冊
上文書依舊疑留的疑問,這兩句到底在幹啥
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
上文已知 protocol 是一個自適應的擴充套件點,自適應擴充套件點是如何適應的我們且已知曉。
那麼根據擴充套件點的原理,我們需要知道invoker中的url屬性值是什麼,就可以確定protocol實際使用的實現類
可見當前URL的protocol為 registry,根據擴充套件點原理,以及上文加粗部分,該出使用擴充套件點實現為registry擴充套件點的包裝類com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
,其中registry擴充套件點建立例項為com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
(自己跟原始碼,這裡不深究)ProtocolFilterWrapper 包裝RegistryProtocol 物件
RegistryProtocol 物件的export 方法
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
//注意這行程式碼會暴露實際提供服務的協議。例如dubbo,即url引數protocol的協議
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
//獲得註冊中心,這裡取決於URL中的registry 引數,該方法內部會使用自適應擴充套件點獲取註冊中心物件,本例最終的註冊中心物件為com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
//返回註冊到註冊中心的URL,對URL引數進行一次過濾,該方法會過濾URL中不需要輸出的引數(以點號開頭的)
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//註冊服務地址(不深究)
registry.register(registedProviderUrl);
// 訂閱override資料
// FIXME 提供者訂閱時,會影響同一JVM即暴露服務,又引用同一服務的的場景,因為subscribed以服務名為快取的key,導致訂閱資訊覆蓋。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保證每次export都返回一個新的exporter例項
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
}
public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}
doLocalExport 暴露實際的服務內容
@SuppressWarnings("unchecked")
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker){
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
//會再把URL轉成 dubbo://ip:port/serviceName?xxx=xx 這種
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
//protocol 通過自適應擴充套件點的方式繼續暴露dubbo服務
exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return (ExporterChangeableWrapper<T>) exporter;
}
關於上面描述的debugger圖,可見invokerDelegete 中的url屬性中protocol為 dubbo,所以這裡會繼續使用自適應擴充套件點,暴露dubbo服務。
注意,Registry介面繼承自RegistryService,關於export方法的相關registry行為,對應文件註釋如下,本文不再贅述。
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.registry;
import java.util.List;
import com.alibaba.dubbo.common.URL;
/**
* RegistryService. (SPI, Prototype, ThreadSafe)
*
* @see com.alibaba.dubbo.registry.Registry
* @see com.alibaba.dubbo.registry.RegistryFactory#getRegistry(URL)
* @author william.liangf
*/
public interface RegistryService {
/**
* 註冊資料,比如:提供者地址,消費者地址,路由規則,覆蓋規則,等資料。
*
* 註冊需處理契約:<br>
* 1. 當URL設定了check=false時,註冊失敗後不報錯,在後臺定時重試,否則丟擲異常。<br>
* 2. 當URL設定了dynamic=false引數,則需持久儲存,否則,當註冊者出現斷電等情況異常退出時,需自動刪除。<br>
* 3. 當URL設定了category=routers時,表示分類儲存,預設類別為providers,可按分類部分通知資料。<br>
* 4. 當註冊中心重啟,網路抖動,不能丟失資料,包括斷線自動刪除資料。<br>
* 5. 允許URI相同但引數不同的URL並存,不能覆蓋。<br>
*
* @param url 註冊資訊,不允許為空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void register(URL url);
/**
* 取消註冊.
*
* 取消註冊需處理契約:<br>
* 1. 如果是dynamic=false的持久儲存資料,找不到註冊資料,則拋IllegalStateException,否則忽略。<br>
* 2. 按全URL匹配取消註冊。<br>
*
* @param url 註冊資訊,不允許為空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void unregister(URL url);
/**
* 訂閱符合條件的已註冊資料,當有註冊資料變更時自動推送.
*
* 訂閱需處理契約:<br>
* 1. 當URL設定了check=false時,訂閱失敗後不報錯,在後臺定時重試。<br>
* 2. 當URL設定了category=routers,只通知指定分類的資料,多個分類用逗號分隔,並允許星號通配,表示訂閱所有分類資料。<br>
* 3. 允許以interface,group,version,classifier作為條件查詢,如:interface=com.alibaba.foo.BarService&version=1.0.0<br>
* 4. 並且查詢條件允許星號通配,訂閱所有介面的所有分組的所有版本,或:interface=*&group=*&version=*&classifier=*<br>
* 5. 當註冊中心重啟,網路抖動,需自動恢復訂閱請求。<br>
* 6. 允許URI相同但引數不同的URL並存,不能覆蓋。<br>
* 7. 必須阻塞訂閱過程,等第一次通知完後再返回。<br>
*
* @param url 訂閱條件,不允許為空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @param listener 變更事件監聽器,不允許為空
*/
void subscribe(URL url, NotifyListener listener);
/**
* 取消訂閱.
*
* 取消訂閱需處理契約:<br>
* 1. 如果沒有訂閱,直接忽略。<br>
* 2. 按全URL匹配取消訂閱。<br>
*
* @param url 訂閱條件,不允許為空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @param listener 變更事件監聽器,不允許為空
*/
void unsubscribe(URL url, NotifyListener listener);
/**
* 查詢符合條件的已註冊資料,與訂閱的推模式相對應,這裡為拉模式,只返回一次結果。
*
* @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
* @param url 查詢條件,不允許為空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @return 已註冊資訊列表,可能為空,含義同{@link com.alibaba.dubbo.registry.NotifyListener#notify(List<URL>)}的引數。
*/
List<URL> lookup(URL url);
}
剩下的操作就是一些ZK相關操作了,zookeeper和dubbo的關係:
Dubbo的將註冊中心進行抽象,是得它可以外接不同的儲存媒介給註冊中心提供服務,有ZooKeeper,Memcached,Redis等。
引入了ZooKeeper作為儲存媒介,也就把ZooKeeper的特性引進來。首先是負載均衡,單註冊中心的承載能力是有限的,在流量達到一定程度的時候就需要分流,負載均衡就是為了分流而存在的,一個ZooKeeper群配合相應的Web應用就可以很容易達到負載均衡;資源同步,單單有負載均衡還不夠,節點之間的資料和資源需要同步,ZooKeeper叢集就天然具備有這樣的功能;命名服務,將樹狀結構用於維護全域性的服務地址列表,服務提供者在啟動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的釋出。其他特性還有Mast選舉,分散式鎖等。
總之有興趣可以自己看一下com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry
相關實現以及《zookeeper分散式過程協同技術詳解》