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/04 14:28:20 UTC

[dubbo] 01/02: try to refactor metadata

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 b10e5bfc28d2e23376b4c6492d0c8c4ed37d995b
Author: ken.lj <ke...@gmail.com>
AuthorDate: Thu Nov 4 19:20:57 2021 +0800

    try to refactor metadata
---
 .../dubbo/rpc/model/ModuleServiceRepository.java   |    9 +
 .../apache/dubbo/rpc/model/ServiceDescriptor.java  |   20 +
 .../dubbo/metadata/definition/MetadataTest.java    |    0
 .../dubbo/metadata/definition/MetadataUtils.java   |    0
 .../definition/ServiceDefinitionBuilderTest.java   |    1 +
 .../metadata/definition/Test3TypeBuilder.java      |    0
 .../dubbo/metadata/definition/TestTypeBuilder.java |    0
 .../definition/TypeDefinitionBuilderTest.java      |    0
 .../definition/common/ClassExtendsMap.java         |    0
 .../metadata/definition/common/ColorEnum.java      |    0
 .../metadata/definition/common/OuterClass.java     |    0
 .../common/ResultWithRawCollections.java           |    0
 .../metadata/definition/common/TestService.java    |    0
 .../metadata/definition/service/ComplexObject.java |    4 +-
 .../metadata/definition/service/DemoService.java   |    0
 .../service/annotation/MockMethodAnnotation.java   |    0
 .../service/annotation/MockMethodAnnotation2.java  |    0
 .../service/annotation/MockTypeAnnotation.java     |    0
 .../org/apache/dubbo/config/ReferenceConfig.java   |   12 +-
 .../org/apache/dubbo/config/ServiceConfig.java     |   14 +-
 .../config/deploy/DefaultApplicationDeployer.java  |   88 +-
 .../config/metadata}/MetadataReportInstance.java   |    4 +-
 .../ServiceInstanceHostPortCustomizer.java         |   83 --
 .../dubbo/config/mock/MockServiceDiscovery.java    |    8 +-
 ...terServiceDiscoveryRegistryIntegrationTest.java |    4 +-
 ...ceDiscoveryRegistryRegistryServiceListener.java |    4 +-
 .../ServiceDiscoveryRegistryInfoWrapper.java       |   10 +-
 ...RegistryCenterDubboProtocolIntegrationTest.java |   19 +-
 dubbo-metadata/dubbo-metadata-api/pom.xml          |    4 +-
 .../dubbo/metadata/AbstractServiceNameMapping.java |   80 +-
 .../org/apache/dubbo/metadata/MappingListener.java |    2 +
 .../org/apache/dubbo/metadata/MetadataInfo.java    |  118 ++-
 .../org/apache/dubbo/metadata/MetadataService.java |    5 +-
 .../apache/dubbo/metadata/ServiceNameMapping.java  |   30 +-
 .../dubbo/metadata/WritableMetadataService.java    |   64 +-
 .../definition/MethodDefinitionBuilder.java        |   78 --
 .../definition/ServiceDefinitionBuilder.java       |  126 ---
 .../metadata/definition/TypeDefinitionBuilder.java |   87 --
 .../definition/builder/ArrayTypeBuilder.java       |   56 --
 .../definition/builder/CollectionTypeBuilder.java  |   83 --
 .../definition/builder/DefaultTypeBuilder.java     |   65 --
 .../definition/builder/EnumTypeBuilder.java        |   69 --
 .../definition/builder/MapTypeBuilder.java         |   87 --
 .../metadata/definition/builder/TypeBuilder.java   |   42 -
 .../definition/model/FullServiceDefinition.java    |   43 -
 .../definition/model/MethodDefinition.java         |  117 ---
 .../definition/model/ServiceDefinition.java        |  136 ---
 .../metadata/definition/model/TypeDefinition.java  |  183 ----
 .../dubbo/metadata/definition/util/ClassUtils.java |  164 ----
 .../definition/util/JaketConfigurationUtils.java   |  102 --
 .../dubbo/metadata/report/MetadataReport.java      |    4 +
 .../AbstractAbstractWritableMetadataService.java   |   98 --
 .../metadata/AbstractServiceNameMappingTest.java   |  117 +--
 .../apache/dubbo/metadata/MetadataInfoTest.java    |   30 +-
 .../support/AbstractMetadataReportFactoryTest.java |    6 +
 .../report/support/AbstractMetadataReportTest.java |   14 +-
 .../metadata/test/JTestMetadataReport4Test.java    |    6 +
 .../store/nacos/NacosConfigServiceWrapper.java     |    4 +
 .../metadata/store/nacos/NacosMetadataReport.java  |   54 +-
 .../store/zookeeper/ZookeeperMetadataReport.java   |   28 +-
 .../dubbo/qos/command/impl/PublishMetadata.java    |   32 +-
 .../registry/RegistryScopeModelInitializer.java    |    2 -
 .../registry/client/AbstractServiceDiscovery.java  |  157 ++-
 .../client/AbstractServiceDiscoveryFactory.java    |   10 +-
 .../client/FileSystemServiceDiscovery.java         |  429 ++++----
 .../client/SelfHostMetaServiceDiscovery.java       |  308 ------
 .../dubbo/registry/client/ServiceDiscovery.java    |   82 +-
 .../registry/client/ServiceDiscoveryFactory.java   |    4 +-
 .../registry/client/ServiceDiscoveryRegistry.java  |   67 +-
 .../listener/ServiceInstancesChangedListener.java  |  148 +--
 .../metadata/MetadataServiceNameMapping.java       |   72 +-
 ...MetadataServiceURLParamsMetadataCustomizer.java |   23 +-
 .../registry/client/metadata/MetadataUtils.java    |  145 ++-
 .../ServiceInstanceMetadataCustomizer.java         |   13 +-
 .../metadata/ServiceInstanceMetadataUtils.java     |   84 +-
 .../store/InMemoryWritableMetadataService.java     |  467 ---------
 .../metadata/store/MetadataServiceDelegation.java  |  228 +++++
 .../metadata/store/RemoteMetadataServiceImpl.java  |  170 ----
 .../client/migration/model/MigrationRule.java      |   14 +-
 ...g.apache.dubbo.metadata.WritableMetadataService |    2 +-
 .../ServiceInstancesChangedListenerTest.java       | 1030 ++++++++++----------
 ...dataServiceURLParamsMetadataCustomizerTest.java |   11 +-
 .../ProtocolPortsMetadataCustomizerTest.java       |   11 +-
 .../ServiceInstanceMetadataCustomizerTest.java     |    6 +-
 .../metadata/ServiceInstanceMetadataUtilsTest.java |   49 +-
 .../store/InMemoryMetadataServiceTest.java         |   11 +-
 .../store/RemoteMetadataServiceImplTest.java       |  134 ---
 .../client/migration/model/MigrationRuleTest.java  |    8 +-
 .../dubbo/registry/consul/ConsulParameter.java     |   87 --
 .../consul/ConsulServiceDiscoveryFactory.java      |   30 -
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |    1 -
 dubbo-registry/dubbo-registry-dns/pom.xml          |   90 --
 .../org/apache/dubbo/registry/dns/DNSRegistry.java |   58 --
 .../dubbo/registry/dns/DNSRegistryFactory.java     |   34 -
 .../dubbo/registry/dns/DNSServiceDiscovery.java    |  153 ---
 .../registry/dns/DNSServiceDiscoveryFactory.java   |   28 -
 .../dubbo/registry/dns/util/DNSClientConst.java    |   47 -
 .../dubbo/registry/dns/util/DNSResolver.java       |  120 ---
 .../dubbo/registry/dns/util/ResolveResult.java     |   66 --
 .../org.apache.dubbo.registry.RegistryFactory      |    1 -
 ...g.apache.dubbo.registry.client.ServiceDiscovery |    1 -
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |    1 -
 .../registry/dns/DNSServiceDiscoveryTest.java      |  198 ----
 .../dubbo/registry/dns/util/DNSResolverTest.java   |   30 -
 .../src/test/resources/dubbo.properties            |    2 -
 dubbo-registry/dubbo-registry-kubernetes/pom.xml   |   73 --
 .../kubernetes/KubernetesMeshEnvListener.java      |  197 ----
 .../KubernetesMeshEnvListenerFactory.java          |   42 -
 .../registry/kubernetes/KubernetesRegistry.java    |   58 --
 .../kubernetes/KubernetesRegistryFactory.java      |   34 -
 .../kubernetes/KubernetesServiceDiscovery.java     |  399 --------
 .../KubernetesServiceDiscoveryFactory.java         |   28 -
 .../dubbo/registry/kubernetes/MeshConstant.java    |   43 -
 .../kubernetes/util/KubernetesClientConst.java     |   76 --
 .../kubernetes/util/KubernetesConfigUtils.java     |  113 ---
 .../org.apache.dubbo.registry.RegistryFactory      |    1 -
 ...g.apache.dubbo.registry.client.ServiceDiscovery |    1 -
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |    1 -
 ...luster.router.mesh.route.MeshEnvListenerFactory |    1 -
 .../kubernetes/KubernetesServiceDiscoveryTest.java |  198 ----
 .../org.mockito.plugins.MockMaker                  |    1 -
 .../multicast/MulticastServiceDiscovery.java       |    8 +-
 .../MulticastServiceDiscoveryFactory.java          |    8 +-
 .../multiple/MultipleServiceDiscovery.java         |   45 +-
 .../registry/nacos/NacosServiceDiscovery.java      |   17 +-
 .../nacos/NacosServiceDiscoveryFactory.java        |    2 +-
 .../nacos/util/NacosNamingServiceUtils.java        |   21 +-
 dubbo-registry/dubbo-registry-xds/pom.xml          |  115 ---
 .../dubbo/registry/xds/XdsCertificateSigner.java   |   52 -
 .../java/org/apache/dubbo/registry/xds/XdsEnv.java |   21 -
 .../org/apache/dubbo/registry/xds/XdsRegistry.java |   58 --
 .../dubbo/registry/xds/XdsRegistryFactory.java     |   34 -
 .../dubbo/registry/xds/XdsServiceDiscovery.java    |   89 --
 .../registry/xds/XdsServiceDiscoveryFactory.java   |   38 -
 .../xds/istio/IstioCitadelCertificateSigner.java   |  249 -----
 .../dubbo/registry/xds/istio/IstioConstant.java    |   53 -
 .../apache/dubbo/registry/xds/istio/IstioEnv.java  |  121 ---
 .../dubbo/registry/xds/util/NodeBuilder.java       |   29 -
 .../dubbo/registry/xds/util/PilotExchanger.java    |  163 ----
 .../apache/dubbo/registry/xds/util/XdsChannel.java |   80 --
 .../xds/util/protocol/AbstractProtocol.java        |  242 -----
 .../registry/xds/util/protocol/DeltaResource.java  |   32 -
 .../registry/xds/util/protocol/XdsProtocol.java    |   48 -
 .../xds/util/protocol/delta/DeltaEndpoint.java     |   51 -
 .../xds/util/protocol/delta/DeltaListener.java     |   49 -
 .../xds/util/protocol/delta/DeltaRoute.java        |   47 -
 .../xds/util/protocol/impl/EdsProtocol.java        |   93 --
 .../xds/util/protocol/impl/LdsProtocol.java        |  106 --
 .../xds/util/protocol/impl/RdsProtocol.java        |   94 --
 .../xds/util/protocol/message/Endpoint.java        |   88 --
 .../xds/util/protocol/message/EndpointResult.java  |   62 --
 .../xds/util/protocol/message/ListenerResult.java  |   70 --
 .../xds/util/protocol/message/RouteResult.java     |   73 --
 .../dubbo-registry-xds/src/main/proto/ca.proto     |   57 --
 .../org.apache.dubbo.registry.RegistryFactory      |    1 -
 ...g.apache.dubbo.registry.client.ServiceDiscovery |    1 -
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |    1 -
 ....apache.dubbo.registry.xds.XdsCertificateSigner |    1 -
 .../zookeeper/ZookeeperServiceDiscovery.java       |   27 +-
 .../ZookeeperServiceDiscoveryFactory.java          |    2 +-
 .../zookeeper/util/CuratorFrameworkUtils.java      |   12 +-
 dubbo-registry/pom.xml                             |    3 -
 162 files changed, 1888 insertions(+), 8787 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleServiceRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleServiceRepository.java
index 9710eb55..5b8a58b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleServiceRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ModuleServiceRepository.java
@@ -186,6 +186,15 @@ public class ModuleServiceRepository {
         return Collections.unmodifiableList(serviceDescriptors);
     }
 
+    public ServiceDescriptor getService(String serviceName) {
+        // TODO, may need to distinguish service by class loader.
+        List<ServiceDescriptor> serviceDescriptors = services.get(serviceName);
+        if (CollectionUtils.isEmpty(serviceDescriptors)) {
+            return null;
+        }
+        return serviceDescriptors.get(0);
+    }
+
     public ServiceDescriptor lookupService(String interfaceName) {
         if (services.containsKey(interfaceName)) {
             List<ServiceDescriptor> serviceDescriptors = services.get(interfaceName);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ServiceDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ServiceDescriptor.java
index c063ec5..b3ac2f4 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ServiceDescriptor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ServiceDescriptor.java
@@ -17,16 +17,21 @@
 package org.apache.dubbo.rpc.model;
 
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
 
 /**
  * ServiceModel and ServiceMetadata are to some extend duplicated with each other. We should merge them in the future.
@@ -37,11 +42,18 @@ public class ServiceDescriptor {
     // to accelerate search
     private final Map<String, List<MethodDescriptor>> methods = new HashMap<>();
     private final Map<String, Map<String, MethodDescriptor>> descToMethods = new HashMap<>();
+    private ConcurrentNavigableMap<String, FullServiceDefinition> serviceDefinitions = new ConcurrentSkipListMap<>();
 
     public ServiceDescriptor(Class<?> interfaceClass) {
         this.serviceInterfaceClass = interfaceClass;
         this.serviceName = interfaceClass.getName();
         initMethods();
+        initServiceDefinition(interfaceClass);
+    }
+
+    private void initServiceDefinition(Class<?> interfaceClass) {
+        FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass, Collections.emptyMap());
+        serviceDefinitions.put(serviceName, fullServiceDefinition);
     }
 
     private void initMethods() {
@@ -116,6 +128,14 @@ public class ServiceDescriptor {
         return methods.get(methodName);
     }
 
+    public ConcurrentNavigableMap<String, FullServiceDefinition> getServiceDefinitions() {
+        return serviceDefinitions;
+    }
+
+    public FullServiceDefinition getServiceDefinition(String serviceName) {
+        return serviceDefinitions.get(serviceName);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/MetadataTest.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/MetadataTest.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/MetadataTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/MetadataTest.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/MetadataUtils.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/MetadataUtils.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/MetadataUtils.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/MetadataUtils.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java
similarity index 99%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java
index 91d65bf..a6bb55b 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilderTest.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.metadata.definition.model.MethodDefinition;
 import org.apache.dubbo.metadata.definition.model.TypeDefinition;
 import org.apache.dubbo.metadata.definition.service.ComplexObject;
 import org.apache.dubbo.metadata.definition.service.DemoService;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ClassExtendsMap.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ClassExtendsMap.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ClassExtendsMap.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ClassExtendsMap.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ColorEnum.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ColorEnum.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ColorEnum.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ColorEnum.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/OuterClass.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/OuterClass.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/OuterClass.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/OuterClass.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ResultWithRawCollections.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ResultWithRawCollections.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/ResultWithRawCollections.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/ResultWithRawCollections.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/TestService.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/TestService.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/common/TestService.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/common/TestService.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java
similarity index 98%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java
index 9371e8a..b984dee 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/ComplexObject.java
@@ -32,8 +32,8 @@ public class ComplexObject {
     public ComplexObject() {
     }
 
-    public ComplexObject(String var1, int var2, long l, String[] var3, List<Integer> var4, ComplexObject.TestEnum testEnum) {
-        this.setInnerObject(new ComplexObject.InnerObject());
+    public ComplexObject(String var1, int var2, long l, String[] var3, List<Integer> var4, TestEnum testEnum) {
+        this.setInnerObject(new InnerObject());
         this.getInnerObject().setInnerA(var1);
         this.getInnerObject().setInnerB(var2);
         this.setIntList(var4);
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/DemoService.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/DemoService.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/DemoService.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/DemoService.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation2.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation2.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation2.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockMethodAnnotation2.java
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockTypeAnnotation.java b/dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockTypeAnnotation.java
similarity index 100%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockTypeAnnotation.java
rename to dubbo-common/src/test/java/org/apache/dubbo/metadata/definition/service/annotation/MockTypeAnnotation.java
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
index 51079e4..11d38e1 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
@@ -31,7 +31,7 @@ import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
-import org.apache.dubbo.registry.client.metadata.MetadataUtils;
+import org.apache.dubbo.metadata.ServiceNameMapping;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.ProxyFactory;
@@ -247,12 +247,14 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
 
         //init serviceMetadata
         initServiceMetadata(consumer);
+
         serviceMetadata.setServiceType(getServiceInterfaceClass());
         // TODO, uncomment this line once service key is unified
         serviceMetadata.setServiceKey(URL.buildKey(interfaceName, group, version));
 
         Map<String, String> referenceParameters = appendConfig();
-
+        // init service-application mapping
+        initServiceAppsMapping(referenceParameters);
 
         ModuleServiceRepository repository = getScopeModel().getServiceRepository();
         ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
@@ -274,6 +276,11 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
         checkInvokerAvailable();
     }
 
+    private void initServiceAppsMapping(Map<String, String> referenceParameters) {
+        ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(getScopeModel());
+        serviceNameMapping.getServices(new ServiceConfigURL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceName, referenceParameters));
+    }
+
     /**
      * convert and aggregate async method info
      *
@@ -385,7 +392,6 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
             referenceParameters.get(INTERFACE_KEY), referenceParameters);
         consumerUrl = consumerUrl.setScopeModel(getScopeModel());
         consumerUrl = consumerUrl.setServiceModel(consumerModel);
-        MetadataUtils.publishServiceDefinition(consumerUrl);
 
         // create service proxy
         return (T) proxyFactory.getProxy(invoker, ProtocolUtils.isGeneric(generic));
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
index 60b7fc8..25da572 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
@@ -32,9 +32,7 @@ import org.apache.dubbo.config.annotation.Service;
 import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
 import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
-import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.ServiceNameMapping;
-import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
@@ -133,7 +131,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
     private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
 
     private final List<ServiceListener> serviceListeners = new ArrayList<>();
-    private WritableMetadataService localMetadataService;
+    ModuleServiceRepository repository = getScopeModel().getServiceRepository();
 
     public ServiceConfig() {
     }
@@ -147,7 +145,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
         super.postProcessAfterScopeModelChanged(oldScopeModel, newScopeModel);
         protocolSPI = this.getExtensionLoader(Protocol.class).getAdaptiveExtension();
         proxyFactory = this.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
-        localMetadataService = this.getScopeModel().getDefaultExtension(WritableMetadataService.class);
     }
 
     @Override
@@ -252,6 +249,8 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
                     boolean succeeded = serviceNameMapping.map(url);
                     if (succeeded) {
                         logger.info("Successfully registered interface application mapping for service " + url.getServiceKey());
+                    } else {
+                        logger.error("Failed register interface application mapping for service " + url.getServiceKey());
                     }
                 } catch (Exception e) {
                     logger.error("Failed register interface application mapping for service " + url.getServiceKey(), e);
@@ -357,7 +356,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
 
     @SuppressWarnings({"unchecked", "rawtypes"})
     private void doExportUrls() {
-        ModuleServiceRepository repository = getScopeModel().getServiceRepository();
         ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
         providerModel = new ProviderModel(getUniqueServiceName(),
             ref,
@@ -561,7 +559,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
             // export to remote if the config is not local (export to local only when config is local)
             if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                 url = exportRemote(url, registryURLs);
-                MetadataUtils.publishServiceDefinition(url);
+                MetadataUtils.publishServiceDefinition(repository.getService(interfaceName), getApplicationModel());
             }
 
         }
@@ -605,10 +603,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
 
         } else {
 
-            if (MetadataService.class.getName().equals(url.getServiceInterface())) {
-                localMetadataService.setMetadataServiceURL(url);
-            }
-
             if (logger.isInfoEnabled()) {
                 logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
             }
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 fd95e3b..a9f3603 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
@@ -49,11 +49,7 @@ import org.apache.dubbo.metadata.MetadataServiceExporter;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.metadata.report.MetadataReportFactory;
 import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ModuleModel;
@@ -83,8 +79,6 @@ import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
 import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_PUBLISH_DELAY;
 import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PUBLISH_DELAY_KEY;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.calInstanceRevision;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.setMetadataStorageType;
 import static org.apache.dubbo.remoting.Constants.CLIENT_KEY;
 
 /**
@@ -104,8 +98,6 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
 
     private final ExecutorRepository executorRepository;
 
-    private volatile ServiceInstance serviceInstance;
-
     private AtomicBoolean hasPreparedApplicationInstance = new AtomicBoolean(false);
     private AtomicBoolean hasPreparedInternalModule = new AtomicBoolean(false);
 
@@ -739,19 +731,13 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
         }
     }
 
-    private void registerServiceInstance() {
-        if (isRegisteredServiceInstance()) {
-            return;
-        }
+    private volatile boolean registered;
 
-        ApplicationConfig application = getApplication();
-        String serviceName = application.getName();
-        ServiceInstance serviceInstance = createServiceInstance(serviceName);
-        boolean registered = true;
+    private void registerServiceInstance() {
         try {
-            ServiceInstanceMetadataUtils.registerMetadataAndInstance(serviceInstance);
+            registered = true;
+            ServiceInstanceMetadataUtils.registerMetadataAndInstance(applicationModel);
         } catch (Exception e) {
-            registered = false;
             logger.error("Register instance error", e);
         }
         if (registered) {
@@ -762,79 +748,25 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
                 if (applicationModel.isDestroyed()) {
                     return;
                 }
-
-                InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(applicationModel);
-                localMetadataService.blockUntilUpdated();
                 try {
                     if (!applicationModel.isDestroyed()) {
-                        ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+                        ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel);
                     }
                 } catch (Exception e) {
                     if (!applicationModel.isDestroyed()) {
                         logger.error("Refresh instance and metadata error", e);
                     }
-                } finally {
-                    localMetadataService.releaseBlock();
                 }
             }, 0, ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit.MILLISECONDS);
         }
     }
 
-    private boolean isRegisteredServiceInstance() {
-        return this.serviceInstance != null;
-    }
-
-    private void doRegisterServiceInstance(ServiceInstance serviceInstance) {
-        // register instance only when at least one service is exported.
-        if (serviceInstance.getPort() > 0) {
-            publishMetadataToRemote(serviceInstance);
-            logger.info("Start registering instance address to registry.");
-            RegistryManager.getInstance(applicationModel).getServiceDiscoveries().forEach(serviceDiscovery ->
-            {
-                ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);
-                calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);
-                if (logger.isDebugEnabled()) {
-                    logger.info("Start registering instance address to registry" + serviceDiscovery.getUrl() + ", instance " + serviceInstanceForRegistry);
-                }
-                // register metadata
-                serviceDiscovery.register(serviceInstanceForRegistry);
-            });
-        }
-    }
-
-    private void publishMetadataToRemote(ServiceInstance serviceInstance) {
-//        InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService)WritableMetadataService.getDefaultExtension();
-//        localMetadataService.blockUntilUpdated();
-        if (logger.isInfoEnabled()) {
-            logger.info("Start publishing metadata to remote center, this only makes sense for applications enabled remote metadata center.");
-        }
-        RemoteMetadataServiceImpl remoteMetadataService = applicationModel.getBeanFactory().getBean(RemoteMetadataServiceImpl.class);
-        remoteMetadataService.publishMetadata(serviceInstance.getServiceName());
-    }
-
     private void unregisterServiceInstance() {
-        if (isRegisteredServiceInstance()) {
-            RegistryManager.getInstance(applicationModel).getServiceDiscoveries().forEach(serviceDiscovery -> {
-                try {
-                    serviceDiscovery.unregister(serviceInstance);
-                } catch (Exception ignored) {
-                    // ignored
-                }
-            });
+        if (registered) {
+            ServiceInstanceMetadataUtils.unregisterMetadataAndInstance(applicationModel);
         }
     }
 
-    private ServiceInstance createServiceInstance(String serviceName) {
-        this.serviceInstance = new DefaultServiceInstance(serviceName, applicationModel);
-        setMetadataStorageType(serviceInstance, getMetadataType());
-        ServiceInstanceMetadataUtils.customizeInstance(this.serviceInstance);
-        return this.serviceInstance;
-    }
-
-    public ServiceInstance getServiceInstance() {
-        return serviceInstance;
-    }
-
     @Override
     public void stop() {
         applicationModel.destroy();
@@ -1001,11 +933,11 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
             }
             // refresh metadata
             try {
-                if (serviceInstance != null) {
-                    ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+                if (registered) {
+                    ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel);
                 }
             } catch (Exception e) {
-                logger.error("refresh metadata failed: " + e.getMessage(), e);
+                logger.error("refresh meta and instance failed: " + e.getMessage(), e);
             }
             // shutdown export/refer executor after started
             executorRepository.shutdownServiceExportExecutor();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataReportInstance.java
similarity index 95%
rename from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java
rename to dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataReportInstance.java
index ee40342..75be9cc 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataReportInstance.java
@@ -14,11 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.report;
+package org.apache.dubbo.config.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.config.MetadataReportConfig;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportFactory;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.HashMap;
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstanceHostPortCustomizer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstanceHostPortCustomizer.java
deleted file mode 100644
index edccd63..0000000
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstanceHostPortCustomizer.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.config.metadata;
-
-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.metadata.WritableMetadataService;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-
-import java.util.Set;
-
-/**
- * The {@link ServiceInstanceCustomizer} to customize the {@link ServiceInstance#getPort() port} of service instance.
- *
- * @since 2.7.5
- */
-public class ServiceInstanceHostPortCustomizer implements ServiceInstanceCustomizer {
-    private static final Logger logger = LoggerFactory.getLogger(ServiceInstanceHostPortCustomizer.class);
-    
-
-    @Override
-    public void customize(ServiceInstance serviceInstance) {
-
-        if (serviceInstance.getPort() > 0) {
-            return;
-        }
-
-        WritableMetadataService writableMetadataService = WritableMetadataService.getDefaultExtension(serviceInstance.getApplicationModel());
-
-        String host = null;
-        int port = -1;
-        Set<URL> urls = writableMetadataService.getExportedServiceURLs();
-        if (CollectionUtils.isNotEmpty(urls)) {
-            ApplicationModel applicationModel = serviceInstance.getApplicationModel();
-            String preferredProtocol = applicationModel.getCurrentConfig().getProtocol();
-            if (preferredProtocol != null) {
-                for (URL exportedURL : urls) {
-                    if (preferredProtocol.equals(exportedURL.getProtocol())) {
-                        host = exportedURL.getHost();
-                        port = exportedURL.getPort();
-                        break;
-                    }
-                }
-                
-                if (host == null || port == -1) {
-                    logger.warn("The default preferredProtocol \"" + preferredProtocol + "\" is not found, fall back to the strategy that pick the first found protocol. Please try to modify the config of dubbo.application.protocol");
-                    URL url = urls.iterator().next();
-                    host = url.getHost();
-                    port = url.getPort();
-                }
-            } else {
-                URL url = urls.iterator().next();
-                host = url.getHost();
-                port = url.getPort();
-            }
-            
-            if (serviceInstance instanceof DefaultServiceInstance) {
-                DefaultServiceInstance instance = (DefaultServiceInstance) serviceInstance;
-                instance.setHost(host);
-                instance.setPort(port);
-            }
-        }
-    }
-}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java
index 3044dcb..fcf827b 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java
@@ -26,6 +26,10 @@ import java.util.Set;
 public class MockServiceDiscovery extends AbstractServiceDiscovery {
     private URL registryURL;
 
+    public MockServiceDiscovery(String serviceName) {
+        super(serviceName);
+    }
+
     @Override
     public void doInitialize(URL registryURL) throws Exception {
         this.registryURL = registryURL;
@@ -42,12 +46,12 @@ public class MockServiceDiscovery extends AbstractServiceDiscovery {
     }
 
     @Override
-    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
+    public void doUpdate() throws RuntimeException {
         this.serviceInstance = serviceInstance;
     }
 
     @Override
-    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
+    public void doUnregister() throws RuntimeException {
         this.serviceInstance = null;
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
index 2be4305..8d2159f 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
@@ -26,7 +26,7 @@ import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.integration.IntegrationTest;
 import org.apache.dubbo.registry.RegistryServiceListener;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 import org.apache.dubbo.registrycenter.RegistryCenter;
 import org.apache.dubbo.registrycenter.ZookeeperMultipleRegistryCenter;
 
@@ -179,7 +179,7 @@ public class MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest imple
             Assertions.assertTrue(serviceDiscoveryRegistryInfoWrapper.isRegistered());
             // check if it's subscribed
             Assertions.assertFalse(serviceDiscoveryRegistryInfoWrapper.isSubscribed());
-            InMemoryWritableMetadataService inMemoryWritableMetadataService = serviceDiscoveryRegistryInfoWrapper.getInMemoryWritableMetadataService();
+            MetadataServiceDelegation inMemoryWritableMetadataService = serviceDiscoveryRegistryInfoWrapper.getInMemoryWritableMetadataService();
             // check if the count of exported urls is right or not
             Assertions.assertEquals(inMemoryWritableMetadataService.getExportedURLs().size(), 1);
             // check the exported url is right or not.
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryRegistryServiceListener.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryRegistryServiceListener.java
index 00d45f4..4b5676d 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryRegistryServiceListener.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryRegistryServiceListener.java
@@ -22,7 +22,7 @@ import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.RegistryServiceListener;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 
 import static org.apache.dubbo.integration.Constants.MULTIPLE_CONFIG_CENTER_SERVICE_DISCOVERY_REGISTRY;
 
@@ -43,7 +43,7 @@ public class MultipleRegistryCenterServiceDiscoveryRegistryRegistryServiceListen
         serviceDiscoveryRegistryInfoWrapper.setHost(host);
         serviceDiscoveryRegistryInfoWrapper.setPort(port);
         serviceDiscoveryRegistryInfoWrapper.setServiceDiscoveryRegistry(serviceDiscoveryRegistry);
-        serviceDiscoveryRegistryInfoWrapper.setInMemoryWritableMetadataService((InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(url.getScopeModel()));
+        serviceDiscoveryRegistryInfoWrapper.setInMemoryWritableMetadataService((MetadataServiceDelegation) WritableMetadataService.getDefaultExtension(url.getScopeModel()));
         serviceDiscoveryRegistryInfoWrapper.setRegistered(true);
         return serviceDiscoveryRegistryInfoWrapper;
     }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
index 583eec9..4df35d3 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
@@ -17,16 +17,16 @@
 package org.apache.dubbo.integration.multiple.servicediscoveryregistry;
 
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 
 /**
  * The instance to wrap {@link org.apache.dubbo.registry.client.ServiceDiscoveryRegistry}
- * and {@link org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService}
+ * and {@link MetadataServiceDelegation}
  */
 public class ServiceDiscoveryRegistryInfoWrapper {
 
     private ServiceDiscoveryRegistry serviceDiscoveryRegistry;
-    private InMemoryWritableMetadataService inMemoryWritableMetadataService;
+    private MetadataServiceDelegation inMemoryWritableMetadataService;
     private boolean registered;
     private boolean subscribed;
     private String host;
@@ -40,11 +40,11 @@ public class ServiceDiscoveryRegistryInfoWrapper {
         this.serviceDiscoveryRegistry = serviceDiscoveryRegistry;
     }
 
-    public InMemoryWritableMetadataService getInMemoryWritableMetadataService() {
+    public MetadataServiceDelegation getInMemoryWritableMetadataService() {
         return inMemoryWritableMetadataService;
     }
 
-    public void setInMemoryWritableMetadataService(InMemoryWritableMetadataService inMemoryWritableMetadataService) {
+    public void setInMemoryWritableMetadataService(MetadataServiceDelegation inMemoryWritableMetadataService) {
         this.inMemoryWritableMetadataService = inMemoryWritableMetadataService;
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
index 862a553..e4e8e5a 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
@@ -34,7 +34,7 @@ import org.apache.dubbo.registry.ListenerRegistryWrapper;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 import org.apache.dubbo.registry.client.migration.MigrationInvoker;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery;
@@ -42,6 +42,7 @@ import org.apache.dubbo.registrycenter.RegistryCenter;
 import org.apache.dubbo.registrycenter.ZookeeperSingleRegistryCenter;
 import org.apache.dubbo.rpc.cluster.Directory;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -237,24 +238,18 @@ public class SingleRegistryCenterDubboProtocolIntegrationTest implements Integra
         Assertions.assertTrue(services.contains(PROVIDER_APPLICATION_NAME));
 
         // obtain InMemoryWritableMetadataService instance
-        InMemoryWritableMetadataService inMemoryWritableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(serviceConfig.getScopeModel());
+        MetadataServiceDelegation inMemoryWritableMetadataService = (MetadataServiceDelegation) WritableMetadataService.getDefaultExtension(serviceConfig.getScopeModel());
         // Exported url is right or not in InMemoryWritableMetadataService
         Assertions.assertEquals(inMemoryWritableMetadataService.getExportedURLs().size(), 1);
         // MetadataInfo exists or not in InMemoryWritableMetadataService
-        Assertions.assertFalse(inMemoryWritableMetadataService.getMetadataInfos().values().isEmpty());
-        // get MetadataInfo
-        MetadataInfo metadataInfo = inMemoryWritableMetadataService.getMetadataInfos().get("default");
-        // MetadataInfo exists or not in InMemoryWritableMetadataService
-        Assertions.assertNotNull(metadataInfo);
-        // MetadataInfo has reported or not
-        Assertions.assertFalse(metadataInfo.hasReported());
+        Assertions.assertFalse(inMemoryWritableMetadataService.getMetadataInfos().isEmpty());
         // MetadataInfo has reported or not has service or not
-        Assertions.assertFalse(metadataInfo.getServices().isEmpty());
+        Assertions.assertFalse(inMemoryWritableMetadataService.getMetadataInfos().get(0).getServices().isEmpty());
         // MetadataInfo has reported or not has service or not
-        Assertions.assertEquals(metadataInfo.getServices().size(), 1);
+        Assertions.assertEquals(inMemoryWritableMetadataService.getMetadataInfos().get(0).getServices().size(), 1);
         // obtain the service's key
         String key = SingleRegistryCenterIntegrationService.class.getName() + ":" + PROTOCOL_NAME;
-        MetadataInfo.ServiceInfo serviceInfo = metadataInfo.getServices().get(key);
+        MetadataInfo.ServiceInfo serviceInfo = inMemoryWritableMetadataService.getMetadataInfos().get(0).getServices().get(key);
         // MetadataInfo's service exists or not
         Assertions.assertNotNull(serviceInfo);
         // The name of MetadataInfo's service is right or not
diff --git a/dubbo-metadata/dubbo-metadata-api/pom.xml b/dubbo-metadata/dubbo-metadata-api/pom.xml
index 9caf31e..03579d5 100644
--- a/dubbo-metadata/dubbo-metadata-api/pom.xml
+++ b/dubbo-metadata/dubbo-metadata-api/pom.xml
@@ -44,7 +44,7 @@
             <artifactId>dubbo-cluster</artifactId>
             <version>${project.parent.version}</version>
         </dependency>
-        
+
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
@@ -87,4 +87,4 @@
 
     </dependencies>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
index 72a7985..7ad3fa5 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
@@ -23,27 +23,29 @@ import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ScopeModelAware;
 
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
 
 import static java.util.Collections.emptySet;
 import static java.util.Collections.unmodifiableSet;
 import static java.util.stream.Collectors.toSet;
 import static java.util.stream.Stream.of;
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDED_BY;
-import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
 import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
 public abstract class AbstractServiceNameMapping implements ServiceNameMapping, ScopeModelAware {
     protected final Logger logger = LoggerFactory.getLogger(getClass());
     protected ApplicationModel applicationModel;
-    private WritableMetadataService metadataService;
+    private final Map<String, Set<String>> serviceToAppsMapping = new ConcurrentHashMap<>();
+    private final Map<String, MappingListener> mappingListeners = new ConcurrentHashMap<>();
 
     @Override
     public void setApplicationModel(ApplicationModel applicationModel) {
         this.applicationModel = applicationModel;
-        metadataService = WritableMetadataService.getDefaultExtension(applicationModel);
     }
 
     /**
@@ -60,6 +62,8 @@ public abstract class AbstractServiceNameMapping implements ServiceNameMapping,
      */
     abstract public Set<String> getAndListen(URL url, MappingListener mappingListener);
 
+    abstract protected void removeListener(URL url, MappingListener mappingListener);
+
     @Override
     public Set<String> getServices(URL subscribedURL) {
         Set<String> subscribedServices = new TreeSet<>();
@@ -71,7 +75,7 @@ public abstract class AbstractServiceNameMapping implements ServiceNameMapping,
         }
 
         if (isEmpty(subscribedServices)) {
-            Set<String> cachedServices = metadataService.getCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL));
+            Set<String> cachedServices = this.getCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL));
             if(!isEmpty(cachedServices)) {
                 subscribedServices.addAll(cachedServices);
             }
@@ -81,35 +85,43 @@ public abstract class AbstractServiceNameMapping implements ServiceNameMapping,
             Set<String> mappedServices = get(subscribedURL);
             logger.info(subscribedURL.getServiceInterface() + " mapping to " + mappedServices + " instructed by remote metadata center.");
             subscribedServices.addAll(mappedServices);
-            metadataService.putCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL), subscribedServices);
         }
+
+        this.putCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL), subscribedServices);
+
         return subscribedServices;
     }
 
     @Override
-    public Set<String> getAndListenServices(URL registryURL, URL subscribedURL, MappingListener listener) {
-        Set<String> subscribedServices = new TreeSet<>();
-        Set<String> globalConfiguredSubscribingServices = parseServices(registryURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY));
+    public Set<String> getAndListen(URL registryURL, URL subscribedURL, MappingListener listener) {
+        // use previously cached services.
+        Set<String> cachedServices = this.getCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL));
+
+        // Asynchronously register listener in case previous cache does not exist or cache updating in the future.
+        ExecutorService executorService = applicationModel.getApplicationExecutorRepository().nextExecutorExecutor();
+        executorService.submit(() -> {
+            synchronized (mappingListeners) {
+                Set<String> mappedServices = getAndListen(subscribedURL, listener);
+                this.putCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL), mappedServices);
+                mappingListeners.put(subscribedURL.getProtocolServiceKey(), listener);
+            }
+        });
 
-        String serviceNames = subscribedURL.getParameter(PROVIDED_BY);
-        if (StringUtils.isNotEmpty(serviceNames)) {
-            logger.info(subscribedURL.getServiceInterface() + " mapping to " + serviceNames + " instructed by provided-by set by user.");
-            subscribedServices.addAll(parseServices(serviceNames));
-        }
+        return cachedServices;
+    }
 
-        if (isEmpty(subscribedServices)) {
-            Set<String> mappedServices = getAndListen(subscribedURL, listener);
-            logger.info(subscribedURL.getServiceInterface() + " mapping to " + mappedServices + " instructed by remote metadata center.");
-            subscribedServices.addAll(mappedServices);
-            if (isEmpty(subscribedServices)) {
-                logger.info(subscribedURL.getServiceInterface() + " mapping to " + globalConfiguredSubscribingServices + " by default.");
-                subscribedServices.addAll(globalConfiguredSubscribingServices);
+    @Override
+    public MappingListener stopListen(URL subscribeURL) {
+        synchronized (mappingListeners) {
+            MappingListener listener = mappingListeners.remove(subscribeURL.getProtocolServiceKey());
+            //todo, remove listener from remote metadata center
+            listener.stop();
+            removeListener(subscribeURL, listener);
+            if (mappingListeners.size() == 0) {
+                removeCachedMapping(ServiceNameMapping.buildMappingKey(subscribeURL));
             }
+            return listener;
         }
-
-        metadataService.putCachedMapping(ServiceNameMapping.buildMappingKey(subscribedURL), subscribedServices);
-
-        return subscribedServices;
     }
 
     static Set<String> parseServices(String literalServices) {
@@ -120,4 +132,24 @@ public abstract class AbstractServiceNameMapping implements ServiceNameMapping,
                 .collect(toSet()));
     }
 
+    @Override
+    public void putCachedMapping(String serviceKey, Set<String> apps) {
+        serviceToAppsMapping.put(serviceKey, new TreeSet<>(apps));
+    }
+
+    @Override
+    public Set<String> getCachedMapping(String mappingKey) {
+        return serviceToAppsMapping.get(mappingKey);
+    }
+
+    @Override
+    public Set<String> getCachedMapping(URL consumerURL) {
+        return serviceToAppsMapping.get(ServiceNameMapping.buildMappingKey(consumerURL));
+    }
+
+    @Override
+    public Set<String> removeCachedMapping(String serviceKey) {
+        return serviceToAppsMapping.remove(serviceKey);
+    }
+
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingListener.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingListener.java
index f709d75..c9e7922 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingListener.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingListener.java
@@ -18,4 +18,6 @@ package org.apache.dubbo.metadata;
 
 public interface MappingListener {
     void onEvent(MappingChangedEvent event);
+
+    void stop();
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
index 4f0cecb..0985e3a 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
@@ -27,12 +27,17 @@ import org.apache.dubbo.common.utils.StringUtils;
 
 import java.io.Serializable;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.SortedSet;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DOT_SEPARATOR;
@@ -61,7 +66,10 @@ public class MetadataInfo implements Serializable {
 
     // used at runtime
     private transient Map<String, String> extendParams;
-    private transient AtomicBoolean reported = new AtomicBoolean(false);
+    protected transient AtomicBoolean updated = new AtomicBoolean(false);
+    private transient ConcurrentNavigableMap<String, SortedSet<URL>> subscribedServiceURLs = new ConcurrentSkipListMap<>();
+    private transient ConcurrentNavigableMap<String, SortedSet<URL>> exportedServiceURLs;
+    private transient ConcurrentNavigableMap<String, String> serviceDefinitions = new ConcurrentSkipListMap<>();
 
     public MetadataInfo() {
         this(null);
@@ -78,38 +86,41 @@ public class MetadataInfo implements Serializable {
         this.extendParams = new ConcurrentHashMap<>();
     }
 
-    public void addService(ServiceInfo serviceInfo) {
-        if (serviceInfo == null) {
-            return;
-        }
+    public synchronized void addService(URL url) {
+        ServiceInfo serviceInfo = new ServiceInfo(url);
         this.services.put(serviceInfo.getMatchKey(), serviceInfo);
-        markChanged();
-    }
 
-    public void removeService(ServiceInfo serviceInfo) {
-        if (serviceInfo == null) {
-            return;
+        if (exportedServiceURLs == null) {
+            exportedServiceURLs = new ConcurrentSkipListMap<>();
         }
-        this.services.remove(serviceInfo.getMatchKey());
-        markChanged();
+        addURL(exportedServiceURLs, url);
+        updated.compareAndSet(false, true);
     }
 
-    public void removeService(String key) {
-        if (key == null) {
+    public synchronized void removeService(URL url) {
+        if (url == null) {
             return;
         }
-        this.services.remove(key);
-        markChanged();
+        this.services.remove(url.getProtocolServiceKey());
+        this.exportedServiceURLs.remove(url);
+
+        updated.compareAndSet(false, true);
+    }
+
+    public String getRevision() {
+        return revision;
     }
 
     /**
      * Reported status and metadata modification must be synchronized if used in multiple threads.
      */
-    public String calAndGetRevision() {
-        if (revision != null && hasReported()) {
+    public synchronized String calAndGetRevision() {
+        if (revision != null && updated.get()) {
             return revision;
         }
 
+        updated.compareAndSet(true, false);
+
         if (CollectionUtils.isEmptyMap(services)) {
             this.revision = EMPTY_REVISION;
         } else {
@@ -133,27 +144,6 @@ public class MetadataInfo implements Serializable {
         this.revision = revision;
     }
 
-    /**
-     * Reported status and metadata modification must be synchronized if used in multiple threads.
-     */
-    public boolean hasReported() {
-        return reported.get();
-    }
-
-    /**
-     * Reported status and metadata modification must be synchronized if used in multiple threads.
-     */
-    public void markReported() {
-        reported.compareAndSet(false, true);
-    }
-
-    /**
-     * Reported status and metadata modification must be synchronized if used in multiple threads.
-     */
-    public void markChanged() {
-        reported.compareAndSet(true, false);
-    }
-
     public String getApp() {
         return app;
     }
@@ -206,6 +196,46 @@ public class MetadataInfo implements Serializable {
         return serviceInfo.toFullString();
     }
 
+    public void addSubscribedURL(URL url) {
+        addURL(subscribedServiceURLs, url);
+    }
+
+    public boolean removeSubscribedURL(URL url) {
+        return removeURL(subscribedServiceURLs, url);
+    }
+
+    public ConcurrentNavigableMap<String, SortedSet<URL>> getSubscribedServiceURLs() {
+        return subscribedServiceURLs;
+    }
+
+    public ConcurrentNavigableMap<String, SortedSet<URL>> getExportedServiceURLs() {
+        return exportedServiceURLs;
+    }
+
+    private boolean addURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {
+        SortedSet<URL> urls = serviceURLs.computeIfAbsent(url.getServiceKey(), this::newSortedURLs);
+        // make sure the parameters of tmpUrl is variable
+        return urls.add(url);
+    }
+
+    boolean removeURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {
+        String key = url.getServiceKey();
+        SortedSet<URL> urls = serviceURLs.getOrDefault(key, null);
+        if (urls == null) {
+            return true;
+        }
+        boolean r = urls.remove(url);
+        // if it is empty
+        if (urls.isEmpty()) {
+            serviceURLs.remove(key);
+        }
+        return r;
+    }
+
+    private SortedSet<URL> newSortedURLs(String serviceKey) {
+        return new TreeSet<>(URLComparator.INSTANCE);
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(app, services);
@@ -548,4 +578,14 @@ public class MetadataInfo implements Serializable {
                 "}";
         }
     }
+
+    static class URLComparator implements Comparator<URL> {
+
+        public static final URLComparator INSTANCE = new URLComparator();
+
+        @Override
+        public int compare(URL o1, URL o2) {
+            return o1.toFullString().compareTo(o2.toFullString());
+        }
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
index 5ccb0b0..946322b 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
@@ -185,8 +185,6 @@ public interface MetadataService {
 
     MetadataInfo getMetadataInfo(String revision);
 
-    Map<String, MetadataInfo> getMetadataInfos();
-
     /**
      * Is the {@link URL} for the {@link MetadataService} or not?
      *
@@ -275,4 +273,7 @@ public interface MetadataService {
     default String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
         throw new UnsupportedOperationException("This operation is not supported for consumer.");
     }
+
+    List<MetadataInfo> getMetadataInfos();
+
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
index ac71b5c..459d299 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
@@ -18,13 +18,14 @@ package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.TreeSet;
 
 import static java.util.Collections.emptySet;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
@@ -65,14 +66,25 @@ public interface ServiceNameMapping {
     }
 
     static String toStringKeys(Set<String> serviceNames) {
-        return serviceNames.toString();
+        if (CollectionUtils.isEmpty(serviceNames)) {
+            return "";
+        }
+
+        StringBuilder builder = new StringBuilder();
+        for (String n : serviceNames) {
+            builder.append(n);
+            builder.append(COMMA_SEPARATOR);
+        }
+
+        builder.deleteCharAt(builder.length() - 1);
+        return builder.toString();
     }
 
     static Set<String> getAppNames(String content) {
         if (StringUtils.isBlank(content)) {
             return emptySet();
         }
-        return new HashSet<>(Arrays.asList(content.split(COMMA_SEPARATOR)));
+        return new TreeSet<>(Arrays.asList(content.split(COMMA_SEPARATOR)));
     }
 
     /**
@@ -86,5 +98,15 @@ public interface ServiceNameMapping {
      * 2.check Interface-App mapping
      * 3.use the services specified in registry url.
      */
-    Set<String> getAndListenServices(URL registryURL, URL subscribedURL, MappingListener listener);
+    Set<String> getAndListen(URL registryURL, URL subscribedURL, MappingListener listener);
+
+    MappingListener stopListen(URL subscribeURL);
+
+    void putCachedMapping(String serviceKey, Set<String> apps);
+
+    Set<String> getCachedMapping(String mappingKey);
+
+    Set<String> getCachedMapping(URL consumerURL);
+
+    Set<String> removeCachedMapping(String serviceKey);
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
index bd77ec5..9c331f7 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
@@ -17,83 +17,21 @@
 package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 
-import java.util.Map;
-import java.util.Set;
-
 /**
- * Local {@link MetadataService} that extends {@link MetadataService} and provides the modification, which is used for
- * Dubbo's consumers and providers.
- *
- * @since 2.7.5
+ * FIXME, this class is not needed anymore.
  */
 @SPI(value = "default", scope = ExtensionScope.APPLICATION)
 public interface WritableMetadataService extends MetadataService {
 
-    /**
-     * Exports a {@link URL}
-     *
-     * @param url a {@link URL}
-     * @return If success , return <code>true</code>
-     */
-    boolean exportURL(URL url);
-
-    /**
-     * Unexports a {@link URL}
-     *
-     * @param url a {@link URL}
-     * @return If success , return <code>true</code>
-     */
-    boolean unexportURL(URL url);
-
-    /**
-     * Subscribes a {@link URL}
-     *
-     * @param url a {@link URL}
-     * @return If success , return <code>true</code>
-     */
-    boolean subscribeURL(URL url);
-
-    /**
-     * Unsubscribes a {@link URL}
-     *
-     * @param url a {@link URL}
-     * @return If success , return <code>true</code>
-     */
-    boolean unsubscribeURL(URL url);
-
-    void publishServiceDefinition(URL url);
-
-    default void setMetadataServiceURL(URL url) {
-
-    }
-
     default URL getMetadataServiceURL() {
         return null;
     }
 
-    void putCachedMapping(String serviceKey, Set<String> apps);
-
-    Set<String> getCachedMapping(String mappingKey);
-
-    Set<String> getCachedMapping(URL consumerURL);
-
-    Set<String> removeCachedMapping(String serviceKey);
-
-    Map<String, Set<String>> getCachedMapping();
-
-    MetadataInfo getDefaultMetadataInfo();
-
-    /**
-     * Get {@link ExtensionLoader#getDefaultExtension() the defautl extension} of {@link WritableMetadataService}
-     *
-     * @return non-null
-     */
     static WritableMetadataService getDefaultExtension(ScopeModel scopeModel) {
         return ScopeModelUtil.getExtensionLoader(WritableMetadataService.class, scopeModel).getDefaultExtension();
     }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/MethodDefinitionBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/MethodDefinitionBuilder.java
deleted file mode 100644
index e7c18e6..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/MethodDefinitionBuilder.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition;
-
-import org.apache.dubbo.metadata.definition.model.MethodDefinition;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link MethodDefinition} Builder based on Java Reflection
- *
- * @since 2.7.6
- */
-public class MethodDefinitionBuilder {
-
-    private final TypeDefinitionBuilder builder;
-
-    public MethodDefinitionBuilder(TypeDefinitionBuilder builder) {
-        this.builder = builder;
-    }
-
-    public MethodDefinitionBuilder() {
-        this.builder = new TypeDefinitionBuilder();
-    }
-
-    /**
-     * Build the instance of {@link MethodDefinition}
-     *
-     * @param method {@link Method}
-     * @return non-null
-     */
-    public MethodDefinition build(Method method) {
-
-        MethodDefinition md = new MethodDefinition();
-        md.setName(method.getName());
-
-        // Process the parameters
-        Class<?>[] paramTypes = method.getParameterTypes();
-        Type[] genericParamTypes = method.getGenericParameterTypes();
-
-        int paramSize = paramTypes.length;
-        String[] parameterTypes = new String[paramSize];
-        List<TypeDefinition> parameters = new ArrayList<>(paramSize);
-        for (int i = 0; i < paramSize; i++) {
-            TypeDefinition parameter = builder.build(genericParamTypes[i], paramTypes[i]);
-            parameterTypes[i] = parameter.getType();
-            parameters.add(parameter);
-        }
-
-        md.setParameterTypes(parameterTypes);
-        md.setParameters(parameters);
-
-        // Process return type.
-        TypeDefinition td = builder.build(method.getGenericReturnType(), method.getReturnType());
-        md.setReturnType(td.getType());
-
-        return md;
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilder.java
deleted file mode 100755
index db09b76..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/ServiceDefinitionBuilder.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
-import org.apache.dubbo.metadata.definition.model.MethodDefinition;
-import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-import org.apache.dubbo.metadata.definition.util.ClassUtils;
-
-import com.google.gson.Gson;
-
-/**
- * 2015/1/27.
- */
-public final class ServiceDefinitionBuilder {
-
-    /**
-     * Describe a Java interface in {@link ServiceDefinition}.
-     *
-     * @return Service description
-     */
-    public static ServiceDefinition build(final Class<?> interfaceClass) {
-        ServiceDefinition sd = new ServiceDefinition();
-        build(sd, interfaceClass);
-        return sd;
-    }
-
-    public static FullServiceDefinition buildFullDefinition(final Class<?> interfaceClass) {
-        FullServiceDefinition sd = new FullServiceDefinition();
-        build(sd, interfaceClass);
-        return sd;
-    }
-
-    public static FullServiceDefinition buildFullDefinition(final Class<?> interfaceClass, Map<String, String> params) {
-        FullServiceDefinition sd = new FullServiceDefinition();
-        build(sd, interfaceClass);
-        sd.setParameters(params);
-        return sd;
-    }
-
-    public static <T extends ServiceDefinition> void build(T sd, final Class<?> interfaceClass) {
-        sd.setCanonicalName(interfaceClass.getCanonicalName());
-        sd.setCodeSource(ClassUtils.getCodeSource(interfaceClass));
-        Annotation[] classAnnotations = interfaceClass.getAnnotations();
-        sd.setAnnotations(annotationToStringList(classAnnotations));
-
-        TypeDefinitionBuilder builder = new TypeDefinitionBuilder();
-        List<Method> methods = ClassUtils.getPublicNonStaticMethods(interfaceClass);
-        for (Method method : methods) {
-            MethodDefinition md = new MethodDefinition();
-            md.setName(method.getName());
-
-            Annotation[] methodAnnotations = method.getAnnotations();
-            md.setAnnotations(annotationToStringList(methodAnnotations));
-
-            // Process parameter types.
-            Class<?>[] paramTypes = method.getParameterTypes();
-            Type[] genericParamTypes = method.getGenericParameterTypes();
-
-            String[] parameterTypes = new String[paramTypes.length];
-            for (int i = 0; i < paramTypes.length; i++) {
-                TypeDefinition td = builder.build(genericParamTypes[i], paramTypes[i]);
-                parameterTypes[i] = td.getType();
-            }
-            md.setParameterTypes(parameterTypes);
-
-            // Process return type.
-            TypeDefinition td = builder.build(method.getGenericReturnType(), method.getReturnType());
-            md.setReturnType(td.getType());
-
-            sd.getMethods().add(md);
-        }
-
-        sd.setTypes(builder.getTypeDefinitions());
-    }
-
-    private static List<String> annotationToStringList(Annotation[] annotations) {
-        if (annotations == null) {
-            return Collections.emptyList();
-        }
-        List<String> list = new ArrayList<>();
-        for (Annotation annotation : annotations) {
-            list.add(annotation.toString());
-        }
-        return list;
-    }
-
-    /**
-     * Describe a Java interface in Json schema.
-     *
-     * @return Service description
-     */
-    public static String schema(final Class<?> clazz) {
-        ServiceDefinition sd = build(clazz);
-        Gson gson = new Gson();
-        return gson.toJson(sd);
-    }
-
-    private ServiceDefinitionBuilder() {
-    }
-}
-
-
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
deleted file mode 100755
index b2758e3..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition;
-
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.ClassUtils;
-import org.apache.dubbo.metadata.definition.builder.DefaultTypeBuilder;
-import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-
-/**
- * 2015/1/27.
- */
-public class TypeDefinitionBuilder {
-    private static final Logger logger = LoggerFactory.getLogger(TypeDefinitionBuilder.class);
-    static final List<TypeBuilder> BUILDERS;
-
-    static {
-        ExtensionLoader<TypeBuilder> extensionLoader = ExtensionLoader.getExtensionLoader(TypeBuilder.class);
-        Set<TypeBuilder> tbs = extensionLoader.getSupportedExtensionInstances();
-        BUILDERS = new ArrayList<>(tbs);
-    }
-
-    public static TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        TypeBuilder builder = getGenericTypeBuilder(clazz);
-        TypeDefinition td;
-
-        if (clazz.isPrimitive() || ClassUtils.isSimpleType(clazz)) { // changed since 2.7.6
-            td = new TypeDefinition(clazz.getCanonicalName());
-            typeCache.put(clazz.getCanonicalName(), td);
-        } else if (builder != null) {
-            td = builder.build(type, clazz, typeCache);
-        } else {
-            td = DefaultTypeBuilder.build(clazz, typeCache);
-        }
-        return td;
-    }
-
-    private static TypeBuilder getGenericTypeBuilder(Class<?> clazz) {
-        for (TypeBuilder builder : BUILDERS) {
-            try {
-                if (builder.accept(clazz)) {
-                    return builder;
-                }
-            } catch (NoClassDefFoundError cnfe) {
-                //ignore
-                logger.info("Throw classNotFound (" + cnfe.getMessage() + ") in " + builder.getClass());
-            }
-        }
-        return null;
-    }
-
-    private Map<String, TypeDefinition> typeCache = new HashMap<>();
-
-    public TypeDefinition build(Type type, Class<?> clazz) {
-        return build(type, clazz, typeCache);
-    }
-
-    public List<TypeDefinition> getTypeDefinitions() {
-        return new ArrayList<>(typeCache.values());
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/ArrayTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/ArrayTypeBuilder.java
deleted file mode 100755
index 78cc7a5..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/ArrayTypeBuilder.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.metadata.definition.TypeDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Type;
-import java.util.Map;
-
-/**
- * 2015/1/27.
- */
-public class ArrayTypeBuilder implements TypeBuilder {
-
-    @Override
-    public boolean accept(Class<?> clazz) {
-        if (clazz == null) {
-            return false;
-        }
-        return clazz.isArray();
-    }
-
-    @Override
-    public TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        final String canonicalName = clazz.getCanonicalName();
-        TypeDefinition td = typeCache.get(canonicalName);
-        if (td != null) {
-            return td;
-        }
-        td = new TypeDefinition(canonicalName);
-        typeCache.put(canonicalName, td);
-        // Process the component type of array.
-        Class<?> componentType = clazz.getComponentType();
-        TypeDefinition itemTd = TypeDefinitionBuilder.build(componentType, componentType, typeCache);
-        if (itemTd != null) {
-            td.getItems().add(itemTd.getType());
-        }
-        return td;
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/CollectionTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/CollectionTypeBuilder.java
deleted file mode 100755
index 6c6ee11..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/CollectionTypeBuilder.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.metadata.definition.TypeDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-import org.apache.dubbo.metadata.definition.util.ClassUtils;
-
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * 2015/1/27.
- */
-public class CollectionTypeBuilder implements TypeBuilder {
-
-    @Override
-    public boolean accept(Class<?> clazz) {
-        if (clazz == null) {
-            return false;
-        }
-        return Collection.class.isAssignableFrom(clazz);
-    }
-
-    @Override
-    public TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        if (!(type instanceof ParameterizedType)) {
-            return new TypeDefinition(clazz.getCanonicalName());
-        }
-
-        ParameterizedType parameterizedType = (ParameterizedType) type;
-        Type[] actualTypeArgs = parameterizedType.getActualTypeArguments();
-        if (actualTypeArgs == null || actualTypeArgs.length != 1) {
-            throw new IllegalArgumentException(MessageFormat.format(
-                    "[ServiceDefinitionBuilder] Collection type [{0}] with unexpected amount of arguments [{1}]."
-                            + Arrays.toString(actualTypeArgs),
-                    type, actualTypeArgs));
-        }
-
-        String colType = ClassUtils.getCanonicalNameForParameterizedType(parameterizedType);
-        TypeDefinition td = typeCache.get(colType);
-        if (td != null) {
-            return td;
-        }
-        td = new TypeDefinition(colType);
-        typeCache.put(colType, td);
-
-        Type actualType = actualTypeArgs[0];
-        TypeDefinition itemTd = null;
-        if (actualType instanceof ParameterizedType) {
-            // Nested collection or map.
-            Class<?> rawType = (Class<?>) ((ParameterizedType) actualType).getRawType();
-            itemTd = TypeDefinitionBuilder.build(actualType, rawType, typeCache);
-        } else if (actualType instanceof Class<?>) {
-            Class<?> actualClass = (Class<?>) actualType;
-            itemTd = TypeDefinitionBuilder.build(null, actualClass, typeCache);
-        }
-        if (itemTd != null) {
-            td.getItems().add(itemTd.getType());
-        }
-
-        return td;
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/DefaultTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/DefaultTypeBuilder.java
deleted file mode 100755
index 4ac52a4..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/DefaultTypeBuilder.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.metadata.definition.TypeDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-import org.apache.dubbo.metadata.definition.util.ClassUtils;
-import org.apache.dubbo.metadata.definition.util.JaketConfigurationUtils;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 2015/1/27.
- */
-public final class DefaultTypeBuilder {
-
-    public static TypeDefinition build(Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        final String canonicalName = clazz.getCanonicalName();
-
-        // Try to get a cached definition
-        TypeDefinition td = typeCache.get(canonicalName);
-        if (td != null) {
-            return td;
-        }
-        td = new TypeDefinition(canonicalName);
-        typeCache.put(canonicalName, td);
-
-        // Primitive type
-        if (!JaketConfigurationUtils.needAnalyzing(clazz)) {
-            return td;
-        }
-
-        // Custom type
-        List<Field> fields = ClassUtils.getNonStaticFields(clazz);
-        for (Field field : fields) {
-            String fieldName = field.getName();
-            Class<?> fieldClass = field.getType();
-            Type fieldType = field.getGenericType();
-            TypeDefinition fieldTd = TypeDefinitionBuilder.build(fieldType, fieldClass, typeCache);
-            td.getProperties().put(fieldName, fieldTd.getType());
-        }
-
-        return td;
-    }
-
-    private DefaultTypeBuilder() {
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/EnumTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/EnumTypeBuilder.java
deleted file mode 100755
index 1f5d52a..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/EnumTypeBuilder.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.metadata.definition.TypeDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.Map;
-
-/**
- * 2015/1/27.
- */
-public class EnumTypeBuilder implements TypeBuilder {
-    private static final Logger logger = LoggerFactory.getLogger(TypeDefinitionBuilder.class);
-
-    @Override
-    public boolean accept(Class<?> clazz) {
-        if (clazz == null) {
-            return false;
-        }
-        return clazz.isEnum();
-    }
-
-    @Override
-    public TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        String canonicalName = clazz.getCanonicalName();
-
-        TypeDefinition td = typeCache.get(canonicalName);
-        if (td != null) {
-            return td;
-        }
-        td = new TypeDefinition(canonicalName);
-        typeCache.put(canonicalName, td);
-
-        try {
-            Method methodValues = clazz.getDeclaredMethod("values");
-            methodValues.setAccessible(true);
-            Object[] values = (Object[]) methodValues.invoke(clazz, new Object[0]);
-            int length = values.length;
-            for (int i = 0; i < length; i++) {
-                Object value = values[i];
-                td.getEnums().add(value.toString());
-            }
-            return td;
-        } catch (Throwable t) {
-            logger.error("There is an error while process class " + clazz, t);
-        }
-        return td;
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/MapTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/MapTypeBuilder.java
deleted file mode 100755
index 121198c..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/MapTypeBuilder.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.metadata.definition.TypeDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-import org.apache.dubbo.metadata.definition.util.ClassUtils;
-
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Map;
-
-import static org.apache.dubbo.common.utils.TypeUtils.getRawClass;
-import static org.apache.dubbo.common.utils.TypeUtils.isClass;
-import static org.apache.dubbo.common.utils.TypeUtils.isParameterizedType;
-
-/**
- * 2015/1/27.
- */
-public class MapTypeBuilder implements TypeBuilder {
-
-    @Override
-    public boolean accept(Class<?> clazz) {
-        if (clazz == null) {
-            return false;
-        }
-        return Map.class.isAssignableFrom(clazz);
-    }
-
-    @Override
-    public TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache) {
-        if (!(type instanceof ParameterizedType)) {
-            return new TypeDefinition(clazz.getCanonicalName());
-        }
-
-        ParameterizedType parameterizedType = (ParameterizedType) type;
-        Type[] actualTypeArgs = parameterizedType.getActualTypeArguments();
-        int actualTypeArgsLength = actualTypeArgs == null ? 0 : actualTypeArgs.length;
-
-        if (actualTypeArgsLength != 2) {
-            throw new IllegalArgumentException(MessageFormat.format(
-                    "[ServiceDefinitionBuilder] Map type [{0}] with unexpected amount of arguments [{1}]."
-                            + Arrays.toString(actualTypeArgs), type, actualTypeArgs));
-        }
-
-        String mapType = ClassUtils.getCanonicalNameForParameterizedType(parameterizedType);
-
-        TypeDefinition td = typeCache.get(mapType);
-        if (td != null) {
-            return td;
-        }
-        td = new TypeDefinition(mapType);
-        typeCache.put(mapType, td);
-
-        for (int i = 0; i < actualTypeArgsLength; i++) {
-            Type actualType = actualTypeArgs[i];
-            TypeDefinition item = null;
-            Class<?> rawType = getRawClass(actualType);
-            if (isParameterizedType(actualType)) {
-                // Nested collection or map.
-                item = TypeDefinitionBuilder.build(actualType, rawType, typeCache);
-            } else if (isClass(actualType)) {
-                item = TypeDefinitionBuilder.build(null, rawType, typeCache);
-            }
-            if (item != null) {
-                td.getItems().add(item.getType());
-            }
-        }
-        return td;
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
deleted file mode 100755
index 050de3c..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.common.lang.Prioritized;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Type;
-import java.util.Map;
-
-/**
- * 2015/1/27.
- */
-@SPI
-public interface TypeBuilder extends Prioritized {
-
-    /**
-     * Whether the build accept the class passed in.
-     */
-    boolean accept(Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<String, TypeDefinition> typeCache);
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/FullServiceDefinition.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/FullServiceDefinition.java
deleted file mode 100644
index fbebcf1..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/FullServiceDefinition.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.model;
-
-import java.util.Map;
-
-/**
- * 2018/10/25
- */
-public class FullServiceDefinition extends ServiceDefinition {
-
-    private Map<String, String> parameters;
-
-    public Map<String, String> getParameters() {
-        return parameters;
-    }
-
-    public void setParameters(Map<String, String> parameters) {
-        this.parameters = parameters;
-    }
-
-    @Override
-    public String toString() {
-        return "FullServiceDefinition{" +
-                "parameters=" + parameters +
-                "} " + super.toString();
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/MethodDefinition.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/MethodDefinition.java
deleted file mode 100755
index 305bfc3..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/MethodDefinition.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.model;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-import static org.apache.dubbo.metadata.definition.model.TypeDefinition.formatType;
-import static org.apache.dubbo.metadata.definition.model.TypeDefinition.formatTypes;
-
-/**
- * 2015/1/27.
- */
-public class MethodDefinition implements Serializable {
-
-    private String name;
-    private String[] parameterTypes;
-    private String returnType;
-
-    /**
-     * @deprecated please use parameterTypes,
-     * and find TypeDefinition in org.apache.dubbo.metadata.definition.model.ServiceDefinition#types
-     */
-    @Deprecated
-    private List<TypeDefinition> parameters;
-
-    private List<String> annotations;
-
-    public String getName() {
-        return name;
-    }
-
-    public List<TypeDefinition> getParameters() {
-        if (parameters == null) {
-            parameters = new ArrayList<>();
-        }
-        return parameters;
-    }
-
-    public String[] getParameterTypes() {
-        return parameterTypes;
-    }
-
-    public String getReturnType() {
-        return returnType;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public void setParameters(List<TypeDefinition> parameters) {
-        this.parameters = parameters;
-    }
-
-    public void setParameterTypes(String[] parameterTypes) {
-        this.parameterTypes = formatTypes(parameterTypes);
-    }
-
-    public void setReturnType(String returnType) {
-        this.returnType = formatType(returnType);
-    }
-
-    public List<String> getAnnotations() {
-        if (annotations == null) {
-            annotations = Collections.emptyList();
-        }
-        return annotations;
-    }
-
-    public void setAnnotations(List<String> annotations) {
-        this.annotations = annotations;
-    }
-
-    @Override
-    public String toString() {
-        return "MethodDefinition [name=" + name + ", parameterTypes=" + Arrays.toString(parameterTypes)
-                + ", returnType=" + returnType + "]";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof MethodDefinition)) {
-            return false;
-        }
-        MethodDefinition that = (MethodDefinition) o;
-        return Objects.equals(getName(), that.getName()) &&
-                Arrays.equals(getParameterTypes(), that.getParameterTypes()) &&
-                Objects.equals(getReturnType(), that.getReturnType());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getName(), getReturnType(), Arrays.toString(getParameterTypes()));
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/ServiceDefinition.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/ServiceDefinition.java
deleted file mode 100755
index 40ced04..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/ServiceDefinition.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.model;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.dubbo.metadata.definition.util.ClassUtils;
-
-/**
- * 2015/1/27.
- */
-public class ServiceDefinition implements Serializable {
-
-    /**
-     * the canonical name of interface
-     *
-     * @see Class#getCanonicalName()
-     */
-    private String canonicalName;
-
-    /**
-     * the location of class file
-     *
-     * @see ClassUtils#getCodeSource(Class)
-     */
-    private String codeSource;
-
-    private List<MethodDefinition> methods;
-
-    /**
-     * the definitions of type
-     */
-    private List<TypeDefinition> types;
-
-    /**
-     * the definitions of annotations
-     */
-    private List<String> annotations;
-
-    public String getCanonicalName() {
-        return canonicalName;
-    }
-
-    public String getCodeSource() {
-        return codeSource;
-    }
-
-    public List<MethodDefinition> getMethods() {
-        if (methods == null) {
-            methods = new ArrayList<>();
-        }
-        return methods;
-    }
-
-    public List<TypeDefinition> getTypes() {
-        if (types == null) {
-            types = new ArrayList<>();
-        }
-        return types;
-    }
-
-    public String getUniqueId() {
-        return canonicalName + "@" + codeSource;
-    }
-
-    public void setCanonicalName(String canonicalName) {
-        this.canonicalName = canonicalName;
-    }
-
-    public void setCodeSource(String codeSource) {
-        this.codeSource = codeSource;
-    }
-
-    public void setMethods(List<MethodDefinition> methods) {
-        this.methods = methods;
-    }
-
-    public void setTypes(List<TypeDefinition> types) {
-        this.types = types;
-    }
-
-    public List<String> getAnnotations() {
-        if (annotations == null) {
-            annotations = Collections.emptyList();
-        }
-        return annotations;
-    }
-
-    public void setAnnotations(List<String> annotations) {
-        this.annotations = annotations;
-    }
-
-    @Override
-    public String toString() {
-        return "ServiceDefinition [canonicalName=" + canonicalName + ", codeSource=" + codeSource + ", methods="
-                + methods + "]";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof ServiceDefinition)) {
-            return false;
-        }
-        ServiceDefinition that = (ServiceDefinition) o;
-        return Objects.equals(getCanonicalName(), that.getCanonicalName()) &&
-                Objects.equals(getCodeSource(), that.getCodeSource()) &&
-                Objects.equals(getMethods(), that.getMethods()) &&
-                Objects.equals(getTypes(), that.getTypes());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getCanonicalName(), getCodeSource(), getMethods(), getTypes());
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/TypeDefinition.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/TypeDefinition.java
deleted file mode 100755
index 6dd0f06..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/model/TypeDefinition.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.model;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.io.Serializable;
-import java.lang.reflect.ParameterizedType;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import static org.apache.dubbo.common.utils.StringUtils.replace;
-
-/**
- * 2015/1/27.
- */
-public class TypeDefinition implements Serializable {
-
-    /**
-     * the name of type
-     *
-     * @see Class#getCanonicalName()
-     * @see org.apache.dubbo.metadata.definition.util.ClassUtils#getCanonicalNameForParameterizedType(ParameterizedType) 
-     */
-    private String type;
-
-    /**
-     * the items(generic parameter) of Map/List(ParameterizedType)
-     * <p>
-     * if this type is not ParameterizedType, the items is null or empty
-     */
-    @SerializedName("items")
-    private List<String> items;
-
-    /**
-     * the enum's value
-     * <p>
-     * If this type is not enum, enums is null or empty
-     */
-    @SerializedName("enum")
-    private List<String> enums;
-
-    /**
-     * the key is property name,
-     * the value is property's type name
-     */
-    private Map<String, String> properties;
-
-    public TypeDefinition() {
-    }
-
-    public TypeDefinition(String type) {
-        this.setType(type);
-    }
-
-    /**
-     * Format the {@link String} array presenting Java types
-     *
-     * @param types the strings presenting Java types
-     * @return new String array of Java types after be formatted
-     * @since 2.7.9
-     */
-    public static String[] formatTypes(String[] types) {
-        String[] newTypes = new String[types.length];
-        for (int i = 0; i < types.length; i++) {
-            newTypes[i] = formatType(types[i]);
-        }
-        return newTypes;
-    }
-
-    /**
-     * Format the {@link String} presenting Java type
-     *
-     * @param type the String presenting type
-     * @return new String presenting Java type after be formatted
-     * @since 2.7.9
-     */
-    public static String formatType(String type) {
-        if (isGenericType(type)) {
-            return formatGenericType(type);
-        }
-        return type;
-    }
-
-    /**
-     * Replacing <code>", "</code> to <code>","</code> will not change the semantic of
-     * {@link ParameterizedType#toString()}
-     *
-     * @param type
-     * @return formatted type
-     * @see sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
-     */
-    private static String formatGenericType(String type) {
-        return replace(type, ", ", ",");
-    }
-
-    private static boolean isGenericType(String type) {
-        return type.contains("<") && type.contains(">");
-    }
-
-    public List<String> getEnums() {
-        if (enums == null) {
-            enums = new ArrayList<>();
-        }
-        return enums;
-    }
-
-    public List<String> getItems() {
-        if (items == null) {
-            items = new ArrayList<>();
-        }
-        return items;
-    }
-
-    public Map<String, String> getProperties() {
-        if (properties == null) {
-            properties = new HashMap<>();
-        }
-        return properties;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public void setEnums(List<String> enums) {
-        this.enums = enums;
-    }
-
-    public void setItems(List<String> items) {
-        this.items = items;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties = properties;
-    }
-
-    public void setType(String type) {
-        this.type = formatType(type);
-    }
-
-    @Override
-    public String toString() {
-        return "TypeDefinition [type=" + type + ", properties=" + properties + "]";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof TypeDefinition)) {
-            return false;
-        }
-        TypeDefinition that = (TypeDefinition) o;
-        return Objects.equals(getType(), that.getType()) &&
-                Objects.equals(getItems(), that.getItems()) &&
-                Objects.equals(getEnums(), that.getEnums()) &&
-                Objects.equals(getProperties(), that.getProperties());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getType(), getItems(), getEnums(), getProperties());
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/ClassUtils.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/ClassUtils.java
deleted file mode 100755
index c6f13d5..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/ClassUtils.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.util;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.security.CodeSource;
-import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 2015/1/27.
- */
-public final class ClassUtils {
-
-    /**
-     * Get the code source file or class path of the Class passed in.
-     *
-     * @param clazz
-     * @return Jar file name or class path.
-     */
-    public static String getCodeSource(Class<?> clazz) {
-        ProtectionDomain protectionDomain = clazz.getProtectionDomain();
-        if (protectionDomain == null || protectionDomain.getCodeSource() == null) {
-            return null;
-        }
-
-        CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
-        URL location = codeSource.getLocation();
-        if (location == null) {
-            return null;
-        }
-
-        String path = location.toExternalForm();
-
-        if (path.endsWith(".jar") && path.contains("/")) {
-            return path.substring(path.lastIndexOf('/') + 1);
-        }
-        return path;
-    }
-
-    /**
-     * Get all non-static fields of the Class passed in or its super classes.
-     * <p>
-     *
-     * @param clazz Class to parse.
-     * @return field list
-     */
-    public static List<Field> getNonStaticFields(final Class<?> clazz) {
-        List<Field> result = new ArrayList<>();
-        Class<?> target = clazz;
-        while (target != null) {
-            if (JaketConfigurationUtils.isExcludedType(target)) {
-                break;
-            }
-
-            Field[] fields = target.getDeclaredFields();
-            for (Field field : fields) {
-                int modifiers = field.getModifiers();
-                if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) {
-                    continue;
-                }
-
-                result.add(field);
-            }
-            target = target.getSuperclass();
-        }
-
-        return result;
-    }
-
-    /**
-     * Get all public, non-static methods of the Class passed in.
-     * <p>
-     *
-     * @param clazz Class to parse.
-     * @return methods list
-     */
-    public static List<Method> getPublicNonStaticMethods(final Class<?> clazz) {
-        List<Method> result = new ArrayList<Method>();
-
-        Method[] methods = clazz.getMethods();
-        for (Method method : methods) {
-            int mod = method.getModifiers();
-            if (Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
-                result.add(method);
-            }
-        }
-        return result;
-    }
-
-    public static String getCanonicalNameForParameterizedType(ParameterizedType parameterizedType) {
-        StringBuilder sb = new StringBuilder();
-        Type ownerType = parameterizedType.getOwnerType();
-        Class<?> rawType = (Class) parameterizedType.getRawType();
-        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
-
-        if (ownerType != null) {
-            if (ownerType instanceof Class) {
-                sb.append(((Class) ownerType).getName());
-            } else {
-                sb.append(ownerType);
-            }
-
-            sb.append('.');
-
-            if (ownerType instanceof ParameterizedType) {
-                // Find simple name of nested type by removing the
-                // shared prefix with owner.
-                sb.append(rawType.getName().replace(((Class) ((ParameterizedType) ownerType).getRawType()).getName() + "$",
-                        ""));
-            } else {
-                sb.append(rawType.getSimpleName());
-            }
-        } else {
-            sb.append(rawType.getCanonicalName());
-        }
-
-        if (actualTypeArguments != null &&
-                actualTypeArguments.length > 0) {
-            sb.append('<');
-            boolean first = true;
-            for (Type t : actualTypeArguments) {
-                if (!first) {
-                    sb.append(", ");
-                }
-                if (t instanceof Class) {
-                    Class c = (Class) t;
-                    sb.append(c.getCanonicalName());
-                } else if (t instanceof ParameterizedType) {
-                    sb.append(getCanonicalNameForParameterizedType((ParameterizedType) t));
-                } else {
-                    sb.append(t.toString());
-                }
-                first = false;
-            }
-            sb.append('>');
-        }
-
-        return sb.toString();
-    }
-
-    private ClassUtils() {
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/JaketConfigurationUtils.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/JaketConfigurationUtils.java
deleted file mode 100755
index a21de3f..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/util/JaketConfigurationUtils.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.definition.util;
-
-import org.apache.dubbo.common.utils.StringUtils;
-
-import java.io.InputStream;
-import java.util.Properties;
-
-/**
- * 2015/1/27.
- */
-public class JaketConfigurationUtils {
-
-    private static final String CONFIGURATION_FILE = "jaket.properties";
-
-    private static String[] includedInterfacePackages;
-    private static String[] includedTypePackages;
-    private static String[] closedTypes;
-
-    static {
-        Properties props = new Properties();
-        InputStream inStream = JaketConfigurationUtils.class.getClassLoader().getResourceAsStream(CONFIGURATION_FILE);
-        try {
-            props.load(inStream);
-            String value = (String) props.get("included_interface_packages");
-            if (StringUtils.isNotEmpty(value)) {
-                includedInterfacePackages = value.split(",");
-            }
-
-            value = props.getProperty("included_type_packages");
-            if (StringUtils.isNotEmpty(value)) {
-                includedTypePackages = value.split(",");
-            }
-
-            value = props.getProperty("closed_types");
-            if (StringUtils.isNotEmpty(value)) {
-                closedTypes = value.split(",");
-            }
-
-        } catch (Throwable e) {
-            // Ignore it.
-        }
-    }
-
-    public static boolean isExcludedInterface(Class<?> clazz) {
-        if (includedInterfacePackages == null || includedInterfacePackages.length == 0) {
-            return false;
-        }
-
-        for (String packagePrefix : includedInterfacePackages) {
-            if (clazz.getCanonicalName().startsWith(packagePrefix)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public static boolean isExcludedType(Class<?> clazz) {
-        if (includedTypePackages == null || includedTypePackages.length == 0) {
-            return false;
-        }
-
-        for (String packagePrefix : includedTypePackages) {
-            if (clazz.getCanonicalName().startsWith(packagePrefix)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public static boolean needAnalyzing(Class<?> clazz) {
-        String canonicalName = clazz.getCanonicalName();
-
-        if (closedTypes != null && closedTypes.length > 0) {
-            for (String type : closedTypes) {
-                if (canonicalName.startsWith(type)) {
-                    return false;
-                }
-            }
-        }
-
-        return !isExcludedType(clazz);
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
index 3dc659e..0208650 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
@@ -78,6 +78,10 @@ public interface MetadataReport {
         return false;
     }
 
+    default void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+
+    }
+
     /**
      * Service<-->Application Mapping -- START
      **/
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java
deleted file mode 100644
index d084477..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.store;
-
-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.StringUtils;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
-
-import com.google.gson.Gson;
-
-import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
-import static org.apache.dubbo.common.utils.ClassUtils.forName;
-import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
-import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
-import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
-import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
-import static org.apache.dubbo.rpc.support.ProtocolUtils.isGeneric;
-
-/**
- * The abstract implementation of {@link WritableMetadataService}
- *
- * @see WritableMetadataService
- * @since 2.7.8
- */
-public abstract class AbstractAbstractWritableMetadataService implements WritableMetadataService {
-
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Override
-    public void publishServiceDefinition(URL url) {
-        if (SERVICE_INTERFACE_NAME.equals(url.getServiceInterface())) { // Ignore the interface "MetadataService"
-            return;
-        }
-
-        // Remove the useless parameters
-        url = url.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY);
-
-        String side = url.getSide();
-        if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
-            publishProviderServiceDefinition(url);
-        } else {
-            publishConsumerParameters(url);
-        }
-    }
-
-    protected void publishProviderServiceDefinition(URL url) {
-        String serviceDefinition = getServiceDefinition(url);
-        if (!StringUtils.isBlank(serviceDefinition)) {
-            publishServiceDefinition(url.getServiceKey(), serviceDefinition);
-        }
-    }
-
-    protected String getServiceDefinition(URL exportedURL) {
-        String interfaceName = exportedURL.getServiceInterface();
-        String json = null;
-        try {
-            if (StringUtils.isNotEmpty(interfaceName) && !isGeneric(exportedURL.getParameter(GENERIC_KEY))) {
-                Class interfaceClass = forName(interfaceName);
-                ServiceDefinition serviceDefinition = buildFullDefinition(interfaceClass, exportedURL.getParameters());
-                Gson gson = new Gson();
-                json = gson.toJson(serviceDefinition);
-            }
-        } catch (ClassNotFoundException e) {
-            //ignore error
-            if (logger.isErrorEnabled()) {
-                logger.error("The interface class[name : " + interfaceName + "] can't be found , providerUrl: "
-                        + exportedURL.toFullString());
-            }
-        }
-        return json;
-    }
-
-    protected void publishConsumerParameters(URL url) {
-    }
-
-    protected void publishServiceDefinition(String key, String json) {
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/AbstractServiceNameMappingTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/AbstractServiceNameMappingTest.java
index 2c974c8..3134c9e 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/AbstractServiceNameMappingTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/AbstractServiceNameMappingTest.java
@@ -17,19 +17,15 @@
 package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
 
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDED_BY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
@@ -40,14 +36,9 @@ import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SER
 class AbstractServiceNameMappingTest {
 
     private MockServiceNameMapping mapping = new MockServiceNameMapping();
-    private MockWritableMetadataService writableMetadataService = new MockWritableMetadataService();
 
     @BeforeEach
-    public void setUp() throws Exception {
-        Field metadataService = mapping.getClass().getSuperclass().getDeclaredField("metadataService");
-        metadataService.setAccessible(true);
-        metadataService.set(mapping, writableMetadataService);
-    }
+    public void setUp() throws Exception {}
 
     @Test
     void testGetServices() {
@@ -63,10 +54,8 @@ class AbstractServiceNameMappingTest {
         Assertions.assertTrue(services.contains("remote-app2"));
 
 
-        Map<String, Set<String>> cachedMapping = writableMetadataService.getCachedMapping();
-        Assertions.assertNotNull(cachedMapping);
-        Assertions.assertTrue(cachedMapping.containsKey(ServiceNameMapping.buildMappingKey(url)));
-        Assertions.assertIterableEquals(cachedMapping.get(ServiceNameMapping.buildMappingKey(url)), services);
+        Assertions.assertNotNull(mapping.getCachedMapping(url));
+        Assertions.assertIterableEquals(mapping.getCachedMapping(url), services);
 
     }
 
@@ -76,17 +65,25 @@ class AbstractServiceNameMappingTest {
         URL registryURL = URL.valueOf("registry://127.0.0.1:7777/test");
         registryURL = registryURL.addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "registry-app1");
 
-        Set<String> services = mapping.getAndListenServices(registryURL, url, null);
+        Set<String> services = mapping.getAndListen(registryURL, url, null);
         Assertions.assertTrue(services.contains("registry-app1"));
 
         mapping.enabled = true;
-        services = mapping.getAndListenServices(registryURL, url, event -> {
+        services = mapping.getAndListen(registryURL, url, new MappingListener() {
+            @Override
+            public void onEvent(MappingChangedEvent event) {
+
+            }
+
+            @Override
+            public void stop() {
+
+            }
         });
         Assertions.assertTrue(services.contains("remote-app3"));
 
     }
 
-
     private class MockServiceNameMapping extends AbstractServiceNameMapping {
 
         public boolean enabled = false;
@@ -105,94 +102,14 @@ class AbstractServiceNameMappingTest {
         }
 
         @Override
-        public boolean map(URL url) {
-            return false;
-        }
-    }
+        protected void removeListener(URL url, MappingListener mappingListener) {
 
-    private class MockWritableMetadataService implements WritableMetadataService {
-        private final Map<String, Set<String>> serviceToAppsMapping = new HashMap<>();
-
-        @Override
-        public String serviceName() {
-            return null;
         }
 
         @Override
-        public SortedSet<String> getExportedURLs(String serviceInterface, String group, String version, String protocol) {
-            return null;
-        }
-
-        @Override
-        public String getServiceDefinition(String serviceKey) {
-            return null;
-        }
-
-        @Override
-        public MetadataInfo getMetadataInfo(String revision) {
-            return null;
-        }
-
-        @Override
-        public Map<String, MetadataInfo> getMetadataInfos() {
-            return null;
-        }
-
-        @Override
-        public boolean exportURL(URL url) {
-            return false;
-        }
-
-        @Override
-        public boolean unexportURL(URL url) {
-            return false;
-        }
-
-        @Override
-        public boolean subscribeURL(URL url) {
-            return false;
-        }
-
-        @Override
-        public boolean unsubscribeURL(URL url) {
+        public boolean map(URL url) {
             return false;
         }
-
-        @Override
-        public void publishServiceDefinition(URL url) {
-
-        }
-
-        @Override
-        public Set<String> getCachedMapping(String mappingKey) {
-            return serviceToAppsMapping.get(mappingKey);
-        }
-
-        @Override
-        public Set<String> getCachedMapping(URL consumerURL) {
-            String serviceKey = ServiceNameMapping.buildMappingKey(consumerURL);
-            return serviceToAppsMapping.get(serviceKey);
-        }
-
-        @Override
-        public Set<String> removeCachedMapping(String serviceKey) {
-            return serviceToAppsMapping.remove(serviceKey);
-        }
-
-        @Override
-        public void putCachedMapping(String serviceKey, Set<String> apps) {
-            serviceToAppsMapping.put(serviceKey, new TreeSet<>(apps));
-        }
-
-        @Override
-        public Map<String, Set<String>> getCachedMapping() {
-            return serviceToAppsMapping;
-        }
-
-        @Override
-        public MetadataInfo getDefaultMetadataInfo() {
-            return null;
-        }
     }
 
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataInfoTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataInfoTest.java
index ca25300..8b05868 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataInfoTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataInfoTest.java
@@ -63,7 +63,7 @@ public class MetadataInfoTest {
         MetadataInfo metadataInfo = new MetadataInfo("demo");
 
         // export normal url again
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url));
+        metadataInfo.addService(url);
         MetadataInfo.ServiceInfo serviceInfo2 = metadataInfo.getServiceInfo(url.getProtocolServiceKey());
         assertNotNull(serviceInfo2);
         assertEquals(5, serviceInfo2.getParams().size());
@@ -80,31 +80,31 @@ public class MetadataInfoTest {
     public void testEqualsAndRevision() {
         // same metadata
         MetadataInfo metadataInfo = new MetadataInfo("demo");
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url));
+        metadataInfo.addService(url);
         MetadataInfo sameMetadataInfo = new MetadataInfo("demo");
-        sameMetadataInfo.addService(new MetadataInfo.ServiceInfo(url));
+        sameMetadataInfo.addService(url);
         assertEquals(metadataInfo, sameMetadataInfo);
         assertEquals(metadataInfo.calAndGetRevision(), sameMetadataInfo.calAndGetRevision());
 
         // url with different params that are not counted in ServiceInfo
         MetadataInfo metadataInfoWithDifferentParam1 = new MetadataInfo("demo");
-        metadataInfoWithDifferentParam1.addService(new MetadataInfo.ServiceInfo(url.addParameter("delay", 6000)));
+        metadataInfoWithDifferentParam1.addService(url.addParameter("delay", 6000));
         assertEquals(metadataInfo, metadataInfoWithDifferentParam1);
         assertEquals(metadataInfo.calAndGetRevision(), metadataInfoWithDifferentParam1.calAndGetRevision());
         // url with different params that are counted in ServiceInfo
         MetadataInfo metadataInfoWithDifferentParam2 = new MetadataInfo("demo");
-        metadataInfoWithDifferentParam2.addService(new MetadataInfo.ServiceInfo(url.addParameter(TIMEOUT_KEY, 6000)));
+        metadataInfoWithDifferentParam2.addService(url.addParameter(TIMEOUT_KEY, 6000));
         assertNotEquals(metadataInfo, metadataInfoWithDifferentParam2);
         assertNotEquals(metadataInfo.calAndGetRevision(), metadataInfoWithDifferentParam2.calAndGetRevision());
 
         MetadataInfo metadataInfoWithDifferentGroup = new MetadataInfo("demo");
-        metadataInfoWithDifferentGroup.addService(new MetadataInfo.ServiceInfo(url.addParameter(GROUP_KEY, "newGroup")));
+        metadataInfoWithDifferentGroup.addService(url.addParameter(GROUP_KEY, "newGroup"));
         assertNotEquals(metadataInfo, metadataInfoWithDifferentGroup);
         assertNotEquals(metadataInfo.calAndGetRevision(), metadataInfoWithDifferentGroup.calAndGetRevision());
 
         MetadataInfo metadataInfoWithDifferentServices = new MetadataInfo("demo");
-        metadataInfoWithDifferentServices.addService(new MetadataInfo.ServiceInfo(url));
-        metadataInfoWithDifferentServices.addService(new MetadataInfo.ServiceInfo(url2));
+        metadataInfoWithDifferentServices.addService(url);
+        metadataInfoWithDifferentServices.addService(url2);
         assertNotEquals(metadataInfo, metadataInfoWithDifferentServices);
         assertNotEquals(metadataInfo.calAndGetRevision(), metadataInfoWithDifferentServices.calAndGetRevision());
     }
@@ -112,12 +112,12 @@ public class MetadataInfoTest {
     @Test
     public void testChanged() {
         MetadataInfo metadataInfo = new MetadataInfo("demo");
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url));
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url2));
-        assertFalse(metadataInfo.hasReported());
-        metadataInfo.markReported();
-        assertTrue(metadataInfo.hasReported());
-        metadataInfo.removeService(new MetadataInfo.ServiceInfo(url2));
-        assertFalse(metadataInfo.hasReported());
+        metadataInfo.addService(url);
+        metadataInfo.addService(url2);
+        assertTrue(metadataInfo.updated.get());
+        metadataInfo.calAndGetRevision();
+        assertFalse(metadataInfo.updated.get());
+        metadataInfo.removeService(url2);
+        assertTrue(metadataInfo.updated.get());
     }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
index 4a9acc9..eb68abf 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.metadata.report.support;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.metadata.MappingListener;
 import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
 import org.apache.dubbo.metadata.report.MetadataReport;
 import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
@@ -79,6 +80,11 @@ public class AbstractMetadataReportFactoryTest {
                 }
 
                 @Override
+                public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+
+                }
+
+                @Override
                 public String getServiceDefinition(MetadataIdentifier consumerMetadataIdentifier) {
                     return null;
                 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
index 9d20e91..624a97f 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
@@ -17,10 +17,10 @@
 
 package org.apache.dubbo.metadata.report.support;
 
-import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.MappingListener;
 import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
 import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
 import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
@@ -28,6 +28,8 @@ import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.google.gson.Gson;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -314,6 +316,11 @@ public class AbstractMetadataReportTest {
         public String getServiceDefinition(MetadataIdentifier consumerMetadataIdentifier) {
             throw new UnsupportedOperationException("This extension does not support working as a remote metadata center.");
         }
+
+        @Override
+        public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+            throw new UnsupportedOperationException("This extension does not support working as a remote metadata center.");
+        }
     }
 
     private static class RetryMetadataReport extends AbstractMetadataReport {
@@ -376,6 +383,11 @@ public class AbstractMetadataReportTest {
             throw new UnsupportedOperationException("This extension does not support working as a remote metadata center.");
         }
 
+        @Override
+        public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+            throw new UnsupportedOperationException("This extension does not support working as a remote metadata center.");
+        }
+
     }
 
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
index c52aff9..8e94495 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
@@ -19,6 +19,7 @@ package org.apache.dubbo.metadata.test;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.metadata.MappingListener;
 import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
 import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
@@ -98,4 +99,9 @@ public class JTestMetadataReport4Test extends AbstractMetadataReport {
     public String getServiceDefinition(MetadataIdentifier consumerMetadataIdentifier) {
         return store.get(consumerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
     }
+
+    @Override
+    public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosConfigServiceWrapper.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosConfigServiceWrapper.java
index 54bfa60..3fb52fb 100644
--- a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosConfigServiceWrapper.java
+++ b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosConfigServiceWrapper.java
@@ -45,6 +45,10 @@ public class NacosConfigServiceWrapper {
         configService.addListener(handleInnerSymbol(dataId), handleInnerSymbol(group), listener);
     }
 
+    public void removeListener(String dataId, String group, Listener listener) throws NacosException {
+        configService.removeListener(handleInnerSymbol(dataId), handleInnerSymbol(group), listener);
+    }
+
     public String getConfig(String dataId, String group) throws NacosException {
         return configService.getConfig(handleInnerSymbol(dataId), handleInnerSymbol(group), DEFAULT_TIMEOUT);
     }
diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
index 30b26b7..bfe982f 100644
--- a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
@@ -34,7 +34,6 @@ import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
-import org.apache.dubbo.rpc.RpcException;
 
 import com.alibaba.nacos.api.NacosFactory;
 import com.alibaba.nacos.api.PropertyKeyConst;
@@ -243,6 +242,14 @@ public class NacosMetadataReport extends AbstractMetadataReport {
     }
 
     @Override
+    public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+        MappingDataListener mappingDataListener = casListenerMap.get(buildListenerKey(serviceKey, group));
+        if (null != mappingDataListener) {
+            removeCasServiceMappingListener(serviceKey, group, listener);
+        }
+    }
+
+    @Override
     public Set<String> getServiceAppMapping(String serviceKey, URL url) {
         String content = getConfig(serviceKey, DEFAULT_MAPPING_GROUP);
         return ServiceNameMapping.getAppNames(content);
@@ -263,6 +270,17 @@ public class NacosMetadataReport extends AbstractMetadataReport {
         addListener(serviceKey, DEFAULT_MAPPING_GROUP, mappingDataListener);
     }
 
+    private void removeCasServiceMappingListener(String serviceKey, String group, MappingListener listener) {
+        MappingDataListener mappingDataListener = casListenerMap.get(buildListenerKey(serviceKey, group));
+        if (mappingDataListener != null) {
+            mappingDataListener.removeListeners(listener);
+            if (mappingDataListener.isEmpty()) {
+                removeListener(serviceKey, DEFAULT_MAPPING_GROUP, mappingDataListener);
+                casListenerMap.remove(buildListenerKey(serviceKey, group), mappingDataListener);
+            }
+        }
+    }
+
     public void addListener(String key, String group, ConfigurationListener listener) {
         String listenerKey = buildListenerKey(key, group);
         NacosConfigListener nacosConfigListener =
@@ -275,6 +293,22 @@ public class NacosMetadataReport extends AbstractMetadataReport {
         }
     }
 
+    public void removeListener(String key, String group, ConfigurationListener listener) {
+        String listenerKey = buildListenerKey(key, group);
+        NacosConfigListener nacosConfigListener = watchListenerMap.get(listenerKey);
+        try {
+            if (nacosConfigListener != null) {
+                nacosConfigListener.removeListener(listener);
+                if (nacosConfigListener.isEmpty()) {
+                    configService.removeListener(key, group, nacosConfigListener);
+                    watchListenerMap.remove(listenerKey);
+                }
+            }
+        } catch (NacosException e) {
+            logger.error(e.getMessage());
+        }
+    }
+
     private NacosConfigListener createTargetListener(String key, String group) {
         NacosConfigListener configListener = new NacosConfigListener();
         configListener.fillContext(key, group);
@@ -294,7 +328,7 @@ public class NacosMetadataReport extends AbstractMetadataReport {
             }
         } catch (Throwable t) {
             logger.error("Failed to put " + identifier + " to nacos " + value + ", cause: " + t.getMessage(), t);
-            throw new RpcException("Failed to put " + identifier + " to nacos " + value + ", cause: " + t.getMessage(), t);
+            throw new RuntimeException("Failed to put " + identifier + " to nacos " + value + ", cause: " + t.getMessage(), t);
         }
     }
 
@@ -306,7 +340,7 @@ public class NacosMetadataReport extends AbstractMetadataReport {
             }
         } catch (Throwable t) {
             logger.error("Failed to remove " + identifier + " from nacos , cause: " + t.getMessage(), t);
-            throw new RpcException("Failed to remove " + identifier + " from nacos , cause: " + t.getMessage(), t);
+            throw new RuntimeException("Failed to remove " + identifier + " from nacos , cause: " + t.getMessage(), t);
         }
     }
 
@@ -315,7 +349,7 @@ public class NacosMetadataReport extends AbstractMetadataReport {
             return configService.getConfig(identifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), group, 3000L);
         } catch (Throwable t) {
             logger.error("Failed to get " + identifier + " from nacos , cause: " + t.getMessage(), t);
-            throw new RpcException("Failed to get " + identifier + " from nacos , cause: " + t.getMessage(), t);
+            throw new RuntimeException("Failed to get " + identifier + " from nacos , cause: " + t.getMessage(), t);
         }
     }
 
@@ -360,6 +394,10 @@ public class NacosMetadataReport extends AbstractMetadataReport {
             this.listeners.remove(configurationListener);
         }
 
+        boolean isEmpty() {
+            return this.listeners.isEmpty();
+        }
+
         private ConfigChangeType getChangeType(String configInfo, String oldValue) {
             if (StringUtils.isBlank(configInfo)) {
                 return ConfigChangeType.DELETED;
@@ -392,6 +430,14 @@ public class NacosMetadataReport extends AbstractMetadataReport {
             listeners.add(mappingListener);
         }
 
+        public void removeListeners(MappingListener mappingListener) {
+            listeners.remove(mappingListener);
+        }
+
+        public boolean isEmpty() {
+            return listeners.isEmpty();
+        }
+
         @Override
         public void process(ConfigChangedEvent event) {
             if (ConfigChangeType.DELETED == event.getChangeType()) {
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
index 78a1909..3909227 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReport.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.metadata.store.zookeeper;
 
-import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
 import org.apache.dubbo.common.logger.Logger;
@@ -35,6 +34,8 @@ import org.apache.dubbo.remoting.zookeeper.DataListener;
 import org.apache.dubbo.remoting.zookeeper.EventType;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperClient;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+
+import com.google.gson.Gson;
 import org.apache.zookeeper.data.Stat;
 
 import java.util.ArrayList;
@@ -161,6 +162,14 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
     }
 
     @Override
+    public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
+        String path = buildPathKey(DEFAULT_MAPPING_GROUP, serviceKey);
+        if (null != casListenerMap.get(path)) {
+            removeCasServiceMappingListener(path, listener);
+        }
+    }
+
+    @Override
     public Set<String> getServiceAppMapping(String serviceKey, URL url) {
         String path = buildPathKey(DEFAULT_MAPPING_GROUP, serviceKey);
         return getAppNames(zkClient.getContent(path));
@@ -204,6 +213,15 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
         zkClient.addDataListener(path, mappingDataListener);
     }
 
+    private void removeCasServiceMappingListener(String path, MappingListener listener) {
+        MappingDataListener mappingDataListener = casListenerMap.get(path);
+        mappingDataListener.removeListener(listener);
+        if (mappingDataListener.isEmpty()) {
+            zkClient.removeDataListener(path, mappingDataListener);
+            casListenerMap.remove(path, mappingDataListener);
+        }
+    }
+
     private static class MappingDataListener implements DataListener {
 
         private String serviceKey;
@@ -220,6 +238,14 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
             this.listeners.add(listener);
         }
 
+        public void removeListener(MappingListener listener) {
+            this.listeners.remove(listener);
+        }
+
+        public boolean isEmpty() {
+            return listeners.isEmpty();
+        }
+
         @Override
         public void dataChanged(String path, Object value, EventType eventType) {
             if (!this.path.equals(path)) {
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/PublishMetadata.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/PublishMetadata.java
index 97767b1..8cf435a 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/PublishMetadata.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/PublishMetadata.java
@@ -20,11 +20,9 @@ import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.utils.ArrayUtils;
-import org.apache.dubbo.config.deploy.DefaultApplicationDeployer;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
 import org.apache.dubbo.qos.command.annotation.Cmd;
-import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -52,24 +50,20 @@ public class  PublishMetadata implements BaseCommand {
         List<ApplicationModel> applicationModels = frameworkModel.getApplicationModels();
 
         for (ApplicationModel applicationModel : applicationModels) {
-            DefaultApplicationDeployer deployer = applicationModel.getBeanFactory().getBean(DefaultApplicationDeployer.class);
-            ServiceInstance serviceInstance = deployer.getServiceInstance();
-            if (serviceInstance != null) {
-                if (ArrayUtils.isEmpty(args)) {
-                    ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
-                    stringBuilder.append("publish metadata succeeded. App:").append(serviceInstance.getServiceName()).append("\n");
-                } else {
-                    try {
-                        int delay = Integer.parseInt(args[0]);
-                        ExecutorRepository executorRepository = applicationModel.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
-                        executorRepository.nextScheduledExecutor()
-                            .schedule(() -> ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance), delay, TimeUnit.SECONDS);
-                    } catch (NumberFormatException e) {
-                        logger.error("Wrong delay param", e);
-                        return "publishMetadata failed! Wrong delay param!";
-                    }
-                    stringBuilder.append("publish task submitted, will publish in ").append(args[0]).append(" seconds. App:").append(serviceInstance.getServiceName()).append("\n");
+            if (ArrayUtils.isEmpty(args)) {
+                ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel);
+                stringBuilder.append("publish metadata succeeded. App:").append(applicationModel.getApplicationName()).append("\n");
+            } else {
+                try {
+                    int delay = Integer.parseInt(args[0]);
+                    ExecutorRepository executorRepository = applicationModel.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+                    executorRepository.nextScheduledExecutor()
+                        .schedule(() -> ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel), delay, TimeUnit.SECONDS);
+                } catch (NumberFormatException e) {
+                    logger.error("Wrong delay param", e);
+                    return "publishMetadata failed! Wrong delay param!";
                 }
+                stringBuilder.append("publish task submitted, will publish in ").append(args[0]).append(" seconds. App:").append(applicationModel.getApplicationName()).append("\n");
             }
         }
         return stringBuilder.toString();
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryScopeModelInitializer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryScopeModelInitializer.java
index f780d0a..6be4e29 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryScopeModelInitializer.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryScopeModelInitializer.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.registry;
 
 import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -33,7 +32,6 @@ public class RegistryScopeModelInitializer implements ScopeModelInitializer {
     @Override
     public void initializeApplicationModel(ApplicationModel applicationModel) {
         ScopeBeanFactory beanFactory = applicationModel.getBeanFactory();
-        beanFactory.registerBean(RemoteMetadataServiceImpl.class);
         beanFactory.registerBean(RegistryManager.class);
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
index 0c606be..c2744e0 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
@@ -17,63 +17,94 @@
 package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
 
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.isInstanceUpdated;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.resetInstanceUpdateKey;
+import java.util.List;
+import java.util.Map;
 
-public abstract class AbstractServiceDiscovery implements ServiceDiscovery {
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
+import static org.apache.dubbo.metadata.RevisionResolver.EMPTY_REVISION;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.customizeInstance;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.setMetadataStorageType;
 
+/**
+ * Each service discovery is bond to one application.
+ */
+public abstract class AbstractServiceDiscovery implements ServiceDiscovery, ScopeModelAware {
+    private Logger logger = LoggerFactory.getLogger(AbstractServiceDiscovery.class);
     private volatile boolean isDestroy;
 
+    private final String serviceName;
     protected volatile ServiceInstance serviceInstance;
+    protected volatile MetadataInfo metadataInfo;
+    protected MetadataReport metadataReport;
+
+    private ApplicationModel applicationModel;
+
+    protected Map<String, MetadataInfo> revisionToMetadata;
+
+    public AbstractServiceDiscovery(String serviceName) {
+        this.serviceName = serviceName;
+        this.metadataInfo = new MetadataInfo(serviceName);
+    }
 
     @Override
-    public final ServiceInstance getLocalInstance() {
-        return serviceInstance;
+    public void setApplicationModel(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
     }
 
     @Override
     public final void initialize(URL registryURL) throws Exception {
         doInitialize(registryURL);
+        String registryCluster = registryURL.getParameter(REGISTRY_CLUSTER_KEY);
+        metadataReport = applicationModel.getBeanFactory().getBean(MetadataReportInstance.class).getMetadataReport(registryCluster);
     }
 
-    public abstract void doInitialize(URL registryURL) throws Exception;
-
-    @Override
-    public final void register(ServiceInstance serviceInstance) throws RuntimeException {
-        if (ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance) == null) {
-            ServiceInstanceMetadataUtils.calInstanceRevision(this, serviceInstance);
+    public final void register() throws RuntimeException {
+        this.serviceInstance = createServiceInstance();
+        customizeInstance(this.serviceInstance);
+        boolean revisionUpdated = calOrUpdateInstanceRevision();
+        if (revisionUpdated) {
+            reportMetadata();
+            doRegister(serviceInstance);
         }
-        doRegister(serviceInstance);
-        this.serviceInstance = serviceInstance;
     }
 
-    public abstract void doRegister(ServiceInstance serviceInstance) throws RuntimeException;
-
-
     @Override
-    public final void update(ServiceInstance serviceInstance) throws RuntimeException {
+    public final void update() throws RuntimeException {
         if (this.serviceInstance == null) {
-            this.register(serviceInstance);
-            return;
+            this.serviceInstance = createServiceInstance();
         }
-        if (!isInstanceUpdated(serviceInstance)) {
-            return;
+        boolean revisionUpdated = calOrUpdateInstanceRevision();
+        if (revisionUpdated) {
+            doUpdate();
         }
-        doUpdate(serviceInstance);
-        resetInstanceUpdateKey(serviceInstance);
-        this.serviceInstance = serviceInstance;
     }
 
-    public abstract void doUpdate(ServiceInstance serviceInstance) throws RuntimeException;
+    @Override
+    public final void unregister() throws RuntimeException {
+        doUnregister();
+    }
 
     @Override
-    public final void unregister(ServiceInstance serviceInstance) throws RuntimeException {
-        doUnregister(serviceInstance);
+    public final ServiceInstance getLocalInstance() {
+        return serviceInstance;
     }
 
-    public abstract void doUnregister(ServiceInstance serviceInstance);
+    @Override
+    public MetadataInfo getMetadata() {
+        return metadataInfo;
+    }
 
     @Override
     public final void destroy() throws Exception {
@@ -81,10 +112,76 @@ public abstract class AbstractServiceDiscovery implements ServiceDiscovery {
         doDestroy();
     }
 
-    public abstract void doDestroy() throws Exception;
-
     @Override
     public final boolean isDestroy() {
         return isDestroy;
     }
+
+    @Override
+    public void register(URL url) {
+        metadataInfo.addService(url);
+    }
+
+    @Override
+    public void unregister(URL url) {
+        metadataInfo.removeService(url);
+    }
+
+    @Override
+    public void subscribe(URL url, NotifyListener listener) {
+        metadataInfo.addSubscribedURL(url);
+    }
+
+    @Override
+    public void unsubscribe(URL url, NotifyListener listener) {
+        metadataInfo.removeSubscribedURL(url);
+    }
+
+    @Override
+    public List<URL> lookup(URL url) {
+       throw new UnsupportedOperationException("Service discovery implementation does not support lookup of url list.");
+    }
+
+    public void doUpdate() throws RuntimeException {
+        this.unregister();
+
+        reportMetadata();
+        this.register();
+    }
+
+    public abstract void doRegister(ServiceInstance serviceInstance) throws RuntimeException;
+
+    public abstract void doUnregister();
+
+    public abstract void doInitialize(URL registryURL) throws Exception;
+
+    public abstract void doDestroy() throws Exception;
+
+    private ServiceInstance createServiceInstance() {
+        DefaultServiceInstance instance = new DefaultServiceInstance(serviceName, applicationModel);
+        instance.setServiceMetadata(metadataInfo);
+        String metadataType = applicationModel.getApplicationConfigManager().getApplicationOrElseThrow().getMetadataType();
+        setMetadataStorageType(instance, metadataType);
+        ServiceInstanceMetadataUtils.customizeInstance(instance);
+        return instance;
+    }
+
+    protected boolean calOrUpdateInstanceRevision() {
+        String existingInstanceRevision = serviceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME);
+        String newRevision = metadataInfo.calAndGetRevision();
+        if (!newRevision.equals(existingInstanceRevision)) {
+            if (EMPTY_REVISION.equals(newRevision)) {
+                return false;
+            }
+            serviceInstance.getMetadata().put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, metadataInfo.calAndGetRevision());
+            return true;
+        }
+        return false;
+    }
+
+    protected void reportMetadata() {
+        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(serviceName, metadataInfo.calAndGetRevision());
+        metadataReport.publishAppMetadata(identifier, metadataInfo);
+    }
+
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscoveryFactory.java
index 07e1d60..a2d74d6 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscoveryFactory.java
@@ -17,6 +17,8 @@
 package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
 
 import java.util.Collections;
 import java.util.LinkedList;
@@ -31,10 +33,16 @@ import java.util.concurrent.ConcurrentMap;
  * @see ServiceDiscoveryFactory
  * @since 2.7.5
  */
-public abstract class AbstractServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public abstract class AbstractServiceDiscoveryFactory implements ServiceDiscoveryFactory, ScopeModelAware {
 
+    protected ApplicationModel applicationModel;
     private final ConcurrentMap<String, ServiceDiscovery> discoveries = new ConcurrentHashMap<>();
 
+    @Override
+    public void setApplicationModel(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+    }
+
     public List<ServiceDiscovery> getAllServiceDiscoveries() {
         return Collections.unmodifiableList(new LinkedList<>(discoveries.values()));
     }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
index 1590b21..fae8f22 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
@@ -1,215 +1,214 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client;
-
-import com.alibaba.fastjson.JSON;
-import org.apache.commons.io.FileUtils;
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
-import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
-import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.StringUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-import static com.alibaba.fastjson.JSON.toJSONString;
-import static java.lang.String.format;
-import static java.nio.channels.FileChannel.open;
-import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
-import static org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration.CONFIG_CENTER_DIR_PARAM_NAME;
-
-/**
- * File System {@link ServiceDiscovery} implementation
- *
- * @see FileSystemDynamicConfiguration
- * @since 2.7.5
- */
-public class FileSystemServiceDiscovery extends AbstractServiceDiscovery {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final Map<File, FileLock> fileLocksCache = new ConcurrentHashMap<>();
-
-    private FileSystemDynamicConfiguration dynamicConfiguration;
-
-    @Override
-    public void doInitialize(URL registryURL) throws Exception {
-        dynamicConfiguration = createDynamicConfiguration(registryURL);
-        registerDubboShutdownHook();
-        registerListener();
-    }
-
-    private void registerDubboShutdownHook() {
-        serviceInstance.getApplicationModel().getBeanFactory().getBean(ShutdownHookCallbacks.class)
-            .addCallback(this::destroy);
-    }
-
-    private void registerListener() {
-        getServices().forEach(serviceName -> {
-            dynamicConfiguration.getConfigKeys(DEFAULT_GROUP).forEach(serviceInstanceId -> {
-                dynamicConfiguration.addListener(serviceInstanceId, serviceName, this::onConfigChanged);
-            });
-        });
-    }
-
-    public void onConfigChanged(ConfigChangedEvent event) {
-
-    }
-
-    @Override
-    public void doDestroy() throws Exception {
-        dynamicConfiguration.close();
-        releaseAndRemoveRegistrationFiles();
-    }
-
-    private void releaseAndRemoveRegistrationFiles() {
-        fileLocksCache.keySet().forEach(file -> {
-            releaseFileLock(file);
-            removeFile(file);
-        });
-    }
-
-    private void removeFile(File file) {
-        FileUtils.deleteQuietly(file);
-    }
-
-    private String getServiceInstanceId(ServiceInstance serviceInstance) {
-        String id = serviceInstance.getAddress();
-        if (StringUtils.isBlank(id)) {
-            return serviceInstance.getHost() + "." + serviceInstance.getPort();
-        }
-        return id;
-    }
-
-    private String getServiceName(ServiceInstance serviceInstance) {
-        return serviceInstance.getServiceName();
-    }
-
-    @Override
-    public List<ServiceInstance> getInstances(String serviceName) {
-        return dynamicConfiguration.getConfigKeys(DEFAULT_GROUP)
-                .stream()
-                .map(serviceInstanceId -> dynamicConfiguration.getConfig(serviceInstanceId, serviceName))
-                .map(content -> JSON.parseObject(content, DefaultServiceInstance.class))
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public URL getUrl() {
-        return null;
-    }
-
-
-    @Override
-    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
-        this.serviceInstance = serviceInstance;
-        String serviceInstanceId = getServiceInstanceId(serviceInstance);
-        String serviceName = getServiceName(serviceInstance);
-        String content = toJSONString(serviceInstance);
-        if (dynamicConfiguration.publishConfig(serviceInstanceId, serviceName, content)) {
-            lockFile(serviceInstanceId, serviceName);
-        }
-    }
-
-    private void lockFile(String serviceInstanceId, String serviceName) {
-        File serviceInstanceFile = serviceInstanceFile(serviceInstanceId, serviceName);
-        Path serviceInstanceFilePath = serviceInstanceFile.toPath();
-
-        fileLocksCache.computeIfAbsent(serviceInstanceFile, file -> {
-            FileLock fileLock = null;
-            try {
-                FileChannel fileChannel = open(serviceInstanceFilePath, StandardOpenOption.READ, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS);
-                fileLock = fileChannel.tryLock();
-            } catch (IOException e) {
-                if (logger.isErrorEnabled()) {
-                    logger.error(e.getMessage(), e);
-                }
-            }
-            if (fileLock != null) {
-                if (logger.isInfoEnabled()) {
-                    logger.info(format("%s has been locked", serviceInstanceFilePath.toAbsolutePath()));
-                }
-            }
-            return fileLock;
-        });
-    }
-
-    @Override
-    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
-        register(serviceInstance);
-    }
-
-    @Override
-    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
-        String key = getServiceInstanceId(serviceInstance);
-        String group = getServiceName(serviceInstance);
-        releaseFileLock(key, group);
-        dynamicConfiguration.removeConfig(key, group);
-    }
-
-    private void releaseFileLock(String serviceInstanceId, String serviceName) {
-        File serviceInstanceFile = serviceInstanceFile(serviceInstanceId, serviceName);
-        releaseFileLock(serviceInstanceFile);
-    }
-
-    private void releaseFileLock(File serviceInstanceFile) {
-        fileLocksCache.computeIfPresent(serviceInstanceFile, (f, fileLock) -> {
-            releaseFileLock(fileLock);
-            if (logger.isInfoEnabled()) {
-                logger.info(format("The file[%s] has been released", serviceInstanceFile.getAbsolutePath()));
-            }
-            return null;
-        });
-    }
-
-    private void releaseFileLock(FileLock fileLock) {
-        try (FileChannel fileChannel = fileLock.channel()) {
-            fileLock.release();
-        } catch (IOException e) {
-            if (logger.isErrorEnabled()) {
-                logger.error(e.getMessage(), e);
-            }
-        }
-    }
-
-    private File serviceInstanceFile(String serviceInstanceId, String serviceName) {
-        return dynamicConfiguration.configFile(serviceInstanceId, serviceName);
-    }
-
-    @Override
-    public Set<String> getServices() {
-        return dynamicConfiguration.getConfigGroups();
-    }
-
-    private static FileSystemDynamicConfiguration createDynamicConfiguration(URL connectionURL) {
-        String path = System.getProperty("user.home") + File.separator + ".dubbo" + File.separator + "registry";
-        return new FileSystemDynamicConfiguration(connectionURL.addParameter(CONFIG_CENTER_DIR_PARAM_NAME, path));
-    }
-}
+///*
+// * Licensed to the Apache Software Foundation (ASF) under one or more
+// * contributor license agreements.  See the NOTICE file distributed with
+// * this work for additional information regarding copyright ownership.
+// * The ASF licenses this file to You under the Apache License, Version 2.0
+// * (the "License"); you may not use this file except in compliance with
+// * the License.  You may obtain a copy of the License at
+// *
+// *     http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// */
+//package org.apache.dubbo.registry.client;
+//
+//import com.alibaba.fastjson.JSON;
+//import org.apache.commons.io.FileUtils;
+//import org.apache.dubbo.common.URL;
+//import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
+//import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
+//import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
+//import org.apache.dubbo.common.logger.Logger;
+//import org.apache.dubbo.common.logger.LoggerFactory;
+//import org.apache.dubbo.common.utils.StringUtils;
+//
+//import java.io.File;
+//import java.io.IOException;
+//import java.nio.channels.FileChannel;
+//import java.nio.channels.FileLock;
+//import java.nio.file.LinkOption;
+//import java.nio.file.Path;
+//import java.nio.file.StandardOpenOption;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.Set;
+//import java.util.concurrent.ConcurrentHashMap;
+//import java.util.stream.Collectors;
+//
+//import static com.alibaba.fastjson.JSON.toJSONString;
+//import static java.lang.String.format;
+//import static java.nio.channels.FileChannel.open;
+//import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
+//import static org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration.CONFIG_CENTER_DIR_PARAM_NAME;
+//
+///**
+// * File System {@link ServiceDiscovery} implementation
+// *
+// * @see FileSystemDynamicConfiguration
+// * @since 2.7.5
+// */
+//public class FileSystemServiceDiscovery extends AbstractServiceDiscovery {
+//
+//    private final Logger logger = LoggerFactory.getLogger(getClass());
+//
+//    private final Map<File, FileLock> fileLocksCache = new ConcurrentHashMap<>();
+//
+//    private FileSystemDynamicConfiguration dynamicConfiguration;
+//
+//    public FileSystemServiceDiscovery(String serviceName) {
+//        super(serviceName);
+//    }
+//
+//    @Override
+//    public void doInitialize(URL registryURL) throws Exception {
+//        dynamicConfiguration = createDynamicConfiguration(registryURL);
+//        registerDubboShutdownHook();
+//        registerListener();
+//    }
+//
+//    private void registerDubboShutdownHook() {
+//        serviceInstance.getApplicationModel().getBeanFactory().getBean(ShutdownHookCallbacks.class)
+//            .addCallback(this::destroy);
+//    }
+//
+//    private void registerListener() {
+//        getServices().forEach(serviceName -> {
+//            dynamicConfiguration.getConfigKeys(DEFAULT_GROUP).forEach(serviceInstanceId -> {
+//                dynamicConfiguration.addListener(serviceInstanceId, serviceName, this::onConfigChanged);
+//            });
+//        });
+//    }
+//
+//    public void onConfigChanged(ConfigChangedEvent event) {
+//
+//    }
+//
+//    @Override
+//    public void doDestroy() throws Exception {
+//        dynamicConfiguration.close();
+//        releaseAndRemoveRegistrationFiles();
+//    }
+//
+//    private void releaseAndRemoveRegistrationFiles() {
+//        fileLocksCache.keySet().forEach(file -> {
+//            releaseFileLock(file);
+//            removeFile(file);
+//        });
+//    }
+//
+//    private void removeFile(File file) {
+//        FileUtils.deleteQuietly(file);
+//    }
+//
+//    private String getServiceInstanceId(ServiceInstance serviceInstance) {
+//        String id = serviceInstance.getAddress();
+//        if (StringUtils.isBlank(id)) {
+//            return serviceInstance.getHost() + "." + serviceInstance.getPort();
+//        }
+//        return id;
+//    }
+//
+//    private String getServiceName(ServiceInstance serviceInstance) {
+//        return serviceInstance.getServiceName();
+//    }
+//
+//    @Override
+//    public List<ServiceInstance> getInstances(String serviceName) {
+//        return dynamicConfiguration.getConfigKeys(DEFAULT_GROUP)
+//                .stream()
+//                .map(serviceInstanceId -> dynamicConfiguration.getConfig(serviceInstanceId, serviceName))
+//                .map(content -> JSON.parseObject(content, DefaultServiceInstance.class))
+//                .collect(Collectors.toList());
+//    }
+//
+//    @Override
+//    public URL getUrl() {
+//        return null;
+//    }
+//
+//
+//    @Override
+//    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
+//        this.serviceInstance = serviceInstance;
+//        String serviceInstanceId = getServiceInstanceId(serviceInstance);
+//        String serviceName = getServiceName(serviceInstance);
+//        String content = toJSONString(serviceInstance);
+//        if (dynamicConfiguration.publishConfig(serviceInstanceId, serviceName, content)) {
+//            lockFile(serviceInstanceId, serviceName);
+//        }
+//    }
+//
+//    private void lockFile(String serviceInstanceId, String serviceName) {
+//        File serviceInstanceFile = serviceInstanceFile(serviceInstanceId, serviceName);
+//        Path serviceInstanceFilePath = serviceInstanceFile.toPath();
+//
+//        fileLocksCache.computeIfAbsent(serviceInstanceFile, file -> {
+//            FileLock fileLock = null;
+//            try {
+//                FileChannel fileChannel = open(serviceInstanceFilePath, StandardOpenOption.READ, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS);
+//                fileLock = fileChannel.tryLock();
+//            } catch (IOException e) {
+//                if (logger.isErrorEnabled()) {
+//                    logger.error(e.getMessage(), e);
+//                }
+//            }
+//            if (fileLock != null) {
+//                if (logger.isInfoEnabled()) {
+//                    logger.info(format("%s has been locked", serviceInstanceFilePath.toAbsolutePath()));
+//                }
+//            }
+//            return fileLock;
+//        });
+//    }
+//
+//    @Override
+//    public void doUnregister() throws RuntimeException {
+//        String key = getServiceInstanceId(serviceInstance);
+//        String group = getServiceName(serviceInstance);
+//        releaseFileLock(key, group);
+//        dynamicConfiguration.removeConfig(key, group);
+//    }
+//
+//    private void releaseFileLock(String serviceInstanceId, String serviceName) {
+//        File serviceInstanceFile = serviceInstanceFile(serviceInstanceId, serviceName);
+//        releaseFileLock(serviceInstanceFile);
+//    }
+//
+//    private void releaseFileLock(File serviceInstanceFile) {
+//        fileLocksCache.computeIfPresent(serviceInstanceFile, (f, fileLock) -> {
+//            releaseFileLock(fileLock);
+//            if (logger.isInfoEnabled()) {
+//                logger.info(format("The file[%s] has been released", serviceInstanceFile.getAbsolutePath()));
+//            }
+//            return null;
+//        });
+//    }
+//
+//    private void releaseFileLock(FileLock fileLock) {
+//        try (FileChannel fileChannel = fileLock.channel()) {
+//            fileLock.release();
+//        } catch (IOException e) {
+//            if (logger.isErrorEnabled()) {
+//                logger.error(e.getMessage(), e);
+//            }
+//        }
+//    }
+//
+//    private File serviceInstanceFile(String serviceInstanceId, String serviceName) {
+//        return dynamicConfiguration.configFile(serviceInstanceId, serviceName);
+//    }
+//
+//    @Override
+//    public Set<String> getServices() {
+//        return dynamicConfiguration.getConfigGroups();
+//    }
+//
+//    private static FileSystemDynamicConfiguration createDynamicConfiguration(URL connectionURL) {
+//        String path = System.getProperty("user.home") + File.separator + ".dubbo" + File.separator + "registry";
+//        return new FileSystemDynamicConfiguration(connectionURL.addParameter(CONFIG_CENTER_DIR_PARAM_NAME, path));
+//    }
+//}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java
deleted file mode 100644
index 30fc1d8..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
-import org.apache.dubbo.common.utils.NetUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.RevisionResolver;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.registry.Constants;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
-import org.apache.dubbo.registry.client.metadata.MetadataUtils;
-import org.apache.dubbo.rpc.RpcException;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.ScopeModelAware;
-import org.apache.dubbo.rpc.model.ScopeModelUtil;
-
-import com.alibaba.fastjson.JSONObject;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public abstract class SelfHostMetaServiceDiscovery implements ServiceDiscovery, ScopeModelAware {
-
-    private volatile boolean isDestroy;
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private URL registryURL;
-
-    /**
-     * Echo check if consumer is still work
-     * echo task may take a lot of time when consumer offline, create a new ScheduledThreadPool
-     */
-    private final ScheduledExecutorService echoCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-Registry-EchoCheck-Consumer"));
-
-    // =================================== Provider side =================================== //
-
-    private ServiceInstance serviceInstance;
-
-    /**
-     * Local {@link ServiceInstance} Metadata's revision
-     */
-    private String lastMetadataRevision;
-
-    // =================================== Consumer side =================================== //
-
-    /**
-     * Local Cache of {@link ServiceInstance} Metadata
-     * <p>
-     * Key - {@link ServiceInstance} ID ( usually ip + port )
-     * Value - Json processed metadata string
-     */
-    private final ConcurrentHashMap<String, String> metadataMap = new ConcurrentHashMap<>();
-
-    /**
-     * Local Cache of {@link ServiceInstance}
-     * <p>
-     * Key - Service Name
-     * Value - List {@link ServiceInstance}
-     */
-    private final ConcurrentHashMap<String, List<ServiceInstance>> cachedServiceInstances = new ConcurrentHashMap<>();
-
-    /**
-     * Local Cache of Service's {@link ServiceInstance} list revision,
-     * used to check if {@link ServiceInstance} list has been updated
-     * <p>
-     * Key - ServiceName
-     * Value - a revision calculate from {@link List} of {@link ServiceInstance}
-     */
-    private final ConcurrentHashMap<String, String> serviceInstanceRevisionMap = new ConcurrentHashMap<>();
-    private ApplicationModel applicationModel;
-    private WritableMetadataService metadataService;
-
-    @Override
-    public void setApplicationModel(ApplicationModel applicationModel) {
-        this.applicationModel = applicationModel;
-        metadataService = WritableMetadataService.getDefaultExtension(applicationModel);
-
-    }
-
-    @Override
-    public void initialize(URL registryURL) throws Exception {
-        this.registryURL = registryURL;
-        doInitialize(registryURL);
-        long echoPollingCycle = registryURL.getParameter(Constants.ECHO_POLLING_CYCLE_KEY, Constants.DEFAULT_ECHO_POLLING_CYCLE);
-
-        // Echo check: test if consumer is offline, remove MetadataChangeListener,
-        // reduce the probability of failure when metadata update
-        echoCheckExecutor.scheduleAtFixedRate(() -> {
-            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
-            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
-
-            while (iterator.hasNext()) {
-                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
-                try {
-                    entry.getValue().echo(CommonConstants.DUBBO);
-                } catch (RpcException e) {
-                    if (logger.isInfoEnabled()) {
-                        logger.info("Send echo message to consumer error. Possible cause: consumer is offline.");
-                    }
-                    iterator.remove();
-                }
-            }
-        }, echoPollingCycle, echoPollingCycle, TimeUnit.MILLISECONDS);
-    }
-
-    @Override
-    public void destroy() throws Exception {
-        isDestroy = true;
-        doDestroy();
-        metadataMap.clear();
-        serviceInstanceRevisionMap.clear();
-        echoCheckExecutor.shutdown();
-    }
-
-    @Override
-    public boolean isDestroy() {
-        return isDestroy;
-    }
-
-    private void updateMetadata(ServiceInstance serviceInstance) {
-        String metadataString = JSONObject.toJSONString(serviceInstance.getMetadata());
-        String metadataRevision = RevisionResolver.calRevision(metadataString);
-
-        // check if metadata updated
-        if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
-            }
-
-            lastMetadataRevision = metadataRevision;
-
-            // save newest metadata to local
-            metadataService.exportInstanceMetadata(metadataString);
-
-            // notify to consumer
-            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
-            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
-
-            while (iterator.hasNext()) {
-                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
-                try {
-                    entry.getValue().onEvent(metadataString);
-                } catch (RpcException e) {
-                    logger.warn("Notify to consumer error. Possible cause: consumer is offline.");
-                    // remove listener if consumer is offline
-                    iterator.remove();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void register(ServiceInstance serviceInstance) throws RuntimeException {
-        this.serviceInstance = serviceInstance;
-
-        updateMetadata(serviceInstance);
-
-        doRegister(serviceInstance);
-    }
-
-    @Override
-    public void update(ServiceInstance serviceInstance) throws RuntimeException {
-        this.serviceInstance = serviceInstance;
-
-        updateMetadata(serviceInstance);
-
-        doUpdate(serviceInstance);
-    }
-
-    @Override
-    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
-        doUnregister(serviceInstance);
-
-        this.serviceInstance = null;
-
-        // notify empty message to consumer
-        metadataService.exportInstanceMetadata("");
-        metadataService.getInstanceMetadataChangedListenerMap().forEach((consumerId, listener) -> listener.onEvent(""));
-        metadataService.getInstanceMetadataChangedListenerMap().clear();
-    }
-
-    @Override
-    public ServiceInstance getLocalInstance() {
-        return serviceInstance;
-    }
-
-    @Override
-    public URL getUrl() {
-        return registryURL;
-    }
-
-    @SuppressWarnings("unchecked")
-    public final void fillServiceInstance(DefaultServiceInstance serviceInstance) {
-        String hostId = serviceInstance.getAddress();
-        if (metadataMap.containsKey(hostId)) {
-            // Use cached metadata.
-            // Metadata will be updated by provider callback
-
-            String metadataString = metadataMap.get(hostId);
-            serviceInstance.setMetadata(JSONObject.parseObject(metadataString, Map.class));
-        } else {
-            // refer from MetadataUtils, this proxy is different from the one used to refer exportedURL
-            MetadataService metadataService = MetadataUtils.getMetadataServiceProxy(serviceInstance);
-
-            String consumerId = ScopeModelUtil.getApplicationModel(registryURL.getScopeModel()).getApplicationName() + NetUtils.getLocalHost();
-            String metadata = metadataService.getAndListenInstanceMetadata(
-                    consumerId, metadataString -> {
-                        if(logger.isDebugEnabled()) {
-                            logger.debug("Receive callback: " + metadataString + serviceInstance);
-                        }
-                        if (StringUtils.isEmpty(metadataString)) {
-                            // provider is shutdown
-                            metadataMap.remove(hostId);
-                        } else {
-                            metadataMap.put(hostId, metadataString);
-                        }
-                    });
-            metadataMap.put(hostId, metadata);
-            serviceInstance.setMetadata(JSONObject.parseObject(metadata, Map.class));
-        }
-    }
-
-    public final void notifyListener(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> instances) {
-        String serviceInstanceRevision = RevisionResolver.calRevision(JSONObject.toJSONString(instances));
-        boolean changed = !serviceInstanceRevision.equalsIgnoreCase(
-                serviceInstanceRevisionMap.put(serviceName, serviceInstanceRevision));
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Service changed event received (possibly because of DNS polling). " +
-                    "Service Instance changed: " + changed + " Service Name: " + serviceName);
-        }
-
-        if (changed) {
-            List<ServiceInstance> oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>());
-
-            // remove expired invoker
-            Set<ServiceInstance> allServiceInstances = new HashSet<>(oldServiceInstances.size() + instances.size());
-            allServiceInstances.addAll(oldServiceInstances);
-            allServiceInstances.addAll(instances);
-
-            allServiceInstances.removeAll(oldServiceInstances);
-
-            allServiceInstances.forEach(removedServiceInstance -> {
-                MetadataUtils.destroyMetadataServiceProxy(removedServiceInstance);
-            });
-
-            cachedServiceInstances.put(serviceName, instances);
-            listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
-        }
-    }
-
-    public void doInitialize(URL registryURL) throws Exception {
-    }
-
-    public void doDestroy() throws Exception {
-    }
-
-    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
-
-    }
-
-    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
-
-    }
-
-    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
-
-    }
-
-    /**
-     * UT used only
-     */
-    @Deprecated
-    public final ConcurrentHashMap<String, List<ServiceInstance>> getCachedServiceInstances() {
-        return cachedServiceInstances;
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java
index 5f2533d..5652e19 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java
@@ -20,9 +20,8 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.common.lang.Prioritized;
 import org.apache.dubbo.common.utils.Page;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.registry.NotifyListener;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.registry.RegistryService;
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
 
 import java.util.LinkedHashMap;
@@ -30,7 +29,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Stream;
 
 import static java.util.Collections.unmodifiableList;
 import static java.util.Collections.unmodifiableMap;
@@ -43,10 +41,7 @@ import static org.apache.dubbo.common.extension.ExtensionScope.APPLICATION;
  * @since 2.7.5
  */
 @SPI(value = "zookeeper", scope = APPLICATION)
-public interface ServiceDiscovery extends Prioritized {
-
-    // ==================================== Lifecycle ==================================== //
-
+public interface ServiceDiscovery extends RegistryService, Prioritized {
     /**
      * Initializes the {@link ServiceDiscovery}
      *
@@ -64,37 +59,12 @@ public interface ServiceDiscovery extends Prioritized {
 
     boolean isDestroy();
 
-    // ==================================================================================== //
 
-    // =================================== Registration =================================== //
+    void register() throws RuntimeException;
 
-    /**
-     * Registers an instance of {@link ServiceInstance}.
-     *
-     * @param serviceInstance an instance of {@link ServiceInstance} to be registered
-     * @throws RuntimeException if failed
-     */
-    void register(ServiceInstance serviceInstance) throws RuntimeException;
+    void update() throws RuntimeException;
 
-    /**
-     * Updates the registered {@link ServiceInstance}.
-     *
-     * @param serviceInstance the registered {@link ServiceInstance}
-     * @throws RuntimeException if failed
-     */
-    void update(ServiceInstance serviceInstance) throws RuntimeException;
-
-    /**
-     * Unregisters an instance of {@link ServiceInstance}.
-     *
-     * @param serviceInstance an instance of {@link ServiceInstance} to be unregistered
-     * @throws RuntimeException if failed
-     */
-    void unregister(ServiceInstance serviceInstance) throws RuntimeException;
-
-    // ==================================================================================== //
-
-    // ==================================== Discovery ===================================== //
+    void unregister() throws RuntimeException;
 
     /**
      * Get the default size of pagination query
@@ -196,44 +166,12 @@ public interface ServiceDiscovery extends Prioritized {
         return unmodifiableMap(instances);
     }
 
-    default void dispatchServiceInstancesChangedEvent(String serviceName) {
-        dispatchServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
-    }
-
-    default void dispatchServiceInstancesChangedEvent(String serviceName, String... otherServiceNames) {
-        dispatchServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
-        if (otherServiceNames != null) {
-            Stream.of(otherServiceNames)
-                    .filter(StringUtils::isNotEmpty)
-                    .forEach(this::dispatchServiceInstancesChangedEvent);
-        }
-    }
-
-    default void dispatchServiceInstancesChangedEvent(String serviceName, List<ServiceInstance> serviceInstances) {
-        dispatchServiceInstancesChangedEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances));
-    }
-
-    default void dispatchServiceInstancesChangedEvent(ServiceInstancesChangedEvent event) {}
-
-    /**
-     * Add an instance of {@link ServiceInstancesChangedListener} for specified service
-     * <p>
-     * Default, Current method will be invoked by {@link ServiceDiscoveryRegistry#subscribe(URL, NotifyListener)
-     * the ServiceDiscoveryRegistry on the subscription}, this method is used to
-     * trigger or adapt the vendor's change notification mechanism typically, like Zookeeper Watcher,
-     * Nacos EventListener. If the registry observes the change, It's suggested that the implementation could invoke
-     * {@link #dispatchServiceInstancesChangedEvent(String)} method or variants
-     *
-     * @param listener an instance of {@link ServiceInstancesChangedListener}
-     * @throws NullPointerException
-     * @throws IllegalArgumentException
-     */
     default void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
             throws NullPointerException, IllegalArgumentException {
     }
 
     /**
-     * unsubscribe to instances change event.
+     * unsubscribe to instance change event.
      *
      * @param listener
      * @throws IllegalArgumentException
@@ -246,16 +184,14 @@ public interface ServiceDiscovery extends Prioritized {
         return new ServiceInstancesChangedListener(serviceNames, this);
     }
 
-    // ==================================================================================== //
+    ServiceInstance getLocalInstance();
 
-//    String getKey(URL exportedURL);
+    MetadataInfo getMetadata();
 
     default URL getUrl() {
         return null;
     }
 
-    ServiceInstance getLocalInstance();
-
     default long getDelay() {
         return getUrl().getParameter(REGISTRY_DELAY_NOTIFICATION_KEY, 5000);
     }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
index f94cce2..2168095 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
@@ -20,13 +20,15 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.SPI;
 
+import static org.apache.dubbo.common.extension.ExtensionScope.APPLICATION;
+
 /**
  * The factory to create {@link ServiceDiscovery}
  *
  * @see ServiceDiscovery
  * @since 2.7.5
  */
-@SPI("default")
+@SPI(value = "default", scope = APPLICATION)
 public interface ServiceDiscoveryFactory {
 
     /**
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
index 079653c..320577b 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
@@ -18,13 +18,13 @@ package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.function.ThrowableAction;
 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.metadata.MappingChangedEvent;
 import org.apache.dubbo.metadata.MappingListener;
 import org.apache.dubbo.metadata.ServiceNameMapping;
-import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
@@ -41,7 +41,6 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static java.lang.String.format;
 import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
@@ -56,6 +55,8 @@ import static org.apache.dubbo.metadata.ServiceNameMapping.toStringKeys;
 import static org.apache.dubbo.registry.client.ServiceDiscoveryFactory.getExtension;
 
 /**
+ * TODO, this bridge implementation is not necessary now, protocol can interact with service discovery directly.
+ *
  * ServiceDiscoveryRegistry is a very special Registry implementation, which is used to bridge the old interface-level service discovery model
  * with the new service discovery model introduced in 3.0 in a compatible manner.
  * <p>
@@ -74,7 +75,7 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
 
     private final ServiceDiscovery serviceDiscovery;
 
-    private final WritableMetadataService writableMetadataService;
+    private final ServiceNameMapping serviceNameMapping;
 
     /* apps - listener */
     private final Map<String, ServiceInstancesChangedListener> serviceListeners = new ConcurrentHashMap<>();
@@ -84,15 +85,15 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
     public ServiceDiscoveryRegistry(URL registryURL) {
         super(registryURL);
         this.serviceDiscovery = createServiceDiscovery(registryURL);
-        this.writableMetadataService = WritableMetadataService.getDefaultExtension(registryURL.getScopeModel());
+        this.serviceNameMapping = ServiceNameMapping.getDefaultExtension(registryURL.getScopeModel());
         this.registryManager = registryURL.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
     }
 
     // Currently, for test purpose
-    protected ServiceDiscoveryRegistry(URL registryURL, ServiceDiscovery serviceDiscovery, WritableMetadataService writableMetadataService) {
+    protected ServiceDiscoveryRegistry(URL registryURL, ServiceDiscovery serviceDiscovery, ServiceNameMapping serviceNameMapping) {
         super(registryURL);
         this.serviceDiscovery = serviceDiscovery;
-        this.writableMetadataService = writableMetadataService;
+        this.serviceNameMapping = serviceNameMapping;
     }
 
     public ServiceDiscovery getServiceDiscovery() {
@@ -107,8 +108,9 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
      */
     protected ServiceDiscovery createServiceDiscovery(URL registryURL) {
         ServiceDiscovery serviceDiscovery = getServiceDiscovery(registryURL);
-        execute(() -> serviceDiscovery.initialize(registryURL.addParameter(INTERFACE_KEY, ServiceDiscovery.class.getName())
-            .removeParameter(REGISTRY_TYPE_KEY)));
+        final ThrowableAction throwableAction = () -> serviceDiscovery.initialize(registryURL.addParameter(INTERFACE_KEY, ServiceDiscovery.class.getName())
+            .removeParameter(REGISTRY_TYPE_KEY));
+        execute(throwableAction);
         return serviceDiscovery;
     }
 
@@ -163,16 +165,9 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
 
     @Override
     public void doRegister(URL url) {
+        // fixme, add registry-cluster is not necessary anymore
         url = addRegistryClusterKey(url);
-        if (writableMetadataService.exportURL(url)) {
-            if (logger.isInfoEnabled()) {
-                logger.info(format("The URL[%s] registered successfully.", url.toString()));
-            }
-        } else {
-            if (logger.isWarnEnabled()) {
-                logger.warn(format("The URL[%s] has been registered.", url.toString()));
-            }
-        }
+        serviceDiscovery.register(url);
     }
 
     @Override
@@ -185,16 +180,9 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
 
     @Override
     public void doUnregister(URL url) {
+        // fixme, add registry-cluster is not necessary anymore
         url = addRegistryClusterKey(url);
-        if (writableMetadataService.unexportURL(url)) {
-            if (logger.isInfoEnabled()) {
-                logger.info(format("The URL[%s] deregistered successfully.", url.toString()));
-            }
-        } else {
-            if (logger.isWarnEnabled()) {
-                logger.warn(format("The URL[%s] has been deregistered.", url.toString()));
-            }
-        }
+       serviceDiscovery.unregister(url);
     }
 
     @Override
@@ -202,20 +190,21 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
         if (!shouldSubscribe(url)) { // Should Not Subscribe
             return;
         }
-        url = addRegistryClusterKey(url);
         doSubscribe(url, listener);
     }
 
     @Override
     public void doSubscribe(URL url, NotifyListener listener) {
-        writableMetadataService.subscribeURL(url);
+        url = addRegistryClusterKey(url);
+
+        serviceDiscovery.subscribe(url, listener);
 
         boolean check = url.getParameter(CHECK_KEY, false);
 
         Set<String> subscribedServices = Collections.emptySet();
         try {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(this.getUrl().getScopeModel());
-            subscribedServices = serviceNameMapping.getAndListenServices(this.getUrl(), url, new DefaultMappingListener(url, subscribedServices, listener));
+            subscribedServices = serviceNameMapping.getAndListen(this.getUrl(), url, new DefaultMappingListener(url, subscribedServices, listener));
         } catch (Exception e) {
             logger.warn("Cannot find app mapping for service " + url.getServiceInterface() + ", will not migrate.", e);
         }
@@ -250,9 +239,10 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
     @Override
     public void doUnsubscribe(URL url, NotifyListener listener) {
         // TODO: remove service name mapping listener
-        writableMetadataService.unsubscribeURL(url);
+        serviceDiscovery.unsubscribe(url, listener);
         String protocolServiceKey = url.getServiceKey() + GROUP_CHAR_SEPARATOR + url.getParameter(PROTOCOL_KEY, DUBBO);
-        Set<String> serviceNames = writableMetadataService.getCachedMapping(url);
+        Set<String> serviceNames = serviceNameMapping.getCachedMapping(url);
+        serviceNameMapping.stopListen(url);
         if (CollectionUtils.isNotEmpty(serviceNames)) {
             String serviceNamesKey = toStringKeys(serviceNames);
             ServiceInstancesChangedListener instancesChangedListener = serviceListeners.get(serviceNamesKey);
@@ -359,6 +349,7 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
         private URL url;
         private Set<String> oldApps;
         private NotifyListener listener;
+        private boolean stopped;
 
         public DefaultMappingListener(URL subscribedURL, Set<String> serviceNames, NotifyListener listener) {
             this.url = subscribedURL;
@@ -371,6 +362,10 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
             if (logger.isDebugEnabled()) {
                 logger.debug("Received mapping notification from meta server, " + event);
             }
+            if (stopped) {
+                logger.warn("Listener has been stopped, ignore mapping notification, check why listener is not removed.");
+                return;
+            }
             Set<String> newApps = event.getApps();
             Set<String> tempOldApps = oldApps;
             oldApps = newApps;
@@ -380,18 +375,24 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
             }
 
             if (CollectionUtils.isEmpty(tempOldApps) && newApps.size() > 0) {
-                writableMetadataService.putCachedMapping(ServiceNameMapping.buildMappingKey(url), newApps);
+                serviceNameMapping.putCachedMapping(ServiceNameMapping.buildMappingKey(url), newApps);
                 subscribeURLs(url, listener, newApps);
                 return;
             }
 
             for (String newAppName : newApps) {
                 if (!tempOldApps.contains(newAppName)) {
-                    writableMetadataService.putCachedMapping(ServiceNameMapping.buildMappingKey(url), newApps);
+                    serviceNameMapping.removeCachedMapping(ServiceNameMapping.buildMappingKey(url));
+                    serviceNameMapping.putCachedMapping(ServiceNameMapping.buildMappingKey(url), newApps);
                     subscribeURLs(url, listener, newApps);
                     return;
                 }
             }
         }
+
+        @Override
+        public void stop() {
+            stopped = true;
+        }
     }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
index faf8578..0b28109 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
@@ -24,17 +24,13 @@ import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
-import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.RegistryClusterIdentifier;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.event.RetryServiceInstancesChangedEvent;
 import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-import org.apache.dubbo.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 
 import java.util.ArrayList;
@@ -52,18 +48,13 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.metadata.RevisionResolver.EMPTY_REVISION;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision;
 
 /**
- * The Service Discovery Changed Listener
- *
- * @see ServiceInstancesChangedEvent
- * @since 2.7.5
+ * TODO, refactor to move revision-metadata mapping to ServiceDiscovery. Instances should already being mapped with metadata when reached here.
  */
 public class ServiceInstancesChangedListener {
 
@@ -79,7 +70,6 @@ public class ServiceInstancesChangedListener {
 
     protected Map<String, List<ServiceInstance>> allInstances;
     protected Map<String, Object> serviceUrls;
-    protected Map<String, MetadataInfo> revisionToMetadata;
 
     private volatile long lastRefreshTime;
     private Semaphore retryPermission;
@@ -93,7 +83,6 @@ public class ServiceInstancesChangedListener {
         this.listenerQueue = new ConcurrentLinkedQueue<>();
         this.allInstances = new HashMap<>();
         this.serviceUrls = new HashMap<>();
-        this.revisionToMetadata = new HashMap<>();
         retryPermission = new Semaphore(1);
         this.scheduler = ScopeModelUtil.getApplicationModel(serviceDiscovery == null || serviceDiscovery.getUrl() == null ? null : serviceDiscovery.getUrl().getScopeModel())
             .getExtensionLoader(ExecutorRepository.class).getDefaultExtension().getMetadataRetryExecutor();
@@ -105,7 +94,7 @@ public class ServiceInstancesChangedListener {
      * @param event {@link ServiceInstancesChangedEvent}
      */
     public synchronized void onEvent(ServiceInstancesChangedEvent event) {
-        if (destroyed.get() || !accept(event) || isRetryAndExpired(event)) {
+        if (destroyed.get() || !accept(event)) {
             return;
         }
 
@@ -138,32 +127,12 @@ public class ServiceInstancesChangedListener {
         // get MetadataInfo with revision
         for (Map.Entry<String, List<ServiceInstance>> entry : revisionToInstances.entrySet()) {
             String revision = entry.getKey();
-            List<ServiceInstance> subInstances = entry.getValue();
-            ServiceInstance instance = selectInstance(subInstances);
-            MetadataInfo metadata = getRemoteMetadata(revision, localServiceToRevisions, instance);
-            // update metadata into each instance, in case new instance created.
-            for (ServiceInstance tmpInstance : subInstances) {
-                ((DefaultServiceInstance) tmpInstance).setServiceMetadata(metadata);
-            }
-//            ((DefaultServiceInstance) instance).setServiceMetadata(metadata);
-            newRevisionToMetadata.putIfAbsent(revision, metadata);
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug(newRevisionToMetadata.size() + " unique revisions: " + newRevisionToMetadata.keySet());
-        }
-
-        if (hasEmptyMetadata(newRevisionToMetadata)) {// retry every 10 seconds
-            if (retryPermission.tryAcquire()) {
-                retryFuture = scheduler.schedule(new AddressRefreshRetryTask(retryPermission, event.getServiceName()), 10_000L, TimeUnit.MILLISECONDS);
-                logger.warn("Address refresh try task submitted.");
-            }
-            logger.error("Address refresh failed because of Metadata Server failure, wait for retry or new address refresh event.");
-            return;
+            List<ServiceInstance> instances = entry.getValue();
+            // instances should never be empty.
+            DefaultServiceInstance instance = (DefaultServiceInstance)instances.iterator().next();
+            parseMetadata(revision, instance.getServiceMetadata(), localServiceToRevisions);
         }
 
-        this.revisionToMetadata = newRevisionToMetadata;
-
         Map<String, Map<Set<String>, Object>> protocolRevisionsToUrls = new HashMap<>();
         Map<String, Object> newServiceUrls = new HashMap<>();
         for (Map.Entry<String, Map<String, Set<String>>> entry : localServiceToRevisions.entrySet()) {
@@ -259,14 +228,6 @@ public class ServiceInstancesChangedListener {
         return allInstances.get(appName);
     }
 
-    public Map<String, MetadataInfo> getRevisionToMetadata() {
-        return revisionToMetadata;
-    }
-
-    public MetadataInfo getMetadata(String revision) {
-        return revisionToMetadata.get(revision);
-    }
-
     /**
      * @param event {@link ServiceInstancesChangedEvent event}
      * @return If service name matches, return <code>true</code>, or <code>false</code>
@@ -275,19 +236,6 @@ public class ServiceInstancesChangedListener {
         return serviceNames.contains(event.getServiceName());
     }
 
-    protected boolean isRetryAndExpired(ServiceInstancesChangedEvent event) {
-        if (event instanceof RetryServiceInstancesChangedEvent) {
-            RetryServiceInstancesChangedEvent retryEvent = (RetryServiceInstancesChangedEvent) event;
-            logger.warn("Received address refresh retry event, " + retryEvent.getFailureRecordTime());
-            if (retryEvent.getFailureRecordTime() < lastRefreshTime && !hasEmptyMetadata(revisionToMetadata)) {
-                logger.warn("Ignore retry event, event time: " + retryEvent.getFailureRecordTime() + ", last refresh time: " + lastRefreshTime);
-                return true;
-            }
-            logger.warn("Retrying address notification...");
-        }
-        return false;
-    }
-
     private void refreshInstance(ServiceInstancesChangedEvent event) {
         if (event instanceof RetryServiceInstancesChangedEvent) {
             return;
@@ -311,42 +259,6 @@ public class ServiceInstancesChangedListener {
         return false;
     }
 
-    protected MetadataInfo getRemoteMetadata(String revision, Map<String, Map<String, Set<String>>> localServiceToRevisions, ServiceInstance instance) {
-        MetadataInfo metadata = revisionToMetadata.get(revision);
-
-        if (metadata != null && metadata != MetadataInfo.EMPTY) {
-            // metadata loaded from cache
-            if (logger.isDebugEnabled()) {
-                logger.debug("MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision
-                    + "&cluster=" + instance.getRegistryCluster() + ", " + metadata);
-            }
-            parseMetadata(revision, metadata, localServiceToRevisions);
-            return metadata;
-        }
-
-        // try to load metadata from remote.
-        int triedTimes = 0;
-        while (triedTimes < 3) {
-            metadata = doGetMetadataInfo(instance);
-
-            if (metadata != MetadataInfo.EMPTY) {// succeeded
-                parseMetadata(revision, metadata, localServiceToRevisions);
-                break;
-            } else {// failed
-                logger.error("Failed to get MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision
-                    + "&cluster=" + instance.getRegistryCluster() + ", wait for retry.");
-                triedTimes++;
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-
-        revisionToMetadata.putIfAbsent(revision, metadata);
-        return metadata;
-    }
-
     protected Map<String, Map<String, Set<String>>> parseMetadata(String revision, MetadataInfo metadata, Map<String, Map<String, Set<String>>> localServiceToRevisions) {
         Map<String, ServiceInfo> serviceInfos = metadata.getServices();
         for (Map.Entry<String, ServiceInfo> entry : serviceInfos.entrySet()) {
@@ -360,37 +272,6 @@ public class ServiceInstancesChangedListener {
         return localServiceToRevisions;
     }
 
-    protected MetadataInfo doGetMetadataInfo(ServiceInstance instance) {
-        String metadataType = ServiceInstanceMetadataUtils.getMetadataStorageType(instance);
-        // FIXME, check "REGISTRY_CLUSTER_KEY" must be set by every registry implementation.
-        if (instance.getRegistryCluster() == null) {
-            instance.setRegistryCluster(RegistryClusterIdentifier.getExtension(url).consumerKey(url));
-        }
-        MetadataInfo metadataInfo;
-        try {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Instance " + instance.getAddress() + " is using metadata type " + metadataType);
-            }
-            if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
-                RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService(instance.getApplicationModel());
-                metadataInfo = remoteMetadataService.getMetadata(instance);
-            } else {
-                // change the instance used to communicate to avoid all requests route to the same instance
-                MetadataService metadataServiceProxy = MetadataUtils.getMetadataServiceProxy(instance);
-                metadataInfo = metadataServiceProxy.getMetadataInfo(ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
-                MetadataUtils.destroyMetadataServiceProxy(instance);
-            }
-        } catch (Exception e) {
-            logger.error("Failed to load service metadata, meta type is " + metadataType, e);
-            metadataInfo = null;
-        }
-
-        if (metadataInfo == null) {
-            metadataInfo = MetadataInfo.EMPTY;
-        }
-        return metadataInfo;
-    }
-
     private ServiceInstance selectInstance(List<ServiceInstance> instances) {
         if (instances.size() == 1) {
             return instances.get(0);
@@ -448,7 +329,6 @@ public class ServiceInstancesChangedListener {
                 if (destroyed.compareAndSet(false, true)) {
                     allInstances.clear();
                     serviceUrls.clear();
-                    revisionToMetadata.clear();
                     if (retryFuture != null && !retryFuture.isDone()) {
                         retryFuture.cancel(true);
                     }
@@ -478,22 +358,6 @@ public class ServiceInstancesChangedListener {
         return Objects.hash(getClass(), getServiceNames());
     }
 
-    protected class AddressRefreshRetryTask implements Runnable {
-        private final RetryServiceInstancesChangedEvent retryEvent;
-        private final Semaphore retryPermission;
-
-        public AddressRefreshRetryTask(Semaphore semaphore, String serviceName) {
-            this.retryEvent = new RetryServiceInstancesChangedEvent(serviceName);
-            this.retryPermission = semaphore;
-        }
-
-        @Override
-        public void run() {
-            retryPermission.release();
-            ServiceInstancesChangedListener.this.onEvent(retryEvent);
-        }
-    }
-
     protected static class NotifyListenerWithKey {
         private String serviceKey;
         private NotifyListener notifyListener;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
index 6c126db..fe8e9a9 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
@@ -32,6 +32,7 @@ import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
@@ -50,6 +51,9 @@ public class MetadataServiceNameMapping extends AbstractServiceNameMapping {
         metadataReportInstance = applicationModel.getBeanFactory().getBean(MetadataReportInstance.class);
     }
 
+    /**
+     * Simply register to all metadata center
+     */
     @Override
     public boolean map(URL url) {
         if (CollectionUtils.isEmpty(applicationModel.getApplicationConfigManager().getMetadataConfigs())) {
@@ -59,37 +63,45 @@ public class MetadataServiceNameMapping extends AbstractServiceNameMapping {
         if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
             return false;
         }
-        String registryCluster = getRegistryCluster(url);
-        MetadataReport metadataReport = metadataReportInstance.getMetadataReport(registryCluster);
 
-        String appName = applicationModel.getApplicationName();
-        if (metadataReport.registerServiceAppMapping(serviceInterface, appName, url)) {
-            // MetadataReport support directly register service-app mapping
-            return true;
-        }
+        boolean result = true;
+        for (Map.Entry<String, MetadataReport> entry : metadataReportInstance.getMetadataReports(true).entrySet()) {
+            MetadataReport metadataReport = entry.getValue();
+            String appName = applicationModel.getApplicationName();
+            try {
+                if (metadataReport.registerServiceAppMapping(serviceInterface, appName, url)) {
+                    // MetadataReport support directly register service-app mapping
+                    continue;
+                }
+
+                boolean succeeded;
+                int currentRetryTimes = 1;
+                String newConfigContent = appName;
+                do {
+                    ConfigItem configItem = metadataReport.getConfigItem(serviceInterface, DEFAULT_MAPPING_GROUP);
+                    String oldConfigContent = configItem.getContent();
+                    if (StringUtils.isNotEmpty(oldConfigContent)) {
+                        boolean contains = StringUtils.isContains(oldConfigContent, appName);
+                        if (contains) {
+                            // From the user's perspective, it means successful when the oldConfigContent has contained the current appName. So we should not throw an Exception to user, it will confuse the user.
+                            succeeded = true;
+                            break;
+                        }
+                        newConfigContent = oldConfigContent + COMMA_SEPARATOR + appName;
+                    }
+                    succeeded = metadataReport.registerServiceAppMapping(serviceInterface, DEFAULT_MAPPING_GROUP, newConfigContent, configItem.getTicket());
+                } while (!succeeded && currentRetryTimes++ <= CAS_RETRY_TIMES);
 
-        int currentRetryTimes = 1;
-        boolean succeeded = false;
-        String newConfigContent = appName;
-        do {
-            ConfigItem configItem = metadataReport.getConfigItem(serviceInterface, DEFAULT_MAPPING_GROUP);
-            String oldConfigContent = configItem.getContent();
-            if (StringUtils.isNotEmpty(oldConfigContent)) {
-                boolean contains = StringUtils.isContains(oldConfigContent, appName);
-                if (contains) {
-                    // From the user's perspective, it means successful when the oldConfigContent has contained the current appName. So we should not throw an Exception to user, it will confuse the user.
-                    succeeded = true;
-                    break;
+                if (!succeeded) {
+                    result = false;
                 }
-                newConfigContent = oldConfigContent + COMMA_SEPARATOR + appName;
+            } catch (Exception e) {
+                result = false;
+                logger.warn("Failed registering mapping to remote." + metadataReport, e);
             }
-            succeeded = metadataReport.registerServiceAppMapping(serviceInterface, DEFAULT_MAPPING_GROUP, newConfigContent, configItem.getTicket());
-        } while (!succeeded && currentRetryTimes++ <= CAS_RETRY_TIMES);
-        if (!succeeded) {
-            throw new RuntimeException();
         }
 
-        return true;
+        return result;
     }
 
     @Override
@@ -103,11 +115,21 @@ public class MetadataServiceNameMapping extends AbstractServiceNameMapping {
     @Override
     public Set<String> getAndListen(URL url, MappingListener mappingListener) {
         String serviceInterface = url.getServiceInterface();
+        // randomly pick one metadata report is ok for it's guaranteed each metadata report will have the same mapping content.
         String registryCluster = getRegistryCluster(url);
         MetadataReport metadataReport = metadataReportInstance.getMetadataReport(registryCluster);
         return metadataReport.getServiceAppMapping(serviceInterface, mappingListener, url);
     }
 
+    @Override
+    protected void removeListener(URL url, MappingListener mappingListener) {
+        String serviceInterface = url.getServiceInterface();
+        // randomly pick one metadata report is ok for it's guaranteed each metadata report will have the same mapping content.
+        String registryCluster = getRegistryCluster(url);
+        MetadataReport metadataReport = metadataReportInstance.getMetadataReport(registryCluster);
+        metadataReport.removeServiceAppMappingListener(serviceInterface, mappingListener);
+    }
+
     protected String getRegistryCluster(URL url) {
         String registryCluster = RegistryClusterIdentifier.getExtension(url).providerKey(url);
         if (registryCluster == null) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java
index d584c74..71b8d8d 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java
@@ -16,10 +16,14 @@
  */
 package org.apache.dubbo.registry.client.metadata;
 
-import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
 
+import java.util.List;
 import java.util.Map;
 
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
@@ -29,7 +33,14 @@ import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataU
 /**
  * Used to interact with non-dubbo systems, also see {@link SpringCloudMetadataServiceURLBuilder}
  */
-public class MetadataServiceURLParamsMetadataCustomizer implements ServiceInstanceCustomizer {
+public class MetadataServiceURLParamsMetadataCustomizer implements ServiceInstanceCustomizer, ScopeModelAware {
+
+    private ApplicationModel applicationModel;
+
+    @Override
+    public void setApplicationModel(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+    }
 
     @Override
     public void customize(ServiceInstance serviceInstance) {
@@ -49,7 +60,11 @@ public class MetadataServiceURLParamsMetadataCustomizer implements ServiceInstan
     }
 
     private String resolveMetadataPropertyValue(ServiceInstance serviceInstance) {
-        WritableMetadataService writableMetadataService = WritableMetadataService.getDefaultExtension(serviceInstance.getApplicationModel());
-        return getMetadataServiceParameter(writableMetadataService.getMetadataServiceURL());
+        MetadataServiceExporter metadataServiceExporter = applicationModel.getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+        if (metadataServiceExporter.isExported()) {
+            List<URL> metadataURLs = metadataServiceExporter.getExportedURLs();
+            return getMetadataServiceParameter(metadataURLs.get(0));
+        }
+        return "";
     }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
index 4fe171b..3fbedb2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
@@ -18,43 +18,72 @@ package org.apache.dubbo.registry.client.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+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.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ScopeModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
 
 public class MetadataUtils {
+    public static final Logger logger = LoggerFactory.getLogger(MetadataUtils.class);
 
     public static ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
 
     public static ConcurrentMap<String, Invoker<?>> metadataServiceInvokers = new ConcurrentHashMap<>();
 
-    public static RemoteMetadataServiceImpl getRemoteMetadataService(ScopeModel scopeModel) {
-        return scopeModel.getBeanFactory().getBean(RemoteMetadataServiceImpl.class);
+    public static ConcurrentMap<String, Invoker<?>> getMetadataServiceInvokers() {
+        return metadataServiceInvokers;
     }
 
-    public static void publishServiceDefinition(URL url) {
-        // store in local
-        WritableMetadataService.getDefaultExtension(url.getScopeModel()).publishServiceDefinition(url);
-        // send to remote
-        if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(url.getParameter(METADATA_KEY))) {
-            getRemoteMetadataService(url.getOrDefaultApplicationModel()).publishServiceDefinition(url);
+    public static synchronized MetadataService getMetadataServiceProxy(ServiceInstance instance) {
+        return metadataServiceProxies.computeIfAbsent(computeKey(instance), k -> referProxy(k, instance));
+    }
+
+    public static void publishServiceDefinition(ServiceDescriptor serviceDescriptor, ApplicationModel applicationModel) {
+        checkRemoteConfigured(applicationModel);
+
+        String serviceName = serviceDescriptor.getServiceName();
+        FullServiceDefinition serviceDefinition = serviceDescriptor.getServiceDefinition(serviceName);
+
+        try {
+            if (StringUtils.isNotEmpty(serviceName)) {
+                for (Map.Entry<String, MetadataReport> entry : getMetadataReports(applicationModel).entrySet()) {
+                    MetadataReport metadataReport = entry.getValue();
+                    metadataReport.storeProviderMetadata(new MetadataIdentifier(serviceName,
+                        "", "",
+                        PROVIDER_SIDE, applicationModel.getApplicationName()), serviceDefinition);
+                }
+                return;
+            }
+            logger.error("publishProvider interfaceName is empty.");
+        } catch (Exception e) {
+            //ignore error
+            logger.error("publishProvider getServiceDescriptor error.", e);
         }
     }
 
@@ -63,10 +92,6 @@ public class MetadataUtils {
                 ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
     }
 
-    public static synchronized MetadataService getMetadataServiceProxy(ServiceInstance instance) {
-        return metadataServiceProxies.computeIfAbsent(computeKey(instance), k -> referProxy(k, instance));
-    }
-
     public static synchronized void destroyMetadataServiceProxy(ServiceInstance instance) {
         String key = computeKey(instance);
         if (metadataServiceProxies.containsKey(key)) {
@@ -110,7 +135,93 @@ public class MetadataUtils {
         return metadataServiceProxies;
     }
 
-    public static ConcurrentMap<String, Invoker<?>> getMetadataServiceInvokers() {
-        return metadataServiceInvokers;
+    public static MetadataInfo getRemoteMetadata(String revision, ServiceInstance instance, Map<String, MetadataInfo> revisionToMetadata, MetadataReport metadataReport) {
+        MetadataInfo metadata = revisionToMetadata.get(revision);
+
+        if (metadata != null && metadata != MetadataInfo.EMPTY) {
+            // metadata loaded from cache
+            if (logger.isDebugEnabled()) {
+                logger.debug("MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision
+                    + "&cluster=" + instance.getRegistryCluster() + ", " + metadata);
+            }
+            return metadata;
+        }
+
+        // try to load metadata from remote.
+        int triedTimes = 0;
+        while (triedTimes < 3) {
+            metadata = doGetMetadataInfo(instance, metadataReport);
+
+            if (metadata != MetadataInfo.EMPTY) {// succeeded
+                break;
+            } else {// failed
+                logger.error("Failed to get MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision
+                    + "&cluster=" + instance.getRegistryCluster() + ", wait for retry.");
+                triedTimes++;
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        revisionToMetadata.putIfAbsent(revision, metadata);
+        return metadata;
     }
+
+    protected static MetadataInfo doGetMetadataInfo(ServiceInstance instance, MetadataReport metadataReport) {
+        String metadataType = ServiceInstanceMetadataUtils.getMetadataStorageType(instance);
+        MetadataInfo metadataInfo = null;
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Instance " + instance.getAddress() + " is using metadata type " + metadataType);
+            }
+            if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
+                getMetadata(instance, metadataReport);
+            } else {
+                // change the instance used to communicate to avoid all requests route to the same instance
+                MetadataService metadataServiceProxy = MetadataUtils.getMetadataServiceProxy(instance);
+                metadataInfo = metadataServiceProxy.getMetadataInfo(ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
+                MetadataUtils.destroyMetadataServiceProxy(instance);
+            }
+        } catch (Exception e) {
+            logger.error("Failed to load service metadata, meta type is " + metadataType, e);
+            metadataInfo = null;
+        }
+
+        if (metadataInfo == null) {
+            metadataInfo = MetadataInfo.EMPTY;
+        }
+        return metadataInfo;
+    }
+
+    public static MetadataInfo getMetadata(ServiceInstance instance, MetadataReport metadataReport) {
+        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(instance.getServiceName(),
+            ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
+
+        if (metadataReport == null) {
+            throw new IllegalStateException("No valid remote metadata report specified.");
+        }
+
+        String registryCluster = instance.getRegistryCluster();
+        Map<String, String> params = new HashMap<>(instance.getExtendParams());
+        if (registryCluster != null && !registryCluster.equalsIgnoreCase(params.get(REGISTRY_CLUSTER_KEY))) {
+            params.put(REGISTRY_CLUSTER_KEY, registryCluster);
+        }
+
+        return metadataReport.getAppMetadata(identifier, params);
+    }
+
+    private static void checkRemoteConfigured(ApplicationModel applicationModel) {
+        if (getMetadataReports(applicationModel).size() == 0) {
+            String msg = "Remote Metadata Report Server not hasn't been configured or unavailable . Unable to get Metadata from remote!";
+            logger.error(msg);
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    private static Map<String, MetadataReport> getMetadataReports(ApplicationModel applicationModel) {
+        return applicationModel.getBeanFactory().getBean(MetadataReportInstance.class).getMetadataReports(false);
+    }
+
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizer.java
index 1c70842..cfdc530 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizer.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizer.java
@@ -23,10 +23,9 @@ import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataParamsFilter;
-import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.Collections;
@@ -61,15 +60,7 @@ public class ServiceInstanceMetadataCustomizer implements ServiceInstanceCustomi
         ApplicationModel applicationModel = serviceInstance.getApplicationModel();
         ExtensionLoader<MetadataParamsFilter> loader = applicationModel.getExtensionLoader(MetadataParamsFilter.class);
 
-        InMemoryWritableMetadataService localMetadataService
-                = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(applicationModel);
-        // pick the first interface metadata available.
-        // FIXME, check the same key in different urls have the same value
-        Map<String, MetadataInfo> metadataInfos = localMetadataService.getMetadataInfos();
-        if (CollectionUtils.isEmptyMap(metadataInfos)) {
-            return;
-        }
-        MetadataInfo metadataInfo = metadataInfos.values().iterator().next();
+        MetadataInfo metadataInfo = ((DefaultServiceInstance)serviceInstance).getServiceMetadata();
         if (metadataInfo == null || CollectionUtils.isEmptyMap(metadataInfo.getServices())) {
             return;
         }
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 e6e19ba..6cfc2df 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
@@ -21,7 +21,6 @@ import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
@@ -29,8 +28,8 @@ import org.apache.dubbo.registry.client.DefaultServiceInstance.Endpoint;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.RegistryManager;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import com.google.gson.Gson;
 
@@ -41,14 +40,11 @@ import java.util.Map;
 
 import static java.util.Collections.emptyMap;
 import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 import static org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.DEFAULT_REGISTER_PROVIDER_KEYS;
 import static org.apache.dubbo.rpc.Constants.DEPRECATED_KEY;
@@ -217,71 +213,30 @@ public class ServiceInstanceMetadataUtils {
         return null;
     }
 
-    public static void calInstanceRevision(ServiceDiscovery serviceDiscovery, ServiceInstance instance) {
-        String registryCluster = serviceDiscovery.getUrl() == null ? DEFAULT_KEY : serviceDiscovery.getUrl().getParameter(REGISTRY_CLUSTER_KEY);
-        if (registryCluster == null) {
-            registryCluster = DEFAULT_KEY;
-        }
-        WritableMetadataService writableMetadataService = WritableMetadataService.getDefaultExtension(instance.getApplicationModel());
-        MetadataInfo metadataInfo = writableMetadataService.getMetadataInfos().get(registryCluster);
-        if (metadataInfo == null) {
-            metadataInfo = writableMetadataService.getDefaultMetadataInfo();
-        }
-        if (metadataInfo != null) {
-            String existingInstanceRevision = instance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME);
-            if (!metadataInfo.calAndGetRevision().equals(existingInstanceRevision)) {
-                instance.getMetadata().put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, metadataInfo.calAndGetRevision());
-                if (existingInstanceRevision != null) {// skip the first registration.
-                    instance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true");
-                }
-            }
-        }
-    }
-
     public static boolean isInstanceUpdated(ServiceInstance instance) {
         return "true".equals(instance.getExtendParams().get(INSTANCE_REVISION_UPDATED_KEY));
     }
 
-    public static void resetInstanceUpdateKey(ServiceInstance instance) {
-        instance.getExtendParams().remove(INSTANCE_REVISION_UPDATED_KEY);
+    public static void registerMetadataAndInstance(ApplicationModel applicationModel) {
+            LOGGER.info("Start registering instance address to registry.");
+            RegistryManager registryManager = applicationModel.getBeanFactory().getBean(RegistryManager.class);
+        // register service instance
+        registryManager.getServiceDiscoveries().forEach(ServiceDiscovery::register);
     }
 
-    public static void registerMetadataAndInstance(ServiceInstance serviceInstance) {
-        // register instance only when at least one service is exported.
-        if (serviceInstance.getPort() > 0) {
-            reportMetadataToRemote(serviceInstance);
-            LOGGER.info("Start registering instance address to registry.");
-            RegistryManager registryManager = serviceInstance.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
-            registryManager.getServiceDiscoveries().forEach(serviceDiscovery ->
-            {
-                // copy instance for each registry to make sure instance in each registry can evolve independently
-                ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);
-                calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);
-                if (LOGGER.isDebugEnabled()) {
-                    LOGGER.debug("Start registering instance address to registry" + serviceDiscovery.getUrl() + ", instance " + serviceInstanceForRegistry);
-                }
-                // register service instance
-                serviceDiscovery.register(serviceInstanceForRegistry);
-            });
-        }
+    public static void refreshMetadataAndInstance(ApplicationModel applicationModel) {
+        RegistryManager registryManager = applicationModel.getBeanFactory().getBean(RegistryManager.class);
+        // update service instance revision
+        registryManager.getServiceDiscoveries().forEach(ServiceDiscovery::update);
     }
 
-    public static void refreshMetadataAndInstance(ServiceInstance serviceInstance) {
-        reportMetadataToRemote(serviceInstance);
-        RegistryManager registryManager = serviceInstance.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
+    public static void unregisterMetadataAndInstance(ApplicationModel applicationModel) {
+        RegistryManager registryManager = applicationModel.getBeanFactory().getBean(RegistryManager.class);
         registryManager.getServiceDiscoveries().forEach(serviceDiscovery -> {
-            ServiceInstance instance = serviceDiscovery.getLocalInstance();
-            if (instance == null) {
-                LOGGER.warn("Refreshing of service instance started, but instance hasn't been registered yet.");
-                instance = serviceInstance;
-            }
-            // copy instance again, in case the same instance accidently shared among registries
-            instance = new DefaultServiceInstance((DefaultServiceInstance) instance);
-            calInstanceRevision(serviceDiscovery, instance);
-            customizeInstance(instance);
-            if (instance.getPort() > 0) {
-                // update service instance revision
-                serviceDiscovery.update(instance);
+            try {
+                serviceDiscovery.unregister();
+            } catch (Exception ignored) {
+                // ignored
             }
         });
     }
@@ -296,13 +251,6 @@ public class ServiceInstanceMetadataUtils {
         });
     }
 
-    private static void reportMetadataToRemote(ServiceInstance serviceInstance) {
-        if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(getMetadataStorageType(serviceInstance))) {
-            RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService(serviceInstance.getApplicationModel());
-            remoteMetadataService.publishMetadata(serviceInstance.getApplicationModel().getApplicationName());
-        }
-    }
-
     /**
      * Set the default parameters via the specified {@link URL providerURL}
      *
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
deleted file mode 100644
index d563972..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client.metadata.store;
-
-import com.google.gson.Gson;
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.ClassUtils;
-import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
-import org.apache.dubbo.metadata.MetadataInfo;
-import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.ServiceNameMapping;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
-import org.apache.dubbo.registry.client.RegistryClusterIdentifier;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.ScopeModelAware;
-import org.apache.dubbo.rpc.support.ProtocolUtils;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentNavigableMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import static java.util.Collections.emptySortedSet;
-import static java.util.Collections.unmodifiableSortedSet;
-import static org.apache.dubbo.common.URL.buildKey;
-import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
-import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_PUBLISH_DELAY;
-import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PUBLISH_DELAY_KEY;
-import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
-
-/**
- * The {@link WritableMetadataService} implementation stores the metadata of Dubbo services in memory locally when they
- * exported. It is used by server (provider).
- *
- * @see MetadataService
- * @see WritableMetadataService
- * @since 2.7.5
- */
-public class InMemoryWritableMetadataService implements WritableMetadataService, ScopeModelAware {
-
-    Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final Lock lock = new ReentrantLock();
-
-    // =================================== Registration =================================== //
-
-    /**
-     * All exported {@link URL urls} {@link Map} whose key is the return value of {@link URL#getServiceKey()} method
-     * and value is the {@link SortedSet sorted set} of the {@link URL URLs}
-     */
-    private ConcurrentNavigableMap<String, SortedSet<URL>> exportedServiceURLs = new ConcurrentSkipListMap<>();
-    private URL metadataServiceURL;
-    private ConcurrentMap<String, MetadataInfo> metadataInfos;
-
-    // used to mark whether current metadata info is being updated to registry,
-    // readLock for export or unExport which are support concurrency update,
-    // writeLock for ServiceInstance update which should not work during exporting services
-    private final ReentrantReadWriteLock updateLock = new ReentrantReadWriteLock();
-    private final Semaphore metadataSemaphore = new Semaphore(0);
-    private final Map<String, Set<String>> serviceToAppsMapping = new HashMap<>();
-
-    private String instanceMetadata;
-    private ConcurrentMap<String, InstanceMetadataChangedListener> instanceMetadataChangedListenerMap = new ConcurrentHashMap<>();
-
-
-    // ==================================================================================== //
-
-    // =================================== Subscription =================================== //
-
-    /**
-     * The subscribed {@link URL urls} {@link Map} of {@link MetadataService},
-     * whose key is the return value of {@link URL#getServiceKey()} method and value is
-     * the {@link SortedSet sorted set} of the {@link URL URLs}
-     */
-    private ConcurrentNavigableMap<String, SortedSet<URL>> subscribedServiceURLs = new ConcurrentSkipListMap<>();
-
-    private ConcurrentNavigableMap<String, String> serviceDefinitions = new ConcurrentSkipListMap<>();
-    private ApplicationModel applicationModel;
-    private long metadataPublishDelayTime ;
-
-    public InMemoryWritableMetadataService() {
-        this.metadataInfos = new ConcurrentHashMap<>();
-    }
-
-    /**
-     * Gets the current Dubbo Service name
-     *
-     * @return non-null
-     */
-    @Override
-    public String serviceName() {
-        return ApplicationModel.ofNullable(applicationModel).getApplicationName();
-    }
-
-    @Override
-    public void setApplicationModel(ApplicationModel applicationModel) {
-        this.applicationModel = applicationModel;
-        this.metadataPublishDelayTime = ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY);
-    }
-
-    @Override
-    public SortedSet<String> getSubscribedURLs() {
-        return getAllUnmodifiableServiceURLs(subscribedServiceURLs);
-    }
-
-    private SortedSet<String> getAllUnmodifiableServiceURLs(Map<String, SortedSet<URL>> serviceURLs) {
-        SortedSet<URL> bizURLs = new TreeSet<>(InMemoryWritableMetadataService.URLComparator.INSTANCE);
-        for (Map.Entry<String, SortedSet<URL>> entry : serviceURLs.entrySet()) {
-            SortedSet<URL> urls = entry.getValue();
-            if (urls != null) {
-                for (URL url : urls) {
-                    if (!MetadataService.class.getName().equals(url.getServiceInterface())) {
-                        bizURLs.add(url);
-                    }
-                }
-            }
-        }
-        return MetadataService.toSortedStrings(bizURLs);
-    }
-
-    @Override
-    public SortedSet<String> getExportedURLs(String serviceInterface, String group, String version, String protocol) {
-        if (ALL_SERVICE_INTERFACES.equals(serviceInterface)) {
-            return getAllUnmodifiableServiceURLs(exportedServiceURLs);
-        }
-        String serviceKey = buildKey(serviceInterface, group, version);
-        return unmodifiableSortedSet(getServiceURLs(exportedServiceURLs, serviceKey, protocol));
-    }
-
-    @Override
-    public Set<URL> getExportedServiceURLs() {
-        Set<URL> set = new HashSet<>();
-        for (Map.Entry<String, SortedSet<URL>> entry : exportedServiceURLs.entrySet()) {
-            set.addAll(entry.getValue());
-        }
-        return set;
-    }
-
-    @Override
-    public boolean exportURL(URL url) {
-        if (MetadataService.class.getName().equals(url.getServiceInterface())) {
-            this.metadataServiceURL = url;
-            return true;
-        }
-
-        updateLock.readLock().lock();
-        try {
-            String[] clusters = getRegistryCluster(url).split(",");
-            for (String cluster : clusters) {
-                MetadataInfo metadataInfo = metadataInfos.computeIfAbsent(cluster, k -> new MetadataInfo(applicationModel.getApplicationName()));
-                metadataInfo.addService(new ServiceInfo(url));
-            }
-            metadataSemaphore.release();
-            return addURL(exportedServiceURLs, url);
-        } finally {
-            updateLock.readLock().unlock();
-        }
-    }
-
-    public void addMetadataInfo(String key, MetadataInfo metadataInfo) {
-        updateLock.readLock().lock();
-        try {
-            metadataInfos.put(key, metadataInfo);
-        } finally {
-            updateLock.readLock().unlock();
-        }
-    }
-
-    @Override
-    public boolean unexportURL(URL url) {
-        if (MetadataService.class.getName().equals(url.getServiceInterface())) {
-            // TODO, metadata service need to be unexported.
-            this.metadataServiceURL = null;
-            return true;
-        }
-
-        updateLock.readLock().lock();
-        try {
-            String[] clusters = getRegistryCluster(url).split(",");
-            for (String cluster : clusters) {
-                MetadataInfo metadataInfo = metadataInfos.get(cluster);
-                metadataInfo.removeService(url.getProtocolServiceKey());
-//            if (metadataInfo.getServices().isEmpty()) {
-//                metadataInfos.remove(cluster);
-//            }
-            }
-            metadataSemaphore.release();
-            return removeURL(exportedServiceURLs, url);
-        } finally {
-            updateLock.readLock().unlock();
-        }
-    }
-
-    private String getRegistryCluster(URL url) {
-        String registryCluster = RegistryClusterIdentifier.getExtension(url).providerKey(url);
-        if (StringUtils.isEmpty(registryCluster)) {
-            registryCluster = DEFAULT_KEY;
-        }
-        return registryCluster;
-    }
-
-    @Override
-    public boolean subscribeURL(URL url) {
-        return addURL(subscribedServiceURLs, url);
-    }
-
-    @Override
-    public boolean unsubscribeURL(URL url) {
-        return removeURL(subscribedServiceURLs, url);
-    }
-
-    @Override
-    public void publishServiceDefinition(URL url) {
-        try {
-            String interfaceName = url.getServiceInterface();
-            if (StringUtils.isNotEmpty(interfaceName)
-                && !ProtocolUtils.isGeneric(url.getParameter(GENERIC_KEY))) {
-                ClassLoader classLoader = url.getServiceModel() != null ?
-                    url.getServiceModel().getClassLoader() :
-                    ClassUtils.getClassLoader();
-                Class interfaceClass = Class.forName(interfaceName, false, classLoader);
-                ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.build(interfaceClass);
-                Gson gson = new Gson();
-                String data = gson.toJson(serviceDefinition);
-                serviceDefinitions.put(url.getServiceKey(), data);
-                return;
-            } else if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
-                //to avoid consumer generic invoke style error
-                return;
-            }
-            logger.error("publish service definition interfaceName is empty. url: " + url.toFullString());
-        } catch (Throwable e) {
-            //ignore error
-            logger.error("publish service definition getServiceDescriptor error. url: " + url.toFullString(), e);
-        }
-    }
-
-    @Override
-    public String getServiceDefinition(String interfaceName, String version, String group) {
-        return serviceDefinitions.get(URL.buildKey(interfaceName, group, version));
-    }
-
-    @Override
-    public String getServiceDefinition(String serviceKey) {
-        return serviceDefinitions.get(serviceKey);
-    }
-
-    @Override
-    public MetadataInfo getMetadataInfo(String revision) {
-        if (StringUtils.isEmpty(revision)) {
-            return null;
-        }
-        for (Map.Entry<String, MetadataInfo> entry : metadataInfos.entrySet()) {
-            MetadataInfo metadataInfo = entry.getValue();
-            if (revision.equals(metadataInfo.calAndGetRevision())) {
-                return metadataInfo;
-            }
-        }
-        if (logger.isInfoEnabled()) {
-            logger.info("metadata not found for revision: " + revision);
-        }
-        return null;
-    }
-
-    @Override
-    public void exportInstanceMetadata(String metadata) {
-        this.instanceMetadata = metadata;
-    }
-
-    @Override
-    public Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
-        return instanceMetadataChangedListenerMap;
-    }
-
-    @Override
-    public String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
-        instanceMetadataChangedListenerMap.put(consumerId, listener);
-        return instanceMetadata;
-    }
-
-    @Override
-    public MetadataInfo getDefaultMetadataInfo() {
-        if (CollectionUtils.isEmptyMap(metadataInfos)) {
-            return null;
-        }
-        for (Map.Entry<String, MetadataInfo> entry : metadataInfos.entrySet()) {
-            if (entry.getKey().equalsIgnoreCase(DEFAULT_KEY)) {
-                return entry.getValue();
-            }
-        }
-        return metadataInfos.entrySet().iterator().next().getValue();
-    }
-
-    public void blockUntilUpdated() {
-        try {
-            metadataSemaphore.tryAcquire(metadataPublishDelayTime, TimeUnit.MILLISECONDS);
-            metadataSemaphore.drainPermits();
-            updateLock.writeLock().lock();
-        } catch (InterruptedException e) {
-            if (!applicationModel.isDestroyed()) {
-                logger.warn("metadata refresh thread has been interrupted unexpectedly while waiting for update.", e);
-            }
-        }
-    }
-
-    public void releaseBlock() {
-        updateLock.writeLock().unlock();
-    }
-
-    public Map<String, MetadataInfo> getMetadataInfos() {
-        return Collections.unmodifiableMap(metadataInfos);
-    }
-
-    void addMetaServiceURL(URL url) {
-        this.metadataServiceURL = url;
-    }
-
-    @Override
-    public URL getMetadataServiceURL() {
-        return this.metadataServiceURL;
-    }
-
-    @Override
-    public void putCachedMapping(String serviceKey, Set<String> apps) {
-        serviceToAppsMapping.put(serviceKey, new TreeSet<>(apps));
-    }
-
-    @Override
-    public Set<String> getCachedMapping(String mappingKey) {
-        return serviceToAppsMapping.get(mappingKey);
-    }
-
-    @Override
-    public Set<String> getCachedMapping(URL consumerURL) {
-        String serviceKey = ServiceNameMapping.buildMappingKey(consumerURL);
-        return serviceToAppsMapping.get(serviceKey);
-    }
-
-    @Override
-    public Set<String> removeCachedMapping(String serviceKey) {
-        return serviceToAppsMapping.remove(serviceKey);
-    }
-
-    @Override
-    public Map<String, Set<String>> getCachedMapping() {
-        return serviceToAppsMapping;
-    }
-
-    @Override
-    public void setMetadataServiceURL(URL url) {
-        this.metadataServiceURL = url;
-    }
-
-    boolean addURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {
-        return executeMutually(() -> {
-            SortedSet<URL> urls = serviceURLs.computeIfAbsent(url.getServiceKey(), this::newSortedURLs);
-            // make sure the parameters of tmpUrl is variable
-            return urls.add(url);
-        });
-    }
-
-    boolean removeURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {
-        return executeMutually(() -> {
-            String key = url.getServiceKey();
-            SortedSet<URL> urls = serviceURLs.getOrDefault(key, null);
-            if (urls == null) {
-                return true;
-            }
-            boolean r = urls.remove(url);
-            // if it is empty
-            if (urls.isEmpty()) {
-                serviceURLs.remove(key);
-            }
-            return r;
-        });
-    }
-
-    private SortedSet<URL> newSortedURLs(String serviceKey) {
-        return new TreeSet<>(InMemoryWritableMetadataService.URLComparator.INSTANCE);
-    }
-
-    boolean executeMutually(Callable<Boolean> callable) {
-        boolean success = false;
-        try {
-            lock.lock();
-            try {
-                success = callable.call();
-            } catch (Exception e) {
-                if (logger.isErrorEnabled()) {
-                    logger.error(e);
-                }
-            }
-        } finally {
-            lock.unlock();
-        }
-        return success;
-    }
-
-    private SortedSet<String> getServiceURLs(Map<String, SortedSet<URL>> exportedServiceURLs, String serviceKey,
-                                             String protocol) {
-
-        SortedSet<URL> serviceURLs = exportedServiceURLs.get(serviceKey);
-
-        if (isEmpty(serviceURLs)) {
-            return emptySortedSet();
-        }
-
-        return MetadataService.toSortedStrings(serviceURLs.stream().filter(url -> isAcceptableProtocol(protocol, url)));
-    }
-
-    private boolean isAcceptableProtocol(String protocol, URL url) {
-        return protocol == null
-            || protocol.equals(url.getParameter(PROTOCOL_KEY))
-            || protocol.equals(url.getProtocol());
-    }
-
-
-    static class URLComparator implements Comparator<URL> {
-
-        public static final URLComparator INSTANCE = new URLComparator();
-
-        @Override
-        public int compare(URL o1, URL o2) {
-            return o1.toFullString().compareTo(o2.toFullString());
-        }
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/MetadataServiceDelegation.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/MetadataServiceDelegation.java
new file mode 100644
index 0000000..a298f17
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/MetadataServiceDelegation.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client.metadata.store;
+
+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.StringUtils;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.support.RegistryManager;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import static java.util.Collections.emptySortedSet;
+import static java.util.Collections.unmodifiableSortedSet;
+import static org.apache.dubbo.common.URL.buildKey;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
+import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
+
+/**
+ * Implementation providing remote RPC service to facilitate the query of metadata information.
+ */
+public class MetadataServiceDelegation implements WritableMetadataService, ScopeModelAware {
+    Logger logger = LoggerFactory.getLogger(getClass());
+    private ApplicationModel applicationModel;
+    private RegistryManager registryManager;
+    private ConcurrentNavigableMap<String, String> serviceDefinitions = new ConcurrentSkipListMap<>();
+
+    public MetadataServiceDelegation() {}
+
+    /**
+     * Gets the current Dubbo Service name
+     *
+     * @return non-null
+     */
+    @Override
+    public String serviceName() {
+        return ApplicationModel.ofNullable(applicationModel).getApplicationName();
+    }
+
+    @Override
+    public void setApplicationModel(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        registryManager = RegistryManager.getInstance(applicationModel);
+    }
+
+    @Override
+    public SortedSet<String> getSubscribedURLs() {
+        return getAllUnmodifiableSubscribedURLs();
+    }
+
+    private SortedSet<String> getAllUnmodifiableServiceURLs() {
+        SortedSet<URL> bizURLs = new TreeSet<>(MetadataServiceDelegation.URLComparator.INSTANCE);
+        List<ServiceDiscovery> serviceDiscoveries = registryManager.getServiceDiscoveries();
+        for (ServiceDiscovery sd : serviceDiscoveries) {
+            MetadataInfo metadataInfo = sd.getMetadata();
+            Map<String, SortedSet<URL>> serviceURLs = metadataInfo.getExportedServiceURLs();
+            for (Map.Entry<String, SortedSet<URL>> entry : serviceURLs.entrySet()) {
+                SortedSet<URL> urls = entry.getValue();
+                if (urls != null) {
+                    for (URL url : urls) {
+                        if (!MetadataService.class.getName().equals(url.getServiceInterface())) {
+                            bizURLs.add(url);
+                        }
+                    }
+                }
+            }
+        }
+        return MetadataService.toSortedStrings(bizURLs);
+    }
+
+    private SortedSet<String> getAllUnmodifiableSubscribedURLs() {
+        SortedSet<URL> bizURLs = new TreeSet<>(MetadataServiceDelegation.URLComparator.INSTANCE);
+        List<ServiceDiscovery> serviceDiscoveries = registryManager.getServiceDiscoveries();
+        for (ServiceDiscovery sd : serviceDiscoveries) {
+            MetadataInfo metadataInfo = sd.getMetadata();
+            Map<String, SortedSet<URL>> serviceURLs = metadataInfo.getSubscribedServiceURLs();
+            for (Map.Entry<String, SortedSet<URL>> entry : serviceURLs.entrySet()) {
+                SortedSet<URL> urls = entry.getValue();
+                if (urls != null) {
+                    for (URL url : urls) {
+                        if (!MetadataService.class.getName().equals(url.getServiceInterface())) {
+                            bizURLs.add(url);
+                        }
+                    }
+                }
+            }
+        }
+        return MetadataService.toSortedStrings(bizURLs);
+    }
+
+    @Override
+    public SortedSet<String> getExportedURLs(String serviceInterface, String group, String version, String protocol) {
+        if (ALL_SERVICE_INTERFACES.equals(serviceInterface)) {
+            return getAllUnmodifiableServiceURLs();
+        }
+        String serviceKey = buildKey(serviceInterface, group, version);
+        return unmodifiableSortedSet(getServiceURLs(getAllServiceURLs(), serviceKey, protocol));
+    }
+
+    private Map<String, SortedSet<URL>> getAllServiceURLs () {
+        List<ServiceDiscovery> serviceDiscoveries = registryManager.getServiceDiscoveries();
+        Map<String, SortedSet<URL>> allServiceURLs = new HashMap<>();
+        for (ServiceDiscovery sd : serviceDiscoveries) {
+            MetadataInfo metadataInfo = sd.getMetadata();
+            Map<String, SortedSet<URL>> serviceURLs = metadataInfo.getExportedServiceURLs();
+            allServiceURLs.putAll(serviceURLs);
+        }
+        return allServiceURLs;
+    }
+
+    @Override
+    public Set<URL> getExportedServiceURLs() {
+        Set<URL> set = new HashSet<>();
+        registryManager.getRegistries();
+        for (Map.Entry<String, SortedSet<URL>> entry : getAllServiceURLs().entrySet()) {
+            set.addAll(entry.getValue());
+        }
+        return set;
+    }
+
+    @Override
+    public String getServiceDefinition(String interfaceName, String version, String group) {
+        return serviceDefinitions.get(URL.buildKey(interfaceName, group, version));
+    }
+
+    @Override
+    public String getServiceDefinition(String serviceKey) {
+        return serviceDefinitions.get(serviceKey);
+    }
+
+    @Override
+    public MetadataInfo getMetadataInfo(String revision) {
+        if (StringUtils.isEmpty(revision)) {
+            return null;
+        }
+
+        for (ServiceDiscovery sd : registryManager.getServiceDiscoveries()) {
+            MetadataInfo metadataInfo = sd.getMetadata();
+            if (revision.equals(metadataInfo.calAndGetRevision())) {
+                return metadataInfo;
+            }
+        }
+
+        if (logger.isWarnEnabled()) {
+            logger.warn("metadata not found for revision: " + revision);
+        }
+        return null;
+    }
+
+    @Override
+    public List<MetadataInfo> getMetadataInfos() {
+        List<MetadataInfo> metadataInfos = new ArrayList<>();
+        for (ServiceDiscovery sd : registryManager.getServiceDiscoveries()) {
+            metadataInfos.add(sd.getMetadata());
+        }
+        return metadataInfos;
+    }
+
+    @Override
+    public URL getMetadataServiceURL() {
+        MetadataServiceExporter metadataServiceExporter = applicationModel.getExtensionLoader(MetadataServiceExporter.class).getDefaultExtension();
+        if (metadataServiceExporter.isExported()) {
+            List<URL> metadataURLs = metadataServiceExporter.getExportedURLs();
+            return metadataURLs.get(0);
+        }
+        return null;
+    }
+
+    private SortedSet<String> getServiceURLs(Map<String, SortedSet<URL>> exportedServiceURLs, String serviceKey,
+                                             String protocol) {
+
+        SortedSet<URL> serviceURLs = exportedServiceURLs.get(serviceKey);
+
+        if (isEmpty(serviceURLs)) {
+            return emptySortedSet();
+        }
+
+        return MetadataService.toSortedStrings(serviceURLs.stream().filter(url -> isAcceptableProtocol(protocol, url)));
+    }
+
+    private boolean isAcceptableProtocol(String protocol, URL url) {
+        return protocol == null
+            || protocol.equals(url.getParameter(PROTOCOL_KEY))
+            || protocol.equals(url.getProtocol());
+    }
+
+
+    static class URLComparator implements Comparator<URL> {
+
+        public static final URLComparator INSTANCE = new URLComparator();
+
+        @Override
+        public int compare(URL o1, URL o2) {
+            return o1.toFullString().compareTo(o2.toFullString());
+        }
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
deleted file mode 100644
index 7b68e4c..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client.metadata.store;
-
-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.ClassUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.MetadataInfo;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
-import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
-import org.apache.dubbo.rpc.RpcException;
-import org.apache.dubbo.rpc.model.ScopeModel;
-import org.apache.dubbo.rpc.model.ScopeModelAware;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
-import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
-import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
-import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
-
-public class RemoteMetadataServiceImpl implements ScopeModelAware {
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-    private WritableMetadataService localMetadataService;
-    private MetadataReportInstance metadataReportInstance;
-
-    @Override
-    public void setScopeModel(ScopeModel scopeModel) {
-        metadataReportInstance = scopeModel.getBeanFactory().getBean(MetadataReportInstance.class);
-        localMetadataService = scopeModel.getDefaultExtension(WritableMetadataService.class);
-    }
-
-    public Map<String, MetadataReport> getMetadataReports() {
-        return metadataReportInstance.getMetadataReports(false);
-    }
-
-    public void publishMetadata(String serviceName) {
-        Map<String, MetadataInfo> metadataInfos = localMetadataService.getMetadataInfos();
-        metadataInfos.forEach((registryCluster, metadataInfo) -> {
-            if (!metadataInfo.hasReported()) {
-                SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(serviceName, metadataInfo.calAndGetRevision());
-                metadataInfo.getExtendParams().put(REGISTRY_CLUSTER_KEY, registryCluster);
-                if (getMetadataReports().size() > 0) {
-                    MetadataReport metadataReport = getMetadataReports().get(registryCluster);
-                    if (metadataReport == null) {
-                        metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
-                    }
-                    logger.info("Publishing metadata to " + metadataReport.getClass().getSimpleName());
-                    if (logger.isDebugEnabled()) {
-                        logger.debug(metadataInfo.toString());
-                    }
-                    metadataReport.publishAppMetadata(identifier, metadataInfo);
-                } else {
-                    if (logger.isInfoEnabled()) {
-                        logger.info("Remote Metadata Report Server not hasn't been configured. Only publish Metadata to local.");
-                    }
-                }
-                metadataInfo.markReported();
-            }
-        });
-    }
-
-    public MetadataInfo getMetadata(ServiceInstance instance) {
-        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(instance.getServiceName(),
-            ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
-
-        String registryCluster = instance.getRegistryCluster();
-
-        checkRemoteConfigured();
-
-        MetadataReport metadataReport = getMetadataReports().get(registryCluster);
-        if (metadataReport == null) {
-            metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
-        }
-        Map<String, String> params = new HashMap<>(instance.getExtendParams());
-        if (registryCluster != null && !registryCluster.equalsIgnoreCase(params.get(REGISTRY_CLUSTER_KEY))) {
-            params.put(REGISTRY_CLUSTER_KEY, registryCluster);
-        }
-        return metadataReport.getAppMetadata(identifier, params);
-    }
-
-    private void checkRemoteConfigured() {
-        if (getMetadataReports().size() == 0) {
-            String msg = "Remote Metadata Report Server not hasn't been configured or unavailable . Unable to get Metadata from remote!";
-            logger.error(msg);
-            throw new IllegalStateException(msg);
-        }
-    }
-
-    public void publishServiceDefinition(URL url) {
-        checkRemoteConfigured();
-
-        String side = url.getSide();
-
-        if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
-            //TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
-            publishProvider(url);
-        } else {
-            //TODO, only useful for ops showing the url parameters, this is duplicate with subscribeURL(url), can be removed in the future.
-            publishConsumer(url);
-        }
-    }
-
-    private void publishProvider(URL providerUrl) throws RpcException {
-        //first add into the list
-        // remove the individual param
-        providerUrl = providerUrl.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY);
-
-        try {
-            String interfaceName = providerUrl.getServiceInterface();
-            if (StringUtils.isNotEmpty(interfaceName)) {
-                ClassLoader classLoader = providerUrl.getServiceModel() != null ?
-                    providerUrl.getServiceModel().getClassLoader() :
-                    ClassUtils.getClassLoader();
-                Class interfaceClass = Class.forName(interfaceName, false, classLoader);
-                FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass,
-                    providerUrl.getParameters());
-                for (Map.Entry<String, MetadataReport> entry : getMetadataReports().entrySet()) {
-                    MetadataReport metadataReport = entry.getValue();
-                    metadataReport.storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),
-                        providerUrl.getVersion(), providerUrl.getGroup(),
-                        PROVIDER_SIDE, providerUrl.getApplication()), fullServiceDefinition);
-                }
-                return;
-            }
-            logger.error("publishProvider interfaceName is empty. providerUrl: " + providerUrl.toFullString());
-        } catch (ClassNotFoundException e) {
-            //ignore error
-            logger.error("publishProvider getServiceDescriptor error. providerUrl: " + providerUrl.toFullString(), e);
-        }
-    }
-
-    private void publishConsumer(URL consumerURL) throws RpcException {
-        final URL url = consumerURL.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, REGISTER_IP_KEY);
-        getMetadataReports().forEach((registryKey, config) -> {
-            config.storeConsumerMetadata(new MetadataIdentifier(url.getServiceInterface(),
-                url.getVersion(), url.getGroup(), CONSUMER_SIDE,
-                url.getApplication()), url.getParameters());
-        });
-    }
-
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/model/MigrationRule.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/model/MigrationRule.java
index e2f10e4..de42362 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/model/MigrationRule.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/model/MigrationRule.java
@@ -20,6 +20,7 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.metadata.ServiceNameMapping;
+
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.constructor.Constructor;
 import org.yaml.snakeyaml.constructor.SafeConstructor;
@@ -87,8 +88,6 @@ public class MigrationRule {
     private transient Map<String, SubMigrationRule> interfaceRules;
     private transient Map<String, SubMigrationRule> applicationRules;
 
-
-
     @SuppressWarnings("unchecked")
     private static MigrationRule parseFromMap(Map<String, Object> map) {
         MigrationRule migrationRule = new MigrationRule();
@@ -161,7 +160,7 @@ public class MigrationRule {
 
         if (applications != null) {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(consumerURL.getScopeModel());
-            Set<String> services = serviceNameMapping.getServices(consumerURL);
+            Set<String> services = serviceNameMapping.getCachedMapping(consumerURL);
             if (CollectionUtils.isNotEmpty(services)) {
                 for (String service : services) {
                     SubMigrationRule rule = applicationRules.get(service);
@@ -202,7 +201,7 @@ public class MigrationRule {
 
         if (applications != null) {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(consumerURL.getScopeModel());
-            Set<String> services = serviceNameMapping.getServices(consumerURL);
+            Set<String> services = serviceNameMapping.getCachedMapping(consumerURL);
             if (CollectionUtils.isNotEmpty(services)) {
                 for (String service : services) {
                     SubMigrationRule rule = applicationRules.get(service);
@@ -242,7 +241,7 @@ public class MigrationRule {
 
         if (applications != null) {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(consumerURL.getScopeModel());
-            Set<String> services = serviceNameMapping.getServices(consumerURL);
+            Set<String> services = serviceNameMapping.getCachedMapping(consumerURL);
             if (CollectionUtils.isNotEmpty(services)) {
                 for (String service : services) {
                     SubMigrationRule rule = applicationRules.get(service);
@@ -278,7 +277,7 @@ public class MigrationRule {
 
         if (applications != null) {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(consumerURL.getScopeModel());
-            Set<String> services = serviceNameMapping.getServices(consumerURL);
+            Set<String> services = serviceNameMapping.getCachedMapping(consumerURL);
             if (CollectionUtils.isNotEmpty(services)) {
                 for (String service : services) {
                     SubMigrationRule rule = applicationRules.get(service);
@@ -318,7 +317,7 @@ public class MigrationRule {
 
         if (applications != null) {
             ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(consumerURL.getScopeModel());
-            Set<String> services = serviceNameMapping.getServices(consumerURL);
+            Set<String> services = serviceNameMapping.getCachedMapping(consumerURL);
             if (CollectionUtils.isNotEmpty(services)) {
                 for (String service : services) {
                     SubMigrationRule rule = applicationRules.get(service);
@@ -364,7 +363,6 @@ public class MigrationRule {
                 applicationRules.put(rule.getServiceKey(), rule);
             });
         }
-
     }
 
     public static MigrationRule parse(String rawRule) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService
index 0030270..13b0fd7 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService
@@ -1 +1 @@
-default=org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService
\ No newline at end of file
+default=org.apache.dubbo.registry.client.metadata.store.WritableMetadataServiceDelegation
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
index 042a071..0093a50 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
@@ -1,522 +1,522 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client.event.listener;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.MetadataInfo;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.NotifyListener;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.InstanceAddressURL;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-import org.apache.dubbo.registry.client.metadata.MetadataUtils;
-
-import com.google.gson.Gson;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-import org.mockito.ArgumentCaptor;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ThreadLocalRandom;
-
-import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
-import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-
-/**
- * {@link ServiceInstancesChangedListener} Test
- *
- * @since 2.7.5
- */
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-public class ServiceInstancesChangedListenerTest {
-    private static Gson gson = new Gson();
-
-    static List<ServiceInstance> app1Instances;
-    static List<ServiceInstance> app2Instances;
-    static List<ServiceInstance> app1FailedInstances;
-    static List<ServiceInstance> app1FailedInstances2;
-    static List<ServiceInstance> app1InstancesWithNoRevision;
-
-    static String metadata_111 = "{\"app\":\"app1\",\"revision\":\"111\",\"services\":{"
-        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"delay\ [...]
-        + "}}";
-    static String metadata_222 = "{\"app\":\"app2\",\"revision\":\"333\",\"services\":{"
-        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"delay\ [...]
-        + "\"org.apache.dubbo.demo.DemoService2:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService2\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService2\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService2\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"de [...]
-        + "}}";
-    static String metadata_333 = "{\"app\":\"app2\",\"revision\":\"333\",\"services\":{"
-        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"delay\ [...]
-        + "\"org.apache.dubbo.demo.DemoService2:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService2\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService2\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService2\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"de [...]
-        + "\"org.apache.dubbo.demo.DemoService3:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService3\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService3\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService3\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"de [...]
-        + "}}";
-    // failed
-    static String metadata_444 = "{\"app\":\"app1\",\"revision\":\"444\",\"services\":{"
-        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"delay\ [...]
-        + "}}";
-
-    static String bad_metadatainfo = "{\"xxx\":\"yyy\"}";
-
-    static String service1 = "org.apache.dubbo.demo.DemoService";
-    static String service2 = "org.apache.dubbo.demo.DemoService2";
-    static String service3 = "org.apache.dubbo.demo.DemoService3";
-
-    static URL consumerURL = URL.valueOf("dubbo://127.0.0.1/org.apache.dubbo.demo.DemoService?registry_cluster=default");
-
-    static MetadataInfo metadataInfo_111;
-    static MetadataInfo metadataInfo_222;
-    static MetadataInfo metadataInfo_333;
-    static MetadataInfo metadataInfo_444;
-
-    static MetadataService metadataService;
-
-    static ServiceDiscovery serviceDiscovery;
-
-    @BeforeAll
-    public static void setUp() {
-        List<Object> urlsSameRevision = new ArrayList<>();
-        urlsSameRevision.add("127.0.0.1:20880?revision=111");
-        urlsSameRevision.add("127.0.0.2:20880?revision=111");
-        urlsSameRevision.add("127.0.0.3:20880?revision=111");
-
-        List<Object> urlsDifferentRevision = new ArrayList<>();
-        urlsDifferentRevision.add("30.10.0.1:20880?revision=222");
-        urlsDifferentRevision.add("30.10.0.2:20880?revision=222");
-        urlsDifferentRevision.add("30.10.0.3:20880?revision=333");
-        urlsDifferentRevision.add("30.10.0.4:20880?revision=333");
-
-        List<Object> urlsFailedRevision = new ArrayList<>();
-        urlsFailedRevision.add("30.10.0.5:20880?revision=222");
-        urlsFailedRevision.add("30.10.0.6:20880?revision=222");
-        urlsFailedRevision.add("30.10.0.7:20880?revision=444");// revision will fail
-        urlsFailedRevision.add("30.10.0.8:20880?revision=444");// revision will fail
-
-        List<Object> urlsFailedRevision2 = new ArrayList<>();
-        urlsFailedRevision2.add("30.10.0.1:20880?revision=222");
-        urlsFailedRevision2.add("30.10.0.2:20880?revision=222");
-
-        List<Object> urlsWithoutRevision = new ArrayList<>();
-        urlsWithoutRevision.add("30.10.0.1:20880");
-
-        app1Instances = buildInstances(urlsSameRevision);
-        app2Instances = buildInstances(urlsDifferentRevision);
-        app1FailedInstances = buildInstances(urlsFailedRevision);
-        app1FailedInstances2 = buildInstances(urlsFailedRevision2);
-        app1InstancesWithNoRevision = buildInstances(urlsWithoutRevision);
-
-        metadataInfo_111 = gson.fromJson(metadata_111, MetadataInfo.class);
-        metadataInfo_222 = gson.fromJson(metadata_222, MetadataInfo.class);
-        metadataInfo_333 = gson.fromJson(metadata_333, MetadataInfo.class);
-        metadataInfo_444 = gson.fromJson(metadata_444, MetadataInfo.class);
-
-        metadataService = Mockito.mock(MetadataService.class);
-        Mockito.doReturn(metadataInfo_111).when(metadataService).getMetadataInfo("111");
-        Mockito.doReturn(metadataInfo_222).when(metadataService).getMetadataInfo("222");
-        Mockito.doReturn(metadataInfo_333).when(metadataService).getMetadataInfo("333");
-        Mockito.doThrow(IllegalStateException.class).when(metadataService).getMetadataInfo("444");
-
-        serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
-    }
-
-    // 正常场景。单应用app1 通知地址基本流程,只做instance-metadata关联,没有metadata内容的解析
-    @Test
-    @Order(1)
-    public void testInstanceNotification() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
-        ServiceInstancesChangedListener spyListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, serviceDiscovery));
-        Mockito.doReturn(metadataInfo_111).when(spyListener).getRemoteMetadata(eq("111"), Mockito.anyMap(), Mockito.any());
-        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
-        spyListener.onEvent(event);
-
-        Map<String, List<ServiceInstance>> allInstances = spyListener.getAllInstances();
-        Assertions.assertEquals(1, allInstances.size());
-        Assertions.assertEquals(3, allInstances.get("app1").size());
-
-        Map<String, MetadataInfo> revisionToMetadata = spyListener.getRevisionToMetadata();
-        Assertions.assertEquals(1, revisionToMetadata.size());
-        Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
-
-//        // test app2 notification
-//        Mockito.doReturn(metadataInfo_222).when(spyListener).getRemoteMetadata(eq("222"), Mockito.anyMap(), Mockito.anyList());
-//        Mockito.doReturn(metadataInfo_333).when(spyListener).getRemoteMetadata(eq("333"), Mockito.anyMap(), Mockito.anyList());
-//
-//        ServiceInstancesChangedEvent event_app2 = new ServiceInstancesChangedEvent("app2", app2Instances);
-//        spyListener.onEvent(event_app2);
-
-    }
-
-    // 正常场景。单应用app1,进一步检查 metadata service 是否正确映射
-    @Test
-    @Order(2)
-    public void testInstanceNotificationAndMetadataParse() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-            // notify instance change
-            ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
-            listener.onEvent(event);
-
-            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
-            Assertions.assertEquals(1, allInstances.size());
-            Assertions.assertEquals(3, allInstances.get("app1").size());
-
-            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
-            Assertions.assertEquals(1, revisionToMetadata.size());
-            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
-
-            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
-            Assertions.assertEquals(3, serviceUrls.size());
-            assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
-
-            assertThat(serviceUrls, Matchers.hasItem(Matchers.hasProperty("instance", Matchers.notNullValue())));
-            assertThat(serviceUrls, Matchers.hasItem(Matchers.hasProperty("metadataInfo", Matchers.notNullValue())));
-
-        }
-    }
-
-    // 正常场景。多应用,app1 app2 分别通知地址
-    @Test
-    @Order(3)
-    public void testMultipleAppNotification() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        serviceNames.add("app2");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-            // notify app1 instance change
-            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
-            listener.onEvent(app1_event);
-
-            // notify app2 instance change
-            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
-            listener.onEvent(app2_event);
-
-            // check
-            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
-            Assertions.assertEquals(2, allInstances.size());
-            Assertions.assertEquals(3, allInstances.get("app1").size());
-            Assertions.assertEquals(4, allInstances.get("app2").size());
-
-            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
-            Assertions.assertEquals(3, revisionToMetadata.size());
-            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
-            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
-            Assertions.assertEquals(metadataInfo_333, revisionToMetadata.get("333"));
-
-            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
-            Assertions.assertEquals(7, serviceUrls.size());
-            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
-            Assertions.assertEquals(4, serviceUrls2.size());
-            assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
-            List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
-            Assertions.assertEquals(2, serviceUrls3.size());
-            assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
-        }
-    }
-
-    // 正常场景。多应用,app1 app2,空地址通知(边界条件)能否解析出正确的空地址列表
-    @Test
-    @Order(4)
-    public void testMultipleAppEmptyNotification() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        serviceNames.add("app2");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-            // notify app1 instance change
-            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
-            listener.onEvent(app1_event);
-
-            // notify app2 instance change
-            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
-            listener.onEvent(app2_event);
-
-            // empty notification
-            ServiceInstancesChangedEvent app1_event_again = new ServiceInstancesChangedEvent("app1", Collections.EMPTY_LIST);
-            listener.onEvent(app1_event_again);
-
-            // check app1 cleared
-            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
-            Assertions.assertEquals(2, allInstances.size());
-            Assertions.assertEquals(0, allInstances.get("app1").size());
-            Assertions.assertEquals(4, allInstances.get("app2").size());
-
-            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
-            Assertions.assertEquals(2, revisionToMetadata.size());
-            Assertions.assertNull(revisionToMetadata.get("111"));
-            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
-            Assertions.assertEquals(metadataInfo_333, revisionToMetadata.get("333"));
-
-            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
-            Assertions.assertEquals(4, serviceUrls.size());
-            assertTrue(serviceUrls.get(0).getIp().contains("30.10."));
-            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
-            Assertions.assertEquals(4, serviceUrls2.size());
-            assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
-            List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
-            Assertions.assertEquals(2, serviceUrls3.size());
-            assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
-
-            // app2 empty notification
-            ServiceInstancesChangedEvent app2_event_again = new ServiceInstancesChangedEvent("app2", Collections.EMPTY_LIST);
-            listener.onEvent(app2_event_again);
-
-            // check app2 cleared
-            Map<String, List<ServiceInstance>> allInstances_app2 = listener.getAllInstances();
-            Assertions.assertEquals(2, allInstances_app2.size());
-            Assertions.assertEquals(0, allInstances_app2.get("app1").size());
-            Assertions.assertEquals(0, allInstances_app2.get("app2").size());
-
-            Map<String, MetadataInfo> revisionToMetadata_app2 = listener.getRevisionToMetadata();
-            Assertions.assertEquals(0, revisionToMetadata_app2.size());
-
-            assertTrue(isEmpty(listener.getAddresses(service1 + ":dubbo", consumerURL)));
-            assertTrue(isEmpty(listener.getAddresses(service2+ ":dubbo", consumerURL)));
-            assertTrue(isEmpty(listener.getAddresses(service3 + ":dubbo", consumerURL)));
-        }
-    }
-
-    // 正常场景。检查instance listener -> service listener(Directory)地址推送流程
-    @Test
-    @Order(5)
-    public void testServiceListenerNotification() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        serviceNames.add("app2");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-        NotifyListener demoServiceListener = Mockito.mock(NotifyListener.class);
-        NotifyListener demoService2Listener = Mockito.mock(NotifyListener.class);
-        listener.addListenerAndNotify(service1 + ":dubbo", demoServiceListener);
-        listener.addListenerAndNotify(service2 + ":dubbo", demoService2Listener);
-
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-            // notify app1 instance change
-            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
-            listener.onEvent(app1_event);
-
-            // check
-            ArgumentCaptor<List<URL>> captor = ArgumentCaptor.forClass(List.class);
-            Mockito.verify(demoServiceListener, Mockito.times(1)).notify(captor.capture());
-            List<URL> notifiedUrls = captor.getValue();
-            Assertions.assertEquals(3, notifiedUrls.size());
-            ArgumentCaptor<List<URL>> captor2 = ArgumentCaptor.forClass(List.class);
-            Mockito.verify(demoService2Listener, Mockito.times(1)).notify(captor2.capture());
-            List<URL> notifiedUrls2 = captor2.getValue();
-            Assertions.assertEquals(0, notifiedUrls2.size());
-
-            // notify app2 instance change
-            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
-            listener.onEvent(app2_event);
-
-            // check
-            ArgumentCaptor<List<URL>> app2_captor = ArgumentCaptor.forClass(List.class);
-            Mockito.verify(demoServiceListener, Mockito.times(2)).notify(app2_captor.capture());
-            List<URL> app2_notifiedUrls = app2_captor.getValue();
-            Assertions.assertEquals(7, app2_notifiedUrls.size());
-            ArgumentCaptor<List<URL>> app2_captor2 = ArgumentCaptor.forClass(List.class);
-            Mockito.verify(demoService2Listener, Mockito.times(2)).notify(app2_captor2.capture());
-            List<URL> app2_notifiedUrls2 = app2_captor2.getValue();
-            Assertions.assertEquals(4, app2_notifiedUrls2.size());
-        }
-
-        // test service listener still get notified when added after instance notification.
-        NotifyListener demoService3Listener = Mockito.mock(NotifyListener.class);
-        listener.addListenerAndNotify(service3 + ":dubbo", demoService3Listener);
-        Mockito.verify(demoService3Listener, Mockito.times(1)).notify(Mockito.anyList());
-    }
-
-    // revision 异常场景。第一次启动,完全拿不到metadata,只能通知部分地址
-    @Test
-    @Order(6)
-    public void testRevisionFailureOnStartup() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-            // notify app1 instance change
-            ServiceInstancesChangedEvent failed_revision_event = new ServiceInstancesChangedEvent("app1", app1FailedInstances);
-            listener.onEvent(failed_revision_event);
-
-            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
-            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
-
-            assertTrue(isEmpty(serviceUrls));
-            assertTrue(isEmpty(serviceUrls2));
-
-            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
-            Assertions.assertEquals(2, revisionToMetadata.size());
-            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
-            Assertions.assertEquals(MetadataInfo.EMPTY, revisionToMetadata.get("444"));
-        }
-    }
-
-    // revision 异常场景。运行中地址通知,拿不到revision就用老版本revision
-    @Test
-    public void testRevisionFailureOnNotification() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        serviceNames.add("app2");
-        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-
-        ConcurrentMap tmpProxyMap = MetadataUtils.metadataServiceProxies;
-
-        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
-            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
-
-            // notify app1 instance change
-            ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
-            listener.onEvent(event);
-
-            Mockito.when(metadataService.getMetadataInfo("222")).thenAnswer(new Answer<MetadataInfo>() {
-                @Override
-                public MetadataInfo answer(InvocationOnMock invocationOnMock) throws Throwable {
-                    if (Thread.currentThread().getName().contains("Dubbo-metadata-retry")) {
-                        return metadataInfo_222;
-                    }
-                    return null;
-                }
-            });
-//            Mockito.when(metadataService.getMetadataInfo("444")).thenAnswer(new Answer<MetadataInfo>() {
+///*
+// * Licensed to the Apache Software Foundation (ASF) under one or more
+// * contributor license agreements.  See the NOTICE file distributed with
+// * this work for additional information regarding copyright ownership.
+// * The ASF licenses this file to You under the Apache License, Version 2.0
+// * (the "License"); you may not use this file except in compliance with
+// * the License.  You may obtain a copy of the License at
+// *
+// *     http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// */
+//package org.apache.dubbo.registry.client.event.listener;
+//
+//import org.apache.dubbo.common.URL;
+//import org.apache.dubbo.common.utils.StringUtils;
+//import org.apache.dubbo.metadata.MetadataInfo;
+//import org.apache.dubbo.metadata.MetadataService;
+//import org.apache.dubbo.registry.NotifyListener;
+//import org.apache.dubbo.registry.client.DefaultServiceInstance;
+//import org.apache.dubbo.registry.client.InstanceAddressURL;
+//import org.apache.dubbo.registry.client.ServiceDiscovery;
+//import org.apache.dubbo.registry.client.ServiceInstance;
+//import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+//import org.apache.dubbo.registry.client.metadata.MetadataUtils;
+//
+//import com.google.gson.Gson;
+//import org.hamcrest.Matchers;
+//import org.junit.jupiter.api.Assertions;
+//import org.junit.jupiter.api.BeforeAll;
+//import org.junit.jupiter.api.MethodOrderer;
+//import org.junit.jupiter.api.Order;
+//import org.junit.jupiter.api.Test;
+//import org.junit.jupiter.api.TestMethodOrder;
+//import org.mockito.ArgumentCaptor;
+//import org.mockito.MockedStatic;
+//import org.mockito.Mockito;
+//import org.mockito.invocation.InvocationOnMock;
+//import org.mockito.stubbing.Answer;
+//
+//import java.util.ArrayList;
+//import java.util.Collections;
+//import java.util.HashMap;
+//import java.util.HashSet;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.Set;
+//import java.util.concurrent.ConcurrentMap;
+//import java.util.concurrent.ThreadLocalRandom;
+//
+//import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
+//import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
+//import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
+//import static org.hamcrest.MatcherAssert.assertThat;
+//import static org.junit.jupiter.api.Assertions.assertTrue;
+//import static org.mockito.ArgumentMatchers.eq;
+//
+///**
+// * {@link ServiceInstancesChangedListener} Test
+// *
+// * @since 2.7.5
+// */
+//@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+//public class ServiceInstancesChangedListenerTest {
+//    private static Gson gson = new Gson();
+//
+//    static List<ServiceInstance> app1Instances;
+//    static List<ServiceInstance> app2Instances;
+//    static List<ServiceInstance> app1FailedInstances;
+//    static List<ServiceInstance> app1FailedInstances2;
+//    static List<ServiceInstance> app1InstancesWithNoRevision;
+//
+//    static String metadata_111 = "{\"app\":\"app1\",\"revision\":\"111\",\"services\":{"
+//        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"dela [...]
+//        + "}}";
+//    static String metadata_222 = "{\"app\":\"app2\",\"revision\":\"333\",\"services\":{"
+//        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"dela [...]
+//        + "\"org.apache.dubbo.demo.DemoService2:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService2\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService2\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService2\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\" [...]
+//        + "}}";
+//    static String metadata_333 = "{\"app\":\"app2\",\"revision\":\"333\",\"services\":{"
+//        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"dela [...]
+//        + "\"org.apache.dubbo.demo.DemoService2:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService2\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService2\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService2\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\" [...]
+//        + "\"org.apache.dubbo.demo.DemoService3:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService3\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService3\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService3\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\" [...]
+//        + "}}";
+//    // failed
+//    static String metadata_444 = "{\"app\":\"app1\",\"revision\":\"444\",\"services\":{"
+//        + "\"org.apache.dubbo.demo.DemoService:dubbo\":{\"name\":\"org.apache.dubbo.demo.DemoService\",\"protocol\":\"dubbo\",\"path\":\"org.apache.dubbo.demo.DemoService\",\"params\":{\"side\":\"provider\",\"release\":\"\",\"methods\":\"sayHello,sayHelloAsync\",\"deprecated\":\"false\",\"dubbo\":\"2.0.2\",\"pid\":\"72723\",\"interface\":\"org.apache.dubbo.demo.DemoService\",\"service-name-mapping\":\"true\",\"timeout\":\"3000\",\"generic\":\"false\",\"metadata-type\":\"remote\",\"dela [...]
+//        + "}}";
+//
+//    static String bad_metadatainfo = "{\"xxx\":\"yyy\"}";
+//
+//    static String service1 = "org.apache.dubbo.demo.DemoService";
+//    static String service2 = "org.apache.dubbo.demo.DemoService2";
+//    static String service3 = "org.apache.dubbo.demo.DemoService3";
+//
+//    static URL consumerURL = URL.valueOf("dubbo://127.0.0.1/org.apache.dubbo.demo.DemoService?registry_cluster=default");
+//
+//    static MetadataInfo metadataInfo_111;
+//    static MetadataInfo metadataInfo_222;
+//    static MetadataInfo metadataInfo_333;
+//    static MetadataInfo metadataInfo_444;
+//
+//    static MetadataService metadataService;
+//
+//    static ServiceDiscovery serviceDiscovery;
+//
+//    @BeforeAll
+//    public static void setUp() {
+//        List<Object> urlsSameRevision = new ArrayList<>();
+//        urlsSameRevision.add("127.0.0.1:20880?revision=111");
+//        urlsSameRevision.add("127.0.0.2:20880?revision=111");
+//        urlsSameRevision.add("127.0.0.3:20880?revision=111");
+//
+//        List<Object> urlsDifferentRevision = new ArrayList<>();
+//        urlsDifferentRevision.add("30.10.0.1:20880?revision=222");
+//        urlsDifferentRevision.add("30.10.0.2:20880?revision=222");
+//        urlsDifferentRevision.add("30.10.0.3:20880?revision=333");
+//        urlsDifferentRevision.add("30.10.0.4:20880?revision=333");
+//
+//        List<Object> urlsFailedRevision = new ArrayList<>();
+//        urlsFailedRevision.add("30.10.0.5:20880?revision=222");
+//        urlsFailedRevision.add("30.10.0.6:20880?revision=222");
+//        urlsFailedRevision.add("30.10.0.7:20880?revision=444");// revision will fail
+//        urlsFailedRevision.add("30.10.0.8:20880?revision=444");// revision will fail
+//
+//        List<Object> urlsFailedRevision2 = new ArrayList<>();
+//        urlsFailedRevision2.add("30.10.0.1:20880?revision=222");
+//        urlsFailedRevision2.add("30.10.0.2:20880?revision=222");
+//
+//        List<Object> urlsWithoutRevision = new ArrayList<>();
+//        urlsWithoutRevision.add("30.10.0.1:20880");
+//
+//        app1Instances = buildInstances(urlsSameRevision);
+//        app2Instances = buildInstances(urlsDifferentRevision);
+//        app1FailedInstances = buildInstances(urlsFailedRevision);
+//        app1FailedInstances2 = buildInstances(urlsFailedRevision2);
+//        app1InstancesWithNoRevision = buildInstances(urlsWithoutRevision);
+//
+//        metadataInfo_111 = gson.fromJson(metadata_111, MetadataInfo.class);
+//        metadataInfo_222 = gson.fromJson(metadata_222, MetadataInfo.class);
+//        metadataInfo_333 = gson.fromJson(metadata_333, MetadataInfo.class);
+//        metadataInfo_444 = gson.fromJson(metadata_444, MetadataInfo.class);
+//
+//        metadataService = Mockito.mock(MetadataService.class);
+//        Mockito.doReturn(metadataInfo_111).when(metadataService).getMetadataInfo("111");
+//        Mockito.doReturn(metadataInfo_222).when(metadataService).getMetadataInfo("222");
+//        Mockito.doReturn(metadataInfo_333).when(metadataService).getMetadataInfo("333");
+//        Mockito.doThrow(IllegalStateException.class).when(metadataService).getMetadataInfo("444");
+//
+//        serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
+//    }
+//
+//    // 正常场景。单应用app1 通知地址基本流程,只做instance-metadata关联,没有metadata内容的解析
+//    @Test
+//    @Order(1)
+//    public void testInstanceNotification() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
+//        ServiceInstancesChangedListener spyListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, serviceDiscovery));
+//        Mockito.doReturn(metadataInfo_111).when(spyListener).getRemoteMetadata(eq("111"), Mockito.anyMap(), Mockito.any());
+//        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//        spyListener.onEvent(event);
+//
+//        Map<String, List<ServiceInstance>> allInstances = spyListener.getAllInstances();
+//        Assertions.assertEquals(1, allInstances.size());
+//        Assertions.assertEquals(3, allInstances.get("app1").size());
+//
+//        Map<String, MetadataInfo> revisionToMetadata = spyListener.getRevisionToMetadata();
+//        Assertions.assertEquals(1, revisionToMetadata.size());
+//        Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
+//
+////        // test app2 notification
+////        Mockito.doReturn(metadataInfo_222).when(spyListener).getRemoteMetadata(eq("222"), Mockito.anyMap(), Mockito.anyList());
+////        Mockito.doReturn(metadataInfo_333).when(spyListener).getRemoteMetadata(eq("333"), Mockito.anyMap(), Mockito.anyList());
+////
+////        ServiceInstancesChangedEvent event_app2 = new ServiceInstancesChangedEvent("app2", app2Instances);
+////        spyListener.onEvent(event_app2);
+//
+//    }
+//
+//    // 正常场景。单应用app1,进一步检查 metadata service 是否正确映射
+//    @Test
+//    @Order(2)
+//    public void testInstanceNotificationAndMetadataParse() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//            // notify instance change
+//            ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//            listener.onEvent(event);
+//
+//            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
+//            Assertions.assertEquals(1, allInstances.size());
+//            Assertions.assertEquals(3, allInstances.get("app1").size());
+//
+//            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(1, revisionToMetadata.size());
+//            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
+//
+//            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(3, serviceUrls.size());
+//            assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+//
+//            assertThat(serviceUrls, Matchers.hasItem(Matchers.hasProperty("instance", Matchers.notNullValue())));
+//            assertThat(serviceUrls, Matchers.hasItem(Matchers.hasProperty("metadataInfo", Matchers.notNullValue())));
+//
+//        }
+//    }
+//
+//    // 正常场景。多应用,app1 app2 分别通知地址
+//    @Test
+//    @Order(3)
+//    public void testMultipleAppNotification() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        serviceNames.add("app2");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//            // notify app1 instance change
+//            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//            listener.onEvent(app1_event);
+//
+//            // notify app2 instance change
+//            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
+//            listener.onEvent(app2_event);
+//
+//            // check
+//            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
+//            Assertions.assertEquals(2, allInstances.size());
+//            Assertions.assertEquals(3, allInstances.get("app1").size());
+//            Assertions.assertEquals(4, allInstances.get("app2").size());
+//
+//            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(3, revisionToMetadata.size());
+//            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
+//            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
+//            Assertions.assertEquals(metadataInfo_333, revisionToMetadata.get("333"));
+//
+//            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(7, serviceUrls.size());
+//            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(4, serviceUrls2.size());
+//            assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
+//            List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(2, serviceUrls3.size());
+//            assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
+//        }
+//    }
+//
+//    // 正常场景。多应用,app1 app2,空地址通知(边界条件)能否解析出正确的空地址列表
+//    @Test
+//    @Order(4)
+//    public void testMultipleAppEmptyNotification() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        serviceNames.add("app2");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//            // notify app1 instance change
+//            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//            listener.onEvent(app1_event);
+//
+//            // notify app2 instance change
+//            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
+//            listener.onEvent(app2_event);
+//
+//            // empty notification
+//            ServiceInstancesChangedEvent app1_event_again = new ServiceInstancesChangedEvent("app1", Collections.EMPTY_LIST);
+//            listener.onEvent(app1_event_again);
+//
+//            // check app1 cleared
+//            Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
+//            Assertions.assertEquals(2, allInstances.size());
+//            Assertions.assertEquals(0, allInstances.get("app1").size());
+//            Assertions.assertEquals(4, allInstances.get("app2").size());
+//
+//            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(2, revisionToMetadata.size());
+//            Assertions.assertNull(revisionToMetadata.get("111"));
+//            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
+//            Assertions.assertEquals(metadataInfo_333, revisionToMetadata.get("333"));
+//
+//            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(4, serviceUrls.size());
+//            assertTrue(serviceUrls.get(0).getIp().contains("30.10."));
+//            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(4, serviceUrls2.size());
+//            assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
+//            List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(2, serviceUrls3.size());
+//            assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
+//
+//            // app2 empty notification
+//            ServiceInstancesChangedEvent app2_event_again = new ServiceInstancesChangedEvent("app2", Collections.EMPTY_LIST);
+//            listener.onEvent(app2_event_again);
+//
+//            // check app2 cleared
+//            Map<String, List<ServiceInstance>> allInstances_app2 = listener.getAllInstances();
+//            Assertions.assertEquals(2, allInstances_app2.size());
+//            Assertions.assertEquals(0, allInstances_app2.get("app1").size());
+//            Assertions.assertEquals(0, allInstances_app2.get("app2").size());
+//
+//            Map<String, MetadataInfo> revisionToMetadata_app2 = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(0, revisionToMetadata_app2.size());
+//
+//            assertTrue(isEmpty(listener.getAddresses(service1 + ":dubbo", consumerURL)));
+//            assertTrue(isEmpty(listener.getAddresses(service2+ ":dubbo", consumerURL)));
+//            assertTrue(isEmpty(listener.getAddresses(service3 + ":dubbo", consumerURL)));
+//        }
+//    }
+//
+//    // 正常场景。检查instance listener -> service listener(Directory)地址推送流程
+//    @Test
+//    @Order(5)
+//    public void testServiceListenerNotification() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        serviceNames.add("app2");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//        NotifyListener demoServiceListener = Mockito.mock(NotifyListener.class);
+//        NotifyListener demoService2Listener = Mockito.mock(NotifyListener.class);
+//        listener.addListenerAndNotify(service1 + ":dubbo", demoServiceListener);
+//        listener.addListenerAndNotify(service2 + ":dubbo", demoService2Listener);
+//
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//            // notify app1 instance change
+//            ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//            listener.onEvent(app1_event);
+//
+//            // check
+//            ArgumentCaptor<List<URL>> captor = ArgumentCaptor.forClass(List.class);
+//            Mockito.verify(demoServiceListener, Mockito.times(1)).notify(captor.capture());
+//            List<URL> notifiedUrls = captor.getValue();
+//            Assertions.assertEquals(3, notifiedUrls.size());
+//            ArgumentCaptor<List<URL>> captor2 = ArgumentCaptor.forClass(List.class);
+//            Mockito.verify(demoService2Listener, Mockito.times(1)).notify(captor2.capture());
+//            List<URL> notifiedUrls2 = captor2.getValue();
+//            Assertions.assertEquals(0, notifiedUrls2.size());
+//
+//            // notify app2 instance change
+//            ServiceInstancesChangedEvent app2_event = new ServiceInstancesChangedEvent("app2", app2Instances);
+//            listener.onEvent(app2_event);
+//
+//            // check
+//            ArgumentCaptor<List<URL>> app2_captor = ArgumentCaptor.forClass(List.class);
+//            Mockito.verify(demoServiceListener, Mockito.times(2)).notify(app2_captor.capture());
+//            List<URL> app2_notifiedUrls = app2_captor.getValue();
+//            Assertions.assertEquals(7, app2_notifiedUrls.size());
+//            ArgumentCaptor<List<URL>> app2_captor2 = ArgumentCaptor.forClass(List.class);
+//            Mockito.verify(demoService2Listener, Mockito.times(2)).notify(app2_captor2.capture());
+//            List<URL> app2_notifiedUrls2 = app2_captor2.getValue();
+//            Assertions.assertEquals(4, app2_notifiedUrls2.size());
+//        }
+//
+//        // test service listener still get notified when added after instance notification.
+//        NotifyListener demoService3Listener = Mockito.mock(NotifyListener.class);
+//        listener.addListenerAndNotify(service3 + ":dubbo", demoService3Listener);
+//        Mockito.verify(demoService3Listener, Mockito.times(1)).notify(Mockito.anyList());
+//    }
+//
+//    // revision 异常场景。第一次启动,完全拿不到metadata,只能通知部分地址
+//    @Test
+//    @Order(6)
+//    public void testRevisionFailureOnStartup() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//            // notify app1 instance change
+//            ServiceInstancesChangedEvent failed_revision_event = new ServiceInstancesChangedEvent("app1", app1FailedInstances);
+//            listener.onEvent(failed_revision_event);
+//
+//            List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+//            List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+//
+//            assertTrue(isEmpty(serviceUrls));
+//            assertTrue(isEmpty(serviceUrls2));
+//
+//            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(2, revisionToMetadata.size());
+//            Assertions.assertEquals(metadataInfo_222, revisionToMetadata.get("222"));
+//            Assertions.assertEquals(MetadataInfo.EMPTY, revisionToMetadata.get("444"));
+//        }
+//    }
+//
+//    // revision 异常场景。运行中地址通知,拿不到revision就用老版本revision
+//    @Test
+//    public void testRevisionFailureOnNotification() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        serviceNames.add("app2");
+//        ServiceInstancesChangedListener listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+//
+//        ConcurrentMap tmpProxyMap = MetadataUtils.metadataServiceProxies;
+//
+//        try (MockedStatic<MetadataUtils> mockedMetadataUtils = Mockito.mockStatic(MetadataUtils.class)) {
+//            mockedMetadataUtils.when(() -> MetadataUtils.getMetadataServiceProxy(Mockito.any())).thenReturn(metadataService);
+//
+//            // notify app1 instance change
+//            ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
+//            listener.onEvent(event);
+//
+//            Mockito.when(metadataService.getMetadataInfo("222")).thenAnswer(new Answer<MetadataInfo>() {
 //                @Override
 //                public MetadataInfo answer(InvocationOnMock invocationOnMock) throws Throwable {
 //                    if (Thread.currentThread().getName().contains("Dubbo-metadata-retry")) {
-//                        return metadataInfo_444;
+//                        return metadataInfo_222;
 //                    }
 //                    return null;
 //                }
 //            });
-
-            ServiceInstancesChangedEvent event2 = new ServiceInstancesChangedEvent("app2", app1FailedInstances2);
-            listener.onEvent(event2);
-
-            // FIXME, manually mock proxy util, for retry task will work on another thread which makes MockStatic useless.
-            ConcurrentMap map = Mockito.mock(ConcurrentMap.class);
-            Mockito.doReturn(metadataService).when(map).get(Mockito.any());
-            Mockito.doReturn(metadataService).when(map).computeIfAbsent(Mockito.any(), Mockito.any());
-            MetadataUtils.metadataServiceProxies = map;
-
-            // event2 did not really take effect
-            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
-            Assertions.assertEquals(2, revisionToMetadata.size());
-            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
-            Assertions.assertEquals(MetadataInfo.EMPTY, revisionToMetadata.get("222"));
-
-            Assertions.assertEquals(3, listener.getAddresses(service1 + ":dubbo", consumerURL).size());
-            assertTrue(isEmpty(listener.getAddresses(service2 + ":dubbo", consumerURL)));
-
-            try {
-                Thread.sleep(15000);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-            // check recovered after retry.
-            Map<String, MetadataInfo> revisionToMetadata_after_retry = listener.getRevisionToMetadata();
-            Assertions.assertEquals(2, revisionToMetadata_after_retry.size());
-            Assertions.assertEquals(metadataInfo_111, revisionToMetadata_after_retry.get("111"));
-            Assertions.assertEquals(metadataInfo_222, revisionToMetadata_after_retry.get("222"));
-
-            List<URL> serviceUrls_after_retry = listener.getAddresses(service1 + ":dubbo", consumerURL);
-            Assertions.assertEquals(5, serviceUrls_after_retry.size());
-            List<URL> serviceUrls2_after_retry = listener.getAddresses(service2 + ":dubbo", consumerURL);
-            Assertions.assertEquals(2, serviceUrls2_after_retry.size());
-        } finally {
-            MetadataUtils.metadataServiceProxies = tmpProxyMap;
-        }
-    }
-
-    // Abnormal case. Instance does not has revision
-    @Test
-    public void testInstanceWithoutRevision() {
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
-        ServiceInstancesChangedListener spyListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, serviceDiscovery));
-        Mockito.doReturn(null).when(spyListener).getRemoteMetadata(eq(null), Mockito.anyMap(), Mockito.any());
-        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1InstancesWithNoRevision);
-        spyListener.onEvent(event);
-        // notification succeeded
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSelectInstance() {
-        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
-        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
-        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
-        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
-        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
-    }
-
-    static List<ServiceInstance> buildInstances(List<Object> rawURls) {
-        List<ServiceInstance> instances = new ArrayList<>();
-
-        for (Object obj : rawURls) {
-            String rawURL = (String)obj;
-            DefaultServiceInstance instance = new DefaultServiceInstance();
-            final URL dubboUrl = URL.valueOf(rawURL);
-            instance.setRawAddress(rawURL);
-            instance.setHost(dubboUrl.getHost());
-            instance.setEnabled(true);
-            instance.setHealthy(true);
-            instance.setPort(dubboUrl.getPort());
-            instance.setRegistryCluster("default");
-
-            Map<String, String> metadata = new HashMap<>();
-            if (StringUtils.isNotEmpty(dubboUrl.getParameter(REVISION_KEY))) {
-                metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, dubboUrl.getParameter(REVISION_KEY));
-            }
-            instance.setMetadata(metadata);
-
-            instances.add(instance);
-        }
-
-        return instances;
-    }
-}
+////            Mockito.when(metadataService.getMetadataInfo("444")).thenAnswer(new Answer<MetadataInfo>() {
+////                @Override
+////                public MetadataInfo answer(InvocationOnMock invocationOnMock) throws Throwable {
+////                    if (Thread.currentThread().getName().contains("Dubbo-metadata-retry")) {
+////                        return metadataInfo_444;
+////                    }
+////                    return null;
+////                }
+////            });
+//
+//            ServiceInstancesChangedEvent event2 = new ServiceInstancesChangedEvent("app2", app1FailedInstances2);
+//            listener.onEvent(event2);
+//
+//            // FIXME, manually mock proxy util, for retry task will work on another thread which makes MockStatic useless.
+//            ConcurrentMap map = Mockito.mock(ConcurrentMap.class);
+//            Mockito.doReturn(metadataService).when(map).get(Mockito.any());
+//            Mockito.doReturn(metadataService).when(map).computeIfAbsent(Mockito.any(), Mockito.any());
+//            MetadataUtils.metadataServiceProxies = map;
+//
+//            // event2 did not really take effect
+//            Map<String, MetadataInfo> revisionToMetadata = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(2, revisionToMetadata.size());
+//            Assertions.assertEquals(metadataInfo_111, revisionToMetadata.get("111"));
+//            Assertions.assertEquals(MetadataInfo.EMPTY, revisionToMetadata.get("222"));
+//
+//            Assertions.assertEquals(3, listener.getAddresses(service1 + ":dubbo", consumerURL).size());
+//            assertTrue(isEmpty(listener.getAddresses(service2 + ":dubbo", consumerURL)));
+//
+//            try {
+//                Thread.sleep(15000);
+//            } catch (InterruptedException e) {
+//                e.printStackTrace();
+//            }
+//            // check recovered after retry.
+//            Map<String, MetadataInfo> revisionToMetadata_after_retry = listener.getRevisionToMetadata();
+//            Assertions.assertEquals(2, revisionToMetadata_after_retry.size());
+//            Assertions.assertEquals(metadataInfo_111, revisionToMetadata_after_retry.get("111"));
+//            Assertions.assertEquals(metadataInfo_222, revisionToMetadata_after_retry.get("222"));
+//
+//            List<URL> serviceUrls_after_retry = listener.getAddresses(service1 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(5, serviceUrls_after_retry.size());
+//            List<URL> serviceUrls2_after_retry = listener.getAddresses(service2 + ":dubbo", consumerURL);
+//            Assertions.assertEquals(2, serviceUrls2_after_retry.size());
+//        } finally {
+//            MetadataUtils.metadataServiceProxies = tmpProxyMap;
+//        }
+//    }
+//
+//    // Abnormal case. Instance does not has revision
+//    @Test
+//    public void testInstanceWithoutRevision() {
+//        Set<String> serviceNames = new HashSet<>();
+//        serviceNames.add("app1");
+//        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
+//        ServiceInstancesChangedListener spyListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, serviceDiscovery));
+//        Mockito.doReturn(null).when(spyListener).getRemoteMetadata(eq(null), Mockito.anyMap(), Mockito.any());
+//        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1InstancesWithNoRevision);
+//        spyListener.onEvent(event);
+//        // notification succeeded
+//        assertTrue(true);
+//    }
+//
+//    @Test
+//    public void testSelectInstance() {
+//        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
+//        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
+//        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
+//        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
+//        System.out.println(ThreadLocalRandom.current().nextInt(0, 100));
+//    }
+//
+//    static List<ServiceInstance> buildInstances(List<Object> rawURls) {
+//        List<ServiceInstance> instances = new ArrayList<>();
+//
+//        for (Object obj : rawURls) {
+//            String rawURL = (String)obj;
+//            DefaultServiceInstance instance = new DefaultServiceInstance();
+//            final URL dubboUrl = URL.valueOf(rawURL);
+//            instance.setRawAddress(rawURL);
+//            instance.setHost(dubboUrl.getHost());
+//            instance.setEnabled(true);
+//            instance.setHealthy(true);
+//            instance.setPort(dubboUrl.getPort());
+//            instance.setRegistryCluster("default");
+//
+//            Map<String, String> metadata = new HashMap<>();
+//            if (StringUtils.isNotEmpty(dubboUrl.getParameter(REVISION_KEY))) {
+//                metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, dubboUrl.getParameter(REVISION_KEY));
+//            }
+//            instance.setMetadata(metadata);
+//
+//            instances.add(instance);
+//        }
+//
+//        return instances;
+//    }
+//}
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizerTest.java
index f3ea39d..eb38149 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizerTest.java
@@ -16,14 +16,15 @@
  */
 package org.apache.dubbo.registry.client.metadata;
 
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -48,7 +49,7 @@ public class MetadataServiceURLParamsMetadataCustomizerTest {
     private static final Gson gson = new Gson();
 
     public DefaultServiceInstance instance;
-    private InMemoryWritableMetadataService metadataService;
+    private MetadataServiceDelegation metadataService;
     private URL metadataServiceURL = URL.valueOf("metadata://127.0.0.1:21881/" + MetadataService.class.getName() +
         "?application=demo&group=g1&version=1.0.0&timestamp=1632662388960");
 
@@ -60,7 +61,7 @@ public class MetadataServiceURLParamsMetadataCustomizerTest {
     @BeforeEach
     public void init() {
         instance = createInstance();
-        metadataService = mock(InMemoryWritableMetadataService.class);
+        metadataService = mock(MetadataServiceDelegation.class);
         when(metadataService.getMetadataServiceURL()).thenReturn(metadataServiceURL);
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ProtocolPortsMetadataCustomizerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ProtocolPortsMetadataCustomizerTest.java
index 3f946c6..fa058f8 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ProtocolPortsMetadataCustomizerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ProtocolPortsMetadataCustomizerTest.java
@@ -16,14 +16,15 @@
  */
 package org.apache.dubbo.registry.client.metadata;
 
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
 import org.hamcrest.MatcherAssert;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -51,7 +52,7 @@ public class ProtocolPortsMetadataCustomizerTest {
     private static final Gson gson = new Gson();
 
     public DefaultServiceInstance instance;
-    private InMemoryWritableMetadataService metadataService;
+    private MetadataServiceDelegation metadataService;
 
     public static DefaultServiceInstance createInstance() {
         return new DefaultServiceInstance("A", "127.0.0.1", 20880, ApplicationModel.defaultModel());
@@ -71,7 +72,7 @@ public class ProtocolPortsMetadataCustomizerTest {
     @BeforeEach
     public void init() {
         instance = createInstance();
-        metadataService = mock(InMemoryWritableMetadataService.class);
+        metadataService = mock(MetadataServiceDelegation.class);
 
         URL dubboUrl = URL.valueOf("dubbo://30.10.104.63:20880/org.apache.dubbo.demo.GreetingService?" +
             "REGISTRY_CLUSTER=registry1&anyhost=true&application=demo-provider2&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=55805&release=&revision=1.0.0&service-name-mapping=true&side=provider&timeout=5000&timestamp=1630229110058&version=1.0.0");
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizerTest.java
index fcc81d7..8140c96 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataCustomizerTest.java
@@ -22,7 +22,7 @@ import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.MetadataServiceDelegation;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.AfterAll;
@@ -45,7 +45,7 @@ import static org.mockito.Mockito.when;
 
 public class ServiceInstanceMetadataCustomizerTest {
     public DefaultServiceInstance instance;
-    private InMemoryWritableMetadataService metadataService;
+    private MetadataServiceDelegation metadataService;
 
     public static DefaultServiceInstance createInstance() {
         return new DefaultServiceInstance("A", "127.0.0.1", 20880, ApplicationModel.defaultModel());
@@ -65,7 +65,7 @@ public class ServiceInstanceMetadataCustomizerTest {
     @BeforeEach
     public void init() {
         instance = createInstance();
-        metadataService = mock(InMemoryWritableMetadataService.class);
+        metadataService = mock(MetadataServiceDelegation.class);
 
         URL url = URL.valueOf("dubbo://30.10.104.63:20880/org.apache.dubbo.demo.GreetingService?" + "params-filter=-default&" +
             "REGISTRY_CLUSTER=registry1&anyhost=true&application=demo-provider2&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting&interface=org.apache.dubbo.demo.GreetingService&metadata-type=remote&methods=hello&pid=55805&release=&revision=1.0.0&service-name-mapping=true&side=provider&timeout=5000&timestamp=1630229110058&version=1.0.0");
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
index 7a83c1d..73ed92b 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java
@@ -18,15 +18,12 @@ package org.apache.dubbo.registry.client.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.ApplicationConfig;
-import org.apache.dubbo.metadata.MetadataInfo;
-import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.InMemoryServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
-import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
@@ -38,7 +35,6 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -48,11 +44,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.INSTANCE_REVISION_UPDATED_KEY;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_CLUSTER_PROPERTY_NAME;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_STORAGE_TYPE_PROPERTY_NAME;
@@ -140,15 +133,6 @@ public class ServiceInstanceMetadataUtilsTest {
     }
 
     @Test
-    public void testInstanceUpdateKey() {
-        serviceInstance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true");
-        Assertions.assertTrue(ServiceInstanceMetadataUtils.isInstanceUpdated(serviceInstance));
-
-        ServiceInstanceMetadataUtils.resetInstanceUpdateKey(serviceInstance);
-        Assertions.assertFalse(ServiceInstanceMetadataUtils.isInstanceUpdated(serviceInstance));
-    }
-
-    @Test
     public void testEndpoints() {
         Assertions.assertFalse(ServiceInstanceMetadataUtils.hasEndpoints(serviceInstance));
 
@@ -167,31 +151,9 @@ public class ServiceInstanceMetadataUtilsTest {
     }
 
     @Test
-    public void testCalInstanceRevision() {
-        URL url1 = URL.valueOf("test://127.0.0.1:8080/" + ServiceInstanceMetadataUtils.class.getName() + "?version=1.0.0");
-        URL url2 = URL.valueOf("test://127.0.0.1:8080/" + ServiceInstanceMetadataUtils.class.getName() + "?version=2.0.0");
-
-        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
-
-        InMemoryWritableMetadataService writableMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension(serviceInstance.getApplicationModel());
-        MetadataInfo metadataInfo = new MetadataInfo("demo");
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url1));
-        writableMetadataService.addMetadataInfo(DEFAULT_KEY, metadataInfo);
-
-        ServiceInstanceMetadataUtils.calInstanceRevision(serviceDiscovery, serviceInstance);
-        Assertions.assertEquals(metadataInfo.calAndGetRevision(), serviceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME));
-        Assertions.assertNull(serviceInstance.getExtendParams().get(INSTANCE_REVISION_UPDATED_KEY));
-
-        writableMetadataService.getMetadataInfos().get(DEFAULT_KEY).addService(new MetadataInfo.ServiceInfo(url2));
-        ServiceInstanceMetadataUtils.calInstanceRevision(serviceDiscovery, serviceInstance);
-        Assertions.assertEquals(metadataInfo.calAndGetRevision(), serviceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME));
-        Assertions.assertEquals(serviceInstance.getExtendParams().get(INSTANCE_REVISION_UPDATED_KEY), "true");
-    }
-
-    @Test
     public void testRegisterMetadataAndInstance() throws Exception {
         InMemoryServiceDiscovery inMemoryServiceDiscovery = prepare();
-        ServiceInstanceMetadataUtils.registerMetadataAndInstance(serviceInstance);
+        ServiceInstanceMetadataUtils.registerMetadataAndInstance(ApplicationModel.defaultModel());
 
         Assertions.assertTrue(inMemoryServiceDiscovery.getServices().contains(serviceInstance.getServiceName()));
     }
@@ -202,7 +164,7 @@ public class ServiceInstanceMetadataUtilsTest {
 
         Assertions.assertNull(inMemoryServiceDiscovery.getLocalInstance());
 
-        ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance);
+        ServiceInstanceMetadataUtils.refreshMetadataAndInstance(ApplicationModel.defaultModel());
 
         Assertions.assertEquals(inMemoryServiceDiscovery.getLocalInstance().getServiceName(), serviceInstance.getServiceName());
         Assertions.assertEquals(inMemoryServiceDiscovery.getLocalInstance().getHost(), serviceInstance.getHost());
@@ -211,14 +173,7 @@ public class ServiceInstanceMetadataUtilsTest {
     }
 
     private InMemoryServiceDiscovery prepare() throws NoSuchMethodException, InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException, NoSuchFieldException {
-
-
-
         WritableMetadataService writableMetadataService = WritableMetadataService.getDefaultExtension(ApplicationModel.defaultModel());
-        // Prevent NPE  when calling the refreshMetadataAndInstance method (customizeInstance -> MetadataServiceURLParamsMetadataCustomizer.customize)
-        URL metadataURL = URL.valueOf("dubbo://127.0.0.1:8080/" + MetadataService.class);
-        writableMetadataService.setMetadataServiceURL(metadataURL);
-
 
         // Construct serviceDiscoveryRegistry
         InMemoryServiceDiscovery inMemoryServiceDiscovery = new InMemoryServiceDiscovery();
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/InMemoryMetadataServiceTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/InMemoryMetadataServiceTest.java
index 7f770e7..1cd9c76 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/InMemoryMetadataServiceTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/InMemoryMetadataServiceTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.registry.client.metadata.store;
 
-import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.metadata.MetadataInfo;
@@ -25,6 +24,8 @@ import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
 import org.apache.dubbo.registry.MockLogger;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+
+import com.google.gson.Gson;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -73,7 +74,7 @@ public class InMemoryMetadataServiceTest {
      */
     @Test
     public void testExport() {
-        InMemoryWritableMetadataService metadataService = new InMemoryWritableMetadataService();
+        MetadataServiceDelegation metadataService = new MetadataServiceDelegation();
         metadataService.setApplicationModel(ApplicationModel.defaultModel());
         // export normal url
         URL url = URL.valueOf("dubbo://30.225.21.30:20880/org.apache.dubbo.registry.service.DemoService?" +
@@ -160,7 +161,7 @@ public class InMemoryMetadataServiceTest {
      */
     @Test
     public void testUnExport() {
-        InMemoryWritableMetadataService metadataService = new InMemoryWritableMetadataService();
+        MetadataServiceDelegation metadataService = new MetadataServiceDelegation();
         metadataService.setApplicationModel(ApplicationModel.defaultModel());
         // export normal url
         URL url = URL.valueOf("dubbo://30.225.21.30:20880/org.apache.dubbo.registry.service.DemoService?" +
@@ -183,7 +184,7 @@ public class InMemoryMetadataServiceTest {
     @Test
     public void testServiceDefinition() {
         URL url = URL.valueOf("dubbo://30.225.21.30:20880/org.apache.dubbo.registry.service.DemoService");
-        InMemoryWritableMetadataService metadataService = new InMemoryWritableMetadataService();
+        MetadataServiceDelegation metadataService = new MetadataServiceDelegation();
         metadataService.setApplicationModel(ApplicationModel.defaultModel());
         metadataService.publishServiceDefinition(url);
 
@@ -195,7 +196,7 @@ public class InMemoryMetadataServiceTest {
 
     @Test
     public void testSubscribe() {
-        InMemoryWritableMetadataService metadataService = new InMemoryWritableMetadataService();
+        MetadataServiceDelegation metadataService = new MetadataServiceDelegation();
         metadataService.setApplicationModel(ApplicationModel.defaultModel());
 
         URL url = URL.valueOf("dubbo://30.225.21.30:20880/org.apache.dubbo.registry.service.DemoService");
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImplTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImplTest.java
deleted file mode 100644
index a7e7f85..0000000
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImplTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client.metadata.store;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
-import org.apache.dubbo.metadata.MetadataInfo;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.stubbing.Answer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class RemoteMetadataServiceImplTest {
-
-    private static final String REGISTRY_CLUSTER = "registry9103";
-    private static final String SERVICE_NAME = "A";
-    private RemoteMetadataServiceImpl remoteMetadataService;
-    private MetadataReport metadataReport;
-    private MetadataInfo metadataInfo;
-    private String reversion;
-
-    @BeforeEach
-    public void setUp() {
-        ApplicationModel applicationModel = spy(ApplicationModel.defaultModel());
-        ScopeBeanFactory beanFactory = mock(ScopeBeanFactory.class);
-        MetadataReportInstance metadataReportInstance = mock(MetadataReportInstance.class);
-        metadataReport = mock(MetadataReport.class);
-
-        Map<String, MetadataReport> clusterToMetadataReport = new HashMap<>();
-        clusterToMetadataReport.put(REGISTRY_CLUSTER, metadataReport);
-        when(metadataReportInstance.getMetadataReports(anyBoolean())).thenReturn(clusterToMetadataReport);
-        when(applicationModel.getBeanFactory()).thenReturn(beanFactory);
-        when(beanFactory.getBean(MetadataReportInstance.class)).thenReturn(metadataReportInstance);
-
-        metadataInfo = new MetadataInfo();
-        URL url = URL.valueOf("dubbo://30.225.21.30:20880/org.apache.dubbo.registry.service.DemoService");
-        metadataInfo.addService(new MetadataInfo.ServiceInfo(url));
-        reversion = metadataInfo.calAndGetRevision();
-        Map<String, MetadataInfo> clusterToMetadataInfo = new HashMap<>();
-        clusterToMetadataInfo.put(REGISTRY_CLUSTER, metadataInfo);
-
-        WritableMetadataService writableMetadataService = mock(WritableMetadataService.class);
-        when(applicationModel.getDefaultExtension(WritableMetadataService.class)).thenReturn(writableMetadataService);
-        when(writableMetadataService.getMetadataInfos()).thenReturn(clusterToMetadataInfo);
-        when(metadataReport.getAppMetadata(any(),any())).thenAnswer((Answer<MetadataInfo>) invocationOnMock -> {
-            SubscriberMetadataIdentifier identifier = invocationOnMock.getArgument(0, SubscriberMetadataIdentifier.class);
-            if (SERVICE_NAME.equals(identifier.getApplication()) && reversion.equals(identifier.getRevision())) {
-                return metadataInfo;
-            }
-            return null;
-        });
-
-
-        remoteMetadataService = new RemoteMetadataServiceImpl();
-        remoteMetadataService.setScopeModel(applicationModel);
-    }
-
-    @Test
-    public void testPublishAndGetMetadata() {
-
-        // test getMetadataReports
-        Map<String, MetadataReport> metadataReports = remoteMetadataService.getMetadataReports();
-        Assertions.assertTrue(metadataReports.containsKey(REGISTRY_CLUSTER));
-
-        // test publishMetadata
-        remoteMetadataService.publishMetadata(SERVICE_NAME);
-
-        ArgumentCaptor<SubscriberMetadataIdentifier> identifierArgumentCaptor = ArgumentCaptor.forClass(SubscriberMetadataIdentifier.class);
-        ArgumentCaptor<MetadataInfo> metadataInfoArgumentCaptor = ArgumentCaptor.forClass(MetadataInfo.class);
-        verify(metadataReport, times(1)).publishAppMetadata(identifierArgumentCaptor.capture(), metadataInfoArgumentCaptor.capture());
-        SubscriberMetadataIdentifier identifier = identifierArgumentCaptor.getValue();
-        Assertions.assertEquals(identifier.getRevision(), reversion);
-        Assertions.assertEquals(identifier.getApplication(), SERVICE_NAME);
-
-        // test getMetadata
-        DefaultServiceInstance serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "127.0.0.1", 20880, ApplicationModel.defaultModel());
-        serviceInstance.setRegistryCluster(REGISTRY_CLUSTER);
-        serviceInstance.getMetadata().put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, reversion);
-
-        MetadataInfo remoteMetadataInfo = remoteMetadataService.getMetadata(serviceInstance);
-        Assertions.assertEquals(remoteMetadataInfo, metadataInfo);
-
-        serviceInstance.setServiceName("FAIL_SERVICE_NAME");
-        remoteMetadataInfo = remoteMetadataService.getMetadata(serviceInstance);
-        Assertions.assertNull(remoteMetadataInfo);
-    }
-
-    @Test
-    public void testPublishServiceDefinition() {
-        // test provider publishServiceDefinition
-        URL providerURL = URL.valueOf("dubbo://127.0.0.1:8888/org.apache.dubbo.registry.service.DemoService?side=provider");
-        remoteMetadataService.publishServiceDefinition(providerURL);
-        verify(metadataReport, times(1)).storeProviderMetadata(any(),any());
-
-        // test consumer publishServiceDefinition
-        URL consumerURL = URL.valueOf("dubbo://127.0.0.1:8888/org.apache.dubbo.registry.service.DemoService?side=consumer");
-        remoteMetadataService.publishServiceDefinition(consumerURL);
-        verify(metadataReport, times(1)).storeConsumerMetadata(any(),any());
-
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/model/MigrationRuleTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/model/MigrationRuleTest.java
index 53b9899..f2668bf 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/model/MigrationRuleTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/migration/model/MigrationRuleTest.java
@@ -18,8 +18,8 @@ package org.apache.dubbo.registry.client.migration.model;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.metadata.ServiceNameMapping;
-import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mockito;
@@ -99,8 +99,7 @@ public class MigrationRuleTest {
 
         Mockito.when(url.getDisplayServiceKey()).thenReturn("GreetingService:1.0.1");
         Mockito.when(url.getServiceInterface()).thenReturn("GreetingService");
-        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension(defaultModel);
-        metadataService.putCachedMapping(ServiceNameMapping.buildMappingKey(url), Collections.singleton("TestApplication"));
+        when(mapping.getCachedMapping(any(URL.class))).thenReturn(Collections.singleton("TestApplication"));
 
         Set<String> services = new HashSet<>();
         services.add("TestApplication");
@@ -110,7 +109,8 @@ public class MigrationRuleTest {
         assertEquals(10, migrationRule.getDelay(url));
         assertEquals(false, migrationRule.getForce(url));
         assertEquals(MigrationStep.FORCE_INTERFACE, migrationRule.getStep(url));
-        metadataService.removeCachedMapping("GreetingService");
+        when(mapping.getCachedMapping(any(URL.class))).thenReturn(Collections.emptySet());
+
         ApplicationModel.defaultModel().destroy();
     }
 }
diff --git a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulParameter.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulParameter.java
deleted file mode 100644
index f3fb024..0000000
--- a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulParameter.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.consul;
-
-import org.apache.dubbo.common.URL;
-
-import static org.apache.dubbo.common.utils.StringUtils.isBlank;
-
-/**
- * The enumeration for the Consul's parameters on the {@link URL}
- *
- * @see URL#getParameters()
- * @since 2.7.8
- */
-public enum ConsulParameter {
-
-    ACL_TOKEN,
-
-    TAGS,
-
-    INSTANCE_ZONE,
-
-    DEFAULT_ZONE_METADATA_NAME("zone"),
-
-    INSTANCE_GROUP,
-
-    CONSISTENCY_MODE,
-
-    ;
-
-    private final String name;
-
-    private final String defaultValue;
-
-    ConsulParameter() {
-        this(null);
-    }
-
-    ConsulParameter(String defaultValue) {
-        this(null, defaultValue);
-    }
-
-    ConsulParameter(String name, String defaultValue) {
-        this.name = isBlank(name) ? defaultName() : name;
-        this.defaultValue = defaultValue;
-    }
-
-    private String defaultName() {
-        return name().toLowerCase().replace('_', '-');
-    }
-
-    /**
-     * The parameter value from the specified registry {@link URL}
-     *
-     * @param registryURL the specified registry {@link URL}
-     * @return <code>defaultValue</code> if not found
-     */
-    public String getValue(URL registryURL) {
-        return registryURL.getParameter(name, defaultValue);
-    }
-
-    /**
-     * The parameter value from the specified registry {@link URL}
-     *
-     * @param registryURL  the specified registry {@link URL}
-     * @param valueType    the type of parameter value
-     * @param defaultValue the default value if parameter is absent
-     * @return <code>defaultValue</code> if not found
-     */
-    public <T> T getValue(URL registryURL, Class<T> valueType, T defaultValue) {
-        return registryURL.getParameter(name, valueType, defaultValue);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java
deleted file mode 100644
index bd77db8..0000000
--- a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.consul;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-
-public class ConsulServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
-
-    @Override
-    protected ServiceDiscovery createDiscovery(URL registryURL) {
-        return new ConsulServiceDiscovery();
-    }
-
-}
diff --git a/dubbo-registry/dubbo-registry-consul/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-consul/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
deleted file mode 100644
index a0f1252..0000000
--- a/dubbo-registry/dubbo-registry-consul/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
+++ /dev/null
@@ -1 +0,0 @@
-consul=org.apache.dubbo.registry.consul.ConsulServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/pom.xml b/dubbo-registry/dubbo-registry-dns/pom.xml
deleted file mode 100644
index 54e526c..0000000
--- a/dubbo-registry/dubbo-registry-dns/pom.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xmlns="http://maven.apache.org/POM/4.0.0"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.apache.dubbo</groupId>
-        <artifactId>dubbo-registry</artifactId>
-        <version>${revision}</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>dubbo-registry-dns</artifactId>
-    <packaging>jar</packaging>
-    <name>${project.artifactId}</name>
-    <description>The DNS registry module of Dubbo project</description>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-registry-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-common</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-all</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-config-api</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-rpc-dubbo</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-serialization-hessian2</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-remoting-netty4</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-registry-multicast</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-    </dependencies>
-
-</project>
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java
deleted file mode 100644
index 79fa0cd..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.NotifyListener;
-import org.apache.dubbo.registry.support.FailbackRegistry;
-
-/**
- * Empty implements for DNS <br/>
- * DNS only support `Service Discovery` mode register <br/>
- * Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
- * {@link DNSServiceDiscovery} is the real implementation of DNS
- */
-public class DNSRegistry extends FailbackRegistry {
-    public DNSRegistry(URL url) {
-        super(url);
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return true;
-    }
-
-    @Override
-    public void doRegister(URL url) {
-
-    }
-
-    @Override
-    public void doUnregister(URL url) {
-
-    }
-
-    @Override
-    public void doSubscribe(URL url, NotifyListener listener) {
-
-    }
-
-    @Override
-    public void doUnsubscribe(URL url, NotifyListener listener) {
-
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java
deleted file mode 100644
index 870485e..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.Registry;
-import org.apache.dubbo.registry.support.AbstractRegistryFactory;
-
-public class DNSRegistryFactory extends AbstractRegistryFactory {
-
-    @Override
-    protected String createRegistryCacheKey(URL url) {
-        return url.toFullString();
-    }
-
-    @Override
-    protected Registry createRegistry(URL url) {
-        return new DNSRegistry(url);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java
deleted file mode 100644
index 8f302fb..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns;
-
-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.NamedThreadFactory;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.SelfHostMetaServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
-import org.apache.dubbo.registry.dns.util.DNSClientConst;
-import org.apache.dubbo.registry.dns.util.DNSResolver;
-import org.apache.dubbo.registry.dns.util.ResolveResult;
-import org.apache.dubbo.rpc.model.ScopeModelUtil;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-public class DNSServiceDiscovery extends SelfHostMetaServiceDiscovery {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    /**
-     * DNS properties
-     */
-
-    private String addressPrefix;
-    private String addressSuffix;
-    private long pollingCycle;
-    private DNSResolver dnsResolver;
-
-    /**
-     * Polling task ScheduledFuture, used to stop task when destroy
-     */
-    private final ConcurrentHashMap<String, ScheduledFuture<?>> pollingExecutorMap = new ConcurrentHashMap<>();
-
-    /**
-     * Polling check provider ExecutorService
-     */
-    private ScheduledExecutorService pollingExecutorService;
-
-    @Override
-    public void doInitialize(URL registryURL) throws Exception {
-        this.addressPrefix = registryURL.getParameter(DNSClientConst.ADDRESS_PREFIX, "");
-        this.addressSuffix = registryURL.getParameter(DNSClientConst.ADDRESS_SUFFIX, "");
-        this.pollingCycle = registryURL.getParameter(DNSClientConst.DNS_POLLING_CYCLE, DNSClientConst.DEFAULT_DNS_POLLING_CYCLE);
-
-        String nameserver = registryURL.getHost();
-        int port = registryURL.getPort();
-        int maxQueriesPerResolve = registryURL.getParameter(DNSClientConst.MAX_QUERIES_PER_RESOLVE, 10);
-        this.dnsResolver = new DNSResolver(nameserver, port, maxQueriesPerResolve);
-
-
-        int scheduledThreadPoolSize = registryURL.getParameter(DNSClientConst.DNS_POLLING_POOL_SIZE_KEY, DNSClientConst.DEFAULT_DNS_POLLING_POOL_SIZE);
-
-        // polling task may take a lot of time, create a new ScheduledThreadPool
-        pollingExecutorService = Executors.newScheduledThreadPool(scheduledThreadPoolSize, new NamedThreadFactory("Dubbo-DNS-Poll"));
-
-    }
-
-    @Override
-    public void doDestroy() throws Exception {
-        dnsResolver.destroy();
-        pollingExecutorMap.forEach((serviceName, scheduledFuture) -> scheduledFuture.cancel(true));
-        pollingExecutorMap.clear();
-        pollingExecutorService.shutdown();
-    }
-
-    @Override
-    public Set<String> getServices() {
-        // it is impossible for dns to discover service names
-        return Collections.singleton("Unsupported Method");
-    }
-
-    @Override
-    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
-
-        String serviceAddress = addressPrefix + serviceName + addressSuffix;
-
-        ResolveResult resolveResult = dnsResolver.resolve(serviceAddress);
-
-        return toServiceInstance(serviceName, resolveResult);
-    }
-
-    @Override
-    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
-        listener.getServiceNames().forEach(serviceName -> {
-            ScheduledFuture<?> scheduledFuture = pollingExecutorService.scheduleAtFixedRate(() -> {
-                        List<ServiceInstance> instances = getInstances(serviceName);
-                        instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
-                        notifyListener(serviceName, listener, instances);
-                    },
-                    pollingCycle, pollingCycle, TimeUnit.MILLISECONDS);
-
-            pollingExecutorMap.put(serviceName, scheduledFuture);
-        });
-    }
-
-    /**
-     * UT used only
-     */
-    @Deprecated
-    public void setDnsResolver(DNSResolver dnsResolver) {
-        this.dnsResolver = dnsResolver;
-    }
-
-    private List<ServiceInstance> toServiceInstance(String serviceName, ResolveResult resolveResult) {
-
-        int port;
-
-        if (resolveResult.getPort().size() > 0) {
-            // use first as default
-            port = resolveResult.getPort().get(0);
-        } else {
-            // not support SRV record
-            port = 20880;
-        }
-
-        List<ServiceInstance> instanceList = new LinkedList<>();
-
-        for (String host : resolveResult.getHostnameList()) {
-            DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, host, port, ScopeModelUtil.getApplicationModel(getUrl().getScopeModel()));
-            fillServiceInstance(serviceInstance);
-            instanceList.add(serviceInstance);
-        }
-
-        return instanceList;
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java
deleted file mode 100644
index 57eba84..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-
-public class DNSServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
-    @Override
-    protected ServiceDiscovery createDiscovery(URL registryURL) {
-        return new DNSServiceDiscovery();
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java
deleted file mode 100644
index 0fc8ada..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns.util;
-
-public class DNSClientConst {
-
-    public final static String ADDRESS_PREFIX = "addressPrefix";
-
-    public final static String ADDRESS_SUFFIX = "addressSuffix";
-
-    public final static String MAX_QUERIES_PER_RESOLVE = "maxQueriesPerResolve";
-
-    /**
-     * To decide the frequency of execute DNS poll (in ms)
-     */
-    public final static String DNS_POLLING_CYCLE = "dnsPollingCycle";
-
-    /**
-     * Default value for check frequency: 60000 (ms)
-     */
-    public final static int DEFAULT_DNS_POLLING_CYCLE = 60000;
-
-    /**
-     * To decide how many threads used to execute DNS poll
-     */
-    public final static String DNS_POLLING_POOL_SIZE_KEY = "dnsPollingPoolSize";
-
-    /**
-     * Default value for DNS pool thread: 1
-     */
-    public final static int DEFAULT_DNS_POLLING_POOL_SIZE = 1;
-
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java
deleted file mode 100644
index 7745873..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns.util;
-
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.AddressedEnvelope;
-import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.nio.NioDatagramChannel;
-import io.netty.handler.codec.dns.DefaultDnsQuestion;
-import io.netty.handler.codec.dns.DnsRawRecord;
-import io.netty.handler.codec.dns.DnsRecordType;
-import io.netty.handler.codec.dns.DnsResponse;
-import io.netty.handler.codec.dns.DnsSection;
-import io.netty.resolver.ResolvedAddressTypes;
-import io.netty.resolver.dns.DnsNameResolver;
-import io.netty.resolver.dns.DnsNameResolverBuilder;
-import io.netty.util.concurrent.Future;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static io.netty.resolver.dns.DnsServerAddresses.sequential;
-
-public class DNSResolver {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final DnsNameResolver resolver;
-
-    private static final EventLoopGroup GROUP = new NioEventLoopGroup(1);
-
-    public DNSResolver(String nameserver, int port, int maxQueriesPerResolve) {
-        this.resolver = newResolver(nameserver, port, maxQueriesPerResolve);
-    }
-
-    public ResolveResult resolve(String path) {
-        ResolveResult recordList = new ResolveResult();
-
-        try {
-            Future<List<InetAddress>> hostFuture = resolver.resolveAll(path);
-            Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> srvFuture =
-                    resolver.query(new DefaultDnsQuestion(path, DnsRecordType.SRV));
-
-            try {
-                recordList.getHostnameList()
-                        .addAll(hostFuture
-                                .sync().getNow()
-                                .stream()
-                                .map(InetAddress::getHostAddress)
-                                .collect(Collectors.toList()));
-
-                DnsResponse srvResponse = srvFuture.sync().getNow().content();
-                for (int i = 0; i < srvResponse.count(DnsSection.ANSWER); i++) {
-                    DnsRawRecord record = srvResponse.recordAt(DnsSection.ANSWER, i);
-                    ByteBuf buf = record.content();
-                    // Priority
-                    buf.readUnsignedShort();
-                    // Weight
-                    buf.readUnsignedShort();
-                    // Port
-                    int port = buf.readUnsignedShort();
-                    recordList.getPort().add(port);
-                }
-
-            } catch (InterruptedException e) {
-                logger.warn("Waiting DNS resolve interrupted. " + e.getLocalizedMessage());
-            }
-        } catch (Throwable t) {
-            if (t instanceof UnknownHostException) {
-                if (logger.isInfoEnabled()) {
-                    logger.info(t.getLocalizedMessage());
-                }
-            } else {
-                logger.error(t.getLocalizedMessage());
-            }
-        }
-
-
-        return recordList;
-    }
-
-    public void destroy() {
-        resolver.close();
-    }
-
-    private static DnsNameResolver newResolver(String nameserver, int port, int maxQueriesPerResolve) {
-        return new DnsNameResolverBuilder(GROUP.next())
-                .channelType(NioDatagramChannel.class)
-                .maxQueriesPerResolve(maxQueriesPerResolve)
-                .decodeIdn(true)
-                .optResourceEnabled(false)
-                .ndots(1)
-                .resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED)
-                // ignore cache
-                .ttl(0, 1)
-                .nameServerProvider((hostname) -> sequential(new InetSocketAddress(nameserver, port)).stream())
-                .build();
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java
deleted file mode 100644
index a02b122..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns.util;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
-public class ResolveResult {
-
-    private List<String> hostnameList = new LinkedList<>();
-
-    private List<Integer> port = new LinkedList<>();
-
-    public List<String> getHostnameList() {
-        return hostnameList;
-    }
-
-    public void setHostnameList(List<String> hostnameList) {
-        this.hostnameList = hostnameList;
-    }
-
-    public List<Integer> getPort() {
-        return port;
-    }
-
-    public void setPort(List<Integer> port) {
-        this.port = port;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        ResolveResult that = (ResolveResult) o;
-        return Objects.equals(hostnameList, that.hostnameList) &&
-                Objects.equals(port, that.port);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(hostnameList, port);
-    }
-
-    @Override
-    public String toString() {
-        return "ResolveResult{" +
-                "hostnameList=" + hostnameList +
-                ", port=" + port +
-                '}';
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
deleted file mode 100644
index 69ca007..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
+++ /dev/null
@@ -1 +0,0 @@
-dns=org.apache.dubbo.registry.dns.DNSRegistryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
deleted file mode 100644
index 3adb9f4..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
+++ /dev/null
@@ -1 +0,0 @@
-dns=org.apache.dubbo.registry.dns.DNSServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
deleted file mode 100644
index 900e78c..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
+++ /dev/null
@@ -1 +0,0 @@
-dns=org.apache.dubbo.registry.dns.DNSServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java
deleted file mode 100644
index 37c215a..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns;
-
-import com.alibaba.fastjson.JSONObject;
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.NetUtils;
-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.bootstrap.DubboBootstrap;
-import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.registry.Constants;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
-import org.apache.dubbo.registry.dns.util.DNSClientConst;
-import org.apache.dubbo.registry.dns.util.DNSResolver;
-import org.apache.dubbo.registry.dns.util.ResolveResult;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.ScopeModelUtil;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
-import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-public class DNSServiceDiscoveryTest {
-
-    @BeforeEach
-    public void setup() {
-        DubboBootstrap.reset();
-        ApplicationConfig applicationConfig = new ApplicationConfig("Test");
-        ApplicationModel.defaultModel().getApplicationConfigManager().setApplication(applicationConfig);
-    }
-
-    @AfterEach
-    public void destroy() {
-        DubboBootstrap.reset();
-    }
-
-    @Test
-    public void testProvider() throws Exception {
-        ApplicationModel applicationModel = ApplicationModel.defaultModel();
-        DNSServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
-        dnsServiceDiscovery.setApplicationModel(applicationModel);
-
-        URL registryURL = URL.valueOf("dns://");
-        registryURL.setScopeModel(ApplicationModel.defaultModel());
-        dnsServiceDiscovery.initialize(registryURL);
-
-        assertEquals(registryURL, dnsServiceDiscovery.getUrl());
-
-        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345, ScopeModelUtil.getApplicationModel(dnsServiceDiscovery.getUrl().getScopeModel()));
-        serviceInstance.getMetadata().put("a", "b");
-
-        dnsServiceDiscovery.register(serviceInstance);
-
-        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension(applicationModel);
-        InstanceMetadataChangedListener changeListener = Mockito.mock(InstanceMetadataChangedListener.class);
-
-        String metadataString = metadataService
-                .getAndListenInstanceMetadata("test", changeListener);
-
-        assertEquals(JSONObject.toJSONString(serviceInstance.getMetadata()), metadataString);
-        assertEquals(serviceInstance, dnsServiceDiscovery.getLocalInstance());
-
-        dnsServiceDiscovery.unregister(serviceInstance);
-
-        Mockito.verify(changeListener, Mockito.times(1)).onEvent(Mockito.any());
-
-        metadataService.getInstanceMetadataChangedListenerMap().clear();
-        metadataService.exportInstanceMetadata(null);
-
-        dnsServiceDiscovery.destroy();
-
-    }
-
-    @Test
-    public void testConsumer() throws Exception {
-        ApplicationModel applicationModel = ApplicationModel.defaultModel();
-        DNSServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
-        dnsServiceDiscovery.setApplicationModel(applicationModel);
-
-        URL registryURL = URL.valueOf("dns://")
-                .addParameter(DNSClientConst.DNS_POLLING_CYCLE, 100)
-                .addParameter(Constants.ECHO_POLLING_CYCLE_KEY, 100);
-        registryURL.setScopeModel(ApplicationModel.defaultModel());
-        applicationModel.getModelEnvironment().getAppExternalConfigMap()
-                .put(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(500));
-        dnsServiceDiscovery.initialize(registryURL);
-
-        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension(applicationModel);
-        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345, ScopeModelUtil.getApplicationModel(dnsServiceDiscovery.getUrl().getScopeModel()));
-        serviceInstance.getMetadata().put("a", "b");
-
-        dnsServiceDiscovery.register(serviceInstance);
-
-        int port = NetUtils.getAvailablePort();
-        applicationModel.getCurrentConfig().setMetadataServicePort(port);
-
-        WritableMetadataService spiedMetadataService = Mockito.spy(metadataService);
-
-        ServiceConfig<MetadataService> serviceConfig = exportMockMetadataService(spiedMetadataService, port);
-
-        DNSResolver dnsResolver = Mockito.mock(DNSResolver.class);
-        ResolveResult resolveResult = new ResolveResult();
-        resolveResult.getHostnameList().add("127.0.0.1");
-        Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(resolveResult);
-        dnsServiceDiscovery.setDnsResolver(dnsResolver);
-
-        List<ServiceInstance> serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
-        assertEquals("b", serviceInstances.get(0).getMetadata("a"));
-
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("Test.Service.");
-        ServiceInstancesChangedListener changedListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, null));
-        Mockito.doNothing().when(changedListener).onEvent(Mockito.any());
-
-        serviceInstance.getMetadata().put("a", "c");
-        dnsServiceDiscovery.update(serviceInstance);
-
-        serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
-        assertEquals("c", serviceInstances.get(0).getMetadata("a"));
-
-        ArgumentCaptor<ServiceInstancesChangedEvent> argument = ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
-        dnsServiceDiscovery.addServiceInstancesChangedListener(changedListener);
-        Thread.sleep(1000);
-        Mockito.verify(changedListener, Mockito.timeout(1000)).onEvent(argument.capture());
-        assertEquals("c", argument.getValue().getServiceInstances().get(0).getMetadata("a"));
-
-        Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(new ResolveResult());
-
-        Thread.sleep(1000);
-        assertTrue(dnsServiceDiscovery.getCachedServiceInstances().get("Test.Service.").isEmpty());
-
-        metadataService.exportInstanceMetadata(null);
-        metadataService.getInstanceMetadataChangedListenerMap().clear();
-        serviceConfig.unexport();
-
-        dnsServiceDiscovery.destroy();
-        applicationModel.getModelEnvironment().getAppExternalConfigMap()
-                .remove(METADATA_PROXY_TIMEOUT_KEY);
-    }
-
-    private ServiceConfig<MetadataService> exportMockMetadataService(MetadataService metadataService, int port) {
-        ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
-        serviceConfig.setProtocol(new ProtocolConfig(DUBBO_PROTOCOL, port));
-        serviceConfig.setRegistry(new RegistryConfig("239.255.255.255", "multicast"));
-        serviceConfig.setInterface(MetadataService.class);
-        serviceConfig.setRef(metadataService);
-        serviceConfig.setGroup("Test.Service.");
-        serviceConfig.setVersion(MetadataService.VERSION);
-        MethodConfig methodConfig = new MethodConfig();
-        methodConfig.setName("getAndListenInstanceMetadata");
-
-        ArgumentConfig argumentConfig = new ArgumentConfig();
-        argumentConfig.setIndex(1);
-        argumentConfig.setCallback(true);
-
-        methodConfig.setArguments(Collections.singletonList(argumentConfig));
-        serviceConfig.setMethods(Collections.singletonList(methodConfig));
-
-        serviceConfig.export();
-
-        return serviceConfig;
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java
deleted file mode 100644
index 17088ac..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.dns.util;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-public class DNSResolverTest {
-
-    @Test
-    public void testResolve() {
-        DNSResolver dnsResolver = new DNSResolver("8.8.8.8", 53, 1);
-        ResolveResult resolve = dnsResolver.resolve("aliyun.com");
-        Assertions.assertTrue(resolve.getHostnameList().size() > 0);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-dns/src/test/resources/dubbo.properties b/dubbo-registry/dubbo-registry-dns/src/test/resources/dubbo.properties
deleted file mode 100644
index 1aade88..0000000
--- a/dubbo-registry/dubbo-registry-dns/src/test/resources/dubbo.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-dubbo.application.enable-file-cache=false
-dubbo.service.shutdown.wait=200
diff --git a/dubbo-registry/dubbo-registry-kubernetes/pom.xml b/dubbo-registry/dubbo-registry-kubernetes/pom.xml
deleted file mode 100644
index 799e45e..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/pom.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.dubbo</groupId>
-        <artifactId>dubbo-registry</artifactId>
-        <version>${revision}</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>dubbo-registry-kubernetes</artifactId>
-    <packaging>jar</packaging>
-    <name>${project.artifactId}</name>
-    <description>The Kubernetes registry module of Dubbo project</description>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-registry-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.dubbo</groupId>
-            <artifactId>dubbo-common</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>io.fabric8</groupId>
-            <artifactId>kubernetes-client</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>io.fabric8</groupId>
-            <artifactId>kubernetes-server-mock</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>3.4.6</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-junit-jupiter</artifactId>
-            <version>3.4.6</version>
-            <scope>test</scope>
-        </dependency>
-
-    </dependencies>
-
-</project>
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java
deleted file mode 100644
index 1a0c1fa..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.kubernetes;
-
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
-import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
-
-import com.google.gson.Gson;
-import io.fabric8.kubernetes.api.model.ListOptionsBuilder;
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.Watch;
-import io.fabric8.kubernetes.client.Watcher;
-import io.fabric8.kubernetes.client.WatcherException;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.SafeConstructor;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class KubernetesMeshEnvListener implements MeshEnvListener {
-    public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListener.class);
-    private volatile static boolean usingApiServer = false;
-    private volatile static KubernetesClient kubernetesClient;
-    private volatile static String namespace;
-
-    private final Map<String, MeshAppRuleListener> appRuleListenerMap = new ConcurrentHashMap<>();
-
-    private final Map<String, Watch> vsAppWatch = new ConcurrentHashMap<>();
-    private final Map<String, Watch> drAppWatch = new ConcurrentHashMap<>();
-
-    private final Map<String, String> vsAppCache = new ConcurrentHashMap<>();
-    private final Map<String, String> drAppCache = new ConcurrentHashMap<>();
-
-    public static void injectKubernetesEnv(KubernetesClient client, String configuredNamespace) {
-        usingApiServer = true;
-        kubernetesClient = client;
-        namespace = configuredNamespace;
-    }
-
-    @Override
-    public boolean isEnable() {
-        return usingApiServer;
-    }
-
-    @Override
-    public void onSubscribe(String appName, MeshAppRuleListener listener) {
-        appRuleListenerMap.put(appName, listener);
-        logger.info("Subscribe Mesh Rule in Kubernetes. AppName: " + appName);
-
-        // subscribe VisualService
-        subscribeVs(appName);
-
-        // subscribe DestinationRule
-        subscribeDr(appName);
-
-        // notify for start
-        notifyOnce(appName);
-    }
-
-    private void subscribeVs(String appName) {
-        if (vsAppWatch.containsKey(appName)) {
-            return;
-        }
-
-        try {
-            Watch watch = kubernetesClient
-                .customResource(
-                    MeshConstant.getVsDefinition())
-                .watch(namespace, appName, null, new ListOptionsBuilder().build(), new Watcher<String>() {
-                    @Override
-                    public void eventReceived(Action action, String resource) {
-                        logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
-
-                        if (action == Action.ADDED || action == Action.MODIFIED) {
-                            Map drRuleMap = new Gson().fromJson(resource, Map.class);
-                            String vsRule = new Yaml(new SafeConstructor()).dump(drRuleMap);
-                            vsAppCache.put(appName, vsRule);
-                            if (drAppCache.containsKey(appName)) {
-                                notifyListener(vsRule, appName, drAppCache.get(appName));
-                            }
-                        } else {
-                            appRuleListenerMap.get(appName).receiveConfigInfo("");
-                        }
-                    }
-
-                    @Override
-                    public void onClose(WatcherException cause) {
-                        // ignore
-                    }
-                });
-            vsAppWatch.put(appName, watch);
-            try {
-                Map<String, Object> vsRule = kubernetesClient
-                    .customResource(
-                        MeshConstant.getVsDefinition())
-                    .get(namespace, appName);
-                vsAppCache.put(appName, new Yaml(new SafeConstructor()).dump(vsRule));
-            } catch (Throwable ignore) {
-
-            }
-        } catch (IOException e) {
-            logger.error("Error occurred when listen kubernetes crd.", e);
-        }
-    }
-
-    private void notifyListener(String vsRule, String appName, String drRule) {
-        String rule = vsRule + "\n---\n" + drRule;
-        logger.info("Notify App Rule Listener. AppName: " + appName + " Rule:" + rule);
-
-        appRuleListenerMap.get(appName).receiveConfigInfo(rule);
-    }
-
-    private void subscribeDr(String appName) {
-        if (drAppWatch.containsKey(appName)) {
-            return;
-        }
-
-        try {
-            Watch watch = kubernetesClient
-                .customResource(
-                    MeshConstant.getDrDefinition())
-                .watch(namespace, appName, null, new ListOptionsBuilder().build(), new Watcher<String>() {
-                    @Override
-                    public void eventReceived(Action action, String resource) {
-                        logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
-
-                        if (action == Action.ADDED || action == Action.MODIFIED) {
-                            Map drRuleMap = new Gson().fromJson(resource, Map.class);
-                            String drRule = new Yaml(new SafeConstructor()).dump(drRuleMap);
-
-                            drAppCache.put(appName, drRule);
-                            if (vsAppCache.containsKey(appName)) {
-                                notifyListener(vsAppCache.get(appName), appName, drRule);
-                            }
-                        } else {
-                            appRuleListenerMap.get(appName).receiveConfigInfo("");
-                        }
-                    }
-
-                    @Override
-                    public void onClose(WatcherException cause) {
-                        // ignore
-                    }
-                });
-            drAppWatch.put(appName, watch);
-            try {
-                Map<String, Object> drRule = kubernetesClient
-                    .customResource(
-                        MeshConstant.getDrDefinition())
-                    .get(namespace, appName);
-                drAppCache.put(appName, new Yaml(new SafeConstructor()).dump(drRule));
-            } catch (Throwable ignore) {
-
-            }
-        } catch (IOException e) {
-            logger.error("Error occurred when listen kubernetes crd.", e);
-        }
-    }
-
-    private void notifyOnce(String appName) {
-        if (vsAppCache.containsKey(appName) && drAppCache.containsKey(appName)) {
-            notifyListener(vsAppCache.get(appName), appName, drAppCache.get(appName));
-        }
-    }
-
-    @Override
-    public void onUnSubscribe(String appName) {
-        appRuleListenerMap.remove(appName);
-
-        if (vsAppWatch.containsKey(appName)) {
-            vsAppWatch.remove(appName).close();
-        }
-        vsAppCache.remove(appName);
-
-        if (drAppWatch.containsKey(appName)) {
-            drAppWatch.remove(appName).close();
-        }
-        drAppCache.remove(appName);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java
deleted file mode 100644
index 67b1b4c..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.kubernetes;
-
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
-import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class KubernetesMeshEnvListenerFactory implements MeshEnvListenerFactory {
-    public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListenerFactory.class);
-    private final AtomicBoolean initialized = new AtomicBoolean(false);
-    private MeshEnvListener listener = null;
-
-    @Override
-    public MeshEnvListener getListener() {
-        try {
-            if (initialized.compareAndSet(false, true)) {
-                listener = new KubernetesMeshEnvListener();
-            }
-        } catch (Throwable t) {
-            logger.info("Current Env not support Kubernetes.");
-        }
-        return listener;
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java
deleted file mode 100644
index b221c89..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.kubernetes;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.NotifyListener;
-import org.apache.dubbo.registry.support.FailbackRegistry;
-
-/**
- * Empty implements for Kubernetes <br/>
- * Kubernetes only support `Service Discovery` mode register <br/>
- * Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
- * {@link KubernetesServiceDiscovery} is the real implementation of Kubernetes
- */
-public class KubernetesRegistry extends FailbackRegistry {
-    public KubernetesRegistry(URL url) {
-        super(url);
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return true;
-    }
-
-    @Override
-    public void doRegister(URL url) {
-
-    }
-
-    @Override
-    public void doUnregister(URL url) {
-
-    }
-
-    @Override
-    public void doSubscribe(URL url, NotifyListener listener) {
-
-    }
-
-    @Override
-    public void doUnsubscribe(URL url, NotifyListener listener) {
-
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
deleted file mode 100644
index ab9af54..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.kubernetes;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.Registry;
-import org.apache.dubbo.registry.support.AbstractRegistryFactory;
-
-public class KubernetesRegistryFactory  extends AbstractRegistryFactory {
-
-    @Override
-    protected String createRegistryCacheKey(URL url) {
-        return url.toFullString();
-    }
-
-    @Override
-    protected Registry createRegistry(URL url) {
-        return new KubernetesRegistry(url);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
deleted file mode 100644
index f9d58f6..0000000
--- a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.kubernetes;
-
-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.StringUtils;
-import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
-import org.apache.dubbo.registry.client.DefaultServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
-import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
-import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
-import org.apache.dubbo.rpc.model.ScopeModelUtil;
-
-import com.alibaba.fastjson.JSONObject;
-import io.fabric8.kubernetes.api.model.EndpointAddress;
-import io.fabric8.kubernetes.api.model.EndpointPort;
-import io.fabric8.kubernetes.api.model.EndpointSubset;
-import io.fabric8.kubernetes.api.model.Endpoints;
-import io.fabric8.kubernetes.api.model.Pod;
-import io.fabric8.kubernetes.api.model.PodBuilder;
-import io.fabric8.kubernetes.api.model.Service;
-import io.fabric8.kubernetes.client.Config;
-import io.fabric8.kubernetes.client.DefaultKubernetesClient;
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.Watch;
-import io.fabric8.kubernetes.client.Watcher;
-import io.fabric8.kubernetes.client.WatcherException;
-
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
-
-public class KubernetesServiceDiscovery extends AbstractServiceDiscovery {
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private KubernetesClient kubernetesClient;
-
-    private String currentHostname;
-
-    private URL registryURL;
-
-    private String namespace;
-
-    private boolean enableRegister;
-
-    public final static String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
-
-    private final static ConcurrentHashMap<String, Watch> SERVICE_WATCHER = new ConcurrentHashMap<>(64);
-
-    private final static ConcurrentHashMap<String, Watch> PODS_WATCHER = new ConcurrentHashMap<>(64);
-
-    private final static ConcurrentHashMap<String, Watch> ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64);
-
-    private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
-
-    @Override
-    public void doInitialize(URL registryURL) throws Exception {
-        Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
-        this.kubernetesClient = new DefaultKubernetesClient(config);
-        this.currentHostname = System.getenv("HOSTNAME");
-        this.registryURL = registryURL;
-        this.namespace = config.getNamespace();
-        this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
-
-        boolean availableAccess;
-        try {
-            availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
-        } catch (Throwable e) {
-            availableAccess = false;
-        }
-        if (!availableAccess) {
-            String message = "Unable to access api server. " +
-                    "Please check your url config." +
-                    " Master URL: " + config.getMasterUrl() +
-                    " Hostname: " + currentHostname;
-            logger.error(message);
-        } else {
-            KubernetesMeshEnvListener.injectKubernetesEnv(kubernetesClient, namespace);
-        }
-    }
-
-    @Override
-    public void doDestroy() throws Exception {
-        SERVICE_WATCHER.forEach((k, v) -> v.close());
-        SERVICE_WATCHER.clear();
-
-        PODS_WATCHER.forEach((k, v) -> v.close());
-        PODS_WATCHER.clear();
-
-        ENDPOINTS_WATCHER.forEach((k, v) -> v.close());
-        ENDPOINTS_WATCHER.clear();
-
-        kubernetesClient.close();
-    }
-
-    @Override
-    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
-        if (enableRegister) {
-            kubernetesClient
-                    .pods()
-                    .inNamespace(namespace)
-                    .withName(currentHostname)
-                    .edit(pod ->
-                            new PodBuilder(pod)
-                                    .editOrNewMetadata()
-                                    .addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
-                                    .endMetadata()
-                                    .build());
-            if (logger.isInfoEnabled()) {
-                logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
-                        "Current pod name: " + currentHostname);
-            }
-        }
-    }
-
-    @Override
-    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
-        register(serviceInstance);
-    }
-
-    @Override
-    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
-        if (enableRegister) {
-            kubernetesClient
-                    .pods()
-                    .inNamespace(namespace)
-                    .withName(currentHostname)
-                    .edit(pod ->
-                            new PodBuilder(pod)
-                                    .editOrNewMetadata()
-                                    .removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
-                                    .endMetadata()
-                                    .build());
-            if (logger.isInfoEnabled()) {
-                logger.info("Remove Current Service Instance from Kubernetes pod. Current pod name: " + currentHostname);
-            }
-        }
-    }
-
-    @Override
-    public Set<String> getServices() {
-        return kubernetesClient
-                .services()
-                .inNamespace(namespace)
-                .list()
-                .getItems()
-                .stream()
-                .map(service -> service.getMetadata().getName())
-                .collect(Collectors.toSet());
-    }
-
-    @Override
-    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
-        Endpoints endpoints =
-                kubernetesClient
-                        .endpoints()
-                        .inNamespace(namespace)
-                        .withName(serviceName)
-                        .get();
-
-        return toServiceInstance(endpoints, serviceName);
-    }
-
-    @Override
-    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
-        listener.getServiceNames().forEach(serviceName -> {
-            SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
-
-            // Watch Service Endpoint Modification
-            watchEndpoints(listener, serviceName);
-
-            // Watch Pods Modification, happens when ServiceInstance updated
-            watchPods(listener, serviceName);
-
-            // Watch Service Modification, happens when Service Selector updated, used to update pods watcher
-            watchService(listener, serviceName);
-        });
-    }
-
-    private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
-        Watch watch = kubernetesClient
-                .endpoints()
-                .inNamespace(namespace)
-                .withName(serviceName)
-                .watch(new Watcher<Endpoints>() {
-                    @Override
-                    public void eventReceived(Action action, Endpoints resource) {
-                        if (logger.isDebugEnabled()) {
-                            logger.debug("Received Endpoint Event. Event type: " + action.name() +
-                                    ". Current pod name: " + currentHostname);
-                        }
-
-                        notifyServiceChanged(serviceName, listener);
-                    }
-
-                    @Override
-                    public void onClose(WatcherException cause) {
-                        // ignore
-                    }
-                });
... 3495 lines suppressed ...