You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2021/11/23 03:12:17 UTC

[dubbo] 02/05: change way to determine the port that metadata service will listen on (#9145)

This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch 3.0-metadata-refactor
in repository https://gitbox.apache.org/repos/asf/dubbo.git

commit c7d8fb1305779086ba334653dd220c5058006271
Author: ken.lj <ke...@gmail.com>
AuthorDate: Thu Nov 4 16:54:57 2021 +0800

    change way to determine the port that metadata service will listen on (#9145)
    
    * change way to determine the port that metadata service will listen on
    
    * fix compilation
    
    * fix metadata service exporter
    
    * check null
    
    * fix util import
    
    * check metadata url, do not calculate if empty
    
    * fix ut
    
    * fix ut
---
 .../dubbo/common/constants/CommonConstants.java    |   2 +
 .../org/apache/dubbo/common/utils/NetUtils.java    |   8 +-
 .../org/apache/dubbo/config/ApplicationConfig.java |  14 ++
 dubbo-config/dubbo-config-api/pom.xml              |   6 +
 .../config/deploy/DefaultApplicationDeployer.java  |  12 +-
 .../ConfigurableMetadataServiceExporter.java       | 147 ++++++++++-------
 .../metadata/MetadataServiceExporterTest.java      | 175 +++++++++++++++++++++
 .../metadata/ServiceInstanceMetadataUtils.java     |   3 +
 8 files changed, 304 insertions(+), 63 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
index d430e9b..70f3629 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
@@ -41,6 +41,8 @@ public interface CommonConstants {
 
     String METADATA_SERVICE_PORT_KEY = "metadata-service-port";
 
+    String METADATA_SERVICE_PROTOCOL_KEY = "metadata-service-protocol";
+
     String LIVENESS_PROBE_KEY = "liveness-probe";
 
     String READINESS_PROBE_KEY = "readiness-probe";
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
index 96ce1f0..a0b56a8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
@@ -102,8 +102,8 @@ public class NetUtils {
     }
 
     public synchronized static int getAvailablePort(int port) {
-        if (port < MIN_PORT) {
-            return port = MIN_PORT;
+         if (port < MIN_PORT) {
+            return MIN_PORT;
         }
         for (int i = port; i < MAX_PORT; i++) {
             if (USED_PORT.get(i)) {
@@ -111,7 +111,8 @@ public class NetUtils {
             }
             try (ServerSocket ignored = new ServerSocket(i)) {
                 USED_PORT.set(i);
-                return i;
+                port = i;
+                break;
             } catch (IOException e) {
                 // continue
             }
@@ -119,6 +120,7 @@ public class NetUtils {
         return port;
     }
 
+
     /**
      * Check the port whether is in use in os
      * @param port
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
index 0ccc484..fd92114 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
@@ -177,6 +177,11 @@ public class ApplicationConfig extends AbstractConfig {
     private String protocol;
 
     /**
+     * The protocol used for peer-to-peer metadata transmission
+     */
+    private String metadataServiceProtocol;
+
+    /**
      * Metadata Service, used in Service Discovery
      */
     private Integer metadataServicePort;
@@ -523,6 +528,15 @@ public class ApplicationConfig extends AbstractConfig {
         this.metadataServicePort = metadataServicePort;
     }
 
+    @Parameter(key = METADATA_SERVICE_PORT_KEY)
+    public String getMetadataServiceProtocol() {
+        return metadataServiceProtocol;
+    }
+
+    public void setMetadataServiceProtocol(String metadataServiceProtocol) {
+        this.metadataServiceProtocol = metadataServiceProtocol;
+    }
+
     @Parameter(key = LIVENESS_PROBE_KEY)
     public String getLivenessProbe() {
         return livenessProbe;
diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml
index b8ea09d..5b1066c 100644
--- a/dubbo-config/dubbo-config-api/pom.xml
+++ b/dubbo-config/dubbo-config-api/pom.xml
@@ -68,6 +68,12 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-triple</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.dubbo</groupId>
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
index 20dc429..6d91c69 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
@@ -601,6 +601,10 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         // ensure init and start internal module first
         prepareInternalModule();
 
+        // always start metadata service on application model start, so it's ready whenever a new module is started
+        // export MetadataService
+        exportMetadataService();
+
         if (hasPreparedApplicationInstance.get()) {
             return;
         }
@@ -618,8 +622,7 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
             if (!hasPreparedInternalModule.compareAndSet(false, true)) {
                 return;
             }
-            // export MetadataService
-            exportMetadataService();
+
             // start internal module
             ModuleDeployer internalModuleDeployer = applicationModel.getInternalModule().getDeployer();
             if (!internalModuleDeployer.isStarted()) {
@@ -711,7 +714,10 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
      * export {@link MetadataService}
      */
     private void exportMetadataService() {
-        metadataServiceExporter.export();
+        // fixme, let's disable local metadata service export at this moment
+        if (!REMOTE_METADATA_STORAGE_TYPE.equals(getMetadataType())) {
+            metadataServiceExporter.export();
+        }
     }
 
     private void unexportMetadataService() {
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
index dce7d23..89659fe 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
@@ -20,21 +20,30 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ArgumentConfig;
+import org.apache.dubbo.config.MethodConfig;
 import org.apache.dubbo.config.ProtocolConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.context.ConfigManager;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProtocolServer;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ScopeModelAware;
 
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Map;
 
 import static java.util.Collections.emptyList;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
+import static org.apache.dubbo.common.constants.CommonConstants.METADATA_SERVICE_PORT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.METADATA_SERVICE_PROTOCOL_KEY;
 
 /**
  * {@link MetadataServiceExporter} implementation based on {@link ConfigManager Dubbo configurations}, the clients
@@ -59,16 +68,15 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
 
     private volatile ServiceConfig<MetadataService> serviceConfig;
     private ApplicationModel applicationModel;
-    private AtomicBoolean exported = new AtomicBoolean(false);
 
-    public ConfigurableMetadataServiceExporter() {}
+    public ConfigurableMetadataServiceExporter() {
+    }
 
     @Override
     public void setApplicationModel(ApplicationModel applicationModel) {
         this.applicationModel = applicationModel;
     }
 
-    // will be automatically injected from ScopeBeanFactory by ScopeBeanExtensionInjector
     public void setMetadataService(MetadataService metadataService) {
         this.metadataService = metadataService;
     }
@@ -76,7 +84,7 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
     @Override
     public ConfigurableMetadataServiceExporter export() {
 
-        if (exported.compareAndSet(false, true)) {
+        if (!isExported()) {
 
             ApplicationConfig applicationConfig = getApplicationConfig();
             ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
@@ -89,10 +97,12 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
             serviceConfig.setRef(metadataService);
             serviceConfig.setGroup(applicationConfig.getName());
             serviceConfig.setVersion(metadataService.version());
-//            serviceConfig.setMethods(generateMethodConfig());
+            serviceConfig.setMethods(generateMethodConfig());
+            serviceConfig.setConnections(1);
+            serviceConfig.setExecutes(100);
 
-            // add to internal module, do export later
-            applicationModel.getInternalModule().getConfigManager().addService(serviceConfig);
+            // export
+            serviceConfig.export();
 
             if (logger.isInfoEnabled()) {
                 logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls());
@@ -109,34 +119,33 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
         return this;
     }
 
-//    /**
-//     * Generate Method Config for Service Discovery Metadata <p/>
-//     * <p>
-//     * Make {@link MetadataService} support argument callback,
-//     * used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
-//     * metadata change event
-//     *
-//     * @since 3.0
-//     */
-//    private List<MethodConfig> generateMethodConfig() {
-//        MethodConfig methodConfig = new MethodConfig();
-//        methodConfig.setName("getAndListenInstanceMetadata");
-//
-//        ArgumentConfig argumentConfig = new ArgumentConfig();
-//        argumentConfig.setIndex(1);
-//        argumentConfig.setCallback(true);
-//
-//        methodConfig.setArguments(Collections.singletonList(argumentConfig));
-//
-//        return Collections.singletonList(methodConfig);
-//    }
+    /**
+     * Generate Method Config for Service Discovery Metadata <p/>
+     * <p>
+     * Make {@link MetadataService} support argument callback,
+     * used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
+     * metadata change event
+     *
+     * @since 3.0
+     */
+    private List<MethodConfig> generateMethodConfig() {
+        MethodConfig methodConfig = new MethodConfig();
+        methodConfig.setName("getAndListenInstanceMetadata");
+
+        ArgumentConfig argumentConfig = new ArgumentConfig();
+        argumentConfig.setIndex(1);
+        argumentConfig.setCallback(true);
+
+        methodConfig.setArguments(Collections.singletonList(argumentConfig));
+
+        return Collections.singletonList(methodConfig);
+    }
 
     @Override
     public ConfigurableMetadataServiceExporter unexport() {
         if (isExported()) {
             serviceConfig.unexport();
         }
-        exported.set(false);
         return this;
     }
 
@@ -146,7 +155,7 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
     }
 
     public boolean isExported() {
-        return exported.get();
+        return serviceConfig != null && serviceConfig.isExported() && !serviceConfig.isUnexported();
     }
 
     private ApplicationConfig getApplicationConfig() {
@@ -154,45 +163,69 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
     }
 
     private ProtocolConfig generateMetadataProtocol() {
-        ProtocolConfig defaultProtocol = new ProtocolConfig();
-        Integer port = getApplicationConfig().getMetadataServicePort();
+        // protocol always defaults to dubbo if not specified
+        String specifiedProtocol = getSpecifiedProtocol();
+        // port can not being determined here if not specified
+        Integer port = getSpecifiedPort();
+
+        ProtocolConfig protocolConfig = new ProtocolConfig();
+        protocolConfig.setName(specifiedProtocol);
 
         if (port == null || port < -1) {
-            if (logger.isInfoEnabled()) {
-                logger.info("Metadata Service Port hasn't been set will use default protocol defined in protocols.");
-            }
-            List<ProtocolConfig> defaultProtocols = applicationModel.getApplicationConfigManager().getDefaultProtocols();
-
-            ProtocolConfig dubboProtocol = findDubboProtocol(defaultProtocols);
-            if (dubboProtocol != null) {
-                logger.info("Using dubbo protocol " + dubboProtocol + " to export MetadataService.");
-                return dubboProtocol;
-            } else {
-                defaultProtocol.setName(DUBBO_PROTOCOL);
-                defaultProtocol.setPort(-1);
+            try {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Metadata Service Port hasn't been set will use default protocol defined in protocols.");
+                }
+
+                Protocol protocol = applicationModel.getExtensionLoader(Protocol.class).getExtension(specifiedProtocol);
+                if (protocol != null && protocol.getServers() != null) {
+                    Iterator<ProtocolServer> it = protocol.getServers().iterator();
+                    if (it.hasNext()) {
+                        String addr = it.next().getAddress();
+                        String rawPort = addr.substring(addr.indexOf(":") + 1);
+                        logger.info("Using " + specifiedProtocol +" protocol to export MetadataService on port " + rawPort);
+                        protocolConfig.setPort(Integer.parseInt(rawPort));
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("Failed to find any valid " + specifiedProtocol + " protocol, will use random port to export metadata service.");
             }
-
         } else {
-            defaultProtocol.setName(DUBBO_PROTOCOL);
-            defaultProtocol.setPort(port);
+            protocolConfig.setPort(port);
+        }
+
+        if (protocolConfig.getPort() == null) {
+            protocolConfig.setPort(-1);
         }
 
-        logger.info("Using dubbo protocol " + defaultProtocol + " to export MetadataService.");
+        logger.info("Using dubbo protocol to export metadata service on port " + protocolConfig.getPort());
 
-        return defaultProtocol;
+        return protocolConfig;
     }
 
-    private ProtocolConfig findDubboProtocol(List<ProtocolConfig> protocolConfigs) {
-        if (CollectionUtils.isEmpty(protocolConfigs)) {
-            return null;
+    private Integer getSpecifiedPort() {
+        Integer port = getApplicationConfig().getMetadataServicePort();
+        if (port == null) {
+            Map<String, String> params = getApplicationConfig().getParameters();
+            if (CollectionUtils.isNotEmptyMap(params)) {
+                String rawPort = getApplicationConfig().getParameters().get(METADATA_SERVICE_PORT_KEY);
+                if (StringUtils.isNotEmpty(rawPort)) {
+                    port = Integer.parseInt(rawPort);
+                }
+            }
         }
+        return port;
+    }
 
-        for (ProtocolConfig protocolConfig : protocolConfigs) {
-            if (DUBBO_PROTOCOL.equalsIgnoreCase(protocolConfig.getName())) {
-                return protocolConfig;
+    private String getSpecifiedProtocol() {
+        String protocol = getApplicationConfig().getMetadataServiceProtocol();
+        if (StringUtils.isEmpty(protocol)) {
+            Map<String, String> params = getApplicationConfig().getParameters();
+            if (CollectionUtils.isNotEmptyMap(params)) {
+                protocol = getApplicationConfig().getParameters().get(METADATA_SERVICE_PROTOCOL_KEY);
             }
         }
 
-        return null;
+        return StringUtils.isNotEmpty(protocol) ? protocol : DUBBO_PROTOCOL;
     }
 }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
index 85ad013..abf99c1 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
@@ -16,20 +16,36 @@
  */
 package org.apache.dubbo.metadata;
 
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ProtocolConfig;
 import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.api.DemoService;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter;
+import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
+import org.apache.dubbo.registrycenter.RegistryCenter;
+import org.apache.dubbo.registrycenter.ZookeeperSingleRegistryCenter;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.util.List;
+
 import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -84,4 +100,163 @@ public class MetadataServiceExporterTest {
         assertFalse(exporter.isExported());
     }
 
+    /**
+     * test reuse of port started by normal service
+     */
+    @Test
+    public void testPortReuse() throws Exception {
+        DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
+        ServiceConfig<DemoService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setInterface(DemoService.class);
+        serviceConfig.setRef(new DemoServiceImpl());
+
+        ApplicationConfig applicationConfig = new ApplicationConfig("exporter-test");
+        applicationConfig.setMetadataType(DEFAULT_METADATA_STORAGE_TYPE);
+
+        providerBootstrap
+            .application(applicationConfig)
+            .registry(registryConfig)
+            .protocol(new ProtocolConfig("dubbo", 2002))
+            .service(serviceConfig);
+
+        // will start exporter
+        providerBootstrap.start();
+        ConfigurableMetadataServiceExporter exporter = (ConfigurableMetadataServiceExporter) providerBootstrap.getApplicationModel().getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+
+        try {
+            assertTrue(exporter.isExported());
+            List<URL> urls = exporter.getExportedURLs();
+            assertNotNull(urls);
+            assertEquals(2002, urls.get(0).getPort());
+            assertEquals(DUBBO_PROTOCOL, urls.get(0).getProtocol());
+        } finally {
+            providerBootstrap.stop();
+        }
+        assertFalse(exporter.isExported());
+    }
+
+    /**
+     * test user specified port and protocol
+     * @throws Exception
+     */
+    @Test
+    public void testSpecifiedPortAndProtocol() throws Exception {
+        DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
+        ServiceConfig<DemoService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setInterface(DemoService.class);
+        serviceConfig.setRef(new DemoServiceImpl());
+
+        ApplicationConfig applicationConfig = new ApplicationConfig("exporter-test");
+        applicationConfig.setMetadataType(DEFAULT_METADATA_STORAGE_TYPE);
+        applicationConfig.setMetadataServiceProtocol("tri");
+        applicationConfig.setMetadataServicePort(8089);
+
+        providerBootstrap
+            .application(applicationConfig)
+            .registry(registryConfig)
+            .protocol(new ProtocolConfig("dubbo", 2002))
+            .service(serviceConfig);
+
+        // will start exporter.export()
+        providerBootstrap.start();
+        ConfigurableMetadataServiceExporter exporter = (ConfigurableMetadataServiceExporter) providerBootstrap.getApplicationModel().getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+
+        try {
+            assertTrue(exporter.isExported());
+            List<URL> urls = exporter.getExportedURLs();
+            assertNotNull(urls);
+            assertEquals(8089, urls.get(0).getPort());
+            assertEquals("tri", urls.get(0).getProtocol());
+        } finally {
+            providerBootstrap.stop();
+        }
+        assertFalse(exporter.isExported());
+    }
+
+    @Test
+    public void testMetadataStartsBeforeNormalService() throws Exception {
+        DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
+        ServiceConfig<DemoService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setInterface(DemoService.class);
+        serviceConfig.setRef(new DemoServiceImpl());
+        serviceConfig.setDelay(1000);
+
+        ApplicationConfig applicationConfig = new ApplicationConfig("exporter-test");
+        applicationConfig.setMetadataType(DEFAULT_METADATA_STORAGE_TYPE);
+//        applicationConfig.setMetadataServiceProtocol("triple");
+//        applicationConfig.setMetadataServicePort(8089);
+
+        providerBootstrap
+            .application(applicationConfig)
+            .registry(registryConfig)
+            .protocol(new ProtocolConfig("dubbo", 2002))
+            .service(serviceConfig);
+
+        // will start exporter.export()
+        providerBootstrap.start();
+        ConfigurableMetadataServiceExporter exporter = (ConfigurableMetadataServiceExporter) providerBootstrap.getApplicationModel().getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+
+        try {
+            assertTrue(exporter.isExported());
+            List<URL> urls = exporter.getExportedURLs();
+            assertNotNull(urls);
+            assertNotEquals(2002, urls.get(0).getPort());
+            assertEquals("dubbo", urls.get(0).getProtocol());
+        } finally {
+            providerBootstrap.stop();
+        }
+        assertFalse(exporter.isExported());
+    }
+//
+//    /**
+//     * test multiple protocols
+//     * @throws Exception
+//     */
+//    @Test
+//    public void testMultiProtocols() throws Exception {
+//        DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
+//        ServiceConfig<DemoService> serviceConfig = new ServiceConfig<>();
+//        serviceConfig.setInterface(DemoService.class);
+//        serviceConfig.setRef(new DemoServiceImpl());
+//
+//        providerBootstrap
+//            .application("provider-app")
+//            .registry(registryConfig)
+//            .protocol(new ProtocolConfig("dubbo", 2002))
+//            .service(serviceConfig);
+//
+//        ConfigurableMetadataServiceExporter exporter = (ConfigurableMetadataServiceExporter) applicationModel.getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+//        MetadataService metadataService = Mockito.mock(MetadataService.class);
+//        exporter.setMetadataService(metadataService);
+//
+//        try {
+//            providerBootstrap.start();
+//            assertTrue(exporter.isExported());
+//            assertTrue(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
+//            assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
+//            assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
+//        } finally {
+//            providerBootstrap.stop();
+//        }
+//        assertFalse(exporter.isExported());
+//    }
+
+    private static ZookeeperSingleRegistryCenter registryCenter;
+    private static RegistryConfig registryConfig;
+
+    @BeforeAll
+    public static void beforeAll() {
+        FrameworkModel.destroyAll();
+        registryCenter = new ZookeeperSingleRegistryCenter(NetUtils.getAvailablePort());
+        registryCenter.startup();
+        RegistryCenter.Instance instance = registryCenter.getRegistryCenterInstance().get(0);
+        registryConfig = new RegistryConfig(String.format("%s://%s:%s",
+            instance.getType(),
+            instance.getHostname(),
+            instance.getPort()));
+
+        // pre-check threads
+        //precheckUnclosedThreads();
+    }
+
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
index 152bde3..63879a6 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
@@ -108,6 +108,9 @@ public class ServiceInstanceMetadataUtils {
     }
 
     public static String getMetadataServiceParameter(URL url) {
+        if (url == null) {
+            return "";
+        }
         url = url.removeParameter(APPLICATION_KEY);
         url = url.removeParameter(GROUP_KEY);
         url = url.removeParameter(DEPRECATED_KEY);