DUBBO服務啟動過程

IT小俠發表於2018-08-03

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

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

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

  1. Class<T> getInterface();

  2. Result invoke(Invocation invocation) throws RpcException;

  3. 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協議。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31540763/viewspace-2169062/,如需轉載,請註明出處,否則將追究法律責任。

相關文章