Merge branch '2.7.9-release'

# Conflicts:
#	dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
#	dubbo-dependencies-bom/pom.xml
#	dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
#	dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InterfaceCompatibleRegistryProtocol.java
#	dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/codec/ExchangeCodec.java
#	dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/CodecSupport.java
#	dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodec.java
#	pom.xml
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..bb5d205
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# Git will understand that all files specified are not text,

+# and it should not try to change them.

+# This will prevent file formatting (such as `crlf` endings to `lf` endings) while commit.

+* -text
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 15eda40..83538c7 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -12,12 +12,12 @@
     }
 
     environment {
-        JAVA_HOME = "${tool 'JDK 1.8 (latest)'}"
+        JAVA_HOME = "${tool 'jdk_1.8_latest'}"
     }
 
     tools {
-        maven 'Maven 3 (latest)'
-        jdk 'JDK 1.8 (latest)'
+        maven 'maven_3_latest'
+        jdk 'jdk_1.8_latest'
     }
 
     triggers {
diff --git a/README.md b/README.md
index 466ea7f..af561f9 100644
--- a/README.md
+++ b/README.md
@@ -33,16 +33,16 @@
 
 ```bash
 # git clone https://github.com/apache/dubbo-samples.git
-# cd dubbo-samples/java/dubbo-samples-api
+# cd dubbo-samples/dubbo-samples-api
 ```
 
-There's a [README](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions:
+There's a [README](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions: 
 
 ### Maven dependency
 
 ```xml
 <properties>
-    <dubbo.version>2.7.8</dubbo.version>
+    <dubbo.version>2.7.9</dubbo.version>
 </properties>
 
 <dependencies>
diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml
index 050ae24..14a98e8 100644
--- a/dubbo-all/pom.xml
+++ b/dubbo-all/pom.xml
@@ -544,6 +544,13 @@
             <scope>compile</scope>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-failover</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
 
         <!-- Transitive dependencies -->
         <dependency>
@@ -684,6 +691,7 @@
                                     <include>org.apache.dubbo:dubbo-metadata-report-consul</include>
                                     <include>org.apache.dubbo:dubbo-metadata-report-etcd</include>
                                     <include>org.apache.dubbo:dubbo-metadata-report-nacos</include>
+                                    <include>org.apache.dubbo:dubbo-metadata-report-failover</include>
                                     <include>org.apache.dubbo:dubbo-serialization-native-hession</include>
                                 </includes>
                             </artifactSet>
@@ -859,6 +867,12 @@
                                         META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
                                     </resource>
                                 </transformer>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                                    <resource>
+                                        META-INF/dubbo/internal/org.apache.dubbo.metadata.store.failover.FailoverCondition
+                                    </resource>
+                                </transformer>
                                 <!-- @since 2.7.5 -->
                                 <transformer
                                         implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
diff --git a/dubbo-bom/pom.xml b/dubbo-bom/pom.xml
index a5b8b87..92aa370 100644
--- a/dubbo-bom/pom.xml
+++ b/dubbo-bom/pom.xml
@@ -250,6 +250,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-registry-multiple</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
                 <artifactId>dubbo-registry-zookeeper</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -399,6 +404,11 @@
                 <artifactId>dubbo-metadata-report-nacos</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-metadata-report-failover</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- config-center -->
             <dependency>
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Directory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Directory.java
index 5d48264..208b0c2 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Directory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Directory.java
@@ -53,4 +53,6 @@
 

     boolean isDestroyed();

 

+    void discordAddresses();

+

 }
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
index 1b1170a..95a540c 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
@@ -40,7 +40,6 @@
 
 /**
  * Abstract implementation of Directory: Invoker list returned from this Directory's list method have been filtered by Routers
- *
  */
 public abstract class AbstractDirectory<T> implements Directory<T> {
 
@@ -59,10 +58,14 @@
     protected RouterChain<T> routerChain;
 
     public AbstractDirectory(URL url) {
-        this(url, null);
+        this(url, null, false);
     }
 
-    public AbstractDirectory(URL url, RouterChain<T> routerChain) {
+    public AbstractDirectory(URL url, boolean isUrlFromRegistry) {
+        this(url, null, isUrlFromRegistry);
+    }
+
+    public AbstractDirectory(URL url, RouterChain<T> routerChain, boolean isUrlFromRegistry) {
         if (url == null) {
             throw new IllegalArgumentException("url == null");
         }
@@ -72,8 +75,13 @@
         this.consumedProtocol = this.queryMap.get(PROTOCOL_KEY) == null ? DUBBO : this.queryMap.get(PROTOCOL_KEY);
         this.url = url.removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
 
-        this.consumerUrl = this.url.setProtocol(consumedProtocol).setPath(path == null ? queryMap.get(INTERFACE_KEY) : path).addParameters(queryMap)
-                .removeParameter(MONITOR_KEY);
+        URL consumerUrlFrom = this.url.setProtocol(consumedProtocol)
+                .setPath(path == null ? queryMap.get(INTERFACE_KEY) : path);
+        if (isUrlFromRegistry) {
+            // reserve parameters if url is already a consumer url
+            consumerUrlFrom = consumerUrlFrom.clearParameters();
+        }
+        this.consumerUrl = consumerUrlFrom.addParameters(queryMap).removeParameter(MONITOR_KEY);
 
         setRouterChain(routerChain);
     }
@@ -123,6 +131,11 @@
         destroyed = true;
     }
 
+    @Override
+    public void discordAddresses() {
+        // do nothing by default
+    }
+
     protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
 
 }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
index 0595c63..db1b202 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
@@ -49,7 +49,7 @@
     }

 

     public StaticDirectory(URL url, List<Invoker<T>> invokers, RouterChain<T> routerChain) {

-        super(url == null && CollectionUtils.isNotEmpty(invokers) ? invokers.get(0).getUrl() : url, routerChain);

+        super(url == null && CollectionUtils.isNotEmpty(invokers) ? invokers.get(0).getUrl() : url, routerChain, false);

         if (CollectionUtils.isEmpty(invokers)) {

             throw new IllegalArgumentException("invokers == null");

         }

diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java
index 77429b5..91a0f8e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java
@@ -34,7 +34,6 @@
 import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule;
 import org.apache.dubbo.rpc.cluster.router.tag.model.TagRuleParser;
 
-import java.net.UnknownHostException;
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -227,8 +226,6 @@
                 if ((ANYHOST_VALUE + ":" + port).equals(address)) {
                     return true;
                 }
-            } catch (UnknownHostException e) {
-                logger.error("The format of ip address is invalid in tag route. Address :" + address, e);
             } catch (Exception e) {
                 logger.error("The format of ip address is invalid in tag route. Address :" + address, e);
             }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/BroadcastClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/BroadcastClusterInvoker.java
index ab0623e..fcfb132 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/BroadcastClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/BroadcastClusterInvoker.java
@@ -16,6 +16,7 @@
  */

 package org.apache.dubbo.rpc.cluster.support;

 

+import org.apache.dubbo.common.URL;

 import org.apache.dubbo.common.logger.Logger;

 import org.apache.dubbo.common.logger.LoggerFactory;

 import org.apache.dubbo.rpc.Invocation;

@@ -30,11 +31,13 @@
 

 /**

  * BroadcastClusterInvoker

- *

  */

 public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {

 

     private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);

+    private static final String BROADCAST_FAIL_PERCENT_KEY = "broadcast.fail.percent";

+    private static final int MAX_BROADCAST_FAIL_PERCENT = 100;

+    private static final int MIN_BROADCAST_FAIL_PERCENT = 0;

 

     public BroadcastClusterInvoker(Directory<T> directory) {

         super(directory);

@@ -47,21 +50,67 @@
         RpcContext.getContext().setInvokers((List) invokers);

         RpcException exception = null;

         Result result = null;

+        URL url = getUrl();

+        // The value range of broadcast.fail.threshold must be 0~100.

+        // 100 means that an exception will be thrown last, and 0 means that as long as an exception occurs, it will be thrown.

+        // see https://github.com/apache/dubbo/pull/7174

+        int broadcastFailPercent = url.getParameter(BROADCAST_FAIL_PERCENT_KEY, MAX_BROADCAST_FAIL_PERCENT);

+

+        if (broadcastFailPercent < MIN_BROADCAST_FAIL_PERCENT || broadcastFailPercent > MAX_BROADCAST_FAIL_PERCENT) {

+            logger.info(String.format("The value corresponding to the broadcast.fail.percent parameter must be between 0 and 100. " +

+                    "The current setting is %s, which is reset to 100.", broadcastFailPercent));

+            broadcastFailPercent = MAX_BROADCAST_FAIL_PERCENT;

+        }

+

+        int failThresholdIndex = invokers.size() * broadcastFailPercent / MAX_BROADCAST_FAIL_PERCENT;

+        int failIndex = 0;

         for (Invoker<T> invoker : invokers) {

             try {

                 result = invoker.invoke(invocation);

-            } catch (RpcException e) {

-                exception = e;

-                logger.warn(e.getMessage(), e);

+                if (null != result && result.hasException()) {

+                    Throwable resultException = result.getException();

+                    if (null != resultException) {

+                        exception = getRpcException(result.getException());

+                        logger.warn(exception.getMessage(), exception);

+                        if (failIndex == failThresholdIndex) {

+                            break;

+                        } else {

+                            failIndex++;

+                        }

+                    }

+                }

             } catch (Throwable e) {

-                exception = new RpcException(e.getMessage(), e);

-                logger.warn(e.getMessage(), e);

+                exception = getRpcException(e);

+                logger.warn(exception.getMessage(), exception);

+                if (failIndex == failThresholdIndex) {

+                    break;

+                } else {

+                    failIndex++;

+                }

             }

         }

+

         if (exception != null) {

+            if (failIndex == failThresholdIndex) {

+                logger.debug(

+                        String.format("The number of BroadcastCluster call failures has reached the threshold %s", failThresholdIndex));

+            } else {

+                logger.debug(String.format("The number of BroadcastCluster call failures has not reached the threshold %s, fail size is %s",

+                        failIndex));

+            }

             throw exception;

         }

+

         return result;

     }

 

+    private RpcException getRpcException(Throwable throwable) {

+        RpcException rpcException = null;

+        if (throwable instanceof RpcException) {

+            rpcException = (RpcException) throwable;

+        } else {

+            rpcException = new RpcException(throwable.getMessage(), throwable);

+        }

+        return rpcException;

+    }

 }

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterComparator.java
similarity index 70%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
copy to dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterComparator.java
index 2042277..72dfdcf 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterComparator.java
@@ -14,7 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.rpc.cluster.support.migration;
 
-public class ConfigurationURL extends URL {
-}
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.rpc.Invoker;
+
+import java.util.List;
+
+@SPI
+public interface MigrationClusterComparator {
+
+    <T> boolean shouldMigrate(List<Invoker<T>>  interfaceInvokers, List<Invoker<T>>  serviceInvokers);
+}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterInvoker.java
new file mode 100644
index 0000000..e15fa11
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationClusterInvoker.java
@@ -0,0 +1,54 @@
+/*
+ * 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.rpc.cluster.support.migration;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public interface MigrationClusterInvoker<T> extends ClusterInvoker<T> {
+
+    boolean isServiceInvoker();
+
+    MigrationRule getMigrationRule();
+
+    void setMigrationRule(MigrationRule rule);
+
+    void destroyServiceDiscoveryInvoker(ClusterInvoker<?> invoker);
+
+    void discardServiceDiscoveryInvokerAddress(ClusterInvoker<?> invoker);
+
+    void discardInterfaceInvokerAddress(ClusterInvoker<T> invoker);
+
+    void refreshServiceDiscoveryInvoker();
+
+    void refreshInterfaceInvoker();
+
+    void destroyInterfaceInvoker(ClusterInvoker<T> invoker);
+
+    boolean isMigrationMultiRegsitry();
+
+    void migrateToServiceDiscoveryInvoker(boolean forceMigrate);
+
+    void reRefer(URL newSubscribeUrl);
+
+    void fallbackToInterfaceInvoker();
+
+    AtomicBoolean invokersChanged();
+
+}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRule.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRule.java
new file mode 100644
index 0000000..7b6cb64
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRule.java
@@ -0,0 +1,90 @@
+/*
+ * 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.rpc.cluster.support.migration;
+
+import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+
+import java.util.Optional;
+
+public class MigrationRule {
+    private static final String DUBBO_SERVICEDISCOVERY_MIGRATION_KEY = "dubbo.application.service-discovery.migration";
+    public static final String DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP = "MIGRATION";
+    public static final String RULE_KEY = ApplicationModel.getName() + ".migration";
+
+    private static DynamicConfiguration configuration = null;
+
+    static {
+        Optional<DynamicConfiguration> optional = ApplicationModel.getEnvironment().getDynamicConfiguration();
+        if (optional.isPresent()) {
+            configuration = optional.get();
+        }
+    }
+
+    private String key;
+    private MigrationStep step = MigrationStep.FORCE_INTERFACE;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public MigrationStep getStep() {
+        return step;
+    }
+
+    public void setStep(MigrationStep step) {
+        this.step = step;
+    }
+
+    public static MigrationRule parse(String rawRule) {
+        if (null == configuration) {
+            return getMigrationRule(null);
+        }
+
+        if (StringUtils.isBlank(rawRule) || "INIT".equals(rawRule)) {
+            String step = (String)configuration.getInternalProperty(DUBBO_SERVICEDISCOVERY_MIGRATION_KEY);
+            return getMigrationRule(step);
+
+        }
+
+        Constructor constructor = new Constructor(MigrationRule.class);
+        Yaml yaml = new Yaml(constructor);
+        return yaml.load(rawRule);
+    }
+
+    public static MigrationRule queryRule() {
+        if (null == configuration) {
+            return getMigrationRule(null);
+        }
+
+        String rawRule = configuration.getConfig(MigrationRule.RULE_KEY, DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP);
+        return parse(rawRule);
+    }
+
+    private  static MigrationRule getMigrationRule(String step) {
+        MigrationRule rule = new MigrationRule();
+        rule.setStep(Enum.valueOf(MigrationStep.class, StringUtils.isBlank(step) ? MigrationStep.APPLICATION_FIRST.name() : step));
+        return rule;
+    }
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationStep.java
similarity index 83%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
copy to dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationStep.java
index 2042277..653e6c5 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationStep.java
@@ -14,7 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.rpc.cluster.support.migration;
 
-public class ConfigurationURL extends URL {
-}
+public enum MigrationStep {
+    FORCE_INTERFACE,
+    APPLICATION_FIRST,
+    FORCE_APPLICATION
+}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
index 97b7a0b..48215e9 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.rpc.cluster.support.registry;
 
+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;
@@ -27,12 +28,20 @@
 import org.apache.dubbo.rpc.cluster.Directory;
 import org.apache.dubbo.rpc.cluster.LoadBalance;
 import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationClusterInvoker;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationClusterComparator;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationRule;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationStep;
 import org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.constants.CommonConstants.PREFERRED_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.LOADBALANCE_AMONG_REGISTRIES;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_ZONE;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_ZONE_FORCE;
 import static org.apache.dubbo.common.constants.RegistryConstants.ZONE_KEY;
@@ -50,6 +59,8 @@
 
     private static final Logger logger = LoggerFactory.getLogger(ZoneAwareClusterInvoker.class);
 
+    private final LoadBalance loadBalanceAmongRegistries = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(LOADBALANCE_AMONG_REGISTRIES);
+
     public ZoneAwareClusterInvoker(Directory<T> directory) {
         super(directory);
     }
@@ -61,7 +72,7 @@
         for (Invoker<T> invoker : invokers) {
             ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
             if (clusterInvoker.isAvailable() && clusterInvoker.getRegistryUrl()
-                    .getParameter(PREFERRED_KEY, false)) {
+                    .getParameter(REGISTRY_KEY + "." + PREFERRED_KEY, false)) {
                 return clusterInvoker.invoke(invocation);
             }
         }
@@ -71,7 +82,7 @@
         if (StringUtils.isNotEmpty(zone)) {
             for (Invoker<T> invoker : invokers) {
                 ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
-                if (clusterInvoker.isAvailable() && zone.equals(clusterInvoker.getRegistryUrl().getParameter(ZONE_KEY))) {
+                if (clusterInvoker.isAvailable() && zone.equals(clusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY + "." + ZONE_KEY))) {
                     return clusterInvoker.invoke(invocation);
                 }
             }
@@ -85,7 +96,7 @@
 
 
         // load balance among all registries, with registry weight count in.
-        Invoker<T> balancedInvoker = select(loadbalance, invocation, invokers, null);
+        Invoker<T> balancedInvoker = select(loadBalanceAmongRegistries, invocation, invokers, null);
         if (balancedInvoker.isAvailable()) {
             return balancedInvoker.invoke(invocation);
         }
@@ -102,4 +113,155 @@
         return invokers.get(0).invoke(invocation);
     }
 
+    @Override
+    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
+        List<Invoker<T>> invokers = super.list(invocation);
+
+        if (null == invokers || invokers.size() < 2) {
+            return invokers;
+        }
+
+        // 开关
+        //
+
+        //List<Invoker<T>>  interfaceInvokers = invokers.stream().filter( s -> !((MigrationCluserInvoker)s).isServiceInvoker()).collect(Collectors.toList());
+        //List<Invoker<T>>  serviceInvokers = invokers.stream().filter( s -> ((MigrationCluserInvoker)s).isServiceInvoker()).collect(Collectors.toList());
+
+        List<Invoker<T>>  interfaceInvokers = new ArrayList<>();
+        List<Invoker<T>>  serviceInvokers = new ArrayList<>();
+        boolean addreddChanged = false;
+        for (Invoker<T> invoker : invokers) {
+            MigrationClusterInvoker migrationClusterInvoker = (MigrationClusterInvoker) invoker;
+            if (migrationClusterInvoker.isServiceInvoker()) {
+                serviceInvokers.add(invoker);
+            } else {
+                interfaceInvokers.add(invoker);
+            }
+
+            if (migrationClusterInvoker.invokersChanged().compareAndSet(true, false)) {
+                addreddChanged = true;
+            }
+        }
+
+        if (serviceInvokers.isEmpty() || interfaceInvokers.isEmpty()) {
+            return invokers;
+        }
+
+        MigrationRule rule = null;
+
+        for (Invoker<T> invoker : serviceInvokers) {
+            MigrationClusterInvoker migrationClusterInvoker = (MigrationClusterInvoker) invoker;
+            if (null == rule) {
+                rule = migrationClusterInvoker.getMigrationRule();
+            } else {
+                // 不一致
+                if (!rule.equals(migrationClusterInvoker.getMigrationRule())) {
+                    rule = MigrationRule.queryRule();
+                    break;
+                }
+            }
+        }
+
+        MigrationStep step = rule.getStep();
+
+        switch (step) {
+            case FORCE_INTERFACE:
+                clusterRefresh(addreddChanged, interfaceInvokers);
+                clusterDestory(addreddChanged, serviceInvokers, true);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("step is FORCE_INTERFACE");
+                }
+                return interfaceInvokers;
+
+            case APPLICATION_FIRST:
+                clusterRefresh(addreddChanged, serviceInvokers);
+                clusterRefresh(addreddChanged, interfaceInvokers);
+
+                if (serviceInvokers.size() > 0) {
+                    if (shouldMigrate(addreddChanged, serviceInvokers, interfaceInvokers)) {
+                        //clusterDestory(addreddChanged, interfaceInvokers, false);
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("step is APPLICATION_FIRST shouldMigrate true get serviceInvokers");
+                        }
+                        return serviceInvokers;
+
+                    } else {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("step is APPLICATION_FIRST shouldMigrate false get interfaceInvokers");
+                        }
+                        return interfaceInvokers;
+                    }
+                } else {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("step is APPLICATION_FIRST serviceInvokers is empty get interfaceInvokers");
+                    }
+                    return interfaceInvokers;
+                }
+
+
+            case FORCE_APPLICATION:
+                clusterRefresh(addreddChanged, serviceInvokers);
+                clusterDestory(addreddChanged, interfaceInvokers, true);
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("step is FORCE_APPLICATION");
+                }
+
+                return serviceInvokers;
+        }
+
+        throw new UnsupportedOperationException(rule.getStep().name());
+    }
+
+
+    private boolean shouldMigrate(boolean addressChanged, List<Invoker<T>>  serviceInvokers, List<Invoker<T>>  interfaceInvokers) {
+        Set<MigrationClusterComparator> detectors = ExtensionLoader.getExtensionLoader(MigrationClusterComparator.class).getSupportedExtensionInstances();
+        if (null != detectors && detectors.size() > 0) {
+            if (detectors.stream().allMatch(s -> s.shouldMigrate(interfaceInvokers, serviceInvokers))) {
+                return  true;
+            } else {
+                return false;
+            }
+        } else {
+            List<Invoker<T>>  availableServiceInvokers = serviceInvokers.stream().filter( s -> ((MigrationClusterInvoker)s).isAvailable()).collect(Collectors.toList());
+            if (availableServiceInvokers.isEmpty()) {
+                return  false;
+            } else {
+                return  true;
+            }
+        }
+    }
+
+    private void clusterDestory(boolean addressChanged, List<Invoker<T>> invokers, boolean destroySub) {
+        if (addressChanged) {
+            invokers.forEach(s -> {
+                MigrationClusterInvoker invoker = (MigrationClusterInvoker)s;
+                if (invoker.isServiceInvoker()) {
+                    invoker.discardServiceDiscoveryInvokerAddress(invoker);
+                    if (destroySub) {
+                        invoker.destroyServiceDiscoveryInvoker(invoker);
+                    }
+                } else {
+                    invoker.discardInterfaceInvokerAddress(invoker);
+                    if (destroySub) {
+                        invoker.destroyInterfaceInvoker(invoker);
+                    }
+                }
+            });
+        }
+    }
+
+    private void clusterRefresh(boolean addressChanged, List<Invoker<T>> invokers) {
+        if (addressChanged) {
+            invokers.forEach( s -> {
+                MigrationClusterInvoker invoker = (MigrationClusterInvoker)s;
+                if (invoker.isServiceInvoker()) {
+                    invoker.refreshServiceDiscoveryInvoker();
+                } else {
+                    invoker.refreshInterfaceInvoker();
+                }
+            });
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
index a682297..51d0a49 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
@@ -162,7 +162,7 @@
     /**

      * Return MockInvoker

      * Contract:

-     * directory.list() will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return.

+     * directory.list() will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is absent or not true in invocation, otherwise, a list of mock invokers will return.

      * if directory.list() returns more than one mock invoker, only one of them will be used.

      *

      * @param invocation

@@ -174,7 +174,7 @@
         if (invocation instanceof RpcInvocation) {

             //Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachment needs to be improved)

             ((RpcInvocation) invocation).setAttachment(INVOCATION_NEED_MOCK, Boolean.TRUE.toString());

-            //directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return.

+            //directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is absent or not true in invocation, otherwise, a list of mock invokers will return.

             try {

                 invokers = directory.list(invocation);

             } catch (RpcException e) {

diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RoundRobinLoadBalanceTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RoundRobinLoadBalanceTest.java
index 8d77aa4..bb6260c 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RoundRobinLoadBalanceTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RoundRobinLoadBalanceTest.java
@@ -131,13 +131,7 @@
                 recycleTimeField = RoundRobinLoadBalance.class.getDeclaredField("RECYCLE_PERIOD");
                 recycleTimeField.setAccessible(true);
                 recycleTimeField.setInt(RoundRobinLoadBalance.class, 10);
-            } catch (NoSuchFieldException e) {
-                Assertions.assertTrue(true, "getField failed");
-            } catch (SecurityException e) {
-                Assertions.assertTrue(true, "getField failed");
-            } catch (IllegalArgumentException e) {
-                Assertions.assertTrue(true, "getField failed");
-            } catch (IllegalAccessException e) {
+            } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException | SecurityException e) {
                 Assertions.assertTrue(true, "getField failed");
             }
         }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRuleTest.java
similarity index 78%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
copy to dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRuleTest.java
index 2042277..5bd431d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/migration/MigrationRuleTest.java
@@ -14,7 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.rpc.cluster.support.migration;
 
-public class ConfigurationURL extends URL {
-}
+import org.junit.jupiter.api.Test;
+
+public class MigrationRuleTest {
+
+    @Test
+    public void testParse() {
+        System.out.println("xxx");
+    }
+
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/Parameters.java b/dubbo-common/src/main/java/org/apache/dubbo/common/Parameters.java
index 90e3415..790c476 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/Parameters.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/Parameters.java
@@ -244,6 +244,9 @@
 

     @Override

     public boolean equals(Object o) {

+        if (this == o) {

+            return true;

+        }

         return parameters.equals(o);

     }

 

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
index 873f461..86e7a02 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
@@ -246,6 +246,11 @@
         int port = 0;

         String path = null;

         Map<String, String> parameters = null;

+        // ignore the url content following '#'

+        int poundIndex = url.indexOf('#');

+        if (poundIndex != -1) {

+            url = url.substring(0, poundIndex);

+        }

         int i = url.indexOf('?'); // separator between body and parameters

         if (i >= 0) {

             String[] parts = url.substring(i + 1).split("&");

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java
index 2699e48..06db68e 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java
@@ -20,6 +20,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
 import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING;
 import static org.apache.dubbo.common.utils.StringUtils.decodeHexByte;
 import static org.apache.dubbo.common.utils.Utf8Utils.decodeUtf8;
@@ -226,11 +227,29 @@
         if (isEncoded) {
             String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
             String value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
+            if (valueStart == valueEnd) {
+                value = name;
+            } else {
+                value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
+            }
             params.put(name, value);
+            // compatible with lower versions registering "default." keys
+            if (name.startsWith(DEFAULT_KEY_PREFIX)) {
+                params.putIfAbsent(name.substring(DEFAULT_KEY_PREFIX.length()), value);
+            }
         } else {
-            String name = str.substring(nameStart, valueStart -1);
+            String name = str.substring(nameStart, valueStart - 1);
             String value = str.substring(valueStart, valueEnd);
+            if (valueStart == valueEnd) {
+                value = name;
+            } else {
+                value = str.substring(valueStart, valueEnd);
+            }
             params.put(name, value);
+            // compatible with lower versions registering "default." keys
+            if (name.startsWith(DEFAULT_KEY_PREFIX)) {
+                params.putIfAbsent(name.substring(DEFAULT_KEY_PREFIX.length()), value);
+            }
         }
         return true;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/ClassGenerator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/ClassGenerator.java
index b81b7a3..252c3aa 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/ClassGenerator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/ClassGenerator.java
@@ -340,9 +340,7 @@
             return mCtc.toClass(loader, pd);
         } catch (RuntimeException e) {
             throw e;
-        } catch (NotFoundException e) {
-            throw new RuntimeException(e.getMessage(), e);
-        } catch (CannotCompileException e) {
+        } catch (NotFoundException | CannotCompileException e) {
             throw new RuntimeException(e.getMessage(), e);
         }
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/CustomizedLoaderClassPath.java b/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/CustomizedLoaderClassPath.java
index b1ef491..918f2b1 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/CustomizedLoaderClassPath.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/bytecode/CustomizedLoaderClassPath.java
@@ -49,8 +49,9 @@
 
     public String toString() {
         Object cl = null;
-        if (clref != null)
+        if (clref != null) {
             cl = clref.get();
+        }
 
         return cl == null ? "<null>" : cl.toString();
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/ClassUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/ClassUtils.java
index 365cb7f..0406dfb 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/ClassUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/ClassUtils.java
@@ -49,9 +49,7 @@
     public static Object newInstance(String name) {

         try {

             return forName(name).newInstance();

-        } catch (InstantiationException e) {

-            throw new IllegalStateException(e.getMessage(), e);

-        } catch (IllegalAccessException e) {

+        } catch (InstantiationException | IllegalAccessException e) {

             throw new IllegalStateException(e.getMessage(), e);

         }

     }

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/CompositeConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/CompositeConfiguration.java
index eebf5a0..29c624b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/CompositeConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/CompositeConfiguration.java
@@ -38,6 +38,9 @@
      */
     private List<Configuration> configList = new LinkedList<Configuration>();
 
+    //FIXME, consider change configList to SortedMap to replace this boolean status.
+    private boolean dynamicIncluded;
+
     public CompositeConfiguration() {
         this(null, null);
     }
@@ -58,6 +61,15 @@
         }
     }
 
+    public void setDynamicIncluded(boolean dynamicIncluded) {
+        this.dynamicIncluded = dynamicIncluded;
+    }
+
+    //FIXME, consider change configList to SortedMap to replace this boolean status.
+    public boolean isDynamicIncluded() {
+        return dynamicIncluded;
+    }
+
     public void addConfiguration(Configuration configuration) {
         if (configList.contains(configuration)) {
             return;
@@ -113,4 +125,4 @@
         }
         return value != null ? value : defaultValue;
     }
-}
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
index a7c0693..ed79f15 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
@@ -66,6 +66,10 @@
         return ApplicationModel.getEnvironment().getConfiguration();
     }
 
+    public static Configuration getDynamicGlobalConfiguration() {
+        return ApplicationModel.getEnvironment().getDynamicGlobalConfiguration();
+    }
+
     // FIXME
     @SuppressWarnings("deprecation")
     public static int getServerShutdownTimeout() {
@@ -92,6 +96,14 @@
         return timeout;
     }
 
+    public static String getDynamicProperty(String property) {
+        return getDynamicProperty(property, null);
+    }
+
+    public static String getDynamicProperty(String property, String defaultValue) {
+        return StringUtils.trim(getDynamicGlobalConfiguration().getString(property, defaultValue));
+    }
+
     public static String getProperty(String property) {
         return getProperty(property, null);
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
index b5f24f7..cfc1b01 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
@@ -20,6 +20,8 @@
 import org.apache.dubbo.common.context.FrameworkExt;
 import org.apache.dubbo.common.context.LifecycleAdapter;
 import org.apache.dubbo.common.extension.DisableInject;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.config.AbstractConfig;
 import org.apache.dubbo.config.ConfigCenterConfig;
 import org.apache.dubbo.config.context.ConfigConfigurationAdapter;
@@ -32,6 +34,7 @@
 import java.util.Optional;
 
 public class Environment extends LifecycleAdapter implements FrameworkExt {
+    private static final Logger logger = LoggerFactory.getLogger(Environment.class);
     public static final String NAME = "environment";
 
     private final PropertiesConfiguration propertiesConfiguration;
@@ -41,6 +44,8 @@
     private final InmemoryConfiguration appExternalConfiguration;
 
     private CompositeConfiguration globalConfiguration;
+    private CompositeConfiguration dynamicGlobalConfiguration;
+
 
     private Map<String, String> externalConfigurationMap = new HashMap<>();
     private Map<String, String> appExternalConfigurationMap = new HashMap<>();
@@ -146,9 +151,6 @@
     public Configuration getConfiguration() {
         if (globalConfiguration == null) {
             globalConfiguration = new CompositeConfiguration();
-            if (dynamicConfiguration != null) {
-                globalConfiguration.addConfiguration(dynamicConfiguration);
-            }
             globalConfiguration.addConfiguration(systemConfiguration);
             globalConfiguration.addConfiguration(environmentConfiguration);
             globalConfiguration.addConfiguration(appExternalConfiguration);
@@ -158,6 +160,21 @@
         return globalConfiguration;
     }
 
+    public Configuration getDynamicGlobalConfiguration() {
+        if (dynamicGlobalConfiguration == null) {
+            if (dynamicConfiguration == null) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("dynamicConfiguration is null , return globalConfiguration.");
+                }
+                return globalConfiguration;
+            }
+            dynamicGlobalConfiguration = new CompositeConfiguration();
+            dynamicGlobalConfiguration.addConfiguration(dynamicConfiguration);
+            dynamicGlobalConfiguration.addConfiguration(getConfiguration());
+        }
+        return dynamicGlobalConfiguration;
+    }
+
     public boolean isConfigCenterFirst() {
         return configCenterFirst;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigChangedEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigChangedEvent.java
index 8195558..f7c2ec7 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigChangedEvent.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigChangedEvent.java
@@ -74,8 +74,12 @@
 
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof ConfigChangedEvent)) return false;
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ConfigChangedEvent)) {
+            return false;
+        }
         ConfigChangedEvent that = (ConfigChangedEvent) o;
         return Objects.equals(getKey(), that.getKey()) &&
                 Objects.equals(getGroup(), that.getGroup()) &&
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
index 3feeeb2..4fa5595 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
@@ -59,10 +59,9 @@
 
     String COMPATIBLE_CONFIG_KEY = "compatible_config";
 
-    String REGISTRY_DUPLICATE_KEY = "duplicate";
+    String REGISTRY_PUBLISH_INTERFACE_KEY = "publish-interface";
 
-    String ENABLE_REGISTRY_DIRECTORY_AUTO_MIGRATION = "enable-auto-migration";
-
+    String DUBBO_PUBLISH_INTERFACE_DEFAULT_KEY = "dubbo.application.publish-interface";
     /**
      * The parameter key of Dubbo Registry type
      *
@@ -114,4 +113,10 @@
     String ZONE_KEY = "zone";
 
     String REGISTRY_SERVICE_REFERENCE_PATH = "org.apache.dubbo.registry.RegistryService";
+
+    String INIT = "INIT";
+
+    boolean MIGRATION_MULTI_REGSITRY = false;
+
+    String LOADBALANCE_AMONG_REGISTRIES = "random";
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
index 922b04a..dc65572 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
@@ -599,26 +599,25 @@
     }
 
     private IllegalStateException findException(String name) {
-        for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
-            if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
-                return entry.getValue();
-            }
-        }
         StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
 
-
         int i = 1;
         for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
-            if (i == 1) {
-                buf.append(", possible causes: ");
+            if (entry.getKey().toLowerCase().startsWith(name.toLowerCase())) {
+                if (i == 1) {
+                    buf.append(", possible causes: ");
+                }
+                buf.append("\r\n(");
+                buf.append(i++);
+                buf.append(") ");
+                buf.append(entry.getKey());
+                buf.append(":\r\n");
+                buf.append(StringUtils.toString(entry.getValue()));
             }
+        }
 
-            buf.append("\r\n(");
-            buf.append(i++);
-            buf.append(") ");
-            buf.append(entry.getKey());
-            buf.append(":\r\n");
-            buf.append(StringUtils.toString(entry.getValue()));
+        if (i == 1) {
+            buf.append(", no related exception was found, please check whether related SPI module is missing.");
         }
         return new IllegalStateException(buf.toString());
     }
@@ -847,6 +846,7 @@
         try {
             try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                 String line;
+                String clazz = null;
                 while ((line = reader.readLine()) != null) {
                     final int ci = line.indexOf('#');
                     if (ci >= 0) {
@@ -859,10 +859,12 @@
                             int i = line.indexOf('=');
                             if (i > 0) {
                                 name = line.substring(0, i).trim();
-                                line = line.substring(i + 1).trim();
+                                clazz = line.substring(i + 1).trim();
+                            } else {
+                                clazz = line;
                             }
-                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
-                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
+                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
+                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                             }
                         } catch (Throwable t) {
                             IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/J2oVisitor.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/J2oVisitor.java
index 3aa3fd9..bc6e4e3 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/J2oVisitor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/J2oVisitor.java
@@ -259,9 +259,7 @@
             try {

                 mValue = mType.newInstance();

                 mWrapper = Wrapper.getWrapper(mType);

-            } catch (IllegalAccessException e) {

-                throw new ParseException(StringUtils.toString(e));

-            } catch (InstantiationException e) {

+            } catch (IllegalAccessException | InstantiationException e) {

                 throw new ParseException(StringUtils.toString(e));

             }

         }

@@ -304,9 +302,7 @@
                             field.setAccessible(true);

                         }

                         field.set(mValue, obj);

-                    } catch (NoSuchFieldException e) {

-                        throw new ParseException(StringUtils.toString(e));

-                    } catch (IllegalAccessException e) {

+                    } catch (NoSuchFieldException | IllegalAccessException e) {

                         throw new ParseException(StringUtils.toString(e));

                     }

                 } else if (!CLASS_PROPERTY.equals(name)) {

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/lang/Prioritized.java b/dubbo-common/src/main/java/org/apache/dubbo/common/lang/Prioritized.java
index babb3ed..76a6a0b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/lang/Prioritized.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/lang/Prioritized.java
@@ -63,7 +63,7 @@
     /**
      * Get the priority
      *
-     * @return the default is {@link #MIN_PRIORITY minimum one}
+     * @return the default is {@link #NORMAL_PRIORITY}
      */
     default int getPriority() {
         return NORMAL_PRIORITY;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalRunnable.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalRunnable.java
new file mode 100644
index 0000000..6cc8db8
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalRunnable.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common.threadlocal;
+
+
+/**
+ * InternalRunnable
+ * There is a risk of memory leak when using {@link InternalThreadLocal} without calling
+ * {@link InternalThreadLocal#removeAll()}.
+ * This design is learning from {@see io.netty.util.concurrent.FastThreadLocalRunnable} which is in Netty.
+ */
+public class InternalRunnable implements Runnable{
+    private final Runnable runnable;
+
+    public InternalRunnable(Runnable runnable){
+        this.runnable=runnable;
+    }
+
+    /**
+     * After the task execution is completed, it will call {@link InternalThreadLocal#removeAll()} to clear
+     * unnecessary variables in the thread.
+     */
+    @Override
+    public void run() {
+        try{
+            runnable.run();
+        }finally {
+            InternalThreadLocal.removeAll();
+        }
+    }
+
+    /**
+     * Wrap ordinary Runnable into {@link InternalThreadLocal}.
+     */
+     static Runnable Wrap(Runnable runnable){
+        return runnable instanceof InternalRunnable?runnable:new InternalRunnable(runnable);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/NamedInternalThreadFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/NamedInternalThreadFactory.java
index 52b8d56..0bb305f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/NamedInternalThreadFactory.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/NamedInternalThreadFactory.java
@@ -40,7 +40,7 @@
     @Override
     public Thread newThread(Runnable runnable) {
         String name = mPrefix + mThreadNum.getAndIncrement();
-        InternalThread ret = new InternalThread(mGroup, runnable, name, 0);
+        InternalThread ret = new InternalThread(mGroup, InternalRunnable.Wrap(runnable), name, 0);
         ret.setDaemon(mDaemon);
         return ret;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
index 1669089..4325ffa 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
@@ -21,6 +21,7 @@
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.ThreadPool;
+import org.apache.dubbo.common.utils.ExecutorUtil;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 
 import java.util.Map;
@@ -71,12 +72,9 @@
      * @return
      */
     public synchronized ExecutorService createExecutorIfAbsent(URL url) {
-        String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY;
-        if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
-            componentKey = CONSUMER_SIDE;
-        }
-        Map<Integer, ExecutorService> executors = data.computeIfAbsent(componentKey, k -> new ConcurrentHashMap<>());
-        Integer portKey = url.getPort();
+        Map<Integer, ExecutorService> executors = data.computeIfAbsent(EXECUTOR_SERVICE_COMPONENT_KEY, k -> new ConcurrentHashMap<>());
+        //issue-7054:Consumer's executor is sharing globally, key=Integer.MAX_VALUE. Provider's executor is sharing by protocol.
+        Integer portKey = CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE : url.getPort();
         ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(url));
         // If executor has been shut down, create a new one
         if (executor.isShutdown() || executor.isTerminated()) {
@@ -88,12 +86,7 @@
     }
 
     public ExecutorService getExecutor(URL url) {
-        String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY;
-        if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
-            componentKey = CONSUMER_SIDE;
-        }
-        Map<Integer, ExecutorService> executors = data.get(componentKey);
-
+        Map<Integer, ExecutorService> executors = data.get(EXECUTOR_SERVICE_COMPONENT_KEY);
         /**
          * It's guaranteed that this method is called after {@link #createExecutorIfAbsent(URL)}, so data should already
          * have Executor instances generated and stored.
@@ -103,17 +96,20 @@
                     "before coming to here.");
             return null;
         }
-
-        Integer portKey = url.getPort();
+        //issue-7054:Consumer's executor is sharing globally, key=Integer.MAX_VALUE. Provider's executor is sharing by protocol.
+        Integer portKey = CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE : url.getPort();
         ExecutorService executor = executors.get(portKey);
-        if (executor != null) {
-            if (executor.isShutdown() || executor.isTerminated()) {
-                executors.remove(portKey);
-                executor = createExecutor(url);
-                executors.put(portKey, executor);
-            }
+        if (executor != null && (executor.isShutdown() || executor.isTerminated())) {
+            executors.remove(portKey);
+            // Does not re-create a shutdown executor, use SHARED_EXECUTOR for downgrade.
+            executor = null;
+            logger.info("Executor for " + url + " is shutdown.");
         }
-        return executor;
+        if (executor == null) {
+            return SHARED_EXECUTOR;
+        } else {
+            return executor;
+        }
     }
 
     @Override
@@ -159,6 +155,19 @@
         return SHARED_EXECUTOR;
     }
 
+    @Override
+    public void destroyAll() {
+        data.values().forEach(executors -> {
+            if (executors != null) {
+                executors.values().forEach(executor -> {
+                    if (executor != null && !executor.isShutdown()) {
+                        ExecutorUtil.shutdownNow(executor, 100);
+                    }
+                });
+            }
+        });
+    }
+
     private ExecutorService createExecutor(URL url) {
         return (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
index af3b110..b19fcdf 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepository.java
@@ -64,4 +64,8 @@
      */
     ExecutorService getSharedExecutor();
 
+    /**
+     * Destroy all executors that are not in shutdown state
+     */
+    void destroyAll();
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
index c6865eb..6347e14 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
@@ -31,8 +31,10 @@
 import org.apache.dubbo.common.logger.LoggerFactory;

 import org.apache.dubbo.common.threadpool.event.ThreadPoolExhaustedEvent;

 import org.apache.dubbo.common.utils.JVMUtil;

+import org.apache.dubbo.common.utils.StringUtils;

 import org.apache.dubbo.event.EventDispatcher;

 

+import static java.lang.String.format;

 import static org.apache.dubbo.common.constants.CommonConstants.DUMP_DIRECTORY;

 

 /**

@@ -61,6 +63,8 @@
 

     private static Semaphore guard = new Semaphore(1);

 

+    private static final String USER_HOME = System.getProperty("user.home");

+

     public AbortPolicyWithReport(String threadName, URL url) {

         this.threadName = threadName;

         this.url = url;

@@ -104,7 +108,7 @@
 

         ExecutorService pool = Executors.newSingleThreadExecutor();

         pool.execute(() -> {

-            String dumpPath = url.getParameter(DUMP_DIRECTORY, System.getProperty("user.home"));

+            String dumpPath = getDumpPath();

 

             SimpleDateFormat sdf;

 

@@ -134,4 +138,21 @@
 

     }

 

+    private String getDumpPath() {

+        final String dumpPath = url.getParameter(DUMP_DIRECTORY);

+        if (StringUtils.isEmpty(dumpPath)) {

+            return USER_HOME;

+        }

+        final File dumpDirectory = new File(dumpPath);

+        if (!dumpDirectory.exists()) {

+            if (dumpDirectory.mkdirs()) {

+                logger.info(format("Dubbo dump directory[%s] created", dumpDirectory.getAbsolutePath()));

+            } else {

+                logger.warn(format("Dubbo dump directory[%s] can't be created, use the 'user.home'[%s]",

+                        dumpDirectory.getAbsolutePath(), USER_HOME));

+                return USER_HOME;

+            }

+        }

+        return dumpPath;

+    }

 }

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
index e648598..33af1bc 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
@@ -50,9 +50,7 @@
     }
 
     public static int indexOf(String[] array, String valueToFind, int startIndex) {
-        if (isEmpty(array) || valueToFind == null) {
-            return -1;
-        } else {
+        if (!isEmpty(array) && valueToFind != null) {
             if (startIndex < 0) {
                 startIndex = 0;
             }
@@ -63,8 +61,8 @@
                 }
             }
 
-            return -1;
         }
+        return -1;
     }
 
     /**
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java
index 1b73370..8f16036 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java
@@ -324,9 +324,7 @@
 

         try {

             return one.containsAll(another);

-        } catch (ClassCastException unused) {

-            return false;

-        } catch (NullPointerException unused) {

+        } catch (ClassCastException | NullPointerException unused) {

             return false;

         }

     }

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
index 5d5cc5c..415f017 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
@@ -1,223 +1,229 @@
-/*

- * 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.common.utils;

-

-import java.lang.reflect.Array;

-import java.math.BigDecimal;

-import java.math.BigInteger;

-import java.text.ParseException;

-import java.text.SimpleDateFormat;

-import java.time.LocalDateTime;

-import java.util.ArrayList;

-import java.util.Collection;

-import java.util.Date;

-import java.util.HashSet;

-import java.util.List;

-import java.util.Set;

-

-public class CompatibleTypeUtils {

-

-    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

-

-    private CompatibleTypeUtils() {

-    }

-

-    /**

-     * Compatible type convert. Null value is allowed to pass in. If no conversion is needed, then the original value

-     * will be returned.

-     * <p>

-     * Supported compatible type conversions include (primary types and corresponding wrappers are not listed):

-     * <ul>

-     * <li> String -> char, enum, Date

-     * <li> byte, short, int, long -> byte, short, int, long

-     * <li> float, double -> float, double

-     * </ul>

-     */

-    @SuppressWarnings({"unchecked", "rawtypes"})

-    public static Object compatibleTypeConvert(Object value, Class<?> type) {

-        if (value == null || type == null || type.isAssignableFrom(value.getClass())) {

-            return value;

-        }

-

-        if (value instanceof String) {

-            String string = (String) value;

-            if (char.class.equals(type) || Character.class.equals(type)) {

-                if (string.length() != 1) {

-                    throw new IllegalArgumentException(String.format("CAN NOT convert String(%s) to char!" +

-                            " when convert String to char, the String MUST only 1 char.", string));

-                }

-                return string.charAt(0);

-            }

-            if (type.isEnum()) {

-                return Enum.valueOf((Class<Enum>) type, string);

-            }

-            if (type == BigInteger.class) {

-                return new BigInteger(string);

-            }

-            if (type == BigDecimal.class) {

-                return new BigDecimal(string);

-            }

-            if (type == Short.class || type == short.class) {

-                return new Short(string);

-            }

-            if (type == Integer.class || type == int.class) {

-                return new Integer(string);

-            }

-            if (type == Long.class || type == long.class) {

-                return new Long(string);

-            }

-            if (type == Double.class || type == double.class) {

-                return new Double(string);

-            }

-            if (type == Float.class || type == float.class) {

-                return new Float(string);

-            }

-            if (type == Byte.class || type == byte.class) {

-                return new Byte(string);

-            }

-            if (type == Boolean.class || type == boolean.class) {

-                return Boolean.valueOf(string);

-            }

-            if (type == Date.class || type == java.sql.Date.class || type == java.sql.Timestamp.class

-                    || type == java.sql.Time.class) {

-                try {

-                    Date date = new SimpleDateFormat(DATE_FORMAT).parse(string);

-                    if (type == java.sql.Date.class) {

-                        return new java.sql.Date(date.getTime());

-                    }

-                    if (type == java.sql.Timestamp.class) {

-                        return new java.sql.Timestamp(date.getTime());

-                    }

-                    if (type == java.sql.Time.class) {

-                        return new java.sql.Time(date.getTime());

-                    }

-                    return date;

-                } catch (ParseException e) {

-                    throw new IllegalStateException("Failed to parse date " + value + " by format "

-                            + DATE_FORMAT + ", cause: " + e.getMessage(), e);

-                }

-            }

-            if (type == java.time.LocalDateTime.class || type == java.time.LocalDate.class

-                    || type == java.time.LocalTime.class) {

-

-                LocalDateTime localDateTime = LocalDateTime.parse(string);

-                if (type == java.time.LocalDate.class) {

-                    return localDateTime.toLocalDate();

-                }

-                if (type == java.time.LocalTime.class) {

-                    return localDateTime.toLocalTime();

-                }

-                return localDateTime;

-            }

-            if (type == Class.class) {

-                try {

-                    return ReflectUtils.name2class(string);

-                } catch (ClassNotFoundException e) {

-                    throw new RuntimeException(e.getMessage(), e);

-                }

-            }

-            if (char[].class.equals(type)) {

-                // Process string to char array for generic invoke

-                // See

-                // - https://github.com/apache/dubbo/issues/2003

-                int len = string.length();

-                char[] chars = new char[len];

-                string.getChars(0, len, chars, 0);

-                return chars;

-            }

-        }

-        if (value instanceof Number) {

-            Number number = (Number) value;

-            if (type == byte.class || type == Byte.class) {

-                return number.byteValue();

-            }

-            if (type == short.class || type == Short.class) {

-                return number.shortValue();

-            }

-            if (type == int.class || type == Integer.class) {

-                return number.intValue();

-            }

-            if (type == long.class || type == Long.class) {

-                return number.longValue();

-            }

-            if (type == float.class || type == Float.class) {

-                return number.floatValue();

-            }

-            if (type == double.class || type == Double.class) {

-                return number.doubleValue();

-            }

-            if (type == BigInteger.class) {

-                return BigInteger.valueOf(number.longValue());

-            }

-            if (type == BigDecimal.class) {

-                return BigDecimal.valueOf(number.doubleValue());

-            }

-            if (type == Date.class) {

-                return new Date(number.longValue());

-            }

-            if (type == boolean.class || type == Boolean.class) {

-                return 0 != number.intValue();

-            }

-        }

-        if (value instanceof Collection) {

-            Collection collection = (Collection) value;

-            if (type.isArray()) {

-                int length = collection.size();

-                Object array = Array.newInstance(type.getComponentType(), length);

-                int i = 0;

-                for (Object item : collection) {

-                    Array.set(array, i++, item);

-                }

-                return array;

-            }

-            if (!type.isInterface()) {

-                try {

-                    Collection result = (Collection) type.newInstance();

-                    result.addAll(collection);

-                    return result;

-                } catch (Throwable ignored) {

-                }

-            }

-            if (type == List.class) {

-                return new ArrayList<Object>(collection);

-            }

-            if (type == Set.class) {

-                return new HashSet<Object>(collection);

-            }

-        }

-        if (value.getClass().isArray() && Collection.class.isAssignableFrom(type)) {

-            Collection collection;

-            if (!type.isInterface()) {

-                try {

-                    collection = (Collection) type.newInstance();

-                } catch (Throwable e) {

-                    collection = new ArrayList<Object>();

-                }

-            } else if (type == Set.class) {

-                collection = new HashSet<Object>();

-            } else {

-                collection = new ArrayList<Object>();

-            }

-            int length = Array.getLength(value);

-            for (int i = 0; i < length; i++) {

-                collection.add(Array.get(value, i));

-            }

-            return collection;

-        }

-        return value;

-    }

-}

+/*
+ * 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.common.utils;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CompatibleTypeUtils {
+
+    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    private CompatibleTypeUtils() {
+    }
+
+    /**
+     * Compatible type convert. Null value is allowed to pass in. If no conversion is needed, then the original value
+     * will be returned.
+     * <p>
+     * Supported compatible type conversions include (primary types and corresponding wrappers are not listed):
+     * <ul>
+     * <li> String -> char, enum, Date
+     * <li> byte, short, int, long -> byte, short, int, long
+     * <li> float, double -> float, double
+     * </ul>
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static Object compatibleTypeConvert(Object value, Class<?> type) {
+        if (value == null || type == null || type.isAssignableFrom(value.getClass())) {
+            return value;
+        }
+
+        if (value instanceof String) {
+            String string = (String) value;
+            if (char.class.equals(type) || Character.class.equals(type)) {
+                if (string.length() != 1) {
+                    throw new IllegalArgumentException(String.format("CAN NOT convert String(%s) to char!" +
+                            " when convert String to char, the String MUST only 1 char.", string));
+                }
+                return string.charAt(0);
+            }
+            if (type.isEnum()) {
+                return Enum.valueOf((Class<Enum>) type, string);
+            }
+            if (type == BigInteger.class) {
+                return new BigInteger(string);
+            }
+            if (type == BigDecimal.class) {
+                return new BigDecimal(string);
+            }
+            if (type == Short.class || type == short.class) {
+                return new Short(string);
+            }
+            if (type == Integer.class || type == int.class) {
+                return new Integer(string);
+            }
+            if (type == Long.class || type == long.class) {
+                return new Long(string);
+            }
+            if (type == Double.class || type == double.class) {
+                return new Double(string);
+            }
+            if (type == Float.class || type == float.class) {
+                return new Float(string);
+            }
+            if (type == Byte.class || type == byte.class) {
+                return new Byte(string);
+            }
+            if (type == Boolean.class || type == boolean.class) {
+                return Boolean.valueOf(string);
+            }
+            if (type == Date.class || type == java.sql.Date.class || type == java.sql.Timestamp.class
+                    || type == java.sql.Time.class) {
+                try {
+                    Date date = new SimpleDateFormat(DATE_FORMAT).parse(string);
+                    if (type == java.sql.Date.class) {
+                        return new java.sql.Date(date.getTime());
+                    }
+                    if (type == java.sql.Timestamp.class) {
+                        return new java.sql.Timestamp(date.getTime());
+                    }
+                    if (type == java.sql.Time.class) {
+                        return new java.sql.Time(date.getTime());
+                    }
+                    return date;
+                } catch (ParseException e) {
+                    throw new IllegalStateException("Failed to parse date " + value + " by format "
+                            + DATE_FORMAT + ", cause: " + e.getMessage(), e);
+                }
+            }
+            if (type == java.time.LocalDateTime.class) {
+                if (StringUtils.isEmpty(string)) {
+                    return null;
+                }
+                return LocalDateTime.parse(string);
+            }
+            if (type == java.time.LocalDate.class) {
+                if (StringUtils.isEmpty(string)) {
+                    return null;
+                }
+                return java.time.LocalDate.parse(string);
+            }
+            if (type == java.time.LocalTime.class) {
+                if (StringUtils.isEmpty(string)) {
+                    return null;
+                }
+                return LocalDateTime.parse(string).toLocalTime();
+            }
+            if (type == Class.class) {
+                try {
+                    return ReflectUtils.name2class(string);
+                } catch (ClassNotFoundException e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+            if (char[].class.equals(type)) {
+                // Process string to char array for generic invoke
+                // See
+                // - https://github.com/apache/dubbo/issues/2003
+                int len = string.length();
+                char[] chars = new char[len];
+                string.getChars(0, len, chars, 0);
+                return chars;
+            }
+        }
+        if (value instanceof Number) {
+            Number number = (Number) value;
+            if (type == byte.class || type == Byte.class) {
+                return number.byteValue();
+            }
+            if (type == short.class || type == Short.class) {
+                return number.shortValue();
+            }
+            if (type == int.class || type == Integer.class) {
+                return number.intValue();
+            }
+            if (type == long.class || type == Long.class) {
+                return number.longValue();
+            }
+            if (type == float.class || type == Float.class) {
+                return number.floatValue();
+            }
+            if (type == double.class || type == Double.class) {
+                return number.doubleValue();
+            }
+            if (type == BigInteger.class) {
+                return BigInteger.valueOf(number.longValue());
+            }
+            if (type == BigDecimal.class) {
+                return BigDecimal.valueOf(number.doubleValue());
+            }
+            if (type == Date.class) {
+                return new Date(number.longValue());
+            }
+            if (type == boolean.class || type == Boolean.class) {
+                return 0 != number.intValue();
+            }
+        }
+        if (value instanceof Collection) {
+            Collection collection = (Collection) value;
+            if (type.isArray()) {
+                int length = collection.size();
+                Object array = Array.newInstance(type.getComponentType(), length);
+                int i = 0;
+                for (Object item : collection) {
+                    Array.set(array, i++, item);
+                }
+                return array;
+            }
+            if (!type.isInterface()) {
+                try {
+                    Collection result = (Collection) type.newInstance();
+                    result.addAll(collection);
+                    return result;
+                } catch (Throwable ignored) {
+                }
+            }
+            if (type == List.class) {
+                return new ArrayList<Object>(collection);
+            }
+            if (type == Set.class) {
+                return new HashSet<Object>(collection);
+            }
+        }
+        if (value.getClass().isArray() && Collection.class.isAssignableFrom(type)) {
+            Collection collection;
+            if (!type.isInterface()) {
+                try {
+                    collection = (Collection) type.newInstance();
+                } catch (Throwable e) {
+                    collection = new ArrayList<Object>();
+                }
+            } else if (type == Set.class) {
+                collection = new HashSet<Object>();
+            } else {
+                collection = new ArrayList<Object>();
+            }
+            int length = Array.getLength(value);
+            for (int i = 0; i < length; i++) {
+                collection.add(Array.get(value, i));
+            }
+            return collection;
+        }
+        return value;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ExecutorUtil.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ExecutorUtil.java
index 93a2a60..97f13a5 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ExecutorUtil.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ExecutorUtil.java
@@ -60,9 +60,7 @@
         try {
             // Disable new tasks from being submitted
             es.shutdown();
-        } catch (SecurityException ex2) {
-            return;
-        } catch (NullPointerException ex2) {
+        } catch (SecurityException | NullPointerException ex2) {
             return;
         }
         try {
@@ -86,9 +84,7 @@
         final ExecutorService es = (ExecutorService) executor;
         try {
             es.shutdownNow();
-        } catch (SecurityException ex2) {
-            return;
-        } catch (NullPointerException ex2) {
+        } catch (SecurityException | NullPointerException ex2) {
             return;
         }
         try {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
index 28cf633..4514dc8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
@@ -587,11 +587,7 @@
                 constructor.setAccessible(true);

                 Object[] parameters = Arrays.stream(constructor.getParameterTypes()).map(PojoUtils::getDefaultValue).toArray();

                 return constructor.newInstance(parameters);

-            } catch (InstantiationException e) {

-                throw new RuntimeException(e.getMessage(), e);

-            } catch (IllegalAccessException e) {

-                throw new RuntimeException(e.getMessage(), e);

-            } catch (InvocationTargetException e) {

+            } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {

                 throw new RuntimeException(e.getMessage(), e);

             }

         }

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java
index a8b728b..5007287 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java
@@ -31,6 +31,7 @@
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
 import java.net.URL;
 import java.security.CodeSource;
 import java.security.ProtectionDomain;
@@ -154,8 +155,8 @@
     }
 
     public static boolean isPrimitives(Class<?> cls) {
-        if (cls.isArray()) {
-            return isPrimitive(cls.getComponentType());
+        while (cls.isArray()) {
+            cls = cls.getComponentType();
         }
         return isPrimitive(cls);
     }
@@ -1202,6 +1203,9 @@
                 if (actualArgType instanceof ParameterizedType) {
                     returnType = (Class<?>) ((ParameterizedType) actualArgType).getRawType();
                     genericReturnType = actualArgType;
+                } else if (actualArgType instanceof TypeVariable) {
+                    returnType = (Class<?>) ((TypeVariable<?>) actualArgType).getBounds()[0];
+                    genericReturnType = actualArgType;
                 } else {
                     returnType = (Class<?>) actualArgType;
                     genericReturnType = returnType;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
index 970a6ae..536286c 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
@@ -26,6 +26,7 @@
 import org.apache.dubbo.config.context.ConfigManager;
 import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ServiceMetadata;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -53,6 +54,21 @@
     private static final long serialVersionUID = -1559314110797223229L;
 
     /**
+     * The interface name of the exported service
+     */
+    protected String interfaceName;
+    /**
+     * The remote service version the customer/provider side will reference
+     */
+    protected String version;
+
+    /**
+     * The remote service group the customer/provider side will reference
+     */
+    protected String group;
+    
+    protected ServiceMetadata serviceMetadata;
+    /**
      * Local impl class name for the service interface
      */
     protected String local;
@@ -312,6 +328,10 @@
 
     }
 
+    protected boolean notHasSelfRegistryProperty() {
+        return CollectionUtils.isEmpty(registries) && StringUtils.isEmpty(registryIds);
+    }
+
     public void completeCompoundConfigs(AbstractInterfaceConfig interfaceConfig) {
         if (interfaceConfig != null) {
             if (application == null) {
@@ -320,15 +340,16 @@
             if (module == null) {
                 setModule(interfaceConfig.getModule());
             }
-            if (registries == null) {
+            if (notHasSelfRegistryProperty()) {
                 setRegistries(interfaceConfig.getRegistries());
+                setRegistryIds(interfaceConfig.getRegistryIds());
             }
             if (monitor == null) {
                 setMonitor(interfaceConfig.getMonitor());
             }
         }
         if (module != null) {
-            if (registries == null) {
+            if (notHasSelfRegistryProperty()) {
                 setRegistries(module.getRegistries());
             }
             if (monitor == null) {
@@ -336,8 +357,9 @@
             }
         }
         if (application != null) {
-            if (registries == null) {
+            if (notHasSelfRegistryProperty()) {
                 setRegistries(application.getRegistries());
+                setRegistryIds(application.getRegistryIds());
             }
             if (monitor == null) {
                 setMonitor(application.getMonitor());
@@ -346,10 +368,9 @@
     }
     
     protected void computeValidRegistryIds() {
-        if (StringUtils.isEmpty(getRegistryIds())) {
-            if (getApplication() != null && StringUtils.isNotEmpty(getApplication().getRegistryIds())) {
-                setRegistryIds(getApplication().getRegistryIds());
-            }
+        if (application != null && notHasSelfRegistryProperty()) {
+            setRegistries(application.getRegistries());
+            setRegistryIds(application.getRegistryIds());
         }
     }
 
@@ -451,11 +472,18 @@
         this.layer = layer;
     }
 
+    /**
+     * Always use the global ApplicationConfig
+     */
     public ApplicationConfig getApplication() {
-        if (application != null) {
+        ApplicationConfig globalApplication = ApplicationModel.getConfigManager().getApplicationOrElseThrow();
+        if (globalApplication == null) {
             return application;
         }
-        return ApplicationModel.getConfigManager().getApplicationOrElseThrow();
+        if (application != null && !StringUtils.isEquals(application.getName(), globalApplication.getName())) {
+            return application;
+        }
+        return globalApplication;
     }
 
     @Deprecated
@@ -685,4 +713,46 @@
     public SslConfig getSslConfig() {
         return ApplicationModel.getConfigManager().getSsl().orElse(null);
     }
+    
+    public void initServiceMetadata(AbstractInterfaceConfig interfaceConfig) {
+        serviceMetadata.setVersion(getVersion(interfaceConfig));
+        serviceMetadata.setGroup(getGroup(interfaceConfig));
+        serviceMetadata.setDefaultGroup(getGroup(interfaceConfig));
+        serviceMetadata.setServiceInterfaceName(getInterface());
+    }
+    
+    public String getGroup(AbstractInterfaceConfig interfaceConfig) {
+        return StringUtils.isEmpty(this.group) ? (interfaceConfig != null ? interfaceConfig.getGroup() : this.group) : this.group;
+    }
+
+    public String getVersion(AbstractInterfaceConfig interfaceConfig) {
+        return StringUtils.isEmpty(this.version) ? (interfaceConfig != null ? interfaceConfig.getVersion() : this.version) : this.version;
+    }
+    
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+    
+    public String getInterface() {
+        return interfaceName;
+    }
+    
+    public void setInterface(String interfaceName) {
+        this.interfaceName = interfaceName;
+//         if (StringUtils.isEmpty(id)) {
+//             id = interfaceName;
+//         }
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
index 27d4ddc..f2038a1 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
@@ -71,15 +71,7 @@
     //TODO solve merge problem
     protected Boolean stubevent;//= Constants.DEFAULT_STUB_EVENT;
 
-    /**
-     * The remote service version the customer side will reference
-     */
-    protected String version;
 
-    /**
-     * The remote service group the customer side will reference
-     */
-    protected String group;
 
     /**
      * declares which app or service this interface belongs to
@@ -212,21 +204,7 @@
         this.sticky = sticky;
     }
 
-    public String getVersion() {
-        return version;
-    }
 
-    public void setVersion(String version) {
-        this.version = version;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public void setGroup(String group) {
-        this.group = group;
-    }
 
     @Parameter(key = "provided-by")
     public String getProvidedBy() {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
index e482e35..fe65c5b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
@@ -42,6 +42,7 @@
 import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE;
 import static org.apache.dubbo.common.constants.QosConstants.QOS_HOST;
 import static org.apache.dubbo.common.constants.QosConstants.QOS_PORT;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PUBLISH_INTERFACE_KEY;
 import static org.apache.dubbo.config.Constants.DEVELOPMENT_ENVIRONMENT;
 import static org.apache.dubbo.config.Constants.PRODUCTION_ENVIRONMENT;
 import static org.apache.dubbo.config.Constants.TEST_ENVIRONMENT;
@@ -159,6 +160,8 @@
 
     private String repository;
 
+    private Boolean publishInterface;
+
     /**
      * Metadata Service, used in Service Discovery
      */
@@ -450,6 +453,15 @@
         this.repository = repository;
     }
 
+    @Parameter(key = REGISTRY_PUBLISH_INTERFACE_KEY)
+    public Boolean getPublishInterface() {
+        return publishInterface;
+    }
+
+    public void setPublishInterface(Boolean publishInterface) {
+        this.publishInterface = publishInterface;
+    }
+
     @Parameter(key = "metadata-service-port")
     public Integer getMetadataServicePort() {
         return metadataServicePort;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
index 966b384..a16b49e 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
@@ -43,11 +43,6 @@
     private static final long serialVersionUID = -5864351140409987595L;
 
     /**
-     * The interface name of the reference service
-     */
-    protected String interfaceName;
-
-    /**
      * The interface class of the reference service
      */
     protected Class<?> interfaceClass;
@@ -72,7 +67,6 @@
      */
     protected String protocol;
 
-    protected ServiceMetadata serviceMetadata;
 
     public ReferenceConfigBase() {
         serviceMetadata = new ServiceMetadata();
@@ -160,18 +154,6 @@
         setInterface(interfaceClass);
     }
 
-    public String getInterface() {
-        return interfaceName;
-    }
-
-    public void setInterface(String interfaceName) {
-        this.interfaceName = interfaceName;
-        // FIXME, add id strategy in ConfigManager
-//        if (StringUtils.isEmpty(id)) {
-//            id = interfaceName;
-//        }
-    }
-
     public void setInterface(Class<?> interfaceClass) {
         if (interfaceClass != null && !interfaceClass.isInterface()) {
             throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
@@ -259,12 +241,14 @@
 
     @Override
     protected void computeValidRegistryIds() {
-        super.computeValidRegistryIds();
-        if (StringUtils.isEmpty(getRegistryIds())) {
-            if (getConsumer() != null && StringUtils.isNotEmpty(getConsumer().getRegistryIds())) {
-                setRegistryIds(getConsumer().getRegistryIds());
+        if (consumer != null) {
+            if (notHasSelfRegistryProperty()) {
+                setRegistries(consumer.getRegistries());
+                setRegistryIds(consumer.getRegistryIds());
             }
         }
+
+        super.computeValidRegistryIds();
     }
 
     @Parameter(excluded = true)
@@ -272,16 +256,6 @@
         return URL.buildKey(interfaceName, getGroup(), getVersion());
     }
 
-    @Override
-    public String getVersion() {
-        return StringUtils.isEmpty(this.version) ? (consumer != null ? consumer.getVersion() : this.version) : this.version;
-    }
-
-    @Override
-    public String getGroup() {
-        return StringUtils.isEmpty(this.group) ? (consumer != null ? consumer.getGroup() : this.group) : this.group;
-    }
-
     public abstract T get();
 
     public abstract void destroy();
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
index f1b14d6..c213e49 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
@@ -43,10 +43,7 @@
 
     private static final long serialVersionUID = 3033787999037024738L;
 
-    /**
-     * The interface name of the exported service
-     */
-    protected String interfaceName;
+
 
     /**
      * The interface class of the exported service
@@ -78,7 +75,7 @@
      */
     protected volatile String generic;
 
-    protected ServiceMetadata serviceMetadata;
+
 
     public ServiceConfigBase() {
         serviceMetadata = new ServiceMetadata();
@@ -202,32 +199,35 @@
     }
 
     public void checkProtocol() {
+        if (provider != null && notHasSelfProtocolProperty()) {
+            setProtocols(provider.getProtocols());
+            setProtocolIds(provider.getProtocolIds());
+        }
+
         if (CollectionUtils.isEmpty(protocols) && provider != null) {
             setProtocols(provider.getProtocols());
         }
         convertProtocolIdsToProtocols();
     }
 
+    private boolean notHasSelfProtocolProperty() {
+        return CollectionUtils.isEmpty(protocols) && StringUtils.isEmpty(protocolIds);
+    }
+
     public void completeCompoundConfigs() {
         super.completeCompoundConfigs(provider);
         if (provider != null) {
-            if (protocols == null) {
+            if (notHasSelfProtocolProperty()) {
                 setProtocols(provider.getProtocols());
+                setProtocolIds(provider.getProtocolIds());
             }
             if (configCenter == null) {
                 setConfigCenter(provider.getConfigCenter());
             }
-            if (StringUtils.isEmpty(registryIds)) {
-                setRegistryIds(provider.getRegistryIds());
-            }
-            if (StringUtils.isEmpty(protocolIds)) {
-                setProtocolIds(provider.getProtocolIds());
-            }
         }
     }
 
     private void convertProtocolIdsToProtocols() {
-        computeValidProtocolIds();
         if (StringUtils.isEmpty(protocolIds)) {
             if (CollectionUtils.isEmpty(protocols)) {
                 List<ProtocolConfig> protocolConfigs = ApplicationModel.getConfigManager().getDefaultProtocols();
@@ -292,9 +292,7 @@
         setInterface(interfaceClass);
     }
 
-    public String getInterface() {
-        return interfaceName;
-    }
+
 
     public void setInterface(Class<?> interfaceClass) {
         if (interfaceClass != null && !interfaceClass.isInterface()) {
@@ -304,14 +302,6 @@
         setInterface(interfaceClass == null ? null : interfaceClass.getName());
     }
 
-    public void setInterface(String interfaceName) {
-        this.interfaceName = interfaceName;
-        // FIXME, add id strategy in ConfigManager
-//        if (StringUtils.isEmpty(id)) {
-//            id = interfaceName;
-//        }
-    }
-
     public T getRef() {
         return ref;
     }
@@ -403,32 +393,16 @@
         return URL.buildKey(interfaceName, getGroup(), getVersion());
     }
 
-    @Override
-    public String getGroup() {
-        return StringUtils.isEmpty(this.group) ? (provider != null ? provider.getGroup() : this.group) : this.group;
-    }
 
-    @Override
-    public String getVersion() {
-        return StringUtils.isEmpty(this.version) ? (provider != null ? provider.getVersion() : this.version) : this.version;
-    }
-
-    private void computeValidProtocolIds() {
-        if (StringUtils.isEmpty(getProtocolIds())) {
-            if (getProvider() != null && StringUtils.isNotEmpty(getProvider().getProtocolIds())) {
-                setProtocolIds(getProvider().getProtocolIds());
-            }
-        }
-    }
 
     @Override
     protected void computeValidRegistryIds() {
-        super.computeValidRegistryIds();
-        if (StringUtils.isEmpty(getRegistryIds())) {
-            if (getProvider() != null && StringUtils.isNotEmpty(getProvider().getRegistryIds())) {
-                setRegistryIds(getProvider().getRegistryIds());
-            }
+        if (provider != null && notHasSelfRegistryProperty()) {
+            setRegistries(provider.getRegistries());
+            setRegistryIds(provider.getRegistryIds());
         }
+
+        super.computeValidRegistryIds();
     }
 
     public abstract void export();
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/extension/ExtensionLoaderTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/extension/ExtensionLoaderTest.java
index a1a534a..b861fc0 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/extension/ExtensionLoaderTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/extension/ExtensionLoaderTest.java
@@ -374,7 +374,7 @@
             fail();
         } catch (IllegalStateException expected) {
             assertThat(expected.getMessage(), containsString("Failed to load extension class (interface: interface org.apache.dubbo.common.extension.ext7.InitErrorExt"));
-            assertThat(expected.getCause(), instanceOf(ExceptionInInitializerError.class));
+            assertThat(expected.getMessage(), containsString("java.lang.ExceptionInInitializerError"));
         }
     }
 
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
index bb3fe4e..b19eb37 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/manager/ExecutorRepositoryTest.java
@@ -39,8 +39,6 @@
     }
 
     private void testGet(URL url) {
-        Assertions.assertNull(executorRepository.getExecutor(url));
-
         ExecutorService executorService = executorRepository.createExecutorIfAbsent(url);
         executorService.shutdown();
         executorService = executorRepository.createExecutorIfAbsent(url);
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReportTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReportTest.java
index ed737ef..178c73a 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReportTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReportTest.java
@@ -17,9 +17,9 @@
 package org.apache.dubbo.common.threadpool.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.threadpool.support.AbortPolicyWithReport;
 import org.junit.jupiter.api.Test;
 
+import java.util.UUID;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -44,4 +44,60 @@
         Thread.sleep(1000);
 
     }
+
+    @Test
+    public void jStackDumpTest_dumpDirectoryNotExists_cannotBeCreatedTakeUserHome() throws InterruptedException {
+        final String dumpDirectory = dumpDirectoryCannotBeCreated();
+
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?dump.directory="
+                + dumpDirectory
+                + "&version=1.0.0&application=morgan&noValue");
+        AbortPolicyWithReport abortPolicyWithReport = new AbortPolicyWithReport("Test", url);
+
+        try {
+            abortPolicyWithReport.rejectedExecution(new Runnable() {
+                @Override
+                public void run() {
+                    System.out.println("hello");
+                }
+            }, (ThreadPoolExecutor) Executors.newFixedThreadPool(1));
+        } catch (RejectedExecutionException rj) {
+            // ignore
+        }
+
+        Thread.sleep(1000);
+    }
+
+    private String dumpDirectoryCannotBeCreated() {
+        final String os = System.getProperty("os.name").toLowerCase();
+        if (os.contains("win")) {
+            // "con" is one of Windows reserved names, https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+            return "con";
+        } else {
+            return "/dev/full/" + UUID.randomUUID().toString();
+        }
+    }
+
+    @Test
+    public void jStackDumpTest_dumpDirectoryNotExists_canBeCreated() throws InterruptedException {
+        final String dumpDirectory = UUID.randomUUID().toString();
+
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?dump.directory="
+                + dumpDirectory
+                + "&version=1.0.0&application=morgan&noValue");
+        AbortPolicyWithReport abortPolicyWithReport = new AbortPolicyWithReport("Test", url);
+
+        try {
+            abortPolicyWithReport.rejectedExecution(new Runnable() {
+                @Override
+                public void run() {
+                    System.out.println("hello");
+                }
+            }, (ThreadPoolExecutor) Executors.newFixedThreadPool(1));
+        } catch (RejectedExecutionException rj) {
+            // ignore
+        }
+
+        Thread.sleep(1000);
+    }
 }
\ No newline at end of file
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/CompatibleTypeUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/CompatibleTypeUtilsTest.java
index 85e7180..eaae795 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/CompatibleTypeUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/CompatibleTypeUtilsTest.java
@@ -84,7 +84,7 @@
             result = CompatibleTypeUtils.compatibleTypeConvert("2011-12-11T12:24:12.047", java.time.LocalTime.class);

             assertEquals(DateTimeFormatter.ofPattern("HH:mm:ss").format((java.time.LocalTime) result), "12:24:12");

 

-            result = CompatibleTypeUtils.compatibleTypeConvert("2011-12-11T12:24:12.047", java.time.LocalDate.class);

+            result = CompatibleTypeUtils.compatibleTypeConvert("2011-12-11", java.time.LocalDate.class);

             assertEquals(DateTimeFormatter.ofPattern("yyyy-MM-dd").format((java.time.LocalDate) result), "2011-12-11");

 

             result = CompatibleTypeUtils.compatibleTypeConvert("ab", char[].class);

@@ -219,4 +219,4 @@
         }

 

     }

-}
\ No newline at end of file
+}

diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
index 4bc4b31..e615dd9 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
@@ -199,6 +199,11 @@
         assertArrayObject(new Float[]{37F, -39F, 123456.7F});
 
         assertArrayObject(new Double[]{37D, -39D, 123456.7D});
+
+        assertObject(new int[][]{{37, -39, 12456}});
+        assertObject(new Integer[][][]{{{37, -39, 12456}}});
+
+        assertArrayObject(new Integer[]{37, -39, 12456});
     }
 
     @Test
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java
index 6b6b7f6..d9adde8 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java
@@ -416,18 +416,44 @@
         Assertions.assertEquals("java.lang.String", types1[0].getTypeName());
         Assertions.assertEquals("java.lang.String", types1[1].getTypeName());
 
-        Type[] types2 = ReflectUtils.getReturnTypes(clazz.getMethod("getListFuture"));
-        Assertions.assertEquals("java.util.List", types2[0].getTypeName());
-        Assertions.assertEquals("java.util.List<java.lang.String>", types2[1].getTypeName());
+        Type[] types2 = ReflectUtils.getReturnTypes(clazz.getMethod("getT"));
+        Assertions.assertEquals("java.lang.String", types2[0].getTypeName());
+        Assertions.assertEquals("T", types2[1].getTypeName());
+
+        Type[] types3 = ReflectUtils.getReturnTypes(clazz.getMethod("getS"));
+        Assertions.assertEquals("java.lang.Object", types3[0].getTypeName());
+        Assertions.assertEquals("S", types3[1].getTypeName());
+
+        Type[] types4 = ReflectUtils.getReturnTypes(clazz.getMethod("getListFuture"));
+        Assertions.assertEquals("java.util.List", types4[0].getTypeName());
+        Assertions.assertEquals("java.util.List<java.lang.String>", types4[1].getTypeName());
+
+        Type[] types5 = ReflectUtils.getReturnTypes(clazz.getMethod("getGenericWithUpperFuture"));
+        // T extends String, the first arg should be the upper bound of param
+        Assertions.assertEquals("java.lang.String", types5[0].getTypeName());
+        Assertions.assertEquals("T", types5[1].getTypeName());
+
+        Type[] types6 = ReflectUtils.getReturnTypes(clazz.getMethod("getGenericFuture"));
+        // default upper bound is Object
+        Assertions.assertEquals("java.lang.Object", types6[0].getTypeName());
+        Assertions.assertEquals("S", types6[1].getTypeName());
     }
 
-    public interface TypeClass {
+    public interface TypeClass<T extends String, S> {
 
         CompletableFuture<String> getFuture();
 
         String getString();
 
+        T getT();
+
+        S getS();
+
         CompletableFuture<List<String>> getListFuture();
+
+        CompletableFuture<T> getGenericWithUpperFuture();
+
+        CompletableFuture<S> getGenericFuture();
     }
 
     public static class EmptyClass {
diff --git a/dubbo-compatible/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/FutureAdapter.java b/dubbo-compatible/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/FutureAdapter.java
index 29cc14a..b2bba70 100644
--- a/dubbo-compatible/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/FutureAdapter.java
+++ b/dubbo-compatible/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/FutureAdapter.java
@@ -66,9 +66,7 @@
             public Object get() throws RemotingException {
                 try {
                     return future.get();
-                } catch (InterruptedException e) {
-                    throw new RemotingException(e);
-                } catch (ExecutionException e) {
+                } catch (InterruptedException | ExecutionException e) {
                     throw new RemotingException(e);
                 }
             }
@@ -77,11 +75,7 @@
             public Object get(int timeoutInMillis) throws RemotingException {
                 try {
                     return future.get(timeoutInMillis, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    throw new RemotingException(e);
-                } catch (ExecutionException e) {
-                    throw new RemotingException(e);
-                } catch (TimeoutException e) {
+                } catch (InterruptedException | TimeoutException | ExecutionException e) {
                     throw new RemotingException(e);
                 }
             }
diff --git a/dubbo-compatible/src/test/java/org/apache/dubbo/generic/GenericServiceTest.java b/dubbo-compatible/src/test/java/org/apache/dubbo/generic/GenericServiceTest.java
index ec9a08e..7b012d4 100644
--- a/dubbo-compatible/src/test/java/org/apache/dubbo/generic/GenericServiceTest.java
+++ b/dubbo-compatible/src/test/java/org/apache/dubbo/generic/GenericServiceTest.java
@@ -18,6 +18,7 @@
 package org.apache.dubbo.generic;
 
 
+import com.alibaba.dubbo.config.ReferenceConfig;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
@@ -89,6 +90,29 @@
     }
 
     @Test
+    public void testGenericCompatible() {
+        DemoService server = new DemoServiceImpl();
+        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        URL url = URL.valueOf("dubbo://127.0.0.1:5342/" + DemoService.class.getName() + "?version=1.0.0&generic=true$timeout=3000");
+        Exporter<DemoService> exporter = protocol.export(proxyFactory.getInvoker(server, DemoService.class, url));
+
+        // simulate normal invoke
+        ReferenceConfig<com.alibaba.dubbo.rpc.service.GenericService> oldReferenceConfig = new ReferenceConfig<>();
+        oldReferenceConfig.setGeneric(true);
+        oldReferenceConfig.setInterface(DemoService.class.getName());
+        oldReferenceConfig.checkAndUpdateSubConfigs();
+        Invoker invoker = protocol.refer(oldReferenceConfig.getInterfaceClass(), url);
+        com.alibaba.dubbo.rpc.service.GenericService client = (com.alibaba.dubbo.rpc.service.GenericService) proxyFactory.getProxy(invoker, true);
+
+        Object result = client.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"haha"});
+        Assertions.assertEquals("hello haha", result);
+
+        invoker.destroy();
+        exporter.unexport();
+    }
+
+    @Test
     public void testGenericComplexCompute4FullServiceMetadata() {
         DemoService server = new DemoServiceImpl();
         ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
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 6445588..cbb15ac 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
@@ -424,11 +424,6 @@
             throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
         }
         completeCompoundConfigs(consumer);
-        if (consumer != null) {
-            if (StringUtils.isEmpty(registryIds)) {
-                setRegistryIds(consumer.getRegistryIds());
-            }
-        }
         // get consumer's global configuration
         checkDefault();
 
@@ -453,12 +448,8 @@
             checkInterfaceAndMethods(interfaceClass, getMethods());
         }
 
-        //init serivceMetadata
-        serviceMetadata.setVersion(getVersion());
-        serviceMetadata.setGroup(getGroup());
-        serviceMetadata.setDefaultGroup(getGroup());
+        initServiceMetadata(consumer);
         serviceMetadata.setServiceType(getActualInterface());
-        serviceMetadata.setServiceInterfaceName(interfaceName);
         // TODO, uncomment this line once service key is unified
         serviceMetadata.setServiceKey(URL.buildKey(interfaceName, group, version));
 
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 8314842..e95fa08 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
@@ -189,12 +189,8 @@
 
         checkAndUpdateSubConfigs();
 
-        //init serviceMetadata
-        serviceMetadata.setVersion(getVersion());
-        serviceMetadata.setGroup(getGroup());
-        serviceMetadata.setDefaultGroup(getGroup());
+        initServiceMetadata(provider);
         serviceMetadata.setServiceType(getInterfaceClass());
-        serviceMetadata.setServiceInterfaceName(getInterface());
         serviceMetadata.setTarget(getRef());
 
         if (!shouldExport()) {
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
index f8f1bc9..4983dfe 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
@@ -1180,11 +1180,18 @@
 
     private void doRegisterServiceInstance(ServiceInstance serviceInstance) {
         //FIXME
+        if (logger.isInfoEnabled()) {
+            logger.info("Start publishing metadata to remote center, this only makes sense for applications enabled remote metadata center.");
+        }
         publishMetadataToRemote(serviceInstance);
 
+        logger.info("Start registering instance address to registry.");
         getServiceDiscoveries().forEach(serviceDiscovery ->
         {
             calInstanceRevision(serviceDiscovery, serviceInstance);
+            if (logger.isDebugEnabled()) {
+                logger.info("Start registering instance address to registry" + serviceDiscovery.getUrl() + ", instance " + serviceInstance);
+            }
             // register metadata
             serviceDiscovery.register(serviceInstance);
         });
@@ -1262,7 +1269,7 @@
                     destroyRegistries();
                     DubboShutdownHook.destroyProtocols();
                     destroyServiceDiscoveries();
-
+                    destroyExecutorRepository();
                     clear();
                     shutdown();
                     release();
@@ -1275,6 +1282,10 @@
         }
     }
 
+    private void destroyExecutorRepository() {
+        ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension().destroyAll();
+    }
+
     private void destroyRegistries() {
         AbstractRegistryFactory.destroyAll();
     }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
index d7d60fc..fdb011f 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
@@ -20,8 +20,6 @@
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 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;
@@ -31,11 +29,10 @@
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import static java.util.Collections.emptyList;
-import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 
 /**
  * {@link MetadataServiceExporter} implementation based on {@link ConfigManager Dubbo configurations}, the clients
@@ -77,7 +74,6 @@
             serviceConfig.setRef(metadataService);
             serviceConfig.setGroup(getApplicationConfig().getName());
             serviceConfig.setVersion(metadataService.version());
-            serviceConfig.setMethods(generateMethodConfig());
 
             // export
             serviceConfig.export();
@@ -97,28 +93,6 @@
         return this;
     }
 
-    /**
-     * Generate Method Config for Service Discovery Metadata <p/>
-     * <p>
-     * Make {@link MetadataService} support argument callback,
-     * used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
-     * metadata change event
-     *
-     * @since 3.0
-     */
-    private List<MethodConfig> generateMethodConfig() {
-        MethodConfig methodConfig = new MethodConfig();
-        methodConfig.setName("getAndListenServiceDiscoveryMetadata");
-
-        ArgumentConfig argumentConfig = new ArgumentConfig();
-        argumentConfig.setIndex(1);
-        argumentConfig.setCallback(true);
-
-        methodConfig.setArguments(Collections.singletonList(argumentConfig));
-
-        return Collections.singletonList(methodConfig);
-    }
-
     @Override
     public ConfigurableMetadataServiceExporter unexport() {
         if (isExported()) {
@@ -146,27 +120,10 @@
 
     private ProtocolConfig generateMetadataProtocol() {
         ProtocolConfig defaultProtocol = new ProtocolConfig();
-        Integer port = getApplicationConfig().getMetadataServicePort();
-
-        if (port == null || port < -1) {
-            if (logger.isInfoEnabled()) {
-                logger.info("Metadata Service Port hasn't been set. " +
-                        "Use default protocol defined in protocols.");
-            }
-            List<ProtocolConfig> defaultProtocols = ApplicationModel.getConfigManager().getDefaultProtocols();
-
-            if (defaultProtocols.isEmpty()) {
-                defaultProtocol.setName(DUBBO_PROTOCOL);
-                defaultProtocol.setPort(-1);
-            } else {
-                return defaultProtocols.get(0);
-            }
-
-        } else {
-            defaultProtocol.setName(DUBBO_PROTOCOL);
-            defaultProtocol.setPort(port);
-        }
-
+        defaultProtocol.setName(DUBBO);
+        // defaultProtocol.setHost() ?
+        // auto-increment port
+        defaultProtocol.setPort(-1);
         return defaultProtocol;
     }
 }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
index a62e9b2..5f3bd1c 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
+import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -88,9 +89,10 @@
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_DUPLICATE_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.DUBBO_PUBLISH_INTERFACE_DEFAULT_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PUBLISH_INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_PROTOCOL;
 import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY;
@@ -220,7 +222,7 @@
             if (provider) {
                 // for registries enabled service discovery, automatically register interface compatible addresses.
                 if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())
-                        && registryURL.getParameter(REGISTRY_DUPLICATE_KEY, false)
+                        && registryURL.getParameter(REGISTRY_PUBLISH_INTERFACE_KEY, ConfigurationUtils.getDynamicGlobalConfiguration().getBoolean(DUBBO_PUBLISH_INTERFACE_DEFAULT_KEY, false))
                         && registryNotExists(registryURL, registryList, REGISTRY_PROTOCOL)) {
                     URL interfaceCompatibleRegistryURL = URLBuilder.from(registryURL)
                             .setProtocol(REGISTRY_PROTOCOL)
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
index db73041..3d90c3b 100644
--- a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
@@ -1,2 +1 @@
-service-mapping=org.apache.dubbo.config.event.listener.ServiceNameMappingListener
-config-logging=org.apache.dubbo.config.event.listener.LoggingEventListener
\ No newline at end of file
+config-logging=org.apache.dubbo.config.event.listener.LoggingEventListener
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
index 1b843b6..2bda3f2 100644
--- a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
@@ -1,3 +1,2 @@
 # since 2.7.8
 local = org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
-remote = org.apache.dubbo.config.metadata.RemoteMetadataServiceExporter
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
index 4d157ff..95c7a0b 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
@@ -160,6 +160,7 @@
 
         assertThat(service2.getExportedUrls(), hasSize(1));
         assertEquals(2, TestProxyFactory.count); // local injvm and registry protocol, so expected is 2
+        TestProxyFactory.count = 0;
     }
 
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/rest/UserService.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/rest/UserService.java
index fa5b7ae..6c32912 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/rest/UserService.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/rest/UserService.java
@@ -19,7 +19,7 @@
 package org.apache.dubbo.config.bootstrap.rest;
 
 
-import org.apache.dubbo.rpc.protocol.rest.support.ContentType;
+//import org.apache.dubbo.rpc.protocol.rest.support.ContentType;
 
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -29,12 +29,11 @@
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 
 @Path("users")
 @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
-@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
+//@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
 @Api(value = "UserService")
 public interface UserService {
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/InvokerSideConfigUrlTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/InvokerSideConfigUrlTest.java
index c380963..1850b90 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/InvokerSideConfigUrlTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/InvokerSideConfigUrlTest.java
@@ -151,7 +151,7 @@
         verifyInvokerUrlGeneration(consumerConf, consumerConfTable);
     }
 
-    @Test
+    //@Test
     public void refConfUrlTest() {
         verifyInvokerUrlGeneration(refConf, refConfTable);
     }
diff --git a/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.RegistryFactory b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.RegistryFactory
index 7b8cf68..ded0832 100644
--- a/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.RegistryFactory
+++ b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.RegistryFactory
@@ -1,2 +1,3 @@
 mockregistry=org.apache.dubbo.config.mock.MockRegistryFactory
 mockprotocol2=org.apache.dubbo.config.mock.MockRegistryFactory2
+test=org.apache.dubbo.config.mock.MockRegistryFactory
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessor.java
index b1c9a30..4942ed7 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessor.java
@@ -24,7 +24,6 @@
 import org.apache.dubbo.config.annotation.Method;
 import org.apache.dubbo.config.annotation.Service;
 import org.apache.dubbo.config.spring.ServiceBean;
-import org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener;
 import org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner;
 import org.apache.dubbo.config.spring.schema.AnnotationBeanDefinitionParser;
 
@@ -68,7 +67,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import static com.alibaba.spring.util.BeanRegistrar.registerInfrastructureBean;
 import static com.alibaba.spring.util.ObjectUtils.of;
 import static java.util.Arrays.asList;
 import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create;
@@ -125,9 +123,6 @@
     @Override
     public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
 
-        // @since 2.7.5
-        registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME, DubboBootstrapApplicationListener.class);
-
         Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
 
         if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
diff --git a/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
index c54d103..60112b9 100644
--- a/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
+++ b/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
@@ -1,123 +1,123 @@
-/*
- * 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.configcenter.consul;
-
-import org.apache.dubbo.common.URL;
-
-import com.google.common.net.HostAndPort;
-import com.orbitz.consul.Consul;
-import com.orbitz.consul.KeyValueClient;
-import com.orbitz.consul.cache.KVCache;
-import com.orbitz.consul.model.kv.Value;
-import com.pszymczyk.consul.ConsulProcess;
-import com.pszymczyk.consul.ConsulStarterBuilder;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.TreeSet;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- *
- */
-public class ConsulDynamicConfigurationTest {
-
-    private static ConsulProcess consul;
-    private static URL configCenterUrl;
-    private static ConsulDynamicConfiguration configuration;
-
-    private static Consul client;
-    private static KeyValueClient kvClient;
-
-    @BeforeAll
-    public static void setUp() throws Exception {
-        consul = ConsulStarterBuilder.consulStarter()
-                .build()
-                .start();
-        configCenterUrl = URL.valueOf("consul://127.0.0.1:" + consul.getHttpPort());
-
-        configuration = new ConsulDynamicConfiguration(configCenterUrl);
-        client = Consul.builder().withHostAndPort(HostAndPort.fromParts("127.0.0.1", consul.getHttpPort())).build();
-        kvClient = client.keyValueClient();
-    }
-
-    @AfterAll
-    public static void tearDown() throws Exception {
-        consul.close();
-        configuration.close();
-    }
-
-    @Test
-    public void testGetConfig() {
-        kvClient.putValue("/dubbo/config/dubbo/foo", "bar");
-        // test equals
-        assertEquals("bar", configuration.getConfig("foo", "dubbo"));
-        // test does not block
-        assertEquals("bar", configuration.getConfig("foo", "dubbo"));
-        Assertions.assertNull(configuration.getConfig("not-exist", "dubbo"));
-    }
-
-    @Test
-    public void testPublishConfig() {
-        configuration.publishConfig("value", "metadata", "1");
-        // test equals
-        assertEquals("1", configuration.getConfig("value", "/metadata"));
-        assertEquals("1", kvClient.getValueAsString("/dubbo/config/metadata/value").get());
-    }
-
-    @Test
-    public void testAddListener() {
-        KVCache cache = KVCache.newCache(kvClient, "/dubbo/config/dubbo/foo");
-        cache.addListener(newValues -> {
-            // Cache notifies all paths with "foo" the root path
-            // If you want to watch only "foo" value, you must filter other paths
-            Optional<Value> newValue = newValues.values().stream()
-                    .filter(value -> value.getKey().equals("foo"))
-                    .findAny();
-
-            newValue.ifPresent(value -> {
-                // Values are encoded in key/value store, decode it if needed
-                Optional<String> decodedValue = newValue.get().getValueAsString();
-                decodedValue.ifPresent(v -> System.out.println(String.format("Value is: %s", v))); //prints "bar"
-            });
-        });
-        cache.start();
-
-        kvClient.putValue("/dubbo/config/dubbo/foo", "new-value");
-        kvClient.putValue("/dubbo/config/dubbo/foo/sub", "sub-value");
-        kvClient.putValue("/dubbo/config/dubbo/foo/sub2", "sub-value2");
-        kvClient.putValue("/dubbo/config/foo", "parent-value");
-
-        System.out.println(kvClient.getKeys("/dubbo/config/dubbo/foo"));
-        System.out.println(kvClient.getKeys("/dubbo/config"));
-        System.out.println(kvClient.getValues("/dubbo/config/dubbo/foo"));
-    }
-
-    @Test
-    public void testGetConfigKeys() {
-        configuration.publishConfig("v1", "metadata", "1");
-        configuration.publishConfig("v2", "metadata", "2");
-        configuration.publishConfig("v3", "metadata", "3");
-        // test equals
-        assertEquals(new TreeSet(Arrays.asList("v1", "v2", "v3")), configuration.getConfigKeys("metadata"));
-    }
-}
+///*
+// * 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.configcenter.consul;
+//
+//import org.apache.dubbo.common.URL;
+//
+//import com.google.common.net.HostAndPort;
+//import com.orbitz.consul.Consul;
+//import com.orbitz.consul.KeyValueClient;
+//import com.orbitz.consul.cache.KVCache;
+//import com.orbitz.consul.model.kv.Value;
+//import com.pszymczyk.consul.ConsulProcess;
+//import com.pszymczyk.consul.ConsulStarterBuilder;
+//import org.junit.jupiter.api.AfterAll;
+//import org.junit.jupiter.api.Assertions;
+//import org.junit.jupiter.api.BeforeAll;
+//import org.junit.jupiter.api.Test;
+//
+//import java.util.Arrays;
+//import java.util.Optional;
+//import java.util.TreeSet;
+//
+//import static org.junit.jupiter.api.Assertions.assertEquals;
+//
+///**
+// *
+// */
+//public class ConsulDynamicConfigurationTest {
+//
+//    private static ConsulProcess consul;
+//    private static URL configCenterUrl;
+//    private static ConsulDynamicConfiguration configuration;
+//
+//    private static Consul client;
+//    private static KeyValueClient kvClient;
+//
+//    @BeforeAll
+//    public static void setUp() throws Exception {
+//        consul = ConsulStarterBuilder.consulStarter()
+//                .build()
+//                .start();
+//        configCenterUrl = URL.valueOf("consul://127.0.0.1:" + consul.getHttpPort());
+//
+//        configuration = new ConsulDynamicConfiguration(configCenterUrl);
+//        client = Consul.builder().withHostAndPort(HostAndPort.fromParts("127.0.0.1", consul.getHttpPort())).build();
+//        kvClient = client.keyValueClient();
+//    }
+//
+//    @AfterAll
+//    public static void tearDown() throws Exception {
+//        consul.close();
+//        configuration.close();
+//    }
+//
+//    @Test
+//    public void testGetConfig() {
+//        kvClient.putValue("/dubbo/config/dubbo/foo", "bar");
+//        // test equals
+//        assertEquals("bar", configuration.getConfig("foo", "dubbo"));
+//        // test does not block
+//        assertEquals("bar", configuration.getConfig("foo", "dubbo"));
+//        Assertions.assertNull(configuration.getConfig("not-exist", "dubbo"));
+//    }
+//
+//    @Test
+//    public void testPublishConfig() {
+//        configuration.publishConfig("value", "metadata", "1");
+//        // test equals
+//        assertEquals("1", configuration.getConfig("value", "/metadata"));
+//        assertEquals("1", kvClient.getValueAsString("/dubbo/config/metadata/value").get());
+//    }
+//
+//    @Test
+//    public void testAddListener() {
+//        KVCache cache = KVCache.newCache(kvClient, "/dubbo/config/dubbo/foo");
+//        cache.addListener(newValues -> {
+//            // Cache notifies all paths with "foo" the root path
+//            // If you want to watch only "foo" value, you must filter other paths
+//            Optional<Value> newValue = newValues.values().stream()
+//                    .filter(value -> value.getKey().equals("foo"))
+//                    .findAny();
+//
+//            newValue.ifPresent(value -> {
+//                // Values are encoded in key/value store, decode it if needed
+//                Optional<String> decodedValue = newValue.get().getValueAsString();
+//                decodedValue.ifPresent(v -> System.out.println(String.format("Value is: %s", v))); //prints "bar"
+//            });
+//        });
+//        cache.start();
+//
+//        kvClient.putValue("/dubbo/config/dubbo/foo", "new-value");
+//        kvClient.putValue("/dubbo/config/dubbo/foo/sub", "sub-value");
+//        kvClient.putValue("/dubbo/config/dubbo/foo/sub2", "sub-value2");
+//        kvClient.putValue("/dubbo/config/foo", "parent-value");
+//
+//        System.out.println(kvClient.getKeys("/dubbo/config/dubbo/foo"));
+//        System.out.println(kvClient.getKeys("/dubbo/config"));
+//        System.out.println(kvClient.getValues("/dubbo/config/dubbo/foo"));
+//    }
+//
+//    @Test
+//    public void testGetConfigKeys() {
+//        configuration.publishConfig("v1", "metadata", "1");
+//        configuration.publishConfig("v2", "metadata", "2");
+//        configuration.publishConfig("v3", "metadata", "3");
+//        // test equals
+//        assertEquals(new TreeSet(Arrays.asList("v1", "v2", "v3")), configuration.getConfigKeys("metadata"));
+//    }
+//}
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
index a96f843..1b0a823 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
@@ -74,7 +74,7 @@
      */
     @Override
     public String getInternalProperty(String key) {
-        return zkClient.getContent(key);
+        return zkClient.getContent(buildPathKey("",key));
     }
 
     @Override
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
index 6a03ac7..1e5a7d5 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
@@ -36,6 +36,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-failover</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-demo-interface</artifactId>
             <version>${project.parent.version}</version>
         </dependency>
@@ -87,5 +91,12 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
         </dependency>
+        <!-- The metadata center cannot be initialized without this dependency -->
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+            <version>1.9.12</version>
+        </dependency>
+
     </dependencies>
 </project>
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml
index 9225959..9c632b8 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml
@@ -1,39 +1,39 @@
-<?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.

-  -->

-<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

-       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"

-       xmlns="http://www.springframework.org/schema/beans"

-       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd

-       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

-

-    <dubbo:application name="demo-consumer">

-        <dubbo:parameter key="mapping-type" value="metadata"/>

-        <dubbo:parameter key="enable-auto-migration" value="true"/>

-    </dubbo:application>

-

-    <!--    <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>-->

-

-    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

-

-    <dubbo:reference provided-by="demo-provider" id="demoService" check="false"

-                     interface="org.apache.dubbo.demo.DemoService"/>

-

-    <dubbo:reference provided-by="demo-provider" version="1.0.0" group="greeting" id="greetingService" check="false"

-                     interface="org.apache.dubbo.demo.GreetingService"/>

-

-</beans>

+<?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.
+  -->
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
+
+    <dubbo:application name="demo-consumer" >
+        <dubbo:parameter key="mapping-type" value="metadata"/>
+        <dubbo:parameter key="enable-auto-migration" value="true"/>
+    </dubbo:application>
+
+    <dubbo:metadata-report address="zookeeper://127.0.1:2181"/>
+
+    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
+
+    <dubbo:reference id="demoService" check="true"
+                     interface="org.apache.dubbo.demo.DemoService"/>
+
+    <dubbo:reference version="1.0.0" group="greeting" id="greetingService" check="false"
+                     interface="org.apache.dubbo.demo.GreetingService"/>
+
+</beans>
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
index 65ddaba..438c542 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
@@ -72,6 +72,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-failover</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-rpc-dubbo</artifactId>
         </dependency>
         <dependency>
@@ -107,5 +111,13 @@
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
+
+        <!-- The metadata center cannot be initialized without this dependency -->
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+            <version>1.9.12</version>
+        </dependency>
+
     </dependencies>
 </project>
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml
index 0628180..05d6f8f 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml
@@ -21,7 +21,7 @@
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
 
-    <dubbo:application name="demo-provider">
+    <dubbo:application name="demo-provider" metadata-type="remote">
         <dubbo:parameter key="mapping-type" value="metadata"/>
     </dubbo:application>
 
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index 3b14893..8543170 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -153,7 +153,7 @@
 
         <jaxb_version>2.2.7</jaxb_version>
         <activation_version>1.2.0</activation_version>
-        <test_container_version>1.11.2</test_container_version>
+        <test_container_version>1.15.2</test_container_version>
         <etcd_launcher_version>0.5.3</etcd_launcher_version>
         <hessian_lite_version>3.2.8</hessian_lite_version>
         <swagger_version>1.5.19</swagger_version>
@@ -165,7 +165,7 @@
         <mortbay_jetty_version>6.1.26</mortbay_jetty_version>
         <portlet_version>2.0</portlet_version>
         <maven_flatten_version>1.1.0</maven_flatten_version>
-        <revision>2.7.9</revision>
+        <revision>2.7.10-SNAPSHOT</revision>
     </properties>
 
     <dependencyManagement>
diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
index 8a01603..fea1cb0 100644
--- a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
+++ b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
@@ -32,7 +32,7 @@
     <packaging>pom</packaging>
 
     <properties>
-        <revision>2.7.9</revision>
+        <revision>2.7.10-SNAPSHOT</revision>
         <maven_flatten_version>1.1.0</maven_flatten_version>
     </properties>
 
@@ -51,7 +51,7 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.curator</groupId>
-            <artifactId>curator-recipes</artifactId>
+            <artifactId>curator-x-discovery</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.zookeeper</groupId>
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/java/org/apache/dubbo/cache/support/expiring/ExpiringMap.java b/dubbo-filter/dubbo-filter-cache/src/main/java/org/apache/dubbo/cache/support/expiring/ExpiringMap.java
index 895f114..326b51e 100644
--- a/dubbo-filter/dubbo-filter-cache/src/main/java/org/apache/dubbo/cache/support/expiring/ExpiringMap.java
+++ b/dubbo-filter/dubbo-filter-cache/src/main/java/org/apache/dubbo/cache/support/expiring/ExpiringMap.java
@@ -137,6 +137,9 @@
 
     @Override
     public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
         return delegateMap.equals(obj);
     }
 
@@ -226,6 +229,9 @@
 
         @Override
         public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
             return value.equals(obj);
         }
 
@@ -282,10 +288,10 @@
 
         private void processExpires() {
             long timeNow = System.currentTimeMillis();
+            if (timeToLiveMillis <= 0) {
+                return;
+            }
             for (ExpiryObject o : delegateMap.values()) {
-                if (timeToLiveMillis <= 0) {
-                    continue;
-                }
                 long timeIdle = timeNow - o.getLastAccessTime();
                 if (timeIdle >= timeToLiveMillis) {
                     delegateMap.remove(o.getKey());
diff --git a/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory b/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory
index c849461..54a1dbf 100644
--- a/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory
+++ b/dubbo-filter/dubbo-filter-cache/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory
@@ -1,4 +1,5 @@
 threadlocal=org.apache.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory

 lru=org.apache.dubbo.cache.support.lru.LruCacheFactory

 jcache=org.apache.dubbo.cache.support.jcache.JCacheFactory

-expiring=org.apache.dubbo.cache.support.expiring.ExpiringCacheFactory
\ No newline at end of file
+expiring=org.apache.dubbo.cache.support.expiring.ExpiringCacheFactory

+lfu=org.apache.dubbo.cache.support.lfu.LfuCacheFactory

diff --git a/dubbo-filter/dubbo-filter-cache/src/test/java/org/apache/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java b/dubbo-filter/dubbo-filter-cache/src/test/java/org/apache/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java
index 23484c6..877faf4 100644
--- a/dubbo-filter/dubbo-filter-cache/src/test/java/org/apache/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java
+++ b/dubbo-filter/dubbo-filter-cache/src/test/java/org/apache/dubbo/cache/support/expiring/ExpiringCacheFactoryTest.java
@@ -19,18 +19,45 @@
 import org.apache.dubbo.cache.Cache;
 import org.apache.dubbo.cache.support.AbstractCacheFactory;
 import org.apache.dubbo.cache.support.AbstractCacheFactoryTest;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.RpcInvocation;
 import org.junit.jupiter.api.Test;
 
-import static org.hamcrest.core.Is.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 public class ExpiringCacheFactoryTest extends AbstractCacheFactoryTest {
     @Test
-    public void testLruCacheFactory() throws Exception {
+    public void testExpiringCacheFactory() throws Exception {
         Cache cache = super.constructCache();
         assertThat(cache instanceof ExpiringCache, is(true));
     }
 
+    @Test
+    public void testExpiringCacheGetExpired() throws Exception {
+        URL url = URL.valueOf("test://test:12/test?cache=expiring&cache.seconds=1&cache.interval=1");
+        AbstractCacheFactory cacheFactory = getCacheFactory();
+        Invocation invocation = new RpcInvocation();
+        Cache cache = cacheFactory.getCache(url, invocation);
+        cache.put("testKey", "testValue");
+        Thread.sleep(2100);
+        assertNull(cache.get("testKey"));
+    }
+
+    @Test
+    public void testExpiringCacheUnExpired() throws Exception {
+        URL url = URL.valueOf("test://test:12/test?cache=expiring&cache.seconds=0&cache.interval=1");
+        AbstractCacheFactory cacheFactory = getCacheFactory();
+        Invocation invocation = new RpcInvocation();
+        Cache cache = cacheFactory.getCache(url, invocation);
+        cache.put("testKey", "testValue");
+        Thread.sleep(1100);
+        assertNotNull(cache.get("testKey"));
+    }
+
     @Override
     protected AbstractCacheFactory getCacheFactory() {
         return new ExpiringCacheFactory();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
index 12d6665..13938ca 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
@@ -66,7 +66,7 @@
             dynamicConfiguration.publishConfig(key, ServiceNameMapping.buildGroup(serviceInterface, group, version, protocol), content);
             if (logger.isInfoEnabled()) {
                 logger.info(String.format("Dubbo service[%s] mapped to interface name[%s].",
-                        group, serviceInterface, group));
+                        group, serviceInterface));
             }
         });
     }
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 6fc6239..915b2cd 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
@@ -178,7 +178,7 @@
         private String path; // most of the time, path is the same with the interface name.
         private Map<String, String> params;
 
-        // params configuried on consumer side,
+        // params configured on consumer side,
         private transient Map<String, String> consumerParams;
         // cached method params
         private transient Map<String, Map<String, String>> methodParams;
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java
index a675eb3..1531c97 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java
@@ -159,8 +159,12 @@
 
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof RestMethodMetadata)) return false;
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof RestMethodMetadata)) {
+            return false;
+        }
         RestMethodMetadata that = (RestMethodMetadata) o;
         return Objects.equals(getMethod(), that.getMethod()) &&
                 Objects.equals(getRequest(), that.getRequest()) &&
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java
index 876b8a3..df396b0 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java
@@ -76,8 +76,12 @@
 
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof ServiceRestMetadata)) return false;
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ServiceRestMetadata)) {
+            return false;
+        }
         ServiceRestMetadata that = (ServiceRestMetadata) o;
         return Objects.equals(getServiceInterface(), that.getServiceInterface()) &&
                 Objects.equals(getVersion(), that.getVersion()) &&
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/pom.xml b/dubbo-metadata/dubbo-metadata-report-failover/pom.xml
new file mode 100644
index 0000000..18ed396
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/pom.xml
@@ -0,0 +1,38 @@
+<!--
+  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">
+    <parent>
+        <artifactId>dubbo-metadata</artifactId>
+        <groupId>org.apache.dubbo</groupId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-metadata-report-failover</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverCondition.java b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverCondition.java
new file mode 100644
index 0000000..512c34e
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverCondition.java
@@ -0,0 +1,51 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.SPI;
+
+@SPI("failover")
+public interface FailoverCondition {
+
+    /**
+     * Whether metadata should be reported.
+     *
+     * @param url registry url, eg: zookeeper://127.0.0.1:2181
+     * @return true store metadata to the specified URL.
+     */
+    boolean shouldRegister(URL url);
+
+    /**
+     * Whether metadata should be read from specified url.
+     *
+     * @param url registry url, eg: zookeeper://127.0.0.1:2181
+     * @return true read metadata from specified URL.
+     */
+    boolean shouldQuery(URL url);
+
+    /**
+     * Judge whether it is a local region or a local datacenter.
+     * <p>
+     * Allows the local region or datacenter to be read first.
+     *
+     * @param url
+     * @return
+     */
+    boolean isLocalDataCenter(URL url);
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReport.java
new file mode 100644
index 0000000..e6a423d
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReport.java
@@ -0,0 +1,581 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.RemotingConstants;
+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.metadata.MappingListener;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportFactory;
+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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_SPLIT_PATTERN;
+
+/**
+ * @author yiji@apache.org
+ */
+public class FailoverMetadataReport extends StrategyMetadataReport {
+
+    private static final Logger logger = LoggerFactory.getLogger(FailoverMetadataReport.class);
+
+    // proxy metadata report protocol, eg: zookeeper
+    private static final String PROTOCOL_KEY = "protocol";
+
+    private static final String CLUSTER_KEY = "clusters";
+
+    // A cluster may have multiple instances
+    private static final String HOST_KEY = "hosts";
+
+    private static final Pattern HOST_SPLIT_PATTERN = Pattern.compile("\\s*[|:]+\\s*");
+
+    // The metadata address of the agent.
+    private List<URL> failoverUrls;
+
+    // The metadata report instance.
+    private List<MetadataReportHolder> proxyReports;
+
+    // Local priority metadata center
+    private MetadataReportHolder localDataCenterReportHolder;
+
+    public FailoverMetadataReport(URL url) {
+        super(url);
+        this.failoverUrls = fetchBackupUrls();
+        this.proxyReports = buildProxyReports();
+    }
+
+    protected List<URL> fetchBackupUrls() {
+        String protocol = url.getParameter(PROTOCOL_KEY);
+        if (protocol == null || !ExtensionLoader.getExtensionLoader(MetadataReportFactory.class).hasExtension(protocol)) {
+            throw new IllegalArgumentException(
+                    "No '" + protocol
+                            + "' medata report extension found, please check if metadata report module dependencies are included.");
+        }
+
+        List<URL> urls = new ArrayList<>();
+
+        String clusters = this.url.getParameter(CLUSTER_KEY);
+        String backupHost = this.url.getParameter(HOST_KEY);
+        URL url = this.url.removeParameters(CLUSTER_KEY, HOST_KEY, PROTOCOL_KEY).setProtocol(protocol);
+
+        URL metadataURL = url;
+        if (backupHost != null && backupHost.length() > 0) {
+            metadataURL = metadataURL.addParameter(RemotingConstants.BACKUP_KEY, backupHost);
+        }
+        urls.add(metadataURL);
+
+        if (clusters != null && (clusters = clusters.trim()).length() > 0) {
+            String[] addresses = REGISTRY_SPLIT_PATTERN.split(clusters);
+            for (String address : addresses) {
+                /**
+                 * find multiple cluster hosts, supports multiple
+                 * metadata report center read and write operations.
+                 */
+                String[] hosts = COMMA_SPLIT_PATTERN.split(address);
+                if (hosts.length > 0) {
+                    String node = hosts[0];
+                    // contains user name and password with address ?
+                    String username = null, password = null;
+                    int index = node.indexOf("@");
+                    if (index > 0) {
+                        String[] authority = HOST_SPLIT_PATTERN.split(node.substring(0, index));
+                        username = authority[0];
+                        password = authority[1];
+                        node = node.substring(index + 1);
+                    }
+
+                    String[] hostInfo = HOST_SPLIT_PATTERN.split(node);
+                    String host = hostInfo[0];
+                    int port = Integer.parseInt(hostInfo[1]);
+                    URL clusterURL = new URL(protocol, username, password, host, port, url.getPath(), url.getParameters());
+                    /**
+                     * append backup address if required,
+                     * the same cluster may have more than one node.
+                     */
+                    if (hosts.length > 1) {
+                        StringBuilder buffer = new StringBuilder();
+                        for (int i = 1; i < hosts.length; i++) {
+                            if (i > 1) {
+                                buffer.append(",");
+                            }
+                            buffer.append(hosts[i]);
+                        }
+                        clusterURL = clusterURL.addParameters(RemotingConstants.BACKUP_KEY, buffer.toString());
+                    }
+                    urls.add(clusterURL);
+                }
+            }
+        }
+        return urls;
+    }
+
+    protected List<MetadataReportHolder> buildProxyReports() {
+        List<MetadataReportHolder> reports = new ArrayList<>();
+        if (this.failoverUrls != null && !this.failoverUrls.isEmpty()) {
+            ExtensionLoader<MetadataReportFactory> factoryLoader = ExtensionLoader.getExtensionLoader(MetadataReportFactory.class);
+            for (URL url : this.failoverUrls) {
+                try {
+                    MetadataReportHolder holder = new MetadataReportHolder(url,
+                            factoryLoader.getExtension(url.getProtocol()).getMetadataReport(url));
+                    reports.add(holder);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw new RuntimeException("Failed to create + '" + url.getProtocol() + "' metadata report extension instance", e);
+                    }
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to create + '" + url.getProtocol()
+                                + "' metadata report extension instance, check=false found.");
+                    }
+                }
+            }
+        }
+
+        Collections.shuffle(reports);
+
+        /**
+         * Select the local priority metadata cluster.
+         * In order to prevent clients from all connecting
+         * to the same cluster, random sorting has been done.
+         */
+        reports.forEach(holder -> {
+            if (isLocalDataCenter(holder.url)) {
+                this.localDataCenterReportHolder = holder;
+            }
+        });
+
+        return reports;
+    }
+
+    @Override
+    public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
+        this.proxyReports.forEach((holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.storeProviderMetadata(providerMetadataIdentifier, serviceDefinition);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to store provider metadata, register is false. url " + holder.url);
+                }
+            }
+        }));
+    }
+
+    @Override
+    public void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.storeConsumerMetadata(consumerMetadataIdentifier, serviceParameterMap);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to store consumer metadata, register is false. url " + holder.url);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.publishAppMetadata(identifier, metadataInfo);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to publish app metadata, register is false. url " + holder.url);
+                }
+            }
+        });
+    }
+
+    @Override
+    public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
+        /**
+         * Support local region or datacenter to read first,
+         * If current region or datacenter failed, it will be demoted to another region or datacenter.
+         */
+        MetadataReportHolder localReportHolder = this.localDataCenterReportHolder;
+        if (localReportHolder != null && shouldQuery(localReportHolder.url)) {
+            try {
+                String definition = localReportHolder.report.getServiceDefinition(metadataIdentifier);
+                if (definition != null && definition.length() > 0) {
+                    return definition;
+                }
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("Failed to get service definition from local metadata report center, url " + localReportHolder.url);
+                }
+            }
+        }
+
+        for (MetadataReportHolder holder : proxyReports) {
+            /**
+             * Skip the local region or datacenter read,
+             * which was queried already.
+             */
+            if (localReportHolder != null
+                    && holder.url == localReportHolder.url) {
+                continue;
+            }
+
+            if (shouldQuery(holder.url)) {
+                try {
+                    String definition = holder.report.getServiceDefinition(metadataIdentifier);
+                    if (definition != null && definition.length() > 0) {
+                        return definition;
+                    }
+                } catch (Exception e) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to get service definition from metadata report center, url " + holder.url);
+                    }
+                }
+            }
+
+            // should never happened.
+            if (logger.isInfoEnabled()) {
+                logger.info("Cancel to get service definition, should query is false. url " + holder.url);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
+        /**
+         * Support local region or datacenter to read first,
+         * If current region or datacenter failed, it will be demoted to another region or datacenter.
+         */
+        MetadataReportHolder localReportHolder = this.localDataCenterReportHolder;
+        if (localReportHolder != null && shouldQuery(localReportHolder.url)) {
+            try {
+                MetadataInfo metadataInfo = localReportHolder.report.getAppMetadata(identifier, instanceMetadata);
+                if (metadataInfo != null) {
+                    return metadataInfo;
+                }
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("Failed to get app metadata from local metadata report center, url " + localReportHolder.url);
+                }
+            }
+        }
+
+        for (MetadataReportHolder holder : proxyReports) {
+            /**
+             * Skip the local region or datacenter read,
+             * which was queried already.
+             */
+            if (localReportHolder != null
+                    && holder.url == localReportHolder.url) {
+                continue;
+            }
+
+            if (shouldQuery(holder.url)) {
+                try {
+                    MetadataInfo metadataInfo = holder.report.getAppMetadata(identifier, instanceMetadata);
+                    if (metadataInfo != null) {
+                        return metadataInfo;
+                    }
+                } catch (Exception e) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to get app metadata from metadata report center, url " + holder.url);
+                    }
+                }
+            }
+
+            // should never happened.
+            if (logger.isInfoEnabled()) {
+                logger.info("Cancel to get app metadata, should query is false. url " + holder.url);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
+        /**
+         * Support local region or datacenter to read first,
+         * If current region or datacenter failed, it will be demoted to another region or datacenter.
+         */
+        MetadataReportHolder localReportHolder = this.localDataCenterReportHolder;
+        if (localReportHolder != null && shouldQuery(localReportHolder.url)) {
+            try {
+                Set<String> appMapping = localReportHolder.report.getServiceAppMapping(serviceKey, listener, url);
+                if (appMapping != null && !appMapping.isEmpty()) {
+                    return appMapping;
+                }
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("Failed to get service mapping from local metadata report center, url " + localReportHolder.url);
+                }
+            }
+        }
+
+        for (MetadataReportHolder holder : proxyReports) {
+            /**
+             * Skip the local region or datacenter read,
+             * which was queried already.
+             */
+            if (localReportHolder != null
+                    && holder.url == localReportHolder.url) {
+                continue;
+            }
+
+            if (shouldQuery(holder.url)) {
+                try {
+                    Set<String> appMapping = holder.report.getServiceAppMapping(serviceKey, listener, url);
+                    if (appMapping != null && !appMapping.isEmpty()) {
+                        return appMapping;
+                    }
+                } catch (Exception e) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to get service mapping from metadata report center, url " + holder.url);
+                    }
+                }
+            }
+
+            // should never happened.
+            if (logger.isInfoEnabled()) {
+                logger.info("Cancel to get service mapping, should query is false. url " + holder.url);
+            }
+        }
+
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
+    public void registerServiceAppMapping(String serviceKey, String application, URL url) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.registerServiceAppMapping(serviceKey, application, url);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to register service app mapping, register is false. url " + holder.url);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.saveServiceMetadata(metadataIdentifier, url);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to register service app mapping, register is false. url " + holder.url);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.saveSubscribedData(subscriberMetadataIdentifier, urls);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            } else {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Cancel to register service app mapping, register is false. url " + holder.url);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        this.proxyReports.forEach(holder -> {
+            if (shouldRegister(holder.url)) {
+                try {
+                    holder.report.removeServiceMetadata(metadataIdentifier);
+                } catch (Exception e) {
+                    if (url.getParameter(CHECK_KEY, true)) {
+                        throw e;
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        /**
+         * Support local region or datacenter to read first,
+         * If current region or datacenter failed, it will be demoted to another region or datacenter.
+         */
+        MetadataReportHolder localReportHolder = this.localDataCenterReportHolder;
+        if (localReportHolder != null && shouldQuery(localReportHolder.url)) {
+            try {
+                List<String> exportedURLs = localReportHolder.report.getExportedURLs(metadataIdentifier);
+                if (exportedURLs != null && !exportedURLs.isEmpty()) {
+                    return exportedURLs;
+                }
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("Failed to get exported urls from local metadata report center, url " + localReportHolder.url);
+                }
+            }
+        }
+
+        for (MetadataReportHolder holder : proxyReports) {
+            /**
+             * Skip the local region or datacenter read,
+             * which was queried already.
+             */
+            if (localReportHolder != null
+                    && holder.url == localReportHolder.url) {
+                continue;
+            }
+
+            if (shouldQuery(holder.url)) {
+                try {
+                    List<String> exportedURLs = holder.report.getExportedURLs(metadataIdentifier);
+                    if (exportedURLs != null && !exportedURLs.isEmpty()) {
+                        return exportedURLs;
+                    }
+                } catch (Exception e) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to get exported urls from metadata report center, url " + holder.url);
+                    }
+                }
+            }
+
+            // should never happened.
+            if (logger.isInfoEnabled()) {
+                logger.info("Cancel to get exported urls, should query is false. url " + holder.url);
+            }
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    @Override
+    public List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+        /**
+         * Support local region or datacenter to read first,
+         * If current region or datacenter failed, it will be demoted to another region or datacenter.
+         */
+        MetadataReportHolder localReportHolder = this.localDataCenterReportHolder;
+        if (localReportHolder != null && shouldQuery(localReportHolder.url)) {
+            try {
+                List<String> subscribedURLs = localReportHolder.report.getSubscribedURLs(subscriberMetadataIdentifier);
+                if (subscribedURLs != null && !subscribedURLs.isEmpty()) {
+                    return subscribedURLs;
+                }
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn("Failed to get subscribed urls from local metadata report center, url " + localReportHolder.url);
+                }
+            }
+        }
+
+        for (MetadataReportHolder holder : proxyReports) {
+            /**
+             * Skip the local region or datacenter read,
+             * which was queried already.
+             */
+            if (localReportHolder != null
+                    && holder.url == localReportHolder.url) {
+                continue;
+            }
+
+            if (shouldQuery(holder.url)) {
+                try {
+                    List<String> subscribedURLs = holder.report.getSubscribedURLs(subscriberMetadataIdentifier);
+                    if (subscribedURLs != null && !subscribedURLs.isEmpty()) {
+                        return subscribedURLs;
+                    }
+                } catch (Exception e) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn("Failed to get subscribed urls from metadata report center, url " + holder.url);
+                    }
+                }
+            }
+
+            // should never happened.
+            if (logger.isInfoEnabled()) {
+                logger.info("Cancel to get subscribed urls, should query is false. url " + holder.url);
+            }
+        }
+
+        return Collections.EMPTY_LIST;
+    }
+
+    public List<MetadataReportHolder> getProxyReports() {
+        return proxyReports;
+    }
+
+    class MetadataReportHolder {
+
+        final URL            url;
+        final MetadataReport report;
+
+        public MetadataReportHolder(URL url, MetadataReport report) {
+            this.url = url;
+            this.report = report;
+        }
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportFactory.java
similarity index 69%
copy from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
copy to dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportFactory.java
index 7bef1f5..b47c14c 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportFactory.java
@@ -14,15 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+package org.apache.dubbo.metadata.store.failover;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class FailoverMetadataReportFactory extends AbstractMetadataReportFactory {
+
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    protected MetadataReport createMetadataReport(URL url) {
+        return new FailoverMetadataReport(url);
     }
-}
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/StrategyMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/StrategyMetadataReport.java
new file mode 100644
index 0000000..ef7636c
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/main/java/org/apache/dubbo/metadata/store/failover/StrategyMetadataReport.java
@@ -0,0 +1,88 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.metadata.report.MetadataReport;
+
+/**
+ * @author yiji@apache.org
+ */
+public abstract class StrategyMetadataReport implements MetadataReport {
+
+    // failover configured url, eg: failover://127.0.1:2181?backup=localhost:2181|localhost:2181
+    protected URL url;
+
+    protected static final String STRATEGY_KEY = "strategy";
+
+    // proxy metadata report strategy, used to decide whether to write or read metadata
+    protected FailoverCondition strategy;
+
+    protected ExtensionLoader<FailoverCondition> failoverLoader = ExtensionLoader.getExtensionLoader(FailoverCondition.class);
+
+    public StrategyMetadataReport(URL url) {
+        if (url == null) {
+            throw new IllegalArgumentException("url is required.");
+        }
+        this.url = url;
+        createFailoverStrategy(url);
+    }
+
+    protected void createFailoverStrategy(URL url) {
+        String strategy = url.getParameter(STRATEGY_KEY);
+        if (strategy != null) {
+            if (!failoverLoader.hasExtension(strategy)) {
+                throw new IllegalArgumentException("No '" + strategy + "' failover condition extension found.");
+            }
+            this.strategy = failoverLoader.getExtension(strategy);
+        }
+    }
+
+    /**
+     * Whether metadata should be reported.
+     *
+     * @param url registry url, eg: zookeeper://127.0.0.1:2181
+     * @return true store metadata to the specified URL.
+     */
+    protected boolean shouldRegister(URL url) {
+        return this.strategy == null || this.strategy.shouldRegister(url);
+    }
+
+    /**
+     * Whether metadata should be read from specified url.
+     *
+     * @param url registry url, eg: zookeeper://127.0.0.1:2181
+     * @return true read metadata from specified URL.
+     */
+    protected boolean shouldQuery(URL url) {
+        return this.strategy == null || this.strategy.shouldQuery(url);
+    }
+
+    /**
+     * Judge whether it is a local region or a local datacenter.
+     * <p>
+     * Allows the local region or datacenter to be read first.
+     *
+     * @param url
+     * @return
+     */
+    protected boolean isLocalDataCenter(URL url) {
+        return this.strategy == null || this.strategy.isLocalDataCenter(url);
+    }
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory b/dubbo-metadata/dubbo-metadata-report-failover/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
new file mode 100644
index 0000000..e530c5e
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
@@ -0,0 +1 @@
+failover=org.apache.dubbo.metadata.store.failover.FailoverMetadataReportFactory
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportTest.java
new file mode 100644
index 0000000..c7e50a8
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/FailoverMetadataReportTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportFactory;
+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.AbstractMetadataReportFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+public class FailoverMetadataReportTest {
+
+    private ExtensionLoader<MetadataReportFactory> reportLoader = ExtensionLoader.getExtensionLoader(MetadataReportFactory.class);
+
+    private URL mockURL = URL.valueOf("failover://127.0.0.1:2181?clusters=localhost:3181&protocol=mock");
+
+    @AfterEach
+    void tearDown() {
+        clearFailoverReport();
+        clearFailoverFactory();
+    }
+
+    @Test
+    public void testReadWriteAllMetadataReport() {
+        URL url = mockURL.addParameter("strategy", "all");
+        FailoverMetadataReport report = getFailoverReport(url);
+        Assertions.assertNotNull(report.getProxyReports(), "metadata reports should not be null.");
+        Assertions.assertEquals(2, report.getProxyReports().size(),
+                "expect 2 metadata report, actual " + report.getProxyReports().size());
+
+        MetadataIdentifier identifier = new MetadataIdentifier("helloService", null, null, null, "test");
+        ServiceDefinition definition = new ServiceDefinition();
+        definition.setCanonicalName("helloService");
+        report.storeProviderMetadata(identifier, definition);
+        Assertions.assertNotNull(report.getServiceDefinition(identifier));
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertNotNull(holder.report.getServiceDefinition(identifier));
+        }
+
+        HashMap parameterMap = new HashMap();
+        report.storeConsumerMetadata(identifier, parameterMap);
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertEquals(parameterMap, ((MockMetadataReport) holder.report).consumerMetadata.get(identifier));
+        }
+
+        SubscriberMetadataIdentifier subscribeIdentifier = new SubscriberMetadataIdentifier("test", "1.0");
+        MetadataInfo metadataInfo = new MetadataInfo(subscribeIdentifier.getApplication(), subscribeIdentifier.getRevision(), null);
+        report.publishAppMetadata(subscribeIdentifier, metadataInfo);
+        Assertions.assertEquals(metadataInfo, report.getAppMetadata(subscribeIdentifier, null));
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertEquals(metadataInfo, holder.report.getAppMetadata(subscribeIdentifier, null));
+        }
+
+        report.registerServiceAppMapping("helloService", "test", null);
+        Set<String> appNames = report.getServiceAppMapping("helloService", null, null);
+        Assertions.assertEquals(appNames, report.getServiceAppMapping("helloService", null, null));
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertEquals(appNames, holder.report.getServiceAppMapping("helloService", null, null));
+        }
+
+        ServiceMetadataIdentifier serviceIdentifier = new ServiceMetadataIdentifier("helloService", null, null, null, "1.0", "dubbo");
+        report.saveServiceMetadata(serviceIdentifier, url);
+        Assertions.assertNotNull(report.getExportedURLs(serviceIdentifier));
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertNotNull(holder.report.getExportedURLs(serviceIdentifier));
+        }
+
+        report.saveSubscribedData(subscribeIdentifier, new HashSet<>());
+        Assertions.assertNotNull(report.getSubscribedURLs(subscribeIdentifier));
+        // assert all metadata report write already.
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            Assertions.assertNotNull(holder.report.getSubscribedURLs(subscribeIdentifier));
+        }
+    }
+
+    @Test
+    public void testLocalDataCenterMetadataReport() {
+        URL url = mockURL.addParameter("strategy", "local");
+        FailoverMetadataReport report = getFailoverReport(url);
+        Assertions.assertNotNull(report.getProxyReports(), "metadata reports should not be null.");
+        Assertions.assertEquals(2, report.getProxyReports().size(),
+                "expect 2 metadata report, actual " + report.getProxyReports().size());
+
+        MetadataReport localReport = null, failoverReport = null;
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            if (holder.url.getBackupAddress().contains(url.getAddress())) {
+                localReport = holder.report;
+            } else {
+                failoverReport = holder.report;
+            }
+        }
+        Assertions.assertNotNull(localReport);
+        Assertions.assertNotNull(failoverReport);
+
+        MetadataIdentifier identifier = new MetadataIdentifier("helloService", null, null, null, "test");
+        ServiceDefinition definition = new ServiceDefinition();
+        definition.setCanonicalName("helloService");
+        report.storeProviderMetadata(identifier, definition);
+
+        // assert local metadata report write already.
+        Assertions.assertNotNull(report.getServiceDefinition(identifier));
+        Assertions.assertNotNull(localReport.getServiceDefinition(identifier));
+        Assertions.assertNull(failoverReport.getServiceDefinition(identifier));
+
+        HashMap parameterMap = new HashMap();
+        report.storeConsumerMetadata(identifier, parameterMap);
+        // assert local metadata report write already.
+        Assertions.assertEquals(parameterMap, ((MockMetadataReport) localReport).consumerMetadata.get(identifier));
+        Assertions.assertNotEquals(parameterMap, ((MockMetadataReport) failoverReport).consumerMetadata.get(identifier));
+
+        SubscriberMetadataIdentifier subscribeIdentifier = new SubscriberMetadataIdentifier("test", "1.0");
+        MetadataInfo metadataInfo = new MetadataInfo(subscribeIdentifier.getApplication(), subscribeIdentifier.getRevision(), null);
+        report.publishAppMetadata(subscribeIdentifier, metadataInfo);
+        // assert all metadata report write already.
+        Assertions.assertEquals(metadataInfo, report.getAppMetadata(subscribeIdentifier, null));
+        Assertions.assertEquals(metadataInfo, localReport.getAppMetadata(subscribeIdentifier, null));
+        Assertions.assertNotEquals(metadataInfo, failoverReport.getAppMetadata(subscribeIdentifier, null));
+
+        report.registerServiceAppMapping("helloService", "test", null);
+        Set<String> appNames = report.getServiceAppMapping("helloService", null, null);
+
+        // assert local metadata report write already.
+        Assertions.assertEquals(appNames, report.getServiceAppMapping("helloService", null, null));
+        Assertions.assertEquals(appNames, localReport.getServiceAppMapping("helloService", null, null));
+        Assertions.assertNotEquals(appNames, failoverReport.getServiceAppMapping("helloService", null, null));
+
+        ServiceMetadataIdentifier serviceIdentifier = new ServiceMetadataIdentifier("helloService", null, null, null, "1.0", "dubbo");
+        report.saveServiceMetadata(serviceIdentifier, url);
+        // assert local metadata report write already.
+        Assertions.assertNotNull(report.getExportedURLs(serviceIdentifier));
+        Assertions.assertNotNull(localReport.getExportedURLs(serviceIdentifier));
+        Assertions.assertNull(failoverReport.getExportedURLs(serviceIdentifier));
+
+        Set<String> urls = new HashSet<>();
+        urls.add(url.toFullString());
+        report.saveSubscribedData(subscribeIdentifier, urls);
+        // assert local metadata report write already.
+        Assertions.assertEquals(new ArrayList<>(urls), report.getSubscribedURLs(subscribeIdentifier));
+        Assertions.assertEquals(new ArrayList<>(urls), localReport.getSubscribedURLs(subscribeIdentifier));
+        Assertions.assertNotEquals(new ArrayList<>(urls), failoverReport.getSubscribedURLs(subscribeIdentifier));
+    }
+
+    protected FailoverMetadataReport getFailoverReport(URL url) {
+        MetadataReportFactory reportFactory = reportLoader.getExtension(url.getProtocol());
+        Assertions.assertTrue(reportFactory instanceof FailoverMetadataReportFactory,
+                "expect " + FailoverMetadataReportFactory.class.getName() + " instance type, "
+                        + "actual " + reportFactory.getClass().getName() + " instance type");
+
+        MetadataReport report = reportFactory.getMetadataReport(url);
+        Assertions.assertTrue(report instanceof FailoverMetadataReport,
+                "expect " + FailoverMetadataReport.class.getName() + " instance type, "
+                        + "actual " + report.getClass().getName() + " instance type");
+
+        FailoverMetadataReport failover = (FailoverMetadataReport) report;
+        return failover;
+    }
+
+    private void clearFailoverReport() {
+        FailoverMetadataReport report = getFailoverReport(mockURL);
+        for (FailoverMetadataReport.MetadataReportHolder holder : report.getProxyReports()) {
+            if (holder.report instanceof MockMetadataReport) {
+                ((MockMetadataReport) (holder.report)).reset();
+            }
+        }
+    }
+
+    private void clearFailoverFactory() {
+        MetadataReportFactory factory = reportLoader.getExtension(mockURL.getProtocol());
+        try {
+            Field reportCache = AbstractMetadataReportFactory.class.getDeclaredField("SERVICE_STORE_MAP");
+            if (!reportCache.isAccessible()) {
+                reportCache.setAccessible(true);
+            }
+            Map<String, MetadataReport> serviceStore = (Map<String, MetadataReport>) reportCache.get(factory);
+            if (serviceStore != null) {
+                for (Iterator<Map.Entry<String, MetadataReport>> iterator = serviceStore.entrySet().iterator(); iterator.hasNext(); ) {
+                    Map.Entry<String, MetadataReport> entry = iterator.next();
+                    if (entry.getKey().startsWith(mockURL.getProtocol())) {
+                        iterator.remove();
+                    }
+                }
+            }
+        } catch (NoSuchFieldException | IllegalAccessException ignored) {
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockAllFailoverCondition.java
similarity index 69%
copy from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
copy to dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockAllFailoverCondition.java
index 7bef1f5..1aa0e7c 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockAllFailoverCondition.java
@@ -14,15 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+package org.apache.dubbo.metadata.store.failover;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class MockAllFailoverCondition extends MockLocalFailoverCondition {
+
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    public boolean shouldRegister(URL url) {
+        return true;
     }
-}
+
+    @Override
+    public boolean isLocalDataCenter(URL url) {
+        // we don't care about local datacenter first.
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockLocalFailoverCondition.java b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockLocalFailoverCondition.java
new file mode 100644
index 0000000..1efa1ac
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockLocalFailoverCondition.java
@@ -0,0 +1,45 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+
+/**
+ * @author yiji@apache.org
+ */
+public class MockLocalFailoverCondition implements FailoverCondition {
+
+    @Override
+    public boolean shouldRegister(URL url) {
+        // we just register same datacenter.
+        return isLocalDataCenter(url);
+    }
+
+    @Override
+    public boolean shouldQuery(URL url) {
+        // we want read any metadata report server.
+        return true;
+    }
+
+    @Override
+    public boolean isLocalDataCenter(URL url) {
+        // we mock current datacenter is `127.0.0.1:2181`
+        String current = "127.0.0.1:2181";
+        return url.getBackupAddress().contains(current);
+    }
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReport.java
new file mode 100644
index 0000000..a264812
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReport.java
@@ -0,0 +1,131 @@
+/*
+ * 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.failover;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.metadata.MappingListener;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class MockMetadataReport implements MetadataReport {
+
+    public URL url;
+
+    public ConcurrentMap<MetadataIdentifier, ServiceDefinition>      providerMetadata  = new ConcurrentHashMap<>();
+    public ConcurrentMap<SubscriberMetadataIdentifier, MetadataInfo> appMetadata       = new ConcurrentHashMap<>();
+    public ConcurrentMap<String, Set<String>>                        appMapping        = new ConcurrentHashMap<>();
+    public ConcurrentMap<MetadataIdentifier, Map<String, String>>    consumerMetadata  = new ConcurrentHashMap<>();
+    public ConcurrentMap<ServiceMetadataIdentifier, List<String>>    serviceMetadata   = new ConcurrentHashMap<>();
+    public ConcurrentMap<SubscriberMetadataIdentifier, Set<String>>  subscribeMetadata = new ConcurrentHashMap<>();
+
+    public MockMetadataReport(URL url) {
+        this.url = url;
+    }
+
+    @Override
+    public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
+        providerMetadata.put(providerMetadataIdentifier, serviceDefinition);
+    }
+
+    @Override
+    public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
+        ServiceDefinition definition = providerMetadata.get(metadataIdentifier);
+        return definition == null ? null : definition.toString();
+    }
+
+    @Override
+    public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
+        appMetadata.put(identifier, metadataInfo);
+    }
+
+    @Override
+    public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
+        return appMetadata.get(identifier);
+    }
+
+    @Override
+    public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
+        return appMapping.get(serviceKey);
+    }
+
+    @Override
+    public void registerServiceAppMapping(String serviceKey, String application, URL url) {
+        appMapping.putIfAbsent(serviceKey, new ConcurrentHashSet<>());
+        Set<String> appNames = appMapping.get(serviceKey);
+        appNames.add(application);
+    }
+
+    @Override
+    public void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
+        consumerMetadata.put(consumerMetadataIdentifier, serviceParameterMap);
+    }
+
+    @Override
+    public List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        return serviceMetadata.get(metadataIdentifier);
+    }
+
+    @Override
+    public void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
+        serviceMetadata.putIfAbsent(metadataIdentifier, new CopyOnWriteArrayList<>());
+        List<String> urls = serviceMetadata.get(metadataIdentifier);
+        urls.add(url.toFullString());
+    }
+
+    @Override
+    public void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        serviceMetadata.remove(metadataIdentifier);
+    }
+
+    @Override
+    public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls) {
+        subscribeMetadata.putIfAbsent(subscriberMetadataIdentifier, new CopyOnWriteArraySet());
+        Set<String> metadataUrls = subscribeMetadata.get(subscriberMetadataIdentifier);
+        metadataUrls.addAll(urls);
+    }
+
+    @Override
+    public List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+        Set<String> urls = subscribeMetadata.get(subscriberMetadataIdentifier);
+        if (urls == null) { return Collections.EMPTY_LIST; }
+        return new ArrayList<>(urls);
+    }
+
+    public void reset() {
+        providerMetadata.clear();
+        appMetadata.clear();
+        appMapping.clear();
+        consumerMetadata.clear();
+        serviceMetadata.clear();
+        subscribeMetadata.clear();
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReportFactory.java
similarity index 69%
copy from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
copy to dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReportFactory.java
index 7bef1f5..0c1c73e 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/java/org/apache/dubbo/metadata/store/failover/MockMetadataReportFactory.java
@@ -14,15 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+package org.apache.dubbo.metadata.store.failover;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.registry.client.ServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class MockMetadataReportFactory extends AbstractMetadataReportFactory {
+
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    protected MetadataReport createMetadataReport(URL url) {
+        return new MockMetadataReport(url);
     }
-}
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory b/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
new file mode 100644
index 0000000..3336979
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
@@ -0,0 +1 @@
+mock=org.apache.dubbo.metadata.store.failover.MockMetadataReportFactory
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.store.failover.FailoverCondition b/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.store.failover.FailoverCondition
new file mode 100644
index 0000000..ee09f7c
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-report-failover/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.store.failover.FailoverCondition
@@ -0,0 +1,2 @@
+local=org.apache.dubbo.metadata.store.failover.MockLocalFailoverCondition
+all=org.apache.dubbo.metadata.store.failover.MockAllFailoverCondition
\ No newline at end of file
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 b221a5b..aea0ebb 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
@@ -20,6 +20,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.metadata.MappingChangedEvent;
 import org.apache.dubbo.metadata.MappingListener;
@@ -162,22 +163,30 @@
     public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
         Set<String>  appNameSet = new HashSet<>();
         String path = toRootDir() + serviceKey;
-        appNameSet.addAll(zkClient.getChildren(path));
+        List<String> appNameList = zkClient.getChildren(path);
+        if (!CollectionUtils.isEmpty(appNameList)) {
+            appNameSet.addAll(appNameList);
+        }
 
         if (null == listenerMap.get(path)) {
-            ChildListener zkListener = new ChildListener() {
-                @Override
-                public void childChanged(String path, List<String> children) {
-                    MappingChangedEvent event = new MappingChangedEvent();
-                    event.setServiceKey(serviceKey);
-                    event.setApps(null != children ? new HashSet<>(children): null);
-                    listener.onEvent(event);
-                }
-            };
-            zkClient.addChildListener(path, zkListener);
-            listenerMap.put(path, zkListener);
+            zkClient.create(path, false);
+            addServiceMappingListener(path, serviceKey, listener);
         }
 
         return appNameSet;
     }
+
+    private void addServiceMappingListener(String path, String serviceKey, MappingListener listener) {
+        ChildListener zkListener = new ChildListener() {
+            @Override
+            public void childChanged(String path, List<String> children) {
+                MappingChangedEvent event = new MappingChangedEvent();
+                event.setServiceKey(serviceKey);
+                event.setApps(null != children ? new HashSet<>(children) : null);
+                listener.onEvent(event);
+            }
+        };
+        zkClient.addChildListener(path, zkListener);
+        listenerMap.put(path, zkListener);
+    }
 }
diff --git a/dubbo-metadata/pom.xml b/dubbo-metadata/pom.xml
index 17ceada..e72db49 100644
--- a/dubbo-metadata/pom.xml
+++ b/dubbo-metadata/pom.xml
@@ -31,6 +31,7 @@
         <module>dubbo-metadata-api</module>
         <module>dubbo-metadata-definition-protobuf</module>
         <module>dubbo-metadata-report-zookeeper</module>
+        <module>dubbo-metadata-report-failover</module>
         <module>dubbo-metadata-report-redis</module>
         <module>dubbo-metadata-report-consul</module>
         <module>dubbo-metadata-report-etcd</module>
diff --git a/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java b/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java
index 7b107cb..4f33ec6 100644
--- a/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java
+++ b/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java
@@ -56,6 +56,7 @@
 

     private static final Logger logger = LoggerFactory.getLogger(MonitorFilter.class);

     private static final String MONITOR_FILTER_START_TIME = "monitor_filter_start_time";

+    private static final String MONITOR_REMOTE_HOST_STORE = "monitor_remote_host_store";

 

     /**

      * The Concurrent counter

@@ -84,6 +85,7 @@
     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

         if (invoker.getUrl().hasParameter(MONITOR_KEY)) {

             invocation.put(MONITOR_FILTER_START_TIME, System.currentTimeMillis());

+            invocation.put(MONITOR_REMOTE_HOST_STORE, RpcContext.getContext().getRemoteHost());

             getConcurrent(invoker, invocation).incrementAndGet(); // count up

         }

         return invoker.invoke(invocation); // proceed invocation chain

@@ -98,7 +100,7 @@
     @Override

     public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {

         if (invoker.getUrl().hasParameter(MONITOR_KEY)) {

-            collect(invoker, invocation, result, RpcContext.getContext().getRemoteHost(), (long) invocation.get(MONITOR_FILTER_START_TIME), false);

+            collect(invoker, invocation, result, (String) invocation.get(MONITOR_REMOTE_HOST_STORE), (long) invocation.get(MONITOR_FILTER_START_TIME), false);

             getConcurrent(invoker, invocation).decrementAndGet(); // count down

         }

     }

@@ -106,7 +108,7 @@
     @Override

     public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

         if (invoker.getUrl().hasParameter(MONITOR_KEY)) {

-            collect(invoker, invocation, null, RpcContext.getContext().getRemoteHost(), (long) invocation.get(MONITOR_FILTER_START_TIME), true);

+            collect(invoker, invocation, null, (String) invocation.get(MONITOR_REMOTE_HOST_STORE), (long) invocation.get(MONITOR_FILTER_START_TIME), true);

             getConcurrent(invoker, invocation).decrementAndGet(); // count down

         }

     }

diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
index 06457f6..7f7dd1a 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
@@ -21,7 +21,7 @@
 import org.apache.dubbo.qos.command.CommandContext;
 import org.apache.dubbo.qos.command.annotation.Cmd;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
+@Cmd(name = "ready",summary = "Judge if service has started? ")
 public class Ready implements BaseCommand {
 
     @Override
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/ListTelnetHandler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/ListTelnetHandler.java
index 3da6cf4..02cd3b2 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/ListTelnetHandler.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/ListTelnetHandler.java
@@ -112,6 +112,7 @@
                 buf.append(" addresses: ");
                 buf.append(ServiceCheckUtils.getConsumerAddressNum(consumer));
             }
+            buf.append("\r\n");
         }
     }
 
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/LogTelnetHandler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/LogTelnetHandler.java
index c77c86c..3cbfc83 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/LogTelnetHandler.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/legacy/LogTelnetHandler.java
@@ -44,7 +44,7 @@
     public String telnet(Channel channel, String message) {
         long size;
         File file = LoggerFactory.getFile();
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         if (message == null || message.trim().length() == 0) {
             buf.append("EXAMPLE: log error / log 100");
         } else {
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 bebc73a..b5b0999 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
@@ -27,6 +27,7 @@
 
     @Override
     public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
     }
 
     @Override
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
index eb581f9..2cd28ea 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
@@ -174,8 +174,12 @@
 
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof DefaultServiceInstance)) return false;
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultServiceInstance)) {
+            return false;
+        }
         DefaultServiceInstance that = (DefaultServiceInstance) o;
         boolean equals = Objects.equals(getServiceName(), that.getServiceName()) &&
                 Objects.equals(getHost(), that.getHost()) &&
@@ -184,7 +188,7 @@
             if (entry.getKey().equals(REVISION_KEY)) {
                 continue;
             }
-            equals = equals && !entry.getValue().equals(that.getMetadata().get(entry.getKey()));
+            equals = equals && entry.getValue().equals(that.getMetadata().get(entry.getKey()));
         }
 
         return equals;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/InstanceAddressURL.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/InstanceAddressURL.java
index 5159cfc..494785d 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/InstanceAddressURL.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/InstanceAddressURL.java
@@ -27,6 +27,7 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 
 public class InstanceAddressURL extends URL {
@@ -105,6 +106,8 @@
             return getGroup();
         } else if (INTERFACE_KEY.equals(key)) {
             return getServiceInterface();
+        } else if (REMOTE_APPLICATION_KEY.equals(key)) {
+            return instance.getServiceName();
         }
 
         String protocolServiceKey = getProtocolServiceKey();
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 9800c35..3b6c4e5 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
@@ -217,6 +217,15 @@
     }
 
     /**
+     * unsubscribe to instances change event.
+     * @param listener
+     * @throws IllegalArgumentException
+     */
+    default void removeServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
+            throws IllegalArgumentException {
+    }
+
+    /**
      * Dispatch the {@link ServiceInstancesChangedEvent}
      *
      * @param serviceName the name of service whose service instances have been changed
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 a350590..e705c7c 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
@@ -52,6 +52,7 @@
 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.CommonConstants.CHECK_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
@@ -111,6 +112,7 @@
 
     /* apps - listener */
     private final Map<String, ServiceInstancesChangedListener> serviceListeners = new HashMap<>();
+    private final Map<String, String> serviceToAppsMapping = new HashMap<>();
 
     private URL registryURL;
 
@@ -258,9 +260,14 @@
     public void doSubscribe(URL url, NotifyListener listener) {
         writableMetadataService.subscribeURL(url);
 
+        boolean check = url.getParameter(CHECK_KEY, false);
         Set<String> serviceNames = getServices(url, listener);
+
         if (CollectionUtils.isEmpty(serviceNames)) {
-            throw new IllegalStateException("Should has at least one way to know which services this interface belongs to, subscription url: " + url);
+            if (check) {
+                throw new IllegalStateException("Should has at least one way to know which services this interface belongs to, subscription url: " + url);
+            }
+            return;
         }
 
         subscribeURLs(url, listener, serviceNames);
@@ -280,6 +287,10 @@
 
     public void doUnsubscribe(URL url, NotifyListener listener) {
         writableMetadataService.unsubscribeURL(url);
+        String protocolServiceKey = url.getServiceKey() + GROUP_CHAR_SEPARATOR + url.getParameter(PROTOCOL_KEY, DUBBO);
+        String serviceNamesKey = serviceToAppsMapping.remove(protocolServiceKey);
+        ServiceInstancesChangedListener instancesChangedListener = serviceListeners.get(serviceNamesKey);
+        instancesChangedListener.removeListener(protocolServiceKey);
     }
 
     @Override
@@ -308,22 +319,30 @@
 
     protected void subscribeURLs(URL url, NotifyListener listener, Set<String> serviceNames) {
         String serviceNamesKey = serviceNames.toString();
+        String protocolServiceKey = url.getServiceKey() + GROUP_CHAR_SEPARATOR + url.getParameter(PROTOCOL_KEY, DUBBO);
+        serviceToAppsMapping.put(protocolServiceKey, serviceNamesKey);
+
         // register ServiceInstancesChangedListener
         ServiceInstancesChangedListener serviceListener = serviceListeners.computeIfAbsent(serviceNamesKey,
                 k -> new ServiceInstancesChangedListener(serviceNames, serviceDiscovery));
         serviceListener.setUrl(url);
         listener.addServiceListener(serviceListener);
 
-        String protocolServiceKey = url.getServiceKey() + GROUP_CHAR_SEPARATOR + url.getParameter(PROTOCOL_KEY, DUBBO);
         serviceListener.addListener(protocolServiceKey, listener);
         registerServiceInstancesChangedListener(url, serviceListener);
 
+
         serviceNames.forEach(serviceName -> {
             List<ServiceInstance> serviceInstances = serviceDiscovery.getInstances(serviceName);
-            serviceListener.onEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances));
+            if (CollectionUtils.isNotEmpty(serviceInstances)) {
+                serviceListener.onEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances));
+            } else {
+                logger.info("getInstances by serviceName=" + serviceName + " is empty, waiting for serviceListener callback. url=" + url);
+            }
         });
 
         listener.notify(serviceListener.getUrls(protocolServiceKey));
+
     }
 
     /**
@@ -356,17 +375,16 @@
 
         String serviceNames = subscribedURL.getParameter(PROVIDED_BY);
         if (StringUtils.isNotEmpty(serviceNames)) {
-            subscribedServices.addAll(parseServices(serviceNames));
-        }
-
-        serviceNames = subscribedURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY);
-        if (StringUtils.isNotEmpty(serviceNames)) {
+            logger.info(subscribedURL.getServiceInterface() + " mapping to " + serviceNames + " instructed by provided-by set by user.");
             subscribedServices.addAll(parseServices(serviceNames));
         }
 
         if (isEmpty(subscribedServices)) {
-            subscribedServices.addAll(findMappedServices(subscribedURL, new DefaultMappingListener(subscribedURL, subscribedServices, listener)));
+            Set<String> mappedServices = findMappedServices(subscribedURL, new DefaultMappingListener(subscribedURL, subscribedServices, listener));
+            logger.info(subscribedURL.getServiceInterface() + " mapping to " + serviceNames + " instructed by remote metadata center.");
+            subscribedServices.addAll(mappedServices);
             if (isEmpty(subscribedServices)) {
+                logger.info(subscribedURL.getServiceInterface() + " mapping to " + serviceNames + " by default.");
                 subscribedServices.addAll(getSubscribedServices());
             }
         }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
index 15c187f..df573d1 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
@@ -127,6 +127,9 @@
                 logger.warn("destroyUnusedInvokers error. ", e);
             }
         }
+
+        // notify invokers refreshed
+        this.invokersChanged();
     }
 
     /**
@@ -199,7 +202,8 @@
     /**
      * Close all invokers
      */
-    private void destroyAllInvokers() {
+    @Override
+    protected void destroyAllInvokers() {
         Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
         if (localUrlInvokerMap != null) {
             for (Invoker<T> invoker : new ArrayList<>(localUrlInvokerMap.values())) {
@@ -258,34 +262,4 @@
             }
         }
     }
-
-    @Override
-    public void destroy() {
-        if (isDestroyed()) {
-            return;
-        }
-
-        // unregister.
-        try {
-            if (getRegisteredConsumerUrl() != null && registry != null && registry.isAvailable()) {
-                registry.unregister(getRegisteredConsumerUrl());
-            }
-        } catch (Throwable t) {
-            logger.warn("unexpected error when unregister service " + serviceKey + "from registry" + registry.getUrl(), t);
-        }
-        // unsubscribe.
-        try {
-            if (getConsumerUrl() != null && registry != null && registry.isAvailable()) {
-                registry.unsubscribe(getConsumerUrl(), this);
-            }
-        } catch (Throwable t) {
-            logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
-        }
-        super.destroy(); // must be executed after unsubscribing
-        try {
-            destroyAllInvokers();
-        } catch (Throwable t) {
-            logger.warn("Failed to destroy service " + serviceKey, t);
-        }
-    }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryProtocolListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryProtocolListener.java
deleted file mode 100644
index bc9748c..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryProtocolListener.java
+++ /dev/null
@@ -1,38 +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.registry.integration.RegistryProtocolListener;
-import org.apache.dubbo.rpc.Exporter;
-import org.apache.dubbo.rpc.Invoker;
-
-public class ServiceDiscoveryRegistryProtocolListener implements RegistryProtocolListener {
-    @Override
-    public void onExport(RegistryProtocol registryProtocol, Exporter<?> exporter) {
-
-    }
-
-    @Override
-    public void onRefer(RegistryProtocol registryProtocol, Invoker<?> invoker) {
-
-    }
-
-    @Override
-    public void onDestroy() {
-
-    }
-}
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 e99b40c..07e1e37 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
@@ -88,10 +88,14 @@
         logger.info("Received instance notification, serviceName: " + event.getServiceName() + ", instances: " + event.getServiceInstances().size());
         String appName = event.getServiceName();
         allInstances.put(appName, event.getServiceInstances());
+        if (logger.isDebugEnabled()) {
+            logger.debug(event.getServiceInstances().toString());
+        }
 
         Map<String, List<ServiceInstance>> revisionToInstances = new HashMap<>();
         Map<String, Set<String>> localServiceToRevisions = new HashMap<>();
         Map<Set<String>, List<URL>> revisionsToUrls = new HashMap();
+        Map<String, List<URL>> tmpServiceUrls = new HashMap<>();
         for (Map.Entry<String, List<ServiceInstance>> entry : allInstances.entrySet()) {
             List<ServiceInstance> instances = entry.getValue();
             for (ServiceInstance instance : instances) {
@@ -108,7 +112,7 @@
                     metadata = getMetadataInfo(instance);
                     logger.info("MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision + " is " + metadata);
                     if (metadata != null) {
-                        revisionToMetadata.put(revision, getMetadataInfo(instance));
+                        revisionToMetadata.put(revision, metadata);
                     } else {
 
                     }
@@ -123,25 +127,26 @@
 //                    Set<String> set = localServiceToRevisions.computeIfAbsent(url.getServiceKey(), k -> new TreeSet<>());
 //                    set.add(revision);
 //                }
-
-                localServiceToRevisions.forEach((serviceKey, revisions) -> {
-                    List<URL> urls = revisionsToUrls.get(revisions);
-                    if (urls != null) {
-                        serviceUrls.put(serviceKey, urls);
-                    } else {
-                        urls = new ArrayList<>();
-                        for (String r : revisions) {
-                            for (ServiceInstance i : revisionToInstances.get(r)) {
-                                urls.add(i.toURL());
-                            }
-                        }
-                        revisionsToUrls.put(revisions, urls);
-                        serviceUrls.put(serviceKey, urls);
-                    }
-                });
             }
+
+            localServiceToRevisions.forEach((serviceKey, revisions) -> {
+                List<URL> urls = revisionsToUrls.get(revisions);
+                if (urls != null) {
+                    tmpServiceUrls.put(serviceKey, urls);
+                } else {
+                    urls = new ArrayList<>();
+                    for (String r : revisions) {
+                        for (ServiceInstance i : revisionToInstances.get(r)) {
+                            urls.add(i.toURL());
+                        }
+                    }
+                    revisionsToUrls.put(revisions, urls);
+                    tmpServiceUrls.put(serviceKey, urls);
+                }
+            });
         }
 
+        this.serviceUrls = tmpServiceUrls;
         this.notifyAddressChanged();
     }
 
@@ -161,6 +166,9 @@
         instance.getExtendParams().putIfAbsent(REGISTRY_CLUSTER_KEY, RegistryClusterIdentifier.getExtension(url).consumerKey(url));
         MetadataInfo metadataInfo;
         try {
+            if (logger.isDebugEnabled()) {
+                logger.info("Instance " + instance.getAddress() + " is using metadata type " + metadataType);
+            }
             if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
                 RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
                 metadataInfo = remoteMetadataService.getMetadata(instance);
@@ -168,6 +176,9 @@
                 MetadataService metadataServiceProxy = MetadataUtils.getMetadataServiceProxy(instance, serviceDiscovery);
                 metadataInfo = metadataServiceProxy.getMetadataInfo(ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
             }
+            if (logger.isDebugEnabled()) {
+                logger.info("Metadata " + metadataInfo.toString());
+            }
         } catch (Exception e) {
             logger.error("Failed to load service metadata, metadta type is " + metadataType, e);
             metadataInfo = null;
@@ -194,6 +205,13 @@
         this.listeners.put(serviceKey, listener);
     }
 
+    public void removeListener(String serviceKey) {
+        listeners.remove(serviceKey);
+        if (listeners.isEmpty()) {
+            serviceDiscovery.removeServiceInstancesChangedListener(this);
+        }
+    }
+
     public List<URL> getUrls(String serviceKey) {
         return toUrlsWithEmpty(serviceUrls.get(serviceKey));
     }
@@ -225,8 +243,12 @@
 
     @Override
     public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof ServiceInstancesChangedListener)) return false;
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ServiceInstancesChangedListener)) {
+            return false;
+        }
         ServiceInstancesChangedListener that = (ServiceInstancesChangedListener) o;
         return Objects.equals(getServiceNames(), that.getServiceNames());
     }
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 4684a27..0a6a106 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
@@ -71,7 +71,7 @@
     }
 
     public static MetadataService getMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
-        String key = instance.getServiceName() + "##" + instance.getId() + "##" +
+        String key = instance.getServiceName() + "##" +
                 ServiceInstanceMetadataUtils.getExportedServicesRevision(instance);
         return metadataServiceProxies.computeIfAbsent(key, k -> {
             MetadataServiceURLBuilder builder = null;
@@ -81,10 +81,10 @@
             Map<String, String> metadata = instance.getMetadata();
             // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
             String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
-            if (metadata.isEmpty() || StringUtils.isEmpty(dubboURLsJSON)) {
-                builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
-            } else {
+            if (StringUtils.isNotEmpty(dubboURLsJSON)) {
                 builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
+            } else {
+                builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
             }
 
             List<URL> urls = builder.build(instance);
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
index 34dce94..c90eea0 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
@@ -19,12 +19,8 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 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.metadata.MetadataService;
 import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.remoting.Constants;
-import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -32,12 +28,10 @@
 
 import static java.lang.String.valueOf;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
-import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
 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.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_TIMEOUT_VALUE;
 import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams;
@@ -49,9 +43,7 @@
  * @since 2.7.5
  */
 public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuilder {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
+    
     public static final String NAME = "standard";
 
     /**
@@ -71,66 +63,27 @@
 
         String host = serviceInstance.getHost();
 
-        if (paramsMap.isEmpty()) {
-            // ServiceInstance Metadata is empty. Happened when registry not support metadata write.
-            urls.add(generateUrlWithoutMetadata(serviceName, host));
-        } else {
-            for (Map.Entry<String, Map<String, String>> entry : paramsMap.entrySet()) {
-                String protocol = entry.getKey();
-                Map<String, String> params = entry.getValue();
+        for (Map.Entry<String, Map<String, String>> entry : paramsMap.entrySet()) {
+            String protocol = entry.getKey();
+            Map<String, String> params = entry.getValue();
+            int port = Integer.parseInt(params.get(PORT_KEY));
+            URLBuilder urlBuilder = new URLBuilder()
+                    .setHost(host)
+                    .setPort(port)
+                    .setProtocol(protocol)
+                    .setPath(MetadataService.class.getName())
+                    .addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
+                    .addParameter(SIDE_KEY, CONSUMER);
 
-                urls.add(generateWithMetadata(serviceName, host, protocol, params));
-            }
+            // add parameters
+            params.forEach((name, value) -> urlBuilder.addParameter(name, valueOf(value)));
+
+            // add the default parameters
+            urlBuilder.addParameter(GROUP_KEY, serviceName);
+
+            urls.add(urlBuilder.build());
         }
 
         return urls;
     }
-
-    private URL generateWithMetadata(String serviceName, String host, String protocol, Map<String, String> params) {
-        int port = Integer.parseInt(params.get(PORT_KEY));
-        URLBuilder urlBuilder = new URLBuilder()
-                .setHost(host)
-                .setPort(port)
-                .setProtocol(protocol)
-                .setPath(MetadataService.class.getName())
-                .addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
-                .addParameter(SIDE_KEY, CONSUMER);
-
-        // add parameters
-        params.forEach((name, value) -> urlBuilder.addParameter(name, valueOf(value)));
-
-        // add the default parameters
-        urlBuilder.addParameter(GROUP_KEY, serviceName);
-        return urlBuilder.build();
-    }
-
-    private URL generateUrlWithoutMetadata(String serviceName, String host) {
-        Integer port = ApplicationModel.getApplicationConfig().getMetadataServicePort();
-
-        if (port == null || port < 1) {
-            String message = "Metadata Service Port should be specified for consumer. " +
-                    "Please set dubbo.application.metadataServicePort and " +
-                    "make sure it has been set in provider side. " +
-                    "ServiceName: " + serviceName + " Host: " + host;
-
-            logger.error(message);
-            throw new IllegalStateException(message);
-        }
-
-        URLBuilder urlBuilder = new URLBuilder()
-                .setHost(host)
-                .setPort(port)
-                .setProtocol(DUBBO_PROTOCOL)
-                .setPath(MetadataService.class.getName())
-                .addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
-                .addParameter(Constants.RECONNECT_KEY, false)
-                .addParameter(SIDE_KEY, CONSUMER)
-                .addParameter(GROUP_KEY, serviceName)
-                .addParameter(VERSION_KEY, MetadataService.VERSION);
-
-        // add ServiceInstance Metadata notify support
-        urlBuilder.addParameter("getAndListenServiceDiscoveryMetadata.1.callback", true);
-
-        return urlBuilder.build();
-    }
 }
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
index c2403ea..44c6bbc 100644
--- 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
@@ -20,7 +20,6 @@
 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.MetadataChangeListener;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
 import org.apache.dubbo.metadata.MetadataService;
@@ -77,8 +76,6 @@
     ConcurrentNavigableMap<String, SortedSet<URL>> exportedServiceURLs = new ConcurrentSkipListMap<>();
     ConcurrentMap<String, MetadataInfo> metadataInfos;
     final Semaphore metadataSemaphore = new Semaphore(1);
-    String serviceDiscoveryMetadata;
-    ConcurrentMap<String, MetadataChangeListener> metadataChangeListenerMap = new ConcurrentHashMap<>();
 
     // ==================================================================================== //
 
@@ -209,22 +206,6 @@
         return null;
     }
 
-    @Override
-    public void exportServiceDiscoveryMetadata(String metadata) {
-        this.serviceDiscoveryMetadata = metadata;
-    }
-
-    @Override
-    public Map<String, MetadataChangeListener> getMetadataChangeListenerMap() {
-        return metadataChangeListenerMap;
-    }
-
-    @Override
-    public String getAndListenServiceDiscoveryMetadata(String consumerId, MetadataChangeListener listener) {
-        metadataChangeListenerMap.put(consumerId, listener);
-        return serviceDiscoveryMetadata;
-    }
-
     public void blockUntilUpdated() {
         try {
             metadataSemaphore.acquire();
@@ -310,4 +291,5 @@
             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
index 01c4eff..209a9fa 100644
--- 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
@@ -69,6 +69,10 @@
                 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);
                 metadataInfo.markReported();
             }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/DefaultMigrationAddressComparator.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/DefaultMigrationAddressComparator.java
new file mode 100644
index 0000000..2936688
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/DefaultMigrationAddressComparator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.migration;
+
+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.CollectionUtils;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+
+import java.util.List;
+
+public class DefaultMigrationAddressComparator implements MigrationAddressComparator {
+    private static final Logger logger = LoggerFactory.getLogger(DefaultMigrationAddressComparator.class);
+    private static final String MIGRATION_THRESHOLD = "dubbo.application.migration.threshold";
+    private static final String DEFAULT_THRESHOLD_STRING = "0.8";
+    private static final float DEFAULT_THREAD = 0.8f;
+
+    @Override
+    public <T> boolean shouldMigrate(ClusterInvoker<T> serviceDiscoveryInvoker, ClusterInvoker<T> invoker) {
+        if (!serviceDiscoveryInvoker.isAvailable()) {
+            logger.info("No instance address available, will not migrate.");
+            return false;
+        }
+        if (!invoker.isAvailable()) {
+            logger.info("No interface address available, will migrate.");
+            return true;
+        }
+
+        List<Invoker<T>> invokers1 = serviceDiscoveryInvoker.getDirectory().getAllInvokers();
+        List<Invoker<T>> invokers2 = invoker.getDirectory().getAllInvokers();
+
+        int newAddressSize = CollectionUtils.isNotEmpty(invokers1) ? invokers1.size() : 0;
+        int oldAddressSize = CollectionUtils.isNotEmpty(invokers2) ? invokers2.size() : 0;
+
+        String rawThreshold = ConfigurationUtils.getDynamicProperty(MIGRATION_THRESHOLD, DEFAULT_THRESHOLD_STRING);
+        float threshold;
+        try {
+            threshold = Float.parseFloat(rawThreshold);
+        } catch (Exception e) {
+            logger.error("Invalid migration threshold " + rawThreshold);
+            threshold = DEFAULT_THREAD;
+        }
+
+        logger.info("Instance address size " + newAddressSize + ", interface address size " + oldAddressSize + ", threshold " + threshold);
+
+        if (newAddressSize != 0 && oldAddressSize == 0) {
+            return true;
+        }
+        if (newAddressSize == 0 && oldAddressSize == 0) {
+            return false;
+        }
+
+        if (((float)newAddressSize / (float)oldAddressSize) >= threshold) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/InvokersChangedListener.java
similarity index 86%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
copy to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/InvokersChangedListener.java
index 2042277..74cd947 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/InvokersChangedListener.java
@@ -14,7 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.registry.client.migration;
 
-public class ConfigurationURL extends URL {
-}
+public interface InvokersChangedListener {
+    void onChange();
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationAddressComparator.java
similarity index 72%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
copy to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationAddressComparator.java
index 2042277..2be527b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationAddressComparator.java
@@ -14,7 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.registry.client.migration;
 
-public class ConfigurationURL extends URL {
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+
+@SPI
+public interface MigrationAddressComparator {
+    <T> boolean shouldMigrate(ClusterInvoker<T> serviceDiscoveryInvoker, ClusterInvoker<T> invoker);
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java
new file mode 100644
index 0000000..b3c9f68
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java
@@ -0,0 +1,390 @@
+/*
+ * 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.migration;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.RegistryConstants;
+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.registry.Registry;
+import org.apache.dubbo.registry.integration.DynamicDirectory;
+import org.apache.dubbo.registry.integration.RegistryProtocol;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.Cluster;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+import org.apache.dubbo.rpc.cluster.Directory;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationClusterInvoker;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationRule;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
+
+public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
+    private Logger logger = LoggerFactory.getLogger(MigrationInvoker.class);
+
+    private URL url;
+    private URL consumerUrl;
+    private Cluster cluster;
+    private Registry registry;
+    private Class<T> type;
+    private RegistryProtocol registryProtocol;
+
+    private volatile ClusterInvoker<T> invoker;
+    private volatile ClusterInvoker<T> serviceDiscoveryInvoker;
+    private volatile ClusterInvoker<T> currentAvailableInvoker;
+
+    private MigrationRule rule;
+
+    private boolean migrationMultiRegsitry;
+
+    public MigrationInvoker(RegistryProtocol registryProtocol,
+                            Cluster cluster,
+                            Registry registry,
+                            Class<T> type,
+                            URL url,
+                            URL consumerUrl) {
+        this(null, null, registryProtocol, cluster, registry, type, url, consumerUrl);
+    }
+
+    public MigrationInvoker(ClusterInvoker<T> invoker,
+                            ClusterInvoker<T> serviceDiscoveryInvoker,
+                            RegistryProtocol registryProtocol,
+                            Cluster cluster,
+                            Registry registry,
+                            Class<T> type,
+                            URL url,
+                            URL consumerUrl) {
+        this.invoker = invoker;
+        this.serviceDiscoveryInvoker = serviceDiscoveryInvoker;
+        this.registryProtocol = registryProtocol;
+        this.cluster = cluster;
+        this.registry = registry;
+        this.type = type;
+        this.url = url;
+        this.consumerUrl = consumerUrl;
+        this.migrationMultiRegsitry = url.getParameter("MIGRATION_MULTI_REGSITRY", RegistryConstants.MIGRATION_MULTI_REGSITRY);
+    }
+
+    public ClusterInvoker<T> getInvoker() {
+        return invoker;
+    }
+
+    public void setInvoker(ClusterInvoker<T> invoker) {
+        this.invoker = invoker;
+    }
+
+    public ClusterInvoker<T> getServiceDiscoveryInvoker() {
+        return serviceDiscoveryInvoker;
+    }
+
+    public void setServiceDiscoveryInvoker(ClusterInvoker<T> serviceDiscoveryInvoker) {
+        this.serviceDiscoveryInvoker = serviceDiscoveryInvoker;
+    }
+
+    @Override
+    public Class<T> getInterface() {
+        return type;
+    }
+
+    @Override
+    public synchronized void migrateToServiceDiscoveryInvoker(boolean forceMigrate) {
+        if (!forceMigrate) {
+            refreshServiceDiscoveryInvoker();
+            refreshInterfaceInvoker();
+            setListener(invoker, () -> {
+                this.compareAddresses(invoker, serviceDiscoveryInvoker);
+            });
+            setListener(serviceDiscoveryInvoker, () -> {
+                this.compareAddresses(invoker, serviceDiscoveryInvoker);
+            });
+        } else {
+            refreshServiceDiscoveryInvoker();
+            setListener(serviceDiscoveryInvoker, () -> {
+                this.destroyInterfaceInvoker(this.invoker);
+            });
+        }
+    }
+
+    @Override
+    public void reRefer(URL newSubscribeUrl) {
+        // update url to prepare for migration refresh
+        this.url = url.addParameter(REFER_KEY, StringUtils.toQueryString(newSubscribeUrl.getParameters()));
+
+        // re-subscribe immediately
+        if (invoker != null && !invoker.isDestroyed()) {
+            doReSubscribe(invoker, newSubscribeUrl);
+        }
+        if (serviceDiscoveryInvoker != null && !serviceDiscoveryInvoker.isDestroyed()) {
+            doReSubscribe(serviceDiscoveryInvoker, newSubscribeUrl);
+        }
+    }
+
+    private void doReSubscribe(ClusterInvoker<T> invoker, URL newSubscribeUrl) {
+        DynamicDirectory<T> directory = (DynamicDirectory<T>)invoker.getDirectory();
+        URL oldSubscribeUrl = directory.getRegisteredConsumerUrl();
+        Registry registry = directory.getRegistry();
+        registry.unregister(directory.getRegisteredConsumerUrl());
+        directory.unSubscribe(RegistryProtocol.toSubscribeUrl(oldSubscribeUrl));
+        registry.register(directory.getRegisteredConsumerUrl());
+
+        directory.setRegisteredConsumerUrl(newSubscribeUrl);
+        directory.buildRouterChain(newSubscribeUrl);
+        directory.subscribe(RegistryProtocol.toSubscribeUrl(newSubscribeUrl));
+    }
+
+    @Override
+    public synchronized void fallbackToInterfaceInvoker() {
+        refreshInterfaceInvoker();
+        setListener(invoker, () -> {
+            this.destroyServiceDiscoveryInvoker(this.serviceDiscoveryInvoker);
+        });
+    }
+
+    @Override
+    public Result invoke(Invocation invocation) throws RpcException {
+        if (!checkInvokerAvailable(serviceDiscoveryInvoker)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Using interface addresses to handle invocation, interface " + type.getName() + ", total address size " + (invoker.getDirectory().getAllInvokers() == null ? "is null" : invoker.getDirectory().getAllInvokers().size()));
+            }
+            return invoker.invoke(invocation);
+        }
+
+        if (!checkInvokerAvailable(invoker)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Using instance addresses to handle invocation, interface " + type.getName() + ", total address size " + (serviceDiscoveryInvoker.getDirectory().getAllInvokers() == null ? " is null " : serviceDiscoveryInvoker.getDirectory().getAllInvokers().size()));
+            }
+            return serviceDiscoveryInvoker.invoke(invocation);
+        }
+
+        return currentAvailableInvoker.invoke(invocation);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return (invoker != null && invoker.isAvailable())
+                || (serviceDiscoveryInvoker != null && serviceDiscoveryInvoker.isAvailable());
+    }
+
+    @Override
+    public void destroy() {
+        if (invoker != null) {
+            invoker.destroy();
+        }
+        if (serviceDiscoveryInvoker != null) {
+            serviceDiscoveryInvoker.destroy();
+        }
+    }
+
+    @Override
+    public URL getUrl() {
+        if (invoker != null) {
+            return invoker.getUrl();
+        } else if (serviceDiscoveryInvoker != null) {
+            return serviceDiscoveryInvoker.getUrl();
+        }
+
+        return consumerUrl;
+    }
+
+    @Override
+    public URL getRegistryUrl() {
+        if (invoker != null) {
+            return invoker.getRegistryUrl();
+        } else if (serviceDiscoveryInvoker != null) {
+            serviceDiscoveryInvoker.getRegistryUrl();
+        }
+        return url;
+    }
+
+    @Override
+    public Directory<T> getDirectory() {
+        if (invoker != null) {
+            return invoker.getDirectory();
+        } else if (serviceDiscoveryInvoker != null) {
+            return serviceDiscoveryInvoker.getDirectory();
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        return (invoker == null || invoker.isDestroyed())
+                && (serviceDiscoveryInvoker == null || serviceDiscoveryInvoker.isDestroyed());
+    }
+
+
+    @Override
+    public AtomicBoolean invokersChanged() {
+        return invokersChanged;
+    }
+
+    private volatile AtomicBoolean invokersChanged = new AtomicBoolean(true);
+
+    private synchronized void compareAddresses(ClusterInvoker<T> serviceDiscoveryInvoker, ClusterInvoker<T> invoker) {
+        this.invokersChanged.set(true);
+        if (logger.isDebugEnabled()) {
+            logger.info(invoker.getDirectory().getAllInvokers() == null ? "null" :invoker.getDirectory().getAllInvokers().size() + "");
+        }
+
+        Set<MigrationAddressComparator> detectors = ExtensionLoader.getExtensionLoader(MigrationAddressComparator.class).getSupportedExtensionInstances();
+        if (detectors != null && detectors.stream().allMatch(migrationDetector -> migrationDetector.shouldMigrate(serviceDiscoveryInvoker, invoker))) {
+            discardInterfaceInvokerAddress(invoker);
+        } else {
+            discardServiceDiscoveryInvokerAddress(serviceDiscoveryInvoker);
+        }
+    }
+
+    private synchronized void setAddressChanged() {
+        this.invokersChanged.set(true);
+    }
+
+    public synchronized void destroyServiceDiscoveryInvoker(ClusterInvoker<?> serviceDiscoveryInvoker) {
+        if (checkInvokerAvailable(this.invoker)) {
+            this.currentAvailableInvoker = this.invoker;
+        }
+        if (serviceDiscoveryInvoker != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Destroying instance address invokers, will not listen for address changes until re-subscribed, " + type.getName());
+            }
+            serviceDiscoveryInvoker.destroy();
+        }
+    }
+
+    public synchronized void discardServiceDiscoveryInvokerAddress(ClusterInvoker<?> serviceDiscoveryInvoker) {
+        if (checkInvokerAvailable(this.invoker)) {
+            this.currentAvailableInvoker = this.invoker;
+        }
+        if (serviceDiscoveryInvoker != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Discarding instance addresses, total size " + (null == serviceDiscoveryInvoker.getDirectory().getAllInvokers() ? "null" : serviceDiscoveryInvoker.getDirectory().getAllInvokers().size()));
+            }
+            serviceDiscoveryInvoker.getDirectory().discordAddresses();
+        }
+    }
+
+    public synchronized void refreshServiceDiscoveryInvoker() {
+        clearListener(serviceDiscoveryInvoker);
+        if (needRefresh(serviceDiscoveryInvoker)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Re-subscribing instance addresses, current interface " + type.getName());
+            }
+            serviceDiscoveryInvoker = registryProtocol.getServiceDiscoveryInvoker(cluster, registry, type, url);
+
+            if (migrationMultiRegsitry) {
+                setListener(serviceDiscoveryInvoker, () -> {
+                    this.setAddressChanged();
+                });
+            }
+        }
+    }
+
+    private void clearListener(ClusterInvoker<T> invoker) {
+        if (migrationMultiRegsitry) {
+            return;
+        }
+
+        if (invoker == null) {
+            return;
+        }
+        DynamicDirectory<T> directory = (DynamicDirectory<T>) invoker.getDirectory();
+        directory.setInvokersChangedListener(null);
+    }
+
+    private void setListener(ClusterInvoker<T> invoker, InvokersChangedListener listener) {
+        if (invoker == null) {
+            return;
+        }
+        DynamicDirectory<T> directory = (DynamicDirectory<T>) invoker.getDirectory();
+        directory.setInvokersChangedListener(listener);
+    }
+
+    public synchronized void refreshInterfaceInvoker() {
+        clearListener(invoker);
+        if (needRefresh(invoker)) {
+            // FIXME invoker.destroy();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Re-subscribing interface addresses for interface " + type.getName());
+            }
+            invoker = registryProtocol.getInvoker(cluster, registry, type, url);
+
+            if (migrationMultiRegsitry) {
+                setListener(serviceDiscoveryInvoker, () -> {
+                    this.setAddressChanged();
+                });
+            }
+        }
+    }
+
+    public synchronized void destroyInterfaceInvoker(ClusterInvoker<T> invoker) {
+        if (checkInvokerAvailable(this.serviceDiscoveryInvoker)) {
+            this.currentAvailableInvoker = this.serviceDiscoveryInvoker;
+        }
+        if (invoker != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Destroying interface address invokers, will not listen for address changes until re-subscribed, " + type.getName());
+            }
+            invoker.destroy();
+        }
+    }
+
+    public synchronized void discardInterfaceInvokerAddress(ClusterInvoker<T> invoker) {
+        if (this.serviceDiscoveryInvoker != null) {
+            this.currentAvailableInvoker = this.serviceDiscoveryInvoker;
+        }
+        if (invoker != null) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Discarding interface addresses, total address size " + (null == invoker.getDirectory().getAllInvokers() ? "null": invoker.getDirectory().getAllInvokers().size()));
+            }
+            invoker.getDirectory().discordAddresses();
+        }
+    }
+
+    private boolean needRefresh(ClusterInvoker<T> invoker) {
+        return invoker == null || invoker.isDestroyed();
+    }
+
+    public boolean checkInvokerAvailable(ClusterInvoker<T> invoker) {
+        return invoker != null && !invoker.isDestroyed() && invoker.isAvailable();
+    }
+
+    @Override
+    public boolean isServiceInvoker() {
+        return false;
+    }
+
+    @Override
+    public MigrationRule getMigrationRule() {
+        return rule;
+    }
+
+    @Override
+    public void setMigrationRule(MigrationRule rule) {
+        this.rule = rule;
+    }
+
+    @Override
+    public boolean isMigrationMultiRegsitry() {
+        return migrationMultiRegsitry;
+    }
+
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java
new file mode 100644
index 0000000..faf3d86
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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.migration;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationRule;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationStep;
+
+@Activate
+public class MigrationRuleHandler<T> {
+    private static final Logger logger = LoggerFactory.getLogger(MigrationRuleHandler.class);
+
+    private MigrationInvoker<T> migrationInvoker;
+
+    public MigrationRuleHandler(MigrationInvoker<T> invoker) {
+        this.migrationInvoker = invoker;
+    }
+
+    private MigrationStep currentStep;
+
+    public void doMigrate(String rawRule) {
+        MigrationRule rule = MigrationRule.parse(rawRule);
+
+        if (null != currentStep && currentStep.equals(rule.getStep())) {
+            if (logger.isInfoEnabled()) {
+                logger.info("Migration step is not change. rule.getStep is " + currentStep.name());
+            }
+            return;
+        } else {
+            currentStep = rule.getStep();
+        }
+
+        migrationInvoker.setMigrationRule(rule);
+
+        if (migrationInvoker.isMigrationMultiRegsitry()) {
+            if (migrationInvoker.isServiceInvoker()) {
+                migrationInvoker.refreshServiceDiscoveryInvoker();
+            } else {
+                migrationInvoker.refreshInterfaceInvoker();
+            }
+        } else {
+            switch (rule.getStep()) {
+                case APPLICATION_FIRST:
+                    migrationInvoker.migrateToServiceDiscoveryInvoker(false);
+                    break;
+                case FORCE_APPLICATION:
+                    migrationInvoker.migrateToServiceDiscoveryInvoker(true);
+                    break;
+                case FORCE_INTERFACE:
+                default:
+                    migrationInvoker.fallbackToInterfaceInvoker();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
new file mode 100644
index 0000000..e0cb6ce
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
@@ -0,0 +1,112 @@
+/*
+ * 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.migration;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
+import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
+import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.extension.Activate;
+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.ConcurrentHashSet;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.registry.integration.RegistryProtocol;
+import org.apache.dubbo.registry.integration.RegistryProtocolListener;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationRule;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.Optional;
+import java.util.Set;
+
+import static org.apache.dubbo.common.constants.RegistryConstants.INIT;
+
+@Activate
+public class MigrationRuleListener implements RegistryProtocolListener, ConfigurationListener {
+    private static final Logger logger = LoggerFactory.getLogger(MigrationRuleListener.class);
+
+    private Set<MigrationRuleHandler> listeners = new ConcurrentHashSet<>();
+    private DynamicConfiguration configuration;
+
+    private volatile String rawRule;
+
+    public MigrationRuleListener() {
+        Optional<DynamicConfiguration> optional =  ApplicationModel.getEnvironment().getDynamicConfiguration();
+
+        if (optional.isPresent()) {
+            this.configuration = optional.orElseGet(null);
+
+            logger.info("Listening for migration rules on dataId-" + MigrationRule.RULE_KEY + " group-" + MigrationRule.DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP);
+            configuration.addListener(MigrationRule.RULE_KEY, MigrationRule.DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP, this);
+
+            rawRule = configuration.getConfig(MigrationRule.RULE_KEY, MigrationRule.DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP);
+            if (StringUtils.isEmpty(rawRule)) {
+                rawRule = INIT;
+            }
+
+        } else {
+            if (logger.isWarnEnabled()) {
+                logger.warn("configceneter is not configured!");
+            }
+
+            rawRule = INIT;
+        }
+
+        process(new ConfigChangedEvent(MigrationRule.RULE_KEY, MigrationRule.DUBBO_SERVICEDISCOVERY_MIGRATION_GROUP, rawRule));
+    }
+
+    @Override
+    public synchronized void process(ConfigChangedEvent event) {
+        rawRule = event.getContent();
+        if (StringUtils.isEmpty(rawRule)) {
+            logger.warn("Received empty migration rule, will ignore.");
+            return;
+        }
+
+        logger.info("Using the following migration rule to migrate:");
+        logger.info(rawRule);
+
+        if (CollectionUtils.isNotEmpty(listeners)) {
+            listeners.forEach(listener -> listener.doMigrate(rawRule));
+        }
+    }
+
+    @Override
+    public synchronized void onExport(RegistryProtocol registryProtocol, Exporter<?> exporter) {
+
+    }
+
+    @Override
+    public synchronized  void onRefer(RegistryProtocol registryProtocol, ClusterInvoker<?> invoker, URL url) {
+        MigrationInvoker<?> migrationInvoker = (MigrationInvoker<?>) invoker;
+
+        MigrationRuleHandler<?> migrationListener = new MigrationRuleHandler<>(migrationInvoker);
+        listeners.add(migrationListener);
+
+        migrationListener.doMigrate(rawRule);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (null != configuration) {
+            configuration.removeListener(MigrationRule.RULE_KEY, this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java
new file mode 100644
index 0000000..feeb5b8
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java
@@ -0,0 +1,61 @@
+/*
+ * 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.migration;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.integration.RegistryProtocol;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.Cluster;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
+
+public class ServiceDiscoveryMigrationInvoker<T> extends MigrationInvoker<T> {
+    private static final Logger logger = LoggerFactory.getLogger(ServiceDiscoveryMigrationInvoker.class);
+
+    public ServiceDiscoveryMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
+        super(registryProtocol, cluster, registry, type, url, consumerUrl);
+    }
+
+    @Override
+    public boolean isServiceInvoker() {
+        return true;
+    }
+
+    @Override
+    public synchronized void fallbackToInterfaceInvoker() {
+        logger.error("Service discovery registry type does not support discovery of interface level addresses, " + getRegistryUrl());
+        migrateToServiceDiscoveryInvoker(true);
+    }
+
+    @Override
+    public synchronized void migrateToServiceDiscoveryInvoker(boolean forceMigrate) {
+        refreshServiceDiscoveryInvoker();
+    }
+
+    @Override
+    public Result invoke(Invocation invocation) throws RpcException {
+        ClusterInvoker<T> invoker = getServiceDiscoveryInvoker();
+        if (invoker == null) {
+            throw new IllegalStateException("There's no service discovery invoker available for service " + invocation.getServiceName());
+        }
+        return invoker.invoke(invocation);
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java
index b6c0038..31323ef 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.registry.integration;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
@@ -25,6 +26,7 @@
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.client.migration.InvokersChangedListener;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
@@ -35,19 +37,21 @@
 import org.apache.dubbo.rpc.cluster.RouterFactory;
 import org.apache.dubbo.rpc.cluster.directory.AbstractDirectory;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY;
+import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
 import static org.apache.dubbo.registry.Constants.REGISTER_KEY;
 import static org.apache.dubbo.registry.Constants.SIMPLIFIED_KEY;
-import static org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.DEFAULT_REGISTER_CONSUMER_KEYS;
+import static org.apache.dubbo.registry.integration.RegistryProtocol.DEFAULT_REGISTER_CONSUMER_KEYS;
 import static org.apache.dubbo.remoting.Constants.CHECK_KEY;
 
 
@@ -65,6 +69,7 @@
 
     protected final String serviceKey; // Initialization at construction time, assertion not null
     protected final Class<T> serviceType; // Initialization at construction time, assertion not null
+    protected final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value
     protected final boolean multiGroup;
     protected Protocol protocol; // Initialization at the time of injection, the assertion is not null
     protected Registry registry; // Initialization at the time of injection, the assertion is not null
@@ -72,7 +77,7 @@
     protected boolean shouldRegister;
     protected boolean shouldSimplified;
 
-    protected volatile URL overrideConsumerUrl; // Initialization at construction time, assertion not null, and always assign non null value
+    protected volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value
 
     protected volatile URL registeredConsumerUrl;
 
@@ -84,17 +89,13 @@
      */
     protected volatile List<Configurator> configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference
 
-    // Map<url, Invoker> cache service url to invoker mapping.
-    protected volatile Map<URL, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
     protected volatile List<Invoker<T>> invokers;
-
     // Set<invokerUrls> cache invokeUrls to invokers mapping.
-    protected volatile Set<URL> cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference
 
     protected ServiceInstancesChangedListener serviceListener;
 
     public DynamicDirectory(Class<T> serviceType, URL url) {
-        super(url);
+        super(url, true);
         if (serviceType == null) {
             throw new IllegalArgumentException("service type is null.");
         }
@@ -107,7 +108,8 @@
         this.serviceType = serviceType;
         this.serviceKey = super.getConsumerUrl().getServiceKey();
 
-        String group = queryMap.get(GROUP_KEY) != null ? queryMap.get(GROUP_KEY) : "";
+        this.overrideDirectoryUrl = this.directoryUrl = turnRegistryUrlToConsumerUrl(url);
+        String group = directoryUrl.getParameter(GROUP_KEY, "");
         this.multiGroup = group != null && (ANY_VALUE.equals(group) || group.contains(","));
     }
 
@@ -116,6 +118,18 @@
         this.serviceListener = instanceListener;
     }
 
+    private URL turnRegistryUrlToConsumerUrl(URL url) {
+        return URLBuilder.from(url)
+                .setHost(queryMap.get(REGISTER_IP_KEY) == null ? url.getHost() : queryMap.get(REGISTER_IP_KEY))
+                .setPort(0)
+                .setProtocol(queryMap.get(PROTOCOL_KEY) == null ? DUBBO : queryMap.get(PROTOCOL_KEY))
+                .setPath(queryMap.get(INTERFACE_KEY))
+                .clearParameters()
+                .addParameters(queryMap)
+                .removeParameter(MONITOR_KEY)
+                .build();
+    }
+
     public void setProtocol(Protocol protocol) {
         this.protocol = protocol;
     }
@@ -177,6 +191,11 @@
         return invokers;
     }
 
+    @Override
+    public URL getConsumerUrl() {
+        return this.overrideDirectoryUrl;
+    }
+
     public URL getRegisteredConsumerUrl() {
         return registeredConsumerUrl;
     }
@@ -191,41 +210,74 @@
         }
     }
 
-    @Override
-    public boolean isAvailable() {
-        if (isDestroyed()) {
-            return false;
-        }
-        Map<URL, Invoker<T>> localUrlInvokerMap = urlInvokerMap;
-        if (localUrlInvokerMap != null && localUrlInvokerMap.size() > 0) {
-            for (Invoker<T> invoker : new ArrayList<>(localUrlInvokerMap.values())) {
-                if (invoker.isAvailable()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public void buildRouterChain(URL url) {
         this.setRouterChain(RouterChain.buildChain(url));
     }
 
-    /**
-     * Haomin: added for test purpose
-     */
-    public Map<URL, Invoker<T>> getUrlInvokerMap() {
-        return urlInvokerMap;
-    }
-
     public List<Invoker<T>> getInvokers() {
         return invokers;
     }
 
     @Override
-    public void setConsumerUrl(URL consumerUrl) {
-        this.consumerUrl = consumerUrl;
-        this.overrideConsumerUrl = consumerUrl;
+    public void destroy() {
+        if (isDestroyed()) {
+            return;
+        }
+
+        // unregister.
+        try {
+            if (getRegisteredConsumerUrl() != null && registry != null && registry.isAvailable()) {
+                registry.unregister(getRegisteredConsumerUrl());
+            }
+        } catch (Throwable t) {
+            logger.warn("unexpected error when unregister service " + serviceKey + "from registry" + registry.getUrl(), t);
+        }
+        // unsubscribe.
+        try {
+            if (getConsumerUrl() != null && registry != null && registry.isAvailable()) {
+                registry.unsubscribe(getConsumerUrl(), this);
+            }
+        } catch (Throwable t) {
+            logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
+        }
+        super.destroy(); // must be executed after unsubscribing
+        try {
+            destroyAllInvokers();
+        } catch (Throwable t) {
+            logger.warn("Failed to destroy service " + serviceKey, t);
+        }
+
+        invokersChangedListener = null;
     }
 
+    @Override
+    public void discordAddresses() {
+        try {
+            destroyAllInvokers();
+        } catch (Throwable t) {
+            logger.warn("Failed to destroy service " + serviceKey, t);
+        }
+    }
+
+    private volatile InvokersChangedListener invokersChangedListener;
+    private volatile boolean addressChanged;
+
+    public void setInvokersChangedListener(InvokersChangedListener listener) {
+        this.invokersChangedListener = listener;
+        if (addressChanged) {
+            invokersChangedListener.onChange();
+            this.addressChanged = false;
+        }
+    }
+
+    protected void invokersChanged() {
+        if (invokersChangedListener != null) {
+            invokersChangedListener.onChange();
+            this.addressChanged = false;
+        } else {
+            this.addressChanged = true;
+        }
+    }
+
+    protected abstract void destroyAllInvokers();
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InterfaceCompatibleRegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InterfaceCompatibleRegistryProtocol.java
index 0a06762..b7374a3 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InterfaceCompatibleRegistryProtocol.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InterfaceCompatibleRegistryProtocol.java
@@ -19,24 +19,15 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.registry.Registry;
-import org.apache.dubbo.registry.client.RegistryProtocol;
-import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory;
+import org.apache.dubbo.registry.client.migration.MigrationInvoker;
 import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.Result;
-import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.Cluster;
 import org.apache.dubbo.rpc.cluster.ClusterInvoker;
-import org.apache.dubbo.rpc.cluster.Directory;
 
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.apache.dubbo.common.constants.RegistryConstants.ENABLE_REGISTRY_DIRECTORY_AUTO_MIGRATION;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL;
-import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL;
 import static org.apache.dubbo.registry.Constants.DEFAULT_REGISTRY;
-import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
 
 /**
  * RegistryProtocol
@@ -62,126 +53,21 @@
     }
 
     @Override
-    protected <T> DynamicDirectory<T> createDirectory(Class<T> type, URL url) {
-        return new RegistryDirectory<>(type, url);
+    public <T> ClusterInvoker<T> getInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
+        DynamicDirectory<T> directory = new RegistryDirectory<>(type, url);
+        return doCreateInvoker(directory, cluster, registry, type);
     }
 
-    protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
-        ClusterInvoker<T> invoker = getInvoker(cluster, registry, type, url);
-        ClusterInvoker<T> serviceDiscoveryInvoker = getServiceDiscoveryInvoker(cluster, type, url);
-        ClusterInvoker<T> migrationInvoker = new MigrationInvoker<>(invoker, serviceDiscoveryInvoker);
-
-        return interceptInvoker(migrationInvoker, url);
+    @Override
+    public <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
+        registry = registryFactory.getRegistry(super.getRegistryUrl(url));
+        DynamicDirectory<T> directory = new ServiceDiscoveryRegistryDirectory<>(type, url);
+        return doCreateInvoker(directory, cluster, registry, type);
     }
 
-    protected <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Class<T> type, URL url) {
-        Registry registry = registryFactory.getRegistry(super.getRegistryUrl(url));
-        ClusterInvoker<T> serviceDiscoveryInvoker = null;
-        // enable auto migration from interface address pool to instance address pool
-        boolean autoMigration = url.getParameter(ENABLE_REGISTRY_DIRECTORY_AUTO_MIGRATION, false);
-        if (autoMigration) {
-            DynamicDirectory<T> serviceDiscoveryDirectory = super.createDirectory(type, url);
-            serviceDiscoveryDirectory.setRegistry(registry);
-            serviceDiscoveryDirectory.setProtocol(protocol);
-            Map<String, String> parameters = new HashMap<String, String>(serviceDiscoveryDirectory.getConsumerUrl().getParameters());
-            URL urlToRegistry = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
-            if (serviceDiscoveryDirectory.isShouldRegister()) {
-                serviceDiscoveryDirectory.setRegisteredConsumerUrl(urlToRegistry);
-                registry.register(serviceDiscoveryDirectory.getRegisteredConsumerUrl());
-            }
-            serviceDiscoveryDirectory.buildRouterChain(urlToRegistry);
-            serviceDiscoveryDirectory.subscribe(toSubscribeUrl(urlToRegistry));
-            serviceDiscoveryInvoker = (ClusterInvoker<T>) cluster.join(serviceDiscoveryDirectory);
-        }
-        return serviceDiscoveryInvoker;
-    }
-
-    private static class MigrationInvoker<T> implements ClusterInvoker<T> {
-        private ClusterInvoker<T> invoker;
-        private ClusterInvoker<T> serviceDiscoveryInvoker;
-
-        public MigrationInvoker(ClusterInvoker<T> invoker, ClusterInvoker<T> serviceDiscoveryInvoker) {
-            this.invoker = invoker;
-            this.serviceDiscoveryInvoker = serviceDiscoveryInvoker;
-        }
-
-        public ClusterInvoker<T> getInvoker() {
-            return invoker;
-        }
-
-        public void setInvoker(ClusterInvoker<T> invoker) {
-            this.invoker = invoker;
-        }
-
-        public ClusterInvoker<T> getServiceDiscoveryInvoker() {
-            return serviceDiscoveryInvoker;
-        }
-
-        public void setServiceDiscoveryInvoker(ClusterInvoker<T> serviceDiscoveryInvoker) {
-            this.serviceDiscoveryInvoker = serviceDiscoveryInvoker;
-        }
-
-        @Override
-        public Class<T> getInterface() {
-            return invoker.getInterface();
-        }
-
-        @Override
-        public Result invoke(Invocation invocation) throws RpcException {
-            if (serviceDiscoveryInvoker == null) {
-                return invoker.invoke(invocation);
-            }
-
-            if (invoker.isDestroyed()) {
-                return serviceDiscoveryInvoker.invoke(invocation);
-            }
-            if (serviceDiscoveryInvoker.isAvailable()) {
-                invoker.destroy(); // can be destroyed asynchronously
-                return serviceDiscoveryInvoker.invoke(invocation);
-            }
-            return invoker.invoke(invocation);
-        }
-
-        @Override
-        public URL getUrl() {
-            return invoker.getUrl();
-        }
-
-        @Override
-        public boolean isAvailable() {
-            if (serviceDiscoveryInvoker == null) {
-                return invoker.isAvailable();
-            }
-            return invoker.isAvailable() || serviceDiscoveryInvoker.isAvailable();
-        }
-
-        @Override
-        public void destroy() {
-            if (invoker != null) {
-                invoker.destroy();
-            }
-            if (serviceDiscoveryInvoker != null) {
-                serviceDiscoveryInvoker.destroy();
-            }
-        }
-
-        @Override
-        public URL getRegistryUrl() {
-            return invoker.getRegistryUrl();
-        }
-
-        @Override
-        public Directory<T> getDirectory() {
-            return invoker.getDirectory();
-        }
-
-        @Override
-        public boolean isDestroyed() {
-            if (serviceDiscoveryInvoker == null) {
-                return invoker.isDestroyed();
-            }
-            return invoker.isDestroyed() && serviceDiscoveryInvoker.isDestroyed();
-        }
+    protected <T> ClusterInvoker<T> getMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
+//        ClusterInvoker<T> invoker = getInvoker(cluster, registry, type, url);
+        return new MigrationInvoker<T>(registryProtocol, cluster, registry, type, url, consumerUrl);
     }
 
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InvokersChangedListener.java
similarity index 87%
rename from dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
rename to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InvokersChangedListener.java
index 2042277..5a55a02 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/ConfigurationURL.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/InvokersChangedListener.java
@@ -14,7 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.common;
+package org.apache.dubbo.registry.integration;
 
-public class ConfigurationURL extends URL {
+public interface InvokersChangedListener {
+    void onChange();
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index b8d0b2d..4c65c6a 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -38,7 +38,6 @@
 import org.apache.dubbo.rpc.cluster.Router;

 import org.apache.dubbo.rpc.cluster.RouterChain;

 import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;

-import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;

 import org.apache.dubbo.rpc.cluster.support.ClusterUtils;

 import org.apache.dubbo.rpc.model.ApplicationModel;

 import org.apache.dubbo.rpc.protocol.InvokerWrapper;

@@ -87,6 +86,12 @@
     private static final ConsumerConfigurationListener CONSUMER_CONFIGURATION_LISTENER = new ConsumerConfigurationListener();

     private ReferenceConfigurationListener referenceConfigurationListener;

 

+    // Map<url, Invoker> cache service url to invoker mapping.

+    // The initial value is null and the midway may be assigned to null, please use the local variable reference

+    protected volatile Map<URL, Invoker<T>> urlInvokerMap;

+    // The initial value is null and the midway may be assigned to null, please use the local variable reference

+    protected volatile Set<URL> cachedInvokerUrls;

+

     public RegistryDirectory(Class<T> serviceType, URL url) {

         super(serviceType, url);

     }

@@ -94,7 +99,6 @@
     @Override

     public void subscribe(URL url) {

         setConsumerUrl(url);

-//        overrideConsumerUrl();

         CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);

         referenceConfigurationListener = new ReferenceConfigurationListener(this, url);

         registry.subscribe(url, this);

@@ -109,38 +113,6 @@
     }

 

     @Override

-    public void destroy() {

-        if (isDestroyed()) {

-            return;

-        }

-

-        // unregister.

-        try {

-            if (getRegisteredConsumerUrl() != null && registry != null && registry.isAvailable()) {

-                registry.unregister(getRegisteredConsumerUrl());

-            }

-        } catch (Throwable t) {

-            logger.warn("unexpected error when unregister service " + serviceKey + "from registry" + registry.getUrl(), t);

-        }

-        // unsubscribe.

-        try {

-            if (getConsumerUrl() != null && registry != null && registry.isAvailable()) {

-                registry.unsubscribe(getConsumerUrl(), this);

-            }

-            ExtensionLoader.getExtensionLoader(GovernanceRuleRepository.class).getDefaultExtension()

-                    .removeListener(ApplicationModel.getApplication(), CONSUMER_CONFIGURATION_LISTENER);

-        } catch (Throwable t) {

-            logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);

-        }

-        super.destroy(); // must be executed after unsubscribing

-        try {

-            destroyAllInvokers();

-        } catch (Throwable t) {

-            logger.warn("Failed to destroy service " + serviceKey, t);

-        }

-    }

-

-    @Override

     public synchronized void notify(List<URL> urls) {

         Map<String, List<URL>> categoryUrls = urls.stream()

                 .filter(Objects::nonNull)

@@ -182,7 +154,7 @@
 

     private void refreshOverrideAndInvoker(List<URL> urls) {

         // mock zookeeper://xxx?mock=return null

-        overrideConsumerUrl();

+        overrideDirectoryUrl();

         refreshInvoker(urls);

     }

 

@@ -252,6 +224,9 @@
                 logger.warn("destroyUnusedInvokers error. ", e);

             }

         }

+

+        // notify invokers refreshed

+        this.invokersChanged();

     }

 

     private List<Invoker<T>> toMergeInvokerList(List<Invoker<T>> invokers) {

@@ -388,16 +363,19 @@
      * @return

      */

     private URL mergeUrl(URL providerUrl) {

-        providerUrl = ClusterUtils.mergeProviderUrl(providerUrl, queryMap); // Merge the consumer side parameters

+        providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters

 

         providerUrl = overrideWithConfigurator(providerUrl);

 

         providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker!

 

+        // The combination of directoryUrl and override is at the end of notify, which can't be handled here

+//        this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters

+

         if ((providerUrl.getPath() == null || providerUrl.getPath()

                 .length() == 0) && DUBBO_PROTOCOL.equals(providerUrl.getProtocol())) { // Compatible version 1.0

             //fix by tony.chenl DUBBO-44

-            String path = getConsumerUrl().getParameter(INTERFACE_KEY);

+            String path = directoryUrl.getParameter(INTERFACE_KEY);

             if (path != null) {

                 int i = path.indexOf('/');

                 if (i >= 0) {

@@ -440,7 +418,8 @@
     /**

      * Close all invokers

      */

-    private void destroyAllInvokers() {

+    @Override

+    protected void destroyAllInvokers() {

         Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference

         if (localUrlInvokerMap != null) {

             for (Invoker<T> invoker : new ArrayList<>(localUrlInvokerMap.values())) {

@@ -453,6 +432,7 @@
             localUrlInvokerMap.clear();

         }

         invokers = null;

+        cachedInvokerUrls = null;

     }

 

     /**

@@ -535,6 +515,11 @@
         return invokers;

     }

 

+    @Override

+    public URL getConsumerUrl() {

+        return this.overrideDirectoryUrl;

+    }

+

     public URL getRegisteredConsumerUrl() {

         return registeredConsumerUrl;

     }

@@ -597,25 +582,23 @@
         return StringUtils.isEmpty(url.getParameter(COMPATIBLE_CONFIG_KEY));

     }

 

-    private void overrideConsumerUrl() {

+    private void overrideDirectoryUrl() {

         // merge override parameters

-        this.overrideConsumerUrl = getConsumerUrl();

-        if (overrideConsumerUrl != null) {

-            List<Configurator> localConfigurators = this.configurators; // local reference

-            doOverrideUrl(localConfigurators);

-            List<Configurator> localAppDynamicConfigurators = CONSUMER_CONFIGURATION_LISTENER.getConfigurators(); // local reference

-            doOverrideUrl(localAppDynamicConfigurators);

-            if (referenceConfigurationListener != null) {

-                List<Configurator> localDynamicConfigurators = referenceConfigurationListener.getConfigurators(); // local reference

-                doOverrideUrl(localDynamicConfigurators);

-            }

+        this.overrideDirectoryUrl = directoryUrl;

+        List<Configurator> localConfigurators = this.configurators; // local reference

+        doOverrideUrl(localConfigurators);

+        List<Configurator> localAppDynamicConfigurators = CONSUMER_CONFIGURATION_LISTENER.getConfigurators(); // local reference

+        doOverrideUrl(localAppDynamicConfigurators);

+        if (referenceConfigurationListener != null) {

+            List<Configurator> localDynamicConfigurators = referenceConfigurationListener.getConfigurators(); // local reference

+            doOverrideUrl(localDynamicConfigurators);

         }

     }

 

     private void doOverrideUrl(List<Configurator> configurators) {

         if (CollectionUtils.isNotEmpty(configurators)) {

             for (Configurator configurator : configurators) {

-                this.overrideConsumerUrl = configurator.configure(overrideConsumerUrl);

+                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);

             }

         }

     }

diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
similarity index 93%
rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/RegistryProtocol.java
rename to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
index 64ffe04..1f5a6c2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/RegistryProtocol.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.client;
+package org.apache.dubbo.registry.integration;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.ConfigurationUtils;
@@ -31,10 +31,8 @@
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.RegistryFactory;
 import org.apache.dubbo.registry.RegistryService;
-import org.apache.dubbo.registry.integration.AbstractConfiguratorListener;
-import org.apache.dubbo.registry.integration.DynamicDirectory;
-import org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol;
-import org.apache.dubbo.registry.integration.RegistryProtocolListener;
+import org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory;
+import org.apache.dubbo.registry.client.migration.ServiceDiscoveryMigrationInvoker;
 import org.apache.dubbo.registry.retry.ReExportTask;
 import org.apache.dubbo.registry.support.SkipFailbackWrapperException;
 import org.apache.dubbo.rpc.Exporter;
@@ -48,6 +46,7 @@
 import org.apache.dubbo.rpc.cluster.Configurator;
 import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
 import org.apache.dubbo.rpc.cluster.support.MergeableCluster;
+import org.apache.dubbo.rpc.cluster.support.migration.MigrationClusterInvoker;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ProviderModel;
 import org.apache.dubbo.rpc.protocol.InvokerWrapper;
@@ -444,32 +443,48 @@
         String group = qs.get(GROUP_KEY);
         if (group != null && group.length() > 0) {
             if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
-                return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url);
+                return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url, qs);
             }
         }
 
         Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
-        return doRefer(cluster, registry, type, url);
+        return doRefer(cluster, registry, type, url, qs);
     }
 
-    protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
-        return interceptInvoker(getInvoker(cluster, registry, type, url), url);
+    protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url, Map<String, String> parameters) {
+        URL consumerUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
+        ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);
+        return interceptInvoker(migrationInvoker, url, consumerUrl);
     }
 
-    protected <T> Invoker<T> interceptInvoker(ClusterInvoker<T> invoker, URL url) {
+    protected <T> ClusterInvoker<T> getMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
+        return new ServiceDiscoveryMigrationInvoker<T>(registryProtocol, cluster, registry, type, url, consumerUrl);
+    }
+
+    protected <T> Invoker<T> interceptInvoker(ClusterInvoker<T> invoker, URL url, URL consumerUrl) {
         List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
         if (CollectionUtils.isEmpty(listeners)) {
             return invoker;
         }
 
         for (RegistryProtocolListener listener : listeners) {
-            listener.onRefer(this, invoker);
+            listener.onRefer(this, invoker, consumerUrl);
         }
         return invoker;
     }
 
-    protected <T> ClusterInvoker<T> getInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
-        DynamicDirectory<T> directory = createDirectory(type, url);
+    public <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
+        DynamicDirectory<T> directory = new ServiceDiscoveryRegistryDirectory<>(type, url);
+        return doCreateInvoker(directory, cluster, registry, type);
+    }
+
+    public <T> ClusterInvoker<T> getInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
+        // FIXME, this method is currently not used, create the right registry before enable.
+        DynamicDirectory<T> directory = new RegistryDirectory<>(type, url);
+        return doCreateInvoker(directory, cluster, registry, type);
+    }
+
+    protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
         directory.setRegistry(registry);
         directory.setProtocol(protocol);
         // all attributes of REFER_KEY
@@ -485,23 +500,17 @@
         return (ClusterInvoker<T>) cluster.join(directory);
     }
 
-    protected <T> DynamicDirectory<T> createDirectory(Class<T> type, URL url) {
-        return new ServiceDiscoveryRegistryDirectory<>(type, url);
+    public <T> void reRefer(ClusterInvoker<?> invoker, URL newSubscribeUrl) {
+        if (!(invoker instanceof MigrationClusterInvoker)) {
+            logger.error("Only invoker type of MigrationClusterInvoker supports reRefer, current invoker is " + invoker.getClass());
+            return;
+        }
+
+        MigrationClusterInvoker<?> migrationClusterInvoker = (MigrationClusterInvoker<?>)invoker;
+        migrationClusterInvoker.reRefer(newSubscribeUrl);
     }
 
-    public <T> void reRefer(DynamicDirectory<T> directory, URL newSubscribeUrl) {
-        URL oldSubscribeUrl = directory.getRegisteredConsumerUrl();
-        Registry registry = directory.getRegistry();
-        registry.unregister(directory.getRegisteredConsumerUrl());
-        directory.unSubscribe(toSubscribeUrl(oldSubscribeUrl));
-        registry.register(directory.getRegisteredConsumerUrl());
-
-        directory.setRegisteredConsumerUrl(newSubscribeUrl);
-        directory.buildRouterChain(newSubscribeUrl);
-        directory.subscribe(toSubscribeUrl(newSubscribeUrl));
-    }
-
-    protected static URL toSubscribeUrl(URL url) {
+    public static URL toSubscribeUrl(URL url) {
         return url.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY);
     }
 
@@ -529,7 +538,7 @@
             }
         }
 
-        List<Exporter<?>> exporters = new ArrayList<>(bounds.values());
+        List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(bounds.values());
         for (Exporter<?> exporter : exporters) {
             exporter.unexport();
         }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocolListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocolListener.java
index 5bf47ca..c4cade5 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocolListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocolListener.java
@@ -18,9 +18,8 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.registry.client.RegistryProtocol;
 import org.apache.dubbo.rpc.Exporter;
-import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 
 /**
  * RegistryProtocol listener is introduced to provide a chance to user to customize or change export and refer behavior
@@ -42,9 +41,10 @@
      *
      * @param registryProtocol RegistryProtocol instance
      * @param invoker          invoker
+     * @param url
      * @see RegistryProtocol#refer(Class, URL)
      */
-    void onRefer(RegistryProtocol registryProtocol, Invoker<?> invoker);
+    void onRefer(RegistryProtocol registryProtocol, ClusterInvoker<?> invoker, URL url);
 
     /**
      * Notify RegistryProtocol's listeners when the protocol is destroyed
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.MigrationAddressComparator b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.MigrationAddressComparator
new file mode 100644
index 0000000..b7fa71c
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.MigrationAddressComparator
@@ -0,0 +1 @@
+default=org.apache.dubbo.registry.client.migration.DefaultMigrationAddressComparator
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener
index d60633c..24943f7 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener
@@ -1 +1 @@
-service-discovery=org.apache.dubbo.registry.client.ServiceDiscoveryRegistryProtocolListener
\ No newline at end of file
+migration=org.apache.dubbo.registry.client.migration.MigrationRuleListener
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
index 5dda00e..4c3b148 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
@@ -1,2 +1,2 @@
 registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
-service-discovery-registry=org.apache.dubbo.registry.client.RegistryProtocol
\ No newline at end of file
+service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/ZKTools.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/ZKTools.java
index 6e71ae6..86ff3a2 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/ZKTools.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/ZKTools.java
@@ -58,8 +58,8 @@
             }
         }, executor);
 
-        tesConditionRule();
-
+        testMigrationRule();
+//        tesConditionRule();
 //        testStartupConfig();
 //        testProviderConfig();
 //        testPathCache();
@@ -68,6 +68,22 @@
 //       Thread.sleep(100000);
     }
 
+    public static void testMigrationRule() {
+        String serviceStr = "---\n" +
+                "key: demo-consumer\n" +
+                "step: INTERFACE_FIRST\n" +
+                "...";
+        try {
+            String servicePath = "/dubbo/config/DUBBO_SERVICEDISCOVERY_MIGRATION/demo-consumer.migration";
+            if (client.checkExists().forPath(servicePath) == null) {
+                client.create().creatingParentsIfNeeded().forPath(servicePath);
+            }
+            setData(servicePath, serviceStr);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
     public static void testStartupConfig() {
         String str = "dubbo.registry.address=zookeeper://127.0.0.1:2181\n" +
                 "dubbo.registry.group=dubboregistrygroup1\n" +
diff --git a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscovery.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscovery.java
index 8b0f322..47474f4 100644
--- a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscovery.java
@@ -88,7 +88,7 @@
     private ExecutorService notifierExecutor = newCachedThreadPool(
             new NamedThreadFactory("dubbo-service-discovery-consul-notifier", true));
     private Map<String, ConsulNotifier> notifiers = new ConcurrentHashMap<>();
-    private Map<String, TtlScheduler> ttlSchedulers = new ConcurrentHashMap<>();
+    private TtlScheduler ttlScheduler;
     private long checkPassInterval;
     private URL url;
 
@@ -123,6 +123,7 @@
         int port = url.getPort() != 0 ? url.getPort() : DEFAULT_PORT;
         checkPassInterval = url.getParameter(CHECK_PASS_INTERVAL, DEFAULT_CHECK_PASS_INTERVAL);
         client = new ConsulClient(host, port);
+        ttlScheduler = new TtlScheduler(checkPassInterval, client);
         this.tag = registryURL.getParameter(QUERY_TAG);
         this.registeringTags.addAll(getRegisteringTags(url));
         this.aclToken = ACL_TOKEN.getValue(registryURL);
@@ -176,23 +177,18 @@
 
     @Override
     public void destroy() {
-        if (!notifiers.isEmpty()) {
-            notifiers.forEach((key, notifier) -> notifier.stop());
-            notifiers.clear();
-        }
-
+        notifiers.forEach((_k, notifier) -> {
+            if (notifier != null) {
+                notifier.stop();
+            }
+        });
+        notifiers.clear();
         notifierExecutor.shutdownNow();
-
-        if (!ttlSchedulers.isEmpty()) {
-            ttlSchedulers.forEach((key, ttlScheduler) -> ttlScheduler.stop());
-            ttlSchedulers.clear();
-        }
+        ttlScheduler.stop();
     }
 
     @Override
     public void register(ServiceInstance serviceInstance) throws RuntimeException {
-        super.register(serviceInstance);
-        TtlScheduler ttlScheduler = ttlSchedulers.computeIfAbsent(serviceInstance.getServiceName(), name -> new TtlScheduler(checkPassInterval, client));
         NewService consulService = buildService(serviceInstance);
         ttlScheduler.add(consulService.getId());
         client.agentServiceRegister(consulService, aclToken);
@@ -200,14 +196,13 @@
 
     @Override
     public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
-        for (String serviceName : listener.getServiceNames()) {
+        Set<String> serviceNames = listener.getServiceNames();
+        for (String serviceName : serviceNames) {
             ConsulNotifier notifier = notifiers.get(serviceName);
-
             if (notifier == null) {
                 Response<List<HealthService>> response = getHealthServices(serviceName, -1, buildWatchTimeout());
                 Long consulIndex = response.getConsulIndex();
                 notifier = new ConsulNotifier(serviceName, consulIndex);
-                notifiers.put(serviceName, notifier);
             }
             notifierExecutor.execute(notifier);
         }
@@ -215,18 +210,14 @@
 
     @Override
     public void update(ServiceInstance serviceInstance) throws RuntimeException {
-        super.update(serviceInstance);
         // TODO
         // client.catalogRegister(buildCatalogService(serviceInstance));
     }
 
     @Override
     public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
-        TtlScheduler ttlScheduler = ttlSchedulers.get(serviceInstance.getServiceName());
         String id = buildId(serviceInstance);
-        if (ttlScheduler != null) {
-            ttlScheduler.remove(id);
-        }
+        ttlScheduler.remove(id);
         client.agentServiceDeregister(id, aclToken);
     }
 
@@ -243,7 +234,6 @@
     public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
         Response<List<HealthService>> response = getHealthServices(serviceName, -1, buildWatchTimeout());
         Long consulIndex = response.getConsulIndex();
-
         ConsulNotifier notifier = notifiers.get(serviceName);
         if (notifier == null) {
             notifier = new ConsulNotifier(serviceName, consulIndex);
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~HEAD
similarity index 74%
rename from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
rename to dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~HEAD
index 7bef1f5..bd77db8 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~HEAD
@@ -14,15 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+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;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class ConsulServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
+
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new ConsulServiceDiscovery();
     }
+
 }
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~dubbo-master
similarity index 74%
copy from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
copy to dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~dubbo-master
index 7bef1f5..bd77db8 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryFactory.java~dubbo-master
@@ -14,15 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+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;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class ConsulServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
+
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new ConsulServiceDiscovery();
     }
+
 }
diff --git a/dubbo-registry/dubbo-registry-consul/src/test/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-consul/src/test/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryTest.java
index 9f10d0a..2b6f526 100644
--- a/dubbo-registry/dubbo-registry-consul/src/test/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryTest.java
+++ b/dubbo-registry/dubbo-registry-consul/src/test/java/org/apache/dubbo/registry/consul/ConsulServiceDiscoveryTest.java
@@ -26,6 +26,7 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
@@ -35,6 +36,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+@Disabled
 public class ConsulServiceDiscoveryTest {
 
     private URL url;
diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java
index 9ddf3b0..bb1f925 100644
--- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java
+++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java
@@ -58,7 +58,9 @@
 import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;

 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;

 import static org.apache.dubbo.common.constants.CommonConstants.DISABLED_KEY;

+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;

 import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY;

+import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;

 import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;

 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;

 import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;

@@ -68,6 +70,7 @@
 import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGORY;

 import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL;

 import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL;

+import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;

 import static org.apache.dubbo.rpc.Constants.MOCK_KEY;

 import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK;

 import static org.apache.dubbo.rpc.cluster.Constants.MOCK_PROTOCOL;

@@ -144,13 +147,17 @@
     @Test

     public void test_Constructor_CheckStatus() throws Exception {

         URL url = URL.valueOf("notsupported://10.20.30.40/" + service + "?a=b").addParameterAndEncoded(REFER_KEY,

-                "foo=bar");

+                "foo=bar&" + REGISTER_IP_KEY + "=10.20.30.40&" + INTERFACE_KEY + "=" + service);

         RegistryDirectory reg = getRegistryDirectory(url);

         Field field = reg.getClass().getSuperclass().getSuperclass().getDeclaredField("queryMap");

         field.setAccessible(true);

         Map<String, String> queryMap = (Map<String, String>) field.get(reg);

         Assertions.assertEquals("bar", queryMap.get("foo"));

-        Assertions.assertEquals(url.setProtocol(CONSUMER_PROTOCOL).clearParameters().addParameter("foo", "bar"), reg.getConsumerUrl());

+        URL expected = url.setProtocol(DUBBO_PROTOCOL).clearParameters()

+                .addParameter("foo", "bar")

+                .addParameter(REGISTER_IP_KEY, "10.20.30.40")

+                .addParameter(INTERFACE_KEY, service);

+        Assertions.assertEquals(expected, reg.getConsumerUrl());

     }

 

     @Test

diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
index 596a217..aedf313 100644
--- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
+++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
@@ -19,10 +19,12 @@
 import org.apache.dubbo.common.URL;

 import org.apache.dubbo.common.config.ConfigurationUtils;

 import org.apache.dubbo.common.extension.ExtensionLoader;

+import org.apache.dubbo.config.ApplicationConfig;

+import org.apache.dubbo.config.context.ConfigManager;

 import org.apache.dubbo.registry.NotifyListener;

 import org.apache.dubbo.registry.RegistryFactory;

 import org.apache.dubbo.registry.RegistryService;

-import org.apache.dubbo.registry.client.RegistryProtocol;

+import org.apache.dubbo.registry.integration.RegistryProtocol;

 import org.apache.dubbo.registry.support.AbstractRegistry;

 import org.apache.dubbo.remoting.exchange.ExchangeClient;

 import org.apache.dubbo.rpc.Exporter;

@@ -37,6 +39,7 @@
 import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;

 

 import org.apache.commons.lang3.ArrayUtils;

+import org.junit.jupiter.api.AfterEach;

 import org.junit.jupiter.api.Assertions;

 import org.junit.jupiter.api.BeforeEach;

 import org.junit.jupiter.api.Test;

@@ -44,7 +47,7 @@
 import java.util.ArrayList;

 import java.util.List;

 

-import static org.apache.dubbo.registry.client.RegistryProtocol.DEFAULT_REGISTER_PROVIDER_KEYS;

+import static org.apache.dubbo.registry.integration.RegistryProtocol.DEFAULT_REGISTER_PROVIDER_KEYS;

 import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;

 import static org.junit.jupiter.api.Assertions.assertEquals;

 import static org.junit.jupiter.api.Assertions.assertFalse;

@@ -61,7 +64,7 @@
     }

 

     final String service = DemoService.class.getName() + ":1.0.0";

-    final String serviceUrl = "dubbo://127.0.0.1:9453/" + service + "?notify=true&methods=test1,test2&side=con&side=consumer";

+    final String serviceUrl = "dubbo://127.0.0.1:9453/" + service + "?notify=true&methods=test1,test2&side=con&side=consumer&register.ip=127.0.0.1";

     final URL registryUrl = URL.valueOf("registry://127.0.0.1:9090/");

     final private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

 

@@ -72,9 +75,18 @@
     @BeforeEach

     public void setUp() {

         ApplicationModel.setApplication("RegistryProtocolTest");

+        ConfigManager configManager = ApplicationModel.getConfigManager();

+        ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-demo-provider");

+        configManager.setApplication(applicationConfig);

         ApplicationModel.getServiceRepository().registerService(RegistryService.class);

     }

 

+    @AfterEach

+    public void reset() {

+        ApplicationModel.getConfigManager().destroy();

+    }

+

+

     @Test

     public void testDefaultPort() {

         RegistryProtocol registryProtocol = getRegistryProtocol();

diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryStatusCheckerTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryStatusCheckerTest.java
index 62dcad1..d8ae08a 100644
--- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryStatusCheckerTest.java
+++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryStatusCheckerTest.java
@@ -64,7 +64,7 @@
         assertEquals(Status.Level.OK, new RegistryStatusChecker().check().getLevel());

 

         String message = new RegistryStatusChecker().check().getMessage();

-        Assertions.assertTrue(message.contains(registryUrl.getAddress() + "(connected)"));

-        Assertions.assertTrue(message.contains(registryUrl2.getAddress() + "(connected)"));

+        Assertions.assertTrue(message.contains(registryUrl.getHost() + "(connected)"));

+        Assertions.assertTrue(message.contains(registryUrl2.getHost() + "(connected)"));

     }

 }
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-etcd3/src/main/java/org/apache/dubbo/registry/etcd/EtcdServiceDiscovery.java b/dubbo-registry/dubbo-registry-etcd3/src/main/java/org/apache/dubbo/registry/etcd/EtcdServiceDiscovery.java
index 4bcdb59..39e5818 100644
--- a/dubbo-registry/dubbo-registry-etcd3/src/main/java/org/apache/dubbo/registry/etcd/EtcdServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-etcd3/src/main/java/org/apache/dubbo/registry/etcd/EtcdServiceDiscovery.java
@@ -102,7 +102,6 @@
 
     @Override
     public void register(ServiceInstance serviceInstance) throws RuntimeException {
-        super.register(serviceInstance);
         try {
             this.serviceInstance = serviceInstance;
             String path = toPath(serviceInstance);
@@ -128,7 +127,6 @@
 
     @Override
     public void update(ServiceInstance serviceInstance) throws RuntimeException {
-        super.register(serviceInstance);
         try {
             String path = toPath(serviceInstance);
             etcdClient.putEphemeral(path, new Gson().toJson(serviceInstance));
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
index 8ad2c10..2e4ca35 100644
--- a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
@@ -118,7 +118,7 @@
 
     @Override
     public boolean isAvailable() {
-        boolean available = serviceRegistries.isEmpty() ? true : false;
+        boolean available = serviceRegistries.isEmpty();
         for (Registry serviceRegistry : serviceRegistries.values()) {
             if (serviceRegistry.isAvailable()) {
                 available = true;
@@ -128,7 +128,7 @@
             return false;
         }
 
-        available = referenceRegistries.isEmpty() ? true : false;
+        available = referenceRegistries.isEmpty();
         for (Registry referenceRegistry : referenceRegistries.values()) {
             if (referenceRegistry.isAvailable()) {
                 available = true;
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscovery.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscovery.java
new file mode 100644
index 0000000..0302c9a
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscovery.java
@@ -0,0 +1,177 @@
+/*
+ * 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.multiple;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.utils.DefaultPage;
+import org.apache.dubbo.common.utils.Page;
+import org.apache.dubbo.event.ConditionalEventListener;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
+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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MultipleRegistryServiceDiscovery implements ServiceDiscovery {
+    public static final String REGISTRY_PREFIX_KEY = "child.";
+    private final Map<String, ServiceDiscovery> serviceDiscoveries = new ConcurrentHashMap<>();
+    private URL registryURL;
+    private ServiceInstance serviceInstance;
+    private String applicationName;
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        this.registryURL = registryURL;
+        this.applicationName = registryURL.getParameter(CommonConstants.APPLICATION_KEY);
+
+        Map<String, String> parameters = registryURL.getParameters();
+        for (String key : parameters.keySet()) {
+            if (key.startsWith(REGISTRY_PREFIX_KEY)) {
+                URL url = URL.valueOf(registryURL.getParameter(key)).addParameter(CommonConstants.APPLICATION_KEY, applicationName)
+                        .addParameter("registry-type", "service");
+                ServiceDiscovery serviceDiscovery = ServiceDiscoveryFactory.getExtension(url).getServiceDiscovery(url);
+                serviceDiscovery.initialize(url);
+                serviceDiscoveries.put(key, serviceDiscovery);
+            }
+        }
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+        for (ServiceDiscovery serviceDiscovery : serviceDiscoveries.values()) {
+            serviceDiscovery.destroy();
+        }
+    }
+
+    @Override
+    public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+        serviceDiscoveries.values().forEach(serviceDiscovery -> serviceDiscovery.register(serviceInstance));
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        serviceDiscoveries.values().forEach(serviceDiscovery -> serviceDiscovery.update(serviceInstance));
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        serviceDiscoveries.values().forEach(serviceDiscovery -> serviceDiscovery.unregister(serviceInstance));
+    }
+
+    @Override
+    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
+        MultiServiceInstancesChangedListener multiListener = new MultiServiceInstancesChangedListener(listener);
+
+        for (String registryKey : serviceDiscoveries.keySet()) {
+            SingleServiceInstancesChangedListener singleListener = new SingleServiceInstancesChangedListener(listener.getServiceNames(), serviceDiscoveries.get(registryKey), multiListener);
+            multiListener.putSingleListener(registryKey, singleListener);
+            serviceDiscoveries.get(registryKey).addServiceInstancesChangedListener(singleListener);
+        }
+    }
+
+    @Override
+    public Page<ServiceInstance> getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) throws NullPointerException, IllegalArgumentException, UnsupportedOperationException {
+
+        List<ServiceInstance> serviceInstanceList = new ArrayList<>();
+        for (ServiceDiscovery serviceDiscovery : serviceDiscoveries.values()) {
+            Page<ServiceInstance> serviceInstancePage =  serviceDiscovery.getInstances(serviceName, offset, pageSize, healthyOnly);
+            serviceInstanceList.addAll(serviceInstancePage.getData());
+        }
+
+        return new DefaultPage<>(offset, pageSize, serviceInstanceList, serviceInstanceList.size());
+    }
+
+    @Override
+    public Set<String> getServices() {
+        Set<String> services = new HashSet<>();
+        for (ServiceDiscovery serviceDiscovery : serviceDiscoveries.values()) {
+            services.addAll(serviceDiscovery.getServices());
+        }
+        return services;
+    }
+
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+
+    protected  static class MultiServiceInstancesChangedListener  implements ConditionalEventListener<ServiceInstancesChangedEvent> {
+        private final ServiceInstancesChangedListener sourceListener;
+        private final Map<String, SingleServiceInstancesChangedListener> singleListenerMap = new ConcurrentHashMap<>();
+
+        public MultiServiceInstancesChangedListener(ServiceInstancesChangedListener sourceListener) {
+            this.sourceListener = sourceListener;
+        }
+
+        @Override
+        public boolean accept(ServiceInstancesChangedEvent event) {
+            return sourceListener.getServiceNames().contains(event.getServiceName());
+        }
+
+        @Override
+        public void onEvent(ServiceInstancesChangedEvent event) {
+            List<ServiceInstance> serviceInstances = new ArrayList<>();
+            for (SingleServiceInstancesChangedListener singleListener : singleListenerMap.values()) {
+                if (null != singleListener.event && null != singleListener.event.getServiceInstances()) {
+                    for (ServiceInstance serviceInstance: singleListener.event.getServiceInstances()) {
+                        if (!serviceInstances.contains(serviceInstance)) {
+                            serviceInstances.add(serviceInstance);
+                        }
+                    }
+                }
+            }
+
+            sourceListener.onEvent(new ServiceInstancesChangedEvent(event.getServiceName(), serviceInstances));
+        }
+
+        public void putSingleListener(String registryKey, SingleServiceInstancesChangedListener singleListener) {
+            singleListenerMap.put(registryKey, singleListener);
+        }
+    }
+
+    protected  static class SingleServiceInstancesChangedListener extends ServiceInstancesChangedListener {
+        private final MultiServiceInstancesChangedListener multiListener;
+        volatile  ServiceInstancesChangedEvent event;
+
+        public SingleServiceInstancesChangedListener(Set<String> serviceNames, ServiceDiscovery serviceDiscovery, MultiServiceInstancesChangedListener multiListener) {
+            super(serviceNames, serviceDiscovery);
+            this.multiListener = multiListener;
+        }
+
+        @Override
+        public void onEvent(ServiceInstancesChangedEvent event) {
+            this.event = event;
+            if (multiListener != null) {
+                multiListener.onEvent(event);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscoveryFactory.java
similarity index 73%
copy from dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
copy to dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscoveryFactory.java
index 7bef1f5..37d7de2 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistryServiceDiscoveryFactory.java
@@ -14,15 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.multicast;
+package org.apache.dubbo.registry.multiple;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
-import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
 
-public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
+public class MultipleRegistryServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory  {
     @Override
-    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
-        return new MulticastServiceDiscovery();
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new MultipleRegistryServiceDiscovery();
     }
 }
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..95e2d98
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+multiple=org.apache.dubbo.registry.multiple.MultipleRegistryServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..710a207
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+multiple=org.apache.dubbo.registry.multiple.MultipleRegistryServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
index 19b0026..a1067f7 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
@@ -81,10 +81,13 @@
 
     @Override
     public void update(ServiceInstance serviceInstance) throws RuntimeException {
-        super.update(serviceInstance);
         // TODO: Nacos should support
-        unregister(serviceInstance);
-        register(serviceInstance);
+        if (this.serviceInstance == null) {
+            register(serviceInstance);
+        } else {
+            unregister(serviceInstance);
+            register(serviceInstance);
+        }
     }
 
     @Override
@@ -136,11 +139,6 @@
         return registryURL;
     }
 
-    @Override
-    public ServiceInstance getLocalInstance() {
-        return null;
-    }
-
     private void handleEvent(NamingEvent event, ServiceInstancesChangedListener listener) {
         String serviceName = event.getServiceName();
         List<ServiceInstance> serviceInstances = event.getInstances()
diff --git a/dubbo-registry/dubbo-registry-sofa/src/main/java/org/apache/dubbo/registry/sofa/SofaRegistryServiceDiscovery.java b/dubbo-registry/dubbo-registry-sofa/src/main/java/org/apache/dubbo/registry/sofa/SofaRegistryServiceDiscovery.java
index 77e7441..86a7dc3 100644
--- a/dubbo-registry/dubbo-registry-sofa/src/main/java/org/apache/dubbo/registry/sofa/SofaRegistryServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-sofa/src/main/java/org/apache/dubbo/registry/sofa/SofaRegistryServiceDiscovery.java
@@ -16,6 +16,17 @@
  */
 package org.apache.dubbo.registry.sofa;
 
+import com.alipay.sofa.registry.client.api.Publisher;
+import com.alipay.sofa.registry.client.api.RegistryClientConfig;
+import com.alipay.sofa.registry.client.api.Subscriber;
+import com.alipay.sofa.registry.client.api.model.RegistryType;
+import com.alipay.sofa.registry.client.api.model.UserData;
+import com.alipay.sofa.registry.client.api.registration.PublisherRegistration;
+import com.alipay.sofa.registry.client.api.registration.SubscriberRegistration;
+import com.alipay.sofa.registry.client.provider.DefaultRegistryClient;
+import com.alipay.sofa.registry.client.provider.DefaultRegistryClientConfigBuilder;
+import com.alipay.sofa.registry.core.model.ScopeEnum;
+import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -29,18 +40,6 @@
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
 import org.apache.dubbo.rpc.RpcException;
 
-import com.alipay.sofa.registry.client.api.Publisher;
-import com.alipay.sofa.registry.client.api.RegistryClientConfig;
-import com.alipay.sofa.registry.client.api.Subscriber;
-import com.alipay.sofa.registry.client.api.model.RegistryType;
-import com.alipay.sofa.registry.client.api.model.UserData;
-import com.alipay.sofa.registry.client.api.registration.PublisherRegistration;
-import com.alipay.sofa.registry.client.api.registration.SubscriberRegistration;
-import com.alipay.sofa.registry.client.provider.DefaultRegistryClient;
-import com.alipay.sofa.registry.client.provider.DefaultRegistryClientConfigBuilder;
-import com.alipay.sofa.registry.core.model.ScopeEnum;
-import com.google.gson.Gson;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -53,6 +52,7 @@
 import static org.apache.dubbo.registry.sofa.SofaRegistryConstants.LOCAL_DATA_CENTER;
 import static org.apache.dubbo.registry.sofa.SofaRegistryConstants.LOCAL_REGION;
 
+
 public class SofaRegistryServiceDiscovery implements ServiceDiscovery {
     private static final Logger LOGGER = LoggerFactory.getLogger(SofaRegistryServiceDiscovery.class);
 
diff --git a/dubbo-registry/dubbo-registry-sofa/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-sofa/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..d16d75c
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-sofa/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+sofa=org.apache.dubbo.registry.sofa.SofaRegistryServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
index a4cdd6c..a1277da 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java
@@ -23,7 +23,7 @@
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.DefaultPage;
 import org.apache.dubbo.common.utils.Page;
-import org.apache.dubbo.event.EventDispatcher;
+import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
@@ -51,26 +51,22 @@
  * Zookeeper {@link ServiceDiscovery} implementation based on
  * <a href="https://curator.apache.org/curator-x-discovery/index.html">Apache Curator X Discovery</a>
  */
-public class ZookeeperServiceDiscovery implements ServiceDiscovery {
+public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private URL registryURL;
 
-    private EventDispatcher dispatcher;
-
     private CuratorFramework curatorFramework;
 
     private String rootPath;
 
     private org.apache.curator.x.discovery.ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
 
-    private ServiceInstance serviceInstance;
-
     /**
      * The Key is watched Zookeeper path, the value is an instance of {@link CuratorWatcher}
      */
-    private final Map<String, CuratorWatcher> watcherCaches = new ConcurrentHashMap<>();
+    private final Map<String, ZookeeperServiceDiscoveryChangeWatcher> watcherCaches = new ConcurrentHashMap<>();
 
     @Override
     public void initialize(URL registryURL) throws Exception {
@@ -90,20 +86,13 @@
         serviceDiscovery.close();
     }
 
-    @Override
-    public ServiceInstance getLocalInstance() {
-        return serviceInstance;
-    }
-
     public void register(ServiceInstance serviceInstance) throws RuntimeException {
-        this.serviceInstance = serviceInstance;
         doInServiceRegistry(serviceDiscovery -> {
             serviceDiscovery.registerService(build(serviceInstance));
         });
     }
 
     public void update(ServiceInstance serviceInstance) throws RuntimeException {
-        this.serviceInstance = serviceInstance;
         if (isInstanceUpdated(serviceInstance)) {
             doInServiceRegistry(serviceDiscovery -> {
                 serviceDiscovery.updateService(build(serviceInstance));
@@ -135,25 +124,30 @@
 
             List<ServiceInstance> serviceInstances = new LinkedList<>();
 
-            List<String> serviceIds = new LinkedList<>(curatorFramework.getChildren().forPath(p));
+            int totalSize = 0;
+            try {
+                List<String> serviceIds = new LinkedList<>(curatorFramework.getChildren().forPath(p));
 
-            int totalSize = serviceIds.size();
+                totalSize = serviceIds.size();
 
-            Iterator<String> iterator = serviceIds.iterator();
+                Iterator<String> iterator = serviceIds.iterator();
 
-            for (int i = 0; i < offset; i++) {
-                if (iterator.hasNext()) { // remove the elements from 0 to offset
-                    iterator.next();
-                    iterator.remove();
+                for (int i = 0; i < offset; i++) {
+                    if (iterator.hasNext()) { // remove the elements from 0 to offset
+                        iterator.next();
+                        iterator.remove();
+                    }
                 }
-            }
 
-            for (int i = 0; i < pageSize; i++) {
-                if (iterator.hasNext()) {
-                    String serviceId = iterator.next();
-                    ServiceInstance serviceInstance = build(serviceDiscovery.queryForInstance(serviceName, serviceId));
-                    serviceInstances.add(serviceInstance);
+                for (int i = 0; i < pageSize; i++) {
+                    if (iterator.hasNext()) {
+                        String serviceId = iterator.next();
+                        ServiceInstance serviceInstance = build(serviceDiscovery.queryForInstance(serviceName, serviceId));
+                        serviceInstances.add(serviceInstance);
+                    }
                 }
+            } catch (KeeperException.NoNodeException e) {
+                logger.warn(p + " path not exist.", e);
             }
 
             return new DefaultPage<>(offset, pageSize, serviceInstances, totalSize);
@@ -166,6 +160,14 @@
         listener.getServiceNames().forEach(serviceName -> registerServiceWatcher(serviceName, listener));
     }
 
+    @Override
+    public void removeServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws IllegalArgumentException {
+        listener.getServiceNames().forEach(serviceName -> {
+            ZookeeperServiceDiscoveryChangeWatcher watcher = watcherCaches.remove(serviceName);
+            watcher.stopWatching();
+        });
+    }
+
     private void doInServiceRegistry(ThrowableConsumer<org.apache.curator.x.discovery.ServiceDiscovery> consumer) {
         ThrowableConsumer.execute(serviceDiscovery, s -> {
             consumer.accept(s);
@@ -178,6 +180,17 @@
 
     protected void registerServiceWatcher(String serviceName, ServiceInstancesChangedListener listener) {
         String path = buildServicePath(serviceName);
+        try {
+            curatorFramework.create().forPath(path);
+        } catch (KeeperException.NodeExistsException e) {
+            // ignored
+            if (logger.isDebugEnabled()) {
+                logger.debug(e);
+            }
+        } catch (Exception e) {
+            throw new IllegalStateException("registerServiceWatcher create path=" + path + " fail.", e);
+        }
+
         CuratorWatcher watcher = watcherCaches.computeIfAbsent(path, key ->
                 new ZookeeperServiceDiscoveryChangeWatcher(this, serviceName, listener));
         try {
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
index 5ee429a..1e8f171 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
@@ -39,6 +39,8 @@
 
     private final ZookeeperServiceDiscovery zookeeperServiceDiscovery;
 
+    private boolean keepWatching = true;
+
     private final String serviceName;
 
     public ZookeeperServiceDiscoveryChangeWatcher(ZookeeperServiceDiscovery zookeeperServiceDiscovery,
@@ -55,9 +57,19 @@
         Watcher.Event.EventType eventType = event.getType();
 
         if (NodeChildrenChanged.equals(eventType) || NodeDataChanged.equals(eventType)) {
-            listener.onEvent(new ServiceInstancesChangedEvent(serviceName, zookeeperServiceDiscovery.getInstances(serviceName)));
-            zookeeperServiceDiscovery.registerServiceWatcher(serviceName, listener);
-            zookeeperServiceDiscovery.dispatchServiceInstancesChangedEvent(serviceName);
+            if (shouldKeepWatching()) {
+                listener.onEvent(new ServiceInstancesChangedEvent(serviceName, zookeeperServiceDiscovery.getInstances(serviceName)));
+                zookeeperServiceDiscovery.registerServiceWatcher(serviceName, listener);
+                zookeeperServiceDiscovery.dispatchServiceInstancesChangedEvent(serviceName);
+            }
         }
     }
+
+    public boolean shouldKeepWatching() {
+        return keepWatching;
+    }
+
+    public void stopWatching() {
+        this.keepWatching = false;
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..12262ad
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+zookeeper=org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
index 0a30a0a..f04a5b5 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
@@ -85,7 +85,7 @@
         assertEquals(asList(serviceInstance), serviceInstances);
 
         Map<String, String> metadata = new HashMap<>();
-        metadata.put("message", "Hello,World");
+        //metadata.put("message", "Hello,World");
         serviceInstance.setMetadata(metadata);
 
         discovery.update(serviceInstance);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/TelnetUtils.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/TelnetUtils.java
index 50a06c9..219ca04 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/TelnetUtils.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/TelnetUtils.java
@@ -115,7 +115,7 @@
         buf.append("\r\n");

         //content

         for (List<String> row : table) {

-            StringBuffer rowbuf = new StringBuffer();

+            StringBuilder rowbuf = new StringBuilder();

             rowbuf.append("|");

             for (int j = 0; j < widths.length; j++) {

                 String cell = row.get(j);

@@ -125,7 +125,7 @@
 

                     if (rowbuf.length() >= totalWidth) {

                         buf.append(rowbuf.toString());

-                        rowbuf = new StringBuffer();

+                        rowbuf = new StringBuilder();

 //                        for(int m = 0;m < maxcountbefore && maxcountbefore < totalWidth ; m++){

 //                            rowbuf.append(" ");

 //                        }

diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/LogTelnetHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/LogTelnetHandler.java
index 2d39c23..6c0167d 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/LogTelnetHandler.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/LogTelnetHandler.java
@@ -44,7 +44,7 @@
     public String telnet(Channel channel, String message) {
         long size = 0;
         File file = LoggerFactory.getFile();
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         if (message == null || message.trim().length() == 0) {
             buf.append("EXAMPLE: log error / log 100");
         } else {
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java
index a4d59cc..68ee081 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java
@@ -22,7 +22,6 @@
 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.ExecutorUtil;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.remoting.Channel;
 import org.apache.dubbo.remoting.ChannelHandler;
@@ -38,6 +37,7 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_CLIENT_THREADPOOL;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.THREAD_NAME_KEY;
 
 /**
  * AbstractClient
@@ -48,6 +48,7 @@
     private static final Logger logger = LoggerFactory.getLogger(AbstractClient.class);
     private final Lock connectLock = new ReentrantLock();
     private final boolean needReconnect;
+    //issue-7054:Consumer's executor is sharing globally.
     protected volatile ExecutorService executor;
     private ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
 
@@ -90,7 +91,8 @@
     }
 
     private void initExecutor(URL url) {
-        url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
+        //issue-7054:Consumer's executor is sharing globally, thread name not require provider ip.
+        url = url.addParameter(THREAD_NAME_KEY, CLIENT_THREAD_POOL_NAME);
         url = url.addParameterIfAbsent(THREADPOOL_KEY, DEFAULT_CLIENT_THREADPOOL);
         executor = executorRepository.createExecutorIfAbsent(url);
     }
@@ -278,14 +280,6 @@
             }
 
             try {
-                if (executor != null) {
-                    ExecutorUtil.shutdownNow(executor, 100);
-                }
-            } catch (Throwable e) {
-                logger.warn(e.getMessage(), e);
-            }
-
-            try {
                 disconnect();
             } catch (Throwable e) {
                 logger.warn(e.getMessage(), e);
@@ -304,7 +298,6 @@
 
     @Override
     public void close(int timeout) {
-        ExecutorUtil.gracefulShutdown(executor, timeout);
         close();
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-etcd3/src/main/java/org/apache/dubbo/remoting/etcd/jetcd/JEtcdClientWrapper.java b/dubbo-remoting/dubbo-remoting-etcd3/src/main/java/org/apache/dubbo/remoting/etcd/jetcd/JEtcdClientWrapper.java
index d6c5733..0de6703 100644
--- a/dubbo-remoting/dubbo-remoting-etcd3/src/main/java/org/apache/dubbo/remoting/etcd/jetcd/JEtcdClientWrapper.java
+++ b/dubbo-remoting/dubbo-remoting-etcd3/src/main/java/org/apache/dubbo/remoting/etcd/jetcd/JEtcdClientWrapper.java
@@ -17,6 +17,7 @@
 
 package org.apache.dubbo.remoting.etcd.jetcd;
 
+import java.nio.charset.StandardCharsets;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -112,7 +113,7 @@
 
     private volatile boolean cancelKeepAlive = false;
 
-    public static final Charset UTF_8 = Charset.forName("UTF-8");
+    public static final Charset UTF_8 = StandardCharsets.UTF_8;
 
     public JEtcdClientWrapper(URL url) {
         this.url = url;
diff --git a/dubbo-remoting/dubbo-remoting-grizzly/src/main/java/org/apache/dubbo/remoting/transport/grizzly/GrizzlyCodecAdapter.java b/dubbo-remoting/dubbo-remoting-grizzly/src/main/java/org/apache/dubbo/remoting/transport/grizzly/GrizzlyCodecAdapter.java
index 39e6990..c720fd5 100644
--- a/dubbo-remoting/dubbo-remoting-grizzly/src/main/java/org/apache/dubbo/remoting/transport/grizzly/GrizzlyCodecAdapter.java
+++ b/dubbo-remoting/dubbo-remoting-grizzly/src/main/java/org/apache/dubbo/remoting/transport/grizzly/GrizzlyCodecAdapter.java
@@ -128,10 +128,8 @@
                         }
                         if (msg != null) {
                             context.setMessage(msg);
-                            return context.getInvokeAction();
-                        } else {
-                            return context.getInvokeAction();
                         }
+                        return context.getInvokeAction();
                     }
                 } while (frame.readable());
             } else { // Other events are passed down directly
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
index 001b56a..3783d02 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
@@ -39,7 +39,7 @@
     private ThreadNameVerifyHandler clientHandler;
 
     private static String serverRegex = "DubboServerHandler\\-localhost:(\\d+)\\-thread\\-(\\d+)";
-    private static String clientRegex = "DubboClientHandler\\-localhost:(\\d+)\\-thread\\-(\\d+)";
+    private static String clientRegex = "DubboClientHandler\\-thread\\-(\\d+)";
 
     @BeforeEach
     public void before() throws Exception {
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
index cbb3747..775d291 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
@@ -62,4 +62,6 @@
 
     String getContent(String path);
 
+    boolean checkExists(String path);
+
 }
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
index 625f001..34679c3 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
@@ -16,6 +16,7 @@
  */

 package org.apache.dubbo.remoting.zookeeper.curator;

 

+import java.nio.charset.StandardCharsets;

 import org.apache.dubbo.common.URL;

 import org.apache.dubbo.common.logger.Logger;

 import org.apache.dubbo.common.logger.LoggerFactory;

@@ -54,7 +55,7 @@
     protected static final Logger logger = LoggerFactory.getLogger(CuratorZookeeperClient.class);

     private static final String ZK_SESSION_EXPIRE_KEY = "zk.session.expire";

 

-    static final Charset CHARSET = Charset.forName("UTF-8");

+    static final Charset CHARSET = StandardCharsets.UTF_8;

     private final CuratorFramework client;

     private Map<String, TreeCache> treeCacheMap = new ConcurrentHashMap<>();

 

diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java
index 6b7f8e2..42fe5c9 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java
@@ -199,7 +199,7 @@
 

     protected abstract void createEphemeral(String path, String data);

 

-    protected abstract boolean checkExists(String path);

+    public abstract boolean checkExists(String path);

 

     protected abstract TargetChildListener createTargetChildListener(String path, ChildListener listener);

 

diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/AppResponse.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/AppResponse.java
index 22538ac..118e701 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/AppResponse.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/AppResponse.java
@@ -16,7 +16,8 @@
  */
 package org.apache.dubbo.rpc;
 
-import java.lang.reflect.Field;
+import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -34,7 +35,7 @@
  *     <li>AsyncRpcResult is the object that is actually passed in the call chain</li>
  *     <li>AppResponse only simply represents the business result</li>
  * </ul>
- *
+ * <p>
  *  The relationship between them can be described as follow, an abstraction of the definition of AsyncRpcResult:
  *  <pre>
  *  {@code
@@ -79,15 +80,7 @@
         if (exception != null) {
             // fix issue#619
             try {
-                // get Throwable class
-                Class clazz = exception.getClass();
-                while (!clazz.getName().equals(Throwable.class.getName())) {
-                    clazz = clazz.getSuperclass();
-                }
-                // get stackTrace value
-                Field stackTraceField = clazz.getDeclaredField("stackTrace");
-                stackTraceField.setAccessible(true);
-                Object stackTrace = stackTraceField.get(exception);
+                Object stackTrace = InvokerInvocationHandler.stackTraceField.get(exception);
                 if (stackTrace == null) {
                     exception.setStackTrace(new StackTraceElement[0]);
                 }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
index 63de8c4..2c451c6 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/AccessLogFilter.java
@@ -43,9 +43,7 @@
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.rpc.Constants.ACCESS_LOG_KEY;
 
 /**
@@ -103,7 +101,8 @@
         try {
             String accessLogKey = invoker.getUrl().getParameter(ACCESS_LOG_KEY);
             if (ConfigUtils.isNotEmpty(accessLogKey)) {
-                AccessLogData logData = buildAccessLogData(invoker, inv);
+                AccessLogData logData = AccessLogData.newLogData(); 
+                logData.buildAccessLogData(invoker, inv);
                 log(accessLogKey, logData);
             }
         } catch (Throwable t) {
@@ -166,18 +165,6 @@
         }
     }
 
-    private AccessLogData buildAccessLogData(Invoker<?> invoker, Invocation inv) {
-        AccessLogData logData = AccessLogData.newLogData();
-        logData.setServiceName(invoker.getInterface().getName());
-        logData.setMethodName(inv.getMethodName());
-        logData.setVersion(invoker.getUrl().getParameter(VERSION_KEY));
-        logData.setGroup(invoker.getUrl().getParameter(GROUP_KEY));
-        logData.setInvocationTime(new Date());
-        logData.setTypes(inv.getParameterTypes());
-        logData.setArguments(inv.getArguments());
-        return logData;
-    }
-
     private void processWithServiceLogger(Set<AccessLogData> logSet) {
         for (Iterator<AccessLogData> iterator = logSet.iterator();
              iterator.hasNext();
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericImplFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericImplFilter.java
index 20ef2ce..f841f3f 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericImplFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericImplFilter.java
@@ -107,7 +107,7 @@
             if (ProtocolUtils.isJavaGenericSerialization(generic)) {

 

                 for (Object arg : args) {

-                    if (!(byte[].class == arg.getClass())) {

+                    if (byte[].class != arg.getClass()) {

                         error(generic, byte[].class.getName(), arg.getClass().getName());

                     }

                 }

diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/FilterNode.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/FilterNode.java
new file mode 100644
index 0000000..00da913
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/FilterNode.java
@@ -0,0 +1,117 @@
+/*
+ * 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.rpc.protocol;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Filter;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.ListenableFilter;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+
+/**
+ * @see org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
+ *
+ */
+class FilterNode<T> implements Invoker<T>{
+    private final Invoker<T> invoker;
+    private final Invoker<T> next;
+    private final Filter filter;
+    
+    public FilterNode(final Invoker<T> invoker, final Invoker<T> next, final Filter filter) {
+        this.invoker = invoker;
+        this.next = next;
+        this.filter = filter;
+    }
+
+    @Override
+    public Class<T> getInterface() {
+        return invoker.getInterface();
+    }
+
+    @Override
+    public URL getUrl() {
+        return invoker.getUrl();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return invoker.isAvailable();
+    }
+
+    @Override
+    public Result invoke(Invocation invocation) throws RpcException {
+        Result asyncResult;
+        try {
+            asyncResult = filter.invoke(next, invocation);
+        } catch (Exception e) {
+            if (filter instanceof ListenableFilter) {
+                ListenableFilter listenableFilter = ((ListenableFilter) filter);
+                try {
+                    Filter.Listener listener = listenableFilter.listener(invocation);
+                    if (listener != null) {
+                        listener.onError(e, invoker, invocation);
+                    }
+                } finally {
+                    listenableFilter.removeListener(invocation);
+                }
+            } else if (filter instanceof Filter.Listener) {
+                Filter.Listener listener = (Filter.Listener) filter;
+                listener.onError(e, invoker, invocation);
+            }
+            throw e;
+        } finally {
+
+        }
+        return asyncResult.whenCompleteWithContext((r, t) -> {
+            if (filter instanceof ListenableFilter) {
+                ListenableFilter listenableFilter = ((ListenableFilter) filter);
+                Filter.Listener listener = listenableFilter.listener(invocation);
+                try {
+                    if (listener != null) {
+                        if (t == null) {
+                            listener.onResponse(r, invoker, invocation);
+                        } else {
+                            listener.onError(t, invoker, invocation);
+                        }
+                    }
+                } finally {
+                    listenableFilter.removeListener(invocation);
+                }
+            } else if (filter instanceof Filter.Listener) {
+                Filter.Listener listener = (Filter.Listener) filter;
+                if (t == null) {
+                    listener.onResponse(r, invoker, invocation);
+                } else {
+                    listener.onError(t, invoker, invocation);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void destroy() {
+        invoker.destroy();
+    }
+
+    @Override
+    public String toString() {
+        return invoker.toString();
+    }
+
+}
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
index 74ac50c..aa17b40 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
@@ -23,12 +23,9 @@
 import org.apache.dubbo.common.utils.UrlUtils;

 import org.apache.dubbo.rpc.Exporter;

 import org.apache.dubbo.rpc.Filter;

-import org.apache.dubbo.rpc.Invocation;

 import org.apache.dubbo.rpc.Invoker;

-import org.apache.dubbo.rpc.ListenableFilter;

 import org.apache.dubbo.rpc.Protocol;

 import org.apache.dubbo.rpc.ProtocolServer;

-import org.apache.dubbo.rpc.Result;

 import org.apache.dubbo.rpc.RpcException;

 

 import java.util.List;

@@ -56,86 +53,8 @@
         List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

 

         if (!filters.isEmpty()) {

-            for (int i = filters.size() - 1; i >= 0; i--) {

-                final Filter filter = filters.get(i);

-                final Invoker<T> next = last;

-                last = new Invoker<T>() {

-

-                    @Override

-                    public Class<T> getInterface() {

-                        return invoker.getInterface();

-                    }

-

-                    @Override

-                    public URL getUrl() {

-                        return invoker.getUrl();

-                    }

-

-                    @Override

-                    public boolean isAvailable() {

-                        return invoker.isAvailable();

-                    }

-

-                    @Override

-                    public Result invoke(Invocation invocation) throws RpcException {

-                        Result asyncResult;

-                        try {

-                            asyncResult = filter.invoke(next, invocation);

-                        } catch (Exception e) {

-                            if (filter instanceof ListenableFilter) {

-                                ListenableFilter listenableFilter = ((ListenableFilter) filter);

-                                try {

-                                    Filter.Listener listener = listenableFilter.listener(invocation);

-                                    if (listener != null) {

-                                        listener.onError(e, invoker, invocation);

-                                    }

-                                } finally {

-                                    listenableFilter.removeListener(invocation);

-                                }

-                            } else if (filter instanceof Filter.Listener) {

-                                Filter.Listener listener = (Filter.Listener) filter;

-                                listener.onError(e, invoker, invocation);

-                            }

-                            throw e;

-                        } finally {

-

-                        }

-                        return asyncResult.whenCompleteWithContext((r, t) -> {

-                            if (filter instanceof ListenableFilter) {

-                                ListenableFilter listenableFilter = ((ListenableFilter) filter);

-                                Filter.Listener listener = listenableFilter.listener(invocation);

-                                try {

-                                    if (listener != null) {

-                                        if (t == null) {

-                                            listener.onResponse(r, invoker, invocation);

-                                        } else {

-                                            listener.onError(t, invoker, invocation);

-                                        }

-                                    }

-                                } finally {

-                                    listenableFilter.removeListener(invocation);

-                                }

-                            } else if (filter instanceof Filter.Listener) {

-                                Filter.Listener listener = (Filter.Listener) filter;

-                                if (t == null) {

-                                    listener.onResponse(r, invoker, invocation);

-                                } else {

-                                    listener.onError(t, invoker, invocation);

-                                }

-                            }

-                        });

-                    }

-

-                    @Override

-                    public void destroy() {

-                        invoker.destroy();

-                    }

-

-                    @Override

-                    public String toString() {

-                        return invoker.toString();

-                    }

-                };

+            for (Filter filter : filters) {

+                last = new FilterNode<T>(invoker, last, filter);

             }

         }

 

diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyFactory.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyFactory.java
index 837750f..8698fe6 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyFactory.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyFactory.java
@@ -60,7 +60,7 @@
         }

 

         if (generic) {

-            if (!GenericService.class.isAssignableFrom(invoker.getInterface())) {

+            if (GenericService.class.equals(invoker.getInterface()) || !GenericService.class.isAssignableFrom(invoker.getInterface())) {

                 interfaces.add(com.alibaba.dubbo.rpc.service.GenericService.class);

             }

 

diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
index 69f1d06..6e43c52 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
@@ -26,6 +26,7 @@
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ConsumerModel;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 
@@ -39,6 +40,17 @@
     private URL url;
     private String protocolServiceKey;
 
+    public static Field stackTraceField;
+
+    static {
+        try {
+            stackTraceField = Throwable.class.getDeclaredField("stackTrace");
+            stackTraceField.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            // ignore
+        }
+    }
+
     public InvokerInvocationHandler(Invoker<?> handler) {
         this.invoker = handler;
         this.url = invoker.getUrl();
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/AccessLogData.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/AccessLogData.java
index e9ff358..ca2eac2 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/AccessLogData.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/AccessLogData.java
@@ -17,10 +17,15 @@
 package org.apache.dubbo.rpc.support;
 
 import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcContext;
 
 import com.alibaba.fastjson.JSON;
 
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -262,5 +267,15 @@
     private void set(String key, Object value) {
         data.put(key, value);
     }
+    
+    public void buildAccessLogData(Invoker<?> invoker, Invocation inv) {
+        setServiceName(invoker.getInterface().getName());
+        setMethodName(inv.getMethodName());
+        setVersion(invoker.getUrl().getParameter(VERSION_KEY));
+        setGroup(invoker.getUrl().getParameter(GROUP_KEY));
+        setInvocationTime(new Date());
+        setTypes(inv.getParameterTypes());
+        setArguments(inv.getArguments());
+    }
 
 }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/RpcContextTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/RpcContextTest.java
index 380b2c3..3c7b495 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/RpcContextTest.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/RpcContextTest.java
@@ -139,6 +139,7 @@
 
         map.keySet().forEach(context::remove);
         Assertions.assertNull(context.get("_11"));
+        RpcContext.removeContext();
     }
 
     @Test
diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
index 5bfcb4b..d02971c 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/AccessLogFilterTest.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.rpc.filter;

 

 import org.apache.dubbo.common.URL;

+import org.apache.dubbo.common.utils.DubboAppender;

 import org.apache.dubbo.common.utils.LogUtil;

 import org.apache.dubbo.rpc.Filter;

 import org.apache.dubbo.rpc.Invocation;

@@ -51,6 +52,7 @@
         accessLogFilter.invoke(invoker, invocation);

         assertEquals(1, LogUtil.findMessage("Exception in AccessLogFilter of service"));

         LogUtil.stop();

+        DubboAppender.clear();

     }

 

     // TODO how to assert thread action

diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java
index 5085f58..3b87068 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java
@@ -296,8 +296,8 @@
             }
             return inObject;
         }
-        byte callBackStatus = isCallBack(url, inv.getProtocolServiceKey(), inv.getMethodName(), paraIndex);
-        switch (callBackStatus) {
+        byte callbackstatus = isCallBack(url, inv.getProtocolServiceKey(), inv.getMethodName(), paraIndex);
+        switch (callbackstatus) {
             case CallbackServiceCodec.CALLBACK_CREATE:
                 try {
                     return referOrDestroyCallbackService(channel, url, pts[paraIndex], inv, Integer.parseInt(inv.getAttachment(INV_ATT_CALLBACK_KEY + paraIndex)), true);
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
index a794863..74115be 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java
@@ -389,11 +389,9 @@
         } catch (ClassNotFoundException e) {
             throw new RpcException("Cannot find the serialization optimizer class: " + className, e);
 
-        } catch (InstantiationException e) {
+        } catch (InstantiationException | IllegalAccessException e) {
             throw new RpcException("Cannot instantiate the serialization optimizer class: " + className, e);
 
-        } catch (IllegalAccessException e) {
-            throw new RpcException("Cannot instantiate the serialization optimizer class: " + className, e);
         }
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
index d2c318b..6eaaf86 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
@@ -90,6 +90,7 @@
     }
 
     @Disabled
+    @Test
     public void test_normal_channel_close_wait_gracefully() throws Exception {
         int testPort = NetUtils.getAvailablePort();
         URL url = URL.valueOf("dubbo://127.0.0.1:" + testPort + "/org.apache.dubbo.rpc.protocol.dubbo.IDemoService?scope=true&lazy=false");
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
index e432dbd..b1eeff9 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
@@ -37,6 +37,7 @@
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.util.HashMap;
@@ -94,7 +95,7 @@
         service = proxy.getProxy(protocol.refer(DemoService.class, URL.valueOf("dubbo://127.0.0.1:" + port + "/" + DemoService.class.getName() + "?client=netty").addParameter("timeout",
                 3000L)));
         // test netty client
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         for (int i = 0; i < 1024 * 32 + 32; i++)
             buf.append('A');
         System.out.println(service.stringLength(buf.toString()));
@@ -108,6 +109,7 @@
         assertEquals(echo.$echo(1234), 1234);
     }
 
+    @Disabled("Mina has been moved to a separate project")
     @Test
     public void testDubboProtocolWithMina() throws Exception {
         DemoService service = new DemoServiceImpl();
@@ -132,7 +134,7 @@
         service = proxy.getProxy(protocol.refer(DemoService.class, URL.valueOf("dubbo://127.0.0.1:" + port + "/" + DemoService.class.getName() + "?client=mina").addParameter("timeout",
                 3000L)));
         // test netty client
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         for (int i = 0; i < 1024 * 32 + 32; i++)
             buf.append('A');
         System.out.println(service.stringLength(buf.toString()));
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
index 3c66d75..c0ffd8a 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/decode/DubboTelnetDecodeTest.java
@@ -42,6 +42,7 @@
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
@@ -261,7 +262,8 @@
      *
      * @throws InterruptedException
      */
-    // @Test
+    @Disabled
+    @Test
     public void testTelnetTelnetDecoded() throws InterruptedException {
         ByteBuf firstByteBuf = Unpooled.wrappedBuffer("ls\r".getBytes());
         ByteBuf secondByteBuf = Unpooled.wrappedBuffer("\nls\r\n".getBytes());
diff --git a/dubbo-rpc/dubbo-rpc-native-thrift/src/test/java/org/apache/dubbo/rpc/protocol/nativethrift/DemoService.java b/dubbo-rpc/dubbo-rpc-native-thrift/src/test/java/org/apache/dubbo/rpc/protocol/nativethrift/DemoService.java
index 97572f1..7e1de88 100644
--- a/dubbo-rpc/dubbo-rpc-native-thrift/src/test/java/org/apache/dubbo/rpc/protocol/nativethrift/DemoService.java
+++ b/dubbo-rpc/dubbo-rpc-native-thrift/src/test/java/org/apache/dubbo/rpc/protocol/nativethrift/DemoService.java
@@ -3680,8 +3680,6 @@
     public boolean equals(timeOut_result that) {
       if (that == null)
         return false;
-      if (this == that)
-        return true;
 
       return true;
     }
@@ -3930,8 +3928,6 @@
     public boolean equals(customException_args that) {
       if (that == null)
         return false;
-      if (this == that)
-        return true;
 
       return true;
     }
diff --git a/dubbo-rpc/dubbo-rpc-redis/src/main/java/org/apache/dubbo/rpc/protocol/redis/RedisProtocol.java b/dubbo-rpc/dubbo-rpc-redis/src/main/java/org/apache/dubbo/rpc/protocol/redis/RedisProtocol.java
index b508188..67dcd7b 100644
--- a/dubbo-rpc/dubbo-rpc-redis/src/main/java/org/apache/dubbo/rpc/protocol/redis/RedisProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-redis/src/main/java/org/apache/dubbo/rpc/protocol/redis/RedisProtocol.java
@@ -43,7 +43,6 @@
 import java.io.IOException;

 import java.net.SocketTimeoutException;

 import java.util.Map;

-import java.util.concurrent.TimeoutException;

 

 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;

 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;

@@ -150,7 +149,7 @@
                         }

                     } catch (Throwable t) {

                         RpcException re = new RpcException("Failed to invoke redis service method. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url + ", cause: " + t.getMessage(), t);

-                        if (t instanceof TimeoutException || t instanceof SocketTimeoutException) {

+                        if (t instanceof SocketTimeoutException) {

                             re.setCode(RpcException.TIMEOUT_EXCEPTION);

                         } else if (t instanceof JedisConnectionException || t instanceof IOException) {

                             re.setCode(RpcException.NETWORK_EXCEPTION);

diff --git a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/$__ClassNameTestDubboStub.java b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/$__ClassNameTestDubboStub.java
index c7f92d7..348f151 100644
--- a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/$__ClassNameTestDubboStub.java
+++ b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/$__ClassNameTestDubboStub.java
@@ -35,7 +35,7 @@
 
     public interface Iface {
 
-        public String echo(String arg);
+        String echo(String arg);
 
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-webservice/src/test/java/org/apache/dubbo/rpc/protocol/webservice/WebserviceProtocolTest.java b/dubbo-rpc/dubbo-rpc-webservice/src/test/java/org/apache/dubbo/rpc/protocol/webservice/WebserviceProtocolTest.java
index da18ba4..e3d15eb 100644
--- a/dubbo-rpc/dubbo-rpc-webservice/src/test/java/org/apache/dubbo/rpc/protocol/webservice/WebserviceProtocolTest.java
+++ b/dubbo-rpc/dubbo-rpc-webservice/src/test/java/org/apache/dubbo/rpc/protocol/webservice/WebserviceProtocolTest.java
@@ -69,7 +69,7 @@
         System.out.println(object);
         assertEquals("webservice://127.0.0.1:" + port + "/org.apache.dubbo.rpc.protocol.webservice.DemoService:invoke", object);
 
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         for (int i = 0; i < 1024 * 32 + 32; i++)
             buf.append('A');
         assertEquals(32800, service.stringLength(buf.toString()));
diff --git a/pom.xml b/pom.xml
index efbb9ca..5040ebc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,7 +126,7 @@
         <arguments />
         <checkstyle.skip>true</checkstyle.skip>
         <rat.skip>true</rat.skip>
-        <revision>2.7.9</revision>
+        <revision>2.7.10-SNAPSHOT</revision>
     </properties>
 
     <modules>