Dubbo注冊(cè)中心

1 Dubbo注冊(cè)中心概述

Dubbo的注冊(cè)中心承擔(dān)著Dubbo服務(wù)的注冊(cè)與發(fā)現(xiàn)的功能燕偶。

Dubbo支持的注冊(cè)中心主要包括:

  • ZookeeperRegistry

  • MulticastRegistry

  • RedisRegistry

  • DubboRegistry

其中dubbo官方推薦用Zookeeper作為注冊(cè)中心贩耐,下面介紹ZookeeperRegistry

2 Dubbo服務(wù)注冊(cè)

2.1 Dubbo服務(wù)注冊(cè)組件

Dubbo在Registry層實(shí)現(xiàn)服務(wù)的注冊(cè)于發(fā)現(xiàn)醒叁,主要包括如下幾個(gè)類:

  • ZookeeperRegistry:負(fù)責(zé)與zookeeper進(jìn)行交互;

  • RegistryProtocol:從注冊(cè)中心獲取可用服務(wù),或者將服務(wù)注冊(cè)到zookeeper疫粥,然后提供服務(wù)或者提供調(diào)用代理蟆湖;

  • RegistryDirectory:維護(hù)著所有可用的遠(yuǎn)程Invoker或者本地的Invoker故爵,這個(gè)類實(shí)現(xiàn)了NotifyListner

  • NotifyListener :負(fù)責(zé)RegistryDirectoryZookeeperRegistry的通信隅津;

  • FailbackRegistry:繼承自Registry诬垂,實(shí)現(xiàn)了失敗重試機(jī)制;

2.2 Zookeeper的服務(wù)注冊(cè)模型

流程說明

  • 服務(wù)提供者啟動(dòng)時(shí)

    • /dubbo/com.foo.BarService/providers目錄下寫入自己的URL地址饥瓷。
  • 服務(wù)消費(fèi)者啟動(dòng)時(shí)

    • 訂閱/dubbo/com.foo.BarService/providers目錄下的提供者URL地址剥纷。

    • 并向/dubbo/com.foo.BarService/consumers目錄下寫入自己的URL地址。

  • 監(jiān)控中心啟動(dòng)時(shí)

    • 訂閱/dubbo/com.foo.BarService目錄下的所有提供者和消費(fèi)者URL地址呢铆。

3 服務(wù)發(fā)布

RegistryProtocol是對(duì)需要暴露服務(wù)到注冊(cè)中心的一層封裝晦鞋,通過RegistryProtocol實(shí)現(xiàn)將暴露的服務(wù)信息注冊(cè)到注冊(cè)中心。

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // 暴露服務(wù),然后包裝暴露者
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

        // 獲取注冊(cè)中心地址
        // 通過服務(wù)的registry配置悠垛,
        //<dubbo:registry id="asRegistry" client="zkclient" protocol="zookeeper" register="true" address="zookeeper:2181" file="D:/dubbo/user/dubbo.registry"/>
        //<dubbo:protocol name="dubbo" port="20880" dispatcher="direct" threadpool="fixed" threads="2"/>
        //<dubbo:service connections="10" interface="com.wuhulala.dubbo.user.service.UserService" ref="demoService" registry="asRegistry"/>

        URL registryUrl = getRegistryUrl(originInvoker);

        //根據(jù)注冊(cè)URL 獲取注冊(cè)中心
        final Registry registry = getRegistry(originInvoker);

        // 處理需要注冊(cè)到注冊(cè)中心的地址
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

        //判斷是否需要注冊(cè)线定,為什么不放到最上面
        boolean register = registedProviderUrl.getParameter("register", true);

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {
            // 注冊(cè)邏輯
            register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
       }
       // 省略 ...

    }

    public void register(URL registryUrl, URL registedProviderUrl) {
        // registry在初始化的時(shí)候被注入一個(gè)自適應(yīng)的擴(kuò)展點(diǎn)ExtensionLoader.getExtensionLoader(type).getAdaptiveExtension()
        //所以發(fā)布的時(shí)候也是自適應(yīng)尋找對(duì)應(yīng)擴(kuò)展點(diǎn)進(jìn)行注冊(cè)到注冊(cè)中心
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registedProviderUrl);
    }

3 ZookeeperRegistry

ZookeeperRegistry是通過ZookeeperRegistryFactory創(chuàng)建。

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 = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

說明:

上面是AbstractRegistryFactory#getRegistry方法根據(jù)URL獲取對(duì)應(yīng)注冊(cè)中心确买,真正的創(chuàng)建在子類的createRegistry方法中實(shí)現(xiàn)斤讥。

3.1 ZookeeperRegistryFactory
    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

注意

ZookeeperTransporter才是真正與Zookeeper通信的對(duì)象。默認(rèn)的Zookeeper客戶端是curator湾趾。

3.2 ZookeeperRegistry的創(chuàng)建
    /**
     * 默認(rèn)zookeeper的跟節(jié)點(diǎn) dubbo
     */
    private final static String DEFAULT_ROOT = "dubbo";

    /**
     * 根節(jié)點(diǎn)
     */
    private final String root;

    /**
     * Service 接口全名集合芭商。
     * 該屬性適可用于監(jiān)控中心,訂閱整個(gè)Service層搀缠。因?yàn)镾ervice層是動(dòng)態(tài)的铛楣,可以有不斷有新的Service服務(wù)發(fā)布(注意,不是服務(wù)實(shí)例)艺普。
     * 在 #doSubscribe(url, notifyListener) 方法中簸州,
     */
    private final Set<String> anyServices = new ConcurrentHashSet<>();

    /**
     * 監(jiān)聽器集合
     */
    private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<>();

    /**
     * zookeeper客戶端
     */
    private final ZookeeperClient zkClient;

    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        //獲取根節(jié)點(diǎn),不設(shè)置則使用/dubbo
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        //獲取zk客戶端調(diào)用 ZookeeperTransporter#connect(url) 方法歧譬,
        // 基于 Dubbo SPIAdaptive 機(jī)制岸浑,根據(jù)url參數(shù),加載對(duì)應(yīng)的 ZookeeperTransporter 實(shí)現(xiàn)類瑰步,創(chuàng)建對(duì)應(yīng)的 ZookeeperClient 實(shí)現(xiàn)類的對(duì)應(yīng)矢洲。
        zkClient = zookeeperTransporter.connect(url);
        //設(shè)置監(jiān)聽器
        zkClient.addStateListener((state) -> {
            //重連,重新獲取最新的服務(wù)提供方地址信息
            if (state == StateListener.RECONNECTED) {
                logger.warn("Trying to fetch the latest urls, in case there're provider changes during connection loss.\n" +
                        " Since ephemeral ZNode will not get deleted for a connection lose, " +
                        "there's no need to re-register url of this instance.");
                ZookeeperRegistry.this.fetchLatestAddresses();
            } else if (state == StateListener.NEW_SESSION_CREATED) {
                //重新創(chuàng)建session時(shí)缩焦,將本服務(wù)進(jìn)行recover恢復(fù)兵钮,重新發(fā)起注冊(cè)和訂閱
                logger.warn("Trying to re-register urls and re-subscribe listeners of this instance to registry...");
                try {
                    ZookeeperRegistry.this.recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            } else if (state == StateListener.SESSION_LOST) {
                logger.warn("Url of this instance will be deleted from registry soon. " +
                        "Dubbo client will try to re-register once a new session is created.");
            } else if (state == StateListener.SUSPENDED) {

            } else if (state == StateListener.CONNECTED) {

            }
        });
    }

ZookeeperRegistry在創(chuàng)建時(shí)便和Zookeeper創(chuàng)建了連接。

4 服務(wù)注冊(cè)

Zookeeper的服務(wù)注冊(cè)就是創(chuàng)建Zookeeper的節(jié)點(diǎn)舌界。

    public void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

根據(jù)url對(duì)象構(gòu)建一個(gè)URL的Path掘譬,然后在Zookeeper上創(chuàng)建一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)是否是持久化的根據(jù)dynamic參數(shù)決定呻拌,true表示創(chuàng)建一個(gè)持久化節(jié)點(diǎn)葱轩,當(dāng)注冊(cè)方下線時(shí),持久化節(jié)點(diǎn)仍然在Zookeeper上藐握。false表示創(chuàng)建臨時(shí)節(jié)點(diǎn)靴拱,當(dāng)注冊(cè)方下線時(shí),會(huì)刪除節(jié)點(diǎn)猾普,同時(shí)通知監(jiān)聽節(jié)點(diǎn)的消費(fèi)方袜炕。

4.1 節(jié)點(diǎn)路徑

Zookeeper中的服務(wù)節(jié)點(diǎn)路徑如Group/ServiceInterface/category/URL

  • Group:表示節(jié)點(diǎn)的根路徑,默認(rèn)是dubbo初家;

  • ServiceInterface:接口的全限定類名偎窘;

  • category:接口的類別乌助,providers、consumers陌知、configurators他托、routers等這些;

  • URL:URL的編碼仆葡;

4.2 服務(wù)下線

服務(wù)下線就是把注冊(cè)的節(jié)點(diǎn)進(jìn)行刪除赏参。

    public void doUnregister(URL url) {
        try {
            zkClient.delete(toUrlPath(url));
        } catch (Throwable e) {
            throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

5 服務(wù)訂閱

訂閱有兩種方式pull、push 沿盅,pull是客戶端定時(shí)輪詢注冊(cè)中心拉取配置把篓,push是注冊(cè)中心主動(dòng)推送數(shù)據(jù)給客戶端。dubbo是兩者結(jié)合的方式進(jìn)行配置的拉取腰涧,當(dāng)?shù)谝淮螁?dòng)的時(shí)候通過客戶端pull拉取所有數(shù)據(jù)方式纸俭,在訂閱的節(jié)點(diǎn)上進(jìn)行注冊(cè)watcher,然后客戶端與注冊(cè)中心保持長(zhǎng)連接南窗,而后 每個(gè)節(jié)點(diǎn)有任何數(shù)據(jù)變化,注冊(cè)中心就會(huì)更新watcher情況進(jìn)行回調(diào)通知到客戶端郎楼,客戶端就接收到這個(gè)通知了万伤。

服務(wù)在進(jìn)行注冊(cè)的時(shí)候,服務(wù)端會(huì)訂閱configurators用來監(jiān)聽動(dòng)態(tài)配置的變更呜袁。

在消費(fèi)者啟動(dòng)的時(shí)候敌买,消費(fèi)者會(huì)監(jiān)聽providers、routers阶界、configurators來監(jiān)聽服務(wù)提供者虹钮、路由規(guī)則、配置變更的通知膘融。

5.1 doSubscribe
 public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            //處理所有service的訂閱,
            //客戶端第一次連接上注冊(cè)中心的時(shí)候芙粱,會(huì)把這個(gè)service設(shè)置成*  也就是 ANY_VALUE,進(jìn)行獲取全量數(shù)據(jù)
            if (ANY_VALUE.equals(url.getServiceInterface())) {
                String root = toRootPath();
                //獲取url的監(jiān)聽器
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    //沒有l(wèi)istener則創(chuàng)建一
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                    listeners = zkListeners.get(url);
                }
                //獲取ChildListener
                ChildListener zkListener = listeners.get(listener);
                //第一次連接時(shí)為空,新建一個(gè)childListener
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, (parentPath, currentChilds) -> {
                        //遍歷所有子節(jié)點(diǎn)
                        for (String child : currentChilds) {
                            child = URL.decode(child);
                            //有新增服務(wù)氧映,則發(fā)起這個(gè)服務(wù)的訂閱
                            if (!anyServices.contains(child)) {
                                anyServices.add(child);
                                subscribe(url.setPath(child).addParameters(INTERFACE_KEY, child,
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                //創(chuàng)建持久化節(jié)點(diǎn)春畔,訂閱持久化節(jié)點(diǎn)的子節(jié)點(diǎn)
                zkClient.create(root, false);
                List<String> services = zkClient.addChildListener(root, zkListener);
                //獲取到全量service,對(duì)每一個(gè)service進(jìn)行訂閱
                if (CollectionUtils.isNotEmpty(services)) {
                    for (String service : services) {
                        service = URL.decode(service);
                        anyServices.add(service);
                        subscribe(url.setPath(service).addParameters(INTERFACE_KEY, service,
                                Constants.CHECK_KEY, String.valueOf(false)), listener);
                    }
                }
            } else {//客戶端非第一次連接上注冊(cè)中心岛都,對(duì)于具體的服務(wù)進(jìn)行訂閱
                List<URL> urls = new ArrayList<>();
                //根據(jù)URL類別律姨,獲取一組需要訂閱的路徑
                //類別:providers、routers臼疫、consumers择份、configurators
                for (String path : toCategoriesPath(url)) {
                    //獲取url對(duì)應(yīng)的監(jiān)聽器,沒有則創(chuàng)建
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                        listeners = zkListeners.get(url);
                    }
                    //獲取ChildListener烫堤,沒有則創(chuàng)建
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        //子節(jié)點(diǎn)數(shù)據(jù)發(fā)生變更時(shí)則調(diào)用notify方法進(jìn)行通知這個(gè)listener
                        listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                        zkListener = listeners.get(listener);
                    }
                    //創(chuàng)建type的持久化節(jié)點(diǎn)荣赶,且對(duì)于這個(gè)節(jié)點(diǎn)進(jìn)行訂閱
                    zkClient.create(path, false);
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                //數(shù)據(jù)獲取完成時(shí)凤价,調(diào)用 `#notify(...)` 方法,回調(diào) NotifyListener讯壶,urls為所有自節(jié)點(diǎn)的url

                notify(url, listener, urls);
            }
        } catch (Throwable e) {
            throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }
5.2 doUnsubscribe
    public void doUnsubscribe(URL url, NotifyListener listener) {
        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
        if (listeners != null) {
            ChildListener zkListener = listeners.get(listener);
            if (zkListener != null) {
                if (ANY_VALUE.equals(url.getServiceInterface())) {
                    String root = toRootPath();
                    zkClient.removeChildListener(root, zkListener);
                } else {
                    for (String path : toCategoriesPath(url)) {
                        zkClient.removeChildListener(path, zkListener);
                    }
                }
            }
        }
    }

取消訂閱就是刪除監(jiān)聽器料仗。

5.3 fetchLatestAddresses

當(dāng)與zookeeper重新連接時(shí),需要刷新本機(jī)生產(chǎn)者列表伏蚊,這個(gè)方法就是干這個(gè)的立轧,它把的已訂閱的列表都加入訂閱失敗的容器。

//刷新生產(chǎn)者列表
    private void fetchLatestAddresses() {
        // subscribe
        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
        if (!recoverSubscribed.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Fetching the latest urls of " + recoverSubscribed.keySet());
            }
            for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                URL url = entry.getKey();
                //將當(dāng)前訂閱的列表全部推送至訂閱失敗的容器
                for (NotifyListener listener : entry.getValue()) {
                    addFailedSubscribed(url, listener);
                }
            }
        }
    }

注意

addFailedSubscribed方法是FailbackRegistry將訂閱失敗的連接放入失敗容器躏吊,FailbackRegistry有定時(shí)器會(huì)定時(shí)的處理這些失敗的訂閱氛改。

6 服務(wù)監(jiān)聽

通知事件是調(diào)用的AbstractRegistry#notify方法完成。

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
       //......
        //將url分類放置
        Map<String, List<URL>> result = new HashMap<>();
        for (URL u : urls) {
            if (UrlUtils.isMatch(url, u)) {
                String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
                List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
                categoryList.add(u);
            }
        }
        if (result.size() == 0) {
            return;
        }
        //獲取url在notified的全部url數(shù)據(jù),若notified無則創(chuàng)建一個(gè)且放入
        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();
            categoryNotified.put(category, categoryList);
            //對(duì)于所有url進(jìn)行通知
            listener.notify(categoryList);
            // We will update our cache file after each notification.
            // When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
            //當(dāng)我們的注冊(cè)表由于網(wǎng)絡(luò)抖動(dòng)而出現(xiàn)訂閱失敗時(shí)比伏,我們至少可以返回現(xiàn)有的緩存URL胜卤。
            saveProperties(url);
        }
    }

流程說明

  1. 對(duì)需要通知的url進(jìn)行分類;
  2. 分類調(diào)用notify方法進(jìn)行通知赁项,這個(gè)也就是org.apache.dubbo.registry.NotifyListener#notify這個(gè)方法葛躏,也就是在服務(wù)訂閱doSubscribe傳入的參數(shù)NotifyListener
  3. 將url進(jìn)行保存緩存悠菜;
6.1 NotifyListener通知
public interface NotifyListener {

    /**
     * 當(dāng)收到服務(wù)變更通知時(shí)觸發(fā)舰攒。
     * <p>
     * 通知需處理契約:<br>
     * 1\. 總是以服務(wù)接口和數(shù)據(jù)類型為維度全量通知,即不會(huì)通知一個(gè)服務(wù)的同類型的部分?jǐn)?shù)據(jù)悔醋,用戶不需要對(duì)比上一次通知結(jié)果摩窃。<br>
     * 2\. 訂閱時(shí)的第一次通知,必須是一個(gè)服務(wù)的所有類型數(shù)據(jù)的全量通知芬骄。<br>
     * 3\. 中途變更時(shí)猾愿,允許不同類型的數(shù)據(jù)分開通知,比如:providers, consumers, routers, overrides账阻,允許只通知其中一種類型蒂秘,但該類型的數(shù)據(jù)必須是全量的,不是增量的淘太。<br>
     * 4\. 如果一種類型的數(shù)據(jù)為空材彪,需通知一個(gè)empty協(xié)議并帶category參數(shù)的標(biāo)識(shí)性URL數(shù)據(jù)。<br>
     * 5\. 通知者(即注冊(cè)中心實(shí)現(xiàn))需保證通知的順序琴儿,比如:?jiǎn)尉€程推送段化,隊(duì)列串行化,帶版本對(duì)比造成。<br>
     *
     * @param urls 已注冊(cè)信息列表显熏,總不為空,含義同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值晒屎。
     */
    void notify(List<URL> urls);

}

RegistryDirectory#notify就是更新本地緩存喘蟆。

6.2 saveProperties
private void saveProperties(URL url) {
        if (file == null) {
            return;
        }

        try {
            StringBuilder buf = new StringBuilder();
            /**
             * notifies是 被通知的 URL 集合
             *
             * key1:消費(fèi)者的 URL 缓升,例如消費(fèi)者的 URL ,和 {@link #subscribed} 的鍵一致
             * key2:分類蕴轨,例如:providers港谊、consumers、routes橙弱、configurators歧寺。【實(shí)際無 consumers 棘脐,因?yàn)橄M(fèi)者不會(huì)去訂閱另外的消費(fèi)者的列表】
             * 在 {@link Constants} 中斜筐,以 "_CATEGORY" 結(jié)尾
             */
            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);
        }
    }

注意

file在AbstractRegistry對(duì)象創(chuàng)建的時(shí)候初始化。

6.3 doSaveProperties
public void doSaveProperties(long version) {
        if (version < lastCacheChanged.get()) {
            return;
        }
        if (file == null) {
            return;
        }
        // Save
        try {
            File lockfile = new File(file.getAbsolutePath() + ".lock");
            if (!lockfile.exists()) {
                lockfile.createNewFile();
            }
            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) {
            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);
        }
    }

這使用了文件鎖的方式保證只有一個(gè)在讀寫文件蛀缝。

消費(fèi)者從注冊(cè)中心獲取注冊(cè)信息后會(huì)做本地緩存顷链。內(nèi)存中也有一份,保存在Properties對(duì)象里屈梁,磁盤上也持久化一份嗤练,通過file對(duì)象進(jìn)行引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末在讶,一起剝皮案震驚了整個(gè)濱河市煞抬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌真朗,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僧诚,死亡現(xiàn)場(chǎng)離奇詭異遮婶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)湖笨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門旗扑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慈省,你說我怎么就攤上這事臀防。” “怎么了边败?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵袱衷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我笑窜,道長(zhǎng)致燥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任排截,我火速辦了婚禮嫌蚤,結(jié)果婚禮上辐益,老公的妹妹穿的比我還像新娘。我一直安慰自己脱吱,他們只是感情好智政,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著箱蝠,像睡著了一般续捂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抡锈,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天疾忍,我揣著相機(jī)與錄音,去河邊找鬼床三。 笑死一罩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撇簿。 我是一名探鬼主播聂渊,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼四瘫!你這毒婦竟也來了汉嗽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤找蜜,失蹤者是張志新(化名)和其女友劉穎饼暑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洗做,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弓叛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诚纸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撰筷。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖畦徘,靈堂內(nèi)的尸體忽然破棺而出毕籽,到底是詐尸還是另有隱情,我是刑警寧澤井辆,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布关筒,位于F島的核電站,受9級(jí)特大地震影響杯缺,放射性物質(zhì)發(fā)生泄漏平委。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一夺谁、第九天 我趴在偏房一處隱蔽的房頂上張望廉赔。 院中可真熱鬧肉微,春花似錦、人聲如沸蜡塌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馏艾。三九已至劳曹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琅摩,已是汗流浹背铁孵。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留房资,地道東北人蜕劝。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像轰异,于是被迫代替她去往敵國(guó)和親岖沛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容