1. 程式人生 > >DUBBO服務啟動過程

DUBBO服務啟動過程

多少 代碼 exception pat false wap http except 發現

Dubbo的啟動主要是發布服務的過程,起到核心作用的就是ServiceConfig(ServiceConfig就是我們在Dubbo的配置文件中配置的dubbo:service這些配置項對應的實體類)。服務的啟動初始位置也基本是在這裏,下面我們來看看具體的實現內容。

講基本內容前首先理清楚幾個名詞概念:

Invoker:Invoker的概念我們在動態代理的時候就接觸過,中文的意思大概是執行者,這裏其實可以理解為具體方法的執行者。其核心內容大致如下:

Class<T> getInterface();
Result invoke(Invocation invocation) throws RpcException;

URL getUrl();

通過以上的三個方法們就可以執行到具體的方法並且獲得方法的執行結果。通過getUrl獲得需要執行的方法具體實現細節,主要是獲得具體的ref;其次就是組裝方法的參數信息等等,這些信息在invocation裏面都有封裝;最後通過執行invoke方法觸發具體的方法並返回結果。從這裏可以看出Invoker是具體方法執行的最後一個守關者,獲得了Invoker,就獲得了具體接口的代碼,然後執行代理就可以。

Invoker僅僅是作為一個代理的門面,其不僅可以代表本地執行Duubo調用的代理,還可以充當RPC時候的代理,更是可以將自己包裝成一個多個Invoker聚合而成的代理(主要是處理集群的一些策略,包括負載均衡和路由等)。

Exporter:服務暴露的過程中會將Invoker轉換成Exporter(暴露者),及Exporter其實包含了Invoker,主要是用於不同層面的服務發布。
其實Dubbo 還有一些比較重要的對象,像Protocol,Exchanger等等。我認為在這裏直接說明不太合適,所以等到我們用到之後再開始說明。

  1. 核心的屬性信息

一些基本的屬性:group,version,interfaceName,interfaceClass,timeout等等。我們凡是可以在dubbo:service上配置的屬性都在ServiceConfig中可以找得到對應的屬性;

//dubbo對應的服務發布協議,這裏可以清楚地看到Dubbo在這裏使用的自己的spi機制,來保證靈活性。(至於SPI機制的具體實現,之後有機會的話會講到,簡單理解就是通過getExtensionLoader獲得對應類的擴展類實現類)

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

private final List<URL> urls = new ArrayList<URL>();
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

2.服務暴露過程

對於服務暴露來說,在ServiceConfig裏面的初始方法就是export()方法了,下面我們從export方法開始來看看:

public synchronized void export() {
        if (provider != null) {
            //默認取provider的配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        //如果export設置為false的話就直接返回
        if (export != null && ! export.booleanValue()) {
            return;
        }
        //如果設置延遲時間的話就延遲指定時間然後進行暴露
        if (delay != null && delay > 0) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(delay);
                    } catch (Throwable e) {
                    }
                    doExport();
                }
            });
            thread.setDaemon(true);//將暴露接口的線程設置為守護線程
            thread.setName("DelayExportServiceThread");
            thread.start();
        } else {
            doExport(); //一切暴露的核心還都是要看doExport方法。
        }
    }

    protected synchronized void doExport() {
        // 防止服務多次暴露

        // 設置默認的基本屬性

        // 針對泛化接口做單獨處理
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {//通過反射初始化接口(interfaceName是實現類的全稱)
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //檢查定義的方法是否為接口中的方法
            checkInterfaceAndMethods(interfaceClass, methods);
            //檢查引用不為空,並且引用必需實現接口
            checkRef();
            //如果到這一步的話說明類實現是自己定義的,所以設置generic為false
            generic = Boolean.FALSE.toString();
        }

        // 處理Local和Stub代理處理

        // 檢查Application,Registry,Protocol的配置情況

        //將配置的屬性綁定到當前對象
        appendProperties(this);

        //針對Local,Stub和Mock進行校驗

        //上面的操作主要是做一些檢驗和初始化的操作,沒有涉及到具體的暴露服務邏輯

        doExportUrls();
    }

    private void doExportUrls() {
        //取到註冊中心的URL
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            //根據配置的通信協議將服務暴露到註冊中心
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {

        //  默認采用Dubbo協議

        //之後的部分邏輯就是想盡一切辦法取到host

        //取到端口號(之後的部分關於端口的邏輯就是想盡一切辦法取端口號)

        // 這個map十分重要,它的意義在於所有存儲所有最終使用到的屬性,我們知道一個屬性例如timeout,可能在Application,provider,service中都有配置,具體以哪個為準,都是這個map處理的事情。
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) { //如果此時anyhost為true的話
            map.put(Constants.ANYHOST_KEY, "true");
        }
        // 存儲簡單的服務信息

        //將application,module,provider,protocol和service的信息設置到map裏面
        //將應用配置的樹勇按照層級存入map中。註意這裏的層級關系,是一層層覆蓋的 即關系為:ServiceConfig->PrtocolConfig->ProviderConfig->ModuleConfig->ApplicaionConfig

        //單獨處理好method層級的參數關系

        //判斷有沒有配置通配協議
        if (ProtocolUtils.isGeneric(generic)) {
            map.put("generic", generic);
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
            //通過包裝類將interfaceClass進行包裝然後取得方法名字,對於wapper包裝器就是將不同的類統一化
            //參考http://blog.csdn.net/quhongwei_zhanqiu/article/details/41597261理解Wapper
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                //將所有的方法拼接成以逗號為分隔符的字符串
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        //處理token屬性

        //如果配置的injvm的話就代表本地調用(本地調用還用Dubbo的話實在有點蛋疼)

        //所有的核心屬性最後都成了URL的拼接屬性,如果我們還記得map裏面拼裝了多少屬性的話就知道這個URL內容有多豐富
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

        // 下面是核心暴露過程,將不會省略源碼
        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置為none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠程服務)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local則暴露為遠程服務.(配置為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) {
                        //dynamic表示是否需要人工管理服務的上線下線(動態管理模式)
                        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對象(ref為接口實現類的引用)
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        //將invoker轉化為exporter對象
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter); //將exporter添加到需要暴露的列表中取
                    }
                    ================================================================
                } else {
                    //如果找不到註冊中心的話就自己充當自己的註冊中心吧
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        //多個協議就有多個url與其對應,所以要一一存儲。
        this.urls.add(url);
    }

通過ServicConfig中的內容分解,我們看出來裏面主要做的內容如下:
檢驗所需參數的合法性
將多層的參數(可能重復配置)最終整理出最終的結果(map),然後根據參數拼接成暴露服務需用到的url。
處理generic,Stub,injvm等其他需要支持的內容,補充dubbo的功能多樣性,但是都不涉及核心流程。
根據對應的協議將服務進行暴露(將提供的服務推送到註冊中心供服務調用者發現),默認使用Dubbo協議。

DUBBO服務啟動過程