dubbo服務發佈一之服務暴露

總體流程以調試 om.alibaba.dubbo.demo.provider.DemoProvider來演示dubbo服務的發佈流程。java

一、啓動Spring容器

參照dubbo容器的啓動, https://segmentfault.com/a/11... 文章描述spring

1.一、解析xml文件

dubbo的xml自定義標籤,都是基於spring提供NamespaceHandlerSupport機制來完成的。
ServiceBean加載和初始化segmentfault

<!-- 和本地bean同樣實現服務 -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

}

xml文件->ServiceBean.class 這就是spring的NamespaceHandlerSupport作的事情。服務器

spring容器在初始化以後,會廣播ContextRefreshedEvent事件,ServiceBean實現了ApplicationListener接口,在執行onApplicationEvent時,啓動了export方法,開啓 服務發佈流程。網絡

public void onApplicationEvent(ApplicationEvent event) {
        if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
            if (isDelay() && !isExported() && !isUnexported()) {
                if (logger.isInfoEnabled()) {
                    logger.info("The service ready on spring started. service: " + getInterface());
                }
                export();
            }
        }
    }

--export()
-----doExport()
-------doExportUrls()
---------loadRegistries()加載註冊中心,能夠有多個
---------doExportUrlsFor1Protocol()
-----------exportLocal(url) 本地暴露服務
-----------protocol.export(invoker) 遠程暴露服務app

二、本地暴露服務

假如服務沒有配置了scope屬性,或者配置了可是值不是」remote「,就會執行本地暴露。自同一個jvm內部,調用本jvm中存在的服務,就能夠直接調用,而不須要走網絡,減小響應時間。異步

private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(NetUtils.LOCALHOST)
                    .setPort(0);
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local)
            );
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }

第一步:設置URL中的參數,jvm

  • protocal:injvm
  • host:127.0.0.1
  • port:0

圖片描述
url的協議已經從dubbo變成了injvm
第二步:將ref【interfaceClass的實現類】包裝成一個Wrapper,並返回一個Invoker
JavassistProxyFactory#getInvokeride

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper類不能正確處理帶$的類名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

第三步:根據injvm協議找到【這個是經過dubbo的Adaptive標籤來動態決定的】InJvmProtocol,並執行他的export方法,裝載Invoker。
第四步:將export放到該服務的exporters集合中。
本地暴露,不須要啓動相似netty的服務器,也不須要註冊zookeeper。函數

三、暴露遠程服務

假如服務沒有配置了scope屬性,或者配置了可是值不是」local「,就會執行遠程暴露。

if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }

下面,咱們來一步一步調試遠程暴露都作了什麼事情。

  1. 經過代理拿到Invoker,這一步就不細說了,和injvm的協議是同樣的。
  2. 核心看着段代碼:Exporter<?> exporter = protocol.export(invoker);

protocol是一個Protocol$Adaptie類,是dubbo動態生成的對象。
如何得到動態生成的類的源碼:將日誌級別調測DEBUG,在控制檯中拷貝出打印的源碼,而後new相同的package、相同的java文件,就能夠進去調試了。
clipboard.png

看一下經過前面一些列操做以後,組裝的Invoker對象
clipboard.png

當前協議值爲
clipboard.png

咱們再看一下export的值爲
clipboard.png

由於此時Invoker的協議爲registry,因此會執行

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

從拿到的Extension的值,咱們就能驗證,dubbo的AOP原理。請戳連接https://segmentfault.com/a/11... 這個裏面講了爲何咱們拿的是 RegistryProtocol,可是實際上確的拿到的是通過兩個wrapper包裝的對象的緣由了?!
clipboard.png

通過兩次ProtocolListenerWrapper、ProtocolFilterWrapper的export方法以後,來到R
RegistryProtocol的export方法,這個是一個很是核心的方法。
clipboard.png]

doLocalExport方法裏面,將從Invoker裏面的拿到了export的值,從新構造了一個InvokerDelegete對象,這個時候又會執行Protocol$Adpative#export方法,這個時候Invoker的Url屬性的協議已是dubbo了。因此會拿到DubboProtocal,並執行export
clipboard.png

DubboProtocol#export方法,發現有expoterMap屬性的key是 com.alibaba.dubbo.demo.DemoService:20880 服務名:協議端口號。
clipboard.png

3.一、啓動netty服務器

從上圖中,看到了openServer的方法了 <~.~> 。看一下入參URL以下,該值與上面一直圖中的export的屬性值如出一轍。
【忽略ip、pid、timestamp字段,由於不是同一次調試的。】
clipboard.png

屬性serverMap存儲了相應的server,ip:prot爲key。所以,假如咱們只配置一個協議端口,createServer只會執行一次。
clipboard.png

執行Exchangers.bind(url, requestHandler) 就是爲拿到 一個 ExchangeServer
clipboard.png
]

在Exchangers裏面, 執行getExchanger(url)拿到 HeaderExchanger執行bind方法
clipboard.png

在HeaderExchanger裏面,執行Transporters#bind也是爲了拿到一個
clipboard.png

在Transporters#bind裏面,執行getTransporter()拿到默認的Transporter 即NettyTransporter。
clipboard.png

在NettyTransporter啓動一個NettyServer
clipboard.png
netty服務器在父類的構造函數中被調用。
clipboard.png
至此一個netty服務器就啓動起來了。

大體完成的事情就是下圖中紅框框框起來的部分。
clipboard.png

四、幾個核心概念總結

4.一、ProxyFactory

實現了全部服務接口的的透明化代理。有兩個方法,
getInvoker 服務端使用,將實現類封裝成一個Invoker。
getProxy 客戶端使用,建立接口的代理對象。

4.一、Ivoker

封裝了一個服務的相關信息,是一個服務可執行體。

4.二、Invocation

是會話域,它持有調用過程當中的變量,好比方法名,參數等。

4.三、Protocol

Protocol是一個服務域,他是Invoker引用和暴露的主要入口,它負責Invoker的生命週期管理。

4.四、Exporter

具體執行Invoker的生命週期

4.五、Exchange

封裝請求響應模式,同步轉異步

4.六、Transport

transport 網絡傳輸層,抽象mina、netty的統一接口
clipboard.png..]

相關文章
相關標籤/搜索