Dubbo源碼消費者端服務的訂閱流程

關注消費者端服務的訂閱,我們從ReferenceConfig中的createProxy()開始分析,我們重點先關注其中的一句,當註冊中心只有一個(單個或集羣)時進入此分支即執行下面這一句。(關於createProxy整個流程會在後面博文中重點介紹)

invoker = refprotocol.refer(interfaceClass, urls.get(0));
其中refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();之前關於@SPI的文章中分析過,refProtocol其實是字節碼動態生成的適配類。
    public class Protocol$Adpative implements Protocol {  
        public Exporter export(Invoker invoker) throws com.alibaba.dubbo.rpc.RpcException {  
            if (invoker == null || invoker.getUrl() == null) {  
                throw new IllegalArgumentException("xxx");  
            }  
              
            URL url = invoker.getUrl();  
            String extName = url.getProtocol() == null ? "dubbo" : url.getProtocol();  
            if(extName == null) {  
                throw new IllegalStateException("xxx");  
            }  
              
            Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);  
            return extension.export(invoker);  
        }  
      
        public Invoker refer(Class cls, URL u) throws RpcException {  
            if (u == null) {  
                throw new IllegalArgumentException("url == null");  
            }  
            URL url = u;  
            String extName = url.getProtocol() == null ? "dubbo" : url.getProtocol();  
            if(extName == null) {  
                throw new IllegalStateException("xxx");  
            }  
            Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);  
            return extension.refer(cls, u);  
        }  
    }  

實際調用了其中的refer()方法,我們可以看到SPI的動態選擇在這裏體現,這裏傳入的URL的protocol爲reistery,所以這裏的extension實際上是registryProtocol的實例。這裏調用了registryProtocol的refer()方法。

    @SuppressWarnings("unchecked")
	public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY))
                .removeParameter(Constants.REGISTRY_KEY);
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
        	return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
        String group = qs.get(Constants.GROUP_KEY);
        if (group != null && group.length() > 0 ) {
            if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
                    || "*".equals( group ) ) {
                return doRefer( getMergeableCluster(), registry, type, url );
            }
        }
        return doRefer(cluster, registry, type, url);
    }

先修改url數據結構上的protocol改爲registry對應的協議(比如multicast或者zookeeper),調用registryFactory的getRegister,根據url返回不同的registry對象,在初始化registry對象時,通過代理和註冊中心建立連接。其中registryFactory也是個代理適配器對象(會根據url對應不同的協議實際上可能是ZookeeperRegistryFactory、MulticastRegistryFactory等)。這裏採用默認的dubbo實現,其中getRegistry()方法具體邏輯在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();
        }
    }

先把要註冊的服務跟接口都改爲RegistryService(RegistryService是註冊中心的接口)。這裏會根據生成的註冊中心服務url尋找註冊中心的實例,如果已經生成過,那麼直接返回,否則調用createRegister()生成新的註冊中心。

默認是dubbo實現的registryFactory爲DubboRegistryFactory,我們來看下具體的createRegistry()方法。

    public Registry createRegistry(URL url) {
        url = getRegistryURL(url);
        List<URL> urls = new ArrayList<URL>();
        urls.add(url.removeParameter(Constants.BACKUP_KEY));
        String backup = url.getParameter(Constants.BACKUP_KEY);
        if (backup != null && backup.length() > 0) {
            String[] addresses = Constants.COMMA_SPLIT_PATTERN.split(backup);
            for (String address : addresses) {
                urls.add(url.setAddress(address));
            }
        }
        RegistryDirectory<RegistryService> directory = new RegistryDirectory<RegistryService>(RegistryService.class,
             url.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
            .addParameterAndEncoded(Constants.REFER_KEY, url.toParameterString()));
        Invoker<RegistryService> registryInvoker = cluster.join(directory);
        RegistryService registryService = proxyFactory.getProxy(registryInvoker);
        DubboRegistry registry = new DubboRegistry(registryInvoker, registryService);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        directory.notify(urls);
        directory.subscribe(new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0,
             RegistryService.class.getName(), url.getParameters()));
        return registry;
    }

我們可以看到,這裏構造了接口registryService的代理,(這裏,我們可以把註冊中心理解成暴露服務端,與註冊中心連接的過程可以看成RegistryService的遠程方法調用的過程,當然跟其他服務調用不同的是,這裏註冊中心作爲生產者是直接與當前消費者直連,而不是在註冊中心得到url,redirect訪問生產者)。這裏directory的subscribe實際上是調用register的subsubscribe。

    public void subscribe(URL url) {
        setConsumerUrl(url);
        registry.subscribe(url, this);
    }    
    protected void doSubscribe(URL url, NotifyListener listener) {
        registryService.subscribe(url, listener);
    }
實則調用了registryService代理的subscrible,根據之前對代理的分析我們明確知道,他是調用了registryInvoker的invoke方法,其實就是一次普通的遠程調用方法。傳入參數的url中被加入了回調參數,listener是變更事件監聽器,註冊中心通過對這個參數的調用回調給消費者。

在得到registry之後,我們繼續registryProtocol的refer()方法

我們可以看到根據url上面的group的配置,分爲group模式跟非group模式,剩下都是調用doRefer(),無非在group模式時返回的invoker爲MergeableClusterInvoker,非group模式時返回的invoker爲FailoverClusterInvoker。

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0,
                 type.getName(), directory.getUrl().getParameters());
        if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
                && url.getParameter(Constants.REGISTER_KEY, true)) {
            registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                    Constants.CHECK_KEY, String.valueOf(false)));
        }
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                Constants.PROVIDERS_CATEGORY 
                + "," + Constants.CONFIGURATORS_CATEGORY 
                + "," + Constants.ROUTERS_CATEGORY));
        return cluster.join(directory);
    }

這裏的registry就是之前的registryService,通過遠程調用的方式,其中url中以接口名字爲參數,來完成接口方法的訂閱。在默認dubbo協議下異步等待註冊中心回調,通過.join()方法得到默認的invoker,FailoverClusterInvoker即遠程服務執行體。當然,註冊中心需要在subscribe()方法末尾遠程回調listener的notify方法,即在完成訂閱後告訴消費者,達到服務地址的回調

小結

通過配置選擇合適的協議,生成對應的registryService代理,通過遠程回調的方式,連接上註冊中心,再通過registryService代理,傳遞帶有接口類型url數據結構,實現在註冊中心的服務的訂閱,得到服務地址,返回invoker。

說在最後的話

本文可能思路多多少少不是特別清楚,比較我也是邊看邊寫的,等我把生產者服務暴露端的流程過一遍後,再把整個串起來總結一遍。


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