前面,我們已經知道,基於spring這個解析入口,到發佈服務的過程,接着基於DubboProtocol去發佈,最終調用Netty的api創建了一個NettyServer。
那麼繼續沿着RegistryProtocol.export這個方法,來看看註冊服務的代碼:
RegistryProtocol.export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); //發佈本地服務
//registry provider
final Registry registry = getRegistry(originInvoker);
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);
}
}
};
}
getRegistry
- 這個方法是invoker的地址獲取registry實例
/**
* 根據invoker的地址獲取registry實例
* @param originInvoker
* @return
*/
private Registry getRegistry(final Invoker<?> originInvoker){
URL registryUrl = originInvoker.getUrl(); //獲得registry://192.168.11.156:2181的協議地址
if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {
//得到zookeeper的協議地址
String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);
//registryUrl就會變成了zookeeper://192.168.11.156
registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);
}
//registryFactory是什麼?
return registryFactory.getRegistry(registryUrl);
}
registryFactory.getRegistry
- 這段代碼很明顯了,通過前面這段代碼的分析,其實就是把registry的協議頭改成服務提供者配置的協議地址,也就是我們配置的
- <dubbo:registry address=”zookeeper://192.168.11.156:2181”/>
- 然後registryFactory.getRegistry的目的,就是通過協議地址匹配到對應的註冊中心。
- 那registryFactory是一個什麼樣的對象呢?我們找一下這個代碼的定義
private RegistryFactory registryFactory;
public void setRegistryFactory(RegistryFactory registryFactory) {
this.registryFactory = registryFactory;
}
- 這個代碼有點眼熟,再來看看RegistryFactory這個類的定義,我猜想一定是一個擴展點,不信,咱們看
- 並且,大家還要注意這裏面的一個方法上,有一個@Adaptive的註解,說明什麼? 這個是一個自適應擴展點。
- 按照我們之前看過代碼,自適應擴展點加在方法層面上,表示會動態生成一個自適應的適配器。
- 所以這個自適應適配器應該是RegistryFactory$Adaptive
@SPI("dubbo")
public interface RegistryFactory {
/**
* 連接註冊中心.
*
* 連接註冊中心需處理契約:<br>
* 1. 當設置check=false時表示不檢查連接,否則在連接不上時拋出異常。<br>
* 2. 支持URL上的username:password權限認證。<br>
* 3. 支持backup=10.20.153.10備選註冊中心集羣地址。<br>
* 4. 支持file=registry.cache本地磁盤文件緩存。<br>
* 5. 支持timeout=1000請求超時設置。<br>
* 6. 支持session=60000會話超時或過期設置。<br>
*
* @param url 註冊中心地址,不允許爲空
* @return 註冊中心引用,總不返回空
*/
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
RegistryFactory$Adaptive
我們拿到這個動態生成的自適應擴展點,看看這段代碼裏面的實現
- 從url中拿到協議頭信息,這個時候的協議頭是zookeeper://
- 通過ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(“zookeeper”)去獲得一個指定的擴展點,而這個擴展點的配置在
- dubbo-registry-zookeeper/resources/META-INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory。
- 得到一個ZookeeperRegistryFactory
public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) " +
"name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension =
(com.alibaba.dubbo.registry.RegistryFactory)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).
getExtension(extName);
return extension.getRegistry(arg0);
}
}
ZookeeperRegistryFactory
這個方法中並沒有getRegistry方法,而是在父類AbstractRegistryFactory
- 從緩存REGISTRIES中,根據key獲得對應的Registry
- 如果不存在,則創建Registry
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
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("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// 釋放鎖
LOCK.unlock();
}
}
createRegistry
創建一個註冊中心,這個是一個抽象方法,具體的實現在對應的子類實例中實現的,在ZookeeperRegistryFactory中
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
通過zkClient,獲得一個zookeeper的連接實例
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (! group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
this.root = group; //設置根節點
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);
}
}
}
});
}
- 代碼分析到這裏,我們對於getRegistry得出了一個結論,根據當前註冊中心的配置信息,獲得一個匹配的註冊中心,也就是ZookeeperRegistry
registry.register(registedProviderUrl);
- 繼續往下分析,會調用registry.register去將dubbo://的協議地址註冊到zookeeper上
- 這個方法會調用FailbackRegistry類中的register. 爲什麼呢?
- 因爲ZookeeperRegistry這個類中並沒有register這個方法,但是他的父類FailbackRegistry中存在register方法,而這個類又重寫了AbstractRegistry類中的register方法。
- 所以我們可以直接定位大FailbackRegistry這個類中的register方法中
FailbackRegistry.register
- FailbackRegistry,從名字上來看,是一個失敗重試機制
- 調用父類的register方法,講當前url添加到緩存集合中
- 調用doRegister方法,這個方法很明顯,是一個抽象方法,會由ZookeeperRegistry子類實現。
@Override
public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// 向服務器端發送註冊請求
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// 如果開啓了啓動時檢測,則直接拋出異常
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true)
&& ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if(skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
// 將失敗的註冊請求記錄到失敗列表,定時重試
failedRegistered.add(url);
}
}
ZookeeperRegistry.doRegister
終於找到你了,調用zkclient.create在zookeeper中創建一個節點。
protected void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- RegistryProtocol.export 這個方法中後續的代碼就不用再分析了。
- 就是去對服務提供端去註冊一個zookeeper監聽,當監聽發生變化的時候,服務端做相應的處理。
在register 方法裏面,調用subscribe 方法,訂閱註冊中心變化
/**
* 訂閱符合條件的已註冊數據,當有註冊數據變更時自動推送.
*
* 訂閱需處理契約:<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);
- subscribe ->doSubscribe ->notify ->
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.size() == 0)
&& ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
// 第一次主動調用 notify
// 對 /router /providers /configerations 路徑下的變更 進行notify
//後續(zookeeper watcher 機制)
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
saveProperties(url);
listener.notify(categoryList);
}
}