A Probe into Davids'Principles: Dubbo Service Exposure, Consumption and Elegant Downtime Principles

Posted by cyronuts on Fri, 12 Jun 2020 04:06:57 +0200

Article Directory

Dubbo Service Exposure and Service Consumption Principles (Based on Dubbo 2.6.5)

The latest Dubbo 2.7.8, compiled locally last time, is based on 2.6.5 for Follow-up Source, and dubbo's groupId has changed since version 2.7.0.There are two options for viewing 2.6.5 source code here

  1. [Recommended] Download Dubbo 2.6.5 Source Compile, Compilation process is the same as 2.7.8.
  2. Introducing alibaba dubbo 2.6.5 package directly with maven, learning source code with IDEA, has a pit that Alibaba has its own Spring support in dubbo, which needs to be depended on independently, otherwise some classes are not available.
<!-- 2.7.0 before -->
<groupId>com.alibaba</groupId>

<!-- 2.7.0 start -->
<groupId>org.apache.dubbo</groupId>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo -->
<!-- 2.7.0 start groupId Change to 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>

Configuration parsing principles

Based on XML Configuration Parsing Principle

What is XSD? The Dubbo framework directly integrates Spring's capabilities, leveraging Spring profiles to extend custom parsingDubbo.xsd.
Dubbo.xsdFiles are used to constrain tags and corresponding attributes when using XML configurations, such as <in DubboDubbo:service>and <Dubbo:reference>Labels, etc.When Spring resolves to a custom namespace tag, it looks for the corresponding spring.schemas andSpring.handlersFile that eventually triggers Dubbo's spaceHandler class for initialization and parsing.

schema module description
type definition Functional overview
applicationType Configure application-level information such as application name, application owner, application version, etc.
protocolType Configure the protocol exposed by the service provider. Dubbo allows multiple protocols to be configured simultaneously, but only one protocol can be exposed by default
registryType Configure the registry's address and protocol, and Dubbo allows multiple registries to use it simultaneously
providerType Configure the global configuration of the service provider, such as when the service provider sets timeout, and the consumer automatically passes through the timeout
consumerType Configure consumer global configurations, such as the connections property representing the number of TCP connections the client will create, and the client global configuration overrides the properties of providerType transmissions
serviceType Configure service provider interface scope information, such as service exposed interfaces and implementation classes
referenceType Configure consumer interface scope information, such as the interface name introduced and whether the call flag is normalized, etc.
moduleType Configure the module information to which the application belongs
monitorType Configure application monitoring report related addresses
methodType Configuration method level parameters, mainly applied to <Dubbo:service>and <Dubbo:reference>
argumentType Configure ancillary information such as application parameter methods, such as configuration of asynchronous parameter callback index in advanced features
parameterType Option parameter configuration as <Dubbo:protocol>, <Dubbo:service>, <Dubbo:reference>, <Dubbo:provider>and <Dubbo:consumer>Word label, easy to add custom parameters, will be passed to the frame's URL

become pregnantDubbo.xsdThe definition of constraints in and the main parsing logic after how to extend fields are done in DubboBeanDefinitionParser#parse.The main content includes parsing tags into their corresponding Bean definitions and registering them in the Spring context, while guaranteeing that beans with the same id in the Spring container will not be overwritten.

// 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());
}

Configuration parsing principles based on annotations

Annotation processing logic mainly consists of three parts:

  1. If the user uses a configuration file, the framework generates the corresponding beans on demand.
  2. To promote all class es using the Dubbo annotation @Service to Bean s.
  3. To inject a proxy object into a field or method that uses the @Reference annotation.
@EnableDubbo
// @EnableDubboConfig, @DubboComponentScan added to the @EnableDubbo comment
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
	...
}

// The DubboConfigConfigurationSelector class is import ed on the @EnableDubboConfig annotation
@Import(DubboConfigConfigurationSelector.class)
public @interface EnableDubboConfig {
	...
}

// The @DubboComponentScan annotation import s the DubboComponentScanRegistrar class
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
	...
}

@DubboComponentScan

  1. Activate DubboComponentScanRegistrar
  2. Generate ServiceAnnotationBeanPostProcessor Processor
  3. Generate the ReferenceAnnotationBeanPostProcessor processor.
// public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar
// DubboComponentScanRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	// Get Scan Package Path
    Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
	// Activate ServiceAnnotationBeanPostProcessor
    registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
	// Activate ReferenceAnnotationBeanPostProcessor
    registerReferenceAnnotationBeanPostProcessor(registry);

}
Role of ServiceAnnotationBeanPostProcessor
// ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	// Gets the package scan configured by the user annotation (@EnableDubbo's value, otherwise default annotation class's package path)
    Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
	// Triggering the definition and injection of a 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);
	// Scan Dubbo's comment@Service instead of Spring's @Service comment
    scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
    for (String packageToScan : packagesToScan) {
        // Injecting @Service as a different Bean container
        scanner.scan(packageToScan);
        // Create a Beandefinitionholder for scanned services to generate ServiceBean definitions
        Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
        if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            	// Register ServiceBean Definition and Data Binding
                registerServiceBean(beanDefinitionHolder, registry, scanner);
            }
            
            ...
            
        }
    }

}
Role of ReferenceAnnotationBeanPostProcessor

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

// Parent AnnotationInjectedBeanPostProcessor#postProcessPropertyValues
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
	// Find all Bean fields and methods labeled @Reference
    InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    try {
    	// Reflectively bind fields and methods
        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) {
    // Return to class name as cache key for backward compatibility with custom callers
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Start with a quick check of concurrent mappings, using minimal locking
    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 {
                	// Processing annotation fields and methods
                    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;
}

// Processing fields and methods with @Reference
private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
	// Processing fields with @Reference
  	Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
  	// Processing methods with @Reference
    Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
    // Injection Container
    return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

// Processing fields with @Reference
private List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {
    final List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>();
    ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
    	// Traverse to find all non-static fields with @Reference added to List and returned
        @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;
}

// Processing methods with @Reference
private List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {
    final List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>();
    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
    	// Traverse to find all non-static methods with @Reference added to List and returned
        @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;

}

Service Exposure Principles

Configuration Initialization

In both service exposure and service consumption scenarios, the Dubbo framework aggregates configuration information based on priority, and the default coverage policy currently follows three main rules:

  1. -D is passed to the JVM parameter with the highest priority, such as -Ddubbo.protocol.port=20880
  2. Code or XML configuration takes precedence, such as XML file development in Spring <Dubbo:protocol="20880">
  3. Configuration files have the lowest priority, such as:Dubbo.propertiesFile SpecificationDubbo.protocol.port=20880
    General RecommendationDubbo.propertiesAs a default, only if the JVM does not specify parameters and the XML does not match,Dubbo.propertiesOnly then will it take effect, and it is often used to share public configurations such as application names.

The configuration of Dubbo is also affected by provider s, which are run-time attribute values, and follow the same two rules:

  1. If only the provider side specifies the configuration, it is automatically passed to the client (e.g., timeout)
  2. If the client also has the appropriate configuration, the server configuration will be overwritten (e.g., timeout)
    Runtime properties can be added dynamically along with the framework features, and properties that do not allow transparency are handled specially in 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);

        // Remove parameters that prohibit transfer
        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) {
        // Use version passed from provider 
        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);
        }
        // Keep timestamp of 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));
        }
        // Combining filters and listeners on Provider and Constumer
        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);
}
Service Exposure Core Class ServiceConfig
  1. Get the configuration object through reflection and place it in the map for subsequent URL construction parameters.
  2. Distinguishes between global configurations, default.prefix is added before attributes by default, and when the framework gets the parameters in the URL, it automatically tries to get the value corresponding to default.prefix if it does not exist.
  3. Handles local JVM protocol exposure.
  4. Append the monitoring report address, and the framework will perform data reporting in the interceptor.
  5. An Invoker object is created through a dynamic proxy, and an AbstractProxyInvoker instance is generated on the server side. All real method calls are delegated to the proxy, and the proxy forwards them to the service ref call.Current two agents:
    1. JavassistProxyFactory: Create a Wrapper subclass to implement the invokeMethod method in the subclass. The method body checks method names and method parameter matches for each ref method. If they match, they can be called directly, eliminating the overhead of reflection calls compared to JdkProxyFactory.
    2. JdkProxyFactory: A method that obtains a real object by reflection and then calls it.
  6. Expose the service (port open, etc.) and register the service metadata.
  7. Finally, the scenario without a registry is handled to expose services directly, and metadata registration is not required because the URL information exposed directly starts with a specific RPC protocol, not with a registry protocol.
// Example Registry URL
registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/xxx?...

// Direct Exposure URL Example
dubbo://ip:port/xxx?timeout=1000&...
// ServiceConfig
// #export()
// #doExport()
// #doExportUrls()
// #doExportUrlsFor1Protocol(protocolConfig, registryURLs)
private void doExportUrls() {
	// Get the current service corresponding registry instance
   	List<URL> registryURLs = loadRegistries(true);
   	// If a service specifies to expose multiple protocols (Dubbo, REST), expose the service in turn
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// dubbo protocol is used by default if the protocol name is empty
    String name = protocolConfig.getName();
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }
	// Construct configuration information
    Map<String, String> map = new HashMap<String, String>();
    // Set side=provider
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    // Set version
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    // Set timestamp
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    // Read additional configuration information to map for subsequent construction
    appendParameters(map, application);
    appendParameters(map, module);
    // Reading global configuration information automatically adds a prefix
    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);
    // Do not export without configuration
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        // If the configuration is not remote, export to local (remote only if the configuration is remote)
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // Export to remote if the configuration is not local (only local if the configuration is 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.isEmpty()) {
            	// Scenarios with a registry, registered directly to the registry
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // If Monitoring Center is configured, service call information will be reported
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    // For providers, enable custom proxies to generate invoker s
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }
                    // Converting to Invoker through a dynamic proxy, the registryURL stores the registry address and uses export as key to append service metadata information
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // Register service information with the registry after service exposure
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
            	// Scenarios without a registry to expose services directly
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                // Direct Exposure Services
                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    this.urls.add(url);
}

// Exposing local services
private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
    	// Set protocol to injvm for export
        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));
        // Cached to exporters, unexport can use
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}
Registry Exposure Services

When the registry exposes its services, it does the following five things in turn:

  1. Delegate specific protocols (Dubbo s) for service exposure, create NettyServer listening ports, and save service instances.
  2. Create a registry object and establish a TCP connection with the registry.
  3. Register service metadata to the registry.
  4. Subscribe to the configurators node to monitor service dynamic property change events.
  5. Service Destroy End Work, such as closing ports, anti-registering service information, etc.
// RegistryProtocol#export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // Open the port and store the service instance in the map
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    URL registryUrl = getRegistryUrl(originInvoker);
    // Create a registry instance to expose services
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
	boolean register = registeredProviderUrl.getParameter("register", true);
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
    // Register metadata after a service is exposed
    if (register) {
        register(registryUrl, registeredProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    }

    // Subscription Coverage Data
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    // Ensure that each export returns a new export instance, internally listening on the configurators node, and after unexport
    return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
Interceptor Initialization

The framework initializes the interceptor before exposing the service, and Dubbo automatically injects ProtocolListenerWrapper and ProcolFilterWrapper when loading the protocol extension point.In the ProtocolListenerWrapper implementation, callback the corresponding listener method when exposed to the service provider.ProtocolFilterWrapper calls the next-level ProtocolListenerWrapper#export method, which triggers buildInvokerChain for interceptor construction.

// ProtocolFilterWrapper---> ProtocolListenerWrapper ---> DubboProtocol
// Protocol Interceptor Extension 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);
    }
    // Construct interceptors (which filter provider-side groupings) and trigger Dubbo protocol exposure
    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;
    // Get the url (key:service.filter) Specified filter extension class (using 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);
            // Place the real Invoker (service object ref, at the end of the interceptor)
            final Invoker<T> next = last;
            // Generate an exporter for each filter in turn
            last = new Invoker<T>() {

			...
			
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                	// Each call is passed to the next interceptor
                    return filter.invoke(next, invocation);
                }
            
            ...
            
            };
        }
    }
    return last;
}

// Protocol Listener Extension 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);
    }
    // Get the url (key:exporter.listener) The specified extension class (using Dubbo SPI)
    return new ListenerExporterWrapper<T>(protocol.export(invoker),
            Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                    .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}

// Dubbo Protocol Exposure
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    URL url = invoker.getUrl();

    // Construct key based on service grouping, version, interface, and port
    String key = serviceKey(url);
    DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
    // Store exporter in a single DubboProtocol
    exporterMap.put(key, exporter);

	...
	
	// Service initial exposure creates a listening server
    openServer(url);
    optimizeSerialization(url);
    return exporter;
}

// DubboProtocol#openServer
private void openServer(URL url) {
    // Find Services
    String key = url.getAddress();
    // Clients can export services for server-only invocation
    boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
    if (isServer) {
        ExchangeServer server = serverMap.get(key);
        if (server == null) {
        	// Create a service and cache
            serverMap.put(key, createServer(url));
        } else {
            // Server supports reset, used with override
            server.reset(url);
        }
    }
}

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

Principles of Service Consumption

Summary

Overall, service consumption within the Dubbo framework is also divided into two parts:

  1. Generates an Invoker by holding a remote object instance, which is the core remote proxy object on the client side.
  2. Converts Invoker from a dynamic proxy to a dynamic proxy reference that implements a user interface.

Here Invoker hosts functions such as network connection, service invocation and retry. On the client side, it may be a remote implementation or a cluster implementation.
The entry point where the framework actually references the service is converted to a ReferenceBean in ReferenceBean#getObject, either XML or annotations, which inherits from ReferenceConfig.

Service Consumption Process

  1. Priority is given to determining whether or not you are in the same JVM. By default, Dubbo finds out the in-memory service of the injvm protocol (registering a service to injvm when exposed, placing the service instance in the in-memory map) and gets the instance call directly.
  2. Consumer metadata information is appended to the registry, which is used when an application starts subscribing to a merge of registry, service provider parameters, and so on.
  3. Processing scenarios where there is only one registry is the most common scenario on the client side, where the client starts pulling service metadata, subscribing to provider, routing, and configuration changes.
  4. Processing scenarios with multiple registries.Get services from the registry one by one and add them to the invokers list, then convert multiple Invokers into one Invoker through Cluster.

Analysis of the Elegant Shutdown Principle of Dubbo

  1. Received kill-9 process exit signal, Spring container triggers container destroy event.
  2. The provider side unregisters the service metadata information.
  3. The consumer side receives the latest address list (excluding addresses to be downloaded).
  4. The Dubbo protocol sends a readonly event message notifying consumer that the service is not available.
  5. The server waits for the task that has been executed to end and refuses to execute the new task.
  6. Finally, the provider disconnects the TCP connection from the consumer.

The registry has notified you of the latest service list, and providers are sending readonly messages because the registry push service may be delayed by the network and the client may take some time to receive and calculate the service list.When the Dubbo protocol sends a readonly message, the consumer side sets the responsive provider unavailable and the offline machine will not be called the next time the load is balanced.

After Dubbo 2.6.3, some bug s in elegant downtime have been fixed. Previous versions of elegant downtime did not complete because Spring and Dubbo both registered hooks that JVM stopped. In this scenario, two threads executing concurrently may refer to a destroyed resource, such as a Bean in Spring for tasks Dubbo is performing, but the Spring hook function has been turned offA Spring context has been added, which results in an error accessing any Spring resource.

Topics: Dubbo Spring xml jvm