Dubbo-聊聊註冊中心的設計

前言

Dubbo源碼閱讀分享系列文章,歡迎大家關注點贊

SPI實現部分

  1. Dubbo-SPI機制
  2. Dubbo-Adaptive實現原理
  3. Dubbo-Activate實現原理
  4. Dubbo SPI-Wrapper

註冊中心作用

image.png 在整個Duubbo架構中,註冊中心主要完成以下三件事情:

  1. Provider應用啓動後的初始化階段會向註冊中心完成註冊操作;
  2. Consumer應用啓動初始化階段會完成對所需 Provider的進行訂閱操作;
  3. 在Provider發生變更時通知監聽的Consumer;

Registry在整個架構中主要是對Consumer 和 Provider 所對應的業務進行解耦,從而提升系統的穩定性。關於爲什麼需要註冊中心,大家可以參考一下微服務下的註冊中心如何選擇,在這篇文章中我花了一小節來介紹這個問題。

源碼分析

開始之前,首先來看下關於註冊中心源碼的項目結構,整個項目由兩塊組成,一個就是核心Api,另外一個就是具體一些中間件的實現,看到這個我們可能會有一些想法,這個定然會有一些模板類以及一些工廠設計,能想到這裏,說明你已經具有很好的抽象思維,廢話不多說開始源碼。 image.png

核心Api

dubbo-registry-api是註冊中心核心對象的抽象以及實現部分,我們首先來看下幾個核心對象設計 image.png

Node

Node位於Dubbo的dubbo-common項目下面,在Dubbo中Node這個接口用來抽象節點的概念,Node不僅可以表示Provider和Consumer節點,還可以表示註冊中心節點。Node節點內部定義三個方法:

  1. getUrl方法返回當前節點的URL;
  2. isAvailable方法方法返回對象是否可用;
  3. destroy方法負責銷燬對象;
RegistryService

RegistryService此接口定義註冊中心的功能,定義五個方法:

  1. register方法向註冊中心註冊一個URL;
  2. unregister方法取消一個URL的註冊;
  3. subscribe方法訂閱一個URL,訂閱成功之後,當訂閱的數據發生變化時,註冊中心會主動通知第二個參數指定的 NotifyListener對象,NotifyListener接口中定義的 notify() 方法就是用來接收該通知的;
  4. unsubscribe方法取消一個URL定義,同時當數據發生變化時候也會主動發起通知;
  5. lookup方法查詢符合條件的註冊的數據,subscribe採用Push方式,而lookup採用的是Pull模式;
Registry

image.png Registry接口繼承了Node和RegistryService這兩個接口,實現該接口類就是註冊中心接口的節點,該方法內部也提供兩個默認方法reExportRegister方法和reExportUnregister方法,這兩個方法實際調用還是RegistryService中的方法。

public interface Registry extends NodeRegistryService {
    //調用RegistryService的register
    default void reExportRegister(URL url) {
        register(url);
    }
    //調用RegistryService的unregister
    default void reExportUnregister(URL url) {
        unregister(url);
    }
}
RegistryFactory

RegistryFactory是 Registry 的工廠類,負責創建 Registry 對象,通過@SPI 註解指定了默認的擴展名爲 dubbo,@Adaptive註解表示會生成適配器類並根據 URL 參數中的 protocol 參數值選擇相應的實現。

@SPI("dubbo")
public interface RegistryFactory {
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}

下圖是RegistryFactory多種不同的實現,每個 Registry 實現類都有對應的 RegistryFactory 工廠實現,每個 RegistryFactory 工廠實現只負責創建對應的 Registry 對象。 image.png

AbstractRegistryFactory

AbstractRegistryFactory 是一個實現了 RegistryFactory 接口的抽象類,內部維護一個Registry的Map集合以及提供銷燬和創建註冊中心方法,針對不同的註冊中心可以有不同的實現。

    //鎖 
    protected static final ReentrantLock LOCK = new ReentrantLock();
    //Map
    protected static final Map<String, Registry> REGISTRIES = new HashMap<>();
銷燬

銷燬方法分爲兩個,一個全量,一個是單個,單個銷燬在AbstractRegistry中調用,參數是註冊實例對象。

   //全量銷燬
   public static void destroyAll() {
        if (!destroyed.compareAndSet(falsetrue)) {
            return;
        }

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

   //單個銷燬
   public static void removeDestroyedRegistry(Registry toRm) {
        LOCK.lock();
        try {
            REGISTRIES.entrySet().removeIf(entry -> entry.getValue().equals(toRm));
        } finally {
            LOCK.unlock();
        }
    }

   
創建/獲取

getRegistry是對RegistryFactory實現,如果沒有在緩存中,則進行創建實例對象createRegistry,createRegistry是抽象方法,爲了讓子類重寫該方法,比如說redis實現的註冊中心和zookeeper實現的註冊中心創建方式肯定不同,而他們相同的一些操作都已經在AbstractRegistryFactory中實現,所以只要關注且實現該抽象方法即可。

    //抽象的createRegistry方法
    protected abstract Registry createRegistry(URL url);
    //獲取實例
    public Registry getRegistry(URL url) {

        Registry defaultNopRegistry = getDefaultNopRegistryIfDestroyed();
        if (null != defaultNopRegistry) {
            return defaultNopRegistry;
        }
        //構建key
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEYRegistryService.class.getName())
                .removeParameters(EXPORT_KEYREFER_KEY)
                .build()
;
        String key = createRegistryCacheKey(url);
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            // double check
            // fix https://github.com/apache/dubbo/issues/7265.
            defaultNopRegistry = getDefaultNopRegistryIfDestroyed();
            if (null != defaultNopRegistry) {
                return defaultNopRegistry;
            }
            //獲取實例對象
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            //沒有獲取到就創建
            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();
        }
    }

ListenerRegistryWrapper

image.png ListenerRegistryWrapper是對RegistryFactory的擴展,創建Registry時候會包裝一個ListenerRegistryWrapper對象,內部維護一個監聽器RegistryServiceListener,當註冊、取消註冊、訂閱以及取消訂閱的時候,會發送通知。

AbstractRegistry

AbstractRegistry該抽象類是對Registry接口的實現,實現了Registry接口中的註冊、訂閱、查詢、通知等方法,但是註冊、訂閱、查詢、通知等方法只是簡單地把URL加入對應的集合,沒有具體的註冊或訂閱邏輯。此外該類還實現了緩存機制,只不過,它的緩存有兩份,一份在內存,一份在磁盤。

    //本地的Properties文件緩存,在內存中 與file是同步的
    private final Properties properties = new Properties();
    //該單線程池負責講Provider的全量數據同步到properties字段和緩存文件中,
    //如果syncSaveFile配置爲false,就由該線程池異步完成文件寫入
    private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1new NamedThreadFactory("DubboSaveRegistryCache"true));
    //是否異步寫入
    private boolean syncSaveFile;
    //註冊數據的版本號 防止舊數據覆蓋新數據
    private final AtomicLong lastCacheChanged = new AtomicLong();
    //保存Properties失敗以後異常重試次數
    private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger();
    //已經註冊服務的URL集合,註冊的URL不僅僅可以是服務提供者的,也可以是服務消費者的
    private final Set<URL> registered = new ConcurrentHashSet<>();
    //消費者Url訂閱的監聽器集合
    private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>();
    //費者被通知的服務URL集合,最外部URL的key是消費者的URL,value是一個map集合,裏面的map中的key爲分類名,value是該類下的服務url集合
    private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
    //註冊中心URL
    private URL registryUrl;
    //本地磁盤Properties文件
    private File file;
變更通知

當 Provider 端暴露的 URL 發生變化時,ZooKeeper 等服務發現組件會通知 Consumer 端的 Registry 組件,Registry 組件會調用 notify() 方法,被通知的 Consumer 能匹配到所有 Provider 的 URL 列表並寫入 properties 集合以及本地文件中。

    protected void notify(List<URL> urls) {
        if (CollectionUtils.isEmpty(urls)) {
            return;
        }
        //遍歷訂閱消費者URL的監聽器集合,通知他們
        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變化結果
                        notify(url, listener, filterEmpty(url, urls));
                    } catch (Throwable t) {
                        logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
                    }
                }
            }
        }
    }

    /**
     * Notify changes from the Provider side.
     *
     * @param url      consumer side url
     * @param listener listener
     * @param urls     provider latest urls
     */

    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 ((CollectionUtils.isEmpty(urls))
                && !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<>();
        for (URL u : urls) {
            //按照url中key爲category對應的值進行分類,如果沒有該值,就找key爲providers的值進行分類
            if (UrlUtils.isMatch(url, u)) {
                String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
                List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
                //分類結果放入result
                categoryList.add(u);
            }
        }
        if (result.size() == 0) {
            return;
        }
        //處理通知監聽器URL變化結果
        Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            String category = entry.getKey();
            List<URL> categoryList = entry.getValue();
            //把分類標實和分類後的列表放入notified的value中
            categoryNotified.put(category, categoryList);
            //調用NotifyListener監聽器
            listener.notify(categoryList);
            //單個Url變更,並將更改信息同步至內存緩存和磁盤緩存中
            saveProperties(url);
        }
    }

    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());
                    }
                }
            }
            //推送url至內存緩存
            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);
        }
    }
緩存設計

註冊中心有兩份緩存,一份在內存,一份在磁盤。該方法的作用是將內存中的緩存數據保存在磁盤文件中,該方法有錯誤重試,最大重試次數是3,重試採用另一個線程去執行重試,不是當前線程。本地緩存設計相當於是一種容錯機制,當網絡抖動等原因而導致訂閱失敗時,Consumer端的Registry就可以通過getCacheUrls()方法獲取本地緩存,從而得到最近註冊的服務提供者。

    //將內存中的文件寫到磁盤上
    public void doSaveProperties(long version) {
        //版本號判斷 防止重複寫
        if (version < lastCacheChanged.get()) {
            return;
        }
        //判斷磁盤文件是否爲空
        if (file == null) {
            return;
        }
        // Save
        try {
            //lock文件,用於加鎖操作
            File lockfile = new File(file.getAbsolutePath() + ".lock");
            if (!lockfile.exists()) {
                lockfile.createNewFile();
            }
            //RandomAccessFile提供對文件的讀寫操作
            try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
                 FileChannel channel = raf.getChannel()) {
                //獲取鎖
                FileLock lock = channel.tryLock();
                if (lock == null) {
                    throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");
                }
                // Save
                try {
                    if (!file.exists()) {
                        file.createNewFile();
                    }
                    try (FileOutputStream outputFile = new FileOutputStream(file)) {
                        //從內存緩存中獲取數據 寫入文件
                        properties.store(outputFile, "Dubbo Registry Cache");
                    }
                } finally {
                    lock.release();
                }
            }
        } catch (Throwable e) {
            //發生異常時,重試次數+1
            savePropertiesRetryTimes.incrementAndGet();
            //重試次數大於拋出異常
            if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) {
                logger.warn("Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e);
                savePropertiesRetryTimes.set(0);
                return;
            }
            //再次對比版本信息,如果版本已過期,返回不再處理
            if (version < lastCacheChanged.get()) {
                savePropertiesRetryTimes.set(0);
                return;
            } else {
                //重試線程
                registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));
            }
            logger.warn("Failed to save registry cache file, will retry, cause: " + e.getMessage(), e);
        }
    }
    //磁盤中文件加載到內存中
    private void loadProperties() {
        if (file != null && file.exists()) {
            InputStream in = null;
            try {
                in = new FileInputStream(file);
                properties.load(in);
                if (logger.isInfoEnabled()) {
                    logger.info("Load registry cache file " + file + ", data: " + properties);
                }
            } catch (Throwable e) {
                logger.warn("Failed to load registry cache file " + file, e);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        logger.warn(e.getMessage(), e);
                    }
                }
            }
        }
    }
註冊/訂閱

AbstractRegistry 實現了 Registry 接口,當新節點註冊進來時候registry() 方法,會將當前節點要註冊的 URL緩存到 registered集合,當節點下線時候, unregistry() 方法會從 registered 集合刪除指定的 URL。當消費者新增加一個訂閱的時候,subscribe() 方法會將當前節點作爲 Consumer 的 URL 以及相關的 NotifyListener 記錄到 subscribed 集合,當消費者取消一個訂閱的時候,unsubscribe() 方法會將當前節點的 URL 以及關聯的 NotifyListener 從 subscribed 集合刪除。這四個方法相對比較簡單,這裏不做展示,此處設計爲抽象類,當子類重寫的時候可以對其進行增強。

恢復/銷燬

當因爲網絡問題與註冊中心斷開連接之後,會進行重連,重新連接成功之後,會調用 recover() 方法將 registered 集合中的全部 URL 重新執行register() 方法,恢復註冊數據。同樣,recover() 方法也會將 subscribed 集合中的 URL 重新執行subscribe() 方法,恢復訂閱監聽器。 當前節點下線的時候,destroy() 方法會調用 unregister() 方法和 unsubscribe() 方法將當前節點註冊的 URL 以及訂閱的監聽全部清理掉,此外還會銷燬本實例。

    public void destroy() {
        if (logger.isInfoEnabled()) {
            logger.info("Destroy registry:" + getUrl());
        }
        Set<URL> destroyRegistered = new HashSet<>(getRegistered());
        if (!destroyRegistered.isEmpty()) {
            for (URL url : new HashSet<>(destroyRegistered)) {
                if (url.getParameter(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<>(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);
                    }
                }
            }
        }
        //移除註冊中心
        AbstractRegistryFactory.removeDestroyedRegistry(this);
    }

重試機制

image.png 關於重試機制,Dubbo將重試機制放在了FailbackRegistry類中,FailbackRegistry 設計思想,重寫了 AbstractRegistry 中 register()/unregister()、subscribe()/unsubscribe() 以及 notify() 這五個核心方法,結合時間輪,實現失敗重試機制。此外,還添加了四個未實現的抽象模板方法,由其繼承者去實現,這裏也就是典型的模板類的設計。

核心字段介紹
    //註冊失敗的URL Key是註冊失敗的 URL,Value 是對應的重試任務
    private final ConcurrentMap<URL, FailedRegisteredTask> failedRegistered = new ConcurrentHashMap<URL, FailedRegisteredTask>();
    //取消註冊失敗URL
    private final ConcurrentMap<URL, FailedUnregisteredTask> failedUnregistered = new ConcurrentHashMap<URL, FailedUnregisteredTask>();
    //訂閱失敗的URL
    private final ConcurrentMap<Holder, FailedSubscribedTask> failedSubscribed = new ConcurrentHashMap<Holder, FailedSubscribedTask>();
    //取消訂閱失敗的URL
    private final ConcurrentMap<Holder, FailedUnsubscribedTask> failedUnsubscribed = new ConcurrentHashMap<Holder, FailedUnsubscribedTask>();

    //重試的時間間隔
    private final int retryPeriod;
    //用於定時執行失敗的時間輪
    private final HashedWheelTimer retryTimer;

構造方法首先會調用父類的構造方法完成本地緩存相關的初始化操作,然後根據傳入URL參數中獲取重試操作的時間間隔來初始化 retryPeriod 字段,最後初始化 HashedWheelTimer時間輪。

    public FailbackRegistry(URL url) {
        //調用
        super(url);
        this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);

        retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer"true), retryPeriod, TimeUnit.MILLISECONDS, 128);
    }
核心方法
register

register方法重寫了父類的註冊方法,首先調用父類的register將url加入對應的容器,然後從failedRegistered 和failedUnregistered 兩個容器中移除失敗URL,然後執行doRegister方法,doRegister是抽象方法,具體的實現交給其繼承者,如果註冊失敗拋出異常,會將URL加入failedRegistered 容器中。

    @Override
    public void register(URL url) {
        if (!acceptable(url)) {
            logger.info("URL " + url + " will not be registered to Registry. Registry " + url + " does not accept service of this protocol type.");
            return;
        }
        //執行父類的方法加入到容器中
        super.register(url);
        //移除註冊失敗
        removeFailedRegistered(url);
        //移除取消註冊失敗
        removeFailedUnregistered(url);
        try {
            //抽象方法交給具體子類去實現
            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)
                    && !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);
            }

            //註冊發生異常將註冊失敗放入到註冊失敗的容器中
            addFailedRegistered(url);
        }
    }

接下來我們看下添加重試任務的方法addFailedRegistered,該方法相對比較簡單,核心就是將失敗的任務放到容器中,然後將失敗的任務加入時間輪等待執行。

    private void addFailedRegistered(URL url) {
        //判斷容器中是是否存在任務
        FailedRegisteredTask oldOne = failedRegistered.get(url);
        if (oldOne != null) {
            return;
        }
        //將任務添加容器中
        FailedRegisteredTask newTask = new FailedRegisteredTask(url, this);
        oldOne = failedRegistered.putIfAbsent(url, newTask);
        if (oldOne == null) {
            // never has a retry task. then start a new task for retry.
            //將任務提交到時間輪中 等待retryPeriod秒後執行
            retryTimer.newTimeout(newTask, retryPeriod, TimeUnit.MILLISECONDS);
        }
    }

對於其他unregister()、subscribe()、unsubscribe() 都與register()類似這裏就不做過多介紹,簡單看下提供幾個抽象的方法。

    public abstract void doRegister(URL url);

    public abstract void doUnregister(URL url);

    public abstract void doSubscribe(URL url, NotifyListener listener);

    public abstract void doUnsubscribe(URL url, NotifyListener listener);
重試任務

addFailedRegistered方法中創建FailedRegisteredTask以及其他重試任務,都是繼承AbstractRetryTask,接下來我們要來關於AbstractRetryTask的設計和實現。 image.png 在AbstractRetryTask抽象類中,有一個核心run方法實現已經一個抽象方法,該抽象方法也是模板類作用。

 @Override
    public void run(Timeout timeout) throws Exception {
        //檢查定義任務狀態以及時間輪狀態
        if (timeout.isCancelled() || timeout.timer().isStop() || isCancel()) {
            // other thread cancel this timeout or stop the timer.
            return;
        }
        //檢查重試次數
        if (times > retryTimes) {
            // reach the most times of retry.
            logger.warn("Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
            return;
        }
        if (logger.isInfoEnabled()) {
            logger.info(taskName + " : " + url);
        }
        try {
            //執行重試
            doRetry(url, registry, timeout);
        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
            logger.warn("Failed to execute task " + taskName + ", url: " + url + ", waiting for again, cause:" + t.getMessage(), t);
            // reput this task when catch exception.
            //執行異常 則重新等待
            reput(timeout, retryPeriod);
        }
    }
    protected void reput(Timeout timeout, long tick) {
        //邊界值檢查
        if (timeout == null) {
            throw new IllegalArgumentException();
        }
        //檢查定時任務
        Timer timer = timeout.timer();
        if (timer.isStop() || timeout.isCancelled() || isCancel()) {
            return;
        }
        //遞增times
        times++;
        //添加下次定時任務
        timer.newTimeout(timeout.task(), tick, TimeUnit.MILLISECONDS);
    }
    protected abstract void doRetry(URL url, FailbackRegistry registry, Timeout timeout);

接下來我們看下FailedRegisteredTask對AbstractRetryTask�的實現,子類doRetry方法會執行關聯Registry的doRegister() 方法,完成與服務發現組件交互。如果註冊成功,則會調用removeFailedRegisteredTask()方法將當前關聯的 URL 以及當前重試任務從 failedRegistered集合中刪除。如果註冊失敗,則會拋出異常,執行AbstractRetryTask的reput()方法重試。

    @Override
    protected void doRetry(URL url, FailbackRegistry registry, Timeout timeout) {
        // 重新註冊
        registry.doRegister(url);
        // 刪除註冊任務
        registry.removeFailedRegisteredTask(url);
    }

未完待續

下一篇會簡單介紹一下時間輪、ZK對註冊中心的實現以及在,歡迎大家點點關注,點點贊!

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