Dubbo源碼(3)-基礎抽象註冊中心源碼解析

本文主要參考自Dubbo官方文檔、Dubbo項目源碼以及網絡文章和相關書籍,並附上自身的一些理解,如有遺漏或錯誤,還望海涵並指出。謝謝!

------本文基於Dubbo-2.6.1版本

一.Dubbo註冊中心

Dubbo的註冊中心的作用接受生產者的服務註冊和客戶端的訂閱,也就是服務間的“中間人”。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-N4eKdwkW-1588683498445)(http://note.youdao.com/yws/res/20595/11AAF08395C14C4F9FE2D6BB8A62FD6A)]

整個Dubbo註冊中心的實現位於dubbo-registry模塊下:

在這裏插入圖片描述
從這張依賴圖來看,註冊中心存在着dubbo、zk、redis和multicast這四種實現,它們都依賴於dubbo-registry-api.com.alibaba.dubbo.registry.support這個包,所以本章主要是來看dubbo-registry-api的代碼實現。
在這裏插入圖片描述

二.代碼構成

  • dubbo-registry-api
    在這裏插入圖片描述

  • 依賴關係
    在這裏插入圖片描述

  • 代碼量統計
    在這裏插入圖片描述

三、RegistryFactory

com.alibaba.dubbo.registry.RegistryFactory是註冊中心接口工廠,採用SPI機制動態拓展:

  • RegistryFactory
@SPI("dubbo")
public interface RegistryFactory {

    /**
     * 連接註冊中心.
     * <p>
     * 連接註冊中心需處理契約:<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);

}

@Adaptive({“protocol”}) 註解,Dubbo SPI 會自動實現 RegistryFactory$Adaptive 類,根據 url.protocol 獲得對應的 RegistryFactory 實現類。例如,url.protocol = zookeeper 時,獲得 ZookeeperRegistryFactory 實現類。

3.1、AbstractRegistryFactory

com.alibaba.dubbo.registry.support.AbstractRegistryFactory是RegistryFactory的抽象實現類,提供了對Registry(註冊中心)的管理。

  • AbstractRegistryFactory
public abstract class AbstractRegistryFactory implements RegistryFactory {

    // logger
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegistryFactory.class);

    // 獲取註冊中心過程中使用的互斥鎖
    private static final ReentrantLock LOCK = new ReentrantLock();

    // 註冊中心映射Map<key=註冊名稱、value=註冊中心>
    private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();

    /**
     * 獲取到所有註冊中心
     *
     * @return all registries
     */
    public static Collection<Registry> getRegistries() {
        return Collections.unmodifiableCollection(REGISTRIES.values());
    }

    /**
     * 關閉所有註冊中心
     */
    // TODO: 2017/8/30 to move somewhere else better
    public static void destroyAll() {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Close all registries " + getRegistries());
        }
        // Lock up the registry shutdown process
        LOCK.lock();
        try {
            for (Registry registry : getRegistries()) {
                try {
                    // 調用關閉方法
                    registry.destroy();
                } catch (Throwable e) {
                    LOGGER.error(e.getMessage(), e);
                }
            }
            // 清空Map
            REGISTRIES.clear();
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

    /*
    *   解析URL獲取指定註冊中心
     */
    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 the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            // registry==null,create
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            // 將創建的註冊中心放到Map中
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }
    /*
    *   模板方法模式,留給子類做實現
    *   創建註冊中心
     */
    protected abstract Registry createRegistry(URL url);

}

四、RegistryService

com.alibaba.dubbo.registry.RegistryService,註冊中心服務接口,定義了註冊、訂閱、查詢三種操作方法。

  • RegistryService
public interface RegistryService {

    /**
     * 註冊數據,比如:提供者地址,消費者地址,路由規則,覆蓋規則,等數據。
     * <p>
     * 註冊需處理契約:<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);

    /**
     * 取消註冊.
     * <p>
     * 取消註冊需處理契約:<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);


    /**
     * 訂閱符合條件的已註冊數據,當有註冊數據變更時自動推送.
     * <p>
     * 訂閱需處理契約:<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);

    /**
     * 取消訂閱.
     * <p>
     * 取消訂閱需處理契約:<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);

    /**
     * 查詢符合條件的已註冊數據,與訂閱的推模式相對應,這裏爲拉模式,只返回一次結果。
     *
     * @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>)}的參數。
     * @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
     */
    List<URL> lookup(URL url);

}

4.1、AbstractRegistry

com.alibaba.dubbo.registry.support.AbstractRegistry實現了Registry接口,而Registry接口繼承了RegistryService接口,所以AbstractRegistry就是Registry的實現類,主要是對註冊、訂閱、通知和查詢這類方法做出了本地的實現,即將各種註冊、監聽關係保存在了自身的屬性之中:

4.1.1、屬性及構造方法
// URL地址分隔符,用於文件緩存中,服務提供者URL分隔
    private static final char URL_SEPARATOR = ' ';
    // URL地址分隔正則表達式,用於解析文件緩存中服務提供者URL列表
    private static final String URL_SPLIT = "\\s+";
    // logger
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    // 本地配置緩存
    private final Properties properties = new Properties();
    // 註冊中心緩存寫入執行器,線程數=1
    private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
    // 是否同步保存文件
    private final boolean syncSaveFile;
    // 上一次緩存改變數據版本號
    private final AtomicLong lastCacheChanged = new AtomicLong();
    // 已註冊的URL集合
    private final Set<URL> registered = new ConcurrentHashSet<URL>();
    // 已訂閱的URL及監聽器映射集合
    private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
    // 被通知的URL集合
    private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();
    // 該註冊中心的URL
    private URL registryUrl;
    // 本地緩存文件
    private File file;

    /*
    *   構造方法,傳入URL
     */
    public AbstractRegistry(URL url) {
        // 設置registryUrl
        setUrl(url);
        // 獲取是否同步保存緩存
        syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
        // 緩存文件路徑名稱
        String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
        File file = null;
        // 創建緩存文件
        if (ConfigUtils.isNotEmpty(filename)) {
            file = new File(filename);
            if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {
                    throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
                }
            }
        }
        this.file = file;
        // 加載配置到properties中
        loadProperties();
        // 通知所有訂閱該URL的監聽者
        notify(url.getBackupUrls());
    }
4.1.2、核心方法
    /*
    *   以下關於註冊、監聽的方法類似於觀察者模式的實現
     */

    /*
    *   查詢特定URL的全部通知列表
     */
    public List<URL> lookup(URL url) {
        // 訂閱列表
        List<URL> result = new ArrayList<URL>();
        // 獲取該url的通知列表
        Map<String, List<URL>> notifiedUrls = getNotified().get(url);
        if (notifiedUrls != null && notifiedUrls.size() > 0) {
            for (List<URL> urls : notifiedUrls.values()) {
                for (URL u : urls) {
                    if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) {
                        // 添加到result中
                        result.add(u);
                    }
                }
            }
        } else {
            // 通知列表爲空,創建監聽者做一次訂閱操作,再獲取到訂閱列表
            final AtomicReference<List<URL>> reference = new AtomicReference<List<URL>>();
            NotifyListener listener = new NotifyListener() {
                public void notify(List<URL> urls) {
                    reference.set(urls);
                }
            };
            subscribe(url, listener); // Subscribe logic guarantees the first notify to return
            List<URL> urls = reference.get();
            if (urls != null && !urls.isEmpty()) {
                for (URL u : urls) {
                    if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) {
                        result.add(u);
                    }
                }
            }
        }
        return result;
    }
    /*
    *   註冊URL
     */
    public void register(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("register url == null");
        }
        if (logger.isInfoEnabled()) {
            logger.info("Register: " + url);
        }
        // 將URL添加到Set<URL>中
        registered.add(url);
    }

    /*
    *   取消註冊URL
     */
    public void unregister(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("unregister url == null");
        }
        if (logger.isInfoEnabled()) {
            logger.info("Unregister: " + url);
        }
        // 將URL從Set<URL>中移除
        registered.remove(url);
    }

    /*
    *   訂閱,建立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);
    }
    /*
    *   取消訂閱
     */
    public void unsubscribe(URL url, NotifyListener listener) {
        if (url == null) {
            throw new IllegalArgumentException("unsubscribe url == null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("unsubscribe listener == null");
        }
        if (logger.isInfoEnabled()) {
            logger.info("Unsubscribe: " + url);
        }
        Set<NotifyListener> listeners = subscribed.get(url);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }
    /*
    *   斷線重連恢復
     */
    protected void recover() throws Exception {
        // 重新建立URL註冊關係
        Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
        if (!recoverRegistered.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover register url " + recoverRegistered);
            }
            for (URL url : recoverRegistered) {
                register(url);
            }
        }
        // 重新建立訂閱關係
        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
        if (!recoverSubscribed.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover subscribe url " + recoverSubscribed.keySet());
            }
            for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                URL url = entry.getKey();
                for (NotifyListener listener : entry.getValue()) {
                    subscribe(url, listener);
                }
            }
        }
    }

    /*
    *   通知所有URL的所有監聽者出現變更
     */
    protected void notify(List<URL> urls) {
        if (urls == null || urls.isEmpty()) return;

        for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
            URL url = entry.getKey();

            if (!UrlUtils.isMatch(url, urls.get(0))) {
                continue;
            }
            // 獲取到監聽者集合
            Set<NotifyListener> listeners = entry.getValue();
            if (listeners != null) {
                for (NotifyListener listener : listeners) {
                    try {
                        // 通知該url的該listener下urls的變更
                        notify(url, listener, filterEmpty(url, urls));
                    } catch (Throwable t) {
                        logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
                    }
                }
            }
        }
    }
    // 對特定的url做全量的變更通知
    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.isEmpty())
                && !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);
        }
        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);
        }
    }
    // 保存配置
    private void saveProperties(URL url) {
        if (file == null) {
            return;
        }

        try {
            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.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);
        }
    }

    /*
    *   關閉dubbo時做銷燬
     */
    public void destroy() {
        if (logger.isInfoEnabled()) {
            logger.info("Destroy registry:" + getUrl());
        }
        // 取消全部登記URL的登記
        Set<URL> destroyRegistered = new HashSet<URL>(getRegistered());
        if (!destroyRegistered.isEmpty()) {
            for (URL url : new HashSet<URL>(getRegistered())) {
                if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
                    try {
                        unregister(url);
                        if (logger.isInfoEnabled()) {
                            logger.info("Destroy unregister url " + url);
                        }
                    } catch (Throwable t) {
                        logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                    }
                }
            }
        }
        // 取消全部訂閱者的訂閱
        Map<URL, Set<NotifyListener>> destroySubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
        if (!destroySubscribed.isEmpty()) {
            for (Map.Entry<URL, Set<NotifyListener>> entry : destroySubscribed.entrySet()) {
                URL url = entry.getKey();
                for (NotifyListener listener : entry.getValue()) {
                    try {
                        unsubscribe(url, listener);
                        if (logger.isInfoEnabled()) {
                            logger.info("Destroy unsubscribe url " + url);
                        }
                    } catch (Throwable t) {
                        logger.warn("Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                    }
                }
            }
        }
    }

4.2、FailbackRegistry

com.alibaba.dubbo.registry.support.FailbackRegistry ,實現 AbstractRegistry 抽象類,支持失敗重試的 Registry 抽象類。

該類實現了註冊中心所需的相關操作。當然,更進一步的話還是需要zk或者redis作爲註冊中心的實現,下文會提到

4.2.1、屬性及構造方法
 /**
     * 定時任務執行器
     */
    // Scheduled executor service
    private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));

    /**
     * 失敗重試定時器,定時檢查是否有請求失敗,如有,無限次重試
     */
    // Timer for failure retry, regular check if there is a request for failure, and if there is, an unlimited retry
    private final ScheduledFuture<?> retryFuture;
    /**
     * 失敗發起註冊失敗的 URL 集合
     */
    private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
    /**
     * 失敗取消註冊失敗的 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>>();
    /**
     * 失敗通知通知的 URL 集合
     */
    private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();
    /**
     * 是否銷燬
     */
    private AtomicBoolean destroyed = new AtomicBoolean(false);

    public FailbackRegistry(URL url) {
        super(url);
        // 重試頻率,單位:毫秒
        int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
        // 創建失敗重試定時器
        this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                // Check and connect to the registry
                try {
                    retry();
                } catch (Throwable t) { // Defensive fault tolerance
                    logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
                }
            }
        }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
    }
4.2.2、核心方法

主要注意的點在與真正完成到zk或redis中寫入服務信息的方法是doRegister(URL url)這個方法,這個方法在本類中只給出了抽象,需要由後續的ZookeeperRegistryRedisRegistry來實現具體的邏輯,這就是很常見的一種模板方法模式。具體的zk或redis實現會在後續的文章中給出。

/**
     * 添加到 `failedSubscribed`
     *
     * @param url
     * @param listener
     */
    private void addFailedSubscribed(URL url, NotifyListener listener) {
        Set<NotifyListener> listeners = failedSubscribed.get(url);
        if (listeners == null) {
            failedSubscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
            listeners = failedSubscribed.get(url);
        }
        listeners.add(listener);
    }

    /**
     * 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified`
     *
     * @param url URL
     * @param listener 監聽器
     */
    private void removeFailedSubscribed(URL url, NotifyListener listener) {
        // 移除出 `failedSubscribed`
        Set<NotifyListener> listeners = failedSubscribed.get(url);
        if (listeners != null) {
            listeners.remove(listener);
        }
        // 移除出 `failedUnsubscribed`
        listeners = failedUnsubscribed.get(url);
        if (listeners != null) {
            listeners.remove(listener);
        }
        // 移除出 `failedNotified`
        Map<NotifyListener, List<URL>> notified = failedNotified.get(url);
        if (notified != null) {
            notified.remove(listener);
        }
    }

    @Override
    public void register(URL url) {
        // 已銷燬,跳過
        if (destroyed.get()){
            return;
        }
        // 添加到 `registered` 變量
        super.register(url);
        // 移除出 `failedRegistered` `failedUnregistered` 變量
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        // 向註冊中心發送註冊請求
        try {
            // Sending a registration request to the server side
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;

            // 如果開啓了啓動時檢測,則直接拋出異常
            // If the startup detection is opened, the Exception is thrown directly.
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol()); // 非消費者。消費者會在 `ReferenceConfig#createProxy(...)` 方法中,調用 `Invoker#avalible()` 方法,進行檢查。
            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`,定時重試
            // Record a failed registration request to a failed list, retry regularly
            failedRegistered.add(url);
        }
    }

    @Override
    public void unregister(URL url) {
        // 已銷燬,跳過
        if (destroyed.get()){
            return;
        }
        // 移除出 `registered` 變量
        super.unregister(url);
        // 移除出 `failedRegistered` `failedUnregistered` 變量
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        // 向註冊中心發送取消註冊請求
        try {
            // Sending a cancellation request to the server side
            doUnregister(url);
        } catch (Exception e) {
            Throwable t = e;

            // 如果開啓了啓動時檢測,則直接拋出異常
            // If the startup detection is opened, the Exception is thrown directly.
            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 unregister " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to uregister " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }

            // 將失敗的取消註冊請求記錄到 `failedUnregistered`,定時重試
            // Record a failed registration request to a failed list, retry regularly
            failedUnregistered.add(url);
        }
    }

    @Override
    public void subscribe(URL url, NotifyListener listener) {
        // 已銷燬,跳過
        if (destroyed.get()){
            return;
        }
        // 移除出 `subscribed` 變量
        super.subscribe(url, listener);
        // 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified`
        removeFailedSubscribed(url, listener);
        // 向註冊中心發送訂閱請求
        try {
            // Sending a subscription request to the server side
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;

            // 如果有緩存的 URL 集合,進行通知。後續訂閱成功後,會使用最新的 URL 集合,進行通知。
            List<URL> urls = getCacheUrls(url);
            if (urls != null && !urls.isEmpty()) {
                notify(url, listener, urls);
                logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
            } else {
                // 如果開啓了啓動時檢測,則直接拋出異常
                // If the startup detection is opened, the Exception is thrown directly.
                boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                        && url.getParameter(Constants.CHECK_KEY, true);
                boolean skipFailback = t instanceof SkipFailbackWrapperException;
                if (check || skipFailback) {
                    if (skipFailback) {
                        t = t.getCause();
                    }
                    throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
                } else {
                    logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
                }
            }

            // 將失敗的訂閱請求記錄到 `failedSubscribed`,定時重試
            // Record a failed registration request to a failed list, retry regularly
            addFailedSubscribed(url, listener);
        }
    }

    @Override
    public void unsubscribe(URL url, NotifyListener listener) {
        // 已銷燬,跳過
        if (destroyed.get()){
            return;
        }
        // 移除出 `unsubscribed` 變量
        super.unsubscribe(url, listener);
        // 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified`
        removeFailedSubscribed(url, listener);
        // 向註冊中心發送取消訂閱請求
        try {
            // Sending a canceling subscription request to the server side
            doUnsubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;

            // 如果開啓了啓動時檢測,則直接拋出異常
            // If the startup detection is opened, the Exception is thrown directly.
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true);
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if (skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to unsubscribe " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to unsubscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }

            // 將失敗的訂閱請求記錄到 `failedUnsubscribed`,定時重試
            // Record a failed registration request to a failed list, retry regularly
            Set<NotifyListener> listeners = failedUnsubscribed.get(url);
            if (listeners == null) {
                failedUnsubscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
                listeners = failedUnsubscribed.get(url);
            }
            listeners.add(listener);
        }
    }

    @Override
    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");
        }
        // 通知監聽器
        try {
            doNotify(url, listener, urls);
        } catch (Exception t) {
            // 將失敗的通知記錄到 `failedNotified`,定時重試
            // Record a failed registration request to a failed list, retry regularly
            Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
            if (listeners == null) {
                failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
                listeners = failedNotified.get(url);
            }
            listeners.put(listener, urls);
            logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }
    }

    protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
        super.notify(url, listener, urls);
    }

    @Override
    protected void recover() throws Exception {
        // register 恢復註冊,添加到 `failedRegistered` ,定時重試
        Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
        if (!recoverRegistered.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover register url " + recoverRegistered);
            }
            for (URL url : recoverRegistered) {
                failedRegistered.add(url);
            }
        }
        // subscribe 恢復訂閱,添加到 `failedSubscribed` ,定時重試
        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
        if (!recoverSubscribed.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover subscribe url " + recoverSubscribed.keySet());
            }
            for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                URL url = entry.getKey();
                for (NotifyListener listener : entry.getValue()) {
                    addFailedSubscribed(url, listener);
                }
            }
        }
    }

    /**
     * 重試
     */
    // Retry the failed actions
    protected void retry() {
        // 重試執行註冊
        if (!failedRegistered.isEmpty()) {
            Set<URL> failed = new HashSet<URL>(failedRegistered); // 避免併發衝突
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry register " + failed);
                }
                try {
                    for (URL url : failed) {
                        try {
                            // 執行註冊
                            doRegister(url);
                            // 移除出 `failedRegistered`
                            failedRegistered.remove(url);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                    logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        // 重試執行取消註冊
        if (!failedUnregistered.isEmpty()) {
            Set<URL> failed = new HashSet<URL>(failedUnregistered); // 避免併發衝突
            if (!failed.isEmpty()) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry unregister " + failed);
                }
                try {
                    for (URL url : failed) {
                        try {
                            // 執行取消註冊
                            doUnregister(url);
                            // 移除出 `failedUnregistered`
                            failedUnregistered.remove(url);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                    logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        // 重試執行註冊
        if (!failedSubscribed.isEmpty()) {
            Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedSubscribed); // 避免併發衝突
            for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry subscribe " + failed);
                }
                try {
                    for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                        URL url = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                // 執行註冊
                                doSubscribe(url, listener);
                                // 移除出監聽器
                                listeners.remove(listener);
                            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                                logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                    logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        // 重試執行取消註冊
        if (!failedUnsubscribed.isEmpty()) {
            Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedUnsubscribed);
            for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().isEmpty()) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry unsubscribe " + failed);
                }
                try {
                    for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                        URL url = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                // 執行取消註冊
                                doUnsubscribe(url, listener);
                                // 移除出監聽器
                                listeners.remove(listener);
                            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                                logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                    logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        // 重試執行通知監聽器
        if (!failedNotified.isEmpty()) {
            Map<URL, Map<NotifyListener, List<URL>>> failed = new HashMap<URL, Map<NotifyListener, List<URL>>>(failedNotified);
            for (Map.Entry<URL, Map<NotifyListener, List<URL>>> entry : new HashMap<URL, Map<NotifyListener, List<URL>>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry notify " + failed);
                }
                try {
                    for (Map<NotifyListener, List<URL>> values : failed.values()) {
                        for (Map.Entry<NotifyListener, List<URL>> entry : values.entrySet()) {
                            try {
                                NotifyListener listener = entry.getKey();
                                List<URL> urls = entry.getValue();
                                // 通知監聽器
                                listener.notify(urls);
                                // 移除出監聽器
                                values.remove(listener);
                            } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                                logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                    logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章