dubbo源碼解析-服務發佈

image-20201025230730147

Invoker 表示遠程通信的對象

Directory 表示服務地址列表

服務發佈過程

  • 掃描xml配置或者註解
  • url的組裝 (dubbo是基於URL驅動的)
  • 註冊到註冊中心
  • 啓動、發佈服務

Dubbo源碼使用樣例(不使用Spring-Boot的Starter組件):

public class Application {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        System.in.read();
    }

    @Configuration
    @EnableDubbo(scanBasePackages = "com.anto.dubbo.dubboprovider")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static class ProviderConfiguration {
        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://172.30.2.7:2181");
            return registryConfig;
        }
    }
}

而在dubbo-spring-boot-starter組件中,則可以直接不帶@EnableDubbo直接在properties文件配置掃描路徑即可。

dubbo.registry.address=zookeeper://172.30.2.7:2181
dubbo.scan.base-packages=com.anto.dubbo.dubboprovider

是因爲自動裝配類中,DubboRelaxedBinding2AutoConfiguration會將上述配置綁定至指定的Bean中。

@Bean(name = BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME)//dubboScanBasePackagesPropertyResolver
    public PropertyResolver dubboScanBasePackagesPropertyResolver(ConfigurableEnvironment environment) {
        ConfigurableEnvironment propertyResolver = new AbstractEnvironment() {
            @Override
            protected void customizePropertySources(MutablePropertySources propertySources) {
                //查找properties文件中的 dubbo.scan. 配置
                Map<String, Object> dubboScanProperties = getSubProperties(environment.getPropertySources(), DUBBO_SCAN_PREFIX);
                propertySources.addLast(new MapPropertySource("dubboScanProperties", dubboScanProperties));
            }
        };
        ConfigurationPropertySources.attach(propertyResolver);
        return new DelegatingPropertyResolver(propertyResolver);
    }

查找待發布的服務--掃描xml或註解

dubbo服務發佈的形式

  • xml形式
  • 註解形式

@EnableDubbo包含了兩個註解@EnableDubboConfig@DubboComponentScan

疑問:dubbo啓動時 @EnableDubbo是否是必須的註解?

非必須的註解,當用Spring-Boot方式集成Starter組件時,掃描路徑是直接讀取application.properties文件的;
至於ServiceAnnotationBeanPostProcessor則在DubboAutoConfiguration聲明瞭該Bean。

以下都是基於註解的方式來進行初始化的。

  • ServiceAnnotationBeanPostProcessor

image-20201028150855600

@DubboComponentScan註解中會import類DubboComponentScanRegistrar,然後預先往IOC容器中註冊幾個BeanDefinition。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {//該註解Import了DubboComponentScanRegistrar類
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
		//註冊ServiceAnnotationBeanPostProcessor的BeanDefinition
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);

    }

註冊完ServiceAnnotationBeanPostProcessor的BeanDefinition後,就應該是將該Bean進行實例化。

//AbstractApplicationContext  調用refresh()方法時觸發
invokeBeanFactoryPostProcessors(beanFactory);
//PostProcessorRegistrationDelegate  觸發getBean的過程
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

初始化完成後,就應該是真正開始其作用了。

而它實現了BeanDefinitionRegistryPostProcessor,那麼就應該是調用其postProcessBeanDefinitionRegistry()方法。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        // 再次保證註冊了DubboBootstrapApplicationListener 其實在`@DubboComponentScan註解中,
    	//導入`DubboComponentScanRegistrar`類時已經註冊了
        registerBeans(registry, DubboBootstrapApplicationListener.class);

        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            //開始註冊帶有dubbo註解的Bean
            registerServiceBeans(resolvedPackagesToScan, registry);
        } 
    //...略

    }

跟Mybatis中類似,Dubbo也定義了專門用來掃描指定路徑的類DubboClassPathBeanDefinitionScanner

private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

    DubboClassPathBeanDefinitionScanner scanner =
            new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

    BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

    scanner.setBeanNameGenerator(beanNameGenerator);

    // serviceAnnotationTypes是一個list  包含DubboService.class 和Service.class
    //使得scanner只掃描帶這倆路徑的註解
    serviceAnnotationTypes.forEach(annotationType -> {
        scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
    });

    for (String packageToScan : packagesToScan) {

        // Registers @Service Bean first
        scanner.scan(packageToScan);

        // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
        Set<BeanDefinitionHolder> beanDefinitionHolders =
                findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

        if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                registerServiceBean(beanDefinitionHolder, registry, scanner);
            }
//...略

    }

}

服務的發佈入口

當dubbo的服務掃描完成後,需要發佈服務,發佈服務我們需要考慮以下的要點:

服務以什麼協議發佈

服務發佈的端口

  • 服務發佈的入口

DubboBootstrapApplicationListener監聽了ContextRefreshedEvent事件,當Spring完成Bean的裝載後,會觸發事件的接口,爲真正發佈服務入口

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        dubboBootstrap.start();
    }

那麼該類是在哪裏進行註冊的呢?

ServiceClassPostProcessor觸發方法postProcessBeanDefinitionRegistry()時會顯式的註冊DubboBootstrapApplicationListener的Bean。

// 此處是爲了保證有DubboBootstrapApplicationListener的Bean   
    	//其實在`@DubboComponentScan註解中,導入`DubboComponentScanRegistrar`類時已經註冊了
        registerBeans(registry, DubboBootstrapApplicationListener.class);

  • 服務發佈的流程

真正開始進行dubbo服務的發佈是通過DubboBootstrap類的start()來完成的。

public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            //...略
            //初始化必要的組件 配置中心、註冊中心、校驗必要的配置等
            initialize();
          
            // 1. 暴露dubbo服務
            exportServices();

            // Not only provider register
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 2. export MetadataService
                exportMetadataService();
                //3. Register the local ServiceInstance if required
                registerServiceInstance();
            }

            referServices();
         //...略
        return this;
    }

接下里調用 DubboBootstrap類的exportServices()方法

private void exportServices() {
    //逐個服務暴露
        configManager.getServices().forEach(sc -> {
            // TODO, compatible with ServiceConfig.export()
            ServiceConfig serviceConfig = (ServiceConfig) sc;
            serviceConfig.setBootstrap(this);

            if (exportAsync) {
                ExecutorService executor = executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    sc.export();
                    exportedServices.add(sc);
                });
                asyncExportingFutures.add(future);
            } else {
                sc.export();
                exportedServices.add(sc);
            }
        });
    }
  • 關鍵對象ServiceConfig

image-20201030095625521

服務發佈過程大致可以分爲三個過程:

1、生成具體服務的Invoker對象

2、發佈協議服務(默認以Dubbo發佈),Invoker轉換生成成Exporter

3、將服務地址信息註冊到註冊中心

//ServiceConfig.doExportUrls()
private void doExportUrls() {
    //得到服務倉庫ServiceRepository   將服務註冊進去全局唯一的服務倉庫對象中
    ServiceRepository repository = ApplicationModel.getServiceRepository();
    ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
    repository.registerProvider(
        getUniqueServiceName(),
        ref,
        serviceDescriptor,
        this,
        serviceMetadata
    );
    //註冊中心地址集合
    List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
    //默認只有dubbo協議 默認的ProtocolConfig對象 <dubbo:protocol name="dubbo" port="20880" />
    for (ProtocolConfig protocolConfig : protocols) {
        String pathKey = URL.buildKey(getContextPath(protocolConfig)
                                      .map(p -> p + "/" + path)
                                      .orElse(path), group, version);
        // In case user specified path, register service one more time to map it to path.
        repository.registerService(pathKey, interfaceClass);
        // TODO, uncomment this line once service key is unified
        serviceMetadata.setServiceKey(pathKey);
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

根據 RegistryConfig 的配置,組裝 registryURL,形成的 URL 格式如下:

registry://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=65324&registry=zookeeper&release=2.7.7&timestamp=1604025007827

這個 URL 表示它是一個 registry 協議(RegistryProtocol),地址是註冊中心的ip:port,服務接口是 RegistryService,registry 的類型爲 zookeeper。在有多個註冊中心時,會生成多個registryURL。

接下來開始根據具體的協議(默認的dubbo協議)暴露服務,同時將服務註冊到一(多)個註冊中心。

doExportUrlsFor1Protocol()

本地暴露服務

ServiceConfig中調用doExportUrlsFor1Protocol()方法進行服務暴露時,會有如下判斷:

 //當沒有顯式的指定scope的值爲remote時,會進行本地暴露
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
injvm://127.0.0.1/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=65324&release=2.7.7&side=provider&timestamp=1604025491044

通過指定scope的值,顯式指定以何種方式暴露服務。

<Dubbo:service interface="org.apache.Dubbo.samples.local.api.DemoService" ref="target" scope="remote"/>

#指定消費者提供端的暴露服務方式 不指定將以dubbo、injvm同時暴露
dubbo.provider.scope=remote

使用 Dubbo 本地調用不需做特殊配置,按正常 Dubbo 服務暴露服務即可。
任一服務在暴露遠程服務的同時,也會同時以 injvm 的協議暴露本地服務。injvm 是一個僞協議,不會像其他協議那樣對外開啓端口,只用於本地調用的目的。

Exporter<?> exporter = PROTOCOL.export(
                PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));

因爲發佈injvm協議時,其協議頭是injvm,所以PROTOCOL根據自適應擴展點得到的是InjvmProtocol。所以此處生成的的Exporter對象是InjvmExporter類型。


那麼通過injvm方式來暴露服務有什麼好好處呢?

與本地對象上方法調用不同的是,Dubbo 本地調用會經過 Filter 鏈,其中包括了 Consumer 端的 Filter 鏈以及 Provider 端的 Filter 鏈。通過這樣的機制,本地消費者和其他消費者都是統一對待,統一監控,服務統一進行治理。

本地調用何時是無用的?

第一,泛化調用的時候無法使用本地調用。
第二,消費者明確指定 URL 發起直連調用。

生成Invoker對象

調用器,是Dubbo領域比較重要的一個對象,在服務的發佈和調用過程中,服務本身會以Invoker對象存在。不管是發佈dubbo服務還是發佈本地的injvm服務,都需要生成一個Invoker對象。

//發佈dubbo服務
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));

Dubbo中都是通過生成一個Invoker對象,然後PROTOCOL.export(wrapperInvoker);來完成服務的發佈。

//發佈本地的injvm服務
Exporter<?> exporter = PROTOCOL.export(
                PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));

PROXY_FACTORY是一個自適應擴展點得到的一個對象。

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

看到這,那麼肯定在/dubbo/META-INF/internal路徑下會有一個名稱爲org.apache.dubbo.rpc.ProxyFactory文件的ProxyFactory接口的配置。

stub=org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory
  • 首先根據URL中的參數來判斷,proxy="jdk",若配置了則直接根據URL中的配置查找。此處邏輯是在生成的ProxyFactory$Adaptive類中
  • 未配置,則按照默認的@SPI("javassist")則爲JavassistProxyFactory類型。

根據之前的分析知道,只要該接口被@SPI修飾,且方法上有@Adaptive修飾時,會生成一個$Adaptive結尾的代理類。所以這裏會生成一個由Dubbo框架生成的一個類ProxyFactory$Adaptive

public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0, arg1);
    }

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        //獲取url中的proxy參數 無則是javassist
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        //再次調用JavassistProxyFactory.getInvoker()方法
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

所以ProxyFactory$Adaptive 的作用主要是根據url中的proxy參數,決定需要用ProxyFactory接口的哪個實現,當沒有配置時,則用@SPI("javassist")配置的值。

此時參數爲

image-20201125091303076

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url)
 proxy: com.anto.dubbo.dubboprovider.HelloServiceImpl
 type: com.anto.dubbo.HelloService
 url:  injvm://127.0.0.1/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=16136&release=2.7.7&side=provider&timestamp=1606266760859   

然後就是調用JavassistProxyFactorygetInvoker方法了。

//JavassistProxyFactory
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // 創建一個動態代理
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                //調用構建的動態代理類的invokeMethod()
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

通過Wrapper類來創建一個動態代理,(Wrapper類的258行)其核心方法如下:

 public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
        com.anto.dubbo.dubboprovider.HelloServiceImpl w;
        try {
            w = ((com.anto.dubbo.dubboprovider.HelloServiceImpl) $1);
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        try {
            //判斷參數是否String 
            if ("sayHello".equals($2) && $3.length == 1 && $3[0].getName().equals("java.lang.String")) {
                return ($w) w.sayHello((java.lang.String) $4[0]);
            }
            if ("sayHello".equals($2) && $3.length == 1 && $3[0].getName().equals("com.anto.dubbo.User")) {
                return ($w) w.sayHello((com.anto.dubbo.User) $4[0]);
            }
        } catch (Throwable e) {
            throw new java.lang.reflect.InvocationTargetException(e);
        }
        throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class com.anto.dubbo.dubboprovider.HelloServiceImpl.");
    }

看到這個動態代理類是否有一絲絲的親切感?

這不就是根據不同的方法名和參數類型來決定調用接口的哪個方法嘛!

構建好由Wrapper類生成的動態代理後,返回一個匿名的AbstractProxyInvoker類型的Invoker對象。那麼它有什麼特點呢?

可以看到它重寫了doInvoke()方法,最終是調用動態代理類的invokeMethod(),那本質上也就是調用dubbo接口的方法。

回顧下生成Invoker對象的過程:

1.Dubbo框架生成一個ProxyFactory$Adaptive代理類---決定用哪個ProxyFactory
2.Wrapper類爲具體要發佈的服務創建一個動態代理類
3.生成一個重寫了doInvoke()方法的AbstractProxyInvoker類型的匿名類--用以轉發消費發起的請求到Wrapper生成的代理類的invokeMethod()

所以,簡單總結一下Invoke本質上應該是一個代理,經過層層包裝最終進行了發佈。當消費者發起請求的時候,會獲得這個Invoker進行調用。
最終發佈出去的Invoker, 也不是單純的一個代理,也是經過多層包裝
InvokerDelegate(DelegateProviderMetaDataInvoker(AbstractProxyInvoker()))

遠程暴露服務

ServiceConfig類中doExportUrlsFor1Protocol()方法中,首先是本地服務的暴露,然後是遠程服務的暴露。

遠程服務暴露的過程其實也就是伴隨着生成Exporter對象過程。

//通過某種協議來暴露服務
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);

遠程服務暴露時,首先需要得到一個PROTOCOL對象,它是一個自適應擴展點接口的得到Protocol的對象。

得到Protocol對象

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

由於PROTOCOL接口的方法標註了@Adaptive,所以會爲其生成代理類對象。

 	@Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

動態生成的類如下Protocol$Adaptive

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    //...略

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        //根據url中的協議頭參數 決定加載哪個協議的實現,最開始協議頭爲registry
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

  //...略
}

思考下:爲什麼需要這麼設計呢?每個標註了@Adaptive擴展類都聽過Compile來生成代碼,而不是單獨設計一個Protocol$Adaptive類呢?

Dubbo針對@SPI擴展接口中,方法標註了@Adaptive註解的類都會生成一個代理類,名稱爲接口名$Adaptive。
這樣設計主要是擴展性和靈活性。
通過註解就能夠去聲明一個動態的適配類,同時用戶在使用的時候,可以根據配置中聲明的屬性來決定適配到的目標類。
可擴展性體現在spi的機制上,當我們自己開發擴展的實現時,同樣可以利用這個動態適配的功能來實現目標類的路由。

在上面一步生成的Invoker對象中,它的URL爲:

registry://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=12248&release=2.7.7&side=provider&timestamp=1606719802290&pid=12248&registry=zookeeper&release=2.7.7&timestamp=1606719800257
 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);

所以在上面的getExtension("registry")會去查找RegistryProtocol,但是在ExtensionLoader中有如下語句:

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

此時的wrapperClasses則是這三個

image-20201104175131505

當在解析META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol時,會把Protocol接口的包裝類放在緩存屬性cachedWrapperClasses中。

思考下:怎麼判斷這個實現SPI接口是一個包裝類型呢?

private boolean isWrapperClass(Class<?> clazz) {
        try {
            //當實現類有將接口自身傳進來的構造函數時,認爲其是一個包裝類型
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

Dubbo運用裝飾器模式對協議的spi接口起到一個裝飾增強作用。

所以暴露服務的代碼Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);此時PROTOCOLProtocolListenerWrapper對象。然後依次調用QosProtocolWrapperProtocolFilterWrapperexport()方法。

ProtocolListenerWrapper :用於服務export時候插入監聽機制 。

QosprotocolWrapper :如果當前配置了註冊中心,則會啓動一個Qos server.qos是dubbo的在線運維命令,dubbo2.5.8新版本重構了telnet模塊,提供了新的telnet命令支持,新版本的telnet端口與dubbo協議的端口是不同的端口,默認爲22222 。

ProtocolFilterWrapper :對invoker進行filter的包裝,實現請求的過濾 。

調用鏈路如下:

ProtocolListenerWrapper.export()--->QosProtocolWrapper.export()---->ProtocolFilterWrapper.export()--->RegistryProtocol.export()

但是在註冊的場景過程中,這幾個擴展點都不會生效,執行的邏輯會先判斷是否爲註冊協議,如果是則直接基於協議發佈服務。

一句話,最後我們得到的是RegistryProtocol對象。

啓動Netty監聽服務

接下來將調用RegistryProtocol.export()方法。

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    //這裏獲得的是zookeeper註冊中心的url: zookeeper://ip:port
    URL registryUrl = getRegistryUrl(originInvoker);
    // 這裏是獲得服務提供者的url, dubbo://ip:port...
    URL providerUrl = getProviderUrl(originInvoker);
	//訂閱override數據。在admin控制檯可以針對服務進行治理,比如修改權重,修改路由機制等,當註冊中心有此服務的覆蓋配置註冊進來時,推送消息給提供者,重新暴露服務
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //這裏就交給了具體的協議去暴露服務(如dubbo
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    final Registry registry = getRegistry(originInvoker);
    //獲取要註冊到註冊中心的URL: dubbo://ip:port
    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

    // 若配置了註冊中心,向註冊中心如zookeeper中註冊服務
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
        register(registryUrl, registeredProviderUrl);
    }

    // register stated url on provider model
    registerStatedUrl(registryUrl, registeredProviderUrl, register);

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);

    notifyExport(exporter);
    //保證每次export都返回一個新的exporter實例
    return new DestroyableExporter<>(exporter);
}

RegistryProtocol.expor()t中,有兩個核心流程:

  • 調用 doLocalExport 啓動本地服務,也就是netty server
  • 調用 register 方法進行服務地址的註冊

接下來看下doLocalExport(originInvoker, providerUrl);方法。

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    //key的值爲發佈該dubbo服務的一個協議串 dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?...
    String key = getCacheKey(originInvoker);
	//當bouds這個Map中不存在該服務的key時,會生成一個該服務的Exporter
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

先將要發佈的服務生成一個唯一的帶有dubbo協議串的key值。

dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=3876&release=2.7.7&side=provider&timestamp=1606963046596

此時再次調用protocol.export(invokerDelegate),會再次進入到protocol$Adaptive.export()方法中。

不過此次返回的是DubboProtocol,調用鏈路如下:

ProtocolListenerWrapper.export()--->QosProtocolWrapper.export()---->ProtocolFilterWrapper.export()--->DubboProtocol.export()

所以自來就來到了DubboProtocol.export()方法:

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();

    // 將服務名稱端口作爲key  Invoker作爲DubboExporter的參數存儲 一起放進一個exportMap中
    //key  com.anto.dubbo.HelloService:20880
    String key = serviceKey(url);
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    exporterMap.put(key, exporter);

    //是否配置了參數回調機制
    Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
    Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
    if (isStubSupportEvent && !isCallbackservice) {
        String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
        if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
            if (logger.isWarnEnabled()) {
                logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                                                      "], has set stubproxy support event ,but no stub methods founded."));
            }

        }
    }
	//開啓一個Netty服務
    openServer(url);
    optimizeSerialization(url);

    return exporter;
}
  • openServer()
private void openServer(URL url) {
    // 獲取 host:port,並將其作爲服務器實例的 key,用於標識當前的服務器實例
    String key = url.getAddress();
    //client 也可以暴露一個只有server可以調用的服務
    boolean isServer = url.getParameter(IS_SERVER_KEY, true);
    if (isServer) {
        ProtocolServer server = serverMap.get(key);
        if (server == null) {
            synchronized (this) {
                server = serverMap.get(key);
                if (server == null) {
                    //創建服務器實例
                    serverMap.put(key, createServer(url));
                }
            }
        } else {
            // 服務器已創建,則根據 url 中的配置重置服務器
            server.reset(url);
        }
    }
}

往下則是基於org.apache.dubbo.remoting.transport.netty4.NettyTransporter 開啓一個Netty服務的過程了。

在基於DubboProtocol協議發佈服務的過程中,有幾個重要的步驟

  • 構建一個exporterMap,以服務路徑名稱作爲key,把invoker包裝成了DubboExporter作爲value存儲 ;
  • 針對同一臺機器上的多個服務,只啓動一個服務實例 ;
  • 採用Netty4來發布服務 。

註冊服務

當配置了諸如Zookeeper的註冊中心時,會將服務的節點信息在相應的地方寫入。

// 向zookeeper中註冊服務
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
    register(registryUrl, registeredProviderUrl);
}

根據URL的key 來動態的找到需要註冊的服務中心,registryFactory是個動態擴展點,先經過包裝的擴展點,然後當爲zookeeper時,則爲ZookeeperRegistryFactory

此時的registryURL已經是解析成Zookeeper開頭的url了。

zookeeper://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.30.60.208%3A20880%2Fcom.anto.dubbo.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-annotation-provider%26bind.ip%3D172.30.60.208%26bind.port%3D20880%26cluster%3Dfailsafe%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.anto.dubbo.HelloService%26methods%3DsayHello%26pid%3D16172%26release%3D2.7.7%26side%3Dprovider%26timestamp%3D1606978398061&pid=16172&release=2.7.7&timestamp=1606978397349

registeredProviderUrl則是dubbo開頭的服務提供地址。

private void register(URL registryUrl, URL registeredProviderUrl) {
    //registry爲ListenerRegistryWrapper  registryFactory同樣也是由動態擴展點生成的對象
    Registry registry = registryFactory.getRegistry(registryUrl);
    registry.register(registeredProviderUrl);
}
  • ListenerRegistryWrapper.register()
public void register(URL url) {
    try {
        //調用ZookeeperRegistry.register()
        registry.register(url);
    } finally {
        if (CollectionUtils.isNotEmpty(listeners)) {
            RuntimeException exception = null;
            for (RegistryServiceListener listener : listeners) {
                if (listener != null) {
                    try {
                        listener.onRegister(url);
                    } catch (RuntimeException t) {
                        logger.error(t.getMessage(), t);
                        exception = t;
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }
        }
    }
}

ListenerRegistryWrapper 是對ZookeeperRegistry做了一層包裝,增加監聽器相應的功能。

  • FailbackRegistry.register()

FailbackRegistry是一個提供的重試機制的父類,是ZookeeperRegistryNacosRegistrySofaRegistry等具體註冊中心的父類。

ZookeeperRegistry 類中並沒有register(),所以將進入父類FailbackRegistry的方法中。

public void register(URL url) {
   //...略
    super.register(url);
    removeFailedRegistered(url);
    removeFailedUnregistered(url);
    try {
        // 真正調用ZookeeperRegistry.doRegister()
        doRegister(url);
    } catch (Exception e) {
        Throwable t = e;

        // 若配置的屬性check爲true 則直接拋出異常 不再進行重試
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
            && url.getParameter(Constants.CHECK_KEY, true)
            && !CONSUMER_PROTOCOL.equals(url.getProtocol());
        boolean skipFailback = t instanceof SkipFailbackWrapperException;
        if (check || skipFailback) {
            if (skipFailback) {
                t = t.getCause();
            }
            throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
        } else {
            logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }

        // 如果註冊失敗拋異常了,會將註冊失敗的url放入註冊失敗的容器中
        addFailedRegistered(url);
    }
}
  • ZookeeperRegistry.doRegister()
public void doRegister(URL url) {
    try {
        zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

所以最終會調用相應dubbo集成的zookeeper的客戶端(curator 2.7以後)來寫入暴露的服務的節點信息。

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