溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

DUBBO服務(wù)啟動(dòng)過程

發(fā)布時(shí)間:2020-08-05 11:27:03 來源:ITPUB博客 閱讀:163 作者:明理蘿 欄目:軟件技術(shù)

Dubbo的啟動(dòng)主要是發(fā)布服務(wù)的過程,起到核心作用的就是ServiceConfig(ServiceConfig就是我們在Dubbo的配置文件中配置的dubbo:service這些配置項(xiàng)對應(yīng)的實(shí)體類)。服務(wù)的啟動(dòng)初始位置也基本是在這里,下面我們來看看具體的實(shí)現(xiàn)內(nèi)容。

講基本內(nèi)容前首先理清楚幾個(gè)名詞概念:

Invoker:Invoker的概念我們在動(dòng)態(tài)代理的時(shí)候就接觸過,中文的意思大概是執(zhí)行者,這里其實(shí)可以理解為具體方法的執(zhí)行者。其核心內(nèi)容大致如下:

  1. Class<T> getInterface();

  2. Result invoke(Invocation invocation) throws RpcException;

  3. URL getUrl();
     

通過以上的三個(gè)方法們就可以執(zhí)行到具體的方法并且獲得方法的執(zhí)行結(jié)果。通過getUrl獲得需要執(zhí)行的方法具體實(shí)現(xiàn)細(xì)節(jié),主要是獲得具體的ref;其次就是組裝方法的參數(shù)信息等等,這些信息在invocation里面都有封裝;最后通過執(zhí)行invoke方法觸發(fā)具體的方法并返回結(jié)果。從這里可以看出Invoker是具體方法執(zhí)行的最后一個(gè)守關(guān)者,獲得了Invoker,就獲得了具體接口的代碼,然后執(zhí)行代理就可以。

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

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

1. 核心的屬性信息

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

//dubbo對應(yīng)的服務(wù)發(fā)布協(xié)議,這里可以清楚地看到Dubbo在這里使用的自己的spi機(jī)制,來保證靈活性。(至于SPI機(jī)制的具體實(shí)現(xiàn),之后有機(jī)會的話會講到,簡單理解就是通過getExtensionLoader獲得對應(yīng)類的擴(kuò)展類實(shí)現(xiàn)類)

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.服務(wù)暴露過程

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

public synchronized void export() {
        if (provider != null) {
            //默認(rèn)取provider的配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        //如果export設(shè)置為false的話就直接返回
        if (export != null && ! export.booleanValue()) {
            return;
        }
        //如果設(shè)置延遲時(shí)間的話就延遲指定時(shí)間然后進(jìn)行暴露
        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);//將暴露接口的線程設(shè)置為守護(hù)線程
            thread.setName("DelayExportServiceThread");
            thread.start();
        } else {
            doExport(); //一切暴露的核心還都是要看doExport方法。
        }
    }
     
    protected synchronized void doExport() {
        // 防止服務(wù)多次暴露
         
        // 設(shè)置默認(rèn)的基本屬性
         
        // 針對泛化接口做單獨(dú)處理
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {//通過反射初始化接口(interfaceName是實(shí)現(xiàn)類的全稱)
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //檢查定義的方法是否為接口中的方法
            checkInterfaceAndMethods(interfaceClass, methods);
            //檢查引用不為空,并且引用必需實(shí)現(xiàn)接口
            checkRef();
            //如果到這一步的話說明類實(shí)現(xiàn)是自己定義的,所以設(shè)置generic為false
            generic = Boolean.FALSE.toString();
        }
         
        // 處理Local和Stub代理處理
         
        // 檢查Application,Registry,Protocol的配置情況
         
        //將配置的屬性綁定到當(dāng)前對象
        appendProperties(this);
         
        //針對Local,Stub和Mock進(jìn)行校驗(yàn)
         
        //上面的操作主要是做一些檢驗(yàn)和初始化的操作,沒有涉及到具體的暴露服務(wù)邏輯
         
        doExportUrls();
    }
     
    private void doExportUrls() {
        //取到注冊中心的URL
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            //根據(jù)配置的通信協(xié)議將服務(wù)暴露到注冊中心
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
     
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
         
        //  默認(rèn)采用Dubbo協(xié)議
         
        //之后的部分邏輯就是想盡一切辦法取到host
         
        //取到端口號(之后的部分關(guān)于端口的邏輯就是想盡一切辦法取端口號)
         
        // 這個(gè)map十分重要,它的意義在于所有存儲所有最終使用到的屬性,我們知道一個(gè)屬性例如timeout,可能在Application,provider,service中都有配置,具體以哪個(gè)為準(zhǔn),都是這個(gè)map處理的事情。
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) { //如果此時(shí)anyhost為true的話
            map.put(Constants.ANYHOST_KEY, "true");
        }
        // 存儲簡單的服務(wù)信息
         
        //將application,module,provider,protocol和service的信息設(shè)置到map里面
        //將應(yīng)用配置的樹勇按照層級存入map中。注意這里的層級關(guān)系,是一層層覆蓋的 即關(guān)系為:ServiceConfig->PrtocolConfig->ProviderConfig->ModuleConfig->ApplicaionConfig
         
        //單獨(dú)處理好method層級的參數(shù)關(guān)系
         
        //判斷有沒有配置通配協(xié)議
        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進(jìn)行包裝然后取得方法名字,對于wapper包裝器就是將不同的類統(tǒng)一化
            //參考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的話就代表本地調(diào)用(本地調(diào)用還用Dubbo的話實(shí)在有點(diǎn)蛋疼)
         
        //所有的核心屬性最后都成了URL的拼接屬性,如果我們還記得map里面拼裝了多少屬性的話就知道這個(gè)URL內(nèi)容有多豐富
        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,則表示只暴露遠(yuǎn)程服務(wù))
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local則暴露為遠(yuǎn)程服務(wù).(配置為local,則表示只暴露遠(yuǎn)程服務(wù))
            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表示是否需要人工管理服務(wù)的上線下線(動(dòng)態(tài)管理模式)
                        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為接口實(shí)現(xiàn)類的引用)
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        //將invoker轉(zhuǎn)化為exporter對象
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter); //將exporter添加到需要暴露的列表中取
                    }
                    ================================================================
                } else {
                    //如果找不到注冊中心的話就自己充當(dāng)自己的注冊中心吧
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
 
                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        //多個(gè)協(xié)議就有多個(gè)url與其對應(yīng),所以要一一存儲。
        this.urls.add(url);
    }
通過ServicConfig中的內(nèi)容分解,我們看出來里面主要做的內(nèi)容如下:
  • 檢驗(yàn)所需參數(shù)的合法性

  • 將多層的參數(shù)(可能重復(fù)配置)最終整理出最終的結(jié)果(map),然后根據(jù)參數(shù)拼接成暴露服務(wù)需用到的url。

  • 處理generic,Stub,injvm等其他需要支持的內(nèi)容,補(bǔ)充dubbo的功能多樣性,但是都不涉及核心流程。

  • 根據(jù)對應(yīng)的協(xié)議將服務(wù)進(jìn)行暴露(將提供的服務(wù)推送到注冊中心供服務(wù)調(diào)用者發(fā)現(xiàn)),默認(rèn)使用Dubbo協(xié)議。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI