44 Metadata Solution in Depth Analysis How to Avoid Registration Center Data Bloat

44 Metadata Solution In-Depth Analysis How to Avoid Registration Center Data Bloat #

In the previous lesson, we discussed in detail the challenges faced by the traditional architecture of Dubbo and how the service introspection scheme introduced in Dubbo 2.7.5 addresses these challenges.

In this lesson, we will start by introducing the infrastructure of the service introspection scheme and its specific implementation. We will first introduce the definition of the basic classes related to metadata, and then discuss metadata reporting and related content of the metadata service. We will also explain how the Service ID is mapped to the Service Name.

ServiceInstance #

The Service Instance uniquely identifies a service instance and corresponds to the ServiceInstance interface in Dubbo’s source code. The specific definition of this interface is as follows:

public interface ServiceInstance extends Serializable {

    // Unique identifier

    String getId();

    // Get the Service Name that the current ServiceInstance belongs to

    String getServiceName();

    // Get the host of the current ServiceInstance

    String getHost();

    // Get the port of the current ServiceInstance

    Integer getPort();

    // Current ServiceInstance's status

    default boolean isEnabled() {

        return true;

    }

    // Check the status of the current ServiceInstance

    default boolean isHealthy() {

        return true;

    }

    // Get the metadata associated with the current ServiceInstance, which is stored in key-value format

    Map<String, String> getMetadata();

    // Calculate the hashCode value of the current ServiceInstance object

    int hashCode();

    // Compare two ServiceInstance objects

    boolean equals(Object another);

}

The DefaultServiceInstance is the only implementation of ServiceInstance. DefaultServiceInstance is a plain old Java object (POJO) class, with the following key fields:

  • id (String type): Unique identifier of the ServiceInstance.
  • serviceName (String type): Service Name associated with the ServiceInstance.
  • host (String type): host of the ServiceInstance.
  • port (Integer type): port of the ServiceInstance.
  • enabled (boolean type): status of whether the ServiceInstance is available.
  • healthy (boolean type): health status of the ServiceInstance.
  • metadata (Map type): metadata associated with the ServiceInstance.

ServiceDefinition #

Dubbo’s metadata service is no different from the Dubbo services we publish in our business. The Consumer can call a service instance’s metadata service to retrieve the metadata of all the services it publishes.

When it comes to metadata, we have to mention the ServiceDefinition class, which is used to describe the definition of a service interface. Its key fields are as follows:

  • canonicalName (String type): Fully qualified name of the interface.
  • codeSource (String type): Complete path where the service interface is located.
  • methods (List type): Detailed information about all the methods defined in the interface. MethodDefinition records the name of the method, parameter types, return value type, and all TypeDefinition involved in the method’s parameters.
  • types (List type): Detailed information about all the types involved in the interface definition, including parameters and fields of methods. If a complex type is encountered, TypeDefinition will recursively retrieve the fields inside the complex type. The dubbo-metadata-api module provides various TypeBuilder implementations corresponding to different types to create the corresponding TypeDefinition. For types without a specific TypeBuilder implementation, DefaultTypeBuilder is used.

6.png

Diagram of TypeBuilder interface implementation relationships

When a service is published, some of the data in the service URL is encapsulated into a FullServiceDefinition object and stored as metadata. FullServiceDefinition extends ServiceDefinition and extends the basic fields of ServiceDefinition with a params collection (Map type) for storing parameters from the URL.

MetadataService #

Next, let’s look at the MetadataService interface. As mentioned in the previous lesson, each ServiceInstance in Dubbo publishes a MetadataService interface for Consumer to query metadata. The following diagram shows the inheritance relationships of the MetadataService interface:

1.png

Diagram of MetadataService interface inheritance relationships

The MetadataService interface defines methods for querying the metadata published by the current ServiceInstance, as shown below:

public interface MetadataService {

    String serviceName(); // Get the name of the service to which the current ServiceInstance belongs

    default String version() {

        return VERSION; // Get the version of the current MetadataService interface

    }

    // Get all subscribed URLs of the current ServiceInstance

    default SortedSet<String> getSubscribedURLs(){

        throw new UnsupportedOperationException("This operation is not supported for consumer.");

    }

    // Get all exported URLs of the current ServiceInstance

    default SortedSet<String> getExportedURLs() {

        return getExportedURLs(ALL_SERVICE_INTERFACES);

    }

    // Get all exported URLs of the current ServiceInstance based on the service interface

    default SortedSet<String> getExportedURLs(String serviceInterface) {

        return getExportedURLs(serviceInterface, null);
    public void publishServiceDefinition(URL providerUrl) {
    
        // Get the service interface
        String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
    
        if (StringUtils.isNotEmpty(interfaceName)
            && !ProtocolUtils.isGeneric(providerUrl.getParameter(GENERIC_KEY))) {
    
            Class interfaceClass = Class.forName(interfaceName);
    
            // Create a ServiceDefinition object for the service interface
            ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.build(interfaceClass);
    
            Gson gson = new Gson();
    
            // Serialize the ServiceDefinition object to a JSON string
            String data = gson.toJson(serviceDefinition);
    
            // Record the serialized ServiceDefinition JSON string to the serviceDefinitions collection
            serviceDefinitions.put(providerUrl.getServiceKey(), data);
        }
    }
        return;

    }

}

In the implementation of RemoteWritableMetadataService, an InMemoryWritableMetadataService object is encapsulated and the publishServiceDefinition() method is overridden, the specific implementation is as follows:

public void publishServiceDefinition(URL url) {

    // Get the value of the 'side' parameter in the URL, which determines whether to call the publishProvider() or publishConsumer() method
    
    String side = url.getParameter(SIDE_KEY);

    if (PROVIDER_SIDE.equalsIgnoreCase(side)) {

        publishProvider(url);

    } else {

        publishConsumer(url);

    }

}

In the publishProvider() method, first, a FullServiceDefinition object is created based on the Provider URL, and then it is reported through the MetadataReport. The specific implementation is as follows:

private void publishProvider(URL providerUrl) throws RpcException {

    // Remove parameters such as pid, timestamp, bind.ip, bind.port, etc.
    
    providerUrl = providerUrl.removeParameters(PID_KEY, TIMESTAMP_KEY, Constants.BIND_IP_KEY, Constants.BIND_PORT_KEY, TIMESTAMP_KEY);

    // Get the service interface name
    
    String interfaceName = providerUrl.getParameter(INTERFACE_KEY);

    if (StringUtils.isNotEmpty(interfaceName)) {

        Class interfaceClass = Class.forName(interfaceName); // Reflection

        // Create a FullServiceDefinition object corresponding to the service interface, the parameters in the URL will be recorded in the params collection of FullServiceDefinition

        FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass, providerUrl.getParameters());

        // Get MetadataReport and report FullServiceDefinition

        getMetadataReport().storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(), providerUrl.getParameter(VERSION_KEY), providerUrl.getParameter(GROUP_KEY), PROVIDER_SIDE, providerUrl.getParameter(APPLICATION_KEY)), fullServiceDefinition);

        return;

    }

}

The publishConsumer() method is relatively simple: it first cleans up parameters such as pid, timestamp in the Consumer URL, and then reports the parameter collection in the Consumer URL.

However, methods such as exportURL(), subscribeURL(), getExportedURLs(), getServiceDefinition() in RemoteWritableMetadataService are empty implementations. So why is that? In fact, we can find the answer from RemoteWritableMetadataServiceDelegate, Note that RemoteWritableMetadataServiceDelegate is the remote extension implementation of the MetadataService interface.

In RemoteWritableMetadataServiceDelegate, both an InMemoryWritableMetadataService object and a RemoteWritableMetadataService object are maintained. Methods related to publish and subscribe, such as exportURL() and subscribeURL(), are delegated to both of these MetadataService objects, while methods for querying, such as getExportedURLs() and getServiceDefinition(), only call the InMemoryWritableMetadataService object for querying. Let’s take exportURL() method as an example to explain:

public boolean exportURL(URL url) {

    return doFunction(WritableMetadataService::exportURL, url);

}

private boolean doFunction(BiFunction<WritableMetadataService, URL, Boolean> func, URL url) {

    // Call the exportURL() method of both InMemoryWritableMetadataService and RemoteWritableMetadataService objects at the same time

    return func.apply(defaultWritableMetadataService, url) && func.apply(remoteWritableMetadataService, url);

}

MetadataReport #

The metadata center is an optimization introduced in Dubbo 2.7.0 version. Its main purpose is to store part of the content in the URL to the metadata center, reducing the pressure on the registry.

The data in the metadata center is only used locally and does not need to be notified to the remote end when changed. For example, when the Provider modifies the metadata, there is no need to notify the Consumer in real time. In this way, while reducing the amount of data stored in the registry, it also reduces the occurrence of frequent notification to listeners due to configuration changes, effectively reducing the pressure on the registry.

The MetadataReport interface is the bridge for interaction between Dubbo nodes and the metadata center. Here is the inheritance relationship of MetadataReport:

2.png

MetadataReport inheritance relationship diagram

Let’s first take a look at the core definition of the MetadataReport interface:

public interface MetadataReport {

    // Store Provider metadata

    void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition);

    // Store Consumer metadata

    void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap);

    // Store and remove Service metadata

    void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url);

    void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier);

    // Query exported URLs

    List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier);

    // Query subscribed data

    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls);

    List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);

    // Query ServiceDefinition
String getServiceDefinition(MetadataIdentifier metadataIdentifier);
}

After understanding the core behavior defined in the MetadataReport interface, next we will introduce the implementation in the order of its implementation: first analyze the common implementation provided by the AbstractMetadataReport abstract class, and then take the specific implementation of ZookeeperMetadataReport as an example to introduce how MetadataReport cooperates with ZooKeeper to achieve metadata reporting.

1. AbstractMetadataReport #

AbstractMetadataReport provides the common implementation for all MetadataReports, and its core fields are as follows:

private URL reportURL; // URL of the metadata center, which includes the address of the metadata center

// Local disk cache used to store the reported metadata
File file;

final Properties properties = new Properties();

// In-memory cache
final Map<MetadataIdentifier, Object> allMetadataReports = new ConcurrentHashMap<>(4);

// This thread pool is used for synchronizing local in-memory cache with file cache, and also for asynchronous reporting
private final ExecutorService reportCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveMetadataReport", true));

// Used to temporarily store the failed reports, which will be retried later
final Map<MetadataIdentifier, Object> failedReports = new ConcurrentHashMap<>(4);

boolean syncReport; // Whether to report metadata synchronously

// Records the version of the most recent metadata report, increasing monotonically
private final AtomicLong lastCacheChanged = new AtomicLong();

// Retry task for retrying failed reports
public MetadataReportRetry metadataReportRetry;

// Whether the current MetadataReport instance has been initialized
private AtomicBoolean initialized = new AtomicBoolean(false);

In the constructor of AbstractMetadataReport, it first initializes the local file cache, creates a MetadataReportRetry retry task, and starts a periodic refresh scheduled task. The specific implementation is as follows:

public AbstractMetadataReport(URL reportServerURL) {

    setUrl(reportServerURL);

    // Default local file cache
    String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-metadata-" + reportServerURL.getParameter(APPLICATION_KEY) + "-" + reportServerURL.getAddress().replaceAll(":", "-") + ".cache";
    String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
    File file = null;

    if (ConfigUtils.isNotEmpty(filename)) {
        file = new File(filename);

        if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                throw new IllegalArgumentException("...");
            }
        }

        // Delete the file if it exists and hasn't been initialized
        if (!initialized.getAndSet(true) && file.exists()) {
            file.delete();
        }
    }

    this.file = file;

    // Load the contents of the file into the properties field
    loadProperties();

    // Whether to report metadata synchronously
    syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);

    // Create the retry task
    metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
            reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));

    // Whether to report metadata periodically
    if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));

        // By default, all local metadata will be refreshed to the metadata center every day
        scheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
    }
}

In the AbstractMetadataReport.storeProviderMetadata() method, it first determines whether to report synchronously or asynchronously based on the value of the syncReport field: if it is synchronous, the report operation will be executed in the current thread; if it is asynchronous, the report operation will be executed in the reportCacheExecutor thread pool. The specific report operation is completed in the storeProviderMetadataTask() method:

private void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {

    try {
        // Store the metadata in the allMetadataReports collection
        allMetadataReports.put(providerMetadataIdentifier, serviceDefinition);

        // If it was reported failed before, there will be a record in the failedReports collection,
        // and it will be removed after a successful report
        failedReports.remove(providerMetadataIdentifier);

        // Serialize the metadata into a JSON string
        Gson gson = new Gson();

String data = gson.toJson(serviceDefinition);

// Report the serialized metadata doStoreProviderMetadata(providerMetadataIdentifier, data);

// Save the serialized metadata to the local file cache saveProperties(providerMetadataIdentifier, data, true, !syncReport);

} catch (Exception e) {

// If the report fails, record it in the failedReports collection and retry it in the metadataReportRetry task failedReports.put(providerMetadataIdentifier, serviceDefinition);

metadataReportRetry.startRetryTask();

}

We can see that the doStoreProviderMetadata() method and saveProperties() method are called here. The doStoreProviderMetadata() method is an abstract method with different implementations for different metadata center implementations. The specific implementation of this method will be analyzed later. In the saveProperties() method, the properties field is updated, the version of the local cache file is incremented, and the SaveProperties task is (synchronously/asynchronously) executed to update the content of the local cache file. The specific implementation is as follows:

private void saveProperties(MetadataIdentifier metadataIdentifier, String value, boolean add, boolean sync) {

if (file == null) {

return;

}

if (add) { // Update the metadata in the properties

properties.setProperty(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), value);

} else {

properties.remove(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));

}

// Increment the version

long version = lastCacheChanged.incrementAndGet();

if (sync) { // Synchronously update the local cache file

new SaveProperties(version).run();

} else { // Asynchronously update the local cache file

reportCacheExecutor.execute(new SaveProperties(version));

}

}

Now let’s take a look at the core method of the SaveProperties task, the doSaveProperties() method, which implements all operations to refresh the local cache file.

private void doSaveProperties(long version) {

if (version < lastCacheChanged.get()) { // Compare the current version number and the version number of this SaveProperties task

return;

}

if (file == null) { // Check if the local cache file exists

return;

}

try {

// Create a lock file

File lockfile = new File(file.getAbsolutePath() + “.lock”);

if (!lockfile.exists()) {

lockfile.createNewFile();

}

try (RandomAccessFile raf = new RandomAccessFile(lockfile, “rw”);

FileChannel channel = raf.getChannel()) {

FileLock lock = channel.tryLock(); // Lock the lock file

if (lock == null) {

throw new IOException(“Can not lock the metadataReport cache file " + file.getAbsolutePath() + “, ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties”);

}

try {

if (!file.exists()) { // Ensure that the local cache file exists

file.createNewFile();

}

// Save the metadata in the properties to the local cache file

try (FileOutputStream outputFile = new FileOutputStream(file)) {

properties.store(outputFile, “Dubbo metadataReport Cache”);

}

} finally {

lock.release(); // Release the lock on the lock file

}

}

} catch (Throwable e) {

if (version < lastCacheChanged.get()) { // Compare the version number

return;

} else { // If writing the file fails, resubmit the SaveProperties task and try again reportCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));

        }

    }

}

After understanding the core logic of refreshing the local cache file, let’s take a look at the logic of retrying on failure in AbstractMetadataReport. MetadataReportRetry maintains the following core fields:

// Thread pool for executing retry tasks
ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));

// Future object associated with the retry task
volatile ScheduledFuture retryScheduledFuture;

// Counter for tracking the number of retry tasks
final AtomicInteger retryCounter = new AtomicInteger(0);

// Interval between retry tasks
long retryPeriod;

// After no failed metadata report, the retry task will be executed 600 times before being destroyed
int retryTimesIfNonFail = 600;

// Maximum number of retry attempts, defaults to 100, i.e. give up after retrying 100 times
int retryLimit;

In the startRetryTask() method, MetadataReportRetry creates a retry task and submits it to the retryExecutor thread pool for execution (if a retry task already exists, a new task will not be created). The retry task will call the AbstractMetadataReport.retry() method to complete the re-reporting. It also checks the execution conditions such as retryLimit. The implementation is relatively simple, so I won’t show it here. If you are interested, you can refer to the source code for learning.

The specific implementation of the retry() method in AbstractMetadataReport is as follows:

public boolean retry() {

    return doHandleMetadataCollection(failedReports);

}

private boolean doHandleMetadataCollection(Map<MetadataIdentifier, Object> metadataMap) {

    if (metadataMap.isEmpty()) { // No failed metadata reports

        return true;

    }

    // Loop through the failedReports map and call storeProviderMetadata() method or storeConsumerMetadata() method to re-report metadata one by one

    Iterator<Map.Entry<MetadataIdentifier, Object>> iterable = metadataMap.entrySet().iterator();

    while (iterable.hasNext()) {

        Map.Entry<MetadataIdentifier, Object> item = iterable.next();

        if (PROVIDER_SIDE.equals(item.getKey().getSide())) {

            this.storeProviderMetadata(item.getKey(), (FullServiceDefinition) item.getValue());

        } else if (CONSUMER_SIDE.equals(item.getKey().getSide())) {

            this.storeConsumerMetadata(item.getKey(), (Map) item.getValue());

        }

    }

    return false;

}

In the constructor of AbstractMetadataReport, a “daily” level scheduled task is started based on the reportServerURL (which is the metadataReportURL later), which executes the publishAll() method. In this method, all metadata in the allMetadataReports collection is re-reported through the doHandleMetadataCollection() method. By default, this scheduled task starts at 02:00~06:00 in the morning and is executed once a day.

That’s all for the common capabilities implemented by AbstractMetadataReport for its subclasses. Other methods are delegated to the corresponding do_() methods, which are implemented in the AbstractMetadataReport subclasses.

2. BaseMetadataIdentifier #

When reporting metadata on AbstractMetadataReport, the key for the metadata is an object of type BaseMetadataIdentifier. The inheritance relationship is shown in the following diagram:

  • MetadataIdentifier contains five core fields: service interface, version, group, side, and application.
  • ServiceMetadataIdentifier contains six core fields: service interface, version, group, side, revision, and protocol.
  • SubscriberMetadataIdentifier contains five core fields: service interface, version, group, side, and revision.

3. MetadataReportFactory & MetadataReportInstance #

MetadataReportFactory is a factory for creating instances of MetadataReport. The definition is as follows:

@SPI("redis")
public interface MetadataReportFactory {
    @Adaptive({"protocol"})
    MetadataReport getMetadataReport(URL url);
}

MetadataReportFactory is an extension interface, and from the default value of the @SPI annotation, it can be seen that Dubbo uses Redis as the default implementation of the metadata center. Dubbo provides MetadataReportFactory implementations for ZooKeeper, Redis, Consul, and other metadata centers, as shown in the following diagram:

These MetadataReportFactory implementations all inherit from AbstractMetadataReportFactory. In AbstractMetadataReportFactory, it provides the ability to cache MetadataReport implementations and defines an abstract method createMetadataReport() for subclasses to implement. In addition, AbstractMetadataReportFactory implements the getMetadataReport() method of the MetadataReportFactory interface. Let’s briefly look at the implementation of this method:

public MetadataReport getMetadataReport(URL url) {
    // Remove export and refer parameters
    url = url.setPath(MetadataReport.class.getName())
             .removeParameters(EXPORT_KEY, REFER_KEY);
        ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
    
        // 设置协议,这里默认使用dubbo协议
    
        serviceConfig.setProtocol(metadataProtocol);
    
        // 设置接口
    
        serviceConfig.setInterface(MetadataService.class);
    
        // 设置实现类
    
        serviceConfig.setRef(metadataService);
    
        // 设置服务提供者信息
    
        serviceConfig.setApplication(applicationModel);
    
        serviceConfig.setRegistry(applicationModel.getApplicationConfig().getRegistry());
    
        // 暴露服务
    
        serviceConfig.export();
    
        // 将已经发布的URL添加到已导出的URL列表中
    
        exportedURLs.add(serviceConfig.getExportedUrls().get(0));
    
    }
    
    return this;
ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();

serviceConfig.setApplication(getApplicationConfig());

serviceConfig.setRegistries(getRegistries());

serviceConfig.setProtocol(generateMetadataProtocol()); // Set the Protocol (default is Dubbo)

serviceConfig.setInterface(MetadataService.class); // Set the service interface

serviceConfig.setRef(metadataService); // Set the MetadataService object

serviceConfig.setGroup(getApplicationConfig().getName()); // Set group

serviceConfig.setVersion(metadataService.version()); // Set version

// Publish the MetadataService service, the process of publishing a service by ServiceConfig has been analyzed in detail earlier, so it is not repeated here

serviceConfig.export();

this.serviceConfig = serviceConfig;

} else {

... // Log output

}

return this;

}

ServiceNameMapping #

The ServiceNameMapping interface is mainly used to implement the conversion between Service ID and Service Name. It depends on the configuration center to store and query data. The definition of the ServiceNameMapping interface is as follows:

@SPI("default")

public interface ServiceNameMapping {

    // The Service ID is composed of the service interface, group, version, and protocol, 
    // and it is mapped to the current Service Name and recorded in the configuration center
    void map(String serviceInterface, String group, String version, String protocol);

    // Query the corresponding Service Name based on the Service ID composed of the service interface, group, version, and protocol
    Set<String> get(String serviceInterface, String group, String version, String protocol);

    // Get the default extension implementation of the ServiceNameMapping interface
    static ServiceNameMapping getDefaultExtension() {
        return getExtensionLoader(ServiceNameMapping.class).getDefaultExtension();
    }

}

DynamicConfigurationServiceNameMapping is the default implementation of ServiceNameMapping, and it is also the only implementation. It depends on DynamicConfiguration to read and write the configuration center to complete the mapping between Service ID and Service Name. First, let’s take a look at the map() method of DynamicConfigurationServiceNameMapping:

public void map(String serviceInterface, String group, String version, String protocol) {

    // Skip the processing of the MetadataService interface
    if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
        return;
    }

    // Get the DynamicConfiguration object
    DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();

    // Get the Service Name from the ApplicationModel
    String key = getName();
    String content = valueOf(System.currentTimeMillis());

    execute(() -> {

        // Create the mapping relationship in the configuration center
        // Although the buildGroup() method accepts four parameters, only the serviceInterface is used here,
        // so it creates a mapping from the service interface to the Service Name.
        // You can temporarily understand the configuration center as a KV storage. The key is composed of the return value of the buildGroup() method and the Service Name, and the value is the content (that is, the timestamp)
        dynamicConfiguration.publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
    });
}

In the DynamicConfigurationServiceNameMapping.get() method, it will search for the corresponding Service Name based on the service interface, group, version, and protocol, which form the Service ID. The code is shown below:

public Set<String> get(String serviceInterface, String group, String version, String protocol) {

    // Get the DynamicConfiguration object
    DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();

    // Query the Service Name from the configuration based on the Service ID
    Set<String> serviceNames = new LinkedHashSet<>();
    execute(() -> {
        Set<String> keys = dynamicConfiguration.getConfigKeys(buildGroup(serviceInterface, group, version, protocol));
        serviceNames.addAll(keys);
    });

    // Return all the Service Names found
    return Collections.unmodifiableSet(serviceNames);
}

Summary #

In this lesson, we have focused on the implementation of metadata in the service introspection architecture.

  • First, we introduced how ServiceInstance and ServiceDefinition abstract a service instance and service definition.
  • Then we explained the definition of the metadata service interface, which is MetadataService interface and its core extension implementation.
  • Next, we analyzed the implementation of the MetadataReport interface and how it works with the metadata center to achieve metadata reporting.
  • Then we explained the implementation of the MetadataServiceExporter and the core principles of publishing metadata services.
  • Finally, we introduced the ServiceNameMapping interface and its default implementation. It realizes the mapping between Service ID and Service Name, which is an indispensable part of the service introspection architecture.