This source code analysis is based on Dubbo 2.7.5, from the following three aspects:

  • Basic principle analysis of service export
  • Service registration process source code analysis
  • Source code analysis of service exposure processes

1. Basic concepts

1.1 the URL

Generally speaking, the URL refers to the Uniform resource locator, which generally refers to the address on the network. Essentially, it is a string of strings containing special formats, the standard format is as follows:

protocol://username:password@host:port/path? key=value&key=valueCopy the code

Dubbo is the use of URL as the parameter type of the contract, which is called the public contract, where we all interact and communicate through URLS.

Specific parameters are as follows:

  • Protocol: Refers to various protocols in Dubbo, such as Dubbo Thrift HTTP
  • Username /password: indicates the username and password
  • Host /port: indicates the host/port
  • Path: indicates the name of the interface
  • Parameters: key value pairs

1.2 SPI mechanisms

The mechanism of SPI and Dubbo SPI mechanism have been introduced in the previous section. Here we will focus on the conditions of @Adaptive annotation for proxy, for example:

  • If the method has no parameters, an error is reported
  • The method has parameters, which can be multiple, and if one of the parameters is a URL, it can be proxied
  • The method has parameters, which can be multiple, but without parameters of the URL type, it cannot be proxied
  • This method can have more than one parameter, no URL type parameter, but if the corresponding class of these parameter types has a getUrl method (return value of type URL), then it can also be proxy

The source code of the service exposure process is used in the last proxy method, the corresponding class exists in the getUrl method.

1.3 the Exporter structure

After a service is successfully exported, its Exporter is:

  • DestroyableExporter: the outermost wrapper class that can be used to maintain an unexporter counterpart
  • ExporterChangeableWrapper: this class is mainly responsible for before unexport corresponding to the service, the service URL removed from the registry, corresponding to the service of dynamic configuration listener is removed
  • ListenerExporterWrapper: This class is responsible for removing the service export listener after unexporting the corresponding service
  • DubboExporter: This class stores the invocation object of the service and the unique identifier of the current service. When NettyServer receives a request, it can locate and export the DubboExporter of the service based on the service information in the request

1.4 the Invoker architecture

  • ProtocolFilterWrapper $CallbackRegistrationInvoker: It calls the lower Invoker, which then iterates through the filter to see if any filter implements ListenableFilter. If so, it calls the corresponding onResponse method, such as TimeoutFilter. When the underlying Invoker is called, the execution time of the service is calculated
  • ProtocolFilterWrapper$1: An Invoker consisting of a filter in a ProtocolFilterWrapper. This Invoker is used to perform a server filter, and then invokes the lower Invoker
  • RegistryProtocol $InvokerDelegate: service delegate class, which contains the corresponding providerUrl DelegateProviderMetaDataInvoker objects and services, called directly below Invoker in execution
  • ElegateProviderMetaDataInvoker: service delegate class, it contains AbstractProxyInvoker object and ServiceConfig objects, called directly below Invoker in execution
  • AbstractProxyInvoker: a proxy class for a service interface, bound to the corresponding implementation class, that uses reflection to invoke concrete methods of the service implementation class instance and get results

2. Service export principle

2.1 Source Code Entry

Since Dubbo 2.7.5 changed this section, the service export entry is no longer the export() method in ServiceBean, so let’s look for the entry now.

Let’s start with the @enableDubbo annotation. Notice that there is an @enableDubbolifecycle annotation. View the comments @ Import a DubboLifecycleComponentRegistrar class. According to Spring, it will carry out the class registration method, register a Bean, see DubboBootstrapApplicationListener class.

The class inherits OneTimeExecutionApplicationContextEventListener class, and class – this class implements the ApplicationListener class, if there is a Spring source students can learn, This is the event listening mechanism in Spring, and when Spring starts, the onApplicationEvent method of that class is executed.

@Override public void onApplicationContextEvent(ApplicationContextEvent event) { if (event instanceof ContextRefreshedEvent((ContextRefreshedEvent) event) {// start the event. } else if (event instanceof ContextClosedEvent) {ContextClosedEvent((ContextClosedEvent) event); }}Copy the code

Now look at the onContextRefreshedEvent() method, which calls dubbobootstrap.start ()

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

Look again at the dubbobootstrap.start () method

Public DubboBootstrap start() {if (started.compareAndSet(false, true)) {// Initialize (); if (logger.isInfoEnabled()) { logger.info(NAME + " is starting..." ); } // Export exportServices(); // Not only register the provider if (! IsOnlyRegisterProvider () | | hasExportedServices ()) {/ / export MetadataService exportMetadataService (); // Register an instance of the local service registerServiceInstance(); } // referServices(); if (logger.isInfoEnabled()) { logger.info(NAME + " has started."); } } return this; }Copy the code

Found the service export entry exportServices() method.

2.2 Evolution of service concepts

  • The DemoService interface represents a service, in which case the service represents the service definition
  • DemoServiceImpl indicates the implementation of DemoService. The service indicates the implementation of the service
  • DemoService+ Group + Version Indicates a service. The concepts of group and version are added to the service
  • http://192.168.1.112:80/com.tuling.DemoService said a service, the service increases the IP and Port to the machine, said the remote machine can access the URL to use com. Tuling. DemoService this service
  • http://192.168.1.112:80/com.tuling.DemoService? Timeout =3000&version=1.0.1&application= dubo-demo-provider-application Indicates a service. In this case, the service has parameters, such as timeout duration, version number, and owning application

2.3 Service Export Roadmap

  • Determine the parameters of the service
  • Determine the protocol supported by the service
  • Build the final URL of the service
  • Register the service URL with the registry
  • Depending on the protocol supported by the service, different servers are started to receive and process requests
  • Dubbo supports dynamic configuration of service parameters. Therefore, when exporting a service, you need to bind a Listener to listen for changes in service parameters. If any changes are found, you need to export the service again

3. Service registration source code analysis

It starts with the preparation of the service registration, which is preceded by obtaining all the configuration information.

3.1 Service export source code parsing

3.1.1 DubboBootstrap. Start () method

First, look at the entry method

Public DubboBootstrap start() {if (started.compareAndSet(false, true)) {// Initialize service parameters (); if (logger.isInfoEnabled()) { logger.info(NAME + " is starting..." ); } // Export exportServices(); // Not only provider register if (! isOnlyRegisterProvider() || hasExportedServices()) { // 2. Export MetadataService exportMetadataService (); //3. Register local Service instance registerServiceInstance(); } // referServices(); if (logger.isInfoEnabled()) { logger.info(NAME + " has started."); } } return this; }Copy the code

3.1.2 the initialize () method

This method performs parameter initialization and contains many small methods, which we will analyze one by one.

private void initialize() { if (! initialized.compareAndSet(false, true)) { return; } / / initialization parameter information ApplicationModel. IniFrameworkExts (); // Initialize the configuration center startConfigCenter(); / / no configuration allocation center, registry as configuration center useRegistryAsConfigCenterIfNecessary (); // Enable metadata configuration startMetadataReport(); LoadRemoteConfigs (); // Check global configuration checkGlobalConfigs(); // Initialize MetadataService initMetadataService(); / / initialize the MetadataService service export initMetadataServiceExporter (); // Initialize the event listener initEventListener(); if (logger.isInfoEnabled()) { logger.info(NAME + " has been initialized!" ); }}Copy the code

3.1.2.1 ApplicationModel. IniFrameworkExts () method

This method mainly takes the configuration center information from the ApplicationModel and finally puts it into the Map. Since there is no configuration center address here, this method is skipped.

3.1.2.2 startConfigCenter () method

This method turns on the configuration center, so the configManager.refreshall () method is executed.

    private void startConfigCenter() {
        Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();

        if (CollectionUtils.isNotEmpty(configCenters)) {
            CompositeDynamicConfiguration compositeDynamicConfiguration = new CompositeDynamicConfiguration();
            for (ConfigCenterConfig configCenter : configCenters) {
                configCenter.refresh();
                ConfigValidationUtils.validateConfigCenterConfig(configCenter);
                compositeDynamicConfiguration.addConfiguration(prepareEnvironment(configCenter));
            }
            environment.setDynamicConfiguration(compositeDynamicConfiguration);
        }
        configManager.refreshAll();
    }
Copy the code

The refreshAll() method, which calls the refresh of each class, ends with a call to the Abstractconfig.refresh method, where the service configuration parameter is called with the following priority: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration

As you can see, the -d configuration has the highest priority, followed by the configuration center, annotations, and dubo.properties.

Notice that since we have not configured a configuration center, there are still no specific parameters for the configuration center.

    public void refreshAll() {
        write(() -> {
            // refresh all configs here,
            getApplication().ifPresent(ApplicationConfig::refresh);
            getMonitor().ifPresent(MonitorConfig::refresh);
            getModule().ifPresent(ModuleConfig::refresh);

            getProtocols().forEach(ProtocolConfig::refresh);
            getRegistries().forEach(RegistryConfig::refresh);
            getProviders().forEach(ProviderConfig::refresh);
            getConsumers().forEach(ConsumerConfig::refresh);
        });

    }
Copy the code

3.1.2.4 permission levels useRegistryAsConfigCenterIfNecessary () method

If no configuration center is configured and the registry is ZooKeeper, zooKeeper is assigned to configManager as the default configuration center, and the startConfigCenter() method is executed.

private void useRegistryAsConfigCenterIfNecessary() { // we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated. if (environment.getDynamicConfiguration().isPresent()) { return; } if (CollectionUtils.isNotEmpty(configManager.getConfigCenters())) { return; } configManager.getDefaultRegistries().stream() .filter(registryConfig -> registryConfig.getUseAsConfigCenter() == null || registryConfig.getUseAsConfigCenter()) .forEach(registryConfig -> { String protocol = registryConfig.getProtocol(); String id = "config-center-" + protocol + "-" + registryConfig.getPort(); ConfigCenterConfig cc = new ConfigCenterConfig(); . / / to omit some to cc assignment code configManager. AddConfigCenter (cc); }); // Start startConfigCenter(); }Copy the code

On startMetadataReport ()

Turn on metadata, and explain metadata here. Metadata is defined as data that describes data. In service governance, such as service interface names, retries, version numbers, etc., can be understood as metadata. Before 2.7, metadata was lost in the registry, which caused a number of problems:

  • Large amount of push -> Large amount of storage data -> Large amount of network transfer -> Severe delay

Nearly half of the 30+ parameters registered on the producer side do not need to be passed as a registry; Consumer side registration 25+ parameters, only individual need to be passed to the registry. With the above theoretical analysis in mind, Dubbo 2.7 made a drastic change to publish only the data that truly belongs to service governance into the registry, greatly reducing the load on the registry.

At the same time, the full amount of metadata is published to another component: the metadata center. The metadata center currently supports Redis (recommended) and ZooKeeper.

You can see the article here, Dubbo 2.7’s three major features

Because metadata-report is not configured in this example, this method returns directly.

3.1.2.5 loadRemoteConfigs () method

For this method, I assume that if the metadata center is configured, the overall parameters of the service may change, so I will load the parameters again. However, this method is useless if the metadata center is not configured.

3.1.2.6 checkGlobalConfigs ()

Verify global configuration information

3.1.2.7 initMetadataService ()

Example Initialize the metadata center service

3.1.2.8 initMetadataServiceExporter

Example Initialize the metadata center export

3.1.2.9 initEventListener

Initialize the event listener

3.1.3 exportServices () method

After analyzing the Initialize () method, we come to the key method, the service export method.

        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();
                });
                asyncExportingFutures.add(future);
            } else {
                sc.export();
                exportedServices.add(sc);
            }
        });
    }
Copy the code

Focus on the export method

3.1.3.1 export method

public synchronized void export() { if (! shouldExport()) { return; } if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); bootstrap.init(); } // checkAndUpdateSubConfigs checkAndUpdateSubConfigs(); / / initialize the service metadata serviceMetadata. SetVersion (version); serviceMetadata.setGroup(group); serviceMetadata.setDefaultGroup(group); serviceMetadata.setServiceType(getInterfaceClass()); serviceMetadata.setServiceInterfaceName(getInterface()); serviceMetadata.setTarget(getRef()); If (shouldDelay()) {delay_export_executor.schedule (this::doExport, getDelay(), timeunit.milliseconds); } else {// Start doExport(); }}Copy the code

3.1.3.2 doExport () method

Focus on doExportUrls methods

protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!" ); } if (exported) { return; } exported = true; if (StringUtils.isEmpty(path)) { path = interfaceName; } doExportUrls(); / / dispatch a ServiceConfigExportedEvent since 2.7.4 dispatch (new ServiceConfigExportedEvent (this)); }Copy the code

3.1.3.3 doExportUrls () method

In this method, you get a list of urls and register different services based on the protocol information.

private void doExportUrls() { ServiceRepository repository = ApplicationModel.getServiceRepository(); ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass()); repository.registerProvider( getUniqueServiceName(), ref, serviceDescriptor, this, serviceMetadata ); / / get the URL List < URL > registryURLs = ConfigValidationUtils. LoadRegistries (this, true); for (ProtocolConfig protocolConfig : protocols) { String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); / / according to the different service repository of agreement registered registerService (pathKey interfaceClass); // TODO, uncomment this line once service key is unified serviceMetadata.setServiceKey(pathKey); doExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

3.1.3.4 doExportUrlsFor1Protocol

This method is too long, but here only the key, this method based on the host, port, etc., to build the URL provided by the service.

String host = findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
Copy the code

The method also provides service exposure in two cases, one local and one remote.

3.1.3.4.1 Local Exposure

Why local exposure? Because it is possible for the same JVM to reference its own services internally, exposed local services can directly consume services from the same JVM when invoked internally, avoiding network communication.

Look at the exportLocal method

Private void exportLocal(URL URL) {// Create a new URL, Injvm URL local = urlBuilder.from (URL).setProtocol(LOCAL_PROTOCOL).sethost (LOCALHOST_VALUE).setPort(0).build();  Injvmprotocol.export () Exporter<? > exporter = protocol.export( PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local); }Copy the code

An InjvmExporter object is eventually generated as a service exposure object.

3.1.3.4.2 Remote Exposure

Remote exposure adds dynamic configuration parameters to the URL, and since the URL is currently preceded by Registry:, the registryProtocol.export method is called first. This method does several things

  • Generate a listener to monitor the change of the parameter data of the service in the dynamic configuration center. Once the change is monitored, the service URL will be rewritten, and the service URL will be rewritten first when the service is exported
  • To rewrite the URL, after call doLocalExport service export (), in this method is called DubboProtocol export method to export services, after the success of the export will get a ExporterChangeableWrapper
    • The main thing to do in the Export method of The DubboProtocol is to start NettyServer and set up a series of Requesthandlers so that they in turn can process requests as they are received
  • Get the registry implementation classes, such as ZookeeperRegistry, from originInvoker
  • Simplify the rewritten service URL by removing parameters that do not need to be stored in the registry
  • The simplified service URL call ZookeeperRegistry. Registry () method to register to the registry
  • Finally ExporterChangeableWrapper encapsulation as DestroyableExporter object returns, complete service export

The service export process is complete.

3.2 Process of Starting the Service

This process is actually started during the service export process, mainly in the openServer method of the Export method of DubboProtocol.

3.2.1 openServer

Private void openServer(URL URL) {// Obtain service address String key = url.getAddress(); //client can export a service which's only for server to invoke boolean isServer = url.getParameter(IS_SERVER_KEY, true); if (isServer) { ProtocolServer server = serverMap.get(key); // double check if (server == null) {synchronized (this) {server = servermap.get (key); Servermap. put(key, createServer(URL)); if (server == null) {// Create a service and place it in the cache. } } } else { // server supports reset, use together with override server.reset(url); }}}Copy the code

3.2.2 createServer () method

Focus on these lines

try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
Copy the code

The nettytransporter.bind method is eventually called here

3.2.3 NettyTransporter. The bind () method

    @Override
    public RemotingServer bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }

Copy the code

3.2.4 NettyServer constructor

    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
        // the handler will be warped: MultiMessageHandler->HeartbeatHandler->handler
        super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url));
    }
Copy the code

3.2.5 AbstractServer constructor

public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();

        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = ANYHOST_VALUE;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
        try {
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        executor = executorRepository.createExecutorIfAbsent(url);
    }
Copy the code

The nettyServer.doopen () method is eventually called to create the Netty service.

3.3 Service Listening

This section describes only the function of service listening. If the dynamic configuration of the service is modified, the synchronization process is as follows:

  • When dynamic service configuration is modified, data in Zookeeper is modified
  • The change of the ServiceConfigurationListener will listen to the node content, Will trigger the superclass AbstractConfiguratorListener ServiceConfigurationListener process (ConfigChangeEvent event) method
  • ConfigChangeEvent represents an event that contains the event type, the event content (node content), and the name of the node that triggered the event
    • ADDED
    • MODIFIED
    • DELETED
  • When a ConfigChangeEvent event is received, it is processed according to the event type
    • The override:// protocol URL is generated based on the node content, and then a Configurator object is generated based on the URL. The object is important because it represents a Configurator, and the URL can be rewritten based on the Configurator
    • Does: delete within ServiceConfigurationListener all Configurator
  • After generating Configurator, the notifyOverrides() method is called to rewrite the service URL

4. General flow chart

The resources

  1. Dubbo Series -Dubbo Service exposure process