dubbo系列一、dubbo啟動流程


dubbo啟動流程分析記錄

一、dubbo provider啟動流程

1.自動裝配

dubbo springboot啟動方式,自動裝配bean DubboAutoConfiguration,創建bean ServiceAnnotationBeanPostProcessor,是個BeanDefinitionRegistryPostProcessor。因此在com.alibaba.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法內,掃描@com.alibaba.dubbo.config.annotation.Service注解,注冊為IOC bean,同時注冊IOC內一個 ServiceBean,其ref就是IOC bean。

比如下面定義一個dubbo provider

@com.alibaba.dubbo.config.annotation.Service
public class ProductServiceImpl implements ProductService {
	//內容略
}

在IOC 內注冊兩個bean,一個是beanName=productServiceImpl ,bean對象就是ProductServiceImpl;另外一個是ServiceBean,其ref就是ProductServiceImpl。

PS:值得參考的是ServiceAnnotationBeanPostProcessor對於@com.alibaba.dubbo.config.annotation.Service注解掃描注解為bean的用法挺好,以后我們自定義注解掃描,也可以直接參考拿來使用。

2.ServiceBean處理

ServiceBean是個InitializingBean、Aware、ApplicationListener,因此在IOC bean的初始化過程也會執行InitializingBean、Aware、ApplicationListener。

ServiceBean采用了模板,ServiceBean持有ProviderConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig、ProtocolConfig

在afterPropertiesSet()內就是設置ProviderConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig、ProtocolConfig保存到ServiceBean。 提問:這些XXXConfig是怎么來的呢?后面解釋 

接着判斷非延時暴露,則直接暴露服務,否則在監聽器執行時候進行暴露。

3.服務暴露export()

3.1.檢測dubbo.xxx.配置屬性,配置到對應的XXXConfig對象

檢查屬性or環境變量的dubbo.xxx.配置開頭的屬性,配置到XXXConfig對象,本質就是保存到ServiceBean對象。

檢測dubbo.provider.開頭屬性,配置到ProviderConfig

檢測dubbo.application.開頭屬性,配置到ApplicationConfig

檢測dubbo.registry.開頭屬性,配置到RegistryConfig

檢測dubbo.service.開頭屬性,配置到ServiceConfig,即ServiceBean

3.2.把服務暴露到每個注冊中心

遍歷注冊中心,把服務注冊到每個注冊中心。

本地暴露,感覺雞肋,本來是個IOC bean了,在當前服務內直接使用就行了,本地暴露忽略。

遠程暴露分兩步

step1: 使用ProxyFactory對目標ref生成代理對象Invoker。

step2: 把step1生成的Invoker對象暴露到zk注冊中心。

3.3.生成Invoker對象

使用ProxyFactory對目標對象ref生成代理對象Invoker。默認使用的是JavassistProxyFactory.getInvoker()

3.4.把Invoker對象暴露注冊到注冊中心

此時協議是registry,使用的是RegistryProtocol進行暴露服務

3.4.1.暴露Invoker對象 doLocalExport

step1: 使用wrapper類,對Invoker增強,創建filter chain

step2: 使用DubboProtocol進行暴露服務,即封裝invoker為DubboExporter,並緩存到DubboProtocol.exporterMap,這個map就是用於接收consumer請求時,根據port+interfaceName+version+group作為key,從此緩存map中獲取到DubboExporter。

step3: 創建tcp server,監聽20880端口(默認)

核心代碼如下

//com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker<T>)
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
    String key = getCacheKey(originInvoker);
    ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    if (exporter == null) {
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
            if (exporter == null) {
                final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);//DubboProtocol.export
                bounds.put(key, exporter);
            }
        }
    }
    return exporter;
}
3.4.2.服務注冊到zk

step1: 連接zk,與zk長連接

step2: 在zk創建provider服務臨時節點

step3: 創建/dubbo/$interfaceName/configurators節點,並訂閱此節點,如果此節點有變化,則dubbo provider服務重新暴露(OverrideListener)。這個對應dubbo-admin上的動態配置。

核心代碼如下在ZookeeperRegistry.doSubscribe(URL, NotifyListener)

4.總結

dubbo provider啟動流程總結,根據官方啟動流程圖如下

image-20210707003414348

服務提供者暴露服務的主過程:
首先 ServiceConfig 類拿到對外提供服務的實際類 ref(如:HelloWorldImpl),然后通過 ProxyFactory 類的 getInvoker 方法使用 ref 生成一個 AbstractProxyInvoker 實例,到這一步就完成具體服務到 Invoker 的轉化。接下來就是 Invoker 轉換到 Exporter 的過程。

Dubbo 處理服務暴露的關鍵就在 Invoker 轉換到 Exporter 的過程,上圖中的紅色部分

Dubbo 協議的 Invoker 轉為 Exporter 發生在 DubboProtocol 類的 export 方法,它主要是打開 socket 偵聽服務,並接收客戶端發來的各種請求,通訊細節由 Dubbo 自己實現。

zk協議是把Exporter 注冊到注冊中心,並監聽注冊中心對應節點的變化。

dubbo provider啟動流程

dubbo provider啟動

provider啟動記錄xmind地址https://gitee.com/yulewo123/mdpicture/blob/master/document/dubbo/dubbo provider啟動記錄.xmind

二、dubbo provider generic啟動流程

1.dubbo provider服務泛化例子

dubbo 服務泛化不能采用注解方式,只能使用xml或java api方式。以java api方式為例

//定義一個service
public class MyGenericService implements GenericService {//必須實現GenericService

    public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
        if ("sayHello".equals(methodName)) {
            return "Welcome " + args[0];
        }
    }
}

//

@org.springframework.stereotype.Service
public class DubboGenericService implements SmartInitializingSingleton {

	@Override
	public void afterSingletonsInstantiated() {
		ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
	    ApplicationConfig application = new ApplicationConfig("test-provider");
	    service.setApplication(application);
	    RegistryConfig registryConfig = new RegistryConfig();
	    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
	    service.setRegistry(registryConfig);
	    service.setInterface("com.suibian.WhateverService");//用於指定要發布的接口名稱,這個名稱可以不對應任何的java接口類,甚至可以隨便寫。
	 
	    GenericService genericService = (method, parameterTypes, args) -> {
	        if ("method1".equals(method)) {
	            return "method1 result:" + args[0];
	        }
	        if ("method2".equals(method)) {
	            return 12345;
	        }
	        if (parameterTypes.length == 2
	                && parameterTypes[0].equals("java.lang.String")
	                && parameterTypes[1].equals("java.lang.Integer")
	                && "method3".equals(method)) {
	            return "method3,param1:" + args[0] + ",param2:" + args[1];
	        }
	        return null;
	    };
	    service.setRef(genericService);
	    service.export();
	}
}

ServiceConfig.setInterface()用於指定要發布的接口名稱,這個名稱可以不對應任何的java接口類,甚至可以隨便寫。在上面的例子中com.suibian.WhateverService這個名字就是隨便寫的,實際上代碼中並沒有這個接口類。

因為這樣發布的Dubbo服務沒有具體的接口類,所以invoke()方法的第一個參數String var1(原本是方法名)和第二個參數String[] var2(原本是方法參數類型列表)就脫離了具體接口類的束縛,可以接收任意值,就像例子中所寫的一樣,按照不同的參數執行不同的邏輯,就像一個網關一樣(一個接口行天下)。

因此當服務提供者使用GenericService接口時,可以省略Interface的代碼,省略方法和參數的聲明,通過指定接口名稱的方式向zookeeper發布Dubbo服務。這時GenericService就像是一個網關。

2.dubbo provider服務泛化啟動流程

泛化服務啟動和普通服務啟動流程完全一致,ref通過ProxyFactory生成Invoker,Invoker通過DubboProtocol進行服務暴露,通過RegistryProtocol注冊到zk。 dubbo泛化服務注冊到zk例子 dubbo://192.168.5.1:20880/com.suibian.WhateverService?anyhost=true&application=test-provider&dubbo=2.0.2&generic=true&interface=com.suibian.WhateverService&methods=*&pid=12744&side=provider&timestamp=1625669557658

和普通服務細節不同之處是:

1.ServiceConfig.interfaceClass是GenericService,ServiceConfig.interfaceName是自定義的服務名稱

2.會在filter chain增加個GenericImplFilter,用於解析泛化請求

三、dubbo consumer啟動流程

@Reference注解由ReferenceBean處理,實現了Aware、InitializingBean、FactoryBean。因此

首先,在afterPropertiesSet()操作內保存ConsumerConfig、ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig到ReferenceBean。

然后,由於是FactoryBean,因此在getObject()獲取真實的對象。

consumer生成核心在createProxy,作用是生成代理。本質是由DubboProtocol生成DubboInvoker,DubboInvoker封裝了引用的接口和nettyClient。然后把生成的Invoker緩存到注冊表RegistryDirectory.methodInvokerMap。這樣在consumer請求provider時候,就可以根據methodName從緩存中獲取Invoker。生成Invoker后,訂閱providers、configurators、routers節點,這些節點有變動,則會通過zk通知到consumer端進行Invoker重新生成。

consumer啟動的核心在RegistryDirectory。

啟動流程圖如下

dubbo consumer啟動

consumer 啟動記錄xmind地址https://gitee.com/yulewo123/mdpicture/blob/master/document/dubbo/dubbo consumer啟動記錄.xmind

四、dubbo consumer generic啟動流程

dubbo泛化調用通常用於網關,例子如下

ReferenceConfigCache referenceCache = ReferenceConfigCache.getCache();
		
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();//緩存,否則每次請求都會創建一個ReferenceConfig,並在zk注冊節點,最終可能導致zk節點過多影響性能
ApplicationConfig application = new ApplicationConfig();
application.setName("pangu-client-consumer-generic");
// 連接注冊中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");

// 服務消費者缺省值配置
ConsumerConfig consumer = new ConsumerConfig();
consumer.setTimeout(10000);
consumer.setRetries(0);

reference.setApplication(application);
reference.setRegistry(registry);
reference.setConsumer(consumer);
reference.setInterface(org.pangu.api.ProductService.class); // 弱類型接口名
//        reference.setVersion("");
//        reference.setGroup("");
reference.setGeneric(true); // 聲明為泛化接口
GenericService svc = referenceCache.get(reference);

Object target = svc.$invoke("findProduct", new String[]{ProductDTO.class.getName()}, new Object[]{dto});//實際網關中,方法名、參數類型、參數是作為參數傳入

consumer泛化,無法以注解方式啟動,通常都是使用java api方式啟動,通常都是在請求時候進行創建並緩存。

consumer是泛化,啟動流程和普通consumer基本相同,不同之處有兩點:

  1. 除了在zk注冊節點path是com.alibaba.dubbo.rpc.service.GenericService且有generic,其它兩者基本相同。也會訂閱對應接口的providers、configurators、routers節點。
  2. 會在filter chain增加GenericImplFilter,用於組裝泛化請求

備注:

普通consumer在zk注冊節點例子:consumer://192.168.5.1/org.pangu.api.ProductService?application=pangu-client-consumer&category=consumers&check=false&dubbo=2.0.2&interface=org.pangu.api.ProductService&methods=findProduct,selectProduct&pid=19276&qos.enable=true&retries=0&revision=1.0-SNAPSHOT&side=consumer&timeout=900000000&timestamp=1625758276910

泛化consumer在zk注冊節點例子:consumer://192.168.5.1/com.alibaba.dubbo.rpc.service.GenericService?application=pangu-client-consumer-generic&category=consumers&check=false&default.retries=0&default.timeout=60000&dubbo=2.0.2&generic=true&interface=org.pangu.api.ProductService&pid=19276&side=consumer&timestamp=1625758288017

五、Invoker是什么

通過分析dubbo啟動,發現provider和consumer都是轉成Invoker。

provider端首先通過javaassit對目標接口進行動態代理,生成代理對象Invoker,然后對此Invoker進行Decorator增強(封裝目標服務和nettyserver),接着再繼續Decorator增強(生成filter chain),得到最終增強后的Invoker對象,並緩存到DubboProtocol.exporterMap。

consumer端是直接創建DubboInvoker,該Invoker封裝了目標服務類型clazz和nettyClient,接着也對此Invoker進行Decorator增強(生成filter chain),得到最終增強后的Invoker對象,並緩存到RegistryDirectory.methodInvokerMap。

使用Invoker,統一不同的接口類型,便於管理以及重要的是可以對一個類型設置攔截,用於功能增強。這也是梁飛大神提到的在重要的過程上設置攔截接口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM