Dubbo解析(五)-服務引用

上一章介紹了Dubbo的服務與註冊中心交互圖spring

image

交互過程以下:app

  1. 提供者向註冊中心註冊,並暴露本地服務
  2. 消費者向註冊中心註冊,並訂閱提供者列表
  3. 消費者獲取提供者列表,
  4. 消費者按照負載均衡選擇一個提供者,直接調用其服務

上一章介紹了第一個操做,即服務的發佈;這一章將介紹第2、三兩個操做,簡單的說,就是服務的引用。負載均衡

消費者引用dubbo服務,能夠經過spring的xml配置jvm

<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>

dubbo:reference將轉換成ReferenceBean,最終造成對遠程提供者服務的代理,即對服務的引用。ReferenceBean繼承ReferenceConfig抽象配置類,同時實現多個Spring相關的容器接口。ide

其中最重要的是FactoryBean接口,它是spring的一個重要的擴展方式。對於dubbo:reference建立的spring Bean,並非想要ReferenceBean這個對象自己,而是想獲取對遠程服務的代理。經過FactoryBean.getObject()來建立基於服務接口的代理,從而將複雜的實現封裝,使用戶能夠不關注底層的實現。url

getObject方法調用ReferenceConfig的get方法,繼而調用init方法初始化服務引用。init方法對消費者的配置進行校驗和組裝,和服務發佈同樣,造成建立URL的map對象,而後調用createProxy方法建立代理。.net

createProxy建立代理

  1. 判斷是否爲JVM內部引用,若是是,構建jvm引用URL, 經過protocol.refer得到invoker執行器
if (isJvmRefer) {
    // 組裝localhost本地URL,經過InjvmProtocol進行服務引用
    URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
    invoker = refprotocol.refer(interfaceClass, url);
    if (logger.isInfoEnabled()) {
        logger.info("Using injvm service " + interfaceClass.getName());
    }
}
  1. 加載註冊中心配置,根據RegistryConfig的配置,組裝registryURL,造成的URL格式以下
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=1528&qos.port=22222&registry=zookeeper&timestamp=1530743640901
  1. 若是有監控配置,在registryUrl加上MONITOR_KEY
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
    map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
  1. 將組裝好的配置map以REFER_KEY爲key拼接到registryUrl
u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))

組裝完的registryUrl以下:代理

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=4772&qos.port=33333&refer=application%3Ddemo-consumer%26check%3Dfalse%26dubbo%3D2.0.0%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D4772%26qos.port%3D33333%26register.ip%3D192.168.199.180%26side%3Dconsumer%26timestamp%3D1531358326151&registry=zookeeper&timestamp=1531358326266
  1. 遍歷registryUrl,使用protocol.refer方法,由RegistryProtocol構建基於目錄服務的集羣策略Invoker。對於存在多個registryUrl,設置集羣策略AvailableCluster,將全部經過registryUrl返回的Invoker執行器假裝成一個Invoker
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
// 遍歷registryUrl,建立對遠程服務的引用Invoker執行器
for (URL url : urls) {
    invokers.add(refprotocol.refer(interfaceClass, url));
    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        registryURL = url; // use last registry url
    }
}
// 將多個Invoker經過集羣策略假裝成一個Invoker
if (registryURL != null) { // registry url is available
    // use AvailableCluster only when register's cluster is available
    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
    invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
    invoker = cluster.join(new StaticDirectory(invokers));
}
  1. 經過ProxyFactory.getProxy(Invoker)建立遠程服務代理供消費方透明調用
return (T) proxyFactory.getProxy(invoker);

RegistryProtocol.refer 註冊與訂閱

上面第5步,將registryUrl經過protocol.refer獲取服務引用,根據SPI機制,會先通過ProtocolListenerWrapper, ProtocolFilterWrapper,但這兩個Wrapper都過濾了註冊中心Url,直接調用Registry.refer方法。code

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 設置url的protocol爲registry key的值
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // 根據url的protocol從註冊中心工廠返回對應的Registry
    // 如zookeeper即返回ZookeeperRegistry
    Registry registry = registryFactory.getRegistry(url);
    // 若是服務引用interface就是RegistryService,則直接返回Invoker
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // 若是存在多個group分組,使用MergeableCluster
    // 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);
}

註冊和訂閱都在doRefer中完成router

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);
    // all attributes of REFER_KEY
    // RegistryDirectory獲取url的parameters爲refer_key對應的url中的parameters
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // 建立URL,根據不一樣的category_key分別做爲consumerUrl(註冊)和subscribeUrl(訂閱)
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
    	// 向註冊中心註冊,url中增長category=consumers&check=false
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    // 向註冊中心訂閱,訂閱目錄爲category=providers,configurators,routers,回調RegistryDirectory的notify方法
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));

    // 經過集羣策略將目錄服務統一暴露成一個Invoker執行器,默認集羣策略爲failover
    Invoker invoker = cluster.join(directory);
    // 本地註冊表註冊消費者
    ProviderConsumerRegTable.registerConsuemr(invoker, url, subscribeUrl, directory);
    return invoker;
}

執行步驟以下:

  1. 設置url的protocol爲registry_key的值,根據url從RegistryFactory註冊工廠返回對應的Registry,好比zookeeper protocol對應ZookeeperRegistry
  2. 若是服務存在多個group分組,設置集羣策略爲Mergeable
  3. 建立目錄服務RegistryDirectory,並設置註冊器和protocol服務
  4. 構建consumerUrl(category=consumers),經過註冊器向註冊中心註冊
  5. 構建subscribeUrl(category=providers,configurators,routers),經過註冊器訂閱,並回調RegistryDirectory的notify方法
  6. 經過集羣策略將目錄服務統一暴露成一個Invoker執行器,默認集羣策略爲failover

這裏介紹下提供者和消費者服務在zookeeper上對應節點path:

  1. 提供者向zookeeper註冊服務,在節點/dubbo/com.alibaba.dubbo.demo.DemoService/providers/寫下本身的URL
  2. 消費者向zookeeper註冊服務,在節點/dubbo/com.alibaba.dubbo.demo.DemoService/consumers/下寫下本身的URL
  3. 消費者向zookeeper訂閱服務,訂閱節點/dubbo/com.alibaba.dubbo.demo.DemoService/providers/下全部服務提供者URL地址

官方提供的zookeeper節點圖:

zookeeper經過watcher機制實現對節點的監聽,節點數據變化經過節點上的watcher回調客戶端。dubbo的消費者訂閱了提供者URL的目錄節點,若是提供者節點數據變化,會主動回調NotifyListener(RegistryDirectory實現了NotifyListener)的notify方法,將節點下全部提供者的url返回,從而從新生成對提供者服務的引用的可執行invokers,供目錄服務持有

RegistryDirectory.notify(urls)方法過濾category=providers的提供者url(invokerUrl),而後調用refreshInvoker(invokerUrls)方法,進行基礎校驗後,調用toInvokers(invokerUrls)方法,經過protocol.refer方法轉換成Invoker。

RegistryDirectory

notify(urls) -> refreshInvoker(invokerUrls) -> toInvokers(invokerUrls) ->

invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);

這裏的protocol根據providerUrl的protocol基於SPI機制獲取,通常就是DubboProtocol。於是最終造成對提供者服務的引用是經過DubboProtocol.refer方法。

DubboProtocol.refer 構建服務引用

  1. protocol.refer會先通過ProtocolListenerWrapper和ProtocolFilterWrapper構建監聽器鏈和過濾器鏈
  2. 根據url獲取ExchangeClient對象,構建消費者和提供者的底層通訊連接
  3. 建立DubboInvoker,它包含對遠程提供者的長連接,從而能夠真正執行遠程調用,並返回給目錄服務RegistryDirectory持有
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    optimizeSerialization(url);
    // create rpc invoker.
    // getClients方法返回ExchangeClient對象
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);
    return invoker;
}

服務引用系列圖及活動圖

參考:https://blog.csdn.net/quhongwei_zhanqiu/article/details/41651487

相關文章
相關標籤/搜索