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

[dubbo] branch 3.0 updated: performance tuning, reduce mem allocation of address URLs on consumer side (#6911)

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

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


The following commit(s) were added to refs/heads/3.0 by this push:
     new d8fc808  performance tuning, reduce mem allocation of address URLs on consumer side (#6911)
d8fc808 is described below

commit d8fc808425c4f1db028c4fdab45d7082a957d760
Author: ken.lj <ke...@gmail.com>
AuthorDate: Mon Nov 16 15:12:23 2020 +0800

    performance tuning, reduce mem allocation of address URLs on consumer side (#6911)
---
 .../apache/dubbo/rpc/cluster/ClusterInvoker.java   |   8 +
 .../org/apache/dubbo/rpc/cluster/Directory.java    |   5 +
 .../cluster/configurator/AbstractConfigurator.java |  13 +-
 .../rpc/cluster/directory/AbstractDirectory.java   |  35 +-
 .../cluster/interceptor/ClusterInterceptor.java    |   4 +-
 .../ConsumerContextClusterInterceptor.java         |   6 +
 .../cluster/support/AbstractClusterInvoker.java    |  27 +
 .../cluster/support/AvailableClusterInvoker.java   |   2 +-
 .../cluster/support/BroadcastClusterInvoker.java   |   2 +-
 .../cluster/support/FailbackClusterInvoker.java    |   4 +-
 .../cluster/support/FailfastClusterInvoker.java    |   2 +-
 .../cluster/support/FailoverClusterInvoker.java    |   2 +-
 .../cluster/support/FailsafeClusterInvoker.java    |   2 +-
 .../rpc/cluster/support/ForkingClusterInvoker.java |   2 +-
 .../cluster/support/MergeableClusterInvoker.java   |   9 +-
 .../cluster/support/ProviderURLMergeProcessor.java |  16 +
 .../cluster/support/wrapper/AbstractCluster.java   |  34 +-
 .../configurator/parser/ConfigParserTest.java      |  17 +-
 .../support/AbstractClusterInvokerTest.java        |  25 +-
 .../rpc/cluster/support/ClusterUtilsTest.java      |  17 +-
 .../src/main/java/org/apache/dubbo/common/URL.java | 780 +++++++++------------
 .../java/org/apache/dubbo/common/URLBuilder.java   |  37 +-
 .../java/org/apache/dubbo/common/URLStrParser.java | 103 ++-
 .../apache/dubbo/common/config/Environment.java    |   9 +-
 .../dubbo/common/constants/CommonConstants.java    |  13 +
 .../dubbo/common/constants/RegistryConstants.java  |   2 +
 .../dubbo/common/extension/ExtensionLoader.java    |  13 +-
 .../manager/DefaultExecutorRepository.java         |  13 +-
 .../threadpool/manager/ExecutorRepository.java     |   8 +
 .../url/component/DubboServiceAddressURL.java      | 143 ++++
 .../dubbo/common/url/component/PathURLAddress.java | 175 +++++
 .../common/url/component/ServiceAddressURL.java    | 218 ++++++
 .../common/url/component/ServiceConfigURL.java     | 134 ++++
 .../dubbo/common/url/component/URLAddress.java     | 260 +++++++
 .../dubbo/common/url/component/URLItemCache.java   |  67 ++
 .../dubbo/common/url/component/URLParam.java       | 286 ++++++++
 .../org/apache/dubbo/common/utils/UrlUtils.java    | 131 +++-
 .../org/apache/dubbo/config/ApplicationConfig.java |  12 +
 .../apache/dubbo/rpc/support/ProtocolUtils.java    |   6 +-
 .../dubbo/common/InterfaceAddressURLTest.java      | 107 +++
 .../org/apache/dubbo/common/URLBuilderTest.java    |   6 +-
 .../test/java/org/apache/dubbo/common/URLTest.java |  56 +-
 .../support/AbortPolicyWithReportTest.java         |   4 +-
 .../apache/dubbo/common/utils/UrlUtilsTest.java    |   2 +-
 .../org/apache/dubbo/config/ReferenceConfig.java   |   4 +-
 .../org/apache/dubbo/config/ServiceConfig.java     |   6 +-
 .../dubbo/config/utils/ConfigValidationUtils.java  |   9 +-
 .../org/apache/dubbo/config/ServiceConfigTest.java |   4 +-
 .../support/apollo/ApolloDynamicConfiguration.java |   8 +-
 .../zookeeper/ZookeeperDynamicConfiguration.java   |   2 +-
 .../dubbo-demo-xml-provider-backend/pom.xml        | 107 +++
 .../demo/provider/chain/ChainApplication.java      |  63 +-
 .../demo/provider/chain/ChainServiceImpl.java      |  69 +-
 .../src/main/resources/dubbo.properties            |   1 +
 .../src/main/resources/log4j.properties            |   7 +
 .../main/resources/spring/dubbo-provider-chain.xml |  35 +
 .../DynamicConfigurationServiceNameMapping.java    |  10 +-
 .../org/apache/dubbo/metadata/MetadataInfo.java    |  13 +-
 .../report/identifier/MetadataIdentifier.java      |  13 +-
 .../identifier/ServiceMetadataIdentifier.java      |   9 +-
 .../identifier/SubscriberMetadataIdentifier.java   |   3 +-
 .../report/support/AbstractMetadataReport.java     |  11 +-
 .../AbstractAbstractWritableMetadataService.java   |   6 +-
 .../metadata/test/JTestMetadataReport4Test.java    |   4 +-
 .../store/zookeeper/ZookeeperMetadataReport.java   |  38 +-
 .../dubbo/monitor/support/MonitorFilter.java       |  10 +-
 .../java/org/apache/dubbo/registry/Constants.java  |   5 -
 .../java/org/apache/dubbo/registry/Registry.java   |   6 +
 .../apache/dubbo/registry/RegistryNotifier.java    |  83 +++
 .../dubbo/registry/client/InstanceAddressURL.java  |  12 +-
 .../registry/client/ServiceDiscoveryRegistry.java  |   3 +-
 .../listener/ServiceInstancesChangedListener.java  |   7 +-
 .../metadata/MetadataServiceNameMapping.java       |  10 +-
 .../store/InMemoryWritableMetadataService.java     |   3 +-
 .../metadata/store/RemoteMetadataServiceImpl.java  |  17 +-
 .../DefaultMigrationAddressComparator.java         |   4 +-
 .../client/migration/MigrationInvoker.java         |  27 +-
 .../registry/integration/DynamicDirectory.java     |  23 +-
 .../registry/integration/RegistryDirectory.java    |  87 +--
 .../registry/integration/RegistryProtocol.java     |  36 +-
 .../dubbo/registry/support/AbstractRegistry.java   |  18 +-
 .../support/CacheableFailbackRegistry.java         | 326 +++++++++
 .../registry/CacheableFailbackRegistryTest.java}   |  73 +-
 .../registry/DelayedRegistryNotifierTest.java}     |  57 +-
 .../dubbo/registry/MockCacheableRegistryImpl.java} | 101 ++-
 .../multiple/MultipleServiceDiscovery.java         | 177 +++++
 .../multiple/MultipleServiceDiscoveryFactory.java} |  62 +-
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |   1 +
 .../multiple/MultipleRegistryTestUtil.java         |   3 +-
 .../apache/dubbo/registry/nacos/NacosRegistry.java |  90 ++-
 .../dubbo/registry/nacos/NacosServiceName.java     |   9 +-
 .../registry/zookeeper/ZookeeperRegistry.java      | 108 +--
 .../zookeeper/ZookeeperServiceDiscovery.java       |  52 +-
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |   1 +
 .../remoting/http/tomcat/TomcatHttpServer.java     |   8 +-
 .../dubbo/remoting/zookeeper/ZookeeperClient.java  |   2 +
 .../zookeeper/support/AbstractZookeeperClient.java |   2 +-
 .../org/apache/dubbo/rpc/FilterChainBuilder.java   |  60 +-
 .../main/java/org/apache/dubbo/rpc/RpcContext.java |   9 +-
 .../java/org/apache/dubbo/rpc/RpcInvocation.java   |   6 +-
 .../apache/dubbo/rpc/filter/AccessLogFilter.java   |   6 +-
 .../dubbo/rpc/filter/ConsumerContextFilter.java    |   8 +-
 .../apache/dubbo/rpc/protocol/AbstractInvoker.java |  11 +-
 .../dubbo/rpc/protocol/AbstractProtocol.java       |   5 +-
 .../dubbo/rpc/protocol/ProtocolFilterWrapper.java  | 103 +--
 .../rpc/protocol/ProtocolListenerWrapper.java      | 185 ++---
 .../DefaultFilterChainBuilder.java}                | 301 ++++----
 .../org.apache.dubbo.rpc.FilterChainBuilder        |   1 +
 .../rpc/protocol/dubbo/CallbackServiceCodec.java   |   2 +-
 .../dubbo/rpc/protocol/dubbo/DubboInvoker.java     |   6 +-
 .../dubbo/rpc/protocol/dubbo/DubboProtocol.java    |   4 +-
 112 files changed, 3804 insertions(+), 1566 deletions(-)

diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
index 0d5de6f..94237e2 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
@@ -38,4 +38,12 @@ public interface ClusterInvoker<T> extends Invoker<T> {
     Directory<T> getDirectory();
 
     boolean isDestroyed();
+
+    default boolean hasProxyInvokers() {
+        Directory<T> directory = getDirectory();
+        if (directory == null) {
+            return false;
+        }
+        return directory.hasInvokers();
+    }
 }
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 e6a20fd..958cbde 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
@@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster;
 
 import org.apache.dubbo.common.Node;
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcException;
@@ -53,6 +54,10 @@ public interface Directory<T> extends Node {
 
     boolean isDestroyed();
 
+    default boolean hasInvokers() {
+        return CollectionUtils.isNotEmpty(getAllInvokers());
+    }
+
     void discordAddresses();
 
     RouterChain<T> getRouterChain();
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/AbstractConfigurator.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/AbstractConfigurator.java
index 166479c..4f27b08 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/AbstractConfigurator.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/configurator/AbstractConfigurator.java
@@ -72,8 +72,8 @@ public abstract class AbstractConfigurator implements Configurator {
          */
         String apiVersion = configuratorUrl.getParameter(CONFIG_VERSION_KEY);
         if (StringUtils.isNotEmpty(apiVersion)) {
-            String currentSide = url.getParameter(SIDE_KEY);
-            String configuratorSide = configuratorUrl.getParameter(SIDE_KEY);
+            String currentSide = url.getSide();
+            String configuratorSide = configuratorUrl.getSide();
             if (currentSide.equals(configuratorSide) && CONSUMER.equals(configuratorSide) && 0 == configuratorUrl.getPort()) {
                 url = configureIfMatch(NetUtils.getLocalHost(), url);
             } else if (currentSide.equals(configuratorSide) && PROVIDER.equals(configuratorSide) && url.getPort() == configuratorUrl.getPort()) {
@@ -102,10 +102,10 @@ public abstract class AbstractConfigurator implements Configurator {
              *  1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore.
              *  2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider.
              */
-            if (url.getParameter(SIDE_KEY, PROVIDER).equals(CONSUMER)) {
+            if (url.getSide(PROVIDER).equals(CONSUMER)) {
                 // NetUtils.getLocalHost is the ip address consumer registered to registry.
                 return configureIfMatch(NetUtils.getLocalHost(), url);
-            } else if (url.getParameter(SIDE_KEY, CONSUMER).equals(PROVIDER)) {
+            } else if (url.getSide(CONSUMER).equals(PROVIDER)) {
                 // take effect on all providers, so address must be 0.0.0.0, otherwise it won't flow to this if branch
                 return configureIfMatch(ANYHOST_VALUE, url);
             }
@@ -118,9 +118,8 @@ public abstract class AbstractConfigurator implements Configurator {
             // TODO, to support wildcards
             String providers = configuratorUrl.getParameter(OVERRIDE_PROVIDERS_KEY);
             if (StringUtils.isEmpty(providers) || providers.contains(url.getAddress()) || providers.contains(ANYHOST_VALUE)) {
-                String configApplication = configuratorUrl.getParameter(APPLICATION_KEY,
-                        configuratorUrl.getUsername());
-                String currentApplication = url.getParameter(APPLICATION_KEY, url.getUsername());
+                String configApplication = configuratorUrl.getApplication(configuratorUrl.getUsername());
+                String currentApplication = url.getApplication(url.getUsername());
                 if (configApplication == null || ANY_VALUE.equals(configApplication)
                         || configApplication.equals(currentApplication)) {
                     Set<String> conditionKeys = new HashSet<String>();
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 8087576..6e7da3b 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
@@ -17,6 +17,7 @@
 package org.apache.dubbo.rpc.cluster.directory;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -51,13 +52,12 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
 
     private volatile boolean destroyed = false;
 
-    private volatile URL consumerUrl;
-
-    protected final Map<String, String> queryMap; // Initialization at construction time, assertion not null
-    protected final String consumedProtocol;
+    protected volatile URL consumerUrl;
 
     protected RouterChain<T> routerChain;
 
+    protected final Map<String, String> queryMap;
+
     public AbstractDirectory(URL url) {
         this(url, null);
     }
@@ -67,17 +67,32 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
             throw new IllegalArgumentException("url == null");
         }
 
-        queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
-        String path = queryMap.get(PATH_KEY);
-        this.consumedProtocol = this.queryMap.get(PROTOCOL_KEY) == null ? DUBBO : this.queryMap.get(PROTOCOL_KEY);
-        this.url = url.removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
+        this.url = url.removeAttribute(REFER_KEY).removeParameter(MONITOR_KEY);
 
-        this.consumerUrl = this.url.setProtocol(consumedProtocol).setPath(path == null ? queryMap.get(INTERFACE_KEY) : path).addParameters(queryMap)
-                .removeParameter(MONITOR_KEY);
+        Object referParams = url.getAttribute(REFER_KEY);
+        if (referParams != null) {
+            this.queryMap = (Map<String, String>) referParams;
+            this.consumerUrl = turnRegistryUrlToConsumerUrl(url, queryMap);
+        } else {
+            this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
+            this.consumerUrl = url.addParameters(queryMap);
+        }
 
         setRouterChain(routerChain);
     }
 
+    private URL turnRegistryUrlToConsumerUrl(URL url, Map<String, String> queryMap) {
+        return URLBuilder.from(url)
+                .setHost(queryMap.get("register.ip"))
+                .setPort(0)
+                .setProtocol(queryMap.get(PROTOCOL_KEY) == null ? DUBBO : queryMap.get(PROTOCOL_KEY))
+                .setPath(queryMap.get(PATH_KEY) != null ? queryMap.get(PATH_KEY) : queryMap.get(INTERFACE_KEY))
+                .clearParameters()
+                .addParameters(queryMap)
+                .removeParameter(MONITOR_KEY)
+                .build();
+    }
+
     @Override
     public List<Invoker<T>> list(Invocation invocation) throws RpcException {
         if (destroyed) {
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ClusterInterceptor.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ClusterInterceptor.java
index e989699..199361f 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ClusterInterceptor.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ClusterInterceptor.java
@@ -34,8 +34,8 @@ public interface ClusterInterceptor {
     void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation);
 
     /**
-     * Does not need to override this method, override {@link #before(AbstractClusterInvoker, Invocation)}
-     * and {@link #after(AbstractClusterInvoker, Invocation)}, methods to add your own logic expected to be
+     * Override this method or {@link #before(AbstractClusterInvoker, Invocation)}
+     * and {@link #after(AbstractClusterInvoker, Invocation)} methods to add your own logic expected to be
      * executed before and after invoke.
      *
      * @param clusterInvoker
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ConsumerContextClusterInterceptor.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ConsumerContextClusterInterceptor.java
index 4beb7a0..053bc87 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ConsumerContextClusterInterceptor.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/interceptor/ConsumerContextClusterInterceptor.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.RpcInvocation;
 import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
 
@@ -43,6 +44,11 @@ public class ConsumerContextClusterInterceptor implements ClusterInterceptor, Cl
     }
 
     @Override
+    public Result intercept(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) throws RpcException {
+        return clusterInvoker.invoke(invocation);
+    }
+
+    @Override
     public void onMessage(Result appResponse, AbstractClusterInvoker<?> invoker, Invocation invocation) {
         RpcContext.getServerContext().setObjectAttachments(appResponse.getObjectAttachments());
     }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
index e8e65fe..acd150f 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
@@ -161,6 +161,7 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
         if (sticky) {
             stickyInvoker = invoker;
         }
+
         return invoker;
     }
 
@@ -196,6 +197,7 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
                 logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
             }
         }
+
         return invoker;
     }
 
@@ -290,6 +292,17 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
         }
     }
 
+    protected Result invokeWithContext(Invoker<T> invoker, Invocation invocation) {
+        setContext(invoker);
+        Result result;
+        try {
+            result = invoker.invoke(invocation);
+        } finally {
+            clearContext(invoker);
+        }
+        return result;
+    }
+
     protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
                                        LoadBalance loadbalance) throws RpcException;
 
@@ -319,4 +332,18 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
             return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
         }
     }
+
+
+    private void setContext(Invoker<T> invoker) {
+        RpcContext context = RpcContext.getContext();
+        context.setInvoker(invoker)
+                .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort())
+                .setRemoteApplicationName(invoker.getUrl().getRemoteApplication());
+    }
+
+    private void clearContext(Invoker<T> invoker) {
+        // do nothing
+        RpcContext context = RpcContext.getContext();
+        context.setInvoker(null);
+    }
 }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AvailableClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AvailableClusterInvoker.java
index c75052c..81208c5 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AvailableClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AvailableClusterInvoker.java
@@ -39,7 +39,7 @@ public class AvailableClusterInvoker<T> extends AbstractClusterInvoker<T> {
     public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
         for (Invoker<T> invoker : invokers) {
             if (invoker.isAvailable()) {
-                return invoker.invoke(invocation);
+                return invokeWithContext(invoker, invocation);
             }
         }
         throw new RpcException("No provider available in " + invokers);
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..92a5743 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
@@ -49,7 +49,7 @@ public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
         Result result = null;
         for (Invoker<T> invoker : invokers) {
             try {
-                result = invoker.invoke(invocation);
+                result = invokeWithContext(invoker, invocation);
             } catch (RpcException e) {
                 exception = e;
                 logger.warn(e.getMessage(), e);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailbackClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailbackClusterInvoker.java
index 1528070..8bc4b13 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailbackClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailbackClusterInvoker.java
@@ -98,7 +98,7 @@ public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
         try {
             checkInvokers(invokers, invocation);
             invoker = select(loadbalance, invocation, invokers, null);
-            return invoker.invoke(invocation);
+            return invokeWithContext(invoker, invocation);
         } catch (Throwable e) {
             logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
                     + e.getMessage() + ", ", e);
@@ -141,7 +141,7 @@ public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
             try {
                 Invoker<T> retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker));
                 lastInvoker = retryInvoker;
-                retryInvoker.invoke(invocation);
+                invokeWithContext(retryInvoker, invocation);
             } catch (Throwable e) {
                 logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
                 if ((++retryTimes) >= retries) {
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailfastClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailfastClusterInvoker.java
index a04bba0..d349808 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailfastClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailfastClusterInvoker.java
@@ -45,7 +45,7 @@ public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
         checkInvokers(invokers, invocation);
         Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
         try {
-            return invoker.invoke(invocation);
+            return invokeWithContext(invoker, invocation);
         } catch (Throwable e) {
             if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
                 throw (RpcException) e;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailoverClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailoverClusterInvoker.java
index 5efe0ce..cc3ed9c 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailoverClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailoverClusterInvoker.java
@@ -79,7 +79,7 @@ public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
             invoked.add(invoker);
             RpcContext.getContext().setInvokers((List) invoked);
             try {
-                Result result = invoker.invoke(invocation);
+                Result result = invokeWithContext(invoker, invocation);
                 if (le != null && logger.isWarnEnabled()) {
                     logger.warn("Although retry the method " + methodName
                             + " in the service " + getInterface().getName()
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailsafeClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailsafeClusterInvoker.java
index 539686e..3548a67 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailsafeClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/FailsafeClusterInvoker.java
@@ -47,7 +47,7 @@ public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
         try {
             checkInvokers(invokers, invocation);
             Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
-            return invoker.invoke(invocation);
+            return invokeWithContext(invoker, invocation);
         } catch (Throwable e) {
             logger.error("Failsafe ignore exception: " + e.getMessage(), e);
             return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
index 53a2c34..85a661b 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ForkingClusterInvoker.java
@@ -85,7 +85,7 @@ public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {
             for (final Invoker<T> invoker : selected) {
                 executor.execute(() -> {
                     try {
-                        Result result = invoker.invoke(invocation);
+                        Result result = invokeWithContext(invoker, invocation);
                         ref.offer(result);
                     } catch (Throwable e) {
                         int value = count.incrementAndGet();
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
index 3b815ce..7bc1f94 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
@@ -39,7 +39,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.rpc.Constants.ASYNC_KEY;
 import static org.apache.dubbo.rpc.Constants.MERGER_KEY;
 
@@ -63,17 +62,17 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
             for (final Invoker<T> invoker : invokers) {
                 if (invoker.isAvailable()) {
                     try {
-                        return invoker.invoke(invocation);
+                        return invokeWithContext(invoker, invocation);
                     } catch (RpcException e) {
                         if (e.isNoInvokerAvailableAfterFilter()) {
-                            log.debug("No available provider for service" + getUrl().getServiceKey() + " on group " + invoker.getUrl().getParameter(GROUP_KEY) + ", will continue to try another group.");
+                            log.debug("No available provider for service" + getUrl().getServiceKey() + " on group " + invoker.getUrl().getGroup() + ", will continue to try another group.");
                         } else {
                             throw e;
                         }
                     }
                 }
             }
-            return invokers.iterator().next().invoke(invocation);
+            return invokeWithContext(invokers.iterator().next(), invocation);
         }
 
         Class<?> returnType;
@@ -88,7 +87,7 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
         for (final Invoker<T> invoker : invokers) {
             RpcInvocation subInvocation = new RpcInvocation(invocation, invoker);
             subInvocation.setAttachment(ASYNC_KEY, "true");
-            results.put(invoker.getUrl().getServiceKey(), invoker.invoke(subInvocation));
+            results.put(invoker.getUrl().getServiceKey(), invokeWithContext(invoker, subInvocation));
         }
 
         Object result = null;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ProviderURLMergeProcessor.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ProviderURLMergeProcessor.java
index 9c01aeb..9c91f38 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ProviderURLMergeProcessor.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ProviderURLMergeProcessor.java
@@ -1,3 +1,19 @@
+/*
+ * 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;
 
 import org.apache.dubbo.common.URL;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/AbstractCluster.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/AbstractCluster.java
index 0170026..6bdfeb7 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/AbstractCluster.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/AbstractCluster.java
@@ -17,7 +17,9 @@
 package org.apache.dubbo.rpc.cluster.support.wrapper;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.rpc.FilterChainBuilder;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
@@ -30,12 +32,16 @@ import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
 
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.FILTER_BUILDER_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_INTERCEPTOR_KEY;
 
 public abstract class AbstractCluster implements Cluster {
 
     private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
-        AbstractClusterInvoker<T> last = clusterInvoker;
+//        AbstractClusterInvoker<T> last = clusterInvoker;
+        AbstractClusterInvoker<T> last = new FilterInvoker<>(clusterInvoker);
         List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
 
         if (!interceptors.isEmpty()) {
@@ -129,4 +135,30 @@ public abstract class AbstractCluster implements Cluster {
             return null;
         }
     }
+
+    static class FilterInvoker<T> extends AbstractClusterInvoker<T> {
+        private Invoker<T> filterInvoker;
+
+        public FilterInvoker(AbstractClusterInvoker<T> invoker) {
+            filterInvoker = ExtensionLoader.getExtensionLoader(FilterChainBuilder.class)
+                    .getExtension(invoker.getUrl().getParameter(FILTER_BUILDER_KEY, DEFAULT_KEY))
+                    .buildInvokerChain(invoker, REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
+        }
+
+        @Override
+        public Result invoke(Invocation invocation) throws RpcException {
+            return filterInvoker.invoke(invocation);
+        }
+
+        /**
+         * The only purpose is to build a interceptor chain, so the cluster related logic doesn't matter.
+         * Use ClusterInvoker<T> to replace AbstractClusterInvoker<T> in the future.
+         */
+        @Override
+        protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
+           return null;
+        }
+    }
+
+
 }
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
index d8d616f..7825b37 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/configurator/parser/ConfigParserTest.java
@@ -30,11 +30,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_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.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY;
 
@@ -83,8 +80,8 @@ public class ConfigParserTest {
             Assertions.assertNotNull(urls);
             Assertions.assertEquals(1, urls.size());
             URL url = urls.get(0);
-            Assertions.assertEquals("testgroup", url.getParameter(GROUP_KEY));
-            Assertions.assertEquals("1.0.0", url.getParameter(VERSION_KEY));
+            Assertions.assertEquals("testgroup", url.getGroup());
+            Assertions.assertEquals("1.0.0", url.getVersion());
         }
     }
 
@@ -97,7 +94,7 @@ public class ConfigParserTest {
             URL url = urls.get(0);
             Assertions.assertEquals("127.0.0.1", url.getAddress());
             Assertions.assertEquals(6666, url.getParameter(TIMEOUT_KEY, 0));
-            Assertions.assertNotNull(url.getParameter(APPLICATION_KEY));
+            Assertions.assertNotNull(url.getApplication());
         }
     }
 
@@ -123,7 +120,7 @@ public class ConfigParserTest {
             Assertions.assertEquals("service1", url.getServiceInterface());
             Assertions.assertEquals(6666, url.getParameter(TIMEOUT_KEY, 0));
             Assertions.assertEquals("random", url.getParameter(LOADBALANCE_KEY));
-            Assertions.assertEquals(url.getParameter(APPLICATION_KEY), "demo-consumer");
+            Assertions.assertEquals(url.getApplication(), "demo-consumer");
         }
     }
 
@@ -139,7 +136,7 @@ public class ConfigParserTest {
             Assertions.assertEquals("*", url.getServiceInterface());
             Assertions.assertEquals(6666, url.getParameter(TIMEOUT_KEY, 0));
             Assertions.assertEquals("random", url.getParameter(LOADBALANCE_KEY));
-            Assertions.assertEquals(url.getParameter(APPLICATION_KEY), "demo-consumer");
+            Assertions.assertEquals(url.getApplication(), "demo-consumer");
         }
     }
 
@@ -154,7 +151,7 @@ public class ConfigParserTest {
             Assertions.assertEquals("*", url.getServiceInterface());
             Assertions.assertEquals(6666, url.getParameter(TIMEOUT_KEY, 0));
             Assertions.assertEquals("random", url.getParameter(LOADBALANCE_KEY));
-            Assertions.assertEquals(url.getParameter(APPLICATION_KEY), "demo-consumer");
+            Assertions.assertEquals(url.getApplication(), "demo-consumer");
         }
     }
 
@@ -170,7 +167,7 @@ public class ConfigParserTest {
             Assertions.assertEquals(6666, url.getParameter(TIMEOUT_KEY, 0));
             Assertions.assertEquals("random", url.getParameter(LOADBALANCE_KEY));
             Assertions.assertEquals("127.0.0.1:20880", url.getParameter(OVERRIDE_PROVIDERS_KEY));
-            Assertions.assertEquals(url.getParameter(APPLICATION_KEY), "demo-consumer");
+            Assertions.assertEquals(url.getApplication(), "demo-consumer");
         }
     }
 
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
index 5300058..6d19dd6 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
@@ -17,9 +17,9 @@
 package org.apache.dubbo.rpc.cluster.support;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.utils.NetUtils;
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
@@ -42,12 +42,17 @@ import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
+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.PATH_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_AVAILABLE_CHECK_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK;
 import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
@@ -87,6 +92,10 @@ public class AbstractClusterInvokerTest {
     @SuppressWarnings({"unchecked"})
     @BeforeEach
     public void setUp() throws Exception {
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put("application", "abstractClusterInvokerTest");
+        url = url.putAttribute(REFER_KEY, attributes);
+
         invocation.setMethodName("sayHello");
 
         invoker1 = mock(Invoker.class);
@@ -226,8 +235,8 @@ public class AbstractClusterInvokerTest {
     @Test
     public void testCloseAvailablecheck() {
         LoadBalance lb = mock(LoadBalance.class);
-        Map<String, String> queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
-        URL tmpUrl = url.addParameters(queryMap).removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
+        Map<String, String> queryMap = (Map<String, String> )url.getAttribute(REFER_KEY);
+        URL tmpUrl = turnRegistryUrlToConsumerUrl(url, queryMap);
         given(lb.select(invokers, tmpUrl, invocation)).willReturn(invoker1);
         initlistsize5();
 
@@ -237,6 +246,16 @@ public class AbstractClusterInvokerTest {
 
     }
 
+    private URL turnRegistryUrlToConsumerUrl(URL url, Map<String, String> queryMap) {
+        return URLBuilder.from(url)
+                .setProtocol(queryMap.get(PROTOCOL_KEY) == null ? DUBBO : queryMap.get(PROTOCOL_KEY))
+                .setPath(queryMap.get(PATH_KEY) != null ? queryMap.get(PATH_KEY) : queryMap.get(INTERFACE_KEY))
+                .clearParameters()
+                .addParameters(queryMap)
+                .removeParameter(MONITOR_KEY)
+                .build();
+    }
+
     @Test
     public void testDonotSelectAgainAndNoCheckAvailable() {
 
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java
index 01cb530..f0cedc0 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java
@@ -36,7 +36,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.QUEUES_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY;
@@ -105,8 +104,8 @@ public class ClusterUtilsTest {
         Assertions.assertEquals(url.getPassword(), "password");
         Assertions.assertEquals(url.getParameter(PID_KEY), "1234");
         Assertions.assertEquals(url.getParameter(THREADPOOL_KEY), "foo");
-        Assertions.assertEquals(url.getParameter(APPLICATION_KEY), "consumer");
-        Assertions.assertEquals(url.getParameter(REMOTE_APPLICATION_KEY), "provider");
+        Assertions.assertEquals(url.getApplication(), "consumer");
+        Assertions.assertEquals(url.getRemoteApplication(), "provider");
         Assertions.assertEquals(url.getParameter(REFERENCE_FILTER_KEY), "filter1,filter2,filter3");
     }
 
@@ -119,8 +118,8 @@ public class ClusterUtilsTest {
                 "&methods=remote&tag=remote&timestamp=remote");
         URL mergedUrl = ClusterUtils.mergeUrl(remoteURL, localURL.getParameters());
 
-        Assertions.assertEquals(remoteURL.getParameter(VERSION_KEY), mergedUrl.getParameter(VERSION_KEY));
-        Assertions.assertEquals(remoteURL.getParameter(GROUP_KEY), mergedUrl.getParameter(GROUP_KEY));
+        Assertions.assertEquals(remoteURL.getVersion(), mergedUrl.getVersion());
+        Assertions.assertEquals(remoteURL.getGroup(), mergedUrl.getGroup());
         Assertions.assertEquals(remoteURL.getParameter(DUBBO_VERSION_KEY), mergedUrl.getParameter(DUBBO_VERSION_KEY));
         Assertions.assertEquals(remoteURL.getParameter(RELEASE_KEY), mergedUrl.getParameter(RELEASE_KEY));
         Assertions.assertEquals(remoteURL.getParameter(METHODS_KEY), mergedUrl.getParameter(METHODS_KEY));
@@ -133,8 +132,8 @@ public class ClusterUtilsTest {
         remoteURL = URL.valueOf("dubbo://localhost:20880/DemoService");
         mergedUrl = ClusterUtils.mergeUrl(remoteURL, localURL.getParameters());
 
-        Assertions.assertEquals(mergedUrl.getParameter(VERSION_KEY),localURL.getParameter(VERSION_KEY));
-        Assertions.assertEquals(mergedUrl.getParameter(GROUP_KEY),localURL.getParameter(GROUP_KEY));
+        Assertions.assertEquals(mergedUrl.getVersion(),localURL.getVersion());
+        Assertions.assertEquals(mergedUrl.getGroup(),localURL.getGroup());
         Assertions.assertNull(mergedUrl.getParameter(DUBBO_VERSION_KEY));
         Assertions.assertNull(mergedUrl.getParameter(RELEASE_KEY));
         Assertions.assertNull(mergedUrl.getParameter(METHODS_KEY));
@@ -147,8 +146,8 @@ public class ClusterUtilsTest {
         remoteURL = URL.valueOf("dubbo://localhost:20880/DemoService?key=value");
         mergedUrl = ClusterUtils.mergeUrl(remoteURL, localURL.getParameters());
 
-        Assertions.assertEquals(mergedUrl.getParameter(VERSION_KEY),localURL.getParameter(VERSION_KEY));
-        Assertions.assertEquals(mergedUrl.getParameter(GROUP_KEY),localURL.getParameter(GROUP_KEY));
+        Assertions.assertEquals(mergedUrl.getVersion(),localURL.getVersion());
+        Assertions.assertEquals(mergedUrl.getGroup(),localURL.getGroup());
         Assertions.assertNull(mergedUrl.getParameter(DUBBO_VERSION_KEY));
         Assertions.assertNull(mergedUrl.getParameter(RELEASE_KEY));
         Assertions.assertNull(mergedUrl.getParameter(METHODS_KEY));
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 f227353..1be2800 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
@@ -19,8 +19,13 @@ package org.apache.dubbo.common;
 import org.apache.dubbo.common.config.Configuration;
 import org.apache.dubbo.common.config.InmemoryConfiguration;
 import org.apache.dubbo.common.constants.RemotingConstants;
+import org.apache.dubbo.common.url.component.PathURLAddress;
+import org.apache.dubbo.common.url.component.ServiceConfigURL;
+import org.apache.dubbo.common.url.component.URLAddress;
+import org.apache.dubbo.common.url.component.URLParam;
 import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.LRUCache;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 
@@ -38,25 +43,28 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PASSWORD_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_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.CATEGORY_KEY;
 import static org.apache.dubbo.common.convert.Converter.convertIfPossible;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
@@ -97,23 +105,10 @@ class URL implements Serializable {
 
     private static final long serialVersionUID = -1985165475234910535L;
 
-    protected String protocol;
+    private static Map<String, URL> cachedURLs = new LRUCache<>();
 
-    protected String username;
-
-    protected String password;
-
-    // by default, host to registry
-    protected String host;
-
-    // by default, port to registry
-    protected int port;
-
-    protected String path;
-
-    private final Map<String, String> parameters;
-
-    private final Map<String, Map<String, String>> methodParameters;
+    private final URLAddress urlAddress;
+    private final URLParam urlParam;
 
     // ==== cache ====
 
@@ -123,31 +118,17 @@ class URL implements Serializable {
 
     private volatile transient Map<String, URL> urls;
 
-    private volatile transient String ip;
-
-    private volatile transient String full;
-
-    private volatile transient String identity;
-
-    private volatile transient String parameter;
-
-    private volatile transient String string;
-
     private transient String serviceKey;
     private transient String protocolServiceKey;
 
-    private transient String address;
-
     protected URL() {
-        this.protocol = null;
-        this.username = null;
-        this.password = null;
-        this.host = null;
-        this.port = 0;
-        this.address = null;
-        this.path = null;
-        this.parameters = null;
-        this.methodParameters = null;
+        this.urlAddress = null;
+        this.urlParam = null;
+    }
+
+    public URL(URLAddress urlAddress, URLParam urlParam) {
+        this.urlAddress = urlAddress;
+        this.urlParam = urlParam;
     }
 
     public URL(String protocol, String host, int port) {
@@ -189,176 +170,70 @@ class URL implements Serializable {
                int port,
                String path,
                Map<String, String> parameters) {
-        this(protocol, username, password, host, port, path, parameters, toMethodParameters(parameters));
+        if (StringUtils.isEmpty(username)
+                && StringUtils.isNotEmpty(password)) {
+            throw new IllegalArgumentException("Invalid url, password without username!");
+        }
+
+        this.urlAddress = new PathURLAddress(protocol, username, password, path, host, port);
+        this.urlParam = new URLParam(parameters);
     }
 
-    public URL(String protocol,
+    protected URL(String protocol,
                String username,
                String password,
                String host,
                int port,
                String path,
                Map<String, String> parameters,
-               Map<String, Map<String, String>> methodParameters) {
+               boolean modifiable) {
         if (StringUtils.isEmpty(username)
                 && StringUtils.isNotEmpty(password)) {
             throw new IllegalArgumentException("Invalid url, password without username!");
         }
-        this.protocol = protocol;
-        this.username = username;
-        this.password = password;
-        this.host = host;
-        this.port = Math.max(port, 0);
-        this.address = getAddress(this.host, this.port);
 
-        // trim the beginning "/"
-        while (path != null && path.startsWith("/")) {
-            path = path.substring(1);
-        }
-        this.path = path;
-        if (parameters == null) {
-            parameters = new HashMap<>();
-        } else {
-            parameters = new HashMap<>(parameters);
-        }
-        this.parameters = Collections.unmodifiableMap(parameters);
-        this.methodParameters = Collections.unmodifiableMap(methodParameters);
+        this.urlAddress = new PathURLAddress(protocol, username, password, path, host, port);
+        this.urlParam = new URLParam(parameters);
     }
 
-    private static String getAddress(String host, int port) {
-        return port <= 0 ? host : host + ':' + port;
+    public static URL cacheableValueOf(String url) {
+        URL cachedURL =  cachedURLs.get(url);
+        if (cachedURL != null) {
+            return cachedURL;
+        }
+        cachedURL = valueOf(url, false);
+        cachedURLs.put(url, cachedURL);
+        return cachedURL;
     }
 
     /**
-     * NOTICE: This method allocate too much objects, we can use {@link URLStrParser#parseDecodedStr(String)} instead.
-     * <p>
-     * Parse url string
+     * parse decoded url string, formatted dubbo://host:port/path?param=value, into strutted URL.
      *
-     * @param url URL string
-     * @return URL instance
-     * @see URL
+     * @param url, decoded url string
+     * @return
      */
     public static URL valueOf(String url) {
-        if (url == null || (url = url.trim()).length() == 0) {
-            throw new IllegalArgumentException("url == null");
-        }
-        String protocol = null;
-        String username = null;
-        String password = null;
-        String host = null;
-        int port = 0;
-        String path = null;
-        Map<String, String> parameters = null;
-        int i = url.indexOf('?'); // separator between body and parameters
-        if (i >= 0) {
-            String[] parts = url.substring(i + 1).split("&");
-            parameters = new HashMap<>();
-            for (String part : parts) {
-                part = part.trim();
-                if (part.length() > 0) {
-                    int j = part.indexOf('=');
-                    if (j >= 0) {
-                        String key = part.substring(0, j);
-                        String value = part.substring(j + 1);
-                        parameters.put(key, value);
-                        // compatible with lower versions registering "default." keys
-                        if (key.startsWith(DEFAULT_KEY_PREFIX)) {
-                            parameters.putIfAbsent(key.substring(DEFAULT_KEY_PREFIX.length()), value);
-                        }
-                    } else {
-                        parameters.put(part, part);
-                    }
-                }
-            }
-            url = url.substring(0, i);
-        }
-        i = url.indexOf("://");
-        if (i >= 0) {
-            if (i == 0) {
-                throw new IllegalStateException("url missing protocol: \"" + url + "\"");
-            }
-            protocol = url.substring(0, i);
-            url = url.substring(i + 3);
-        } else {
-            // case: file:/path/to/file.txt
-            i = url.indexOf(":/");
-            if (i >= 0) {
-                if (i == 0) {
-                    throw new IllegalStateException("url missing protocol: \"" + url + "\"");
-                }
-                protocol = url.substring(0, i);
-                url = url.substring(i + 1);
-            }
-        }
-
-        i = url.indexOf('/');
-        if (i >= 0) {
-            path = url.substring(i + 1);
-            url = url.substring(0, i);
-        }
-        i = url.lastIndexOf('@');
-        if (i >= 0) {
-            username = url.substring(0, i);
-            int j = username.indexOf(':');
-            if (j >= 0) {
-                password = username.substring(j + 1);
-                username = username.substring(0, j);
-            }
-            url = url.substring(i + 1);
-        }
-        i = url.lastIndexOf(':');
-        if (i >= 0 && i < url.length() - 1) {
-            if (url.lastIndexOf('%') > i) {
-                // ipv6 address with scope id
-                // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
-                // see https://howdoesinternetwork.com/2013/ipv6-zone-id
-                // ignore
-            } else {
-                port = Integer.parseInt(url.substring(i + 1));
-                url = url.substring(0, i);
-            }
-        }
-        if (url.length() > 0) {
-            host = url;
-        }
-
-        return new URL(protocol, username, password, host, port, path, parameters);
+        return valueOf(url, false);
     }
 
-    public static Map<String, Map<String, String>> toMethodParameters(Map<String, String> parameters) {
-        Map<String, Map<String, String>> methodParameters = new HashMap<>();
-        if (parameters == null) {
-            return methodParameters;
-        }
+    /**
+     * parse normal or encoded url string into strutted URL:
+     * - dubbo://host:port/path?param=value
+     * - URL.encode("dubbo://host:port/path?param=value")
+     *
+     * @param url,     url string
+     * @param encoded, encoded or decoded
+     * @return
+     */
+    public static URL valueOf(String url, boolean encoded) {
+        return valueOf(url, encoded, false);
+    }
 
-        String methodsString = parameters.get(METHODS_KEY);
-        if (StringUtils.isNotEmpty(methodsString)) {
-            List<String> methods = StringUtils.splitToList(methodsString, ',');
-            for (Map.Entry<String, String> entry : parameters.entrySet()) {
-                String key = entry.getKey();
-                for (int i = 0; i < methods.size(); i++) {
-                    String method = methods.get(i);
-                    int methodLen = method.length();
-                    if (key.length() > methodLen
-                            && key.startsWith(method)
-                            && key.charAt(methodLen) == '.') {//equals to: key.startsWith(method + '.')
-                        String realKey = key.substring(methodLen + 1);
-                        URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
-                    }
-                }
-            }
-        } else {
-            for (Map.Entry<String, String> entry : parameters.entrySet()) {
-                String key = entry.getKey();
-                int methodSeparator = key.indexOf('.');
-                if (methodSeparator > 0) {
-                    String method = key.substring(0, methodSeparator);
-                    String realKey = key.substring(methodSeparator + 1);
-                    URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
-                }
-            }
+    public static URL valueOf(String url, boolean encoded, boolean modifiable) {
+        if (encoded) {
+            return URLStrParser.parseEncodedStr(url, modifiable);
         }
-        return methodParameters;
+        return URLStrParser.parseDecodedStr(url, modifiable);
     }
 
     public static URL valueOf(String url, String... reserveParams) {
@@ -398,8 +273,8 @@ class URL implements Serializable {
                 }
             }
         }
-        return newMap.isEmpty() ? new URL(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath())
-                : new URL(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath(), newMap);
+        return newMap.isEmpty() ? new ServiceConfigURL(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath(), (Map<String, String>) null)
+                : new ServiceConfigURL(url.getProtocol(), url.getUsername(), url.getPassword(), url.getHost(), url.getPort(), url.getPath(), newMap);
     }
 
     public static String encode(String value) {
@@ -436,92 +311,94 @@ class URL implements Serializable {
         return address;
     }
 
+    public URLAddress getUrlAddress() {
+        return urlAddress;
+    }
+
+    public URLParam getUrlParam() {
+        return urlParam;
+    }
+
     public String getProtocol() {
-        return protocol;
+        return urlAddress == null ? null : urlAddress.getProtocol();
     }
 
     public URL setProtocol(String protocol) {
-        return new URL(protocol, username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setProtocol(protocol);
+        return returnURL(newURLAddress);
     }
 
     public String getUsername() {
-        return username;
+        return urlAddress == null ? null : urlAddress.getUsername();
     }
 
     public URL setUsername(String username) {
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setUsername(username);
+        return returnURL(newURLAddress);
     }
 
     public String getPassword() {
-        return password;
+        return urlAddress == null ? null : urlAddress.getPassword();
     }
 
     public URL setPassword(String password) {
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setPassword(password);
+        return returnURL(newURLAddress);
     }
 
     public String getAuthority() {
-        if (StringUtils.isEmpty(username)
-                && StringUtils.isEmpty(password)) {
+        if (StringUtils.isEmpty(getUsername())
+                && StringUtils.isEmpty(getPassword())) {
             return null;
         }
-        return (username == null ? "" : username)
-                + ":" + (password == null ? "" : password);
+        return (getUsername() == null ? "" : getUsername())
+                + ":" + (getPassword() == null ? "" : getPassword());
     }
 
     public String getHost() {
-        return host;
+        return urlAddress == null ? null : urlAddress.getHost();
     }
 
     public URL setHost(String host) {
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setHost(host);
+        return returnURL(newURLAddress);
     }
 
-    /**
-     * Fetch IP address for this URL.
-     * <p>
-     * Pls. note that IP should be used instead of Host when to compare with socket's address or to search in a map
-     * which use address as its key.
-     *
-     * @return ip in string format
-     */
-    public String getIp() {
-        if (ip == null) {
-            ip = NetUtils.getIpByHost(host);
-        }
-        return ip;
-    }
 
     public int getPort() {
-        return port;
+        return urlAddress == null ? 0 : urlAddress.getPort();
     }
 
     public URL setPort(int port) {
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setPort(port);
+        return returnURL(newURLAddress);
     }
 
     public int getPort(int defaultPort) {
+        int port = getPort();
         return port <= 0 ? defaultPort : port;
     }
 
     public String getAddress() {
-        if (address == null) {
-            address = getAddress(host, port);
-        }
-        return address;
+        return urlAddress.getAddress();
     }
 
     public URL setAddress(String address) {
         int i = address.lastIndexOf(':');
         String host;
-        int port = this.port;
+        int port = this.getPort();
         if (i >= 0) {
             host = address.substring(0, i);
             port = Integer.parseInt(address.substring(i + 1));
         } else {
             host = address;
         }
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setAddress(host, port);
+        return returnURL(newURLAddress);
+    }
+
+    public String getIp() {
+        return urlAddress.getIp();
     }
 
     public String getBackupAddress() {
@@ -553,14 +430,16 @@ class URL implements Serializable {
     }
 
     public String getPath() {
-        return path;
+        return urlAddress == null ? null : urlAddress.getPath();
     }
 
     public URL setPath(String path) {
-        return new URL(getProtocol(), username, password, host, port, path, getParameters());
+        URLAddress newURLAddress = urlAddress.setPath(path);
+        return returnURL(newURLAddress);
     }
 
     public String getAbsolutePath() {
+        String path = getPath();
         if (path != null && !path.startsWith("/")) {
             return "/" + path;
         }
@@ -568,7 +447,7 @@ class URL implements Serializable {
     }
 
     public Map<String, String> getParameters() {
-        return parameters;
+        return urlParam.getParameters();
     }
 
     /**
@@ -589,6 +468,41 @@ class URL implements Serializable {
         return Collections.unmodifiableMap(selectedParameters);
     }
 
+    public Map<String, Map<String, String>> getMethodParameters() {
+        return urlParam.getMethodParameters();
+    }
+
+    public String getParameterAndDecoded(String key) {
+        return getParameterAndDecoded(key, null);
+    }
+
+    public String getParameterAndDecoded(String key, String defaultValue) {
+        return decode(getParameter(key, defaultValue));
+    }
+
+    public String getParameter(String key) {
+        return urlParam.getParameter(key);
+    }
+
+    public String getParameter(String key, String defaultValue) {
+        String value = getParameter(key);
+        return StringUtils.isEmpty(value) ? defaultValue : value;
+    }
+
+    public String[] getParameter(String key, String[] defaultValue) {
+        String value = getParameter(key);
+        return StringUtils.isEmpty(value) ? defaultValue : COMMA_SPLIT_PATTERN.split(value);
+    }
+
+    public List<String> getParameter(String key, List<String> defaultValue) {
+        String value = getParameter(key);
+        if (StringUtils.isEmpty(value)) {
+            return defaultValue;
+        }
+        String[] strArray = COMMA_SPLIT_PATTERN.split(value);
+        return Arrays.asList(strArray);
+    }
+
     /**
      * Get parameter
      *
@@ -624,42 +538,6 @@ class URL implements Serializable {
         return result;
     }
 
-
-    public Map<String, Map<String, String>> getMethodParameters() {
-        return methodParameters;
-    }
-
-    public String getParameterAndDecoded(String key) {
-        return getParameterAndDecoded(key, null);
-    }
-
-    public String getParameterAndDecoded(String key, String defaultValue) {
-        return decode(getParameter(key, defaultValue));
-    }
-
-    public String getParameter(String key) {
-        return parameters.get(key);
-    }
-
-    public String getParameter(String key, String defaultValue) {
-        String value = getParameter(key);
-        return StringUtils.isEmpty(value) ? defaultValue : value;
-    }
-
-    public String[] getParameter(String key, String[] defaultValue) {
-        String value = getParameter(key);
-        return StringUtils.isEmpty(value) ? defaultValue : COMMA_SPLIT_PATTERN.split(value);
-    }
-
-    public List<String> getParameter(String key, List<String> defaultValue) {
-        String value = getParameter(key);
-        if (StringUtils.isEmpty(value)) {
-            return defaultValue;
-        }
-        String[] strArray = COMMA_SPLIT_PATTERN.split(value);
-        return Arrays.asList(strArray);
-    }
-
     protected Map<String, Number> getNumbers() {
         // concurrent initialization is tolerant
         if (numbers == null) {
@@ -852,24 +730,24 @@ class URL implements Serializable {
         return URL.decode(getMethodParameter(method, key, defaultValue));
     }
 
-    public String getMethodParameterStrict(String method, String key) {
+    public String getMethodParameter(String method, String key) {
         Map<String, String> keyMap = getMethodParameters().get(method);
         String value = null;
         if (keyMap != null) {
             value = keyMap.get(key);
         }
+        if (StringUtils.isEmpty(value)) {
+            value = urlParam.getParameter(key);
+        }
         return value;
     }
 
-    public String getMethodParameter(String method, String key) {
+    public String getMethodParameterStrict(String method, String key) {
         Map<String, String> keyMap = getMethodParameters().get(method);
         String value = null;
         if (keyMap != null) {
             value = keyMap.get(key);
         }
-        if (StringUtils.isEmpty(value)) {
-            value = parameters.get(key);
-        }
         return value;
     }
 
@@ -1036,7 +914,7 @@ class URL implements Serializable {
     public boolean hasMethodParameter(String method, String key) {
         if (method == null) {
             String suffix = "." + key;
-            for (String fullKey : parameters.keySet()) {
+            for (String fullKey : getParameters().keySet()) {
                 if (fullKey.endsWith(suffix)) {
                     return true;
                 }
@@ -1045,7 +923,7 @@ class URL implements Serializable {
         }
         if (key == null) {
             String prefix = method + ".";
-            for (String fullKey : parameters.keySet()) {
+            for (String fullKey : getParameters().keySet()) {
                 if (fullKey.startsWith(prefix)) {
                     return true;
                 }
@@ -1056,6 +934,16 @@ class URL implements Serializable {
         return StringUtils.isNotEmpty(value);
     }
 
+    public String getAnyMethodParameter(String key) {
+        String suffix = "." + key;
+        for (String fullKey : getUrlParam().getParameters().keySet()) {
+            if (fullKey.endsWith(suffix)) {
+                return getUrlParam().getParameters().get(fullKey);
+            }
+        }
+        return null;
+    }
+
     public boolean hasMethodParameter(String method) {
         if (method == null) {
             return false;
@@ -1064,11 +952,11 @@ class URL implements Serializable {
     }
 
     public boolean isLocalHost() {
-        return NetUtils.isLocalHost(host) || getParameter(LOCALHOST_KEY, false);
+        return NetUtils.isLocalHost(getHost()) || getParameter(LOCALHOST_KEY, false);
     }
 
     public boolean isAnyHost() {
-        return ANYHOST_VALUE.equals(host) || getParameter(ANYHOST_KEY, false);
+        return ANYHOST_VALUE.equals(getHost()) || getParameter(ANYHOST_KEY, false);
     }
 
     public URL addParameterAndEncoded(String key, String value) {
@@ -1132,66 +1020,13 @@ class URL implements Serializable {
     }
 
     public URL addParameter(String key, String value) {
-        if (StringUtils.isEmpty(key)
-                || StringUtils.isEmpty(value)) {
-            return this;
-        }
-        // if value doesn't change, return immediately
-        if (value.equals(getParameters().get(key))) { // value != null
-            return this;
-        }
-
-        Map<String, String> map = new HashMap<>(getParameters());
-        map.put(key, value);
-
-        return new URL(getProtocol(), username, password, host, port, path, map);
+        URLParam newParam = urlParam.addParameter(key, value);
+        return returnURL(newParam);
     }
 
     public URL addParameterIfAbsent(String key, String value) {
-        if (StringUtils.isEmpty(key)
-                || StringUtils.isEmpty(value)) {
-            return this;
-        }
-        if (hasParameter(key)) {
-            return this;
-        }
-        Map<String, String> map = new HashMap<>(getParameters());
-        map.put(key, value);
-
-        return new URL(getProtocol(), username, password, host, port, path, map);
-    }
-
-    public URL addMethodParameter(String method, String key, String value) {
-        if (StringUtils.isEmpty(method)
-                || StringUtils.isEmpty(key)
-                || StringUtils.isEmpty(value)) {
-            return this;
-        }
-
-        Map<String, String> map = new HashMap<>(getParameters());
-        map.put(method + "." + key, value);
-        Map<String, Map<String, String>> methodMap = toMethodParameters(map);
-        URL.putMethodParameter(method, key, value, methodMap);
-
-        return new URL(getProtocol(), username, password, host, port, path, map, methodMap);
-    }
-
-    public URL addMethodParameterIfAbsent(String method, String key, String value) {
-        if (StringUtils.isEmpty(method)
-                || StringUtils.isEmpty(key)
-                || StringUtils.isEmpty(value)) {
-            return this;
-        }
-        if (hasMethodParameter(method, key)) {
-            return this;
-        }
-
-        Map<String, String> map = new HashMap<>(getParameters());
-        map.put(method + "." + key, value);
-        Map<String, Map<String, String>> methodMap = toMethodParameters(map);
-        URL.putMethodParameter(method, key, value, methodMap);
-
-        return new URL(getProtocol(), username, password, host, port, path, map, methodMap);
+        URLParam newParam = urlParam.addParameterIfAbsent(key, value);
+        return returnURL(newParam);
     }
 
     /**
@@ -1201,42 +1036,13 @@ class URL implements Serializable {
      * @return A new URL
      */
     public URL addParameters(Map<String, String> parameters) {
-        if (CollectionUtils.isEmptyMap(parameters)) {
-            return this;
-        }
-
-        boolean hasAndEqual = true;
-        for (Map.Entry<String, String> entry : parameters.entrySet()) {
-            String value = getParameters().get(entry.getKey());
-            if (value == null) {
-                if (entry.getValue() != null) {
-                    hasAndEqual = false;
-                    break;
-                }
-            } else {
-                if (!value.equals(entry.getValue())) {
-                    hasAndEqual = false;
-                    break;
-                }
-            }
-        }
-        // return immediately if there's no change
-        if (hasAndEqual) {
-            return this;
-        }
-
-        Map<String, String> map = new HashMap<>(getParameters());
-        map.putAll(parameters);
-        return new URL(getProtocol(), username, password, host, port, path, map);
+        URLParam newParam = urlParam.addParameters(parameters);
+        return returnURL(newParam);
     }
 
     public URL addParametersIfAbsent(Map<String, String> parameters) {
-        if (CollectionUtils.isEmptyMap(parameters)) {
-            return this;
-        }
-        Map<String, String> map = new HashMap<>(parameters);
-        map.putAll(getParameters());
-        return new URL(getProtocol(), username, password, host, port, path, map);
+        URLParam newURLParam = urlParam.addParametersIfAbsent(parameters);
+        return returnURL(newURLParam);
     }
 
     public URL addParameters(String... pairs) {
@@ -1244,7 +1050,7 @@ class URL implements Serializable {
             return this;
         }
         if (pairs.length % 2 != 0) {
-            throw new IllegalArgumentException("Map pairs can not be odd number.");
+            throw new IllegalArgumentException("Map pairs can not be odd numrer.");
         }
         Map<String, String> map = new HashMap<>();
         int len = pairs.length / 2;
@@ -1276,74 +1082,63 @@ class URL implements Serializable {
     }
 
     public URL removeParameters(String... keys) {
-        if (keys == null || keys.length == 0) {
-            return this;
-        }
-        Map<String, String> map = new HashMap<>(getParameters());
-        for (String key : keys) {
-            map.remove(key);
-        }
-        if (map.size() == getParameters().size()) {
-            return this;
-        }
-        return new URL(getProtocol(), username, password, host, port, path, map);
+        URLParam newURLParam = urlParam.removeParameters(keys);
+        return returnURL(newURLParam);
     }
 
     public URL clearParameters() {
-        return new URL(getProtocol(), username, password, host, port, path, new HashMap<>());
+        URLParam newURLParam = urlParam.clearParameters();
+        return returnURL(newURLParam);
     }
 
     public String getRawParameter(String key) {
         if (PROTOCOL_KEY.equals(key)) {
-            return protocol;
+            return urlAddress.getProtocol();
         }
         if (USERNAME_KEY.equals(key)) {
-            return username;
+            return urlAddress.getUsername();
         }
         if (PASSWORD_KEY.equals(key)) {
-            return password;
+            return urlAddress.getPassword();
         }
         if (HOST_KEY.equals(key)) {
-            return host;
+            return urlAddress.getHost();
         }
         if (PORT_KEY.equals(key)) {
-            return String.valueOf(port);
+            return String.valueOf(urlAddress.getPort());
         }
         if (PATH_KEY.equals(key)) {
-            return path;
+            return urlAddress.getPath();
         }
-        return getParameter(key);
+        return urlParam.getParameter(key);
     }
 
     public Map<String, String> toMap() {
-        Map<String, String> map = new HashMap<>(parameters);
-        if (protocol != null) {
-            map.put(PROTOCOL_KEY, protocol);
+        Map<String, String> map = new HashMap<>(getParameters());
+        if (getProtocol() != null) {
+            map.put(PROTOCOL_KEY, getProtocol());
         }
-        if (username != null) {
-            map.put(USERNAME_KEY, username);
+        if (getUsername() != null) {
+            map.put(USERNAME_KEY, getUsername());
         }
-        if (password != null) {
-            map.put(PASSWORD_KEY, password);
+        if (getPassword() != null) {
+            map.put(PASSWORD_KEY, getPassword());
         }
-        if (host != null) {
-            map.put(HOST_KEY, host);
+        if (getHost() != null) {
+            map.put(HOST_KEY, getHost());
         }
-        if (port > 0) {
-            map.put(PORT_KEY, String.valueOf(port));
+        if (getPort() > 0) {
+            map.put(PORT_KEY, String.valueOf(getPort()));
         }
-        if (path != null) {
-            map.put(PATH_KEY, path);
+        if (getPath() != null) {
+            map.put(PATH_KEY, getPath());
         }
         return map;
     }
 
     @Override
     public String toString() {
-        if (string != null) {
-            return string;
-        }
-        return string = buildString(false, true); // no show username and password
+        return buildString(false, true); // no show username and password
     }
 
     public String toString(String... parameters) {
@@ -1351,10 +1146,7 @@ class URL implements Serializable {
     }
 
     public String toIdentityString() {
-        if (identity != null) {
-            return identity;
-        }
-        return identity = buildString(true, false); // only return identity message, see the method "equals" and "hashCode"
+        return buildString(true, false); // only return identity message, see the method "equals" and "hashCode"
     }
 
     public String toIdentityString(String... parameters) {
@@ -1362,10 +1154,7 @@ class URL implements Serializable {
     }
 
     public String toFullString() {
-        if (full != null) {
-            return full;
-        }
-        return full = buildString(true, true);
+        return buildString(true, true);
     }
 
     public String toFullString(String... parameters) {
@@ -1373,10 +1162,7 @@ class URL implements Serializable {
     }
 
     public String toParameterString() {
-        if (parameter != null) {
-            return parameter;
-        }
-        return parameter = toParameterString(new String[0]);
+        return toParameterString(new String[0]);
     }
 
     public String toParameterString(String... parameters) {
@@ -1385,7 +1171,7 @@ class URL implements Serializable {
         return buf.toString();
     }
 
-    private void buildParameters(StringBuilder buf, boolean concat, String[] parameters) {
+    protected void buildParameters(StringBuilder buf, boolean concat, String[] parameters) {
         if (CollectionUtils.isNotEmptyMap(getParameters())) {
             List<String> includes = (ArrayUtils.isEmpty(parameters) ? null : Arrays.asList(parameters));
             boolean first = true;
@@ -1414,29 +1200,29 @@ class URL implements Serializable {
 
     private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, boolean useService, String... parameters) {
         StringBuilder buf = new StringBuilder();
-        if (StringUtils.isNotEmpty(protocol)) {
-            buf.append(protocol);
+        if (StringUtils.isNotEmpty(getProtocol())) {
+            buf.append(getProtocol());
             buf.append("://");
         }
-        if (appendUser && StringUtils.isNotEmpty(username)) {
-            buf.append(username);
-            if (StringUtils.isNotEmpty(password)) {
+        if (appendUser && StringUtils.isNotEmpty(getUsername())) {
+            buf.append(getUsername());
+            if (StringUtils.isNotEmpty(getPassword())) {
                 buf.append(":");
-                buf.append(password);
+                buf.append(getPassword());
             }
             buf.append("@");
         }
         String host;
         if (useIP) {
-            host = getIp();
+            host = urlAddress.getIp();
         } else {
             host = getHost();
         }
         if (StringUtils.isNotEmpty(host)) {
             buf.append(host);
-            if (port > 0) {
+            if (getPort() > 0) {
                 buf.append(":");
-                buf.append(port);
+                buf.append(getPort());
             }
         }
         String path;
@@ -1465,7 +1251,7 @@ class URL implements Serializable {
     }
 
     public InetSocketAddress toInetSocketAddress() {
-        return new InetSocketAddress(host, port);
+        return new InetSocketAddress(getHost(), getPort());
     }
 
     /**
@@ -1506,7 +1292,7 @@ class URL implements Serializable {
         if (inf == null) {
             return null;
         }
-        serviceKey = buildKey(inf, getParameter(GROUP_KEY), getParameter(VERSION_KEY));
+        serviceKey = buildKey(inf, getGroup(), getVersion());
         return serviceKey;
     }
 
@@ -1516,11 +1302,11 @@ class URL implements Serializable {
      * @return
      */
     public String getPathKey() {
-        String inf = StringUtils.isNotEmpty(path) ? path : getServiceInterface();
+        String inf = StringUtils.isNotEmpty(getPath()) ? getPath() : getServiceInterface();
         if (inf == null) {
             return null;
         }
-        return buildKey(inf, getParameter(GROUP_KEY), getParameter(VERSION_KEY));
+        return buildKey(inf, getGroup(), getVersion());
     }
 
     public static String buildKey(String path, String group, String version) {
@@ -1549,7 +1335,7 @@ class URL implements Serializable {
     }
 
     public String getServiceInterface() {
-        return getParameter(INTERFACE_KEY, path);
+        return getParameter(INTERFACE_KEY, getPath());
     }
 
     public URL setServiceInterface(String service) {
@@ -1648,22 +1434,13 @@ class URL implements Serializable {
 
     public Configuration toConfiguration() {
         InmemoryConfiguration configuration = new InmemoryConfiguration();
-        configuration.addProperties(parameters);
+        configuration.addProperties(getParameters());
         return configuration;
     }
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((host == null) ? 0 : host.hashCode());
-        result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
-        result = prime * result + ((password == null) ? 0 : password.hashCode());
-        result = prime * result + ((path == null) ? 0 : path.hashCode());
-        result = prime * result + port;
-        result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
-        result = prime * result + ((username == null) ? 0 : username.hashCode());
-        return result;
+        return Objects.hash(urlAddress, urlParam);
     }
 
     @Override
@@ -1674,36 +1451,11 @@ class URL implements Serializable {
         if (obj == null) {
             return false;
         }
-        if (getClass() != obj.getClass()) {
+        if (!(obj instanceof URL)) {
             return false;
         }
         URL other = (URL) obj;
-        if (!StringUtils.isEquals(host, other.host)) {
-            return false;
-        }
-        if (parameters == null) {
-            if (other.parameters != null) {
-                return false;
-            }
-        } else if (!parameters.equals(other.parameters)) {
-            return false;
-        }
-        if (!StringUtils.isEquals(password, other.password)) {
-            return false;
-        }
-        if (!StringUtils.isEquals(path, other.path)) {
-            return false;
-        }
-        if (port != other.port) {
-            return false;
-        }
-        if (!StringUtils.isEquals(protocol, other.protocol)) {
-            return false;
-        }
-        if (!StringUtils.isEquals(username, other.username)) {
-            return false;
-        }
-        return true;
+        return Objects.equals(this.getUrlAddress(), other.getUrlAddress()) && Objects.equals(this.getUrlParam(), other.getUrlParam());
     }
 
     public static void putMethodParameter(String method, String key, String value, Map<String, Map<String, String>> methodParameters) {
@@ -1711,6 +1463,114 @@ class URL implements Serializable {
         subParameter.put(key, value);
     }
 
+    protected <T extends URL> T newURL(URLAddress urlAddress, URLParam urlParam) {
+        return (T) new ServiceConfigURL(urlAddress, urlParam, null);
+    }
+
+    /* methods introduced for CompositeURL, CompositeURL must override to make the implementations meaningful */
+
+    public String getApplication(String defaultValue) {
+        String value = getApplication();
+        return StringUtils.isEmpty(value) ? defaultValue : value;
+    }
+
+    public String getApplication() {
+        return getParameter(APPLICATION_KEY);
+    }
+
+    public String getRemoteApplication() {
+        return getParameter(REMOTE_APPLICATION_KEY);
+    }
+
+    public String getGroup() {
+        return getParameter(GROUP_KEY);
+    }
+
+    public String getGroup(String defaultValue) {
+        String value = getGroup();
+        return StringUtils.isEmpty(value) ? defaultValue : value;
+    }
+
+    public String getVersion() {
+        return getParameter(VERSION_KEY);
+    }
+
+    public String getVersion(String defaultValue) {
+        String value = getVersion();
+        return StringUtils.isEmpty(value) ? defaultValue : value;
+    }
+
+    public String getConcatenatedParameter(String key) {
+        return getParameter(key);
+    }
+
+    public String getCategory(String defaultValue) {
+        String value = getCategory();
+        if (StringUtils.isEmpty(value)) {
+            value = defaultValue;
+        }
+        return value;
+    }
+
+    public String[] getCategory(String[] defaultValue) {
+        String value = getCategory();
+        return StringUtils.isEmpty(value) ? defaultValue : COMMA_SPLIT_PATTERN.split(value);
+    }
+
+    public String getCategory() {
+        return getParameter(CATEGORY_KEY);
+    }
+
+    public String getSide(String defaultValue) {
+        String value = getSide();
+        return StringUtils.isEmpty(value) ? defaultValue : value;
+    }
+
+    public String getSide() {
+        return getParameter(SIDE_KEY);
+    }
+
+    /* Service Config URL, START*/
+    public Map<String, Object> getAttributes() {
+        return new HashMap<>();
+    }
+
+    public URL addAttributes(Map<String, Object> attributes) {
+        return this;
+    }
+
+    public Object getAttribute(String key) {
+        return null;
+    }
+
+    public URL putAttribute(String key, Object obj) {
+        return this;
+    }
+
+    public URL removeAttribute(String key) {
+        return this;
+    }
+
+    public boolean hasAttribute(String key) {
+        return true;
+    }
+
+    /* Service Config URL, END*/
+
+    private URL returnURL(URLAddress newURLAddress) {
+        if (urlAddress == newURLAddress) {
+            return this;
+        }
+        return newURL(newURLAddress, urlParam);
+    }
+
+    private URL returnURL(URLParam newURLParam) {
+        if (urlParam == newURLParam) {
+            return this;
+        }
+        return newURL(urlAddress, newURLParam);
+    }
+
     /* add service scope operations, see InstanceAddressURL */
     public Map<String, String> getServiceParameters(String service) {
         return getParameters();
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URLBuilder.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URLBuilder.java
index 20c6c60..7ab8bb3 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URLBuilder.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URLBuilder.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.common;
 
+import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 
@@ -24,7 +25,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
-public final class URLBuilder {
+public final class URLBuilder extends ServiceConfigURL {
     private String protocol;
 
     private String username;
@@ -41,6 +42,8 @@ public final class URLBuilder {
 
     private Map<String, String> parameters;
 
+    private Map<String, Object> attributes;
+
     private Map<String, Map<String, String>> methodParameters;
 
     public URLBuilder() {
@@ -51,6 +54,7 @@ public final class URLBuilder {
         port = 0;
         path = null;
         parameters = new HashMap<>();
+        attributes = new HashMap<>();
         methodParameters = new HashMap<>();
     }
 
@@ -83,8 +87,9 @@ public final class URLBuilder {
                       String password,
                       String host,
                       int port,
-                      String path, Map<String, String> parameters) {
-        this(protocol, username, password, host, port, path, parameters, URL.toMethodParameters(parameters));
+                      String path,
+                      Map<String, String> parameters) {
+        this(protocol, username, password, host, port, path, parameters, null);
     }
 
     public URLBuilder(String protocol,
@@ -92,8 +97,9 @@ public final class URLBuilder {
                       String password,
                       String host,
                       int port,
-                      String path, Map<String, String> parameters,
-                      Map<String, Map<String, String>> methodParameters) {
+                      String path,
+                      Map<String, String> parameters,
+                      Map<String, Object> attributes) {
         this.protocol = protocol;
         this.username = username;
         this.password = password;
@@ -101,7 +107,7 @@ public final class URLBuilder {
         this.port = port;
         this.path = path;
         this.parameters = parameters != null ? parameters : new HashMap<>();
-        this.methodParameters = (methodParameters != null ? methodParameters : new HashMap<>());
+        this.attributes = attributes != null ? attributes : new HashMap<>();
     }
 
     public static URLBuilder from(URL url) {
@@ -112,7 +118,7 @@ public final class URLBuilder {
         int port = url.getPort();
         String path = url.getPath();
         Map<String, String> parameters = new HashMap<>(url.getParameters());
-        Map<String, Map<String, String>> methodParameters = new HashMap<>(url.getMethodParameters());
+        Map<String, Object> attributes = new HashMap<>(url.getAttributes());
         return new URLBuilder(
                 protocol,
                 username,
@@ -121,14 +127,14 @@ public final class URLBuilder {
                 port,
                 path,
                 parameters,
-                methodParameters);
+                attributes);
     }
 
-    public URL build() {
+    public ServiceConfigURL build() {
         if (StringUtils.isEmpty(username) && StringUtils.isNotEmpty(password)) {
             throw new IllegalArgumentException("Invalid url, password without username!");
         }
-        port = port < 0 ? 0 : port;
+        port = Math.max(port, 0);
         // trim the leading "/"
         int firstNonSlash = 0;
         if (path != null) {
@@ -141,13 +147,14 @@ public final class URLBuilder {
                 path = path.substring(firstNonSlash);
             }
         }
-        if (CollectionUtils.isEmptyMap(methodParameters)) {
-            return new URL(protocol, username, password, host, port, path, parameters);
-        } else {
-            return new URL(protocol, username, password, host, port, path, parameters, methodParameters);
-        }
+        return new ServiceConfigURL(protocol, username, password, host, port, path, parameters, attributes);
     }
 
+    @Override
+    public URLBuilder putAttribute(String key, Object obj) {
+        attributes.put(key, obj);
+        return this;
+    }
 
     public URLBuilder setProtocol(String protocol) {
         this.protocol = protocol;
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 37afb78..619b1bb 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
@@ -16,16 +16,26 @@
  */
 package org.apache.dubbo.common;
 
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.url.component.ServiceConfigURL;
+import org.apache.dubbo.common.url.component.URLItemCache;
+
 import java.util.Collections;
 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;
 
 public final class URLStrParser {
-
+    private static final Logger logger = LoggerFactory.getLogger(URLStrParser.class);
+    public static final String ENCODED_QUESTION_MARK = "%3F";
+    public static final String ENCODED_TIMESTAMP_KEY = "timestamp%3D";
+    public static final String ENCODED_PID_KEY = "pid%3D";
+    public static final String ENCODED_AND_MARK = "%26";
     private static final char SPACE = 0x20;
 
     private static final ThreadLocal<TempBuf> DECODE_TEMP_BUF = ThreadLocal.withInitial(() -> new TempBuf(1024));
@@ -34,12 +44,16 @@ public final class URLStrParser {
         //empty
     }
 
+    public static URL parseDecodedStr(String decodedURLStr) {
+        return parseDecodedStr(decodedURLStr, false);
+    }
+
     /**
      * @param decodedURLStr : after {@link URL#decode} string
      *                      decodedURLStr format: protocol://username:password@host:port/path?k1=v1&k2=v2
      *                      [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
      */
-    public static URL parseDecodedStr(String decodedURLStr) {
+    public static URL parseDecodedStr(String decodedURLStr, boolean modifiable) {
         Map<String, String> parameters = null;
         int pathEndIdx = decodedURLStr.indexOf('?');
         if (pathEndIdx >= 0) {
@@ -49,7 +63,7 @@ public final class URLStrParser {
         }
 
         String decodedBody = decodedURLStr.substring(0, pathEndIdx);
-        return parseURLBody(decodedURLStr, decodedBody, parameters);
+        return parseURLBody(decodedURLStr, decodedBody, parameters, modifiable);
     }
 
     private static Map<String, String> parseDecodedParams(String str, int from) {
@@ -92,7 +106,7 @@ public final class URLStrParser {
      * @param parameters  :
      * @return URL
      */
-    private static URL parseURLBody(String fullURLStr, String decodedBody, Map<String, String> parameters) {
+    private static URL parseURLBody(String fullURLStr, String decodedBody, Map<String, String> parameters, boolean modifiable) {
         int starIdx = 0, endIdx = decodedBody.length();
         String protocol = null;
         int protoEndIdx = decodedBody.indexOf("://");
@@ -125,9 +139,13 @@ public final class URLStrParser {
         String password = null;
         int pwdEndIdx = lastIndexOf(decodedBody, '@', starIdx, endIdx);
         if (pwdEndIdx > 0) {
-            int userNameEndIdx = indexOf(decodedBody, ':', starIdx, pwdEndIdx);
-            username = decodedBody.substring(starIdx, userNameEndIdx);
-            password = decodedBody.substring(userNameEndIdx + 1, pwdEndIdx);
+            int passwordStartIdx = indexOf(decodedBody, ':', starIdx, pwdEndIdx);
+            if (passwordStartIdx != -1) {//tolerate incomplete user pwd input, like '1234@'
+                username = decodedBody.substring(starIdx, passwordStartIdx);
+                password = decodedBody.substring(passwordStartIdx + 1, pwdEndIdx);
+            } else {
+                username = decodedBody.substring(starIdx, pwdEndIdx);
+            }
             starIdx = pwdEndIdx + 1;
         }
 
@@ -149,7 +167,44 @@ public final class URLStrParser {
         if (endIdx > starIdx) {
             host = decodedBody.substring(starIdx, endIdx);
         }
-        return new URL(protocol, username, password, host, port, path, parameters);
+
+        // check cache
+        protocol = URLItemCache.checkProtocol(protocol);
+        path = URLItemCache.checkPath(path);
+
+        return new ServiceConfigURL(protocol, username, password, host, port, path, parameters);
+    }
+
+    public static URL parseEncodedStr(String encodedURLStr) {
+        return parseEncodedStr(encodedURLStr, false);
+    }
+
+    public static String[] parseRawURLToArrays(String rawURLStr, int pathEndIdx) {
+        String[] parts = new String[2];
+        int paramStartIdx = pathEndIdx + 3;//skip ENCODED_QUESTION_MARK
+        if (pathEndIdx == -1) {
+            pathEndIdx = rawURLStr.indexOf('?');
+            if (pathEndIdx == -1) {
+                // url with no params, decode anyway
+                rawURLStr = URL.decode(rawURLStr);
+            } else {
+                paramStartIdx = pathEndIdx + 1;
+            }
+        }
+        if (pathEndIdx >= 0) {
+            parts[0] = rawURLStr.substring(0, pathEndIdx);
+            parts[1] = rawURLStr.substring(paramStartIdx);
+        } else {
+            parts = new String[]{rawURLStr};
+        }
+        return parts;
+    }
+
+    public static Map<String, String> parseParams(String rawParams, boolean encoded) {
+        if (encoded) {
+            return parseEncodedParams(rawParams, 0);
+        }
+        return parseDecodedParams(rawParams, 0);
     }
 
     /**
@@ -157,7 +212,7 @@ public final class URLStrParser {
      *                      encodedURLStr after decode format: protocol://username:password@host:port/path?k1=v1&k2=v2
      *                      [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2]
      */
-    public static URL parseEncodedStr(String encodedURLStr) {
+    public static URL parseEncodedStr(String encodedURLStr, boolean modifiable) {
         Map<String, String> parameters = null;
         int pathEndIdx = encodedURLStr.indexOf("%3F");// '?'
         if (pathEndIdx >= 0) {
@@ -168,7 +223,7 @@ public final class URLStrParser {
 
         //decodedBody format: [protocol://][username:password@][host:port]/[path]
         String decodedBody = decodeComponent(encodedURLStr, 0, pathEndIdx, false, DECODE_TEMP_BUF.get());
-        return parseURLBody(encodedURLStr, decodedBody, parameters);
+        return parseURLBody(encodedURLStr, decodedBody, parameters, modifiable);
     }
 
     private static Map<String, String> parseEncodedParams(String str, int from) {
@@ -225,12 +280,30 @@ public final class URLStrParser {
 
         if (isEncoded) {
             String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
-            String value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
-            params.put(name, value);
+            String value;
+            if (valueStart == valueEnd) {
+                value = name;
+            } else {
+                value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
+            }
+            URLItemCache.putParams(params,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 value = str.substring(valueStart, valueEnd);
-            params.put(name, value);
+            String name = str.substring(nameStart, valueStart - 1);
+            String value;
+            if (valueStart == valueEnd) {
+                value = name;
+            } else {
+                value = str.substring(valueStart, valueEnd);
+            }
+            URLItemCache.putParams(params, 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/config/Environment.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Environment.java
index 37f5e82..ea4b4e5 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.config.configcenter.DynamicConfiguration;
 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,8 @@ import java.util.Map;
 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;
@@ -160,7 +164,10 @@ public class Environment extends LifecycleAdapter implements FrameworkExt {
     public Configuration getDynamicGlobalConfiguration() {
         if (dynamicGlobalConfiguration == null) {
             if (dynamicConfiguration == null) {
-                throw new IllegalStateException("Init dynamic configuration before use.");
+                if (logger.isWarnEnabled()) {
+                    logger.warn("dynamicConfiguration is null , return globalConfiguration.");
+                }
+                return globalConfiguration;
             }
             dynamicGlobalConfiguration = new CompositeConfiguration();
             dynamicGlobalConfiguration.addConfiguration(dynamicConfiguration);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
index a9ac119..5bb4e5f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
@@ -306,6 +306,12 @@ public interface CommonConstants {
     String GENERIC_WITH_CLZ_KEY = "generic.include.class";
 
     /**
+     * Whether to cache locally, default is true
+     */
+    String REGISTRY_LOCAL_FILE_CACHE_ENABLED = "file.cache";
+
+
+    /**
      * The limit of callback service instances for one interface on every client
      */
     String CALLBACK_INSTANCES_LIMIT_KEY = "callbacks";
@@ -355,4 +361,11 @@ public interface CommonConstants {
      */
     String DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH = "META-INF/dubbo/service-name-mapping.properties";
 
+    String REGISTRY_DELAY_NOTIFICATION_KEY = "delay-notification";
+
+    String FILTER_BUILDER_KEY = "filter-builder";
+
+    String CACHE_CLEAR_TASK_INTERVAL = "dubbo.application.url.cache.task.interval";
+    String CACHE_CLEAR_WAITING_THRESHOLD = "dubbo.application.url.cache.clear.waiting";
+
 }
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 3419deb..f70304d 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
@@ -45,6 +45,8 @@ public interface RegistryConstants {
 
     String CONFIGURATORS_CATEGORY = "configurators";
 
+    String ALL_CATEGORIES = "providers,configurators,routers";
+
     String DYNAMIC_CONFIGURATORS_CATEGORY = "dynamicconfigurators";
 
     String APP_DYNAMIC_CONFIGURATORS_CATEGORY = "appdynamicconfigurators";
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..8b88422 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
@@ -334,13 +334,12 @@ public class ExtensionLoader<T> {
                 keyValue = arr[1];
             }
 
-            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
-                String k = entry.getKey();
-                String v = entry.getValue();
-                if ((k.equals(key) || k.endsWith("." + key))
-                        && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
-                    return true;
-                }
+            String realValue = url.getParameter(key);
+            if (StringUtils.isEmpty(realValue)) {
+                realValue = url.getAnyMethodParameter(key);
+            }
+            if ((keyValue != null && keyValue.equals(realValue)) || (keyValue == null && ConfigUtils.isNotEmpty(realValue))) {
+                return true;
             }
         }
         return false;
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 d53236a..66eafca 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
@@ -33,7 +33,6 @@ import java.util.concurrent.ThreadPoolExecutor;
 
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.EXECUTOR_SERVICE_COMPONENT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY;
 
 /**
@@ -50,6 +49,8 @@ public class DefaultExecutorRepository implements ExecutorRepository {
 
     private ScheduledExecutorService serviceExporterExecutor;
 
+    public ScheduledExecutorService registryNotificationExecutor;
+
     private ScheduledExecutorService reconnectScheduledExecutor;
 
     private ScheduledExecutorService serviceDiscveryAddressNotificationExecutor;
@@ -65,6 +66,7 @@ public class DefaultExecutorRepository implements ExecutorRepository {
 //        reconnectScheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-reconnect-scheduler"));
         serviceExporterExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-exporter-scheduler"));
         serviceDiscveryAddressNotificationExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-SD-address-refresh"));
+        registryNotificationExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-registry-notification"));
     }
 
     /**
@@ -75,7 +77,7 @@ public class DefaultExecutorRepository implements ExecutorRepository {
      */
     public synchronized ExecutorService createExecutorIfAbsent(URL url) {
         String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY;
-        if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
+        if (CONSUMER_SIDE.equalsIgnoreCase(url.getSide())) {
             componentKey = CONSUMER_SIDE;
         }
         Map<Integer, ExecutorService> executors = data.computeIfAbsent(componentKey, k -> new ConcurrentHashMap<>());
@@ -92,7 +94,7 @@ public class DefaultExecutorRepository implements ExecutorRepository {
 
     public ExecutorService getExecutor(URL url) {
         String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY;
-        if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
+        if (CONSUMER_SIDE.equalsIgnoreCase(url.getSide())) {
             componentKey = CONSUMER_SIDE;
         }
         Map<Integer, ExecutorService> executors = data.get(componentKey);
@@ -157,6 +159,11 @@ public class DefaultExecutorRepository implements ExecutorRepository {
         return serviceExporterExecutor;
     }
 
+    @Override
+    public ScheduledExecutorService getRegistryNotificationExecutor() {
+        return registryNotificationExecutor;
+    }
+
     public ScheduledExecutorService getServiceDiscveryAddressNotificationExecutor() {
         return serviceDiscveryAddressNotificationExecutor;
     }
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 0f0211d..5c08fe3 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
@@ -60,6 +60,14 @@ public interface ExecutorRepository {
     ScheduledExecutorService getServiceDiscveryAddressNotificationExecutor();
 
     /**
+     * Scheduled executor handle registry notification.
+     *
+     * @return
+     */
+    ScheduledExecutorService getRegistryNotificationExecutor();
+
+
+    /**
      * Get the default shared threadpool.
      *
      * @return
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/DubboServiceAddressURL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/DubboServiceAddressURL.java
new file mode 100644
index 0000000..80cadc6
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/DubboServiceAddressURL.java
@@ -0,0 +1,143 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+
+public class DubboServiceAddressURL extends ServiceAddressURL {
+    public static DubboServiceAddressURL valueOf(String rawURL, URL consumerURL) {
+        return valueOf(rawURL, consumerURL, null);
+    }
+
+    public static DubboServiceAddressURL valueOf(String rawURL, URL consumerURL, ServiceConfigURL overriddenURL) {
+        URL url = valueOf(rawURL, true);
+        return new DubboServiceAddressURL(url.getUrlAddress(), url.getUrlParam(), consumerURL, overriddenURL);
+    }
+
+    private ServiceConfigURL overrideURL;
+
+    public DubboServiceAddressURL(URLAddress urlAddress, URLParam urlParam, URL consumerURL, ServiceConfigURL overrideURL) {
+        super(urlAddress, urlParam, consumerURL);
+        this.overrideURL = overrideURL;
+    }
+
+    protected <T extends URL> T newURL(URLAddress urlAddress, URLParam urlParam) {
+        return (T) new DubboServiceAddressURL(urlAddress, urlParam, this.consumerURL, this.overrideURL);
+    }
+
+    @Override
+    public String getSide() {
+        return consumerURL.getParameter(SIDE_KEY);
+    }
+
+    @Override
+    public String getParameter(String key) {
+        String value = null;
+        if (overrideURL != null) {
+            value = overrideURL.getParameter(key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getParameter(key);
+        }
+        return value;
+    }
+
+    @Override
+    public String getMethodParameter(String method, String key) {
+        String value = null;
+        if (overrideURL != null) {
+            value = overrideURL.getMethodParameterStrict(method, key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getMethodParameter(method, key);
+        }
+        return value;
+    }
+
+    @Override
+    public String getAnyMethodParameter(String key) {
+        String value = null;
+        if (overrideURL != null) {
+            value = overrideURL.getAnyMethodParameter(key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getAnyMethodParameter(key);
+        }
+        return value;
+    }
+
+    public ServiceConfigURL getOverrideURL() {
+        return overrideURL;
+    }
+
+    public void setOverrideURL(ServiceConfigURL overrideURL) {
+        this.overrideURL = overrideURL;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        return prime * super.hashCode() + (overrideURL == null ? 0 : overrideURL.hashCode());
+    }
+
+    /**
+     * ignore consumer url compare.
+     * It's only meaningful for comparing two AddressURLs related to the same consumerURL.
+     *
+     * @param obj
+     * @return
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof DubboServiceAddressURL)) {
+            return false;
+        }
+        if (overrideURL == null) {
+            return super.equals(obj);
+        } else {
+            DubboServiceAddressURL other = (DubboServiceAddressURL) obj;
+            boolean overrideEquals = Objects.equals(overrideURL.getParameters(), other.getOverrideURL().getParameters());
+            if (!overrideEquals) {
+                return false;
+            }
+
+            Map<String, String> params = this.getParameters();
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                String key = entry.getKey();
+                if (overrideURL.getParameters().containsKey(key)) {
+                    continue;
+                }
+                if (!entry.getValue().equals(other.getUrlParam().getParameter(key))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/PathURLAddress.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/PathURLAddress.java
new file mode 100644
index 0000000..f723991
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/PathURLAddress.java
@@ -0,0 +1,175 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.Objects;
+
+public class PathURLAddress extends URLAddress {
+    private String protocol;
+    private String username;
+    private String password;
+    private String path;
+
+    private transient String address;
+    private transient String ip;
+
+    public PathURLAddress(String protocol, String username, String password, String path, String host, int port) {
+        this(protocol, username, password, path, host, port, null);
+    }
+
+    public PathURLAddress(String protocol, String username, String password, String path, String host, int port, String rawAddress) {
+        super(host, port, rawAddress);
+
+        this.protocol = protocol;
+        this.username = username;
+        this.password = password;
+
+        // trim the beginning "/"
+        while (path != null && path.startsWith("/")) {
+            path = path.substring(1);
+        }
+        this.path = path;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public URLAddress setProtocol(String protocol) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public URLAddress setUsername(String username) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public PathURLAddress setPassword(String password) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public PathURLAddress setPath(String path) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    @Override
+    public URLAddress setHost(String host) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    @Override
+    public URLAddress setPort(int port) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    @Override
+    public URLAddress setAddress(String host, int port) {
+        return new PathURLAddress(protocol, username, password, path, host, port, rawAddress);
+    }
+
+    public String getAddress() {
+        if (address == null) {
+            address = getAddress(getHost(), getPort());
+        }
+        return address;
+    }
+
+    /**
+     * Fetch IP address for this URL.
+     * <p>
+     * Pls. note that IP should be used instead of Host when to compare with socket's address or to search in a map
+     * which use address as its key.
+     *
+     * @return ip in string format
+     */
+    public String getIp() {
+        if (ip == null) {
+            ip = NetUtils.getIpByHost(getHost());
+        }
+        return ip;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(protocol, username, password, path, host, port);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (!(obj instanceof URLAddress)) return false;
+        URLAddress that = (URLAddress) obj;
+        return Objects.equals(this.getProtocol(), that.getProtocol()) &&
+                Objects.equals(this.getUsername(), that.getUsername()) &&
+                Objects.equals(this.getPassword(), that.getPassword()) &&
+                Objects.equals(this.getPath(), that.getPath()) &&
+                Objects.equals(this.getHost(), that.getHost()) &&
+                Objects.equals(this.getPort(), that.getPort());
+    }
+
+    @Override
+    public String toString() {
+        if (rawAddress != null) {
+            return rawAddress;
+        }
+
+        StringBuilder buf = new StringBuilder();
+        if (StringUtils.isNotEmpty(protocol)) {
+            buf.append(protocol);
+            buf.append("://");
+        }
+//
+//        if (StringUtils.isNotEmpty(username)) {
+//            buf.append(username);
+//            if (StringUtils.isNotEmpty(password)) {
+//                buf.append(":");
+//                buf.append(password);
+//            }
+//            buf.append("@");
+//        }
+
+        if (StringUtils.isNotEmpty(host)) {
+            buf.append(host);
+            if (port > 0) {
+                buf.append(":");
+                buf.append(port);
+            }
+        }
+
+        if (StringUtils.isNotEmpty(path)) {
+            buf.append("/");
+            buf.append(path);
+        }
+
+        return buf.toString();
+    }
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceAddressURL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceAddressURL.java
new file mode 100644
index 0000000..91c8412
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceAddressURL.java
@@ -0,0 +1,218 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+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.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
+
+public abstract class ServiceAddressURL extends URL {
+    protected final transient URL consumerURL;
+
+    //cache
+    private transient Map<String, String> concatenatedPrams;
+//    private transient Map<String, String> allParameters;
+
+    public ServiceAddressURL(
+            String protocol,
+            String username,
+            String password,
+            String host,
+            int port,
+            String path,
+            Map<String, String> parameters,
+            URL consumerURL
+    ) {
+        super(protocol, username, password, host, port, path, parameters);
+        this.consumerURL = consumerURL;
+    }
+
+    public ServiceAddressURL(URLAddress urlAddress, URLParam urlParam, URL consumerURL){
+        super(urlAddress, urlParam);
+        this.consumerURL = consumerURL;
+    }
+
+    @Override
+    public String getPath() {
+        return consumerURL.getPath();
+    }
+
+    @Override
+    public String getServiceInterface() {
+        return consumerURL.getServiceInterface();
+    }
+
+    @Override
+    public String getApplication() {
+        return consumerURL.getApplication();
+    }
+
+    @Override
+    public String getRemoteApplication() {
+        return super.getParameter(APPLICATION_KEY);
+    }
+
+    @Override
+    public String getGroup() {
+        String group = super.getParameter(GROUP_KEY);
+        if (StringUtils.isNotEmpty(group)) {
+            return group;
+        }
+        return consumerURL.getGroup();
+    }
+
+    @Override
+    public String getVersion() {
+        String version = super.getParameter(VERSION_KEY);
+        if (StringUtils.isNotEmpty(version)) {
+            return version;
+        }
+        return consumerURL.getVersion();
+    }
+
+    /**
+     * FIXME, Avoid calling this method on the main line.
+     */
+//    @Override
+//    public Map<String, String> getParameters() {
+//        Map<String, String> allParameters = new HashMap<>((int)(super.getParameters().size()/.75 + 1));
+//        allParameters.putAll(super.getParameters());
+//        if (consumerURL != null) {
+//            allParameters.putAll(consumerURL.getParameters());
+//        }
+//        if (overriddenURL != null) {
+//            allParameters.putAll(overriddenURL.getParameters());
+//        }
+//        allParameters.remove(CATEGORY_KEY);
+//        return Collections.unmodifiableMap(allParameters);
+//    }
+
+    @Override
+    public String getParameter(String key) {
+        String value = null;
+        if (consumerURL != null) {
+            value = consumerURL.getParameter(key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getParameter(key);
+        }
+        return value;
+    }
+
+    @Override
+    public String getMethodParameter(String method, String key) {
+        String value = null;
+        if (consumerURL != null) {
+            value = consumerURL.getMethodParameterStrict(method, key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getMethodParameterStrict(method, key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = getParameter(key);
+        }
+        return value;
+    }
+
+    @Override
+    public String getAnyMethodParameter(String key) {
+        String value = null;
+        if (consumerURL != null) {
+            value = consumerURL.getAnyMethodParameter(key);
+        }
+        if (StringUtils.isEmpty(value)) {
+            value = super.getAnyMethodParameter(key);
+        }
+        return value;
+    }
+
+    @Override
+    public String getConcatenatedParameter(String key) {
+        if (concatenatedPrams == null) {
+            concatenatedPrams = new HashMap<>(1);
+        }
+        String value = concatenatedPrams.get(key);
+        if (StringUtils.isNotEmpty(value)) {
+            return value;
+        }
+
+        // Combine filters and listeners on Provider and Consumer
+        String remoteValue = super.getParameter(key);
+        String localValue = consumerURL.getParameter(key);
+        if (remoteValue != null && remoteValue.length() > 0
+                && localValue != null && localValue.length() > 0) {
+            value = remoteValue + "," + localValue;
+            concatenatedPrams.put(key, value);
+            return value;
+        }
+        if (localValue != null && localValue.length() > 0) {
+            value = localValue;
+        } else if (remoteValue != null && remoteValue.length() > 0) {
+            value = remoteValue;
+        }
+        concatenatedPrams.put(key, value);
+        return value;
+    }
+
+    @Override
+    public String getCategory() {
+        return PROVIDERS_CATEGORY;
+    }
+
+    @Override
+    public String getSide() {
+        return CONSUMER_SIDE;
+    }
+
+    public URL getConsumerURL() {
+        return consumerURL;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ;
+    }
+
+    /**
+     * ignore consumer url compare.
+     * It's only meaningful for comparing two address urls related to the same consumerURL.
+     *
+     * @param obj
+     * @return
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof ServiceAddressURL)) {
+            return false;
+        }
+        return super.equals(obj);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java
new file mode 100644
index 0000000..2bbb062
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java
@@ -0,0 +1,134 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.URL;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ServiceConfigURL extends URL {
+    private final Map<String, Object> attributes;
+
+    private volatile transient String full;
+    private volatile transient String string;
+    private volatile transient String identity;
+    private volatile transient String parameter;
+
+    public ServiceConfigURL() {
+        this.attributes = null;
+    }
+
+    public ServiceConfigURL(URLAddress urlAddress, URLParam urlParam, Map<String, Object> attributes) {
+        super(urlAddress, urlParam);
+        this.attributes = (attributes != null ? attributes : new HashMap<>());
+    }
+
+    public ServiceConfigURL(String protocol,
+               String username,
+               String password,
+               String host,
+               int port,
+               String path,
+               Map<String, String> parameters) {
+        this(new PathURLAddress(protocol, username, password, path, host, port), new URLParam(parameters), null);
+    }
+
+    public ServiceConfigURL(String protocol,
+                            String username,
+                            String password,
+                            String host,
+                            int port,
+                            String path,
+                            Map<String, String> parameters,
+                            Map<String, Object> attributes) {
+        this(new PathURLAddress(protocol, username, password, path, host, port), new URLParam(parameters), attributes);
+    }
+
+    protected <T extends URL> T newURL(URLAddress urlAddress, URLParam urlParam) {
+        return (T) new ServiceConfigURL(urlAddress, urlParam, attributes);
+    }
+
+    public Map<String, Object> getAttributes() {
+        return attributes;
+    }
+
+    public Object getAttribute(String key) {
+        return attributes.get(key);
+    }
+
+    @Override
+    public URL addAttributes(Map<String, Object> attributes) {
+        Map<String, Object> newAttributes = new HashMap<>();
+        newAttributes.putAll(this.attributes);
+        newAttributes.putAll(attributes);
+
+        return new ServiceConfigURL(getUrlAddress(), getUrlParam(), newAttributes);
+    }
+
+    public ServiceConfigURL putAttribute(String key, Object obj) {
+        Map<String, Object> newAttributes = new HashMap<>(attributes);
+        newAttributes.put(key, obj);
+
+        return new ServiceConfigURL(getUrlAddress(), getUrlParam(), newAttributes);
+    }
+
+    @Override
+    public URL removeAttribute(String key) {
+        Map<String, Object> newAttributes = new HashMap<>(attributes);
+        newAttributes.remove(key);
+
+        return new ServiceConfigURL(getUrlAddress(), getUrlParam(), newAttributes);
+    }
+
+    @Override
+    public boolean hasAttribute(String key) {
+        return getAttribute(key) != null;
+    }
+
+    @Override
+    public String toString() {
+        if (string != null) {
+            return string;
+        }
+        return string = super.toString();
+    }
+
+    @Override
+    public String toFullString() {
+        if (full != null) {
+            return full;
+        }
+        return full = super.toFullString();
+    }
+
+    @Override
+    public String toIdentityString() {
+        if (identity != null) {
+            return identity;
+        }
+        return identity = super.toIdentityString();
+    }
+
+    @Override
+    public String toParameterString() {
+        if (parameter != null) {
+            return parameter;
+        }
+        return parameter = super.toParameterString();
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java
new file mode 100644
index 0000000..614862a
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLAddress.java
@@ -0,0 +1,260 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Objects;
+
+public class URLAddress implements Serializable {
+    private static final long serialVersionUID = -1985165475234910535L;
+
+    protected String host;
+    protected int port;
+
+    // cache
+    protected transient String rawAddress;
+    protected transient long timestamp;
+
+    public URLAddress(String host, int port) {
+        this(host, port, null);
+    }
+
+    public URLAddress(String host, int port, String rawAddress) {
+        this.host = host;
+        port = Math.max(port, 0);
+        this.port = port;
+
+        this.rawAddress = rawAddress;
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public String getProtocol() {
+        return "";
+    }
+
+    public URLAddress setProtocol(String protocol) {
+        return this;
+    }
+
+    public String getUsername() {
+        return "";
+    }
+
+    public URLAddress setUsername(String username) {
+        return this;
+    }
+
+    public String getPassword() {
+        return "";
+    }
+
+    public URLAddress setPassword(String password) {
+        return this;
+    }
+
+    public String getPath() {
+        return "";
+    }
+
+    public URLAddress setPath(String path) {
+        return this;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public URLAddress setHost(String host) {
+        return new URLAddress(host, port, rawAddress);
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public URLAddress setPort(int port) {
+        return new URLAddress(host, port, rawAddress);
+    }
+
+    public String getAddress() {
+        if (rawAddress == null) {
+            rawAddress = getAddress(getHost(), getPort());
+        }
+        return rawAddress;
+    }
+
+    public URLAddress setAddress(String host, int port) {
+        return new URLAddress(host, port, rawAddress);
+    }
+
+    public String getIp() {
+        return NetUtils.getIpByHost(getHost());
+    }
+
+    public String getRawAddress() {
+        return rawAddress;
+    }
+
+    protected String getAddress(String host, int port) {
+        return port <= 0 ? host : host + ':' + port;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(host, port);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (!(obj instanceof URLAddress)) return false;
+        URLAddress that = (URLAddress) obj;
+        return Objects.equals(this.getProtocol(), that.getProtocol()) &&
+                Objects.equals(this.getUsername(), that.getUsername()) &&
+                Objects.equals(this.getPassword(), that.getPassword()) &&
+                Objects.equals(this.getPath(), that.getPath()) &&
+                Objects.equals(this.getHost(), that.getHost()) &&
+                Objects.equals(this.getPort(), that.getPort());
+    }
+
+    @Override
+    public String toString() {
+        if (rawAddress != null) {
+            return rawAddress;
+        }
+
+        StringBuilder buf = new StringBuilder();
+        if (StringUtils.isNotEmpty(host)) {
+            buf.append(host);
+            if (port > 0) {
+                buf.append(":");
+                buf.append(port);
+            }
+        }
+        return buf.toString();
+    }
+
+    public static URLAddress parse(String rawAddress, String defaultProtocol, boolean encoded) {
+        try {
+            if (encoded) {
+                rawAddress = URLDecoder.decode(rawAddress, "UTF-8");
+            }
+
+            boolean isPathAddress = !Character.isDigit(rawAddress.charAt(0));
+            if (isPathAddress) {
+                return createPathURLAddress(rawAddress, defaultProtocol);
+            }
+            return createURLAddress(rawAddress, defaultProtocol);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    private static URLAddress createURLAddress(String rawAddress, String defaultProtocol) {
+        String host = null;
+        int port = 0;
+
+        int i = rawAddress.lastIndexOf(':');
+        if (i >= 0 && i < rawAddress.length() - 1) {
+            if (rawAddress.lastIndexOf('%') > i) {
+                // ipv6 address with scope id
+                // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
+                // see https://howdoesinternetwork.com/2013/ipv6-zone-id
+                // ignore
+            } else {
+                port = Integer.parseInt(rawAddress.substring(i + 1));
+                host = rawAddress.substring(0, i);
+            }
+        } else {
+            host = rawAddress;
+        }
+
+        return new URLAddress(host, port, rawAddress);
+    }
+
+    private static PathURLAddress createPathURLAddress(String rawAddress, String defaultProtocol) {
+        String protocol = defaultProtocol;
+        String copyOfRawAddress = rawAddress;
+        String path = null, username = null, password = null, host = null;
+        int port = 0;
+        int i = rawAddress.indexOf("://");
+        if (i >= 0) {
+            if (i == 0) {
+                throw new IllegalStateException("url missing protocol: \"" + rawAddress + "\"");
+            }
+            protocol = rawAddress.substring(0, i);
+            rawAddress = rawAddress.substring(i + 3);
+        } else {
+            // case: file:/path/to/file.txt
+            i = rawAddress.indexOf(":/");
+            if (i >= 0) {
+                if (i == 0) {
+                    throw new IllegalStateException("url missing protocol: \"" + rawAddress + "\"");
+                }
+                protocol = rawAddress.substring(0, i);
+                rawAddress = rawAddress.substring(i + 1);
+            }
+        }
+
+        i = rawAddress.indexOf('/');
+        if (i >= 0) {
+            path = rawAddress.substring(i + 1);
+            rawAddress = rawAddress.substring(0, i);
+        }
+        i = rawAddress.lastIndexOf('@');
+        if (i >= 0) {
+            username = rawAddress.substring(0, i);
+            int j = username.indexOf(':');
+            if (j >= 0) {
+                password = username.substring(j + 1);
+                username = username.substring(0, j);
+            }
+            rawAddress = rawAddress.substring(i + 1);
+        }
+        i = rawAddress.lastIndexOf(':');
+        if (i >= 0 && i < rawAddress.length() - 1) {
+            if (rawAddress.lastIndexOf('%') > i) {
+                // ipv6 address with scope id
+                // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
+                // see https://howdoesinternetwork.com/2013/ipv6-zone-id
+                // ignore
+            } else {
+                port = Integer.parseInt(rawAddress.substring(i + 1));
+                host = rawAddress.substring(0, i);
+            }
+        }
+
+        // check cache
+        protocol = URLItemCache.checkProtocol(protocol);
+        path = URLItemCache.checkPath(path);
+
+        return new PathURLAddress(protocol, username, password, path, host, port, copyOfRawAddress);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLItemCache.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLItemCache.java
new file mode 100644
index 0000000..2384493
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLItemCache.java
@@ -0,0 +1,67 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.utils.LRUCache;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class URLItemCache {
+    // thread safe with limited size, by default 1000
+    private static final Map<String, String> PARAM_KEY_CACHE = new LRUCache<>(10000);
+    private static final Map<String, String> PARAM_VALUE_CACHE = new LRUCache<>(100000);
+    private static final Map<String, String> PATH_CACHE = new LRUCache<>(10000);
+    private static final Map<String, String> PROTOCOL_CACHE = new ConcurrentHashMap<>();
+
+    public static void putParams(Map<String, String> params, String key, String value) {
+        String cachedKey = PARAM_KEY_CACHE.get(key);
+        if (cachedKey == null) {
+            cachedKey = key;
+            PARAM_KEY_CACHE.put(key, key);
+        }
+        String cachedValue = PARAM_VALUE_CACHE.get(value);
+        if (cachedValue == null) {
+            cachedValue = value;
+            PARAM_VALUE_CACHE.put(value, value);
+        }
+
+        params.put(cachedKey, cachedValue);
+    }
+
+    public static String checkProtocol(String _protocol) {
+        if (_protocol == null) {
+            return _protocol;
+        }
+        String cachedProtocol = PROTOCOL_CACHE.putIfAbsent(_protocol, _protocol);
+        if (cachedProtocol != null) {
+            return cachedProtocol;
+        }
+        return _protocol;
+    }
+
+    public static String checkPath(String _path) {
+        if (_path == null) {
+            return _path;
+        }
+        String cachedPath = PATH_CACHE.putIfAbsent(_path, _path);
+        if (cachedPath != null) {
+            return cachedPath;
+        }
+        return _path;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java
new file mode 100644
index 0000000..b2dca0c
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java
@@ -0,0 +1,286 @@
+/*
+ * 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.url.component;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLStrParser;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
+import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
+
+public class URLParam implements Serializable {
+    private static final long serialVersionUID = -1985165475234910535L;
+
+    private final String rawParam;
+    private final Map<String, String> params;
+
+    //cache
+    private transient Map<String, Map<String, String>> methodParameters;
+    private transient long timestamp;
+
+    public URLParam(Map<String, String> params) {
+        this(params, null);
+    }
+
+    public URLParam(Map<String, String> params, String rawParam) {
+        this.params = Collections.unmodifiableMap((params == null ? new HashMap<>() : new HashMap<>(params)));
+        this.rawParam = rawParam;
+
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public Map<String, Map<String, String>> getMethodParameters() {
+        if (methodParameters == null) {
+            methodParameters = Collections.unmodifiableMap(initMethodParameters(this.params));
+        }
+        return methodParameters;
+    }
+
+    public static Map<String, Map<String, String>> initMethodParameters(Map<String, String> parameters) {
+        Map<String, Map<String, String>> methodParameters = new HashMap<>();
+        if (parameters == null) {
+            return methodParameters;
+        }
+
+        String methodsString = parameters.get(METHODS_KEY);
+        if (StringUtils.isNotEmpty(methodsString)) {
+            String[] methods = methodsString.split(",");
+            for (Map.Entry<String, String> entry : parameters.entrySet()) {
+                String key = entry.getKey();
+                for (String method : methods) {
+                    String methodPrefix = method + '.';
+                    if (key.startsWith(methodPrefix)) {
+                        String realKey = key.substring(methodPrefix.length());
+                        URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
+                    }
+                }
+            }
+        } else {
+            for (Map.Entry<String, String> entry : parameters.entrySet()) {
+                String key = entry.getKey();
+                int methodSeparator = key.indexOf('.');
+                if (methodSeparator > 0) {
+                    String method = key.substring(0, methodSeparator);
+                    String realKey = key.substring(methodSeparator + 1);
+                    URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
+                }
+            }
+        }
+        return methodParameters;
+    }
+
+    public Map<String, String> getParameters() {
+        return params;
+    }
+
+    public URLParam addParameter(String key, String value) {
+        if (StringUtils.isEmpty(key)
+                || StringUtils.isEmpty(value)) {
+            return this;
+        }
+        // if value doesn't change, return immediately
+        if (value.equals(getParameters().get(key))) { // value != null
+            return this;
+        }
+
+        Map<String, String> map = new HashMap<>(getParameters());
+        map.put(key, value);
+        return new URLParam(map, rawParam);
+    }
+
+    public URLParam addParameterIfAbsent(String key, String value) {
+        if (StringUtils.isEmpty(key)
+                || StringUtils.isEmpty(value)) {
+            return this;
+        }
+        if (hasParameter(key)) {
+            return this;
+        }
+
+        Map<String, String> map = new HashMap<>(getParameters());
+        map.put(key, value);
+
+        return new URLParam(map, rawParam);
+    }
+
+    /**
+     * Add parameters to a new url.
+     *
+     * @param parameters parameters in key-value pairs
+     * @return A new URL
+     */
+    public URLParam addParameters(Map<String, String> parameters) {
+        if (CollectionUtils.isEmptyMap(parameters)) {
+            return this;
+        }
+
+        boolean hasAndEqual = true;
+        for (Map.Entry<String, String> entry : parameters.entrySet()) {
+            String value = getParameters().get(entry.getKey());
+            if (value == null) {
+                if (entry.getValue() != null) {
+                    hasAndEqual = false;
+                    break;
+                }
+            } else {
+                if (!value.equals(entry.getValue())) {
+                    hasAndEqual = false;
+                    break;
+                }
+            }
+        }
+        // return immediately if there's no change
+        if (hasAndEqual) {
+            return this;
+        }
+
+        Map<String, String> map = new HashMap<>((int)(getParameters().size() + parameters.size() / 0.75f) + 1);
+        map.putAll(getParameters());
+        map.putAll(parameters);
+        return new URLParam(map, rawParam);
+    }
+
+    public URLParam addParametersIfAbsent(Map<String, String> parameters) {
+        if (CollectionUtils.isEmptyMap(parameters)) {
+            return this;
+        }
+
+        Map<String, String> map = new HashMap<>((int)(getParameters().size() + parameters.size() / 0.75f) + 1);
+        map.putAll(parameters);
+        map.putAll(getParameters());
+        return new URLParam(map, rawParam);
+    }
+
+    public URLParam removeParameters(String... keys) {
+        if (keys == null || keys.length == 0) {
+            return this;
+        }
+
+        Map<String, String> map = new HashMap<>(getParameters());
+        for (String key : keys) {
+            map.remove(key);
+        }
+        if (map.size() == getParameters().size()) {
+            return this;
+        }
+        return new URLParam(map, rawParam);
+    }
+
+    public URLParam clearParameters() {
+        return new URLParam(new HashMap<>());
+    }
+
+    public boolean hasParameter(String key) {
+        String value = getParameter(key);
+        return value != null && value.length() > 0;
+    }
+
+    public String getParameter(String key) {
+        return params.get(key);
+    }
+
+    public String getRawParam() {
+        return rawParam;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(params);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (!(obj instanceof URLParam)) return false;
+        URLParam that = (URLParam) obj;
+        return Objects.equals(this.getParameters(), that.getParameters());
+    }
+
+    @Override
+    public String toString() {
+        if (StringUtils.isNotEmpty(rawParam)) {
+            return rawParam;
+        }
+        if (params == null) {
+            return "";
+        }
+
+        StringBuilder buf = new StringBuilder();
+        boolean first = true;
+        for (Map.Entry<String, String> entry : new TreeMap<>(params).entrySet()) {
+            if (StringUtils.isNotEmpty(entry.getKey())) {
+                if (first) {
+                    first = false;
+                } else {
+                    buf.append("&");
+                }
+                buf.append(entry.getKey());
+                buf.append("=");
+                buf.append(entry.getValue() == null ? "" : entry.getValue().trim());
+            }
+        }
+        return buf.toString();
+    }
+
+    public static URLParam parse(String rawParam, boolean encoded, Map<String, String> extraParameters) {
+        Map<String, String> parameters = URLStrParser.parseParams(rawParam, encoded);
+        if (CollectionUtils.isNotEmptyMap(extraParameters)) {
+            parameters.putAll(extraParameters);
+        }
+        return new URLParam(parameters, rawParam);
+    }
+
+    public static URLParam parse(String rawParam) {
+        String[] parts = rawParam.split("&");
+        Map<String, String> parameters = new HashMap<>((int) (parts.length/.75f) + 1);
+        for (String part : parts) {
+            part = part.trim();
+            if (part.length() > 0) {
+                int j = part.indexOf('=');
+                if (j >= 0) {
+                    String key = part.substring(0, j);
+                    String value = part.substring(j + 1);
+                    parameters.put(key, value);
+                    // compatible with lower versions registering "default." keys
+                    if (key.startsWith(DEFAULT_KEY_PREFIX)) {
+                        parameters.putIfAbsent(key.substring(DEFAULT_KEY_PREFIX.length()), value);
+                    }
+                } else {
+                    parameters.put(part, part);
+                }
+            }
+        }
+        return new URLParam(parameters, rawParam);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java
index d45ada1..17ce7ad 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.common.utils;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLStrParser;
 import org.apache.dubbo.common.constants.RemotingConstants;
 
 import java.util.ArrayList;
@@ -31,6 +32,7 @@ import static java.util.Collections.emptyMap;
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
 import static org.apache.dubbo.common.constants.CommonConstants.CLASSIFIER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX;
 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.GROUP_KEY;
@@ -102,7 +104,7 @@ public class UrlUtils {
             defaultParameters.remove(PORT_KEY);
             defaultParameters.remove(PATH_KEY);
         }
-        URL u = URL.valueOf(url);
+        URL u = URL.cacheableValueOf(url);
         boolean changed = false;
         String protocol = u.getProtocol();
         String username = u.getUsername();
@@ -400,8 +402,8 @@ public class UrlUtils {
             return false;
         }
 
-        if (!isMatchCategory(providerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY),
-                consumerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY))) {
+        if (!isMatchCategory(providerUrl.getCategory(DEFAULT_CATEGORY),
+                consumerUrl.getCategory(DEFAULT_CATEGORY))) {
             return false;
         }
         if (!providerUrl.getParameter(ENABLED_KEY, true)
@@ -409,12 +411,12 @@ public class UrlUtils {
             return false;
         }
 
-        String consumerGroup = consumerUrl.getParameter(GROUP_KEY);
-        String consumerVersion = consumerUrl.getParameter(VERSION_KEY);
+        String consumerGroup = consumerUrl.getGroup();
+        String consumerVersion = consumerUrl.getVersion();
         String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
 
-        String providerGroup = providerUrl.getParameter(GROUP_KEY);
-        String providerVersion = providerUrl.getParameter(VERSION_KEY);
+        String providerGroup = providerUrl.getGroup();
+        String providerVersion = providerUrl.getVersion();
         String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
         return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))
                 && (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))
@@ -463,10 +465,10 @@ public class UrlUtils {
     public static boolean isServiceKeyMatch(URL pattern, URL value) {
         return pattern.getParameter(INTERFACE_KEY).equals(
                 value.getParameter(INTERFACE_KEY))
-                && isItemMatch(pattern.getParameter(GROUP_KEY),
-                value.getParameter(GROUP_KEY))
-                && isItemMatch(pattern.getParameter(VERSION_KEY),
-                value.getParameter(VERSION_KEY));
+                && isItemMatch(pattern.getGroup(),
+                value.getGroup())
+                && isItemMatch(pattern.getVersion(),
+                value.getVersion());
     }
 
     public static List<URL> classifyUrls(List<URL> urls, Predicate<URL> predicate) {
@@ -475,22 +477,24 @@ public class UrlUtils {
 
     public static boolean isConfigurator(URL url) {
         return OVERRIDE_PROTOCOL.equals(url.getProtocol()) ||
-                CONFIGURATORS_CATEGORY.equals(url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY));
+                CONFIGURATORS_CATEGORY.equals(url.getCategory(DEFAULT_CATEGORY));
     }
 
     public static boolean isRoute(URL url) {
         return ROUTE_PROTOCOL.equals(url.getProtocol()) ||
-                ROUTERS_CATEGORY.equals(url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY));
+                ROUTERS_CATEGORY.equals(url.getCategory(DEFAULT_CATEGORY));
     }
 
     public static boolean isProvider(URL url) {
         return !OVERRIDE_PROTOCOL.equals(url.getProtocol()) &&
                 !ROUTE_PROTOCOL.equals(url.getProtocol()) &&
-                PROVIDERS_CATEGORY.equals(url.getParameter(CATEGORY_KEY, PROVIDERS_CATEGORY));
+                PROVIDERS_CATEGORY.equals(url.getCategory(PROVIDERS_CATEGORY));
     }
 
     public static boolean isRegistry(URL url) {
-        return REGISTRY_PROTOCOL.equals(url.getProtocol()) || SERVICE_REGISTRY_PROTOCOL.equalsIgnoreCase(url.getProtocol());
+        return REGISTRY_PROTOCOL.equals(url.getProtocol())
+                || SERVICE_REGISTRY_PROTOCOL.equalsIgnoreCase(url.getProtocol())
+                || (url.getProtocol() != null && url.getProtocol().endsWith("-registry-protocol"));
     }
 
     /**
@@ -553,4 +557,101 @@ public class UrlUtils {
         arr[1] = serviceKey;
         return arr;
     }
+
+    /**
+     * NOTICE: This method allocate too much objects, we can use {@link URLStrParser#parseDecodedStr(String)} instead.
+     * <p>
+     * Parse url string
+     *
+     * @param url URL string
+     * @return URL instance
+     * @see URL
+     */
+    public static URL valueOf(String url) {
+        if (url == null || (url = url.trim()).length() == 0) {
+            throw new IllegalArgumentException("url == null");
+        }
+        String protocol = null;
+        String username = null;
+        String password = null;
+        String host = null;
+        int port = 0;
+        String path = null;
+        Map<String, String> parameters = null;
+        int i = url.indexOf('?'); // separator between body and parameters
+        if (i >= 0) {
+            String[] parts = url.substring(i + 1).split("&");
+            parameters = new HashMap<>();
+            for (String part : parts) {
+                part = part.trim();
+                if (part.length() > 0) {
+                    int j = part.indexOf('=');
+                    if (j >= 0) {
+                        String key = part.substring(0, j);
+                        String value = part.substring(j + 1);
+                        parameters.put(key, value);
+                        // compatible with lower versions registering "default." keys
+                        if (key.startsWith(DEFAULT_KEY_PREFIX)) {
+                            parameters.putIfAbsent(key.substring(DEFAULT_KEY_PREFIX.length()), value);
+                        }
+                    } else {
+                        parameters.put(part, part);
+                    }
+                }
+            }
+            url = url.substring(0, i);
+        }
+        i = url.indexOf("://");
+        if (i >= 0) {
+            if (i == 0) {
+                throw new IllegalStateException("url missing protocol: \"" + url + "\"");
+            }
+            protocol = url.substring(0, i);
+            url = url.substring(i + 3);
+        } else {
+            // case: file:/path/to/file.txt
+            i = url.indexOf(":/");
+            if (i >= 0) {
+                if (i == 0) {
+                    throw new IllegalStateException("url missing protocol: \"" + url + "\"");
+                }
+                protocol = url.substring(0, i);
+                url = url.substring(i + 1);
+            }
+        }
+
+        i = url.indexOf('/');
+        if (i >= 0) {
+            path = url.substring(i + 1);
+            url = url.substring(0, i);
+        }
+        i = url.lastIndexOf('@');
+        if (i >= 0) {
+            username = url.substring(0, i);
+            int j = username.indexOf(':');
+            if (j >= 0) {
+                password = username.substring(j + 1);
+                username = username.substring(0, j);
+            }
+            url = url.substring(i + 1);
+        }
+        i = url.lastIndexOf(':');
+        if (i >= 0 && i < url.length() - 1) {
+            if (url.lastIndexOf('%') > i) {
+                // ipv6 address with scope id
+                // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0
+                // see https://howdoesinternetwork.com/2013/ipv6-zone-id
+                // ignore
+            } else {
+                port = Integer.parseInt(url.substring(i + 1));
+                url = url.substring(0, i);
+            }
+        }
+        if (url.length() > 0) {
+            host = url;
+        }
+
+        return new URL(protocol, username, password, host, port, path, parameters);
+    }
+
 }
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 06bf05e..e8d79ee 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
@@ -37,6 +37,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DUMP_DIRECTORY;
 import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_LOCAL_FILE_CACHE_ENABLED;
 import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY;
 import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP;
 import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE;
@@ -160,6 +161,8 @@ public class ApplicationConfig extends AbstractConfig {
 
     private String repository;
 
+    private Boolean enableFileCache;
+
     private Boolean publishInterface;
 
     public ApplicationConfig() {
@@ -448,6 +451,15 @@ public class ApplicationConfig extends AbstractConfig {
         this.repository = repository;
     }
 
+    @Parameter(key = REGISTRY_LOCAL_FILE_CACHE_ENABLED)
+    public Boolean getEnableFileCache() {
+        return enableFileCache;
+    }
+
+    public void setEnableFileCache(Boolean enableFileCache) {
+        this.enableFileCache = enableFileCache;
+    }
+
     @Parameter(key = REGISTRY_PUBLISH_INTERFACE_KEY)
     public Boolean getPublishInterface() {
         return publishInterface;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/support/ProtocolUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/support/ProtocolUtils.java
index 801132f..85920e2 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/support/ProtocolUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/support/ProtocolUtils.java
@@ -27,8 +27,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZ
 import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_DEFAULT;
 import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_NATIVE_JAVA;
 import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_PROTOBUF;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 
 public class ProtocolUtils {
 
@@ -38,8 +36,8 @@ public class ProtocolUtils {
     }
 
     public static String serviceKey(URL url) {
-        return serviceKey(url.getPort(), url.getPath(), url.getParameter(VERSION_KEY),
-                url.getParameter(GROUP_KEY));
+        return serviceKey(url.getPort(), url.getPath(), url.getVersion(),
+                url.getGroup());
     }
 
     public static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) {
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/InterfaceAddressURLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/InterfaceAddressURLTest.java
new file mode 100644
index 0000000..8aad037
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/InterfaceAddressURLTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
+import org.apache.dubbo.common.url.component.ServiceAddressURL;
+import org.apache.dubbo.common.url.component.ServiceConfigURL;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+
+public class InterfaceAddressURLTest {
+    private static final String rawURL = "dubbo://10.20.130.230:20880/context/path?version=1.0.0&group=g1&application=provider&timeout=1000&category=provider&side=provider&sayHello.weight=222";
+    private static final URL overrideURL = URL.valueOf("override://10.20.130.230:20880/context/path?version=1.0.0&application=morgan&timeout=2000&category=configurators&sayHello.overrideKey=override");
+    private static final URL consumerURL = URL.valueOf("consumer://10.20.130.230/context/path?version=2.0.0,1.0.0&group=g2&application=morgan&timeout=3000&side=consumer&sayHello.timeout=5000");
+
+    @Test
+    public void testMergeOverriden() {
+        URL url = URL.valueOf(rawURL);
+        ServiceAddressURL interfaceAddressURL = new DubboServiceAddressURL(url.getUrlAddress(), url.getUrlParam(), null, null);
+        assertEquals("1000", interfaceAddressURL.getParameter(TIMEOUT_KEY));
+
+        ServiceAddressURL withConsumer = DubboServiceAddressURL.valueOf(rawURL, consumerURL);
+        assertEquals("3000", withConsumer.getParameter(TIMEOUT_KEY));
+
+        ServiceAddressURL withOverriden = DubboServiceAddressURL.valueOf(rawURL, consumerURL, (ServiceConfigURL) overrideURL);
+        assertEquals("2000", withOverriden.getParameter(TIMEOUT_KEY));
+    }
+
+    @Test
+    public void testGetParameter() {
+        URL url = URL.valueOf(rawURL);
+        ServiceAddressURL interfaceAddressURL = new DubboServiceAddressURL(url.getUrlAddress(), url.getUrlParam(), consumerURL, null);
+
+        assertEquals("3000", interfaceAddressURL.getParameter(TIMEOUT_KEY));
+        assertNotEquals("1.0.0", interfaceAddressURL.getVersion());
+
+        assertEquals("morgan", interfaceAddressURL.getApplication());
+        assertEquals("provider", interfaceAddressURL.getRemoteApplication());
+
+        assertEquals("dubbo", interfaceAddressURL.getProtocol());
+        assertEquals("context/path", interfaceAddressURL.getPath());
+
+        assertEquals("consumer", interfaceAddressURL.getSide());
+        assertEquals("1.0.0", interfaceAddressURL.getVersion());
+        assertEquals("g1", interfaceAddressURL.getGroup());
+    }
+
+    @Test
+    public void testGetMethodParameter() {
+        URL url = URL.valueOf(rawURL);
+        ServiceAddressURL interfaceAddressURL = new DubboServiceAddressURL(url.getUrlAddress(), url.getUrlParam(), consumerURL, (ServiceConfigURL) overrideURL);
+
+        assertEquals("5000", interfaceAddressURL.getMethodParameter("sayHello", TIMEOUT_KEY));
+        assertEquals("2000", interfaceAddressURL.getMethodParameter("non-exist-methods", TIMEOUT_KEY));
+        assertEquals("222", interfaceAddressURL.getMethodParameter("sayHello", "weight"));
+        assertEquals("222", interfaceAddressURL.getMethodParameter("sayHello", "weight"));
+        assertEquals("override", interfaceAddressURL.getMethodParameter("sayHello", "overrideKey"));
+    }
+
+    @Test
+    public void testURLEquals() {
+        URL url1 = URL.valueOf(rawURL);
+        URL url2 = URL.valueOf(rawURL);
+        assertNotSame(url1, url2);
+        assertEquals(url1, url2);
+
+        // with consumer
+        ServiceAddressURL withConsumer = new DubboServiceAddressURL(url1.getUrlAddress(), url1.getUrlParam(), consumerURL, null);
+        ServiceAddressURL withConsumer2 = new DubboServiceAddressURL(url1.getUrlAddress(), url1.getUrlParam(), consumerURL, null);
+        assertEquals(withConsumer, withConsumer2);
+
+        ServiceAddressURL withOverride = new DubboServiceAddressURL(url1.getUrlAddress(), url1.getUrlParam(), consumerURL, (ServiceConfigURL) overrideURL);
+        url2 = url2.addParameter("timeout", "4444");
+        ServiceAddressURL withOverride2 = new DubboServiceAddressURL(url2.getUrlAddress(), url2.getUrlParam(), consumerURL, (ServiceConfigURL) overrideURL);
+        assertNotEquals(url1, url2);
+        assertEquals(withOverride, withOverride2);
+    }
+
+    @Test
+    public void testToString() {
+        URL url1 = URL.valueOf(rawURL);
+        System.out.println(url1.toString());
+        ServiceAddressURL withConsumer = new DubboServiceAddressURL(url1.getUrlAddress(), url1.getUrlParam(), consumerURL, null);
+        System.out.println(withConsumer.toString());
+        ServiceAddressURL withOverride2 = new DubboServiceAddressURL(url1.getUrlAddress(), url1.getUrlParam(), consumerURL, (ServiceConfigURL) overrideURL);
+        System.out.println(withOverride2.toString());
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLBuilderTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLBuilderTest.java
index c607bd3..5fa5bec 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLBuilderTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLBuilderTest.java
@@ -40,7 +40,7 @@ public class URLBuilderTest {
                 .build();
         assertThat(url2.getParameter("newKey1"), equalTo("newValue1"));
         assertThat(url2.getParameter("newKey2"), equalTo("2"));
-        assertThat(url2.getParameter("version"), equalTo("1"));
+        assertThat(url2.getVersion(), equalTo("1"));
     }
 
     @Test
@@ -84,7 +84,7 @@ public class URLBuilderTest {
                 .removeParameters(Arrays.asList("key2", "application"))
                 .build();
         assertThat(url2.getParameters().size(), equalTo(1));
-        assertThat(url2.getParameter("version"), equalTo("1.0.0"));
+        assertThat(url2.getVersion(), equalTo("1.0.0"));
     }
 
     @Test
@@ -94,7 +94,7 @@ public class URLBuilderTest {
                 .addParameterIfAbsent("absentKey", "absentValue")
                 .addParameterIfAbsent("version", "2.0.0") // should not override
                 .build();
-        assertThat(url2.getParameter("version"), equalTo("1.0.0"));
+        assertThat(url2.getVersion(), equalTo("1.0.0"));
         assertThat(url2.getParameter("absentKey"), equalTo("absentValue"));
     }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
index 5ba606d..b48dbdb 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
@@ -50,7 +50,7 @@ public class URLTest {
         assertEquals(0, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = URL.valueOf("context/path?version=1.0.0&application=morgan");
@@ -63,7 +63,7 @@ public class URLTest {
         assertEquals(0, url.getPort());
         assertEquals("path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -132,7 +132,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -260,7 +260,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertNull(url.getPath());
         assertEquals(1, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
         assertURLStrDecoder(url);
@@ -272,10 +272,10 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue");
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue=");
         assertURLStrDecoder(url);
         assertEquals("dubbo", url.getProtocol());
         assertEquals("admin", url.getUsername());
@@ -285,7 +285,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(3, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
         assertEquals("noValue", url.getParameter("noValue"));
     }
@@ -301,7 +301,7 @@ public class URLTest {
 
     @Test
     public void test_noValueKey() throws Exception {
-        URL url = URL.valueOf("http://1.2.3.4:8080/path?k0&k1=v1");
+        URL url = URL.valueOf("http://1.2.3.4:8080/path?k0=&k1=v1");
 
         assertURLStrDecoder(url);
         assertTrue(url.hasParameter("k0"));
@@ -403,7 +403,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = url.setPort(1);
@@ -417,7 +417,7 @@ public class URLTest {
         assertEquals(1, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = url.setPath("path");
@@ -431,7 +431,7 @@ public class URLTest {
         assertEquals(1, url.getPort());
         assertEquals("path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = url.setProtocol("protocol");
@@ -445,7 +445,7 @@ public class URLTest {
         assertEquals(1, url.getPort());
         assertEquals("path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = url.setUsername("username");
@@ -459,7 +459,7 @@ public class URLTest {
         assertEquals(1, url.getPort());
         assertEquals("path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
 
         url = url.setPassword("password");
@@ -473,7 +473,7 @@ public class URLTest {
         assertEquals(1, url.getPort());
         assertEquals("path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -495,7 +495,7 @@ public class URLTest {
         assertEquals("morgan", url.getParameter("application"));
         assertEquals("v1", url.getParameter("k1"));
         assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
+        assertNull(url.getVersion());
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
         url = url.removeParameters("version", "application", "NotExistedKey");
@@ -510,7 +510,7 @@ public class URLTest {
         assertEquals(2, url.getParameters().size());
         assertEquals("v1", url.getParameter("k1"));
         assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
+        assertNull(url.getVersion());
         assertNull(url.getParameter("application"));
 
         url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
@@ -526,7 +526,7 @@ public class URLTest {
         assertEquals(2, url.getParameters().size());
         assertEquals("v1", url.getParameter("k1"));
         assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
+        assertNull(url.getVersion());
         assertNull(url.getParameter("application"));
     }
 
@@ -769,7 +769,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -787,7 +787,7 @@ public class URLTest {
         assertEquals(20880, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -801,7 +801,7 @@ public class URLTest {
         assertEquals(0, url.getPort());
         assertEquals("context/path", url.getPath());
         assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("morgan", url.getParameter("application"));
     }
 
@@ -897,4 +897,20 @@ public class URLTest {
         assertEquals(Integer.valueOf(1), url.getParameter("i", Integer.class));
         assertEquals(Boolean.FALSE, url.getParameter("b", Boolean.class));
     }
+
+    @Test
+    public void testEquals() {
+        URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        URL url2 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        Assertions.assertEquals(url1, url2);
+
+        URL url3 = URL.valueOf("10.20.130.230:20881/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        Assertions.assertNotEquals(url1, url3);
+
+        URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&weight=10&group=group&version=1.0.0");
+        Assertions.assertNotEquals(url1, url4);
+
+        URL url5 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&weight=10&group=group&version=1.0.0");
+        Assertions.assertEquals(url4, url5);
+    }
 }
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..ea54e9f 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,7 +17,7 @@
 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.concurrent.Executors;
@@ -27,7 +27,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 public class AbortPolicyWithReportTest {
     @Test
     public void jStackDumpTest() throws InterruptedException {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?dump.directory=/tmp&version=1.0.0&application=morgan&noValue");
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?dump.directory=/tmp&version=1.0.0&application=morgan&noValue=");
         AbortPolicyWithReport abortPolicyWithReport = new AbortPolicyWithReport("Test", url);
 
         try {
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/UrlUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/UrlUtilsTest.java
index a29c8e9..dc0eda6 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/UrlUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/UrlUtilsTest.java
@@ -367,7 +367,7 @@ public class UrlUtilsTest {
     @Test
     public void testIsMatchUrlWithDefaultPrefix() {
         URL url = URL.valueOf("dubbo://127.0.0.1:20880/com.xxx.XxxService?default.version=1.0.0&default.group=test");
-        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("1.0.0", url.getVersion());
         assertEquals("1.0.0", url.getParameter("default.version"));
 
         URL consumerUrl = URL.valueOf("consumer://127.0.0.1/com.xxx.XxxService?version=1.0.0&group=test");
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 7c6d463..874944a 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
@@ -337,7 +337,7 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
                             url = url.setPath(interfaceName);
                         }
                         if (UrlUtils.isRegistry(url)) {
-                            urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
+                            urls.add(url.putAttribute(REFER_KEY, map));
                         } else {
                             urls.add(ClusterUtils.mergeUrl(url, map));
                         }
@@ -354,7 +354,7 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
                             if (monitorUrl != null) {
                                 map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                             }
-                            urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
+                            urls.add(u.putAttribute(REFER_KEY, map));
                         }
                     }
                     if (urls.isEmpty()) {
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 e63c4f3..e2e8b09 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
@@ -484,9 +484,9 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
                         }
                         if (logger.isInfoEnabled()) {
                             if (url.getParameter(REGISTER_KEY, true)) {
-                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
+                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url.getServiceKey() + " to registry " + registryURL.getAddress());
                             } else {
-                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
+                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url.getServiceKey());
                             }
                         }
 
@@ -496,7 +496,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
                             registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                         }
 
-                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
+                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.putAttribute(EXPORT_KEY, url));
                         DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
 
                         Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
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 62e172d..45cb9c6 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
@@ -275,7 +275,7 @@ public class ConfigValidationUtils {
             return URLBuilder.from(registryURL)
                     .setProtocol(DUBBO_PROTOCOL)
                     .addParameter(PROTOCOL_KEY, monitor.getProtocol())
-                    .addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))
+                    .putAttribute(REFER_KEY, StringUtils.toQueryString(map))
                     .build();
         }
         return null;
@@ -529,7 +529,12 @@ public class ConfigValidationUtils {
     }
 
     private static String extractRegistryType(URL url) {
-        return isServiceDiscoveryRegistryType(url) ? SERVICE_REGISTRY_PROTOCOL : REGISTRY_PROTOCOL;
+        return isServiceDiscoveryRegistryType(url) ? SERVICE_REGISTRY_PROTOCOL : getRegistryProtocolType(url);
+    }
+
+    private static String getRegistryProtocolType(URL url) {
+        String registryProtocol = url.getParameter("registry-protocol-type");
+        return StringUtils.isNotEmpty(registryProtocol) ? registryProtocol : REGISTRY_PROTOCOL;
     }
 
     public static void checkExtension(Class<?> type, String property, String value) {
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..ed46382 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
@@ -258,7 +258,7 @@ public class ServiceConfigTest {
     @Test
     public void testApplicationInUrl() {
         service.export();
-        Assertions.assertNotNull(service.toUrl().getParameter(APPLICATION_KEY));
-        Assertions.assertEquals("app", service.toUrl().getParameter(APPLICATION_KEY));
+        Assertions.assertNotNull(service.toUrl().getApplication());
+        Assertions.assertEquals("app", service.toUrl().getApplication());
     }
 }
diff --git a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
index f191178..64626f7 100644
--- a/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-apollo/src/main/java/org/apache/dubbo/configcenter/support/apollo/ApolloDynamicConfiguration.java
@@ -44,11 +44,9 @@ import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.config.configcenter.Constants.CONFIG_NAMESPACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 
 /**
  * Apollo implementation, https://github.com/ctripcorp/apollo
@@ -97,7 +95,7 @@ public class ApolloDynamicConfiguration implements DynamicConfiguration {
         }
 
         String namespace = url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP);
-        String apolloNamespace = StringUtils.isEmpty(namespace) ? url.getParameter(GROUP_KEY, DEFAULT_GROUP) : namespace;
+        String apolloNamespace = StringUtils.isEmpty(namespace) ? url.getGroup(DEFAULT_GROUP) : namespace;
         dubboConfig = ConfigService.getConfig(apolloNamespace);
         dubboConfigFile = ConfigService.getConfigFile(apolloNamespace, ConfigFileFormat.Properties);
 
@@ -155,7 +153,7 @@ public class ApolloDynamicConfiguration implements DynamicConfiguration {
     @Override
     public String getConfig(String key, String group, long timeout) throws IllegalStateException {
         if (StringUtils.isNotEmpty(group)) {
-            if (group.equals(url.getParameter(APPLICATION_KEY))) {
+            if (group.equals(url.getApplication())) {
                 return ConfigService.getAppConfig().getProperty(key, null);
             } else {
                 return ConfigService.getConfig(group).getProperty(key, null);
@@ -181,7 +179,7 @@ public class ApolloDynamicConfiguration implements DynamicConfiguration {
         if (StringUtils.isEmpty(group)) {
             return dubboConfigFile.getContent();
         }
-        if (group.equals(url.getParameter(APPLICATION_KEY))) {
+        if (group.equals(url.getApplication())) {
             return ConfigService.getConfigFile(APOLLO_APPLICATION_KEY, ConfigFileFormat.Properties).getContent();
         }
 
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..fbdbf74 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 @@ public class ZookeeperDynamicConfiguration extends TreePathDynamicConfiguration
      */
     @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-provider-backend/pom.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/pom.xml
new file mode 100644
index 0000000..bceae6a
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/pom.xml
@@ -0,0 +1,107 @@
+<!--
+  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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-demo-xml</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>dubbo-demo-xml-provider-backend</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>The demo provider module of dubbo project</description>
+    <properties>
+        <skip_maven_deploy>true</skip_maven_deploy>
+        <slf4j-log4j12.version>1.7.25</slf4j-log4j12.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-demo-interface</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-multicast</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-nacos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.nacos</groupId>
+            <artifactId>nacos-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-zookeeper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-configcenter-zookeeper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-configcenter-nacos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-nacos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-zookeeper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-dubbo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-config-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-remoting-netty4</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-hessian2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-qos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>${slf4j-log4j12.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainApplication.java
similarity index 60%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainApplication.java
index d5b3dbc..f57e635 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainApplication.java
@@ -1,36 +1,27 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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.demo.provider.chain;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+public class ChainApplication {
+    public static void main(String[] args) throws Exception {
+        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider-chain.xml");
+        context.start();
+        System.in.read();
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainServiceImpl.java
similarity index 56%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainServiceImpl.java
index d5b3dbc..bd15576 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/java/org/apache/dubbo/demo/provider/chain/ChainServiceImpl.java
@@ -1,36 +1,33 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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.demo.provider.chain;
+
+import org.apache.dubbo.demo.ChainService;
+import org.apache.dubbo.rpc.RpcContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChainServiceImpl implements ChainService {
+    private static final Logger logger = LoggerFactory.getLogger(ChainServiceImpl.class);
+
+    @Override
+    public String chain(String input) {
+        logger.info("Received " + input + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
+        return "Received " + input + ", response from provider: " + RpcContext.getContext().getLocalAddress();
+    }
+}
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/dubbo.properties b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/dubbo.properties
new file mode 100644
index 0000000..ad602ba
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/dubbo.properties
@@ -0,0 +1 @@
+dubbo.application.qos.port=22222
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/log4j.properties b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/log4j.properties
new file mode 100644
index 0000000..15a0900
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/log4j.properties
@@ -0,0 +1,7 @@
+###set log levels###
+log4j.rootLogger=info, stdout
+###output to the console###
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n
\ No newline at end of file
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/spring/dubbo-provider-chain.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/spring/dubbo-provider-chain.xml
new file mode 100644
index 0000000..d677188
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider-backend/src/main/resources/spring/dubbo-provider-chain.xml
@@ -0,0 +1,35 @@
+<?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 metadata-type="remote" name="demo-provider-chain"/>
+    <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
+
+    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
+
+    <dubbo:protocol name="dubbo" port="-1"/>
+
+    <bean id="chainService" class="org.apache.dubbo.demo.provider.chain.ChainServiceImpl"/>
+
+    <dubbo:service group="g2" interface="org.apache.dubbo.demo.ChainService" ref="chainService"/>
+
+</beans>
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..b38b032 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
@@ -28,8 +28,6 @@ import java.util.Set;
 
 import static java.lang.String.valueOf;
 import static java.util.Arrays.asList;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
 
 /**
@@ -46,8 +44,8 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
     @Override
     public void map(URL url) {
         String serviceInterface = url.getServiceInterface();
-        String group = url.getParameter(GROUP_KEY);
-        String version = url.getParameter(VERSION_KEY);
+        String group = url.getGroup();
+        String version = url.getVersion();
         String protocol = url.getProtocol();
 
         if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
@@ -74,8 +72,8 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
     @Override
     public Set<String> getAndListen(URL url, MappingListener mappingListener) {
         String serviceInterface = url.getServiceInterface();
-        String group = url.getParameter(GROUP_KEY);
-        String version = url.getParameter(VERSION_KEY);
+        String group = url.getGroup();
+        String version = url.getVersion();
         String protocol = url.getProtocol();
         DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
 
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 1564f36..0ad553d 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
@@ -19,6 +19,7 @@ package org.apache.dubbo.metadata;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.compiler.support.ClassUtils;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.url.component.URLParam;
 import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -38,9 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DOT_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 
 public class MetadataInfo implements Serializable {
     public static String DEFAULT_REVISION = "0";
@@ -210,7 +209,7 @@ public class MetadataInfo implements Serializable {
         }
 
         public ServiceInfo(URL url) {
-            this(url.getServiceInterface(), url.getParameter(GROUP_KEY), url.getParameter(VERSION_KEY), url.getProtocol(), url.getPath(), null);
+            this(url.getServiceInterface(), url.getGroup(), url.getVersion(), url.getProtocol(), url.getPath(), null);
 
             this.url = url;
             Map<String, String> params = new HashMap<>();
@@ -339,8 +338,8 @@ public class MetadataInfo implements Serializable {
 
         public String getMethodParameter(String method, String key, String defaultValue) {
             if (methodParams == null) {
-                methodParams = URL.toMethodParameters(params);
-                consumerMethodParams = URL.toMethodParameters(consumerParams);
+                methodParams = URLParam.initMethodParameters(params);
+                consumerMethodParams = URLParam.initMethodParameters(consumerParams);
             }
 
             String value = getMethodParameter(method, key, consumerMethodParams);
@@ -372,8 +371,8 @@ public class MetadataInfo implements Serializable {
 
         public boolean hasMethodParameter(String method) {
             if (methodParams == null) {
-                methodParams = URL.toMethodParameters(params);
-                consumerMethodParams = URL.toMethodParameters(consumerParams);
+                methodParams = URLParam.initMethodParameters(params);
+                consumerMethodParams = URLParam.initMethodParameters(consumerParams);
             }
 
             return consumerMethodParams.containsKey(method) || methodParams.containsKey(method);
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifier.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifier.java
index ce7542f..b035a4f 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifier.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/MetadataIdentifier.java
@@ -18,11 +18,6 @@ package org.apache.dubbo.metadata.report.identifier;
 
 import org.apache.dubbo.common.URL;
 
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
-
 /**
  * The MetadataIdentifier is used to store method descriptor.
  * <p>
@@ -48,10 +43,10 @@ public class MetadataIdentifier extends BaseServiceMetadataIdentifier implements
 
     public MetadataIdentifier(URL url) {
         this.serviceInterface = url.getServiceInterface();
-        this.version = url.getParameter(VERSION_KEY);
-        this.group = url.getParameter(GROUP_KEY);
-        this.side = url.getParameter(SIDE_KEY);
-        setApplication(url.getParameter(APPLICATION_KEY));
+        this.version = url.getVersion();
+        this.group = url.getGroup();
+        this.side = url.getSide();
+        setApplication(url.getApplication());
     }
 
     public String getUniqueKey(KeyTypeEnum keyType) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/ServiceMetadataIdentifier.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/ServiceMetadataIdentifier.java
index 01a2196..100d844 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/ServiceMetadataIdentifier.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/ServiceMetadataIdentifier.java
@@ -18,9 +18,6 @@ package org.apache.dubbo.metadata.report.identifier;
 
 import org.apache.dubbo.common.URL;
 
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.metadata.MetadataConstants.KEY_REVISON_PREFIX;
 
 /**
@@ -49,9 +46,9 @@ public class ServiceMetadataIdentifier extends BaseServiceMetadataIdentifier imp
 
     public ServiceMetadataIdentifier(URL url) {
         this.serviceInterface = url.getServiceInterface();
-        this.version = url.getParameter(VERSION_KEY);
-        this.group = url.getParameter(GROUP_KEY);
-        this.side = url.getParameter(SIDE_KEY);
+        this.version = url.getVersion();
+        this.group = url.getGroup();
+        this.side = url.getSide();
         this.protocol = url.getProtocol();
     }
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/SubscriberMetadataIdentifier.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/SubscriberMetadataIdentifier.java
index 7a012e1..e375593 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/SubscriberMetadataIdentifier.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/SubscriberMetadataIdentifier.java
@@ -18,7 +18,6 @@ package org.apache.dubbo.metadata.report.identifier;
 
 import org.apache.dubbo.common.URL;
 
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
 
 /**
@@ -38,7 +37,7 @@ public class SubscriberMetadataIdentifier extends BaseApplicationMetadataIdentif
 
 
     public SubscriberMetadataIdentifier(URL url) {
-        this.application = url.getParameter(APPLICATION_KEY, "");
+        this.application = url.getApplication("");
         this.revision = url.getParameter(REVISION_KEY, "");
     }
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
index 1d39401..ab282fe 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
@@ -61,11 +61,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
-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.FILE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.utils.StringUtils.replace;
 import static org.apache.dubbo.metadata.report.support.Constants.CYCLE_REPORT_KEY;
 import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
 import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_RETRY_PERIOD;
@@ -103,7 +102,11 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     public AbstractMetadataReport(URL reportServerURL) {
         setUrl(reportServerURL);
         // Start file save timer
-        String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-metadata-" + reportServerURL.getParameter(APPLICATION_KEY) + "-" + reportServerURL.getAddress().replaceAll(":", "-") + ".cache";
+        String defaultFilename = System.getProperty("user.home") +
+                "/.dubbo/dubbo-metadata-" +
+                reportServerURL.getApplication() + "-" +
+                replace(reportServerURL.getAddress(), ":", "-") +
+                ".cache";
         String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
         File file = null;
         if (ConfigUtils.isNotEmpty(filename)) {
@@ -336,7 +339,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     }
 
     String getProtocol(URL url) {
-        String protocol = url.getParameter(SIDE_KEY);
+        String protocol = url.getSide();
         protocol = protocol == null ? url.getProtocol() : protocol;
         return protocol;
     }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java
index 35054c9..d084477 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/AbstractAbstractWritableMetadataService.java
@@ -25,10 +25,8 @@ import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
 
 import com.google.gson.Gson;
 
-import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
 import static org.apache.dubbo.common.utils.ClassUtils.forName;
 import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
@@ -56,7 +54,7 @@ public abstract class AbstractAbstractWritableMetadataService implements Writabl
         // Remove the useless parameters
         url = url.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY);
 
-        String side = url.getParameter(SIDE_KEY);
+        String side = url.getSide();
         if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
             publishProviderServiceDefinition(url);
         } else {
@@ -72,7 +70,7 @@ public abstract class AbstractAbstractWritableMetadataService implements Writabl
     }
 
     protected String getServiceDefinition(URL exportedURL) {
-        String interfaceName = exportedURL.getParameter(INTERFACE_KEY);
+        String interfaceName = exportedURL.getServiceInterface();
         String json = null;
         try {
             if (StringUtils.isNotEmpty(interfaceName) && !isGeneric(exportedURL.getParameter(GENERIC_KEY))) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
index 34ebce3..c52aff9 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/test/JTestMetadataReport4Test.java
@@ -30,8 +30,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-
 /**
  * ZookeeperRegistry
  */
@@ -48,7 +46,7 @@ public class JTestMetadataReport4Test extends AbstractMetadataReport {
 
 
     private static String getProtocol(URL url) {
-        String protocol = url.getParameter(SIDE_KEY);
+        String protocol = url.getSide();
         protocol = protocol == null ? url.getProtocol() : protocol;
         return protocol;
     }
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 d7abde3..20a7742 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
@@ -19,6 +19,7 @@ package org.apache.dubbo.metadata.store.zookeeper;
 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;
@@ -45,7 +46,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
 
 /**
@@ -68,7 +68,7 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
         if (url.isAnyHost()) {
             throw new IllegalStateException("registry address == null");
         }
-        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
+        String group = url.getGroup(DEFAULT_ROOT);
         if (!group.startsWith(PATH_SEPARATOR)) {
             group = PATH_SEPARATOR + group;
         }
@@ -161,24 +161,32 @@ public class ZookeeperMetadataReport extends AbstractMetadataReport {
 
     @Override
     public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
-        Set<String>  appNameSet = new HashSet<>();
+        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-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..1492e0b 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
@@ -36,14 +36,12 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.monitor.Constants.COUNT_PROTOCOL;
 import static org.apache.dubbo.rpc.Constants.INPUT_KEY;
@@ -150,15 +148,15 @@ public class MonitorFilter implements Filter, Filter.Listener {
         // ---- service statistics ----
         long elapsed = System.currentTimeMillis() - start; // invocation cost
         int concurrent = getConcurrent(invoker, invocation).get(); // current concurrent count
-        String application = invoker.getUrl().getParameter(APPLICATION_KEY);
+        String application = invoker.getUrl().getApplication();
         String service = invoker.getInterface().getName(); // service name
         String method = RpcUtils.getMethodName(invocation); // method name
-        String group = invoker.getUrl().getParameter(GROUP_KEY);
-        String version = invoker.getUrl().getParameter(VERSION_KEY);
+        String group = invoker.getUrl().getGroup();
+        String version = invoker.getUrl().getVersion();
 
         int localPort;
         String remoteKey, remoteValue;
-        if (CONSUMER_SIDE.equals(invoker.getUrl().getParameter(SIDE_KEY))) {
+        if (CONSUMER_SIDE.equals(invoker.getUrl().getSide())) {
             // ---- for service consumer ----
             localPort = 0;
             remoteKey = MonitorService.PROVIDER;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
index 638af54..1bf4168 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
@@ -59,11 +59,6 @@ public interface Constants {
     String REGISTRY_FILESAVE_SYNC_KEY = "save.file";
 
     /**
-     * Whether to cache locally, default is true
-     */
-    String REGISTRY__LOCAL_FILE_CACHE_ENABLED = "file.cache";
-
-    /**
      * Reconnection period in milliseconds for register center
      */
     String REGISTRY_RECONNECT_PERIOD_KEY = "reconnect.period";
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
index d5b3dbc..084deff 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
@@ -19,6 +19,8 @@ package org.apache.dubbo.registry;
 import org.apache.dubbo.common.Node;
 import org.apache.dubbo.common.URL;
 
+import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_DELAY_NOTIFICATION_KEY;
+
 /**
  * Registry. (SPI, Prototype, ThreadSafe)
  *
@@ -26,6 +28,10 @@ import org.apache.dubbo.common.URL;
  * @see org.apache.dubbo.registry.support.AbstractRegistry
  */
 public interface Registry extends Node, RegistryService {
+    default int getDelay() {
+        return getUrl().getParameter(REGISTRY_DELAY_NOTIFICATION_KEY, -1);
+    }
+
     default void reExportRegister(URL url) {
         register(url);
     }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryNotifier.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryNotifier.java
new file mode 100644
index 0000000..babf5ce
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/RegistryNotifier.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public abstract class RegistryNotifier {
+
+    private volatile long lastExecuteTime;
+    private volatile long lastEventTime;
+
+    private Object rawAddresses;
+    private Registry registry;
+
+    private ScheduledExecutorService scheduler = ExtensionLoader.getExtensionLoader(ExecutorRepository.class)
+            .getDefaultExtension().getRegistryNotificationExecutor();
+
+    public Registry getRegistry() {
+        return registry;
+    }
+
+    public RegistryNotifier(Registry registry) {
+        this.registry = registry;
+    }
+
+    public synchronized void notify(Object rawAddresses) {
+        this.rawAddresses = rawAddresses;
+        long notifyTime = System.currentTimeMillis();
+        this.lastEventTime = notifyTime;
+
+        int delayTime = getRegistry().getDelay();
+        long delta = (System.currentTimeMillis() - lastExecuteTime) - delayTime;
+        if (delta >= 0) {
+            scheduler.submit(new NotificationTask(this, notifyTime));
+        } else {
+            scheduler.schedule(new NotificationTask(this, notifyTime), -delta, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    protected abstract void doNotify(Object rawAddresses);
+
+    public static class NotificationTask implements Runnable {
+        private final RegistryNotifier listener;
+        private final long time;
+
+        public NotificationTask(RegistryNotifier listener, long time) {
+            this.listener = listener;
+            this.time = time;
+        }
+
+        @Override
+        public void run() {
+            if (this.time == listener.lastEventTime) {
+                listener.doNotify(listener.rawAddresses);
+                listener.lastExecuteTime = System.currentTimeMillis();
+                synchronized (listener) {
+                    if (this.time == listener.lastEventTime) {
+                        listener.rawAddresses = null;
+                    }
+                }
+            }
+        }
+    }
+
+}
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 51c9ffa..d2ee657 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
@@ -47,8 +47,6 @@ public class InstanceAddressURL extends URL {
     ) {
         this.instance = instance;
         this.metadataInfo = metadataInfo;
-        this.host = instance.getHost();
-        this.port = instance.getPort();
     }
 
     public ServiceInstance getInstance() {
@@ -93,6 +91,16 @@ public class InstanceAddressURL extends URL {
     }
 
     @Override
+    public String getHost() {
+        return instance.getHost();
+    }
+
+    @Override
+    public int getPort() {
+        return instance.getPort();
+    }
+
+    @Override
     public String getPath() {
         MetadataInfo.ServiceInfo serviceInfo = metadataInfo.getServiceInfo(getProtocolServiceKey());
         if (serviceInfo == null) {
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 65f50af..a6c7dd9 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
@@ -60,7 +60,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MAPPING_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDED_BY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
@@ -180,7 +179,7 @@ public class ServiceDiscoveryRegistry implements Registry {
 
     protected boolean shouldRegister(URL providerURL) {
 
-        String side = providerURL.getParameter(SIDE_KEY);
+        String side = providerURL.getSide();
 
         boolean should = PROVIDER_SIDE.equals(side); // Only register the Provider.
 
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 aff9c22..540deaf 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
@@ -21,6 +21,7 @@ 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.threadpool.manager.ExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.event.ConditionalEventListener;
 import org.apache.dubbo.event.EventListener;
 import org.apache.dubbo.metadata.MetadataInfo;
@@ -169,8 +170,12 @@ public class ServiceInstancesChangedListener implements ConditionalEventListener
         this.notifyAddressChanged();
     }
 
-    public void addListener(String serviceKey, NotifyListener listener) {
+    public synchronized void addListener(String serviceKey, NotifyListener listener) {
         this.listeners.put(serviceKey, listener);
+        List<URL> urls = this.serviceUrls.get(serviceKey);
+        if (CollectionUtils.isNotEmpty(urls)) {
+            listener.notify(urls);
+        }
     }
 
     public void removeListener(String serviceKey) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
index b6d7a9c..5c83825 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceNameMapping.java
@@ -31,8 +31,6 @@ import java.util.Set;
 
 import static java.util.Arrays.asList;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
 
 public class MetadataServiceNameMapping implements ServiceNameMapping {
@@ -41,8 +39,8 @@ public class MetadataServiceNameMapping implements ServiceNameMapping {
     @Override
     public void map(URL url) {
         String serviceInterface = url.getServiceInterface();
-        String group = url.getParameter(GROUP_KEY);
-        String version = url.getParameter(VERSION_KEY);
+        String group = url.getGroup();
+        String version = url.getVersion();
         String protocol = url.getProtocol();
 
         if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
@@ -56,8 +54,8 @@ public class MetadataServiceNameMapping implements ServiceNameMapping {
     @Override
     public Set<String> getAndListen(URL url, MappingListener mappingListener) {
         String serviceInterface = url.getServiceInterface();
-        String group = url.getParameter(GROUP_KEY);
-        String version = url.getParameter(VERSION_KEY);
+        String group = url.getGroup();
+        String version = url.getVersion();
         String protocol = url.getProtocol();
 
         String mappingKey = ServiceNameMapping.buildGroup(serviceInterface, group, version, protocol);
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 44c6bbc..c2f03ee 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
@@ -48,7 +48,6 @@ import java.util.concurrent.locks.ReentrantLock;
 import static java.util.Collections.emptySortedSet;
 import static java.util.Collections.unmodifiableSortedSet;
 import static org.apache.dubbo.common.URL.buildKey;
-import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
 import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
@@ -165,7 +164,7 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
     @Override
     public void publishServiceDefinition(URL providerUrl) {
         try {
-            String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
+            String interfaceName = providerUrl.getServiceInterface();
             if (StringUtils.isNotEmpty(interfaceName)
                     && !ProtocolUtils.isGeneric(providerUrl.getParameter(GENERIC_KEY))) {
                 Class interfaceClass = Class.forName(interfaceName);
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 209a9fa..25bc03c 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
@@ -35,15 +35,10 @@ import org.apache.dubbo.rpc.RpcException;
 
 import java.util.Map;
 
-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.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
 
 public class RemoteMetadataServiceImpl {
@@ -93,7 +88,7 @@ public class RemoteMetadataServiceImpl {
     }
 
     public void publishServiceDefinition(URL url) {
-        String side = url.getParameter(SIDE_KEY);
+        String side = url.getSide();
         if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
             //TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
             publishProvider(url);
@@ -110,7 +105,7 @@ public class RemoteMetadataServiceImpl {
                 Constants.BIND_PORT_KEY, TIMESTAMP_KEY);
 
         try {
-            String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
+            String interfaceName = providerUrl.getServiceInterface();
             if (StringUtils.isNotEmpty(interfaceName)) {
                 Class interfaceClass = Class.forName(interfaceName);
                 FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass,
@@ -118,8 +113,8 @@ public class RemoteMetadataServiceImpl {
                 for (Map.Entry<String, MetadataReport> entry : getMetadataReports().entrySet()) {
                     MetadataReport metadataReport = entry.getValue();
                     metadataReport.storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),
-                            providerUrl.getParameter(VERSION_KEY), providerUrl.getParameter(GROUP_KEY),
-                            PROVIDER_SIDE, providerUrl.getParameter(APPLICATION_KEY)), fullServiceDefinition);
+                            providerUrl.getVersion(), providerUrl.getGroup(),
+                            PROVIDER_SIDE, providerUrl.getApplication()), fullServiceDefinition);
                 }
                 return;
             }
@@ -135,8 +130,8 @@ public class RemoteMetadataServiceImpl {
                 Constants.BIND_PORT_KEY, TIMESTAMP_KEY);
         getMetadataReports().forEach((registryKey, config) -> {
             config.storeConsumerMetadata(new MetadataIdentifier(url.getServiceInterface(),
-                    url.getParameter(VERSION_KEY), url.getParameter(GROUP_KEY), CONSUMER_SIDE,
-                    url.getParameter(APPLICATION_KEY)), url.getParameters());
+                    url.getVersion(), url.getGroup(), CONSUMER_SIDE,
+                    url.getApplication()), url.getParameters());
         });
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/DefaultMigrationAddressComparator.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/DefaultMigrationAddressComparator.java
index 08ae6c9..aaa3b3b 100644
--- 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
@@ -35,11 +35,11 @@ public class DefaultMigrationAddressComparator implements MigrationAddressCompar
 
     @Override
     public <T> boolean shouldMigrate(ClusterInvoker<T> serviceDiscoveryInvoker, ClusterInvoker<T> invoker, MigrationRule rule) {
-        if (!serviceDiscoveryInvoker.isAvailable()) {
+        if (!serviceDiscoveryInvoker.hasProxyInvokers()) {
             logger.info("No instance address available, will not migrate.");
             return false;
         }
-        if (!invoker.isAvailable()) {
+        if (!invoker.hasProxyInvokers()) {
             logger.info("No interface address available, will migrate.");
             return true;
         }
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
index f8a78cb..28b49ce 100644
--- 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
@@ -167,6 +167,7 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
 
         switch (step) {
             case APPLICATION_FIRST:
+                // FIXME, check ClusterInvoker.hasProxyInvokers() or ClusterInvoker.isAvailable()
                 if (checkInvokerAvailable(serviceDiscoveryInvoker)) {
                     currentAvailableInvoker = serviceDiscoveryInvoker;
                 } else {
@@ -186,8 +187,9 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
 
     @Override
     public boolean isAvailable() {
-        return (invoker != null && invoker.isAvailable())
-                || (serviceDiscoveryInvoker != null && serviceDiscoveryInvoker.isAvailable());
+        return currentAvailableInvoker != null
+                ? currentAvailableInvoker.isAvailable()
+                : (invoker != null && invoker.isAvailable()) || (serviceDiscoveryInvoker != null && serviceDiscoveryInvoker.isAvailable());
     }
 
     @Override
@@ -202,7 +204,9 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
 
     @Override
     public URL getUrl() {
-        if (invoker != null) {
+        if (currentAvailableInvoker != null) {
+            return currentAvailableInvoker.getUrl();
+        } else if (invoker != null) {
             return invoker.getUrl();
         } else if (serviceDiscoveryInvoker != null) {
             return serviceDiscoveryInvoker.getUrl();
@@ -213,17 +217,21 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
 
     @Override
     public URL getRegistryUrl() {
-        if (invoker != null) {
+        if (currentAvailableInvoker != null) {
+            return currentAvailableInvoker.getRegistryUrl();
+        } else if (invoker != null) {
             return invoker.getRegistryUrl();
         } else if (serviceDiscoveryInvoker != null) {
-            serviceDiscoveryInvoker.getRegistryUrl();
+            return serviceDiscoveryInvoker.getRegistryUrl();
         }
         return url;
     }
 
     @Override
     public Directory<T> getDirectory() {
-        if (invoker != null) {
+        if (currentAvailableInvoker != null) {
+            return currentAvailableInvoker.getDirectory();
+        } else if (invoker != null) {
             return invoker.getDirectory();
         } else if (serviceDiscoveryInvoker != null) {
             return serviceDiscoveryInvoker.getDirectory();
@@ -233,8 +241,9 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
 
     @Override
     public boolean isDestroyed() {
-        return (invoker == null || invoker.isDestroyed())
-                && (serviceDiscoveryInvoker == null || serviceDiscoveryInvoker.isDestroyed());
+        return currentAvailableInvoker != null
+                ? currentAvailableInvoker.isDestroyed()
+                : (invoker == null || invoker.isDestroyed()) && (serviceDiscoveryInvoker == null || serviceDiscoveryInvoker.isDestroyed());
     }
 
     @Override
@@ -339,7 +348,7 @@ public class MigrationInvoker<T> implements MigrationClusterInvoker<T> {
     }
 
     protected synchronized void destroyInterfaceInvoker(ClusterInvoker<T> invoker) {
-        if (checkInvokerAvailable(this.serviceDiscoveryInvoker)) {
+        if (this.serviceDiscoveryInvoker != null) {
             this.currentAvailableInvoker = this.serviceDiscoveryInvoker;
             updateConsumerModel(currentAvailableInvoker, invoker);
         }
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 56dc3a9..61d33be 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,7 +17,6 @@
 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;
@@ -41,14 +40,8 @@ import java.util.Collections;
 import java.util.List;
 
 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;
@@ -108,8 +101,8 @@ public abstract class DynamicDirectory<T> extends AbstractDirectory<T> implement
         this.serviceType = serviceType;
         this.serviceKey = super.getConsumerUrl().getServiceKey();
 
-        this.overrideDirectoryUrl = this.directoryUrl = turnRegistryUrlToConsumerUrl(url);
-        String group = directoryUrl.getParameter(GROUP_KEY, "");
+        this.overrideDirectoryUrl = this.directoryUrl = consumerUrl;
+        String group = directoryUrl.getGroup("");
         this.multiGroup = group != null && (ANY_VALUE.equals(group) || group.contains(","));
     }
 
@@ -118,18 +111,6 @@ public abstract class DynamicDirectory<T> extends AbstractDirectory<T> implement
         this.serviceListener = instanceListener;
     }
 
-    private URL turnRegistryUrlToConsumerUrl(URL url) {
-        return URLBuilder.from(url)
-                .setHost(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;
     }
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 03cd9bf..cf89e51 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
@@ -17,11 +17,15 @@
 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.config.configcenter.DynamicConfiguration;
 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.url.component.DubboServiceAddressURL;
+import org.apache.dubbo.common.url.component.ServiceAddressURL;
+import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -39,7 +43,6 @@ import org.apache.dubbo.rpc.cluster.Router;
 import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
 import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.protocol.InvokerWrapper;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -53,12 +56,13 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 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.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CONFIG_KEY;
@@ -73,6 +77,7 @@ import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL
 import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX;
 import static org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.DEFAULT_REGISTER_CONSUMER_KEYS;
 import static org.apache.dubbo.remoting.Constants.CHECK_KEY;
+import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY;
 
 
@@ -232,7 +237,7 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
         List<Invoker<T>> mergedInvokers = new ArrayList<>();
         Map<String, List<Invoker<T>>> groupMap = new HashMap<>();
         for (Invoker<T> invoker : invokers) {
-            String group = invoker.getUrl().getParameter(GROUP_KEY, "");
+            String group = invoker.getUrl().getGroup("");
             groupMap.computeIfAbsent(group, k -> new ArrayList<>());
             groupMap.get(group).add(invoker);
         }
@@ -294,7 +299,6 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
         if (urls == null || urls.isEmpty()) {
             return newUrlInvokerMap;
         }
-        Set<URL> keys = new HashSet<>();
         String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
         for (URL providerUrl : urls) {
             // If protocol is configured at the reference side, only the matching protocol is selected
@@ -323,10 +327,6 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
             }
             URL url = mergeUrl(providerUrl);
 
-            if (keys.contains(url)) { // Repeated url
-                continue;
-            }
-            keys.add(url);
             // Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again
             Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
             Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(url);
@@ -339,7 +339,7 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
                         enabled = url.getParameter(ENABLED_KEY, true);
                     }
                     if (enabled) {
-                        invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
+                        invoker = protocol.refer(serviceType, url);
                     }
                 } catch (Throwable t) {
                     logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
@@ -351,7 +351,6 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
                 newUrlInvokerMap.put(url, invoker);
             }
         }
-        keys.clear();
         return newUrlInvokerMap;
     }
 
@@ -362,19 +361,24 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
      * @return
      */
     private URL mergeUrl(URL providerUrl) {
-        providerUrl = ClusterUtils.mergeProviderUrl(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!
+        if (providerUrl instanceof ServiceAddressURL) {
+            providerUrl = overrideWithConfigurator(providerUrl);
+        } else {
+            providerUrl = ClusterUtils.mergeProviderUrl(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
+        // FIXME, kept for mock
+        if (providerUrl.hasParameter(MOCK_KEY) || providerUrl.getAnyMethodParameter(MOCK_KEY) != null) {
+            providerUrl = providerUrl.removeParameter(TAG_KEY);
+            this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters());
+        }
 
         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 = directoryUrl.getParameter(INTERFACE_KEY);
+            String path = directoryUrl.getServiceInterface();
             if (path != null) {
                 int i = path.indexOf('/');
                 if (i >= 0) {
@@ -407,8 +411,29 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
 
     private URL overrideWithConfigurators(List<Configurator> configurators, URL url) {
         if (CollectionUtils.isNotEmpty(configurators)) {
-            for (Configurator configurator : configurators) {
-                url = configurator.configure(url);
+            if (url instanceof DubboServiceAddressURL) {
+                DubboServiceAddressURL interfaceAddressURL = (DubboServiceAddressURL) url;
+                URL overriddenURL = interfaceAddressURL.getOverrideURL();
+                if (overriddenURL == null) {
+                    String appName = interfaceAddressURL.getApplication();
+                    String side = interfaceAddressURL.getSide();
+                    overriddenURL = URLBuilder.from(interfaceAddressURL)
+                            .clearParameters()
+                            .addParameter(APPLICATION_KEY, appName)
+                            .addParameter(SIDE_KEY, side).build();
+                }
+                for (Configurator configurator : configurators) {
+                    overriddenURL = configurator.configure(overriddenURL);
+                }
+                url = new DubboServiceAddressURL(
+                        interfaceAddressURL.getUrlAddress(),
+                        interfaceAddressURL.getUrlParam(),
+                        interfaceAddressURL.getConsumerURL(),
+                        (ServiceConfigURL) overriddenURL);
+            } else {
+                for (Configurator configurator : configurators) {
+                    url = configurator.configure(url);
+                }
             }
         }
         return url;
@@ -561,7 +586,7 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
     }
 
     private boolean isValidCategory(URL url) {
-        String category = url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
+        String category = url.getCategory(DEFAULT_CATEGORY);
         if ((ROUTERS_CATEGORY.equals(category) || ROUTE_PROTOCOL.equals(url.getProtocol())) ||
                 PROVIDERS_CATEGORY.equals(category) ||
                 CONFIGURATORS_CATEGORY.equals(category) || DYNAMIC_CONFIGURATORS_CATEGORY.equals(category) ||
@@ -598,24 +623,6 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> implements NotifyL
         }
     }
 
-    /**
-     * The delegate class, which is mainly used to store the URL address sent by the registry,and can be reassembled on the basis of providerURL queryMap overrideMap for re-refer.
-     *
-     * @param <T>
-     */
-    private static class InvokerDelegate<T> extends InvokerWrapper<T> {
-        private URL providerUrl;
-
-        public InvokerDelegate(Invoker<T> invoker, URL url, URL providerUrl) {
-            super(invoker, url);
-            this.providerUrl = providerUrl;
-        }
-
-        public URL getProviderUrl() {
-            return providerUrl;
-        }
-    }
-
     private static class ReferenceConfigurationListener extends AbstractConfiguratorListener {
         private RegistryDirectory directory;
         private URL url;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
index eec9493..8b4a769 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
@@ -77,20 +77,18 @@ import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.common.constants.FilterConstants.VALIDATION_KEY;
 import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP;
 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.ALL_CATEGORIES;
 import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.OVERRIDE_PROTOCOL;
-import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
 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.ROUTERS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_PROTOCOL;
 import static org.apache.dubbo.common.utils.UrlUtils.classifyUrls;
 import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX;
@@ -123,7 +121,7 @@ public class RegistryProtocol implements Protocol {
     public static final String[] DEFAULT_REGISTER_PROVIDER_KEYS = {
             APPLICATION_KEY, CODEC_KEY, EXCHANGER_KEY, SERIALIZATION_KEY, CLUSTER_KEY, CONNECTIONS_KEY, DEPRECATED_KEY,
             GROUP_KEY, LOADBALANCE_KEY, MOCK_KEY, PATH_KEY, TIMEOUT_KEY, TOKEN_KEY, VERSION_KEY, WARMUP_KEY,
-            WEIGHT_KEY, TIMESTAMP_KEY, DUBBO_VERSION_KEY, RELEASE_KEY
+            WEIGHT_KEY, DUBBO_VERSION_KEY, RELEASE_KEY
     };
 
     public static final String[] DEFAULT_REGISTER_CONSUMER_KEYS = {
@@ -177,8 +175,7 @@ public class RegistryProtocol implements Protocol {
         return overrideListeners;
     }
 
-    private void register(URL registryUrl, URL registeredProviderUrl) {
-        Registry registry = registryFactory.getRegistry(registryUrl);
+    private void register(Registry registry, URL registeredProviderUrl) {
         registry.register(registeredProviderUrl);
     }
 
@@ -209,13 +206,13 @@ public class RegistryProtocol implements Protocol {
         final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
 
         // url to registry
-        final Registry registry = getRegistry(originInvoker);
+        final Registry registry = getRegistry(registryUrl);
         final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
 
         // decide if we need to delay publish
         boolean register = providerUrl.getParameter(REGISTER_KEY, true);
         if (register) {
-            register(registryUrl, registeredProviderUrl);
+            register(registry, registeredProviderUrl);
         }
 
         // register stated url on provider model
@@ -316,7 +313,7 @@ public class RegistryProtocol implements Protocol {
         if (getProviderUrl(originInvoker).getParameter(REGISTER_KEY, true)) {
             Registry registry = null;
             try {
-                registry = getRegistry(originInvoker);
+                registry = getRegistry(getRegistryUrl(originInvoker));
             } catch (Exception e) {
                 throw new SkipFailbackWrapperException(e);
             }
@@ -350,11 +347,10 @@ public class RegistryProtocol implements Protocol {
     /**
      * Get an instance of registry based on the address of invoker
      *
-     * @param originInvoker
+     * @param registryUrl
      * @return
      */
-    protected Registry getRegistry(final Invoker<?> originInvoker) {
-        URL registryUrl = getRegistryUrl(originInvoker);
+    protected Registry getRegistry(final URL registryUrl) {
         return registryFactory.getRegistry(registryUrl);
     }
 
@@ -411,11 +407,11 @@ public class RegistryProtocol implements Protocol {
      * @return
      */
     private URL getProviderUrl(final Invoker<?> originInvoker) {
-        String export = originInvoker.getUrl().getParameterAndDecoded(EXPORT_KEY);
-        if (export == null || export.length() == 0) {
-            throw new IllegalArgumentException("The registry export url is null! registry: " + originInvoker.getUrl());
+        Object providerURL = originInvoker.getUrl().getAttribute(EXPORT_KEY);
+        if (!(providerURL instanceof URL)) {
+            throw new IllegalArgumentException("The registry export url is null! registry: " + originInvoker.getUrl().getAddress());
         }
-        return URL.valueOf(export);
+        return (URL)providerURL;
     }
 
     /**
@@ -440,7 +436,7 @@ public class RegistryProtocol implements Protocol {
         }
 
         // group="a,b" or group="*"
-        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
+        Map<String, String> qs = (Map<String, String>)url.getAttribute(REFER_KEY);
         String group = qs.get(GROUP_KEY);
         if (group != null && group.length() > 0) {
             if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
@@ -512,7 +508,7 @@ public class RegistryProtocol implements Protocol {
     }
 
     public static URL toSubscribeUrl(URL url) {
-        return url.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY);
+        return url.addParameter(CATEGORY_KEY, ALL_CATEGORIES);
     }
 
     protected List<RegistryProtocolListener> findRegistryProtocolListeners(URL url) {
@@ -679,7 +675,7 @@ public class RegistryProtocol implements Protocol {
             for (URL url : configuratorUrls) {
                 URL overrideUrl = url;
                 // Compatible with the old version
-                if (url.getParameter(CATEGORY_KEY) == null && OVERRIDE_PROTOCOL.equals(url.getProtocol())) {
+                if (url.getCategory() == null && OVERRIDE_PROTOCOL.equals(url.getProtocol())) {
                     overrideUrl = url.addParameter(CATEGORY_KEY, CONFIGURATORS_CATEGORY);
                 }
 
@@ -773,7 +769,7 @@ public class RegistryProtocol implements Protocol {
             String key = getCacheKey(this.originInvoker);
             bounds.remove(key);
 
-            Registry registry = RegistryProtocol.this.getRegistry(originInvoker);
+            Registry registry = RegistryProtocol.this.getRegistry(getRegistryUrl(originInvoker));
             try {
                 registry.unregister(registerUrl);
             } catch (Throwable t) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
index efcd7e9..a73b75f 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistry.java
@@ -54,16 +54,14 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
 import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_LOCAL_FILE_CACHE_ENABLED;
 import static org.apache.dubbo.common.constants.RegistryConstants.ACCEPTS_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static org.apache.dubbo.registry.Constants.REGISTRY_FILESAVE_SYNC_KEY;
-import static org.apache.dubbo.registry.Constants.REGISTRY__LOCAL_FILE_CACHE_ENABLED;
 
 /**
  * AbstractRegistry. (SPI, Prototype, ThreadSafe)
@@ -92,13 +90,15 @@ public abstract class AbstractRegistry implements Registry {
     private URL registryUrl;
     // Local disk cache file
     private File file;
+    private boolean localCacheEnabled;
 
     public AbstractRegistry(URL url) {
         setUrl(url);
-        if (url.getParameter(REGISTRY__LOCAL_FILE_CACHE_ENABLED, true)) {
+        localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
+        if (localCacheEnabled) {
             // Start file save timer
             syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
-            String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress().replaceAll(":", "-") + ".cache";
+            String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getApplication() + "-" + url.getAddress().replaceAll(":", "-") + ".cache";
             String filename = url.getParameter(FILE_KEY, defaultFilename);
             File file = null;
             if (ConfigUtils.isNotEmpty(filename)) {
@@ -404,13 +404,13 @@ public abstract class AbstractRegistry implements Registry {
             return;
         }
         if (logger.isInfoEnabled()) {
-            logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
+            logger.info("Notify urls for subscribe url " + url + ", url size: " + urls.size());
         }
         // keep every provider's category.
         Map<String, List<URL>> result = new HashMap<>();
         for (URL u : urls) {
             if (UrlUtils.isMatch(url, u)) {
-                String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
+                String category = u.getCategory(DEFAULT_CATEGORY);
                 List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
                 categoryList.add(u);
             }
@@ -426,7 +426,9 @@ public abstract class AbstractRegistry implements Registry {
             listener.notify(categoryList);
             // We will update our cache file after each notification.
             // When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
-            saveProperties(url);
+            if (localCacheEnabled) {
+                saveProperties(url);
+            }
         }
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java
new file mode 100644
index 0000000..9ca1b74
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java
@@ -0,0 +1,326 @@
+/*
+ * 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.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLBuilder;
+import org.apache.dubbo.common.URLStrParser;
+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;
+import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
+import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
+import org.apache.dubbo.common.url.component.ServiceAddressURL;
+import org.apache.dubbo.common.url.component.URLAddress;
+import org.apache.dubbo.common.url.component.URLParam;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.common.utils.UrlUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.dubbo.common.URLStrParser.ENCODED_AND_MARK;
+import static org.apache.dubbo.common.URLStrParser.ENCODED_PID_KEY;
+import static org.apache.dubbo.common.URLStrParser.ENCODED_QUESTION_MARK;
+import static org.apache.dubbo.common.URLStrParser.ENCODED_TIMESTAMP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.CACHE_CLEAR_TASK_INTERVAL;
+import static org.apache.dubbo.common.constants.CommonConstants.CACHE_CLEAR_WAITING_THRESHOLD;
+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.DUBBO_VERSION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR_ENCODED;
+import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
+import static org.apache.dubbo.common.constants.RegistryConstants.OVERRIDE_PROTOCOL;
+import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL;
+
+/**
+ * Useful for registries who's sdk returns raw string as provider instance, for example, zookeeper and etcd.
+ */
+public abstract class CacheableFailbackRegistry extends FailbackRegistry {
+    private static final Logger logger = LoggerFactory.getLogger(CacheableFailbackRegistry.class);
+    private static String[] VARIABLE_KEYS = new String[]{ENCODED_TIMESTAMP_KEY, ENCODED_PID_KEY};
+
+    protected final static Map<String, URLAddress> stringAddress = new ConcurrentHashMap<>();
+    protected final static Map<String, URLParam> stringParam = new ConcurrentHashMap<>();
+    private static final ScheduledExecutorService cacheRemovalScheduler;
+    private static final int cacheRemovalTaskIntervalInMillis;
+    private static final int cacheClearWaitingThresholdInMillis;
+    private final static Map<ServiceAddressURL, Long> waitForRemove = new ConcurrentHashMap<>();
+    private static final Semaphore semaphore = new Semaphore(1);
+
+    private final Map<String, String> extraParameters;
+    protected final Map<URL, Map<String, ServiceAddressURL>> stringUrls = new HashMap<>();
+
+    static {
+        ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+        cacheRemovalScheduler = executorRepository.nextScheduledExecutor();
+        cacheRemovalTaskIntervalInMillis = getIntConfig(CACHE_CLEAR_TASK_INTERVAL, 10 * 60 * 1000);
+        cacheClearWaitingThresholdInMillis = getIntConfig(CACHE_CLEAR_WAITING_THRESHOLD, 10 * 60 * 1000);
+    }
+
+    public CacheableFailbackRegistry(URL url) {
+        super(url);
+        extraParameters = new HashMap<>(8);
+        extraParameters.put(CHECK_KEY, String.valueOf(false));
+    }
+
+    protected static int getIntConfig(String key, int def) {
+        String str = ConfigurationUtils.getProperty(key);
+        int result = def;
+        if (StringUtils.isNotEmpty(str)) {
+            try {
+                result = Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                logger.warn("Invalid registry properties configuration key " + key + ", value " + str);
+            }
+        }
+        return result;
+    }
+
+    protected List<URL> toUrlsWithoutEmpty(URL consumer, Collection<String> providers) {
+        // keep old urls
+        Map<String, ServiceAddressURL> oldURLs = stringUrls.get(consumer);
+        // create new urls
+        Map<String, ServiceAddressURL> newURLs;
+        URL copyOfConsumer = removeParamsFromConsumer(consumer);
+        if (oldURLs == null) {
+            newURLs = new HashMap<>();
+            for (String rawProvider : providers) {
+                rawProvider = stripOffVariableKeys(rawProvider);
+                ServiceAddressURL cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
+                if (cachedURL == null) {
+                    logger.warn("Invalid address, failed to parse into URL " + rawProvider);
+                    continue;
+                }
+                newURLs.put(rawProvider, cachedURL);
+            }
+        } else {
+            newURLs = new HashMap<>((int)(oldURLs.size()/.75 + 1));
+            // maybe only default , or "env" + default
+            for (String rawProvider : providers) {
+                rawProvider = stripOffVariableKeys(rawProvider);
+                ServiceAddressURL cachedURL = oldURLs.remove(rawProvider);
+                if (cachedURL == null) {
+                    cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
+                    if (cachedURL == null) {
+                        logger.warn("Invalid address, failed to parse into URL " + rawProvider);
+                        continue;
+                    }
+                }
+                newURLs.put(rawProvider, cachedURL);
+            }
+        }
+
+        stringUrls.put(consumer, newURLs);
+
+        // destroy used urls
+        try {
+            if (oldURLs != null && oldURLs.size() > 0) {
+                Long currentTimestamp = System.currentTimeMillis();
+                for (Map.Entry<String, ServiceAddressURL> entry : oldURLs.entrySet()) {
+                    waitForRemove.put(entry.getValue(), currentTimestamp);
+                }
+                if (CollectionUtils.isNotEmptyMap(waitForRemove)) {
+                    if (semaphore.tryAcquire()) {
+                        cacheRemovalScheduler.schedule(new RemovalTask(), cacheRemovalTaskIntervalInMillis, TimeUnit.MILLISECONDS);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("Failed to evict url for " + consumer, e);
+        }
+
+        return new ArrayList<>(newURLs.values());
+    }
+
+    protected List<URL> toUrlsWithEmpty(URL consumer, String path, Collection<String> providers) {
+        List<URL> urls;
+        if (CollectionUtils.isEmpty(providers)) {
+            urls = new ArrayList<>(1);
+        } else {
+            String rawProvider = providers.iterator().next();
+            if (rawProvider.startsWith(OVERRIDE_PROTOCOL) || rawProvider.startsWith(ROUTE_PROTOCOL)) {
+                urls = toConfiguratorsWithoutEmpty(consumer, providers);
+            } else {
+                urls = toUrlsWithoutEmpty(consumer, providers);
+            }
+        }
+        if (urls.isEmpty()) {
+            int i = path.lastIndexOf(PATH_SEPARATOR);
+            String category = i < 0 ? path : path.substring(i + 1);
+            URL empty = URLBuilder.from(consumer)
+                    .setProtocol(EMPTY_PROTOCOL)
+                    .addParameter(CATEGORY_KEY, category)
+                    .build();
+            urls.add(empty);
+        }
+        return urls;
+    }
+
+    protected ServiceAddressURL createURL(String rawProvider, URL consumerURL, Map<String, String> extraParameters) {
+        boolean encoded = true;
+        // use encoded value directly to avoid URLDecoder.decode allocation.
+        int paramStartIdx = rawProvider.indexOf(ENCODED_QUESTION_MARK);
+        if (paramStartIdx == -1) {// if ENCODED_QUESTION_MARK does not shown, mark as not encoded.
+            encoded = false;
+        }
+        String[] parts = URLStrParser.parseRawURLToArrays(rawProvider, paramStartIdx);
+        if (parts.length <= 1) {
+            logger.warn("Received url without any parameters " + rawProvider);
+            return DubboServiceAddressURL.valueOf(rawProvider, consumerURL);
+        }
+
+        String rawAddress = parts[0];
+        String rawParams = parts[1];
+        boolean isEncoded = encoded;
+        URLAddress address = stringAddress.computeIfAbsent(rawAddress, k -> {
+            URLAddress newAddress = URLAddress.parse(k, getDefaultURLProtocol(), isEncoded);
+            stringAddress.put(k, newAddress);
+            return newAddress;
+        });
+        address.setTimestamp(System.currentTimeMillis());
+
+        URLParam param = stringParam.computeIfAbsent(rawParams, k -> {
+            URLParam newParam = URLParam.parse(k, isEncoded, extraParameters);
+            stringParam.put(k, newParam);
+            return newParam;
+        });
+        param.setTimestamp(System.currentTimeMillis());
+
+        ServiceAddressURL cachedURL = createServiceURL(address, param, consumerURL);
+        if (isMatch(consumerURL, cachedURL)) {
+            return cachedURL;
+        }
+        return null;
+    }
+
+
+    protected ServiceAddressURL createServiceURL(URLAddress address, URLParam param, URL consumerURL) {
+        return new DubboServiceAddressURL(address, param, consumerURL, null);
+    }
+
+    protected URL removeParamsFromConsumer(URL consumer) {
+        return consumer.removeParameters(RELEASE_KEY, DUBBO_VERSION_KEY, METHODS_KEY, TIMESTAMP_KEY, TAG_KEY);
+    }
+
+    private String stripOffVariableKeys(String rawProvider) {
+        String[] keys = getVariableKeys();
+        if (keys == null || keys.length == 0) {
+            return rawProvider;
+        }
+
+        for (String key : keys) {
+            int idxStart = rawProvider.indexOf(key);
+            if (idxStart == -1) {
+                continue;
+            }
+            int idxEnd = rawProvider.indexOf(ENCODED_AND_MARK, idxStart);
+            String part1 = rawProvider.substring(0, idxStart);
+            if (idxEnd == -1) {
+                rawProvider = part1;
+            } else {
+                String part2 = rawProvider.substring(idxEnd + ENCODED_AND_MARK.length());
+                rawProvider = part1 + part2;
+            }
+        }
+
+        if (rawProvider.endsWith(ENCODED_AND_MARK)) {
+            rawProvider = rawProvider.substring(0, rawProvider.length() - ENCODED_AND_MARK.length());
+        }
+        if (rawProvider.endsWith(ENCODED_QUESTION_MARK)) {
+            rawProvider = rawProvider.substring(0, rawProvider.length() - ENCODED_QUESTION_MARK.length());
+        }
+
+        return rawProvider;
+    }
+
+    private List<URL> toConfiguratorsWithoutEmpty(URL consumer, Collection<String> configurators) {
+        List<URL> urls = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(configurators)) {
+            for (String provider : configurators) {
+                if (provider.contains(PROTOCOL_SEPARATOR_ENCODED)) {
+                    URL url = URLStrParser.parseEncodedStr(provider);
+                    if (UrlUtils.isMatch(consumer, url)) {
+                        urls.add(url);
+                    }
+                }
+            }
+        }
+        return urls;
+    }
+
+    protected Map<String, String> getExtraParameters() {
+        return extraParameters;
+    }
+
+    protected String[] getVariableKeys() {
+       return VARIABLE_KEYS;
+    }
+
+    protected String getDefaultURLProtocol() {
+        return DUBBO;
+    }
+
+    protected abstract boolean isMatch(URL subscribeUrl, URL providerUrl);
+
+
+    private static class RemovalTask implements Runnable {
+        @Override
+        public void run() {
+            semaphore.release();
+            logger.info("Clearing cached URLs, size " + waitForRemove.size());
+            Iterator<Map.Entry<ServiceAddressURL, Long>> it = waitForRemove.entrySet().iterator();
+            while(it.hasNext()) {
+                Map.Entry<ServiceAddressURL, Long> entry = it.next();
+                ServiceAddressURL removeURL = entry.getKey();
+                long removeTime = entry.getValue();
+                long current = System.currentTimeMillis();
+                if (current - removeTime >= cacheClearWaitingThresholdInMillis) {
+                    URLAddress urlAddress = removeURL.getUrlAddress();
+                    URLParam urlParam = removeURL.getUrlParam();
+                    if (current - urlAddress.getTimestamp() >= cacheClearWaitingThresholdInMillis) {
+                        stringAddress.remove(urlAddress.getRawAddress());
+                    }
+                    if (current - urlParam.getTimestamp() >= cacheClearWaitingThresholdInMillis) {
+                        stringParam.remove(urlParam.getRawParam());
+                    }
+                    it.remove();
+                }
+            }
+
+            if (semaphore.tryAcquire() && CollectionUtils.isNotEmptyMap(waitForRemove)) {//move to next schedule
+                cacheRemovalScheduler.schedule(new RemovalTask(), cacheRemovalTaskIntervalInMillis, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
similarity index 63%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
index d5b3dbc..6045e2c 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
@@ -1,36 +1,37 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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;
+
+public class CacheableFailbackRegistryTest {
+
+    public void testFullURLCache() {
+
+    }
+
+    public void testURLAddressCache() {
+
+    }
+
+    public void testURLParamCache() {
+
+    }
+
+    public void testRemove() {
+
+    }
+
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/DelayedRegistryNotifierTest.java
similarity index 62%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/DelayedRegistryNotifierTest.java
index d5b3dbc..c158fdc 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/DelayedRegistryNotifierTest.java
@@ -1,36 +1,21 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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;
+
+public class DelayedRegistryNotifierTest {
+
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/MockCacheableRegistryImpl.java
similarity index 54%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/MockCacheableRegistryImpl.java
index d5b3dbc..2ebab9a 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/MockCacheableRegistryImpl.java
@@ -1,36 +1,65 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.support.CacheableFailbackRegistry;
+
+/**
+ *
+ */
+public class MockCacheableRegistryImpl extends CacheableFailbackRegistry {
+
+    public MockCacheableRegistryImpl(URL url) {
+        super(url);
+    }
+
+    @Override
+    public int getDelay() {
+        return 0;
+    }
+
+    @Override
+    protected boolean isMatch(URL subscribeUrl, URL providerUrl) {
+        return false;
+    }
+
+    @Override
+    public void doRegister(URL url) {
+
+    }
+
+    @Override
+    public void doUnregister(URL url) {
+
+    }
+
+    @Override
+    public void doSubscribe(URL url, NotifyListener listener) {
+
+    }
+
+    @Override
+    public void doUnsubscribe(URL url, NotifyListener listener) {
+
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return false;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscovery.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscovery.java
new file mode 100644
index 0000000..084376b
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscovery.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 MultipleServiceDiscovery 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-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscoveryFactory.java
similarity index 63%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscoveryFactory.java
index d5b3dbc..9de8d3d 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleServiceDiscoveryFactory.java
@@ -1,36 +1,28 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
+/*
+ * 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.registry.client.AbstractServiceDiscoveryFactory;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+
+public class MultipleServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
+    @Override
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new MultipleServiceDiscovery();
+    }
 }
\ 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.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..9fc8f6d
--- /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.MultipleServiceDiscovery
\ 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..5565f75
--- /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.MultipleServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistryTestUtil.java b/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistryTestUtil.java
index a0aeef2..5f5adfa 100644
--- a/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistryTestUtil.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistryTestUtil.java
@@ -31,7 +31,6 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
-import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CONFIG_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
@@ -85,7 +84,7 @@ public class MultipleRegistryTestUtil {
     }
 
     private static boolean isValidCategory(URL url) {
-        String category = url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
+        String category = url.getCategory(DEFAULT_CATEGORY);
         if ((ROUTERS_CATEGORY.equals(category) || ROUTE_PROTOCOL.equals(url.getProtocol())) ||
                 PROVIDERS_CATEGORY.equals(category) ||
                 CONFIGURATORS_CATEGORY.equals(category) || DYNAMIC_CONFIGURATORS_CATEGORY.equals(category) ||
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java
index 187da71..923401f 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java
@@ -17,25 +17,28 @@
 package org.apache.dubbo.registry.nacos;
 
 
-import com.alibaba.nacos.api.common.Constants;
-import com.google.common.collect.Lists;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.RegistryNotifier;
 import org.apache.dubbo.registry.nacos.util.NacosInstanceManageUtil;
 import org.apache.dubbo.registry.support.FailbackRegistry;
 
+import com.alibaba.nacos.api.common.Constants;
 import com.alibaba.nacos.api.exception.NacosException;
 import com.alibaba.nacos.api.naming.NamingService;
+import com.alibaba.nacos.api.naming.listener.Event;
 import com.alibaba.nacos.api.naming.listener.EventListener;
 import com.alibaba.nacos.api.naming.listener.NamingEvent;
 import com.alibaba.nacos.api.naming.pojo.Instance;
 import com.alibaba.nacos.api.naming.pojo.ListView;
+import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -140,7 +143,7 @@ public class NacosRegistry extends FailbackRegistry {
             Set<String> serviceNames = getServiceNames(url, null);
             for (String serviceName : serviceNames) {
                 List<Instance> instances = namingService.getAllInstances(serviceName,
-                        getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP));
+                        getUrl().getGroup(Constants.DEFAULT_GROUP));
                 urls.addAll(buildURLs(url, instances));
             }
         });
@@ -158,7 +161,7 @@ public class NacosRegistry extends FailbackRegistry {
          * in https://github.com/apache/dubbo/issues/5978
          */
         execute(namingService -> namingService.registerInstance(serviceName,
-                getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP), instance));
+                getUrl().getGroup(Constants.DEFAULT_GROUP), instance));
     }
 
     @Override
@@ -167,7 +170,7 @@ public class NacosRegistry extends FailbackRegistry {
             String serviceName = getServiceName(url);
             Instance instance = createInstance(url);
             namingService.deregisterInstance(serviceName,
-                    getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP),
+                    getUrl().getGroup(Constants.DEFAULT_GROUP),
                     instance.getIp()
                     , instance.getPort());
         });
@@ -203,7 +206,7 @@ public class NacosRegistry extends FailbackRegistry {
                  */
                 for (String serviceName : serviceNames) {
                     List<Instance> instances = namingService.getAllInstances(serviceName,
-                            getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP));
+                            getUrl().getGroup(Constants.DEFAULT_GROUP));
                     NacosInstanceManageUtil.initOrRefreshServiceInstanceList(serviceName, instances);
                     allCorrespondingInstanceList.addAll(instances);
                 }
@@ -215,7 +218,7 @@ public class NacosRegistry extends FailbackRegistry {
                 List<Instance> instances = new LinkedList<>();
                 for (String serviceName : serviceNames) {
                     instances.addAll(namingService.getAllInstances(serviceName
-                            , getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP)));
+                            , getUrl().getGroup(Constants.DEFAULT_GROUP)));
                     notifySubscriber(url, listener, instances);
                     subscribeEventListener(serviceName, url, listener);
                 }
@@ -295,7 +298,7 @@ public class NacosRegistry extends FailbackRegistry {
         execute(namingService -> {
 
             serviceNames.addAll(namingService.getServicesOfServer(1, Integer.MAX_VALUE,
-                    getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP)).getData()
+                    getUrl().getGroup(Constants.DEFAULT_GROUP)).getData()
                     .stream()
                     .map(NacosServiceName::new)
                     .filter(serviceName::isCompatible)
@@ -375,7 +378,7 @@ public class NacosRegistry extends FailbackRegistry {
 
             int pageIndex = 1;
             ListView<String> listView = namingService.getServicesOfServer(pageIndex, PAGINATION_SIZE,
-                    getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP));
+                    getUrl().getGroup(Constants.DEFAULT_GROUP));
             // First page data
             List<String> firstPageData = listView.getData();
             // Append first page into list
@@ -392,7 +395,7 @@ public class NacosRegistry extends FailbackRegistry {
             // If more than 1 page
             while (pageIndex < pageNumbers) {
                 listView = namingService.getServicesOfServer(++pageIndex, PAGINATION_SIZE,
-                        getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP));
+                        getUrl().getGroup(Constants.DEFAULT_GROUP));
                 serviceNames.addAll(listView.getData());
             }
 
@@ -407,9 +410,9 @@ public class NacosRegistry extends FailbackRegistry {
 
         final String targetServiceInterface = url.getServiceInterface();
 
-        final String targetVersion = url.getParameter(VERSION_KEY, "");
+        final String targetVersion = url.getVersion("");
 
-        final String targetGroup = url.getParameter(GROUP_KEY, "");
+        final String targetGroup = url.getGroup("");
 
         filterData(serviceNames, serviceName -> {
             // split service name to segments
@@ -478,7 +481,7 @@ public class NacosRegistry extends FailbackRegistry {
         List<URL> urls = new LinkedList<>();
         if (instances != null && !instances.isEmpty()) {
             for (Instance instance : instances) {
-                URL url = buildURL(instance);
+                URL url = buildURL(consumerURL, instance);
                 if (UrlUtils.isMatch(consumerURL, url)) {
                     urls.add(url);
                 }
@@ -489,26 +492,9 @@ public class NacosRegistry extends FailbackRegistry {
 
     private void subscribeEventListener(String serviceName, final URL url, final NotifyListener listener)
             throws NacosException {
-        EventListener eventListener = event -> {
-            if (event instanceof NamingEvent) {
-                NamingEvent e = (NamingEvent) event;
-                List<Instance> instances = e.getInstances();
-
-
-                if (isServiceNamesWithCompatibleMode(url)) {
-                    /**
-                     * Get all instances with corresponding serviceNames to avoid instance overwrite and but with empty instance mentioned
-                     * in https://github.com/apache/dubbo/issues/5885 and https://github.com/apache/dubbo/issues/5899
-                     */
-                    NacosInstanceManageUtil.initOrRefreshServiceInstanceList(serviceName, instances);
-                    instances = NacosInstanceManageUtil.getAllCorrespondingServiceInstanceList(serviceName);
-                }
-
-                notifySubscriber(url, listener, instances);
-            }
-        };
+        EventListener eventListener = new RegistryChildListenerImpl(serviceName, url, listener);
         namingService.subscribe(serviceName,
-                getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP),
+                getUrl().getGroup(Constants.DEFAULT_GROUP),
                 eventListener);
     }
 
@@ -540,20 +526,21 @@ public class NacosRegistry extends FailbackRegistry {
                 ALL_SUPPORTED_CATEGORIES : Arrays.asList(DEFAULT_CATEGORY);
     }
 
-    private URL buildURL(Instance instance) {
+    private URL buildURL(URL consumerURL, Instance instance) {
         Map<String, String> metadata = instance.getMetadata();
         String protocol = metadata.get(PROTOCOL_KEY);
         String path = metadata.get(PATH_KEY);
-        return new URL(protocol,
+        URL url = new URL(protocol,
                 instance.getIp(),
                 instance.getPort(),
                 path,
                 instance.getMetadata());
+        return new DubboServiceAddressURL(url.getUrlAddress(), url.getUrlParam(), consumerURL, null);
     }
 
     private Instance createInstance(URL url) {
         // Append default category if absent
-        String category = url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
+        String category = url.getCategory(DEFAULT_CATEGORY);
         URL newURL = url.addParameter(CATEGORY_KEY, category);
         newURL = newURL.addParameter(PROTOCOL_KEY, url.getProtocol());
         newURL = newURL.addParameter(PATH_KEY, url.getPath());
@@ -571,7 +558,7 @@ public class NacosRegistry extends FailbackRegistry {
     }
 
     private String getServiceName(URL url) {
-        return getServiceName(url, url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY));
+        return getServiceName(url, url.getCategory(DEFAULT_CATEGORY));
     }
 
     private String getServiceName(URL url, String category) {
@@ -626,4 +613,35 @@ public class NacosRegistry extends FailbackRegistry {
         void callback(NamingService namingService) throws NacosException;
 
     }
+
+    private class RegistryChildListenerImpl implements EventListener {
+        private RegistryNotifier notifier;
+
+        public RegistryChildListenerImpl(String serviceName, URL consumerUrl, NotifyListener listener) {
+            notifier = new RegistryNotifier(NacosRegistry.this) {
+                @Override
+                protected void doNotify(Object rawAddresses) {
+                    List<Instance> instances = (List<Instance>) rawAddresses;
+                    if (isServiceNamesWithCompatibleMode(consumerUrl)) {
+                        /**
+                         * Get all instances with corresponding serviceNames to avoid instance overwrite and but with empty instance mentioned
+                         * in https://github.com/apache/dubbo/issues/5885 and https://github.com/apache/dubbo/issues/5899
+                         */
+                        NacosInstanceManageUtil.initOrRefreshServiceInstanceList(serviceName, instances);
+                        instances = NacosInstanceManageUtil.getAllCorrespondingServiceInstanceList(serviceName);
+                    }
+                    NacosRegistry.this.notifySubscriber(consumerUrl, listener, instances);
+                }
+            };
+        }
+
+        @Override
+        public void onEvent(Event event) {
+            if (event instanceof NamingEvent) {
+                NamingEvent e = (NamingEvent) event;
+                notifier.notify(e.getInstances());
+            }
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceName.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceName.java
index 27c45ee..03a74df 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceName.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceName.java
@@ -22,10 +22,7 @@ import org.apache.dubbo.common.utils.StringUtils;
 import java.util.Arrays;
 import java.util.Objects;
 
-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.VERSION_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
@@ -67,9 +64,9 @@ public class NacosServiceName {
 
     public NacosServiceName(URL url) {
         serviceInterface = url.getParameter(INTERFACE_KEY);
-        category = isConcrete(serviceInterface) ? DEFAULT_CATEGORY : url.getParameter(CATEGORY_KEY);
-        version = url.getParameter(VERSION_KEY, DEFAULT_PARAM_VALUE);
-        group = url.getParameter(GROUP_KEY, DEFAULT_PARAM_VALUE);
+        category = isConcrete(serviceInterface) ? DEFAULT_CATEGORY : url.getCategory();
+        version = url.getVersion(DEFAULT_PARAM_VALUE);
+        group = url.getGroup(DEFAULT_PARAM_VALUE);
         value = toValue();
     }
 
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
index 51fa4aa..ae22530 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java
@@ -17,15 +17,14 @@
 package org.apache.dubbo.registry.zookeeper;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.URLBuilder;
-import org.apache.dubbo.common.URLStrParser;
 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.UrlUtils;
 import org.apache.dubbo.registry.NotifyListener;
-import org.apache.dubbo.registry.support.FailbackRegistry;
+import org.apache.dubbo.registry.RegistryNotifier;
+import org.apache.dubbo.registry.support.CacheableFailbackRegistry;
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.zookeeper.ChildListener;
 import org.apache.dubbo.remoting.zookeeper.StateListener;
@@ -40,26 +39,22 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
-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.PATH_SEPARATOR;
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR_ENCODED;
-import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGORY;
 
 /**
  * ZookeeperRegistry
- *
  */
-public class ZookeeperRegistry extends FailbackRegistry {
+public class ZookeeperRegistry extends CacheableFailbackRegistry {
 
     private final static Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
 
@@ -78,7 +73,7 @@ public class ZookeeperRegistry extends FailbackRegistry {
         if (url.isAnyHost()) {
             throw new IllegalStateException("registry address == null");
         }
-        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
+        String group = url.getGroup(DEFAULT_ROOT);
         if (!group.startsWith(PATH_SEPARATOR)) {
             group = PATH_SEPARATOR + group;
         }
@@ -168,10 +163,11 @@ public class ZookeeperRegistry extends FailbackRegistry {
                     }
                 }
             } else {
+                CountDownLatch latch = new CountDownLatch(1);
                 List<URL> urls = new ArrayList<>();
                 for (String path : toCategoriesPath(url)) {
                     ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
-                    ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
+                    ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, path, k, latch));
                     zkClient.create(path, false);
                     List<String> children = zkClient.addChildListener(path, zkListener);
                     if (children != null) {
@@ -179,6 +175,8 @@ public class ZookeeperRegistry extends FailbackRegistry {
                     }
                 }
                 notify(url, listener, urls);
+                // tells the listener to run only after the sync notification of main thread finishes.
+                latch.countDown();
             }
         } catch (Throwable e) {
             throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
@@ -187,9 +185,9 @@ public class ZookeeperRegistry extends FailbackRegistry {
 
     @Override
     public void doUnsubscribe(URL url, NotifyListener listener) {
-        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
+        ConcurrentMap<NotifyListener, org.apache.dubbo.remoting.zookeeper.ChildListener> listeners = zkListeners.get(url);
         if (listeners != null) {
-            ChildListener zkListener = listeners.get(listener);
+            org.apache.dubbo.remoting.zookeeper.ChildListener zkListener = listeners.get(listener);
             if (zkListener != null) {
                 if (ANY_VALUE.equals(url.getServiceInterface())) {
                     String root = toRootPath();
@@ -243,10 +241,10 @@ public class ZookeeperRegistry extends FailbackRegistry {
 
     private String[] toCategoriesPath(URL url) {
         String[] categories;
-        if (ANY_VALUE.equals(url.getParameter(CATEGORY_KEY))) {
+        if (ANY_VALUE.equals(url.getCategory())) {
             categories = new String[]{PROVIDERS_CATEGORY, CONSUMERS_CATEGORY, ROUTERS_CATEGORY, CONFIGURATORS_CATEGORY};
         } else {
-            categories = url.getParameter(CATEGORY_KEY, new String[]{DEFAULT_CATEGORY});
+            categories = url.getCategory(new String[]{DEFAULT_CATEGORY});
         }
         String[] paths = new String[categories.length];
         for (int i = 0; i < categories.length; i++) {
@@ -256,42 +254,13 @@ public class ZookeeperRegistry extends FailbackRegistry {
     }
 
     private String toCategoryPath(URL url) {
-        return toServicePath(url) + PATH_SEPARATOR + url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
+        return toServicePath(url) + PATH_SEPARATOR + url.getCategory(DEFAULT_CATEGORY);
     }
 
     private String toUrlPath(URL url) {
         return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
     }
 
-    private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
-        List<URL> urls = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(providers)) {
-            for (String provider : providers) {
-                if (provider.contains(PROTOCOL_SEPARATOR_ENCODED)) {
-                    URL url = URLStrParser.parseEncodedStr(provider);
-                    if (UrlUtils.isMatch(consumer, url)) {
-                        urls.add(url);
-                    }
-                }
-            }
-        }
-        return urls;
-    }
-
-    private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
-        List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
-        if (urls == null || urls.isEmpty()) {
-            int i = path.lastIndexOf(PATH_SEPARATOR);
-            String category = i < 0 ? path : path.substring(i + 1);
-            URL empty = URLBuilder.from(consumer)
-                    .setProtocol(EMPTY_PROTOCOL)
-                    .addParameter(CATEGORY_KEY, category)
-                    .build();
-            urls.add(empty);
-        }
-        return urls;
-    }
-
     /**
      * When zookeeper connection recovered from a connection loss, it need to fetch the latest provider list.
      * re-register watcher is only a side effect and is not mandate.
@@ -312,4 +281,53 @@ public class ZookeeperRegistry extends FailbackRegistry {
         }
     }
 
+    @Override
+    protected boolean isMatch(URL subscribeUrl, URL providerUrl) {
+        return UrlUtils.isMatch(subscribeUrl, providerUrl);
+    }
+
+    private class RegistryChildListenerImpl implements ChildListener {
+        private RegistryNotifier notifier;
+        private long lastExecuteTime;
+        private CountDownLatch latch;
+
+        public RegistryChildListenerImpl(URL consumerUrl, String path, NotifyListener listener, CountDownLatch latch) {
+            this.latch = latch;
+            notifier = new RegistryNotifier(ZookeeperRegistry.this) {
+                @Override
+                public void notify(Object rawAddresses) {
+                    int delayTime = getRegistry().getDelay();
+                    if (delayTime <= 0) {
+                        this.doNotify(rawAddresses);
+                    } else {
+                        long interval = delayTime - (System.currentTimeMillis() - lastExecuteTime);
+                        if (interval > 0) {
+                            try {
+                                Thread.sleep(interval);
+                            } catch (InterruptedException e) {
+                                // ignore
+                            }
+                        }
+                        lastExecuteTime = System.currentTimeMillis();
+                        this.doNotify(rawAddresses);
+                    }
+                }
+
+                @Override
+                protected void doNotify(Object rawAddresses) {
+                    ZookeeperRegistry.this.notify(consumerUrl, listener, ZookeeperRegistry.this.toUrlsWithEmpty(consumerUrl, path, (List<String>) rawAddresses));
+                }
+            };
+        }
+
+        @Override
+        public void childChanged(String path, List<String> children) {
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                logger.warn("Zookeeper children listener thread was interrupted unexpectedly, may cause race condition with the main thread.");
+            }
+            notifier.notify(children);
+        }
+    }
 }
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 d5f4f3d..80b7d2a 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
@@ -24,7 +24,6 @@ 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;
@@ -52,7 +51,7 @@ import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.bui
  * 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 extends AbstractServiceDiscovery {
+public class ZookeeperServiceDiscovery implements ServiceDiscovery {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -66,6 +65,8 @@ public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
 
     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}
      */
@@ -89,6 +90,11 @@ public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
         serviceDiscovery.close();
     }
 
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+
     public void register(ServiceInstance serviceInstance) throws RuntimeException {
         this.serviceInstance = serviceInstance;
         doInServiceRegistry(serviceDiscovery -> {
@@ -129,25 +135,30 @@ public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
 
             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);
@@ -180,6 +191,17 @@ public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
 
     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/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-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java
index 9fd9652..0711590 100755
--- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java
+++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java
@@ -51,21 +51,23 @@ public class TomcatHttpServer extends AbstractHttpServer {
         String baseDir = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
         tomcat = new Tomcat();
 
-        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
+        Connector connector = tomcat.getConnector();
         connector.setPort(url.getPort());
         connector.setProperty("maxThreads", String.valueOf(url.getParameter(THREADS_KEY, DEFAULT_THREADS)));
         connector.setProperty("maxConnections", String.valueOf(url.getParameter(ACCEPTS_KEY, -1)));
         connector.setProperty("URIEncoding", "UTF-8");
         connector.setProperty("connectionTimeout", "60000");
         connector.setProperty("maxKeepAliveRequests", "-1");
-        tomcat.setConnector(connector);
 
         tomcat.setBaseDir(baseDir);
         tomcat.setPort(url.getPort());
 
         Context context = tomcat.addContext("/", baseDir);
         Tomcat.addServlet(context, "dispatcher", new DispatcherServlet());
-        context.addServletMapping("/*", "dispatcher");
+        // Issue : https://github.com/apache/dubbo/issues/6418
+        // addServletMapping method will be removed since Tomcat 9
+        // context.addServletMapping("/*", "dispatcher");
+        context.addServletMappingDecoded("/*", "dispatcher");
         ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext());
 
         // tell tomcat to fail on startup failures.
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 @@ public interface ZookeeperClient {
 
     String getContent(String path);
 
+    boolean checkExists(String path);
+
 }
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 @@ public abstract class AbstractZookeeperClient<TargetDataListener, TargetChildLis
 
     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-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/FilterChainBuilder.java
similarity index 60%
copy from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
copy to dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/FilterChainBuilder.java
index d5b3dbc..b8a7ac5 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Registry.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/FilterChainBuilder.java
@@ -1,36 +1,24 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.Node;
-import org.apache.dubbo.common.URL;
-
-/**
- * Registry. (SPI, Prototype, ThreadSafe)
- *
- * @see org.apache.dubbo.registry.RegistryFactory#getRegistry(URL)
- * @see org.apache.dubbo.registry.support.AbstractRegistry
- */
-public interface Registry extends Node, RegistryService {
-    default void reExportRegister(URL url) {
-        register(url);
-    }
-
-    default void reExportUnregister(URL url) {
-        unregister(url);
-    }
-}
\ No newline at end of file
+/*
+ * 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;
+
+import org.apache.dubbo.common.extension.SPI;
+
+@SPI("default")
+public interface FilterChainBuilder {
+    <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group);
+}
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcContext.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcContext.java
index 8077067..36ae6a8 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcContext.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcContext.java
@@ -35,11 +35,8 @@ import java.util.concurrent.Future;
 
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 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.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.rpc.Constants.ASYNC_KEY;
 import static org.apache.dubbo.rpc.Constants.RETURN_KEY;
 
@@ -237,7 +234,7 @@ public class RpcContext {
      * @return consumer side.
      */
     public boolean isConsumerSide() {
-        return getUrl().getParameter(SIDE_KEY, PROVIDER_SIDE).equals(CONSUMER_SIDE);
+        return getUrl().getSide(PROVIDER_SIDE).equals(CONSUMER_SIDE);
     }
 
     /**
@@ -806,14 +803,14 @@ public class RpcContext {
         if (consumerUrl == null) {
             return null;
         }
-        return consumerUrl.getParameter(GROUP_KEY);
+        return consumerUrl.getGroup();
     }
 
     public String getVersion() {
         if (consumerUrl == null) {
             return null;
         }
-        return consumerUrl.getParameter(VERSION_KEY);
+        return consumerUrl.getVersion();
     }
 
     public String getInterfaceName() {
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcInvocation.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcInvocation.java
index be830d7..ba6f9b6 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcInvocation.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/RpcInvocation.java
@@ -94,10 +94,10 @@ public class RpcInvocation implements Invocation, Serializable {
                 setAttachment(INTERFACE_KEY, url.getParameter(INTERFACE_KEY));
             }
             if (url.hasParameter(GROUP_KEY)) {
-                setAttachment(GROUP_KEY, url.getParameter(GROUP_KEY));
+                setAttachment(GROUP_KEY, url.getGroup());
             }
             if (url.hasParameter(VERSION_KEY)) {
-                setAttachment(VERSION_KEY, url.getParameter(VERSION_KEY, "0.0.0"));
+                setAttachment(VERSION_KEY, url.getVersion("0.0.0"));
             }
             if (url.hasParameter(TIMEOUT_KEY)) {
                 setAttachment(TIMEOUT_KEY, url.getParameter(TIMEOUT_KEY));
@@ -106,7 +106,7 @@ public class RpcInvocation implements Invocation, Serializable {
                 setAttachment(TOKEN_KEY, url.getParameter(TOKEN_KEY));
             }
             if (url.hasParameter(APPLICATION_KEY)) {
-                setAttachment(APPLICATION_KEY, url.getParameter(APPLICATION_KEY));
+                setAttachment(APPLICATION_KEY, url.getApplication());
             }
         }
         this.targetServiceUniqueName = invocation.getTargetServiceUniqueName();
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..1cf09fb 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.Executors;
 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;
 
 /**
@@ -170,8 +168,8 @@ public class AccessLogFilter implements Filter {
         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.setVersion(invoker.getUrl().getVersion());
+        logData.setGroup(invoker.getUrl().getGroup());
         logData.setInvocationTime(new Date());
         logData.setTypes(inv.getParameterTypes());
         logData.setArguments(inv.getArguments());
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ConsumerContextFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ConsumerContextFilter.java
index 5aad186..ad14831 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ConsumerContextFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ConsumerContextFilter.java
@@ -28,7 +28,6 @@ import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.RpcInvocation;
 import org.apache.dubbo.rpc.TimeoutCountDown;
 
-import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIME_COUNTDOWN_KEY;
@@ -46,12 +45,9 @@ public class ConsumerContextFilter implements Filter {
     @Override
     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
         RpcContext context = RpcContext.getContext();
-        context.setInvoker(invoker)
-                .setInvocation(invocation)
+        context.setInvocation(invocation)
                 .setLocalAddress(NetUtils.getLocalHost(), 0)
-                .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort())
-                .setRemoteApplicationName(invoker.getUrl().getParameter(REMOTE_APPLICATION_KEY))
-                .setAttachment(REMOTE_APPLICATION_KEY, invoker.getUrl().getParameter(APPLICATION_KEY));
+                .setAttachment(REMOTE_APPLICATION_KEY, invoker.getUrl().getApplication());
         if (invocation instanceof RpcInvocation) {
             ((RpcInvocation) invocation).setInvoker(invoker);
         }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
index 94c7b79..f8a5ac9 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
@@ -42,7 +42,6 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This Invoker works on Consumer side.
@@ -59,7 +58,7 @@ public abstract class AbstractInvoker<T> implements Invoker<T> {
 
     private volatile boolean available = true;
 
-    private AtomicBoolean destroyed = new AtomicBoolean(false);
+    private boolean destroyed = false;
 
     public AbstractInvoker(Class<T> type, URL url) {
         this(type, url, (Map<String, Object>) null);
@@ -116,14 +115,12 @@ public abstract class AbstractInvoker<T> implements Invoker<T> {
 
     @Override
     public void destroy() {
-        if (!destroyed.compareAndSet(false, true)) {
-            return;
-        }
+        this.destroyed = true;
         setAvailable(false);
     }
 
     public boolean isDestroyed() {
-        return destroyed.get();
+        return destroyed;
     }
 
     @Override
@@ -134,7 +131,7 @@ public abstract class AbstractInvoker<T> implements Invoker<T> {
     @Override
     public Result invoke(Invocation inv) throws RpcException {
         // if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
-        if (destroyed.get()) {
+        if (isDestroyed()) {
             logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
                     + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
         }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
index d27fcbb..903cd27 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java
@@ -36,9 +36,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
-
 /**
  * abstract ProtocolSupport.
  */
@@ -58,7 +55,7 @@ public abstract class AbstractProtocol implements Protocol {
 
     protected static String serviceKey(URL url) {
         int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
-        return serviceKey(port, url.getPath(), url.getParameter(VERSION_KEY), url.getParameter(GROUP_KEY));
+        return serviceKey(port, url.getPath(), url.getVersion(), url.getGroup());
     }
 
     protected static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) {
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..24fcc8a 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
@@ -22,18 +22,14 @@ import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 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.FilterChainBuilder;
 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;
 
-import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_FILTER_KEY;
 
 /**
@@ -43,6 +39,8 @@ import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_FILTER_K
 public class ProtocolFilterWrapper implements Protocol {
 
     private final Protocol protocol;
+    private static final FilterChainBuilder builder
+            = ExtensionLoader.getExtensionLoader(FilterChainBuilder.class).getDefaultExtension();
 
     public ProtocolFilterWrapper(Protocol protocol) {
         if (protocol == null) {
@@ -51,97 +49,6 @@ public class ProtocolFilterWrapper implements Protocol {
         this.protocol = protocol;
     }
 
-    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
-        Invoker<T> last = invoker;
-        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();
-                    }
-                };
-            }
-        }
-
-        return last;
-    }
-
     @Override
     public int getDefaultPort() {
         return protocol.getDefaultPort();
@@ -152,7 +59,7 @@ public class ProtocolFilterWrapper implements Protocol {
         if (UrlUtils.isRegistry(invoker.getUrl())) {
             return protocol.export(invoker);
         }
-        return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
+        return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
     }
 
     @Override
@@ -160,7 +67,7 @@ public class ProtocolFilterWrapper implements Protocol {
         if (UrlUtils.isRegistry(url)) {
             return protocol.refer(type, url);
         }
-        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
+        return protocol.refer(type, url);
     }
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
index af943c3..351024a 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
@@ -1,89 +1,96 @@
-/*
- * 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.common.extension.Activate;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.utils.UrlUtils;
-import org.apache.dubbo.rpc.Exporter;
-import org.apache.dubbo.rpc.ExporterListener;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.InvokerListener;
-import org.apache.dubbo.rpc.Protocol;
-import org.apache.dubbo.rpc.ProtocolServer;
-import org.apache.dubbo.rpc.RpcException;
-import org.apache.dubbo.rpc.listener.ListenerExporterWrapper;
-import org.apache.dubbo.rpc.listener.ListenerInvokerWrapper;
-
-import java.util.Collections;
-import java.util.List;
-
-import static org.apache.dubbo.common.constants.CommonConstants.EXPORTER_LISTENER_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.INVOKER_LISTENER_KEY;
-
-/**
- * ListenerProtocol
- */
-@Activate(order = 200)
-public class ProtocolListenerWrapper implements Protocol {
-
-    private final Protocol protocol;
-
-    public ProtocolListenerWrapper(Protocol protocol) {
-        if (protocol == null) {
-            throw new IllegalArgumentException("protocol == null");
-        }
-        this.protocol = protocol;
-    }
-
-    @Override
-    public int getDefaultPort() {
-        return protocol.getDefaultPort();
-    }
-
-    @Override
-    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
-        if (UrlUtils.isRegistry(invoker.getUrl())) {
-            return protocol.export(invoker);
-        }
-        return new ListenerExporterWrapper<T>(protocol.export(invoker),
-                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
-                        .getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
-    }
-
-    @Override
-    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
-        if (UrlUtils.isRegistry(url)) {
-            return protocol.refer(type, url);
-        }
-        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
-                Collections.unmodifiableList(
-                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
-                                .getActivateExtension(url, INVOKER_LISTENER_KEY)));
-    }
-
-    @Override
-    public void destroy() {
-        protocol.destroy();
-    }
-
-    @Override
-    public List<ProtocolServer> getServers() {
-        return protocol.getServers();
-    }
-}
+/*
+ * 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.common.extension.Activate;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.common.utils.UrlUtils;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.ExporterListener;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.InvokerListener;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProtocolServer;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.listener.ListenerExporterWrapper;
+import org.apache.dubbo.rpc.listener.ListenerInvokerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.apache.dubbo.common.constants.CommonConstants.EXPORTER_LISTENER_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.FILTER_BUILDER_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.INVOKER_LISTENER_KEY;
+
+/**
+ * ListenerProtocol
+ */
+@Activate(order = 200)
+public class ProtocolListenerWrapper implements Protocol {
+
+    private final Protocol protocol;
+
+    public ProtocolListenerWrapper(Protocol protocol) {
+        if (protocol == null) {
+            throw new IllegalArgumentException("protocol == null");
+        }
+        this.protocol = protocol;
+    }
+
+    @Override
+    public int getDefaultPort() {
+        return protocol.getDefaultPort();
+    }
+
+    @Override
+    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
+        if (UrlUtils.isRegistry(invoker.getUrl())) {
+            return protocol.export(invoker);
+        }
+        return new ListenerExporterWrapper<T>(protocol.export(invoker),
+                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
+                        .getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
+    }
+
+    @Override
+    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
+        if (UrlUtils.isRegistry(url)) {
+            return protocol.refer(type, url);
+        }
+
+        Invoker<T> invoker = protocol.refer(type, url);
+        if (StringUtils.isEmpty(url.getParameter(FILTER_BUILDER_KEY))) {
+            invoker = new ListenerInvokerWrapper<>(invoker,
+                    Collections.unmodifiableList(
+                            ExtensionLoader.getExtensionLoader(InvokerListener.class)
+                                    .getActivateExtension(url, INVOKER_LISTENER_KEY)));
+        }
+        return invoker;
+    }
+
+    @Override
+    public void destroy() {
+        protocol.destroy();
+    }
+
+    @Override
+    public List<ProtocolServer> getServers() {
+        return protocol.getServers();
+    }
+}
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/support/DefaultFilterChainBuilder.java
similarity index 72%
copy from dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
copy to dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/DefaultFilterChainBuilder.java
index 74ac50c..24b350b 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/support/DefaultFilterChainBuilder.java
@@ -1,176 +1,125 @@
-/*
- * 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.common.constants.CommonConstants;
-import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-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;
-
-import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_FILTER_KEY;
-
-/**
- * ListenerProtocol
- */
-@Activate(order = 100)
-public class ProtocolFilterWrapper implements Protocol {
-
-    private final Protocol protocol;
-
-    public ProtocolFilterWrapper(Protocol protocol) {
-        if (protocol == null) {
-            throw new IllegalArgumentException("protocol == null");
-        }
-        this.protocol = protocol;
-    }
-
-    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
-        Invoker<T> last = invoker;
-        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();
-                    }
-                };
-            }
-        }
-
-        return last;
-    }
-
-    @Override
-    public int getDefaultPort() {
-        return protocol.getDefaultPort();
-    }
-
-    @Override
-    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
-        if (UrlUtils.isRegistry(invoker.getUrl())) {
-            return protocol.export(invoker);
-        }
-        return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
-    }
-
-    @Override
-    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
-        if (UrlUtils.isRegistry(url)) {
-            return protocol.refer(type, url);
-        }
-        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
-    }
-
-    @Override
-    public void destroy() {
-        protocol.destroy();
-    }
-
-    @Override
-    public List<ProtocolServer> getServers() {
-        return protocol.getServers();
-    }
-
-}
+/*
+ * 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.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.rpc.Filter;
+import org.apache.dubbo.rpc.FilterChainBuilder;
+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;
+
+import java.util.List;
+
+public class DefaultFilterChainBuilder implements FilterChainBuilder {
+
+    @Override
+    public <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
+        Invoker<T> last = invoker;
+        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();
+                    }
+                };
+            }
+        }
+
+        return last;
+    }
+
+}
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.FilterChainBuilder b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.FilterChainBuilder
new file mode 100644
index 0000000..b3a9854
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.FilterChainBuilder
@@ -0,0 +1 @@
+default=org.apache.dubbo.rpc.support.DefaultFilterChainBuilder
\ No newline at end of file
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 3b87068..607f291 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
@@ -99,7 +99,7 @@ class CallbackServiceCodec {
         params.put(IS_SERVER_KEY, Boolean.FALSE.toString());
         // mark it's a callback, for troubleshooting
         params.put(IS_CALLBACK_SERVICE, Boolean.TRUE.toString());
-        String group = (url == null ? null : url.getParameter(GROUP_KEY));
+        String group = (url == null ? null : url.getGroup());
         if (group != null && group.length() > 0) {
             params.put(GROUP_KEY, group);
         }
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
index 4fc52f7..d0e7f20 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
@@ -76,7 +76,7 @@ public class DubboInvoker<T> extends AbstractInvoker<T> {
         super(serviceType, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY});
         this.clients = clients;
         // get version.
-        this.version = url.getParameter(VERSION_KEY, "0.0.0");
+        this.version = url.getVersion("0.0.0");
         this.invokers = invokers;
     }
 
@@ -137,9 +137,7 @@ public class DubboInvoker<T> extends AbstractInvoker<T> {
         // in order to avoid closing a client multiple times, a counter is used in case of connection per jvm, every
         // time when client.close() is called, counter counts down once, and when counter reaches zero, client will be
         // closed.
-        if (super.isDestroyed()) {
-            return;
-        } else {
+        if (!super.isDestroyed()) {
             // double check to avoid dup close
             destroyLock.lock();
             try {
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 791df67..0c9204a 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
@@ -197,9 +197,9 @@ public class DubboProtocol extends AbstractProtocol {
 
             RpcInvocation invocation = new RpcInvocation(method, url.getParameter(INTERFACE_KEY), "", new Class<?>[0], new Object[0]);
             invocation.setAttachment(PATH_KEY, url.getPath());
-            invocation.setAttachment(GROUP_KEY, url.getParameter(GROUP_KEY));
+            invocation.setAttachment(GROUP_KEY, url.getGroup());
             invocation.setAttachment(INTERFACE_KEY, url.getParameter(INTERFACE_KEY));
-            invocation.setAttachment(VERSION_KEY, url.getParameter(VERSION_KEY));
+            invocation.setAttachment(VERSION_KEY, url.getVersion());
             if (url.getParameter(STUB_EVENT_KEY, false)) {
                 invocation.setAttachment(STUB_EVENT_KEY, Boolean.TRUE.toString());
             }