Davids原理探究:Dubbo服務暴露、消費和優雅停機原理

Dubbo服務暴露和服務消費原理(基於Dubbo 2.6.5)

上次本地編譯的最新的Dubbo 2.7.8,跟着書看源碼是基於2.6.5,從2.7.0版本開始dubbo的groupId已經變了。這裏查看2.6.5的源碼有兩種方案

  1. 【推薦】下載Dubbo 2.6.5源碼編譯,編譯流程和2.7.8一樣
  2. 直接用maven引入了alibaba dubbo 2.6.5的包,用IDEA查看源碼學習,有個坑就是ali在dubbo裏面有用自己的Spring support,需要單獨依賴,不然有些類是沒有的。
<!-- 2.7.0之前 -->
<groupId>com.alibaba</groupId>

<!-- 2.7.0開始 -->
<groupId>org.apache.dubbo</groupId>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
<!-- 2.7.0開始groupId變更爲org.apache.dubbo -->
<!--<dependency>
     <groupId>org.apache.dubbo</groupId>
     <artifactId>dubbo</artifactId>
     <version>2.7.0</version>
 </dependency>-->
 
 <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>dubbo</artifactId>
     <version>2.6.5</version>
 </dependency>

 <!-- https://mvnrepository.com/artifact/com.alibaba.spring/spring-context-support -->
 <dependency>
     <groupId>com.alibaba.spring</groupId>
     <artifactId>spring-context-support</artifactId>
     <version>1.0.6</version>
 </dependency>

配置解析原理

基於XML配置解析原理

什麼是XSD?Dubbo框架直接集成了Spring的能力,利用了Spring配置文件擴展出自定義的解析方式dubbo.xsd。
dubbo.xsd文件用來約束使用XML配置時的標籤和對應的屬性,比如Dubbo中的<dubbo:service>和<dubbo:reference>標籤等。Spring在解析到自定義的namespace標籤時,會查找對應的spring。schemas和spring.handlers文件,最終觸發Dubbo的spaceHandler類來進行初始化和解析。

schema模塊說明
類型定義 功能概述
applicationType 配置應用級別的信息,比如應用名稱,應用負責人和應用版本等
protocolType 配置服務提供者暴露的協議,Dubbo允許同時配置多個協議,但只能有一個協議默認暴露
registryType 配置註冊中心的地址和協議,Dubbo也允許多個註冊中心同時使用
providerType 配置服務提供方的全局配置,比如服務方設置了timeout,消費方會自動透傳超時
consumerType 配置消費方全局配置,比如connections屬性代表客戶端會創建的TCP連接數,客戶端全局配置會覆蓋providerType透傳的屬性
serviceType 配置服務提供方接口範圍信息,比如服務暴露的接口和具體實現類等
referenceType 配置消費方接口範圍信息,比如引入的接口名稱和是否範化調用標誌等
moduleType 配置應用所屬模塊信息
monitorType 配置應用監控上報相關地址
methodType 配置方法級別參數,主要應用於<dubbo:service>和<dubbo:reference>
argumentType 配置應用參數方法等輔助信息,比如高級特性中異步參數回調索引的配置等
parameterType 選項參數配置,可以作爲<dubbo:protocol>、<dubbo:service>、<dubbo:reference>、<dubbo:provider>和<dubbo:consumer>的字標籤,方便添加自定義參數,會透傳到框架的URL中

有了dubbo.xsd中約束的定義,以及如何擴展字段之後主要的解析邏輯在DubboBeanDefinitionParser#parse中完成。主要內容包含將標籤解析成對應的Bean定義並註冊到Spring上下文中,同時保證Spring容器中相同id的Bean不會覆蓋。

// public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement
@Override
public void init() {
    registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
    registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
    registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
    registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
    registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
    registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
    registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
    registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
    registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

基於註解配置解析原理

註解處理邏輯主要包含3部分內容:

  1. 如果用戶使用了配置文件,則框架按需生成對應的Bean。
  2. 要將所有使用Dubbo註解@Service的class提升爲Bean。
  3. 要爲使用@Reference註解的字段或方法注入代理對象。
@EnableDubbo
// @EnableDubbo 註解上加了@EnableDubboConfig、@DubboComponentScan
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
	...
}

// @EnableDubboConfig 註解上import了DubboConfigConfigurationSelector類
@Import(DubboConfigConfigurationSelector.class)
public @interface EnableDubboConfig {
	...
}

// @DubboComponentScan 註解上import了DubboComponentScanRegistrar類
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
	...
}

@DubboComponentScan

  1. 激活DubboComponentScanRegistrar
  2. 生成ServiceAnnotationBeanPostProcessor處理器
  3. 生成ReferenceAnnotationBeanPostProcessor處理器。
// public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar
// DubboComponentScanRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	// 獲取掃描包路徑
    Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
	// 激活ServiceAnnotationBeanPostProcessor
    registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
	// 激活ReferenceAnnotationBeanPostProcessor
    registerReferenceAnnotationBeanPostProcessor(registry);

}
ServiceAnnotationBeanPostProcessor的作用
// ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	// 獲取用戶註解配置的包掃描(@EnableDubbo 的value值,沒有則默認註解類的包路徑)
    Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
	// 觸發ServiceBean的定義和注入
    if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
        registerServiceBeans(resolvedPackagesToScan, registry);
    } else {
        if (logger.isWarnEnabled()) {
            logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
        }
    }
}

private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
    DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
    BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
    scanner.setBeanNameGenerator(beanNameGenerator);
	// 掃描Dubbo的註解@Service,不會掃描Spring的@Service註解
    scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
    for (String packageToScan : packagesToScan) {
        // 將@Service作爲不同的Bean注入容器
        scanner.scan(packageToScan);
        // 對掃描的服務創建Beandefinitionholder,用於生成ServiceBean定義
        Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
        if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            	// 註冊ServiceBean定義與數據綁定
                registerServiceBean(beanDefinitionHolder, registry, scanner);
            }
            
            ...
            
        }
    }

}
ReferenceAnnotationBeanPostProcessor的作用

ReferenceAnnotationBeanPostProcessor UML

public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor<Reference>
        implements ApplicationContextAware, ApplicationListener

// 父類 AnnotationInjectedBeanPostProcessor#postProcessPropertyValues
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
	// 查找Bean所有標註了@Reference的字段和方法
    InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    try {
    	// 對字段、方法進行反射綁定
        metadata.inject(bean, beanName, pvs);
    } catch (BeanCreationException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
                + " dependencies is failed", ex);
    }
    return pvs;
}

private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
    // 返回到類名作爲緩存鍵,以便向後兼容自定義調用者
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 首先對併發映射進行快速檢查,使用最小的鎖定
    AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                try {
                	// 處理註解字段和方法
                    metadata = buildAnnotatedMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                } catch (NoClassDefFoundError err) {
                    throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
                            "] for annotation metadata: could not find class that it depends on", err);
                }
            }
        }
    }
    return metadata;
}

// 處理帶@Reference的字段和方法
private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
	// 處理帶@Reference的字段
  	Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
  	// 處理帶@Reference的方法
    Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
    // 注入容器
    return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

// 處理帶@Reference的字段
private List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {
    final List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>();
    ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
    	// 遍歷查找所有帶@Reference的非靜態字段添加到List最後返回
        @Override
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
            A annotation = getAnnotation(field, getAnnotationType());
            if (annotation != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("@" + getAnnotationType().getName() + " is not supported on static fields: " + field);
                    }
                    return;
                }
                elements.add(new AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement(field, annotation));
            }
        }
    });
    return elements;
}

// 處理帶@Reference的方法
private List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {
    final List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>();
    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
    	// 遍歷查找所有帶@Reference的非靜態方法添加到List最後返回
        @Override
        public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
            Method bridgedMethod = findBridgedMethod(method);
            if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            A annotation = findAnnotation(bridgedMethod, getAnnotationType());
            if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("@" + getAnnotationType().getSimpleName() + " annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                if (method.getParameterTypes().length == 0) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("@" + getAnnotationType().getSimpleName() + " annotation should only be used on methods with parameters: " +
                                method);
                    }
                }
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                elements.add(new AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement(method, pd, annotation));
            }
        }
    });
    return elements;

}

服務暴露原理

Dubbo服務暴露機制

配置初始化

不管是在服務暴露還是在服務消費場景下,Dubbo框架都會根據優先級對配置信息做聚合處理,目前默認覆蓋策略主要遵循以下三點規則:

  1. -D傳遞給JVM參數優先級最高,比如-Ddubbo.protocol.port=20880
  2. 代碼或者XML配置優先級次高,如:Spring中XML文件制定<dubbo:protocol=“20880”>
  3. 配置文件優先級最低,如:dubbo.properties文件指定dubbo.protocol.port=20880
    一般推薦使用dubbo.properties作爲默認值,只有JVM沒有指定參數,並且XML沒有匹配時,dubbo.properties纔會生效,通常用語共享公共配置,如應用名稱等。

Dubbo的配置也會受到provider的影響,這個屬於運行期屬性值影響,同樣遵循一下兩點規則:

  1. 如果只有provider端指定配置,則會自動透傳到客戶端(如:timeout)
  2. 如果客戶端也有相應配置,則服務端配置會被覆蓋(如:timeout)
    運行時屬性隨着框架特性可以動態添加,不允許透傳的屬性會在ClusterUtils#mergeUrl中進行特殊處理。
// ClusterUtils#mergeUrl
public static URL mergeUrl(URL remoteUrl, Map<String, String> localMap) {
   	Map<String, String> map = new HashMap<String, String>();
    Map<String, String> remoteMap = remoteUrl.getParameters();
    if (remoteMap != null && remoteMap.size() > 0) {
        map.putAll(remoteMap);

        // 移除禁止透傳的參數
        map.remove(Constants.THREAD_NAME_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.THREAD_NAME_KEY);

        map.remove(Constants.THREADPOOL_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.THREADPOOL_KEY);

        map.remove(Constants.CORE_THREADS_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.CORE_THREADS_KEY);

        map.remove(Constants.THREADS_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.THREADS_KEY);

        map.remove(Constants.QUEUES_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.QUEUES_KEY);

        map.remove(Constants.ALIVE_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.ALIVE_KEY);

        map.remove(Constants.TRANSPORTER_KEY);
        map.remove(Constants.DEFAULT_KEY_PREFIX + Constants.TRANSPORTER_KEY);
    }
    if (localMap != null && localMap.size() > 0) {
        map.putAll(localMap);
    }
    if (remoteMap != null && remoteMap.size() > 0) {
        // 使用從provider 傳遞的version 
        String dubbo = remoteMap.get(Constants.DUBBO_VERSION_KEY);
        if (dubbo != null && dubbo.length() > 0) {
            map.put(Constants.DUBBO_VERSION_KEY, dubbo);
        }
        String version = remoteMap.get(Constants.VERSION_KEY);
        if (version != null && version.length() > 0) {
            map.put(Constants.VERSION_KEY, version);
        }
        String group = remoteMap.get(Constants.GROUP_KEY);
        if (group != null && group.length() > 0) {
            map.put(Constants.GROUP_KEY, group);
        }
        String methods = remoteMap.get(Constants.METHODS_KEY);
        if (methods != null && methods.length() > 0) {
            map.put(Constants.METHODS_KEY, methods);
        }
        // 保留provider url的時間戳
        String remoteTimestamp = remoteMap.get(Constants.TIMESTAMP_KEY);
        if (remoteTimestamp != null && remoteTimestamp.length() > 0) {
            map.put(Constants.REMOTE_TIMESTAMP_KEY, remoteMap.get(Constants.TIMESTAMP_KEY));
        }
        // 在Provider和Consumer上組合過濾器和偵聽器
        String remoteFilter = remoteMap.get(Constants.REFERENCE_FILTER_KEY);
        String localFilter = localMap.get(Constants.REFERENCE_FILTER_KEY);
        if (remoteFilter != null && remoteFilter.length() > 0
                && localFilter != null && localFilter.length() > 0) {
            localMap.put(Constants.REFERENCE_FILTER_KEY, remoteFilter + "," + localFilter);
        }
        String remoteListener = remoteMap.get(Constants.INVOKER_LISTENER_KEY);
        String localListener = localMap.get(Constants.INVOKER_LISTENER_KEY);
        if (remoteListener != null && remoteListener.length() > 0
                && localListener != null && localListener.length() > 0) {
            localMap.put(Constants.INVOKER_LISTENER_KEY, remoteListener + "," + localListener);
        }
    }
    return remoteUrl.clearParameters().addParameters(map);
}
服務暴露的核心類ServiceConfig
  1. 通過反射獲取配置對象並放到map中用於後續構造URL參數。
  2. 區分全局配置,默認在屬性前面添加default.前綴,當框架獲取URL中的參數時,如果不存在則會自動嘗試獲取default.前綴對應的值。
  3. 處理本地JVM協議暴露。
  4. 追加監控上報地址,框架會在攔截器中執行數據上報。
  5. 通過動態代理創建Invoker對象,在服務端生成的是AbstractProxyInvoker實例,所有真實的方法調用都會委託給代理,然後代理轉發給服務ref調用。目前的兩種代理:
    1. JavassistProxyFactory:創建Wrapper子類,在子類中實現invokeMethod方法,方法體內會爲每個ref方法都做方法名和方法參數匹配校驗,如果匹配則直接調用即可,相比JdkProxyFactory省去了反射調用的開銷。
    2. JdkProxyFactory:通過反射獲取真是對象的方法,然後調用即可。
  6. 暴露服務(端口打開等),然後進行服務元數據註冊。
  7. 最後處理沒有註冊中心的場景,直接進行服務暴露,不需要元數據註冊,因爲直接暴露的URL信息是以具體的RPC協議開頭的,並不是以註冊中心協議開頭的。
// 註冊中心URL示例
registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/xxx?...

// 直接暴露URL示例
dubbo://ip:port/xxx?timeout=1000&...
// ServiceConfig
// #export()
// #doExport()
// #doExportUrls()
// #doExportUrlsFor1Protocol(protocolConfig, registryURLs)
private void doExportUrls() {
	// 獲取當前服務對應註冊中心實例
   	List<URL> registryURLs = loadRegistries(true);
   	// 如果服務指定暴露多個協議(Dubbo、REST),則依次暴露服務
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// 如果協議名稱爲空則默認使用dubbo協議
    String name = protocolConfig.getName();
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }
	// 構造配置信息
    Map<String, String> map = new HashMap<String, String>();
    // 設置side=provider
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    // 設置version
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    // 設置時間戳
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    // 讀其他配置信息到map,用於後續構造
    appendParameters(map, application);
    appendParameters(map, module);
    // 讀取全局配置信息會自動添加前綴
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    
    ...
    
    URL url = new URL(name, host, port, path, map);
    
    ...
    
    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 沒有配置時不要導出
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        // 如果配置不是遠程的,則導出爲本地(僅當配置是遠程的時候才導出爲遠程)
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // 如果配置不是本地的就導出到遠程(只有當配置是本地的時候才導出到本地)
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            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.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    // 對於providers,用於啓用自定義代理來生成invoker
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }
                    // 通過動態代理轉換成Invoker,registryURL存儲的是註冊中心地址,使用export作爲key追加服務元數據信息
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // 服務暴露後向註冊中心註冊服務信息
                    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);
}

// 暴露本地服務
private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
    	// 將協議設置爲injvm進行導出
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
        Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        // 緩存到exporters,unexport可以使用
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}
註冊中心暴露服務

註冊中心暴露服務的時候依次作了以下5件事情:

  1. 委託具體協議(Dubbo)進行服務暴露,創建NettyServer監聽端口和保存服務實例。
  2. 創建註冊中心對象,與註冊中心建立TCP連接。
  3. 註冊服務元數據到註冊中心。
  4. 訂閱configurators節點,監聽服務動態屬性變更事件。
  5. 服務銷燬收尾工作,比如關閉端口、反註冊服務信息等。
// RegistryProtocol#export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // 打開端口,把服務實例儲存到map
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    URL registryUrl = getRegistryUrl(originInvoker);
    // 創建註冊中心實例,暴露服務
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
	boolean register = registeredProviderUrl.getParameter("register", true);
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
    // 服務暴露之後,註冊元數據
    if (register) {
        register(registryUrl, registeredProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    }

    // 訂閱覆蓋數據
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    // 確保每次導出時都返回一個新的export實例,內部監聽configurators節點,以及unexport之後的操作
    return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
攔截器初始化

在進行服務暴露前,框架會做攔截器初始化,Dubbo在加載protocol擴展點時會自動注入ProtocolListenerWrapper和ProtocolFilterWrapper。在ProtocolListenerWrapper實現中,在對服務提供者進行暴露時回調對應的監聽器方法。ProtocolFilterWrapper會調用下一級ProtocolListenerWrapper#export方法,在該方法內部會觸發buildInvokerChain進行攔截器構造。

// ProtocolFilterWrapper---> ProtocolListenerWrapper ---> DubboProtocol
// 協議攔截器擴展 ProtocolFilterWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    // 先構造攔截器(會過濾provider端分組),然後觸發Dubbo協議暴露
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // 獲取url中(key:service.filter)指定的filter擴展類 (使用Dubbo SPI)
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            // 將真實的Invoker(服務對象ref,放到攔截器末尾)
            final Invoker<T> next = last;
            // 爲每一個filter生成一個exporter依次串起來
            last = new Invoker<T>() {

			...
			
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                	// 每次調用都會傳遞給下一個攔截器
                    return filter.invoke(next, invocation);
                }
            
            ...
            
            };
        }
    }
    return last;
}

// 協議監聽器擴展 ProtocolListenerWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    // 獲取url中(key:exporter.listener)指定的擴展類(使用Dubbo SPI)
    return new ListenerExporterWrapper<T>(protocol.export(invoker),
            Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                    .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}

// Dubbo協議暴露 DubboProtocol#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();

    // 根據服務分組、版本、接口和端口構造key
    String key = serviceKey(url);
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    // 把exporter儲存到單例DubboProtocol中
    exporterMap.put(key, exporter);

	...
	
	// 服務初次暴露會創建監聽服務器
    openServer(url);
    optimizeSerialization(url);
    return exporter;
}

// DubboProtocol#openServer
private void openServer(URL url) {
    // 查找服務
    String key = url.getAddress();
    // 客戶端可以導出僅供服務器調用的服務
    boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
    if (isServer) {
        ExchangeServer server = serverMap.get(key);
        if (server == null) {
        	// 創建服務並緩存
            serverMap.put(key, createServer(url));
        } else {
            // 服務器支持重置,與覆蓋一起使用
            server.reset(url);
        }
    }
}

// DubboProtocol#createServer
private ExchangeServer createServer(URL url) {
   	
   	...
   	
    ExchangeServer server;
    try {
    	// 創建NettyServer並初始化Handler
        server = Exchangers.bind(url, requestHandler);
    } catch (RemotingException e) {
        throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
    }
    
    ...
    
    return server;
}

服務消費原理

概述

整體上看,Dubbo框架做服務消費也分爲兩大部分:

  1. 通過持有遠程對象實例生成Invoker,這個Invoker在客戶端是核心的遠程代理對象。
  2. 把Invoker通過動態代理轉換成實現用戶接口的動態代理引用。

這裏Invoker承載了網絡連接、服務調用和重試等功能,在客戶端,它可能是一個遠程的實現,也可能是一個集羣的實現。
框架真正進行服務引用的入口點在ReferenceBean#getObject,不管是XML還是註解,都會轉換成ReferenceBean,它繼承自ReferenceConfig。

服務消費過程

  1. 優先判斷是否處於同一個JVM裏面,默認場景下Dubbo會找出內存中的injvm協議的服務(服務暴露時會註冊一份到injvm,將服務實例放到內存map中)直接獲取實例調用。
  2. 在註冊中心追加消費者元數據信息,應用啓動時訂閱註冊中心、服務提供者參數等合併時會用到這部分信息。
  3. 處理只有一個註冊中心的場景,這種場景在客戶端中是最常見的,客戶端啓動拉取服務元數據,訂閱provider、路由和配置變更。
  4. 處理多註冊中心的場景。逐個獲取註冊中心的服務,並添加到invokers列表中,後面通過Cluster將多個Invoker轉換成一個Invoker。

Dubbo優雅停機原理解析

  1. 收到kill -9進程退出信號,Spring容器會觸發容器銷燬事件。
  2. provider端會取消註冊服務元數據信息。
  3. consumer端會收到最新的地址列表(不包含準備停機的地址)。
  4. Dubbo協議會發送readonly事件報文通知consumer服務不可用。
  5. 服務端等待已經執行的任務結束並拒絕新任務執行。
  6. 最後provider與consumer斷開TCP連接。

註冊中心已經通知了最新服務列表,provider還要發送readonly報文的原因是,註冊中心推送服務可能會由網絡延遲,客戶端接收和計算服務列表可能佔用一些時間。Dubbo協議發送readonly報文時,consumer端會設置響應的provider爲不可用,下次負載均衡的時候就不會調用下線的機器了。

Dubbo2.6.3以後修復了優雅停機的一些bug,之前版本中沒有做到完全的優雅停機的原因是,Spring和Dubbo都註冊了JVM停止的鉤子,這種場景下兩個線程併發執行的時候可能引用一句銷燬的資源,比如Dubbo正在執行的任務需要引用Spring中的Bean,但這時Spring鉤子函數已經關閉了Spring上下文,導致訪問任何Spring資源都會報錯。

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