1. 程式人生 > >Dubbo/Dubbox的服務暴露(三)- 服務的註冊

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分散式過程協同技術詳解》