Dubbo服務發佈與註冊

Dubbo服務導出過程開始於Spring容器的刷新事件,Dubbo在接收到事件後,會立即執行服務導出邏輯。
整個導出邏輯大體分爲三部分:

  • 前置工作:主要是配置檢查和組裝URL
  • 導出服務:包含導出本地服務和遠程服務
  • 向註冊中心註冊服務,用於服務的發現

具體實現:
main()方法啓動後:根據20880端口(默認端口號),發佈一個遠程調用的地址:dubbo//:192.168.1.1:20880/interface?...(發佈到註冊中心)
上述的這個過程主要做了一下的事情:

  • 解析配置文件–>dubbo是基於spring標籤進行擴展的
  • 暴露服務–>本地服務(以injvm開頭的URL)和遠程服務
  • 啓動netty–>提供netty服務,暴露端口
  • 連接zk
  • zk註冊地址
  • 監聽zk

1、前置工作

1.1 解析配置並檢查

spring裏面提供了很多擴展
dubbo-config-spring 模塊裏面提供了配置文件的擴展
spring 提供了兩個可擴展的:NamespaceHandlerSupport 和 BeanDefinitionParser
根據SPI擴展機制進入DubboNamespaceHandler 解析配置文件,進入到serviceBeanServiceConfig中,開始進行導出服務的前置工作;通過onApplicationEvent 監聽事件,檢查服務是否發佈,調用export()導出服務,以下是對export()的分析:

  • 檢查<dubbo service: interface>的屬性是否合法
  • 檢查providerConfig 和 ApplicationConfig等核心配置類對象是否爲空,爲空則出其他配置類獲取對應的實例
  • 檢查並處理泛化服務和普通服務
  • 檢查本地存根配置
  • ApplicationConfig 和 RegisterConfig 等配置類進行檢查,爲空則嘗試創建,無法創建則拋出異常

多協議多註冊中心的導出服務

private void doExportUrls(){
		//加載註冊中連接
		List<URL> registryURLs = loadRegistries(true);
		// 遍歷 protocols,並在每個協議下導出服務
	    for (ProtocolConfig protocolConfig : protocols) {
	        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
	    }
 }

1.3 組裝URL

走到這一步url大概是這個樣子: registry://192.168.22.130:2181/org.apache.dubbo.registry.RegistryService/……
這一步是根據配置信息組裝url, 而且url驅動流程的執行,會再各個模塊之間一直往下傳,後續會不斷修改URL頭或協議地址,並根據url地址中的信息做相應的處理;
在doExportUrlsFor1Protocol方法中做了url的組裝處理,通過反射的方式獲取到版本、時間戳、方法名以及各種配置對象的字段信息,然後放入map, 貼一下僞代碼幫助理解:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // 獲取 ArgumentConfig 列表
    for (遍歷 ArgumentConfig 列表) {
        if (type 不爲 null,也不爲空串) {    // 分支1
            1. 通過反射獲取 interfaceClass 的方法列表
            for (遍歷方法列表) {
                1. 比對方法名,查找目標方法
                2. 通過反射獲取目標方法的參數類型數組 argtypes
                if (index != -1) {    // 分支2
                    1. 從 argtypes 數組中獲取下標 index 處的元素 argType
                    2. 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
                    3. 添加 ArgumentConfig 字段信息到 map 中,或拋出異常
                } else {    // 分支3
                    1. 遍歷參數類型數組 argtypes,查找 argument.type 類型的參數
                    2. 添加 ArgumentConfig 字段信息到 map 中
                }
            }
        } else if (index != -1) {    // 分支4
            1. 添加 ArgumentConfig 字段信息到 map 中
        }
    }
}

2. 導出Dubbo服務

準備工作做完了,接下來進行服務導出。服務導出分爲導出到本地JVM和導出到遠程。
代碼根據 url 中的 scope 參數決定服務導出方式,分別如下:
scope = none,不導出服務
scope != remote,導出到本地
scope != local,導出到遠程
服務發佈的本質就是把export的每個服務轉爲一個對應的Invoker可執行體,然後把轉換後的Invoker都放到一個exporterMap(key,invoker)集合中:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    
    // 省略無關代碼
    
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 加載 ConfiguratorFactory,並生成 Configurator 實例,然後通過實例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,則什麼都不做
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
        // scope != remote,導出到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }

        // scope != local,導出到遠程
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (registryURLs != null && !registryURLs.isEmpty()) {
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // 加載監視器鏈接
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        // 將監視器鏈接作爲參數添加到 url 中
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }

                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // 爲服務提供類(ref)生成 Invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用於持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    // 導出服務,並生成 Exporter
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                
            // 不存在註冊中心,僅導出服務
            } else {
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    this.urls.add(url);
}

不管是導出到本地,還是遠程。進行服務導出之前,均需要先創建 Invoker,這是一個很重要的步驟。因此下面先來分析 Invoker 的創建過程。

2.1 invoker創建過程:

在 Dubbo 中,Invoker 是一個非常重要的模型。在服務提供端,以及服務引用端均會出現 Invoker。Dubbo 官方文檔中對 Invoker 進行了說明,這裏引用一下。

Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集羣實現。

Invoker由ProxyFactory創建而來,Dubbo默認的ProxyFactory實現類是javassistProFactory。來看一下javassistProFactory類創建Invoker的過程:

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  // 爲目標類創建 Wrapper
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    // 創建匿名 Invoker 類對象,並實現 doInvoke 方法。
    // 
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
      // 調用 Wrapper 的 invokeMethod 方法,invokeMethod 最終會調用目標方法
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

重寫後的doInvoke邏輯比較簡單,僅僅是將調用請求轉發給了Wrapper類的invokeMethod,Wrapper用於“包裹”目標類,Wrapper是一個抽象類,僅可通過getWrapper方法傳入的Class對象進行解析,拿到類的信息,生成invokeMethod方法和其他方法代碼,代碼生成完畢之後,通過javassist生成Class對象,最後再通過反射創建Wrapper實例。

 public static Wrapper getWrapper(Class<?> c) { 
    while (ClassGenerator.isDynamicClass(c))
        c = c.getSuperclass();

    if (c == Object.class)
        return OBJECT_WRAPPER;

    // 從緩存中獲取 Wrapper 實例
    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        // 緩存未命中,創建 Wrapper
        ret = makeWrapper(c);
        // 寫入緩存
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

未完待續。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章