Dubbo系列-4.工廠模式的Registry

本想按照服務發佈和服務引用來寫,但是感覺先理解Registry,後面發佈和服務引用裏面可以省略掉這裏的東西,更方便關注主流程的東西。

工廠模式創建Registry

Registry提供服務的註冊,訂閱功能,採用工廠模式創建,看圖:

registry

左邊Registry代表產品體系,右邊RegistryFactory工廠生成Registry,每個具體factory生成具體的Registry,分離產品的創建。後期只需要增加具體的工廠生成具體的產品。

RegistryFactory

RegistryFactory也是採用dubbo的擴展點機制加載,默認dubbo,我例子用的是zk,後面講解也以zk爲主。

@SPI("dubbo")
public interface RegistryFactory {

    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}

AbstractRegistryFactory

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有具體的工廠生成
protected abstract Registry createRegistry(URL url);

 ZookeeperRegistryFactory

public class ZookeeperRegistryFactory extends AbstractRegistryFactory {

    private ZookeeperTransporter zookeeperTransporter;

    //ZookeeperTransporter通過擴展點加載injectExtension
    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }

    //創建ZookeeperRegistry
    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

}

這樣就生成了具體的產品ZookeeperRegistry。

Registry

AbstractRegistry

RegistryService接口定義了註冊的幾個接口,包括註冊、訂閱、取消註冊、取消訂閱接口和服務lookup查找接口,在其繼承體系中AbstractRegistry主要提供服務的本地文件緩存功能,其線程安全通過:

  1. 異步情況下通過線程池的newFixedThreadPool(1),只有一個核心線程處理來保障;
  2. 建立臨時文件.lcok,通過FileLock加鎖;
  3. 通過version。

對RegistryService接口的實現,主要是入參的存儲,例如

public void register(URL url) {
    if (url == null) {
        throw new IllegalArgumentException("register url == null");
    }
    if (logger.isInfoEnabled()){
        logger.info("Register: " + url);
    }
    //服務的本地存儲
    registered.add(url);
}

public void subscribe(URL url, NotifyListener listener) {
    if (url == null) {
        throw new IllegalArgumentException("subscribe url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("subscribe listener == null");
    }
    if (logger.isInfoEnabled()){
        logger.info("Subscribe: " + url);
    }
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    //監聽存儲
    listeners.add(listener);
}

其他類似。

FailbackRegistry

繼承體系中FailbackRegistry,主要提供服務註冊、訂閱失敗情況的5秒定時重試機制,並且通過模板模式,定義處理註冊訂閱流程,具體實現由具體子類實現。

例如

@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);
        }
    }

// ==== 模板方法 ====
protected abstract void doRegister(URL url);

其他訂閱等接口類似處理。

ZookeeperRegistry

我配置的註冊是zk,所以看下ZookeeperRegistry,主要看下注冊和訂閱接口的實現。

構造

//構造,這裏的url是註冊中心的地址
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    super(url);
    if (url.isAnyHost()) {
        throw new IllegalStateException("registry address == null");
    }
    //group主要用在後面的註冊的服務路徑裏面,如果服務本身沒有group使用默認dubbo
    String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
    if (! group.startsWith(Constants.PATH_SEPARATOR)) {
        group = Constants.PATH_SEPARATOR + group;
    }
    this.root = group;
    //取得zk的連接
    zkClient = zookeeperTransporter.connect(url);
    //zk的監聽,zk狀態變換,需要將本地失敗的註冊和訂閱重新註冊和訂閱
    zkClient.addStateListener(new StateListener() {
        public void stateChanged(int state) {
            if (state == RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    });
}

doRegister

//註冊
protected void doRegister(URL url) {
    try {
        //通過zkClient實現,瞭解zk的應該知道zk類似於目錄結構,這裏也是建立服務的目錄結構
        //toUrlPath方法轉將註冊的服務轉換爲路徑,/group(沒有就是dubbo)/接口名/provider(默認provider,根據url中category參數決定,現在基本這幾種:consumer、routers、configurators)/服務信息(url形式)
        //第二個主要表示創建的臨時節點還是持久化的節點,一般路徑都是持久的,只有最後服務信息是臨時的
        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);
    }
}

doSubscribe

//訂閱服務,類似zk的watch吧,入參NotifyListener爲回調接口
protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        //這個if暫時不關心,最後還是到else那裏
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            String root = toRootPath();
            ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            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(Constants.INTERFACE_KEY, child, 
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            zkClient.create(root, false);
            List<String> services = zkClient.addChildListener(root, zkListener);
            if (services != null && services.size() > 0) {
                anyServices.addAll(services);
                for (String service : services) {
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, 
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List<URL> urls = new ArrayList<URL>();
            //url中的參數category可以配置多個,相當於watch多個目錄,這裏for下
            for (String path : toCategoriesPath(url)) {
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                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);
                }
                //watch的目錄不存在就創建
                zkClient.create(path, false);
                List<String> children = zkClient.addChildListener(path, zkListener);
                if (children != null) {
                    //toUrlsWithEmpty是將watch的路徑轉換爲url,dubbo中服務信息,參數傳遞很多都是url來處理的
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            //這裏最重要的,回調listen
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

notify方法最後的處理邏輯在AbstractRegistry裏面。

//AbstractRegistry
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) {
        //isMatch主要做匹配,group,接口,版本,watch目錄等是否一致
        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);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        //緩存到本地文件
        saveProperties(url);
        //回調listen
        listener.notify(categoryList);
    }
}

做本地緩存和listen回調。

取消註冊和取消訂閱處理方式差不多,不做解釋。

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