Merge branch 'apache-3.0' into apache-3.1

# Conflicts:
#	dubbo-dependencies-bom/pom.xml
#	dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
#	dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
#	pom.xml
diff --git a/.codecov.yml b/.codecov.yml
index 4b49c86..0b498ec 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,3 +1,18 @@
+# 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.
+#
 coverage:
   status:
     # pull-requests only
@@ -8,4 +23,4 @@
   - "dubbo-demo/.*"
   - "dubbo-common/src/main/java/org/apache/dubbo/common/json/*.java" #  internal JSON impl is deprecate, ignore test coverage for them
   - "dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java" # Deprecated
-  - "dubbo-rpc/dubbo-rpc-thrift/.*"
\ No newline at end of file
+  - "dubbo-rpc/dubbo-rpc-thrift/.*"
diff --git a/.github/workflows/build-and-test-3.yml b/.github/workflows/build-and-test-3.1.yml
similarity index 80%
rename from .github/workflows/build-and-test-3.yml
rename to .github/workflows/build-and-test-3.1.yml
index 3a9b654..f8c4370 100644
--- a/.github/workflows/build-and-test-3.yml
+++ b/.github/workflows/build-and-test-3.1.yml
@@ -1,7 +1,10 @@
-name: Build and Test For Dubbo 3
+name: Build and Test For Dubbo 3.1
 
 on: [push, pull_request, workflow_dispatch]
 
+permissions:
+  contents: read
+
 env:
   FORK_COUNT: 2
   FAIL_FAST: 0
@@ -15,8 +18,16 @@
     '
 
 jobs:
+  license:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: Check License
+        uses: apache/skywalking-eyes@main
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
   build-source:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     outputs:
       version: ${{ steps.dubbo-version.outputs.version }}
     steps:
@@ -71,7 +82,7 @@
     strategy:
       fail-fast: false
       matrix:
-        os: [ ubuntu-18.04, windows-2019 ]
+        os: [ ubuntu-20.04, windows-2019 ]
         jdk: [ 8, 11, 17 ]
     env:
       DISABLE_FILE_SYSTEM_TEST: true
@@ -123,7 +134,7 @@
     strategy:
       fail-fast: false
       matrix:
-        os: [ ubuntu-18.04, windows-2019 ]
+        os: [ ubuntu-20.04, windows-2019 ]
         jdk: [ 8, 11, 17 ]
     env:
       DISABLE_FILE_SYSTEM_TEST: true
@@ -151,8 +162,45 @@
       - name: "Upload coverage to Codecov"
         uses: codecov/codecov-action@v1
 
+  unit-test-fastjson2:
+    needs: [build-source, unit-test-prepare]
+    name: "Unit Test On ${{ matrix.os }} (JDK: ${{ matrix.jdk }}, Serialization: fastjson2)"
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ ubuntu-20.04, windows-2019 ]
+        jdk: [ 8, 11, 17 ]
+    env:
+      DISABLE_FILE_SYSTEM_TEST: true
+      DUBBO_DEFAULT_SERIALIZATION: fastjson2
+      MAVEN_SUREFIRE_ADD_OPENS: true
+    steps:
+      - uses: actions/checkout@v2
+      - name: "Set up JDK ${{ matrix.jdk }}"
+        uses: actions/setup-java@v1
+        with:
+          java-version: ${{ matrix.jdk }}
+      - uses: actions/cache@v2
+        name: "Cache local Maven repository"
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: "Test with Maven with Integration Tests"
+        timeout-minutes: 70
+        if: ${{ startsWith( matrix.os, 'ubuntu') }}
+        run: ./mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast clean test verify -Pjacoco -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.count=5 -DskipTests=false -DskipIntegrationTests=false -Dcheckstyle.skip=false -Dcheckstyle_unix.skip=false -Drat.skip=false -Dmaven.javadoc.skip=true -DembeddedZookeeperPath=${{ github.workspace }}/.tmp/zookeeper
+      - name: "Test with Maven without Integration Tests"
+        timeout-minutes: 90
+        if: ${{ startsWith( matrix.os, 'windows') }}
+        run: ./mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast clean test verify -Pjacoco -D"http.keepAlive=false" -D"maven.wagon.http.pool=false" -D"maven.wagon.httpconnectionManager.ttlSeconds=120" -D"maven.wagon.http.retryHandler.count=5" -DskipTests=false -DskipIntegrationTests=true -D"checkstyle.skip=false" -D"checkstyle_unix.skip=true" -D"rat.skip=false" -D"maven.javadoc.skip=true" -D"embeddedZookeeperPath=${{ github.workspace }}/.tmp/zookeeper"
+      - name: "Upload coverage to Codecov"
+        uses: codecov/codecov-action@v1
+
   integration-test-prepare:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     env:
       JOB_COUNT: 3
     steps:
@@ -171,8 +219,8 @@
 
   integration-test-job:
     needs: [build-source, integration-test-prepare]
-    name: "Integration Test on ubuntu-18.04 (JobId: ${{matrix.job_id}})"
-    runs-on: ubuntu-18.04
+    name: "Integration Test on ubuntu-20.04 (JobId: ${{matrix.job_id}})"
+    runs-on: ubuntu-20.04
     timeout-minutes: 30
     env:
       JAVA_VER: 8
@@ -227,7 +275,7 @@
   integration-test-result:
     needs: [integration-test-job]
     if: always()
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     env:
       JAVA_VER: 8
     steps:
diff --git a/.licenserc.yaml b/.licenserc.yaml
new file mode 100644
index 0000000..00b7286
--- /dev/null
+++ b/.licenserc.yaml
@@ -0,0 +1,82 @@
+header:
+  license:
+    spdx-id: Apache-2.0
+    content: |
+      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.
+
+  paths-ignore:
+    - '**/*.versionsBackup'
+    - '**/.idea/'
+    - '**/*.iml'
+    - '**/.settings/*'
+    - '**/.classpath'
+    - '**/.project'
+    - '**/target/**'
+    - '**/generated/**'
+    - '**/*.log'
+    - '**/codestyle/*'
+    - '**/resources/META-INF/**'
+    - '**/resources/mockito-extensions/**'
+    - '**/*.proto'
+    - '**/*.cache'
+    - '**/*.txt'
+    - '**/*.load'
+    - '**/*.flex'
+    - '**/*.fc'
+    - '**/*.javascript'
+    - '**/*.properties'
+    - '**/*.thrift'
+    - '**/*.sh'
+    - '**/*.bat'
+    - '**/*.md'
+    - '**/*.svg'
+    - '**/*.png'
+    - '**/*.json'
+    - '**/*.conf'
+    - '**/*.ftl'
+    - '**/*.tpl'
+    - '**/*.factories'
+    - '**/*.handlers'
+    - '**/*.schemas'
+    - '**/*.nojekyll'
+    - '.git/'
+    - '.github/**'
+    - '**/.gitignore'
+    - '**/.helmignore'
+    - '.repository/'
+    - 'compiler/**'
+    - '.gitmodules'
+    - '.mvn'
+    - 'mvnw'
+    - 'mvnw.cmd'
+    - 'LICENSE'
+    - 'NOTICE'
+    - 'CNAME'
+    - 'Jenkinsfile'
+    - '**/vendor/**'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocal.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocalMap.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timeout.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timer.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/TimerTask.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/utils/CIDRUtils.java'
+    - 'dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java'
+    - 'dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java'
+
+  comment: on-failure
+
+  license-location-threshold: 130
diff --git a/.travis.yml b/.travis.yml
index 6b59c76..a7b6c63 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,18 @@
+# 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.
+#
 sudo: false # faster builds
 os: linux
 dist: focal
diff --git a/dubbo-cluster/pom.xml b/dubbo-cluster/pom.xml
index c20fb74..f7235cf 100644
--- a/dubbo-cluster/pom.xml
+++ b/dubbo-cluster/pom.xml
@@ -58,6 +58,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-test-check</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java
index f3b4f5d..64246b9 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.Holder;
@@ -43,13 +43,15 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_STOP;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_NO_VALID_PROVIDER;
 import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY;
 
 /**
  * Router chain
  */
 public class RouterChain<T> {
-    private static final Logger logger = LoggerFactory.getLogger(RouterChain.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RouterChain.class);
 
     /**
      * full list of addresses from registry, classified by method name.
@@ -241,6 +243,7 @@
         RouterSnapshotNode<T> commonRouterNode = new RouterSnapshotNode<T>("CommonRouter", resultInvokers.clone());
         parentNode.appendNode(commonRouterNode);
         List<Invoker<T>> commonRouterResult = resultInvokers;
+
         // 2. route common router
         for (Router router : routers) {
             // Copy resultInvokers to a arrayList. BitList not support
@@ -296,7 +299,7 @@
                 if (routerSnapshotSwitcher.isEnable()) {
                     routerSnapshotSwitcher.setSnapshot(message);
                 }
-                logger.warn(message);
+                logger.warn(CLUSTER_NO_VALID_PROVIDER,"No provider available after route for the service","",message);
             }
         } else {
             if (logger.isInfoEnabled()) {
@@ -344,7 +347,7 @@
             try {
                 router.stop();
             } catch (Exception e) {
-                logger.error("Error trying to stop router " + router.getClass(), e);
+                logger.error(CLUSTER_FAILED_STOP,"route stop failed","","Error trying to stop router " + router.getClass(),e);
             }
         }
         routers = Collections.emptyList();
@@ -354,7 +357,7 @@
             try {
                 router.stop();
             } catch (Exception e) {
-                logger.error("Error trying to stop stateRouter " + router.getClass(), e);
+                logger.error(CLUSTER_FAILED_STOP,"StateRouter stop failed","","Error trying to stop StateRouter " + router.getClass(),e);
             }
         }
         stateRouters = Collections.emptyList();
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 c190fa5..11e7e82 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.config.Configuration;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
@@ -61,6 +61,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.RECONNECT_TASK_PERIOD;
 import static org.apache.dubbo.common.constants.CommonConstants.RECONNECT_TASK_TRY_COUNT;
 import static org.apache.dubbo.common.constants.CommonConstants.REGISTER_IP_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_NO_VALID_PROVIDER;
 import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
 import static org.apache.dubbo.rpc.cluster.Constants.CONSUMER_URL_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
@@ -71,7 +72,7 @@
 public abstract class AbstractDirectory<T> implements Directory<T> {
 
     // logger
-    private static final Logger logger = LoggerFactory.getLogger(AbstractDirectory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractDirectory.class);
 
     private final URL url;
 
@@ -193,7 +194,10 @@
 
         List<Invoker<T>> routedResult = doList(availableInvokers, invocation);
         if (routedResult.isEmpty()) {
-            logger.warn("No provider available after connectivity filter for the service " + getConsumerUrl().getServiceKey()
+            // 2-2 - No provider available.
+
+            logger.warn(CLUSTER_NO_VALID_PROVIDER, "provider server or registry center crashed", "",
+                "No provider available after connectivity filter for the service " + getConsumerUrl().getServiceKey()
                 + " All validInvokers' size: " + validInvokers.size()
                 + " All routed invokers' size: " + routedResult.size()
                 + " All invokers' size: " + invokers.size()
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
index 58a2462..106e997 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/StaticDirectory.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.directory;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.rpc.Invocation;
@@ -28,11 +28,13 @@
 
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_SITE_SELECTION;
+
 /**
  * StaticDirectory
  */
 public class StaticDirectory<T> extends AbstractDirectory<T> {
-    private static final Logger logger = LoggerFactory.getLogger(StaticDirectory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(StaticDirectory.class);
 
     public StaticDirectory(List<Invoker<T>> invokers) {
         this(null, invokers, null);
@@ -108,7 +110,7 @@
                 List<Invoker<T>> finalInvokers = routerChain.route(getConsumerUrl(), invokers, invocation);
                 return finalInvokers == null ? BitList.emptyList() : finalInvokers;
             } catch (Throwable t) {
-                logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
+                logger.error(CLUSTER_FAILED_SITE_SELECTION,"Failed to execute router","","Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(),t);
                 return BitList.emptyList();
             }
         }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/DefaultFilterChainBuilder.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/DefaultFilterChainBuilder.java
index 15be300..22a22cd 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/DefaultFilterChainBuilder.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/DefaultFilterChainBuilder.java
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.extension.ExtensionDirector;
-import org.apache.dubbo.common.extension.support.MultiInstanceActivateComparator;
+import org.apache.dubbo.common.extension.support.ActivateComparator;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.rpc.Filter;
 import org.apache.dubbo.rpc.Invoker;
@@ -113,7 +113,7 @@
     }
 
     private <T> List<T> sortingAndDeduplication(List<T> filters, List<ExtensionDirector> directors) {
-        Map<Class<?>, T> filtersSet = new TreeMap<>(new MultiInstanceActivateComparator(directors));
+        Map<Class<?>, T> filtersSet = new TreeMap<>(new ActivateComparator(directors));
         for (T filter : filters) {
             filtersSet.putIfAbsent(filter.getClass(), filter);
         }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalance.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalance.java
index 74c54ce..f8c2089 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalance.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalance.java
@@ -43,8 +43,9 @@
 
     /**
      * Select one invoker between a list using a random criteria
-     * @param invokers List of possible invokers
-     * @param url URL
+     *
+     * @param invokers   List of possible invokers
+     * @param url        URL
      * @param invocation Invocation
      * @param <T>
      * @return The selected invoker
@@ -54,7 +55,7 @@
         // Number of invokers
         int length = invokers.size();
 
-        if (!needWeightLoadBalance(invokers,invocation)){
+        if (!needWeightLoadBalance(invokers, invocation)) {
             return invokers.get(ThreadLocalRandom.current().nextInt(length));
         }
 
@@ -89,8 +90,7 @@
     }
 
     private <T> boolean needWeightLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
-
-        Invoker invoker = invokers.get(0);
+        Invoker<T> invoker = invokers.get(0);
         URL invokerUrl = invoker.getUrl();
         if (invoker instanceof ClusterInvoker) {
             invokerUrl = ((ClusterInvoker<?>) invoker).getRegistryUrl();
@@ -99,22 +99,15 @@
         // Multiple registry scenario, load balance among multiple registries.
         if (REGISTRY_SERVICE_REFERENCE_PATH.equals(invokerUrl.getServiceInterface())) {
             String weight = invokerUrl.getParameter(WEIGHT_KEY);
-            if (StringUtils.isNotEmpty(weight)) {
-                return true;
-            }
+            return StringUtils.isNotEmpty(weight);
         } else {
             String weight = invokerUrl.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY);
             if (StringUtils.isNotEmpty(weight)) {
                 return true;
-            }else {
+            } else {
                 String timeStamp = invoker.getUrl().getParameter(TIMESTAMP_KEY);
-                if (StringUtils.isNotEmpty(timeStamp)) {
-                    return true;
-                }
+                return StringUtils.isNotEmpty(timeStamp);
             }
         }
-        return false;
     }
-
-
 }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/merger/MergerFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/merger/MergerFactory.java
index 09a9cfc..038e38f 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/merger/MergerFactory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/merger/MergerFactory.java
@@ -17,7 +17,7 @@
 
 package org.apache.dubbo.rpc.cluster.merger;
 
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.TypeUtils;
@@ -31,9 +31,11 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_LOAD_MERGER;
+
 public class MergerFactory implements ScopeModelAware {
 
-    private static final Logger logger = LoggerFactory.getLogger(MergerFactory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MergerFactory.class);
 
     private ConcurrentMap<Class<?>, Merger<?>> MERGER_CACHE = new ConcurrentHashMap<Class<?>, Merger<?>>();
     private ScopeModel scopeModel;
@@ -73,7 +75,7 @@
             Merger m = scopeModel.getExtensionLoader(Merger.class).getExtension(name);
             Class<?> actualTypeArg = getActualTypeArgument(m.getClass());
             if (actualTypeArg == null) {
-                logger.warn("Failed to get actual type argument from merger " + m.getClass().getName());
+                logger.warn(CLUSTER_FAILED_LOAD_MERGER,"load merger config failed","","Failed to get actual type argument from merger " + m.getClass().getName());
                 continue;
             }
             MERGER_CACHE.putIfAbsent(actualTypeArg, m);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
index 92cee81..be4774e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.router.condition;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.Holder;
@@ -45,6 +45,8 @@
 import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
 import static org.apache.dubbo.rpc.cluster.Constants.ADDRESS_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
@@ -54,14 +56,14 @@
  * ConditionRouter
  * It supports the conditional routing configured by "override://", in 2.6.x,
  * refer to https://dubbo.apache.org/en/docs/v2.7/user/examples/routing-rule/ .
- * For 2.7.x and later, please refer to {@link org.apache.dubbo.rpc.cluster.router.condition.config.ServiceRouter}
+ * For 2.7.x and later, please refer to {@link org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouter}
  * and {@link AppStateRouter}
  * refer to https://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/ .
  */
 public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
     public static final String NAME = "condition";
 
-    private static final Logger logger = LoggerFactory.getLogger(ConditionStateRouter.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractStateRouter.class);
     protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
     protected static Pattern ARGUMENTS_PATTERN = Pattern.compile("arguments\\[([0-9]+)\\]");
     protected Map<String, MatchPair> whenCondition;
@@ -202,8 +204,7 @@
                 return invokers;
             }
             if (thenCondition == null) {
-                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
-                if (needToPrintMessage) {
+                logger.warn(CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY,"condition state router thenCondition is empty","","The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());                if (needToPrintMessage) {
                     messageHolder.set("Empty return. Reason: ThenCondition is empty.");
                 }
                 return BitList.emptyList();
@@ -217,15 +218,14 @@
                 }
                 return result;
             } else if (this.isForce()) {
-                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
-
+                logger.warn(CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY,"execute condition state router result list is empty. and force=true","","The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
                 if (needToPrintMessage) {
                     messageHolder.set("Empty return. Reason: Empty result from condition and condition is force.");
                 }
                 return result;
             }
         } catch (Throwable t) {
-            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
+            logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,"execute condition state router exception","","Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(),t);
         }
         if (needToPrintMessage) {
             messageHolder.set("Directly return. Reason: Error occurred ( or result is empty ).");
@@ -325,7 +325,7 @@
                 return true;
             }
         } catch (Exception e) {
-            logger.warn("Arguments match failed, matchPair[]" + matchPair + "] invocation[" + invocation + "]", e);
+            logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,"condition state router arguments match failed","","Arguments match failed, matchPair[]" + matchPair + "] invocation[" + invocation + "]",e);
         }
 
         return false;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
index c295a66..2348a3e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
@@ -21,7 +21,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.Holder;
@@ -41,6 +41,8 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RULE_PARSING;
+
 /**
  * Abstract router which listens to dynamic configuration
  */
@@ -48,7 +50,7 @@
     public static final String NAME = "LISTENABLE_ROUTER";
     private static final String RULE_SUFFIX = ".condition-router";
 
-    private static final Logger logger = LoggerFactory.getLogger(ListenableStateRouter.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
     private volatile ConditionRouterRule routerRule;
     private volatile List<ConditionStateRouter<T>> conditionRouters = Collections.emptyList();
     private String ruleKey;
@@ -75,8 +77,8 @@
                 routerRule = ConditionRuleParser.parse(event.getContent());
                 generateConditions(routerRule);
             } catch (Exception e) {
-                logger.error("Failed to parse the raw condition rule and it will not take effect, please check " +
-                        "if the condition rule matches with the template, the raw rule is:\n " + event.getContent(), e);
+                logger.error(CLUSTER_FAILED_RULE_PARSING,"Failed to parse the raw condition rule","","Failed to parse the raw condition rule and it will not take effect, please check " +
+                    "if the condition rule matches with the template, the raw rule is:\n " + event.getContent(),e);
             }
         }
     }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java
index ceb237e..b6332e4 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.rpc.cluster.router.mesh.util.MeshRuleDispatcher;
@@ -36,6 +36,7 @@
 import java.util.List;
 import java.util.Map;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RECEIVE_RULE;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.METADATA_KEY;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.NAME_KEY;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.STANDARD_ROUTER_KEY;
@@ -43,7 +44,7 @@
 
 public class MeshAppRuleListener implements ConfigurationListener {
 
-    public static final Logger logger = LoggerFactory.getLogger(MeshAppRuleListener.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MeshAppRuleListener.class);
 
     private final MeshRuleDispatcher meshRuleDispatcher;
 
@@ -78,17 +79,17 @@
                     if (ruleType != null) {
                         groupMap.computeIfAbsent(ruleType, (k)-> new LinkedList<>()).add(resultMap);
                     } else {
-                        logger.error("Unable to get rule type from raw rule. " +
+                        logger.error(CLUSTER_FAILED_RECEIVE_RULE,"receive mesh app route rule is invalid","","Unable to get rule type from raw rule. " +
                             "Probably the metadata.name is absent. App Name: " + appName + " RawRule: " + configInfo);
                     }
                 } else {
-                    logger.error("Rule format is unacceptable. App Name: " + appName + " RawRule: " + configInfo);
+                    logger.error(CLUSTER_FAILED_RECEIVE_RULE,"receive mesh app route rule is invalid","","Rule format is unacceptable. App Name: " + appName + " RawRule: " + configInfo);
                 }
             }
 
             ruleMapHolder = groupMap;
         } catch (Exception e) {
-            logger.error("[MeshAppRule] parse failed: " + configInfo, e);
+            logger.error(CLUSTER_FAILED_RECEIVE_RULE,"failed to receive mesh app route rule","","[MeshAppRule] parse failed: " + configInfo,e);
         }
         if (ruleMapHolder != null) {
             meshRuleDispatcher.post(ruleMapHolder);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java
index e7d46b5..ac49475 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java
@@ -18,7 +18,7 @@
 package org.apache.dubbo.rpc.cluster.router.mesh.route;
 
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
 import org.apache.dubbo.rpc.cluster.router.mesh.util.MeshRuleListener;
@@ -29,11 +29,12 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RECEIVE_RULE;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.MESH_RULE_DATA_ID_SUFFIX;
 
 public class MeshRuleManager {
 
-    public static final Logger logger = LoggerFactory.getLogger(MeshRuleManager.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MeshRuleManager.class);
 
     private final ConcurrentHashMap<String, MeshAppRuleListener> APP_RULE_LISTENERS = new ConcurrentHashMap<>();
 
@@ -63,7 +64,7 @@
                 meshAppRuleListener.receiveConfigInfo(rawConfig);
             }
         } catch (Throwable throwable) {
-            logger.error("get MeshRuleManager app rule failed.", throwable);
+            logger.error(CLUSTER_FAILED_RECEIVE_RULE,"failed to get mesh app route rule","","get MeshRuleManager app rule failed.",throwable);
         }
 
         ruleRepository.addListener(appRuleDataId, DynamicConfiguration.DEFAULT_GROUP, meshAppRuleListener);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java
index 105332e..0d3d8b5 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.router.mesh.route;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.Holder;
@@ -51,6 +51,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ThreadLocalRandom;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RECEIVE_RULE;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.DESTINATION_RULE_KEY;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.INVALID_APP_NAME;
 import static org.apache.dubbo.rpc.cluster.router.mesh.route.MeshRuleConstants.KIND_KEY;
@@ -58,7 +59,7 @@
 
 public abstract class MeshRuleRouter<T> extends AbstractStateRouter<T> implements MeshRuleListener {
 
-    public static final Logger logger = LoggerFactory.getLogger(MeshRuleRouter.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MeshRuleRouter.class);
 
     private final Map<String, String> sourcesLabels;
     private volatile BitList<Invoker<T>> invokerList = BitList.emptyList();
@@ -308,7 +309,7 @@
                 appToVDGroup.put(appName, vsDestinationGroup);
             }
         } catch (Throwable t) {
-            logger.error("Error occurred when parsing rule component.", t);
+            logger.error(CLUSTER_FAILED_RECEIVE_RULE,"failed to parse mesh route rule","","Error occurred when parsing rule component.",t);
         }
 
         computeSubset(appToVDGroup);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/util/MeshRuleDispatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/util/MeshRuleDispatcher.java
index 3a3c240..03f68dd 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/util/MeshRuleDispatcher.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/util/MeshRuleDispatcher.java
@@ -17,7 +17,7 @@
 
 package org.apache.dubbo.rpc.cluster.router.mesh.util;
 
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
@@ -27,9 +27,11 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_NO_RULE_LISTENER;
+
 
 public class MeshRuleDispatcher {
-    public static final Logger logger = LoggerFactory.getLogger(MeshRuleDispatcher.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MeshRuleDispatcher.class);
 
     private final String appName;
     private final Map<String, Set<MeshRuleListener>> listenerMap = new ConcurrentHashMap<>();
@@ -55,7 +57,7 @@
                         listener.onRuleChange(appName, entry.getValue());
                     }
                 } else {
-                    logger.warn("Receive rule but none of listener has been registered. Maybe type not matched. Rule Type: " + ruleType);
+                    logger.warn(CLUSTER_NO_RULE_LISTENER,"Receive mesh rule but none of listener has been registered","","Receive rule but none of listener has been registered. Maybe type not matched. Rule Type: " + ruleType);
                 }
             }
             // clear rule listener not being notified in this time
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/ScriptStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/ScriptStateRouter.java
index 3e2d570..174ecb9 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/ScriptStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/ScriptStateRouter.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.router.script;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.Holder;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -49,6 +49,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_SCRIPT_EXCEPTION;
 import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_SCRIPT_TYPE_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
@@ -61,7 +62,7 @@
 public class ScriptStateRouter<T> extends AbstractStateRouter<T> {
     public static final String NAME = "SCRIPT_ROUTER";
     private static final int SCRIPT_ROUTER_DEFAULT_PRIORITY = 0;
-    private static final Logger logger = LoggerFactory.getLogger(ScriptStateRouter.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ScriptStateRouter.class);
 
     private static final Map<String, ScriptEngine> ENGINES = new ConcurrentHashMap<>();
 
@@ -92,8 +93,8 @@
             Compilable compilable = (Compilable) engine;
             function = compilable.compile(rule);
         } catch (ScriptException e) {
-            logger.error("route error, rule has been ignored. rule: " + rule +
-                    ", url: " + RpcContext.getServiceContext().getUrl(), e);
+            logger.error(CLUSTER_SCRIPT_EXCEPTION,"script route rule invalid","","script route error, rule has been ignored. rule: " + rule +
+                ", url: " + RpcContext.getServiceContext().getUrl(),e);
         }
     }
 
@@ -136,8 +137,8 @@
             try {
                 return function.eval(bindings);
             } catch (ScriptException e) {
-                logger.error("route error, rule has been ignored. rule: " + rule + ", method:" +
-                    invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
+                logger.error(CLUSTER_SCRIPT_EXCEPTION,"Scriptrouter exec script error","","Script route error, rule has been ignored. rule: " + rule + ", method:" +
+                    invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(),e);
                 return invokers;
             }
         }, accessControlContext));
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
index 5b57b21..20aee26 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
@@ -21,7 +21,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.Holder;
@@ -41,6 +41,8 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
 import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID;
 import static org.apache.dubbo.rpc.Constants.FORCE_USE_TAG;
 
 /**
@@ -48,7 +50,7 @@
  */
 public class TagStateRouter<T> extends AbstractStateRouter<T> implements ConfigurationListener {
     public static final String NAME = "TAG_ROUTER";
-    private static final Logger logger = LoggerFactory.getLogger(TagStateRouter.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(TagStateRouter.class);
     private static final String RULE_SUFFIX = ".tag-router";
 
     private TagRouterRule tagRouterRule;
@@ -72,8 +74,8 @@
                 this.tagRouterRule = TagRuleParser.parse(event.getContent());
             }
         } catch (Exception e) {
-            logger.error("Failed to parse the raw tag router rule and it will not take effect, please check if the " +
-                "rule matches with the template, the raw rule is:\n ", e);
+            logger.error(CLUSTER_TAG_ROUTE_INVALID,"Failed to parse the raw tag router rule","","Failed to parse the raw tag router rule and it will not take effect, please check if the " +
+                "rule matches with the template, the raw rule is:\n ",e);
         }
     }
 
@@ -235,7 +237,7 @@
                     return true;
                 }
             } catch (Exception e) {
-                logger.error("The format of ip address is invalid in tag route. Address :" + address, e);
+                logger.error(CLUSTER_TAG_ROUTE_INVALID,"tag route address is invalid","","The format of ip address is invalid in tag route. Address :" + address,e);
             }
         }
         return false;
@@ -256,7 +258,7 @@
         String providerApplication = url.getRemoteApplication();
 
         if (StringUtils.isEmpty(providerApplication)) {
-            logger.error("TagRouter must getConfig from or subscribe to a specific application, but the application " +
+            logger.error(CLUSTER_TAG_ROUTE_EMPTY,"tag router get providerApplication is empty","","TagRouter must getConfig from or subscribe to a specific application, but the application " +
                 "in this TagRouter is not specified.");
             return;
         }
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 5406ac6..c4435a4 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.config.Configuration;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.profiler.ProfilerSwitch;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -49,6 +49,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.ENABLE_CONNECTIVITY_VALIDATION;
 import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.RESELECT_COUNT;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RESELECT_INVOKERS;
 import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_AVAILABLE_CHECK_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_CLUSTER_AVAILABLE_CHECK;
@@ -59,7 +60,7 @@
  */
 public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
 
-    private static final Logger logger = LoggerFactory.getLogger(AbstractClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractClusterInvoker.class);
 
     protected Directory<T> directory;
 
@@ -213,11 +214,11 @@
                         //Avoid collision
                         invoker = invokers.get((index + 1) % invokers.size());
                     } catch (Exception e) {
-                        logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
+                        logger.warn(CLUSTER_FAILED_RESELECT_INVOKERS,"select invokers exception","",e.getMessage() + " may because invokers list dynamic change, ignore.",e);
                     }
                 }
             } catch (Throwable t) {
-                logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
+                logger.error(CLUSTER_FAILED_RESELECT_INVOKERS,"failed to reselect invokers","","cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url",t);
             }
         }
 
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 6d03e36..02b8f6d 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
@@ -32,12 +32,14 @@
 import java.util.HashMap;
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_ERROR_RESPONSE;
+
 /**
  * BroadcastClusterInvoker
  */
 public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
 
-    private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(BroadcastClusterInvoker.class);
     private static final String BROADCAST_FAIL_PERCENT_KEY = "broadcast.fail.percent";
     private static final int MAX_BROADCAST_FAIL_PERCENT = 100;
     private static final int MIN_BROADCAST_FAIL_PERCENT = 0;
@@ -81,7 +83,7 @@
                     Throwable resultException = result.getException();
                     if (null != resultException) {
                         exception = getRpcException(result.getException());
-                        logger.warn(exception.getMessage(), exception);
+                        logger.warn(CLUSTER_ERROR_RESPONSE,"provider return error response","",exception.getMessage(),exception);
                         failIndex++;
                         if (failIndex == failThresholdIndex) {
                             break;
@@ -90,7 +92,7 @@
                 }
             } catch (Throwable e) {
                 exception = getRpcException(e);
-                logger.warn(exception.getMessage(), exception);
+                logger.warn(CLUSTER_ERROR_RESPONSE,"provider return error response","",exception.getMessage(),exception);
                 failIndex++;
                 if (failIndex == failThresholdIndex) {
                     break;
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 120f594..608b00e 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
 import org.apache.dubbo.common.timer.Timeout;
@@ -39,6 +39,8 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_FAILBACK_TIMES;
 import static org.apache.dubbo.common.constants.CommonConstants.RETRIES_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_INVOKE_SERVICE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TIMER_RETRY_FAILED;
 import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_FAILBACK_TASKS;
 import static org.apache.dubbo.rpc.cluster.Constants.FAIL_BACK_TASKS_KEY;
 
@@ -50,7 +52,7 @@
  */
 public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
 
-    private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FailbackClusterInvoker.class);
 
     private static final long RETRY_FAILED_PERIOD = 5;
 
@@ -93,7 +95,7 @@
         try {
             failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
         } catch (Throwable e) {
-            logger.error("Failback background works error, invocation->" + invocation + ", exception: " + e.getMessage());
+            logger.error(CLUSTER_TIMER_RETRY_FAILED,"add newTimeout exception","","Failback background works error, invocation->" + invocation + ", exception: " + e.getMessage(),e);
         }
     }
 
@@ -108,8 +110,8 @@
             // Then the serviceContext will be cleared after the call is completed.
             return invokeWithContextAsync(invoker, invocation, consumerUrl);
         } catch (Throwable e) {
-            logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
-                + e.getMessage() + ", ", e);
+            logger.error(CLUSTER_FAILED_INVOKE_SERVICE,"Failback to invoke method and start to retries","","Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
+                + e.getMessage() + ", ",e);
             if (retries > 0) {
                 addFailed(loadbalance, invocation, invokers, invoker, consumerUrl);
             }
@@ -166,9 +168,9 @@
                 lastInvoker = retryInvoker;
                 invokeWithContextAsync(retryInvoker, invocation, consumerUrl);
             } catch (Throwable e) {
-                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
+                logger.error(CLUSTER_FAILED_INVOKE_SERVICE,"Failed retry to invoke method","","Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.",e);
                 if ((++retriedTimes) >= retries) {
-                    logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation);
+                    logger.error(CLUSTER_FAILED_INVOKE_SERVICE,"Failed retry to invoke method and retry times exceed threshold","","Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation,e);
                 } else {
                     rePut(timeout);
                 }
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 8781dc7..1858374 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.rpc.cluster.support;
 
 import org.apache.dubbo.common.Version;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.rpc.Invocation;
@@ -36,6 +36,7 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_RETRIES;
 import static org.apache.dubbo.common.constants.CommonConstants.RETRIES_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_MULTIPLE_RETRIES;
 
 /**
  * When invoke fails, log the initial error and retry other invokers (retry n times, which means at most n different invokers will be invoked)
@@ -46,7 +47,7 @@
  */
 public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
 
-    private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FailoverClusterInvoker.class);
 
     public FailoverClusterInvoker(Directory<T> directory) {
         super(directory);
@@ -79,15 +80,15 @@
             try {
                 Result result = invokeWithContext(invoker, invocation);
                 if (le != null && logger.isWarnEnabled()) {
-                    logger.warn("Although retry the method " + methodName
-                            + " in the service " + getInterface().getName()
-                            + " was successful by the provider " + invoker.getUrl().getAddress()
-                            + ", but there have been failed providers " + providers
-                            + " (" + providers.size() + "/" + copyInvokers.size()
-                            + ") from the registry " + directory.getUrl().getAddress()
-                            + " on the consumer " + NetUtils.getLocalHost()
-                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
-                            + le.getMessage(), le);
+                    logger.warn(CLUSTER_FAILED_MULTIPLE_RETRIES,"failed to retry do invoke","","Although retry the method " + methodName
+                        + " in the service " + getInterface().getName()
+                        + " was successful by the provider " + invoker.getUrl().getAddress()
+                        + ", but there have been failed providers " + providers
+                        + " (" + providers.size() + "/" + copyInvokers.size()
+                        + ") from the registry " + directory.getUrl().getAddress()
+                        + " on the consumer " + NetUtils.getLocalHost()
+                        + " using the dubbo version " + Version.getVersion() + ". Last error is: "
+                        + le.getMessage(),le);
                 }
                 success = true;
                 return result;
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 edc9ec5..c09205b 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
@@ -16,7 +16,7 @@
  */
 package org.apache.dubbo.rpc.cluster.support;
 
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.rpc.AsyncRpcResult;
 import org.apache.dubbo.rpc.Invocation;
@@ -28,6 +28,8 @@
 
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_ERROR_RESPONSE;
+
 /**
  * When invoke fails, log the error message and ignore this error by returning an empty Result.
  * Usually used to write audit logs and other operations
@@ -36,7 +38,7 @@
  *
  */
 public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
-    private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FailsafeClusterInvoker.class);
 
     public FailsafeClusterInvoker(Directory<T> directory) {
         super(directory);
@@ -49,7 +51,7 @@
             Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
             return invokeWithContext(invoker, invocation);
         } catch (Throwable e) {
-            logger.error("Failsafe ignore exception: " + e.getMessage(), e);
+            logger.error(CLUSTER_ERROR_RESPONSE,"Failsafe for provider exception","","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/wrapper/MockClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
index e6147da..a935fd9 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
@@ -18,7 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConfigUtils;
@@ -39,13 +39,14 @@
 
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_MOCK_REQUEST;
 import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK;
 
 public class MockClusterInvoker<T> implements ClusterInvoker<T> {
 
-    private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MockClusterInvoker.class);
     private static final boolean setFutureWhenSync = Boolean.parseBoolean(System.getProperty(CommonConstants.SET_FUTURE_IN_SYNC_MODE, "true"));
 
     private final Directory<T> directory;
@@ -102,7 +103,7 @@
             result = this.invoker.invoke(invocation);
         } else if (value.startsWith(FORCE_KEY)) {
             if (logger.isWarnEnabled()) {
-                logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl());
+                logger.warn(CLUSTER_FAILED_MOCK_REQUEST,"force mock","","force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl());
             }
             //force:direct mock
             result = doMockInvoke(invocation, null);
@@ -127,7 +128,7 @@
                 }
 
                 if (logger.isWarnEnabled()) {
-                    logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e);
+                    logger.warn(CLUSTER_FAILED_MOCK_REQUEST,"failed to mock invoke","","fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(),e);
                 }
                 result = doMockInvoke(invocation, e);
             }
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LeastActiveBalanceTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LeastActiveBalanceTest.java
index 89dc66b..0e172e4 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LeastActiveBalanceTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LeastActiveBalanceTest.java
@@ -3,7 +3,7 @@
  * 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 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
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LoadBalanceBaseTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LoadBalanceBaseTest.java
index 0331873..c96c0fc 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LoadBalanceBaseTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/LoadBalanceBaseTest.java
@@ -3,7 +3,7 @@
  * 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 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
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalanceTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalanceTest.java
index f18a07d..7bd0a76 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalanceTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/RandomLoadBalanceTest.java
@@ -3,7 +3,7 @@
  * 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 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
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalanceTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalanceTest.java
index aebaf00..9d43790 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalanceTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/loadbalance/ShortestResponseLoadBalanceTest.java
@@ -3,7 +3,7 @@
  * 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 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
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ProtocolServiceKey.java b/dubbo-common/src/main/java/org/apache/dubbo/common/ProtocolServiceKey.java
new file mode 100644
index 0000000..0cc8b20
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/ProtocolServiceKey.java
@@ -0,0 +1,124 @@
+/*
+ * 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.constants.CommonConstants;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.Objects;
+
+public class ProtocolServiceKey extends ServiceKey {
+    private final String protocol;
+
+    public ProtocolServiceKey(String interfaceName, String version, String group, String protocol) {
+        super(interfaceName, version, group);
+        this.protocol = protocol;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public String getServiceKeyString() {
+        return super.toString();
+    }
+
+    public boolean isSameWith(ProtocolServiceKey protocolServiceKey) {
+        // interface version group should be the same
+        if (!super.equals(protocolServiceKey)) {
+            return false;
+        }
+
+        // origin protocol is *, can not match any protocol
+        if (CommonConstants.ANY_VALUE.equals(protocol)) {
+            return false;
+        }
+
+        // origin protocol is null, can match any protocol
+        if (StringUtils.isEmpty(protocol) || StringUtils.isEmpty(protocolServiceKey.getProtocol())) {
+            return true;
+        }
+
+        // origin protocol is not *, match itself
+        return Objects.equals(protocol, protocolServiceKey.getProtocol());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        ProtocolServiceKey that = (ProtocolServiceKey) o;
+        return Objects.equals(protocol, that.protocol);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), protocol);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + CommonConstants.GROUP_CHAR_SEPARATOR + protocol;
+    }
+
+    public static class Matcher {
+        public static boolean isMatch(ProtocolServiceKey rule, ProtocolServiceKey target) {
+            // 1. 2. 3. match interface / version / group
+            if (!ServiceKey.Matcher.isMatch(rule, target)) {
+                return false;
+            }
+
+            // 4.match protocol
+            // 4.1. if rule group is *, match all
+            if (!CommonConstants.ANY_VALUE.equals(rule.getProtocol())) {
+                // 4.2. if rule protocol is null, match all
+                if (StringUtils.isNotEmpty(rule.getProtocol())) {
+                    // 4.3. if rule protocol contains ',', split and match each
+                    if (rule.getProtocol().contains(CommonConstants.COMMA_SEPARATOR)) {
+                        String[] protocols = rule.getProtocol().split("\\" +CommonConstants.COMMA_SEPARATOR, -1);
+                        boolean match = false;
+                        for (String protocol : protocols) {
+                            protocol = protocol.trim();
+                            if (StringUtils.isEmpty(protocol) && StringUtils.isEmpty(target.getProtocol())) {
+                                match = true;
+                                break;
+                            } else if (protocol.equals(target.getProtocol())) {
+                                match = true;
+                                break;
+                            }
+                        }
+                        if (!match) {
+                            return false;
+                        }
+                    }
+                    // 4.3. if rule protocol is not contains ',', match directly
+                    else if (!Objects.equals(rule.getProtocol(), target.getProtocol())) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ServiceKey.java b/dubbo-common/src/main/java/org/apache/dubbo/common/ServiceKey.java
new file mode 100644
index 0000000..c8d8f84
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/ServiceKey.java
@@ -0,0 +1,146 @@
+/*
+ * 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.constants.CommonConstants;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.Objects;
+
+public class ServiceKey {
+    private final String interfaceName;
+    private final String group;
+    private final String version;
+
+    public ServiceKey(String interfaceName, String version, String group) {
+        this.interfaceName = interfaceName;
+        this.group = group;
+        this.version = version;
+    }
+
+    public String getInterfaceName() {
+        return interfaceName;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ServiceKey that = (ServiceKey) o;
+        return Objects.equals(interfaceName, that.interfaceName) && Objects.equals(group, that.group) && Objects.equals(version, that.version);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(interfaceName, group, version);
+    }
+
+    @Override
+    public String toString() {
+        return BaseServiceMetadata.buildServiceKey(interfaceName, group, version);
+    }
+
+
+    public static class Matcher {
+        public static boolean isMatch(ServiceKey rule, ServiceKey target) {
+            // 1. match interface (accurate match)
+            if (!Objects.equals(rule.getInterfaceName(), target.getInterfaceName())) {
+                return false;
+            }
+
+            // 2. match version (accurate match)
+            // 2.1. if rule version is *, match all
+            if (!CommonConstants.ANY_VALUE.equals(rule.getVersion())) {
+                // 2.2. if rule version is null, target version should be null
+                if (StringUtils.isEmpty(rule.getVersion()) && !StringUtils.isEmpty(target.getVersion())) {
+                    return false;
+                }
+                if (!StringUtils.isEmpty(rule.getVersion())) {
+                    // 2.3. if rule version contains ',', split and match each
+                    if (rule.getVersion().contains(CommonConstants.COMMA_SEPARATOR)) {
+                        String[] versions = rule.getVersion().split("\\" +CommonConstants.COMMA_SEPARATOR, -1);
+                        boolean match = false;
+                        for (String version : versions) {
+                            version = version.trim();
+                            if (StringUtils.isEmpty(version) && StringUtils.isEmpty(target.getVersion())) {
+                                match = true;
+                                break;
+                            } else if (version.equals(target.getVersion())) {
+                                match = true;
+                                break;
+                            }
+                        }
+                        if (!match) {
+                            return false;
+                        }
+                    }
+                    // 2.4. if rule version is not contains ',', match directly
+                    else if (!Objects.equals(rule.getVersion(), target.getVersion())) {
+                        return false;
+                    }
+                }
+            }
+
+            // 3. match group (wildcard match)
+            // 3.1. if rule group is *, match all
+            if (!CommonConstants.ANY_VALUE.equals(rule.getGroup())) {
+                // 3.2. if rule group is null, target group should be null
+                if (StringUtils.isEmpty(rule.getGroup()) && !StringUtils.isEmpty(target.getGroup())) {
+                    return false;
+                }
+                if (!StringUtils.isEmpty(rule.getGroup())) {
+                    // 3.3. if rule group contains ',', split and match each
+                    if (rule.getGroup().contains(CommonConstants.COMMA_SEPARATOR)) {
+                        String[] groups = rule.getGroup().split("\\" +CommonConstants.COMMA_SEPARATOR, -1);
+                        boolean match = false;
+                        for (String group : groups) {
+                            group = group.trim();
+                            if (StringUtils.isEmpty(group) && StringUtils.isEmpty(target.getGroup())) {
+                                match = true;
+                                break;
+                            } else if (group.equals(target.getGroup())) {
+                                match = true;
+                                break;
+                            }
+                        }
+                        if (!match) {
+                            return false;
+                        }
+                    }
+                    // 3.4. if rule group is not contains ',', match directly
+                    else if (!Objects.equals(rule.getGroup(), target.getGroup())) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+}
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 29d9df9..cb98b66 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
@@ -146,7 +146,12 @@
     public URL(URLAddress urlAddress, URLParam urlParam, Map<String, Object> attributes) {
         this.urlAddress = urlAddress;
         this.urlParam = null == urlParam ? URLParam.parse(new HashMap<>()) : urlParam;
-        this.attributes = (attributes != null ? attributes.isEmpty() ? null : attributes : null);
+
+        if (attributes != null && !attributes.isEmpty()) {
+            this.attributes = attributes;
+        } else {
+            this.attributes = null;
+        }
     }
 
     public URL(String protocol, String host, int port) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java b/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java
index 9d66014..ba78c46 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java
@@ -79,7 +79,7 @@
      *         a value greater than {@code 0} if {@code version1 > version2}
      */
     public static int compare(String version1, String version2) {
-        return Integer.compare (getIntVersion(version1), getIntVersion(version2));
+        return Integer.compare(getIntVersion(version1), getIntVersion(version2));
     }
 
     /**
@@ -89,10 +89,8 @@
         if (StringUtils.isEmpty(version)) {
             return false;
         }
-        if (getIntVersion(version) >= 2070000) {
-            return true;
-        }
-        return false;
+
+        return getIntVersion(version) >= 2070000;
     }
 
     /**
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java b/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
index b4ca8b4..9d73843 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/beans/support/InstantiationStrategy.java
@@ -33,7 +33,7 @@
  */
 public class InstantiationStrategy {
 
-    private ScopeModelAccessor scopeModelAccessor;
+    private final ScopeModelAccessor scopeModelAccessor;
 
     public InstantiationStrategy() {
         this(null);
@@ -43,6 +43,7 @@
         this.scopeModelAccessor = scopeModelAccessor;
     }
 
+    @SuppressWarnings("unchecked")
     public <T> T instantiate(Class<T> type) throws ReflectiveOperationException {
 
         // should not use default constructor directly, maybe also has another constructor matched scope model arguments
@@ -55,7 +56,7 @@
         }
 
         // 2. use matched constructor if found
-        List<Constructor> matchedConstructors = new ArrayList<>();
+        List<Constructor<?>> matchedConstructors = new ArrayList<>();
         Constructor<?>[] declaredConstructors = type.getConstructors();
         for (Constructor<?> constructor : declaredConstructors) {
             if (isMatched(constructor)) {
@@ -71,7 +72,7 @@
         // 1. the only matched constructor with parameters
         // 2. default constructor if absent
 
-        Constructor targetConstructor;
+        Constructor<?> targetConstructor;
         if (matchedConstructors.size() > 1) {
             throw new IllegalArgumentException("Expect only one but found " +
                 matchedConstructors.size() + " matched constructors for type: " + type.getName() +
@@ -85,7 +86,7 @@
         }
 
         // create instance with arguments
-        Class[] parameterTypes = targetConstructor.getParameterTypes();
+        Class<?>[] parameterTypes = targetConstructor.getParameterTypes();
         Object[] args = new Object[parameterTypes.length];
         for (int i = 0; i < parameterTypes.length; i++) {
             args[i] = getArgumentValueForType(parameterTypes[i]);
@@ -106,7 +107,7 @@
         return ScopeModel.class.isAssignableFrom(parameterType);
     }
 
-    private Object getArgumentValueForType(Class parameterType) {
+    private Object getArgumentValueForType(Class<?> parameterType) {
         // get scope mode value
         if (scopeModelAccessor != null) {
             if (parameterType == ScopeModel.class) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStore.java b/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStore.java
index a8ecc80..84601ef 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStore.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStore.java
@@ -16,7 +16,7 @@
  */
 package org.apache.dubbo.common.cache;
 
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
@@ -30,18 +30,24 @@
 import java.io.Writer;
 import java.nio.channels.FileLock;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_CACHE_MAX_ENTRY_COUNT_LIMIT_EXCEED;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_CACHE_MAX_FILE_SIZE_LIMIT_EXCEED;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_CACHE_PATH_INACCESSIBLE;
+
 /**
  * Local file interaction class that can back different caches.
  * <p>
  * All items in local file are of human friendly format.
  */
 public class FileCacheStore {
-    private static final Logger logger = LoggerFactory.getLogger(FileCacheStore.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FileCacheStore.class);
 
     private String cacheFilePath;
     private File cacheFile;
@@ -71,10 +77,14 @@
             }
 
             if (count > entrySize) {
-                logger.warn("Cache file was truncated for exceeding the maximum entry size " + entrySize);
+                logger.warn(COMMON_CACHE_MAX_FILE_SIZE_LIMIT_EXCEED, "mis-configuration of system properties",
+                    "Check Java system property 'dubbo.mapping.cache.entrySize' and 'dubbo.meta.cache.entrySize'.",
+                    "Cache file was truncated for exceeding the maximum entry size: " + entrySize);
             }
         } catch (IOException e) {
-            logger.warn("Load cache failed ", e);
+            logger.warn(COMMON_CACHE_PATH_INACCESSIBLE, "inaccessible of cache path", "",
+                "Load cache failed ", e);
+
             throw e;
         }
         return properties;
@@ -88,6 +98,9 @@
                 directoryLock.channel().close();
                 deleteFile(lockFile);
             } catch (IOException e) {
+                logger.error(COMMON_CACHE_PATH_INACCESSIBLE, "inaccessible of cache path", "",
+                    "Failed to release cache path's lock file:" + lockFile, e);
+
                 throw new RuntimeException("Failed to release cache path's lock file:" + lockFile, e);
             }
         }
@@ -102,29 +115,41 @@
                  new LimitedLengthBufferedWriter(
                      new OutputStreamWriter(
                          new FileOutputStream(cacheFile, false), StandardCharsets.UTF_8), maxFileSize)) {
+
             bw.write("#" + comment);
             bw.newLine();
             bw.write("#" + new Date());
             bw.newLine();
+
             for (Map.Entry<String, String> e : properties.entrySet()) {
                 String key = e.getKey();
                 String val = e.getValue();
                 bw.write(key + "=" + val);
                 bw.newLine();
             }
+
             bw.flush();
+
             long remainSize = bw.getRemainSize();
             if (remainSize < 0) {
-                logger.info("Cache file was truncated for exceeding the maximum file size " + maxFileSize + " byte. Exceeded by " + (-remainSize) + " byte.");
+                logger.warn(COMMON_CACHE_MAX_ENTRY_COUNT_LIMIT_EXCEED, "mis-configuration of system properties",
+                    "Check Java system property 'dubbo.mapping.cache.maxFileSize' and 'dubbo.meta.cache.maxFileSize'.",
+                    "Cache file was truncated for exceeding the maximum file size " + maxFileSize + " byte. Exceeded by " + (-remainSize) + " byte.");
             }
         } catch (IOException e) {
-            logger.warn("Update cache error.");
+            logger.warn(COMMON_CACHE_PATH_INACCESSIBLE, "inaccessible of cache path", "",
+                "Update cache error.", e);
         }
     }
 
     private static void deleteFile(File f) {
-        if (!f.delete()) {
-            logger.debug("Failed to delete file " + f.getAbsolutePath());
+
+        Path pathOfFile = f.toPath();
+
+        try {
+            Files.delete(pathOfFile);
+        } catch (IOException ioException) {
+            logger.debug("Failed to delete file " + f.getAbsolutePath(), ioException);
         }
     }
 
@@ -179,6 +204,9 @@
         }
     }
 
+    /**
+     * An empty (or fallback) implementation of FileCacheStore. Used when cache file creation failed.
+     */
     protected static class Empty extends FileCacheStore {
 
         private Empty(String cacheFilePath) {
@@ -196,9 +224,13 @@
 
         @Override
         public void refreshCache(Map<String, String> properties, String comment, long maxFileSize) {
+            // No-op.
         }
     }
 
+    /**
+     * A BufferedWriter which limits the length (in bytes). When limit exceed, this writer stops writing.
+     */
     private static class LimitedLengthBufferedWriter extends BufferedWriter {
 
         private long remainSize;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStoreFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStoreFactory.java
index f4375c7..a49f19a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStoreFactory.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/cache/FileCacheStoreFactory.java
@@ -14,9 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.dubbo.common.cache;
 
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 
 import java.io.File;
@@ -25,22 +26,34 @@
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_CACHE_PATH_INACCESSIBLE;
+
 /**
  * ClassLoader Level static share.
  * Prevent FileCacheStore being operated in multi-application
  */
-public class FileCacheStoreFactory {
-    private final static Logger logger = LoggerFactory.getLogger(FileCacheStoreFactory.class);
+public final class FileCacheStoreFactory {
+
+    /**
+     * Forbids instantiation.
+     */
+    private FileCacheStoreFactory() {
+        throw new UnsupportedOperationException("No instance of 'FileCacheStoreFactory' for you! ");
+    }
+
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FileCacheStoreFactory.class);
     private static final Map<String, FileCacheStore> cacheMap = new ConcurrentHashMap<>();
 
     private static final String SUFFIX = ".dubbo.cache";
-    private static final char ESCAPE = '%';
+    private static final char ESCAPE_MARK = '%';
     private static final Set<Character> LEGAL_CHARACTERS = Collections.unmodifiableSet(new HashSet<Character>(){{
         // - $ . _ 0-9 a-z A-Z
         add('-');
@@ -64,6 +77,7 @@
 
     public static FileCacheStore getInstance(String basePath, String cacheName, boolean enableFileCache) {
         if (basePath == null) {
+            // default case: ~/.dubbo
             basePath = System.getProperty("user.home") + File.separator + ".dubbo";
         }
         if (basePath.endsWith(File.separator)) {
@@ -71,9 +85,20 @@
         }
 
         File candidate = new File(basePath);
+        Path path = candidate.toPath();
+
         // ensure cache store path exists
-        if (!candidate.isDirectory() && !candidate.mkdirs()) {
-            throw new RuntimeException("Cache store path can't be created: " + candidate);
+        if (!candidate.isDirectory()) {
+            try {
+                Files.createDirectories(path);
+            } catch (IOException e) {
+                // 0-3 - cache path inaccessible
+
+                logger.error(COMMON_CACHE_PATH_INACCESSIBLE, "inaccessible of cache path", "",
+                    "Cache store path can't be created: ", e);
+
+                throw new RuntimeException("Cache store path can't be created: " + candidate, e);
+            }
         }
 
         cacheName = safeName(cacheName);
@@ -83,7 +108,7 @@
 
         String cacheFilePath = basePath + File.separator + cacheName;
 
-        return cacheMap.computeIfAbsent(cacheFilePath, (k) -> getFile(k, enableFileCache));
+        return cacheMap.computeIfAbsent(cacheFilePath, k -> getFile(k, enableFileCache));
     }
 
     /**
@@ -100,7 +125,7 @@
             if (LEGAL_CHARACTERS.contains(c)) {
                 sb.append(c);
             } else {
-                sb.append(ESCAPE);
+                sb.append(ESCAPE_MARK);
                 sb.append(String.format("%04x", (int) c));
             }
         }
@@ -114,22 +139,29 @@
      * @return a file object
      */
     private static FileCacheStore getFile(String name, boolean enableFileCache) {
-        if(!enableFileCache) {
+        if (!enableFileCache) {
             return FileCacheStore.Empty.getInstance(name);
         }
+
         try {
             FileCacheStore.Builder builder = FileCacheStore.newBuilder();
             tryFileLock(builder, name);
             File file = new File(name);
+
             if (!file.exists()) {
-                file.createNewFile();
+                Path pathObjectOfFile = file.toPath();
+                Files.createFile(pathObjectOfFile);
             }
 
             builder.cacheFilePath(name)
                 .cacheFile(file);
+
             return builder.build();
         } catch (Throwable t) {
-            logger.info("Failed to create file store cache. Local file cache will be disabled. Cache file name: " + name, t);
+
+            logger.warn(COMMON_CACHE_PATH_INACCESSIBLE, "inaccessible of cache path", "",
+                "Failed to create file store cache. Local file cache will be disabled. Cache file name: " + name, t);
+
             return FileCacheStore.Empty.getInstance(name);
         }
     }
@@ -153,13 +185,13 @@
         }
 
         if (dirLock == null) {
-            throw new PathNotExclusiveException(fileName + " is not exclusive.");
+            throw new PathNotExclusiveException(fileName + " is not exclusive. Maybe multiple Dubbo instances are using the same folder.");
         }
 
         builder.directoryLock(dirLock).lockFile(lockFile);
     }
 
-    protected static void removeCache(String cacheFileName) {
+    static void removeCache(String cacheFileName) {
         cacheMap.remove(cacheFileName);
     }
 
@@ -167,7 +199,7 @@
      * for unit test only
      */
     @Deprecated
-    protected static Map<String, FileCacheStore> getCacheMap() {
+    static Map<String, FileCacheStore> getCacheMap() {
         return cacheMap;
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/JdkCompiler.java b/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/JdkCompiler.java
index 443690b..cebeaf7 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/JdkCompiler.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/compiler/support/JdkCompiler.java
@@ -55,7 +55,7 @@
 
     private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 
-    private final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
+    private final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
 
     private final ClassLoaderImpl classLoader;
 
@@ -83,7 +83,7 @@
                 && (!"sun.misc.Launcher$AppClassLoader".equals(loader.getClass().getName()))) {
             try {
                 URLClassLoader urlClassLoader = (URLClassLoader) loader;
-                List<File> files = new ArrayList<File>();
+                List<File> files = new ArrayList<>();
                 for (URL url : urlClassLoader.getURLs()) {
                     files.add(new File(url.getFile()));
                 }
@@ -172,7 +172,7 @@
 
         private final ClassLoaderImpl classLoader;
 
-        private final Map<URI, JavaFileObject> fileObjects = new HashMap<URI, JavaFileObject>();
+        private final Map<URI, JavaFileObject> fileObjects = new HashMap<>();
 
         public JavaFileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) {
             super(fileManager);
@@ -224,7 +224,7 @@
 
             ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
 
-            ArrayList<JavaFileObject> files = new ArrayList<JavaFileObject>();
+            ArrayList<JavaFileObject> files = new ArrayList<>();
 
             if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
                 for (JavaFileObject file : fileObjects.values()) {
@@ -252,7 +252,7 @@
 
     private static final class ClassLoaderImpl extends ClassLoader {
 
-        private final Map<String, JavaFileObject> classes = new HashMap<String, JavaFileObject>();
+        private final Map<String, JavaFileObject> classes = new HashMap<>();
 
         ClassLoaderImpl(final ClassLoader parentClassLoader) {
             super(parentClassLoader);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/RejectException.java b/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/RejectException.java
index 1b7537b..a7c2177 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/RejectException.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/concurrent/RejectException.java
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.threadpool.MemorySafeLinkedBlockingQueue;
 
 /**
- * Exception thrown by an {@link MemorySafeLinkedBlockingQueue} when a element cannot be accepted.
+ * Exception thrown by an {@link MemorySafeLinkedBlockingQueue} when an element cannot be accepted.
  */
 public class RejectException extends RuntimeException {
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Configuration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Configuration.java
index 5b8531b..5c3988a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/Configuration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Configuration.java
@@ -16,14 +16,21 @@
  */
 package org.apache.dubbo.common.config;
 
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+
 import java.util.NoSuchElementException;
 
 import static org.apache.dubbo.common.config.ConfigurationUtils.isEmptyValue;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_PROPERTY_MISSPELLING;
 
 /**
  * Configuration interface, to fetch the value for the specified key.
  */
 public interface Configuration {
+
+    ErrorTypeAwareLogger interfaceLevelLogger = LoggerFactory.getErrorTypeAwareLogger(Configuration.class);
+
     /**
      * Get a string associated with the given configuration key.
      *
@@ -66,6 +73,11 @@
         try {
             return convert(Integer.class, key, defaultValue);
         } catch (NumberFormatException e) {
+            // 0-2 Property type mismatch.
+            interfaceLevelLogger.error(COMMON_PROPERTY_MISSPELLING, "typo in property value",
+                "This property requires an integer value.",
+                "Actual Class: " + getClass().getName(), e);
+
             throw new IllegalStateException('\'' + key + "' doesn't map to a Integer object", e);
         }
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
index acbbf36..6daeb9f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/ConfigurationUtils.java
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.dubbo.common.config;
 
 import org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory;
@@ -47,7 +48,15 @@
 /**
  * Utilities for manipulating configurations from different sources
  */
-public class ConfigurationUtils {
+public final class ConfigurationUtils {
+
+    /**
+     * Forbids instantiation.
+     */
+    private ConfigurationUtils() {
+        throw new UnsupportedOperationException("No instance of 'ConfigurationUtils' for you! ");
+    }
+
     private static final Logger logger = LoggerFactory.getLogger(ConfigurationUtils.class);
     private static final List<String> securityKey;
 
@@ -75,13 +84,12 @@
      *
      * @return
      */
-
     public static Configuration getEnvConfiguration(ScopeModel scopeModel) {
         return getScopeModelOrDefaultApplicationModel(scopeModel).getModelEnvironment().getEnvironmentConfiguration();
     }
 
     /**
-     * Used to get an composite property value.
+     * Used to get a composite property value.
      * <p>
      * Also see {@link Environment#getConfiguration()}
      *
@@ -231,7 +239,11 @@
         }
 
         if (CollectionUtils.isNotEmptyMap(configMap)) {
-            for(Map.Entry<String, V> entry : configMap.entrySet()) {
+            Map<String,V> copy ;
+            synchronized (configMap){
+                copy = new HashMap<>(configMap);
+            }
+            for(Map.Entry<String, V> entry : copy.entrySet()) {
                 String key = entry.getKey();
                 V val = entry.getValue();
                 if (StringUtils.startsWithIgnoreCase(key, prefix)
@@ -268,7 +280,11 @@
         if (!prefix.endsWith(".")) {
             prefix += ".";
         }
-        for (Map.Entry<String, V> entry : configMap.entrySet()) {
+        Map<String,V> copy ;
+        synchronized (configMap){
+            copy = new HashMap<>(configMap);
+        }
+        for (Map.Entry<String, V> entry : copy.entrySet()) {
             String key = entry.getKey();
             if (StringUtils.startsWithIgnoreCase(key, prefix)
                 && key.length() > prefix.length()
@@ -303,7 +319,11 @@
         }
         Set<String> ids = new LinkedHashSet<>();
         for (Map<String, V> configMap : configMaps) {
-            for (Map.Entry<String, V> entry : configMap.entrySet()) {
+            Map<String,V> copy ;
+            synchronized (configMap){
+                copy = new HashMap<>(configMap);
+            }
+            for (Map.Entry<String, V> entry : copy.entrySet()) {
                 String key = entry.getKey();
                 V val = entry.getValue();
                 if (StringUtils.startsWithIgnoreCase(key, prefix)
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
index 0cc6af2..164372d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.common.config.configcenter;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -75,10 +75,10 @@
     /**
      * Logger
      */
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     /**
-     * The thread pool for workers who executes the tasks
+     * The thread pool for workers who execute the tasks
      */
     private final ThreadPoolExecutor workersThreadPool;
 
@@ -86,12 +86,12 @@
 
     private final long timeout;
 
-    public AbstractDynamicConfiguration(URL url) {
+    protected AbstractDynamicConfiguration(URL url) {
         this(getThreadPoolPrefixName(url), getThreadPoolSize(url), getThreadPoolKeepAliveTime(url), getGroup(url),
                 getTimeout(url));
     }
 
-    public AbstractDynamicConfiguration(String threadPoolPrefixName,
+    protected AbstractDynamicConfiguration(String threadPoolPrefixName,
                                         int threadPoolSize,
                                         long keepAliveTime,
                                         String group,
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
index fcd08eb..fc505d5 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
@@ -23,7 +23,7 @@
 /**
  * Dynamic Configuration
  * <br/>
- * From the use scenario internally inside framework, there're mainly three kinds of methods:
+ * From the use scenario internally inside framework, there are mainly three kinds of methods:
  * <ol>
  * <li>{@link #getProperties(String, String, long)}, get configuration file from Config Center at start up.</li>
  * <li>{@link #addListener(String, String, ConfigurationListener)}/ {@link #removeListener(String, String, ConfigurationListener)}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
index cd43c54..2612c7b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
@@ -287,24 +287,32 @@
                 WatchKey watchKey = null;
                 try {
                     watchKey = watchService.take();
-                    if (watchKey.isValid()) {
-                        for (WatchEvent event : watchKey.pollEvents()) {
-                            WatchEvent.Kind kind = event.kind();
-                            // configChangeType's key to match WatchEvent's Kind
-                            ConfigChangeType configChangeType = CONFIG_CHANGE_TYPES_MAP.get(kind.name());
-                            if (configChangeType != null) {
-                                Path configDirectoryPath = (Path) watchKey.watchable();
-                                Path currentPath = (Path) event.context();
-                                Path configFilePath = configDirectoryPath.resolve(currentPath);
-                                File configDirectory = configDirectoryPath.toFile();
-                                executeMutually(configDirectory, () -> {
-                                    fireConfigChangeEvent(configDirectory, configFilePath.toFile(), configChangeType);
-                                    signalConfigDirectory(configDirectory);
-                                    return null;
-                                });
-                            }
-                        }
+
+                    if (!watchKey.isValid()) {
+                        continue;
                     }
+
+                    for (WatchEvent event : watchKey.pollEvents()) {
+                        WatchEvent.Kind kind = event.kind();
+                        // configChangeType's key to match WatchEvent's Kind
+                        ConfigChangeType configChangeType = CONFIG_CHANGE_TYPES_MAP.get(kind.name());
+
+                        if (configChangeType == null) {
+                            continue;
+                        }
+
+                        Path configDirectoryPath = (Path) watchKey.watchable();
+                        Path currentPath = (Path) event.context();
+                        Path configFilePath = configDirectoryPath.resolve(currentPath);
+                        File configDirectory = configDirectoryPath.toFile();
+
+                        executeMutually(configDirectory, () -> {
+                            fireConfigChangeEvent(configDirectory, configFilePath.toFile(), configChangeType);
+                            signalConfigDirectory(configDirectory);
+                            return null;
+                        });
+                    }
+
                 } catch (Exception e) {
                     return;
                 } finally {
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 2939eb3..bd4e256 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
@@ -392,6 +392,8 @@
 
     String PROTOCOL_SERVER = "server";
 
+    String IPV6_KEY = "ipv6";
+
     /**
      * The parameter key for the class path of the ServiceNameMapping {@link Properties} file
      *
@@ -438,6 +440,8 @@
 
     String DEFAULT_VERSION = "0.0.0";
 
+    String CLASS_DESERIALIZE_OPEN_CHECK = "dubbo.security.serialize.openCheckClass";
+
     String ROUTER_KEY = "router";
 
     String EXPORT_ASYNC_KEY = "export-async";
@@ -546,4 +550,31 @@
 
     String PREFER_JSON_FRAMEWORK_NAME = "dubbo.json-framework.prefer";
 
+    /**
+     * @since 3.1.0
+     */
+    String MESH_ENABLE = "mesh-enable";
+
+    /**
+     * @since 3.1.0
+     */
+    Integer DEFAULT_MESH_PORT = 80;
+
+    /**
+     * @since 3.1.0
+     */
+    String SVC = ".svc.";
+
+    /**
+     * Domain name suffix used inside k8s.
+     *
+     * @since 3.1.0
+     */
+    String DEFAULT_CLUSTER_DOMAIN = "cluster.local";
+
+    /**
+     * @since 3.1.0
+     */
+    String UNLOAD_CLUSTER_RELATED = "unloadClusterRelated";
+
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoadbalanceRules.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoadbalanceRules.java
index c0e9285..daeff6c 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoadbalanceRules.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoadbalanceRules.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.common.constants;
 
 /**
- *  constant for Loadbalance strategy
+ *  constant for Load-balance strategy
  */
 public interface LoadbalanceRules {
 
@@ -27,7 +27,7 @@
     String RANDOM = "random";
 
     /**
-     *  Round robin load balance.
+     * Round-robin load balance.
      **/
     String ROUND_ROBIN = "roundrobin";
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java
new file mode 100644
index 0000000..ae41ddf
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java
@@ -0,0 +1,167 @@
+/*
+ * 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.constants;
+
+/**
+ * constants for logger
+ */
+public interface LoggerCodeConstants {
+
+    // common module 0-1 ~ 0-4
+    String COMMON_THREAD_POOL_EXHAUSTED = "0-1";
+
+    String COMMON_PROPERTY_MISSPELLING = "0-2";
+
+    String COMMON_CACHE_PATH_INACCESSIBLE = "0-3";
+
+    String COMMON_CACHE_MAX_FILE_SIZE_LIMIT_EXCEED = "0-4";
+
+
+    String COMMON_CACHE_MAX_ENTRY_COUNT_LIMIT_EXCEED = "0-5";
+
+    // registry module
+    String REGISTRY_ADDRESS_INVALID = "1-1";
+
+    String REGISTRY_ABSENCE = "1-2";
+
+    String REGISTRY_FAILED_URL_EVICTING = "1-3";
+
+    String REGISTRY_EMPTY_ADDRESS = "1-4";
+
+    String REGISTRY_NO_PARAMETERS_URL = "1-5";
+
+    String REGISTRY_FAILED_CLEAR_CACHED_URLS = "1-6";
+
+    String REGISTRY_FAILED_NOTIFY_EVENT = "1-7";
+
+    String REGISTRY_FAILED_DESTROY_UNREGISTER_URL = "1-8";
+
+    String REGISTRY_FAILED_READ_WRITE_CACHE_FILE = "1-9";
+
+    String REGISTRY_FAILED_DELETE_LOCKFILE = "1-10";
+
+    String REGISTRY_FAILED_CREATE_INSTANCE = "1-11";
+
+    String REGISTRY_FAILED_FETCH_INSTANCE = "1-12";
+
+    String REGISTRY_EXECUTE_RETRYING_TASK = "1-13";
+
+    String REGISTRY_FAILED_PARSE_DYNAMIC_CONFIG = "1-14";
+
+    String REGISTRY_FAILED_DESTROY_SERVICE = "1-15";
+
+    String REGISTRY_UNSUPPORTED_CATEGORY = "1-16";
+
+    String REGISTRY_FAILED_REFRESH_ADDRESS = "1-17";
+
+    String REGISTRY_MISSING_METADATA_CONFIG_PORT = "1-18";
+
+    // cluster module 2-1 ~ 2-18
+    String CLUSTER_FAILED_SITE_SELECTION = "2-1";
+
+    String CLUSTER_NO_VALID_PROVIDER = "2-2";
+
+    String CLUSTER_FAILED_STOP = "2-3";
+
+    String CLUSTER_FAILED_LOAD_MERGER = "2-4";
+
+    String CLUSTER_FAILED_RESELECT_INVOKERS = "2-5";
+
+    String CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY = "2-6";
+
+    String CLUSTER_FAILED_EXEC_CONDITION_ROUTER = "2-7";
+
+    String CLUSTER_ERROR_RESPONSE = "2-8";
+
+    String CLUSTER_TIMER_RETRY_FAILED = "2-9";
+
+    String CLUSTER_FAILED_INVOKE_SERVICE = "2-10";
+
+    String CLUSTER_TAG_ROUTE_INVALID = "2-11";
+
+    String CLUSTER_TAG_ROUTE_EMPTY = "2-12";
+
+    String CLUSTER_FAILED_RECEIVE_RULE = "2-13";
+
+    String CLUSTER_SCRIPT_EXCEPTION = "2-14";
+
+    String CLUSTER_FAILED_RULE_PARSING = "2-15";
+
+    String CLUSTER_FAILED_MULTIPLE_RETRIES = "2-16";
+
+    String CLUSTER_FAILED_MOCK_REQUEST = "2-17";
+
+    String CLUSTER_NO_RULE_LISTENER = "2-18";
+
+    // proxy module 3-1
+    String PROXY_FAILED_CONVERT_URL = "3-1";
+
+    // protocol module 4-1 ~ 4-3
+    String PROTOCOL_UNSUPPORTED = "4-1";
+
+    String PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER = "4-2";
+
+    String PROTOCOL_FAILED_REFER_INVOKER = "4-3";
+
+    // config module 5-1 ~ 5-20
+    String CONFIG_FAILED_CONNECT_REGISTRY = "5-1";
+
+    String CONFIG_FAILED_SHUTDOWN_HOOK = "5-2";
+
+    String CONFIG_FAILED_DESTROY_INVOKER = "5-3";
+
+    String CONFIG_NO_METHOD_FOUND = "5-4";
+
+    String CONFIG_FAILED_LOAD_ENV_VARIABLE = "5-5";
+
+    String CONFIG_PROPERTY_CONFLICT = "5-6";
+
+    String CONFIG_UNEXPORT_ERROR = "5-7";
+
+    String CONFIG_USE_RANDOM_PORT = "5-8";
+
+    String CONFIG_FAILED_EXPORT_SERVICE = "5-9";
+
+    String CONFIG_SERVER_DISCONNECTED = "5-10";
+
+    String CONFIG_REGISTER_INSTANCE_ERROR = "5-11";
+
+    String CONFIG_REFRESH_INSTANCE_ERROR = "5-12";
+
+    String CONFIG_UNABLE_DESTROY_MODEL = "5-13";
+
+    String CONFIG_FAILED_START_MODEL = "5-14";
+
+    String CONFIG_FAILED_REFERENCE_MODEL = "5-15";
+
+    String CONFIG_FAILED_FIND_PROTOCOL = "5-16";
+
+    String CONFIG_PARAMETER_FORMAT_ERROR = "5-17";
+
+    String CONFIG_FAILED_NOTIFY_EVENT = "5-18";
+
+    String CONFIG_ZOOKEEPER_SERVER_ERROR = "5-19";
+
+    String CONFIG_STOP_DUBBO_ERROR = "5-20";
+
+    // transport module 6-1 ~ 6-2
+    String TRANSPORT_FAILED_CONNECT_PROVIDER = "6-1";
+
+    String TRANSPORT_CLIENT_CONNECT_TIMEOUT = "6-2";
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/MetricsConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/MetricsConstants.java
index a8fd9a9..04bb400 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/MetricsConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/MetricsConstants.java
@@ -20,9 +20,59 @@
 
     String PROTOCOL_PROMETHEUS = "prometheus";
 
-    String AGGREGATION_ENABLE = "aggregation.enable";
+    String TAG_IP = "ip";
 
-    String AGGREGATION_BUCKET_NUM = "aggregation.bucket.num";
+    String TAG_HOSTNAME = "hostname";
 
-    String AGGREGATION_TIME_WINDOW_SECONDS = "aggregation.time.window.seconds";
+    String TAG_APPLICATION_NAME = "application.name";
+
+    String TAG_INTERFACE_KEY = "interface";
+
+    String TAG_METHOD_KEY = "method";
+
+    String TAG_GROUP_KEY = "group";
+
+    String TAG_VERSION_KEY = "version";
+
+    String ENABLE_JVM_METRICS_KEY = "enable.jvm.metrics";
+
+    String AGGREGATION_COLLECTOR_KEY = "aggregation";
+
+    String AGGREGATION_ENABLED_KEY = "aggregation.enabled";
+
+    String AGGREGATION_BUCKET_NUM_KEY = "aggregation.bucket.num";
+
+    String AGGREGATION_TIME_WINDOW_SECONDS_KEY = "aggregation.time.window.seconds";
+
+    String PROMETHEUS_EXPORTER_ENABLED_KEY = "prometheus.exporter.enabled";
+
+    String PROMETHEUS_EXPORTER_ENABLE_HTTP_SERVICE_DISCOVERY_KEY = "prometheus.exporter.enable.http.service.discovery";
+
+    String PROMETHEUS_EXPORTER_HTTP_SERVICE_DISCOVERY_URL_KEY = "prometheus.exporter.http.service.discovery.url";
+
+    String PROMETHEUS_EXPORTER_METRICS_PORT_KEY = "prometheus.exporter.metrics.port";
+
+    String PROMETHEUS_EXPORTER_METRICS_PATH_KEY = "prometheus.exporter.metrics.path";
+
+    String PROMETHEUS_PUSHGATEWAY_ENABLED_KEY = "prometheus.pushgateway.enabled";
+
+    String PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY = "prometheus.pushgateway.base.url";
+
+    String PROMETHEUS_PUSHGATEWAY_USERNAME_KEY = "prometheus.pushgateway.username";
+
+    String PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY = "prometheus.pushgateway.password";
+
+    String PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY = "prometheus.pushgateway.push.interval";
+
+    String PROMETHEUS_PUSHGATEWAY_JOB_KEY = "prometheus.pushgateway.job";
+
+    int PROMETHEUS_DEFAULT_METRICS_PORT = 20888;
+
+    String PROMETHEUS_DEFAULT_METRICS_PATH = "/metrics";
+
+    int PROMETHEUS_DEFAULT_PUSH_INTERVAL = 30;
+
+    String PROMETHEUS_DEFAULT_JOB_NAME = "default_dubbo_job";
+
+    String METRIC_FILTER_START_TIME = "metric_filter_start_time";
 }
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 0e4f093..622780a 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
@@ -103,6 +103,13 @@
     String PROVIDED_BY = "provided-by";
 
     /**
+     * The provider tri port
+     *
+     * @since 3.1.0
+     */
+    String PROVIDER_PORT = "provider-port";
+
+    /**
      * The request size of service instances
      *
      * @since 2.7.5
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 2d84934..087d792 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
@@ -524,8 +524,9 @@
     }
 
     /**
-     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
-     * will be thrown.
+     * Find the extension with the given name.
+     *
+     * @throws IllegalStateException If the specified extension is not found.
      */
     public T getExtension(String name) {
         T extension = getExtension(name, true);
@@ -1074,30 +1075,23 @@
             List<String> newContentList = getResourceContent(resourceURL);
             String clazz;
             for (String line : newContentList) {
-                final int ci = line.indexOf('#');
-                if (ci >= 0) {
-                    line = line.substring(0, ci);
-                }
-                line = line.trim();
-                if (line.length() > 0) {
-                    try {
-                        String name = null;
-                        int i = line.indexOf('=');
-                        if (i > 0) {
-                            name = line.substring(0, i).trim();
-                            clazz = line.substring(i + 1).trim();
-                        } else {
-                            clazz = line;
-                        }
-                        if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages) && isIncluded(clazz, includedPackages)
-                            && !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
-                            loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
-                        }
-                    } catch (Throwable t) {
-                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type +
-                            ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
-                        exceptions.put(line, e);
+                try {
+                    String name = null;
+                    int i = line.indexOf('=');
+                    if (i > 0) {
+                        name = line.substring(0, i).trim();
+                        clazz = line.substring(i + 1).trim();
+                    } else {
+                        clazz = line;
                     }
+                    if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages) && isIncluded(clazz, includedPackages)
+                        && !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
+                        loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
+                    }
+                } catch (Throwable t) {
+                    IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type +
+                        ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
+                    exceptions.put(line, e);
                 }
             }
         } catch (Throwable t) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
index 8adc441..d8386f1 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
@@ -22,8 +22,10 @@
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.common.utils.ArrayUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -32,11 +34,16 @@
  */
 public class ActivateComparator implements Comparator<Class<?>> {
 
-    private final ExtensionDirector extensionDirector;
+    private final List<ExtensionDirector> extensionDirectors;
     private final Map<Class<?>, ActivateInfo> activateInfoMap = new ConcurrentHashMap<>();
 
     public ActivateComparator(ExtensionDirector extensionDirector) {
-        this.extensionDirector = extensionDirector;
+        extensionDirectors = new ArrayList<>();
+        extensionDirectors.add(extensionDirector);
+    }
+
+    public ActivateComparator(List<ExtensionDirector> extensionDirectors) {
+        this.extensionDirectors = extensionDirectors;
     }
 
     @Override
@@ -60,9 +67,16 @@
         ActivateInfo a2 = parseActivate(o2);
 
         if ((a1.applicableToCompare() || a2.applicableToCompare()) && inf != null) {
-            ExtensionLoader<?> extensionLoader = extensionDirector.getExtensionLoader(inf);
             if (a1.applicableToCompare()) {
-                String n2 = extensionLoader.getExtensionName(o2);
+                String n2 = null;
+                for (ExtensionDirector director : extensionDirectors) {
+                    ExtensionLoader<?> extensionLoader = director.getExtensionLoader(inf);
+                    n2 = extensionLoader.getExtensionName(o2);
+                    if (n2 != null) {
+                        break;
+                    }
+                }
+
                 if (a1.isLess(n2)) {
                     return -1;
                 }
@@ -73,7 +87,15 @@
             }
 
             if (a2.applicableToCompare()) {
-                String n1 = extensionLoader.getExtensionName(o1);
+                String n1 = null;
+                for (ExtensionDirector director : extensionDirectors) {
+                    ExtensionLoader<?> extensionLoader = director.getExtensionLoader(inf);
+                    n1 = extensionLoader.getExtensionName(o1);
+                    if (n1 != null) {
+                        break;
+                    }
+                }
+
                 if (a2.isLess(n1)) {
                     return 1;
                 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/MultiInstanceActivateComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/MultiInstanceActivateComparator.java
deleted file mode 100644
index 9d78914..0000000
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/MultiInstanceActivateComparator.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.dubbo.common.extension.support;
-
-import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.common.extension.ExtensionDirector;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.common.utils.ArrayUtils;
-
-import java.util.List;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.Arrays;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class MultiInstanceActivateComparator implements Comparator<Class<?>> {
-
-    private final List<ExtensionDirector> extensionDirectors;
-    private final Map<Class<?>, ActivateInfo> activateInfoMap = new ConcurrentHashMap<>();
-
-    public MultiInstanceActivateComparator(List<ExtensionDirector> extensionDirectors) {
-        this.extensionDirectors = extensionDirectors;
-    }
-
-    @Override
-    public int compare(Class o1, Class o2) {
-        if (o1 == null && o2 == null) {
-            return 0;
-        }
-        if (o1 == null) {
-            return -1;
-        }
-        if (o2 == null) {
-            return 1;
-        }
-        if (o1.equals(o2)) {
-            return 0;
-        }
-
-        Class<?> inf = findSpi(o1);
-
-        ActivateInfo a1 = parseActivate(o1);
-        ActivateInfo a2 = parseActivate(o2);
-
-        if ((a1.applicableToCompare() || a2.applicableToCompare()) && inf != null) {
-
-
-            if (a1.applicableToCompare()) {
-                String n2 = null;
-                for (ExtensionDirector director : extensionDirectors) {
-                    ExtensionLoader<?> extensionLoader = director.getExtensionLoader(inf);
-                    n2 = extensionLoader.getExtensionName(o2);
-                    if (n2 != null) {
-                        break;
-                    }
-                }
-                if (a1.isLess(n2)) {
-                    return -1;
-                }
-
-                if (a1.isMore(n2)) {
-                    return 1;
-                }
-            }
-
-            if (a2.applicableToCompare()) {
-                String n1 = null;
-                for (ExtensionDirector director : extensionDirectors) {
-                    ExtensionLoader<?> extensionLoader = director.getExtensionLoader(inf);
-                    n1 = extensionLoader.getExtensionName(o1);
-                    if (n1 != null) {
-                        break;
-                    }
-                }
-
-                if (a2.isLess(n1)) {
-                    return 1;
-                }
-
-                if (a2.isMore(n1)) {
-                    return -1;
-                }
-            }
-
-            return a1.order > a2.order ? 1 : -1;
-        }
-
-        // In order to avoid the problem of inconsistency between the loading order of two filters
-        // in different loading scenarios without specifying the order attribute of the filter,
-        // when the order is the same, compare its filterName
-        if (a1.order > a2.order) {
-            return 1;
-        } else if (a1.order == a2.order) {
-            return o1.getSimpleName().compareTo(o2.getSimpleName()) > 0 ? 1 : -1;
-        } else {
-            return -1;
-        }
-    }
-
-    private Class<?> findSpi(Class<?> clazz) {
-        if (clazz.getInterfaces().length == 0) {
-            return null;
-        }
-
-        for (Class<?> intf : clazz.getInterfaces()) {
-            if (intf.isAnnotationPresent(SPI.class)) {
-                return intf;
-            }
-            Class<?> result = findSpi(intf);
-            if (result != null) {
-                return result;
-            }
-        }
-
-        return null;
-    }
-
-    private ActivateInfo parseActivate(Class<?> clazz) {
-        ActivateInfo info = activateInfoMap.get(clazz);
-        if (info != null) {
-            return info;
-        }
-        info = new ActivateInfo();
-        if (clazz.isAnnotationPresent(Activate.class)) {
-            Activate activate = clazz.getAnnotation(Activate.class);
-            info.before = activate.before();
-            info.after = activate.after();
-            info.order = activate.order();
-        } else if (clazz.isAnnotationPresent(com.alibaba.dubbo.common.extension.Activate.class)) {
-            com.alibaba.dubbo.common.extension.Activate activate = clazz.getAnnotation(
-                com.alibaba.dubbo.common.extension.Activate.class);
-            info.before = activate.before();
-            info.after = activate.after();
-            info.order = activate.order();
-        } else {
-            info.order = 0;
-        }
-        activateInfoMap.put(clazz, info);
-        return info;
-    }
-
-    private static class ActivateInfo {
-        private String[] before;
-        private String[] after;
-        private int order;
-
-        private boolean applicableToCompare() {
-            return ArrayUtils.isNotEmpty(before) || ArrayUtils.isNotEmpty(after);
-        }
-
-        private boolean isLess(String name) {
-            return Arrays.asList(before).contains(name);
-        }
-
-        private boolean isMore(String name) {
-            return Arrays.asList(after).contains(name);
-        }
-    }
-}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/ErrorTypeAwareLogger.java b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/ErrorTypeAwareLogger.java
new file mode 100644
index 0000000..fe9ebcc
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/ErrorTypeAwareLogger.java
@@ -0,0 +1,66 @@
+/*
+ * 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.logger;
+
+/**
+ * Logger interface with the ability of displaying solution of different types of error.
+ */
+public interface ErrorTypeAwareLogger extends Logger {
+
+    /**
+     * Logs a message with warn log level.
+     *
+     * @param code error code
+     * @param cause error cause
+     * @param extendedInformation extended information
+     * @param msg log this message
+     */
+    void warn(String code, String cause, String extendedInformation, String msg);
+
+    /**
+     * Logs a message with warn log level.
+     *
+     * @param code error code
+     * @param cause error cause
+     * @param extendedInformation extended information
+     * @param msg log this message
+     * @param e log this cause
+     */
+    void warn(String code, String cause, String extendedInformation, String msg, Throwable e);
+
+    /**
+     * Logs a message with error log level.
+     *
+     * @param code error code
+     * @param cause error cause
+     * @param extendedInformation extended information
+     * @param msg log this message
+     */
+    void error(String code, String cause, String extendedInformation, String msg);
+
+    /**
+     * Logs a message with error log level.
+     *
+     * @param code error code
+     * @param cause error cause
+     * @param extendedInformation extended information
+     * @param msg log this message
+     * @param e log this cause
+     */
+    void error(String code, String cause, String extendedInformation, String msg, Throwable e);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/LoggerFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/LoggerFactory.java
index 9f787c5..91c0e60 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/LoggerFactory.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/LoggerFactory.java
@@ -21,6 +21,7 @@
 import org.apache.dubbo.common.logger.log4j.Log4jLoggerAdapter;
 import org.apache.dubbo.common.logger.log4j2.Log4j2LoggerAdapter;
 import org.apache.dubbo.common.logger.slf4j.Slf4jLoggerAdapter;
+import org.apache.dubbo.common.logger.support.FailsafeErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.support.FailsafeLogger;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 
@@ -39,6 +40,7 @@
 public class LoggerFactory {
 
     private static final ConcurrentMap<String, FailsafeLogger> LOGGERS = new ConcurrentHashMap<>();
+    private static final ConcurrentMap<String, FailsafeErrorTypeAwareLogger> ERROR_TYPE_AWARE_LOGGERS = new ConcurrentHashMap<>();
     private static volatile LoggerAdapter LOGGER_ADAPTER;
 
     // search common-used logging frameworks
@@ -128,6 +130,26 @@
     }
 
     /**
+     * Get error type aware logger by Class object.
+     *
+     * @param key the returned logger will be named after clazz
+     * @return error type aware logger
+     */
+    public static ErrorTypeAwareLogger getErrorTypeAwareLogger(Class<?> key) {
+        return ERROR_TYPE_AWARE_LOGGERS.computeIfAbsent(key.getName(), name -> new FailsafeErrorTypeAwareLogger(LOGGER_ADAPTER.getLogger(name)));
+    }
+
+    /**
+     * Get error type aware logger by a String key.
+     *
+     * @param key the returned logger will be named after key
+     * @return error type aware logger
+     */
+    public static ErrorTypeAwareLogger getErrorTypeAwareLogger(String key) {
+        return ERROR_TYPE_AWARE_LOGGERS.computeIfAbsent(key, k -> new FailsafeErrorTypeAwareLogger(LOGGER_ADAPTER.getLogger(k)));
+    }
+
+    /**
      * Get logging level
      *
      * @return logging level
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLogger.java b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLogger.java
new file mode 100644
index 0000000..8438c0e
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLogger.java
@@ -0,0 +1,127 @@
+/*
+ * 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.logger.support;
+
+import org.apache.dubbo.common.Version;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.utils.NetUtils;
+
+import java.util.regex.Pattern;
+
+/**
+ * A fail-safe (ignoring exception thrown by logger) wrapper of error type aware logger.
+ */
+public class FailsafeErrorTypeAwareLogger extends FailsafeLogger implements ErrorTypeAwareLogger {
+
+    /**
+     * Template address for formatting.
+     */
+    private static final String INSTRUCTIONS_URL = "https://dubbo.apache.org/faq/%d/%d";
+
+    private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("\\d+-\\d+");
+
+    public FailsafeErrorTypeAwareLogger(Logger logger) {
+        super(logger);
+    }
+
+    private String appendContextMessageWithInstructions(String code, String cause, String extendedInformation, String msg) {
+        return " [DUBBO] " + msg + ", dubbo version: " + Version.getVersion() +
+            ", current host: " + NetUtils.getLocalHost() + ", error code: " + code +
+            ". This may be caused by " + cause + ", " +
+            "go to " + getErrorUrl(code) + " to find instructions. " + extendedInformation;
+    }
+
+    private String getErrorUrl(String code) {
+
+        String trimmedString = code.trim();
+
+        if (!ERROR_CODE_PATTERN.matcher(trimmedString).matches()) {
+            error("Invalid error code: " + code + ", the format of error code is: X-X (where X is a number).");
+            return "";
+        }
+
+        String[] segments = trimmedString.split("[-]");
+
+        int[] errorCodeSegments = new int[2];
+
+        try {
+            errorCodeSegments[0] = Integer.parseInt(segments[0]);
+            errorCodeSegments[1] = Integer.parseInt(segments[1]);
+        } catch (NumberFormatException numberFormatException) {
+            error("Invalid error code: " + code + ", the format of error code is: X-X (where X is a number).",
+                numberFormatException);
+
+            return "";
+        }
+
+        return String.format(INSTRUCTIONS_URL, errorCodeSegments[0], errorCodeSegments[1]);
+    }
+
+    @Override
+    public void warn(String code, String cause, String extendedInformation, String msg) {
+        if (getDisabled()) {
+            return;
+        }
+
+        try {
+            getLogger().warn(appendContextMessageWithInstructions(code, cause, extendedInformation, msg));
+        } catch (Throwable t) {
+            // ignored.
+        }
+    }
+
+    @Override
+    public void warn(String code, String cause, String extendedInformation, String msg, Throwable e) {
+        if (getDisabled()) {
+            return;
+        }
+
+        try {
+            getLogger().warn(appendContextMessageWithInstructions(code, cause, extendedInformation, msg), e);
+        } catch (Throwable t) {
+            // ignored.
+        }
+    }
+
+    @Override
+    public void error(String code, String cause, String extendedInformation, String msg) {
+        if (getDisabled()) {
+            return;
+        }
+
+        try {
+            getLogger().error(appendContextMessageWithInstructions(code, cause, extendedInformation, msg));
+        } catch (Throwable t) {
+            // ignored.
+        }
+    }
+
+    @Override
+    public void error(String code, String cause, String extendedInformation, String msg, Throwable e) {
+        if (getDisabled()) {
+            return;
+        }
+
+        try {
+            getLogger().error(appendContextMessageWithInstructions(code, cause, extendedInformation, msg), e);
+        } catch (Throwable t) {
+            // ignored.
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeLogger.java b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeLogger.java
index ff6804b..66e369f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeLogger.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/logger/support/FailsafeLogger.java
@@ -34,6 +34,10 @@
         FailsafeLogger.disabled = disabled;
     }
 
+    static boolean getDisabled() {
+        return disabled;
+    }
+
     public Logger getLogger() {
         return logger;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporter.java
new file mode 100644
index 0000000..b07af77
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporter.java
@@ -0,0 +1,30 @@
+/*
+ * 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.metrics;
+
+/**
+ * Metrics Reporter.
+ * Report metrics to specific metrics server(e.g. Prometheus).
+ */
+public interface MetricsReporter {
+
+    /**
+     * Initialize metrics reporter.
+     */
+    void init();
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporterFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporterFactory.java
new file mode 100644
index 0000000..ade70ca
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/MetricsReporterFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Adaptive;
+import org.apache.dubbo.common.extension.ExtensionScope;
+import org.apache.dubbo.common.extension.SPI;
+
+/**
+ * The factory interface to create the instance of {@link MetricsReporter}.
+ */
+@SPI(value = "nop", scope = ExtensionScope.APPLICATION)
+public interface MetricsReporterFactory {
+
+    /**
+     * Create metrics reporter.
+     *
+     * @param url URL
+     * @return Metrics reporter implementation.
+     */
+    @Adaptive({"protocol"})
+    MetricsReporter createMetricsReporter(URL url);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollector.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollector.java
new file mode 100644
index 0000000..6e5ba2d
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollector.java
@@ -0,0 +1,171 @@
+/*
+ * 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.metrics.collector;
+
+import static org.apache.dubbo.common.metrics.model.MetricsCategory.REQUESTS;
+import static org.apache.dubbo.common.metrics.model.MetricsCategory.RT;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.dubbo.common.metrics.collector.stat.MetricsStatComposite;
+import org.apache.dubbo.common.metrics.collector.stat.MetricsStatHandler;
+import org.apache.dubbo.common.metrics.event.RequestEvent;
+import org.apache.dubbo.common.metrics.listener.MetricsListener;
+import org.apache.dubbo.common.metrics.model.MetricsKey;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+/**
+ * Default implementation of {@link MetricsCollector}
+ */
+public class DefaultMetricsCollector implements MetricsCollector {
+
+    private AtomicBoolean collectEnabled = new AtomicBoolean(false);
+    private final List<MetricsListener> listeners = new ArrayList<>();
+    private final ApplicationModel applicationModel;
+    private final MetricsStatComposite stats;
+
+    public DefaultMetricsCollector(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        this.stats = new MetricsStatComposite(applicationModel.getApplicationName(), this);
+    }
+
+    public void setCollectEnabled(Boolean collectEnabled) {
+        this.collectEnabled.compareAndSet(isCollectEnabled(), collectEnabled);
+    }
+
+    public Boolean isCollectEnabled() {
+        return collectEnabled.get();
+    }
+
+    public void addListener(MetricsListener listener) {
+        listeners.add(listener);
+    }
+
+    public List<MetricsListener> getListener() {
+        return this.listeners;
+    }
+
+    public void increaseTotalRequests(String interfaceName, String methodName, String group, String version) {
+        doExecute(RequestEvent.Type.TOTAL,statHandler-> {
+            statHandler.increase(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void increaseSucceedRequests(String interfaceName, String methodName, String group, String version) {
+        doExecute(RequestEvent.Type.SUCCEED,statHandler->{
+            statHandler.increase(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void increaseFailedRequests(String interfaceName,
+                                       String methodName,
+                                       String group,
+                                       String version) {
+        doExecute(RequestEvent.Type.FAILED,statHandler->{
+            statHandler.increase(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void businessFailedRequests(String interfaceName, String methodName, String group, String version) {
+        doExecute(RequestEvent.Type.BUSINESS_FAILED,statHandler->{
+            statHandler.increase(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void increaseProcessingRequests(String interfaceName, String methodName, String group, String version) {
+        doExecute(RequestEvent.Type.PROCESSING,statHandler-> {
+            statHandler.increase(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void decreaseProcessingRequests(String interfaceName, String methodName, String group, String version) {
+        doExecute(RequestEvent.Type.PROCESSING,statHandler-> {
+            statHandler.decrease(interfaceName, methodName, group, version);
+        });
+    }
+
+    public void addRT(String interfaceName, String methodName, String group, String version, Long responseTime) {
+        stats.addRT(interfaceName, methodName, group, version, responseTime);
+    }
+
+    @Override
+    public List<MetricSample> collect() {
+        List<MetricSample> list = new ArrayList<>();
+        collectRequests(list);
+        collectRT(list);
+
+        return list;
+    }
+
+    private void collectRequests(List<MetricSample> list) {
+        doExecute(RequestEvent.Type.TOTAL, MetricsStatHandler::get).filter(e->!e.isEmpty())
+            .ifPresent(map-> map.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_TOTAL, k.getTags(), REQUESTS, v::get))));
+
+        doExecute(RequestEvent.Type.SUCCEED, MetricsStatHandler::get).filter(e->!e.isEmpty())
+            .ifPresent(map-> map.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_SUCCEED, k.getTags(), REQUESTS, v::get))));
+
+        doExecute(RequestEvent.Type.FAILED, MetricsStatHandler::get).filter(e->!e.isEmpty())
+            .ifPresent(map->{
+                map.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_FAILED, k.getTags(), REQUESTS, v::get)));
+            });
+
+        doExecute(RequestEvent.Type.PROCESSING, MetricsStatHandler::get).filter(e->!e.isEmpty())
+            .ifPresent(map-> map.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_PROCESSING, k.getTags(), REQUESTS, v::get))));
+
+        doExecute(RequestEvent.Type.BUSINESS_FAILED, MetricsStatHandler::get).filter(e->!e.isEmpty())
+            .ifPresent(map-> map.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUEST_BUSINESS_FAILED, k.getTags(), REQUESTS, v::get))));
+    }
+
+    private void collectRT(List<MetricSample> list) {
+        this.stats.getLastRT().forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_LAST, k.getTags(), RT, v::get)));
+        this.stats.getMinRT().forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_MIN, k.getTags(), RT, v::get)));
+        this.stats.getMaxRT().forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_MAX, k.getTags(), RT, v::get)));
+
+        this.stats.getTotalRT().forEach((k, v) -> {
+            list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_TOTAL, k.getTags(), RT, v::get));
+
+            AtomicLong avg = this.stats.getAvgRT().get(k);
+            AtomicLong count = this.stats.getRtCount().get(k);
+            avg.set(v.get() / count.get());
+            list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_AVG, k.getTags(), RT, avg::get));
+        });
+    }
+    private <T> Optional<T> doExecute(RequestEvent.Type requestType, Function<MetricsStatHandler,T> statExecutor) {
+        if (isCollectEnabled()) {
+            MetricsStatHandler handler = stats.getHandler(requestType);
+            T result =  statExecutor.apply(handler);
+            return Optional.ofNullable(result);
+        }
+        return Optional.empty();
+    }
+
+    private void doExecute(RequestEvent.Type requestType, Consumer<MetricsStatHandler> statExecutor) {
+        if (isCollectEnabled()) {
+            MetricsStatHandler handler = stats.getHandler(requestType);
+             statExecutor.accept(handler);
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/MetricsCollector.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/MetricsCollector.java
new file mode 100644
index 0000000..12eaa2a
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/MetricsCollector.java
@@ -0,0 +1,36 @@
+/*
+ * 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.metrics.collector;
+
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+
+import java.util.List;
+
+/**
+ * Metrics Collector.
+ * An interface of collector to collect framework internal metrics.
+ */
+public interface MetricsCollector {
+
+    /**
+     * Collect metrics as {@link MetricSample}
+     *
+     * @return List of MetricSample
+     */
+    List<MetricSample> collect();
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/DefaultMetricsStatHandler.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/DefaultMetricsStatHandler.java
new file mode 100644
index 0000000..1670b9d
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/DefaultMetricsStatHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.metrics.collector.stat;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+
+
+public class DefaultMetricsStatHandler implements MetricsStatHandler {
+
+    private final String applicationName;
+    private final Map<MethodMetric, AtomicLong> counts = new ConcurrentHashMap<>();
+
+    public DefaultMetricsStatHandler(String applicationName) {
+        this.applicationName = applicationName;
+    }
+
+    @Override
+    public void increase(String interfaceName, String methodName, String group, String version) {
+        this.doIncrExecute(interfaceName,methodName,group,version);
+    }
+
+    public void decrease(String interfaceName, String methodName, String group, String version){
+        this.doDecrExecute(interfaceName,methodName,group,version);
+    }
+
+    protected void doExecute(String interfaceName, String methodName, String group, String version, BiConsumer<MethodMetric,Map<MethodMetric, AtomicLong>> execute){
+        MethodMetric metric = new MethodMetric(applicationName, interfaceName, methodName, group, version);
+        execute.accept(metric,counts);
+
+        this.doNotify(metric);
+    }
+
+    protected void doIncrExecute(String interfaceName, String methodName, String group, String version){
+        this.doExecute(interfaceName,methodName,group,version,(metric,counts)->{
+            AtomicLong count = counts.computeIfAbsent(metric, k -> new AtomicLong(0L));
+            count.incrementAndGet();
+
+        });
+    }
+
+    protected void doDecrExecute(String interfaceName, String methodName, String group, String version){
+        this.doExecute(interfaceName,methodName,group,version,(metric,counts)->{
+            AtomicLong count = counts.computeIfAbsent(metric, k -> new AtomicLong(0L));
+            count.decrementAndGet();
+        });
+    }
+
+    @Override
+    public Map<MethodMetric, AtomicLong> get() {
+        return counts;
+    }
+
+    public  void doNotify(MethodMetric metric){}
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatComposite.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatComposite.java
new file mode 100644
index 0000000..d86a206
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatComposite.java
@@ -0,0 +1,138 @@
+/*
+ * 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.metrics.collector.stat;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.LongAccumulator;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.event.MetricsEvent;
+import org.apache.dubbo.common.metrics.event.RTEvent;
+import org.apache.dubbo.common.metrics.event.RequestEvent;
+import org.apache.dubbo.common.metrics.listener.MetricsListener;
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+public class MetricsStatComposite{
+
+    public Map<RequestEvent.Type, MetricsStatHandler> stats = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, AtomicLong>     lastRT = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, LongAccumulator> minRT  = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, LongAccumulator> maxRT  = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, AtomicLong> avgRT = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, AtomicLong> totalRT = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, AtomicLong> rtCount = new ConcurrentHashMap<>();
+    private final String applicationName;
+    private final List<MetricsListener> listeners;
+    private DefaultMetricsCollector collector;
+
+    public MetricsStatComposite(String applicationName, DefaultMetricsCollector collector){
+        this.applicationName = applicationName;
+        this.listeners = collector.getListener();
+        this.collector = collector;
+        this.init();
+    }
+
+    public MetricsStatHandler getHandler(RequestEvent.Type statType) {
+        return stats.get(statType);
+    }
+
+    public Map<MethodMetric, AtomicLong> getLastRT(){
+        return this.lastRT;
+    }
+    public Map<MethodMetric, LongAccumulator> getMinRT(){
+        return this.minRT;
+    }
+
+    public Map<MethodMetric, LongAccumulator> getMaxRT(){
+        return this.maxRT;
+    }
+    public Map<MethodMetric, AtomicLong> getAvgRT(){
+        return this.avgRT;
+    }
+    public Map<MethodMetric, AtomicLong> getTotalRT(){
+        return this.totalRT;
+    }
+    public Map<MethodMetric, AtomicLong> getRtCount(){
+        return this.rtCount;
+    }
+
+    public void addRT(String interfaceName, String methodName, String group, String version, Long responseTime) {
+        if (collector.isCollectEnabled()) {
+            MethodMetric metric = new MethodMetric(applicationName, interfaceName, methodName, group, version);
+
+            AtomicLong last = lastRT.computeIfAbsent(metric, k -> new AtomicLong());
+            last.set(responseTime);
+
+            LongAccumulator min = minRT.computeIfAbsent(metric, k -> new LongAccumulator(Long::min, Long.MAX_VALUE));
+            min.accumulate(responseTime);
+
+            LongAccumulator max = maxRT.computeIfAbsent(metric, k -> new LongAccumulator(Long::max, Long.MIN_VALUE));
+            max.accumulate(responseTime);
+
+            AtomicLong total = totalRT.computeIfAbsent(metric, k -> new AtomicLong());
+            total.addAndGet(responseTime);
+
+            AtomicLong count = rtCount.computeIfAbsent(metric, k -> new AtomicLong());
+            count.incrementAndGet();
+
+            avgRT.computeIfAbsent(metric, k -> new AtomicLong());
+
+            publishEvent(new RTEvent(metric, responseTime));
+        }
+    }
+
+    private void init() {
+        stats.put(RequestEvent.Type.TOTAL, new DefaultMetricsStatHandler(applicationName){
+            @Override
+            public void doNotify(MethodMetric metric) {
+                publishEvent(new RequestEvent(metric, RequestEvent.Type.TOTAL));
+            }
+        });
+
+        stats.put(RequestEvent.Type.SUCCEED, new DefaultMetricsStatHandler(applicationName) {
+            @Override
+            public void doNotify(MethodMetric metric) {
+                publishEvent(new RequestEvent(metric, RequestEvent.Type.SUCCEED));
+            }
+        });
+
+        stats.put(RequestEvent.Type.FAILED, new DefaultMetricsStatHandler(applicationName) {
+            @Override
+            public void doNotify(MethodMetric metric) {
+                publishEvent(new RequestEvent(metric, RequestEvent.Type.FAILED));
+            }
+        });
+
+        stats.put(RequestEvent.Type.BUSINESS_FAILED, new DefaultMetricsStatHandler(applicationName) {
+            @Override
+            public void doNotify(MethodMetric metric) {
+                publishEvent(new RequestEvent(metric, RequestEvent.Type.BUSINESS_FAILED));
+            }
+        });
+
+        stats.put(RequestEvent.Type.PROCESSING, new DefaultMetricsStatHandler(applicationName));
+    }
+
+    private void publishEvent(MetricsEvent event) {
+        for (MetricsListener listener : listeners) {
+            listener.onEvent(event);
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatHandler.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatHandler.java
new file mode 100644
index 0000000..d39c5d4
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/collector/stat/MetricsStatHandler.java
@@ -0,0 +1,29 @@
+/*
+ * 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.metrics.collector.stat;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+
+public interface MetricsStatHandler {
+    Map<MethodMetric, AtomicLong> get();
+    void increase(String interfaceName, String methodName, String group, String version);
+    void decrease(String interfaceName, String methodName, String group, String version);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/MetricsEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/MetricsEvent.java
new file mode 100644
index 0000000..3b009db
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/MetricsEvent.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.common.metrics.event;
+
+/**
+ * BaseMetricsEvent.
+ */
+public abstract class MetricsEvent {
+
+    /**
+     * Metric object. (eg. {@link org.apache.dubbo.common.metrics.model.MethodMetric})
+     */
+    protected transient Object source;
+
+    public MetricsEvent(Object source) {
+        if (source == null) {
+            throw new IllegalArgumentException("null source");
+        }
+
+        this.source = source;
+    }
+
+    public Object getSource() {
+        return source;
+    }
+
+    public String toString() {
+        return getClass().getName() + "[source=" + source + "]";
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RTEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RTEvent.java
new file mode 100644
index 0000000..68887dc
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RTEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.common.metrics.event;
+
+/**
+ * RtEvent.
+ */
+public class RTEvent extends MetricsEvent {
+    private Long rt;
+
+    public RTEvent(Object source, Long rt) {
+        super(source);
+        this.rt = rt;
+    }
+
+    public Long getRt() {
+        return rt;
+    }
+
+    public void setRt(Long rt) {
+        this.rt = rt;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RequestEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RequestEvent.java
new file mode 100644
index 0000000..f0a6677
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/event/RequestEvent.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.event;
+
+/**
+ * RequestEvent.
+ */
+public class RequestEvent extends MetricsEvent {
+    private Type type;
+
+    public RequestEvent(Object source, Type type) {
+        super(source);
+        this.type = type;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public enum Type {
+        TOTAL,
+        SUCCEED,
+        FAILED,
+        BUSINESS_FAILED,
+
+        PROCESSING
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/listener/MetricsListener.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/listener/MetricsListener.java
new file mode 100644
index 0000000..0f55e9b
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/listener/MetricsListener.java
@@ -0,0 +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.common.metrics.listener;
+
+import org.apache.dubbo.common.metrics.event.MetricsEvent;
+
+/**
+ * Metrics Listener.
+ */
+public interface MetricsListener {
+
+    /**
+     * notify event.
+     *
+     * @param event BaseMetricsEvent
+     */
+    void onEvent(MetricsEvent event);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MethodMetric.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MethodMetric.java
new file mode 100644
index 0000000..3e3f75a
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MethodMetric.java
@@ -0,0 +1,124 @@
+/*
+ * 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.metrics.model;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_IP;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_HOSTNAME;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_APPLICATION_NAME;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_METHOD_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_VERSION_KEY;
+import static org.apache.dubbo.common.utils.NetUtils.getLocalHost;
+import static org.apache.dubbo.common.utils.NetUtils.getLocalHostName;
+
+/**
+ * Metric class for method.
+ */
+public class MethodMetric {
+    private String applicationName;
+    private String interfaceName;
+    private String methodName;
+    private String group;
+    private String version;
+
+    public MethodMetric() {
+
+    }
+
+    public MethodMetric(String applicationName, String interfaceName, String methodName, String group, String version) {
+        this.applicationName = applicationName;
+        this.interfaceName = interfaceName;
+        this.methodName = methodName;
+        this.group = group;
+        this.version = version;
+    }
+
+    public String getInterfaceName() {
+        return interfaceName;
+    }
+
+    public void setInterfaceName(String interfaceName) {
+        this.interfaceName = interfaceName;
+    }
+
+    public String getMethodName() {
+        return methodName;
+    }
+
+    public void setMethodName(String methodName) {
+        this.methodName = methodName;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public Map<String, String> getTags() {
+        Map<String, String> tags = new HashMap<>();
+        tags.put(TAG_IP, getLocalHost());
+        tags.put(TAG_HOSTNAME, getLocalHostName());
+        tags.put(TAG_APPLICATION_NAME, applicationName);
+
+        tags.put(TAG_INTERFACE_KEY, interfaceName);
+        tags.put(TAG_METHOD_KEY, methodName);
+        tags.put(TAG_GROUP_KEY, group);
+        tags.put(TAG_VERSION_KEY, version);
+        return tags;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MethodMetric that = (MethodMetric) o;
+        return Objects.equals(interfaceName, that.interfaceName) && Objects.equals(methodName, that.methodName)
+            && Objects.equals(group, that.group) && Objects.equals(version, that.version);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(interfaceName, methodName, group, version);
+    }
+
+    @Override
+    public String toString() {
+        return "MethodMetric{" +
+            "interfaceName='" + interfaceName + '\'' +
+            ", methodName='" + methodName + '\'' +
+            ", group='" + group + '\'' +
+            ", version='" + version + '\'' +
+            '}';
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsCategory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsCategory.java
new file mode 100644
index 0000000..700a47f
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsCategory.java
@@ -0,0 +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.common.metrics.model;
+
+/**
+ * Metric category.
+ */
+public enum MetricsCategory {
+    RT,
+    QPS,
+    REQUESTS,
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsKey.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsKey.java
new file mode 100644
index 0000000..bbb1bd8
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/MetricsKey.java
@@ -0,0 +1,58 @@
+/*
+ * 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.metrics.model;
+
+public enum MetricsKey {
+
+    METRIC_REQUESTS_TOTAL("requests.total", "Total Requests"),
+    METRIC_REQUESTS_SUCCEED("requests.succeed", "Succeed Requests"),
+    METRIC_REQUESTS_FAILED("requests.failed", "Failed Requests"),
+    METRIC_REQUEST_BUSINESS_FAILED("requests.business.failed","Failed Business Requests"),
+    METRIC_REQUESTS_PROCESSING("requests.processing", "Processing Requests"),
+
+    METRIC_REQUESTS_TOTAL_AGG("requests.total.aggregate", "Aggregated Total Requests"),
+    METRIC_REQUESTS_SUCCEED_AGG("requests.succeed.aggregate", "Aggregated Succeed Requests"),
+    METRIC_REQUESTS_FAILED_AGG("requests.failed.aggregate", "Aggregated Failed Requests"),
+    METRIC_REQUESTS_BUSINESS_FAILED_AGG("requests.business.failed.aggregate", "Aggregated Business Failed Requests"),
+
+    METRIC_QPS("qps", "Query Per Seconds"),
+    METRIC_RT_LAST("rt.last", "Last Response Time"),
+    METRIC_RT_MIN("rt.min", "Min Response Time"),
+    METRIC_RT_MAX("rt.max", "Max Response Time"),
+    METRIC_RT_TOTAL("rt.total", "Total Response Time"),
+    METRIC_RT_AVG("rt.avg", "Average Response Time"),
+    METRIC_RT_P99("rt.p99", "Response Time P99"),
+    METRIC_RT_P95("rt.p95", "Response Time P95"),
+    ;
+
+    private final String name;
+    private final String description;
+
+    public final String getName() {
+        return this.name;
+    }
+
+    public final String getDescription() {
+        return this.description;
+    }
+
+    MetricsKey(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSample.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSample.java
new file mode 100644
index 0000000..1bdb2aa
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSample.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metrics.model.sample;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+import org.apache.dubbo.common.metrics.model.MetricsKey;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * GaugeMetricSample.
+ */
+public class GaugeMetricSample extends MetricSample {
+
+    private Supplier<Number> supplier;
+
+    public GaugeMetricSample(MetricsKey metricsKey, Map<String, String> tags, MetricsCategory category, Supplier<Number> supplier) {
+        super(metricsKey.getName(), metricsKey.getDescription(), tags, Type.GAUGE, category);
+        this.supplier = supplier;
+    }
+
+    public GaugeMetricSample(String name, String description, Map<String, String> tags, MetricsCategory category, String baseUnit, Supplier<Number> supplier) {
+        super(name, description, tags, Type.GAUGE, category, baseUnit);
+        this.supplier = supplier;
+    }
+
+    public Supplier<Number> getSupplier() {
+        return supplier;
+    }
+
+    public void setSupplier(Supplier<Number> supplier) {
+        this.supplier = supplier;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/MetricSample.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/MetricSample.java
new file mode 100644
index 0000000..21fe5e0
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/model/sample/MetricSample.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.common.metrics.model.sample;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * MetricSample.
+ */
+public class MetricSample {
+    private String name;
+    private String description;
+    private Map<String, String> tags;
+    private Type type;
+    private MetricsCategory category;
+    private String baseUnit;
+
+    public MetricSample(String name, String description, Map<String, String> tags, Type type, MetricsCategory category) {
+        this(name, description, tags, type, category, null);
+    }
+
+    public MetricSample(String name, String description, Map<String, String> tags, Type type, MetricsCategory category, String baseUnit) {
+        this.name = name;
+        this.description = description;
+        this.tags = tags;
+        this.type = type;
+        this.category = category;
+        this.baseUnit = baseUnit;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Map<String, String> getTags() {
+        return tags;
+    }
+
+    public void setTags(Map<String, String> tags) {
+        this.tags = tags;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public MetricsCategory getCategory() {
+        return category;
+    }
+
+    public void setCategory(MetricsCategory category) {
+        this.category = category;
+    }
+
+    public String getBaseUnit() {
+        return baseUnit;
+    }
+
+    public void setBaseUnit(String baseUnit) {
+        this.baseUnit = baseUnit;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MetricSample that = (MetricSample) o;
+        return Objects.equals(name, that.name) && Objects.equals(description, that.description)
+            && Objects.equals(baseUnit, that.baseUnit) && type == that.type
+            && Objects.equals(category, that.category) && Objects.equals(tags, that.tags);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, description, baseUnit, type, category, tags);
+    }
+
+    @Override
+    public String toString() {
+        return "MetricSample{" +
+            "name='" + name + '\'' +
+            ", description='" + description + '\'' +
+            ", baseUnit='" + baseUnit + '\'' +
+            ", type=" + type +
+            ", category=" + category +
+            ", tags=" + tags +
+            '}';
+    }
+
+    public enum Type {
+        COUNTER,
+        GAUGE,
+        LONG_TASK_TIMER,
+        TIMER,
+        DISTRIBUTION_SUMMARY
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporter.java
new file mode 100644
index 0000000..07e8c6c
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.metrics.nop;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+
+/**
+ * Metrics reporter without any operations.
+ */
+public class NopMetricsReporter implements MetricsReporter {
+
+    public NopMetricsReporter(URL url) {
+
+    }
+
+    @Override
+    public void init() {
+
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporterFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporterFactory.java
new file mode 100644
index 0000000..462de5e
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/nop/NopMetricsReporterFactory.java
@@ -0,0 +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.common.metrics.nop;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.common.metrics.MetricsReporterFactory;
+
+/**
+ * MetricsReporterFactory to create NopMetricsReporter.
+ */
+public class NopMetricsReporterFactory implements MetricsReporterFactory {
+
+    @Override
+    public MetricsReporter createMetricsReporter(URL url) {
+        return new NopMetricsReporter(url);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsEntity.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsEntity.java
new file mode 100644
index 0000000..7c64e29
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsEntity.java
@@ -0,0 +1,91 @@
+/*
+ * 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.metrics.service;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Metrics response entity.
+ */
+public class MetricsEntity {
+
+    private String name;
+    private Map<String, String> tags;
+    private MetricsCategory category;
+    private Object value;
+
+    public MetricsEntity() {
+
+    }
+
+    public MetricsEntity(String name, Map<String, String> tags, MetricsCategory category, Object value) {
+        this.name = name;
+        this.tags = tags;
+        this.category = category;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Map<String, String> getTags() {
+        return tags;
+    }
+
+    public void setTags(Map<String, String> tags) {
+        this.tags = tags;
+    }
+
+    public MetricsCategory getCategory() {
+        return category;
+    }
+
+    public void setCategory(MetricsCategory category) {
+        this.category = category;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MetricsEntity entity = (MetricsEntity) o;
+        return Objects.equals(name, entity.name) && Objects.equals(tags, entity.tags)
+            && Objects.equals(category, entity.category) && Objects.equals(value, entity.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, tags, category, value);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsService.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsService.java
new file mode 100644
index 0000000..7c8724e
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsService.java
@@ -0,0 +1,73 @@
+/*
+ * 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.metrics.service;
+
+import org.apache.dubbo.common.extension.ExtensionScope;
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.dubbo.common.metrics.service.MetricsService.DEFAULT_EXTENSION_NAME;
+
+/**
+ * Metrics Service.
+ * Provide an interface to get metrics from {@link org.apache.dubbo.common.metrics.collector.MetricsCollector}
+ */
+@SPI(value = DEFAULT_EXTENSION_NAME, scope = ExtensionScope.APPLICATION)
+public interface MetricsService {
+
+    /**
+     * Default {@link MetricsService} extension name.
+     */
+    String DEFAULT_EXTENSION_NAME = "default";
+
+    /**
+     * The contract version of {@link MetricsService}, the future update must make sure compatible.
+     */
+    String VERSION = "1.0.0";
+
+    /**
+     * Get metrics by prefixes
+     *
+     * @param categories categories
+     * @return metrics - key=MetricCategory value=MetricsEntityList
+     */
+    Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(List<MetricsCategory> categories);
+
+    /**
+     * Get metrics by interface and prefixes
+     *
+     * @param serviceUniqueName serviceUniqueName (eg.group/interfaceName:version)
+     * @param categories categories
+     * @return metrics - key=MetricCategory value=MetricsEntityList
+     */
+    Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(String serviceUniqueName, List<MetricsCategory> categories);
+
+    /**
+     * Get metrics by interface、method and prefixes
+     *
+     * @param serviceUniqueName serviceUniqueName (eg.group/interfaceName:version)
+     * @param methodName methodName
+     * @param parameterTypes method parameter types
+     * @param categories categories
+     * @return metrics - key=MetricCategory value=MetricsEntityList
+     */
+    Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(String serviceUniqueName, String methodName, Class<?>[] parameterTypes, List<MetricsCategory> categories);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsServiceExporter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsServiceExporter.java
new file mode 100644
index 0000000..8a9a6a0
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/metrics/service/MetricsServiceExporter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.service;
+
+import org.apache.dubbo.common.extension.ExtensionScope;
+import org.apache.dubbo.common.extension.SPI;
+
+/**
+ * The exporter of {@link MetricsService}
+ */
+@SPI(value = "default", scope = ExtensionScope.APPLICATION)
+public interface MetricsServiceExporter {
+
+    /**
+     * Initialize exporter
+     */
+    void init();
+
+    /**
+     * Exports the {@link MetricsService} as a Dubbo service
+     *
+     * @return {@link MetricsServiceExporter itself}
+     */
+    MetricsServiceExporter export();
+
+    /**
+     * Unexports the {@link MetricsService}
+     *
+     * @return {@link MetricsServiceExporter itself}
+     */
+    MetricsServiceExporter unexport();
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java
index 4fc50d9..5d2c420 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java
@@ -25,7 +25,7 @@
     public static final String PROFILER_KEY = "DUBBO_INVOKE_PROFILER";
     public static final int MAX_ENTRY_SIZE = 1000;
 
-    private final static InternalThreadLocal<ProfilerEntry> bizProfiler = new InternalThreadLocal<>();
+    private static final InternalThreadLocal<ProfilerEntry> bizProfiler = new InternalThreadLocal<>();
 
     public static ProfilerEntry start(String message) {
         return new ProfilerEntry(message);
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 0942d88..223ad94 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
@@ -42,6 +42,7 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_EXPORT_THREAD_NUM;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_REFER_THREAD_NUM;
 import static org.apache.dubbo.common.constants.CommonConstants.EXECUTOR_SERVICE_COMPONENT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERNAL_EXECUTOR_SERVICE_COMPONENT_KEY;
@@ -83,8 +84,14 @@
         Map<Integer, ExecutorService> executors = data.computeIfAbsent(getExecutorKey(url), k -> new ConcurrentHashMap<>());
         // Consumer's executor is sharing globally, key=Integer.MAX_VALUE. Provider's executor is sharing by protocol.
         Integer portKey = CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE : url.getPort();
+
+        String protocol = url.getProtocol();
+        if (StringUtils.isEmpty(protocol)) {
+            protocol = DEFAULT_PROTOCOL;
+        }
+
         if (url.getParameter(THREAD_NAME_KEY) == null) {
-            url = url.putAttribute(THREAD_NAME_KEY, "Dubbo-protocol-" + portKey);
+            url = url.putAttribute(THREAD_NAME_KEY, protocol + "-protocol-" + portKey);
         }
         URL finalUrl = url;
         ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(finalUrl));
@@ -129,6 +136,7 @@
         if (executors == null) {
             logger.warn("No available executors, this is not expected, framework should call createExecutorIfAbsent first " +
                 "before coming to here.");
+
             return null;
         }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/FrameworkExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/FrameworkExecutorRepository.java
index df4e10e..d630ee4 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/FrameworkExecutorRepository.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/FrameworkExecutorRepository.java
@@ -91,7 +91,7 @@
 
     /**
      * Returns a scheduler from the scheduler list, call this method whenever you need a scheduler for a cron job.
-     * If your cron cannot burden the possible schedule delay caused by sharing the same scheduler, please consider define a dedicate one.
+     * If your cron cannot burden the possible schedule delay caused by sharing the same scheduler, please consider define a dedicated one.
      *
      * @return ScheduledExecutorService
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
index 77dff05..43520f9 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/AbortPolicyWithReport.java
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.dubbo.common.threadpool.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.event.ThreadPoolExhaustedEvent;
 import org.apache.dubbo.common.threadpool.event.ThreadPoolExhaustedListener;
@@ -40,6 +41,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.DUMP_DIRECTORY;
 import static org.apache.dubbo.common.constants.CommonConstants.OS_NAME_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.OS_WIN_PREFIX;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_THREAD_POOL_EXHAUSTED;
 
 /**
  * Abort Policy.
@@ -47,7 +49,7 @@
  */
 public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
 
-    protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);
+    protected static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbortPolicyWithReport.class);
 
     private final String threadName;
 
@@ -82,9 +84,13 @@
                 e.getLargestPoolSize(),
                 e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
                 url.getProtocol(), url.getIp(), url.getPort());
-        logger.warn(msg);
+
+        // 0-1 - Thread pool is EXHAUSTED!
+        logger.warn(COMMON_THREAD_POOL_EXHAUSTED, "too much client requesting provider", "", msg);
+
         dumpJStack();
         dispatchThreadPoolExhaustedEvent(msg);
+
         throw new RejectedExecutionException(msg);
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
index 8aa8fac..53ee25c 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPool.java
@@ -51,7 +51,7 @@
         int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
 
         // init queue and executor
-        TaskQueue<Runnable> taskQueue = new TaskQueue<Runnable>(queues <= 0 ? 1 : queues);
+        TaskQueue<Runnable> taskQueue = new TaskQueue<>(queues <= 0 ? 1 : queues);
         EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores,
                 threads,
                 alive,
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
index de1fb7e..06acc39 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/fixed/FixedThreadPool.java
@@ -22,6 +22,7 @@
 import org.apache.dubbo.common.threadpool.ThreadPool;
 import org.apache.dubbo.common.threadpool.support.AbortPolicyWithReport;
 
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.SynchronousQueue;
@@ -47,10 +48,18 @@
         String name = url.getParameter(THREAD_NAME_KEY, (String) url.getAttribute(THREAD_NAME_KEY, DEFAULT_THREAD_NAME));
         int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
         int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
-        return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
-                queues == 0 ? new SynchronousQueue<Runnable>() :
-                        (queues < 0 ? new MemorySafeLinkedBlockingQueue<Runnable>()
-                                : new LinkedBlockingQueue<Runnable>(queues)),
+
+        BlockingQueue<Runnable> blockingQueue;
+
+        if (queues == 0) {
+            blockingQueue = new SynchronousQueue<>();
+        } else if (queues < 0) {
+            blockingQueue = new MemorySafeLinkedBlockingQueue<>();
+        } else {
+            blockingQueue = new LinkedBlockingQueue<>(queues);
+        }
+
+        return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, blockingQueue,
                 new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java
index 2ccf48d..e6cb3cb 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java
@@ -52,14 +52,14 @@
  * You can increase or decrease the accuracy of the execution timing by
  * specifying smaller or larger tick duration in the constructor.  In most
  * network applications, I/O timeout does not need to be accurate.  Therefore,
- * the default tick duration is 100 milliseconds and you will not need to try
+ * the default tick duration is 100 milliseconds, and you will not need to try
  * different configurations in most cases.
  *
  * <h3>Ticks per Wheel (Wheel Size)</h3>
  * <p>
  * {@link HashedWheelTimer} maintains a data structure called 'wheel'.
  * To put simply, a wheel is a hash table of {@link TimerTask}s whose hash
- * function is 'dead line of the task'.  The default number of ticks per wheel
+ * function is 'deadline of the task'.  The default number of ticks per wheel
  * (i.e. the size of the wheel) is 512.  You could specify a larger value
  * if you are going to schedule a lot of timeouts.
  *
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
index 2695f2a..0362af7 100644
--- 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
@@ -86,7 +86,7 @@
     }
 
     public URLAddress setHost(String host) {
-        return new URLAddress(host, port, rawAddress);
+        return new URLAddress(host, port, null);
     }
 
     public int getPort() {
@@ -94,7 +94,7 @@
     }
 
     public URLAddress setPort(int port) {
-        return new URLAddress(host, port, rawAddress);
+        return new URLAddress(host, port, null);
     }
 
     public String getAddress() {
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
index 05deabe..243e1d4 100644
--- 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
@@ -978,7 +978,7 @@
      *
      * @param params   params map added into URLParam
      * @param rawParam original rawParam string, directly add to rawParam field,
-     *                 will not effect real key-pairs store in URLParam.
+     *                 will not affect real key-pairs store in URLParam.
      *                 Please make sure it can correspond with params or will
      *                 cause unexpected result when calling {@link URLParam#getRawParam()}
      *                 and {@link URLParam#toString()} ()}. If you not sure, you can call
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
index dc113e1..a57f53f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CompatibleTypeUtils.java
@@ -23,6 +23,7 @@
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
@@ -34,6 +35,11 @@
 
     private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
 
+    /**
+     * the text to parse such as "2007-12-03T10:15:30"
+     */
+    private static final int ISO_LOCAL_DATE_TIME_MIN_LEN = 19;
+
     private CompatibleTypeUtils() {
     }
 
@@ -128,7 +134,12 @@
                 if (StringUtils.isEmpty(string)) {
                     return null;
                 }
-                return LocalDateTime.parse(string).toLocalTime();
+                
+                if (string.length() >= ISO_LOCAL_DATE_TIME_MIN_LEN) {
+                    return LocalDateTime.parse(string).toLocalTime();
+                } else {
+                    return LocalTime.parse(string);
+                }
             }
             if (type == Class.class) {
                 try {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java
index 90e905a..425e064 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java
@@ -20,6 +20,12 @@
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+/**
+ * A 'least recently used' cache based on LinkedHashMap.
+ *
+ * @param <K> key
+ * @param <V> value
+ */
 public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 
     private static final long serialVersionUID = -5167631809472116969L;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
index c4f24df..43656f0 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
@@ -57,11 +57,25 @@
 /**
  * IP and Port Helper for RPC
  */
-public class NetUtils {
+public final class NetUtils {
+
+    /**
+     * Forbids instantiation.
+     */
+    private NetUtils() {
+        throw new UnsupportedOperationException("No instance of 'NetUtils' for you! ");
+    }
 
     private static Logger logger;
 
     static {
+        /*
+            DO NOT replace this logger to error type aware logger (or fail-safe logger), since its
+            logging method calls NetUtils.getLocalHost().
+
+            According to issue #4992, getLocalHost() method will be endless recursively invoked when network disconnected.
+        */
+
         logger = LoggerFactory.getLogger(NetUtils.class);
         if (logger instanceof FailsafeLogger) {
             logger = ((FailsafeLogger) logger).getLogger();
@@ -82,6 +96,7 @@
 
     private static final Map<String, String> HOST_NAME_CACHE = new LRUCache<>(1000);
     private static volatile InetAddress LOCAL_ADDRESS = null;
+    private static volatile Inet6Address LOCAL_ADDRESS_V6 = null;
 
     private static final String SPLIT_IPV4_CHARACTER = "\\.";
     private static final String SPLIT_IPV6_CHARACTER = ":";
@@ -96,15 +111,16 @@
         return RND_PORT_START + ThreadLocalRandom.current().nextInt(RND_PORT_RANGE);
     }
 
-    public synchronized static int getAvailablePort() {
+    public static synchronized int getAvailablePort() {
         int randomPort = getRandomPort();
         return getAvailablePort(randomPort);
     }
 
-    public synchronized static int getAvailablePort(int port) {
-         if (port < MIN_PORT) {
+    public static synchronized int getAvailablePort(int port) {
+        if (port < MIN_PORT) {
             return MIN_PORT;
         }
+
         for (int i = port; i < MAX_PORT; i++) {
             if (USED_PORT.get(i)) {
                 continue;
@@ -123,8 +139,8 @@
 
     /**
      * Check the port whether is in use in os
-     * @param port
-     * @return
+     * @param port port to check
+     * @return true if it's occupied
      */
     public static boolean isPortInUsed(int port) {
         try (ServerSocket ignored = new ServerSocket(port)) {
@@ -135,10 +151,24 @@
         return true;
     }
 
+    /**
+     * Tells whether the port to test is an invalid port.
+     *
+     * @implNote Numeric comparison only.
+     * @param port port to test
+     * @return true if invalid
+     */
     public static boolean isInvalidPort(int port) {
         return port < MIN_PORT || port > MAX_PORT;
     }
 
+    /**
+     * Tells whether the address to test is an invalid address.
+     *
+     * @implNote Pattern matching only.
+     * @param address address to test
+     * @return true if invalid
+     */
     public static boolean isValidAddress(String address) {
         return ADDRESS_PATTERN.matcher(address).matches();
     }
@@ -221,6 +251,8 @@
 
     private static volatile String HOST_ADDRESS;
 
+    private static volatile String HOST_ADDRESS_V6;
+
     public static String getLocalHost() {
         if (HOST_ADDRESS != null) {
             return HOST_ADDRESS;
@@ -228,11 +260,45 @@
 
         InetAddress address = getLocalAddress();
         if (address != null) {
-            return HOST_ADDRESS = address.getHostAddress();
+            if (address instanceof Inet6Address) {
+                String ipv6AddressString = address.getHostAddress();
+                if (ipv6AddressString.contains("%")) {
+                    ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
+                }
+                HOST_ADDRESS = ipv6AddressString;
+                return HOST_ADDRESS;
+            }
+
+            HOST_ADDRESS = address.getHostAddress();
+            return HOST_ADDRESS;
         }
+
         return LOCALHOST_VALUE;
     }
 
+    public static String getLocalHostV6() {
+        if (StringUtils.isNotEmpty(HOST_ADDRESS_V6)) {
+            return HOST_ADDRESS_V6;
+        }
+        //avoid to search network interface card many times
+        if("".equals(HOST_ADDRESS_V6)){
+            return null;
+        }
+
+        Inet6Address address = getLocalAddressV6();
+        if (address != null) {
+            String ipv6AddressString = address.getHostAddress();
+            if (ipv6AddressString.contains("%")) {
+                ipv6AddressString = ipv6AddressString.substring(0, ipv6AddressString.indexOf("%"));
+            }
+
+            HOST_ADDRESS_V6 = ipv6AddressString;
+            return HOST_ADDRESS_V6;
+        }
+        HOST_ADDRESS_V6 = "";
+        return null;
+    }
+
     public static String filterLocalHost(String host) {
         if (host == null || host.length() == 0) {
             return host;
@@ -278,6 +344,15 @@
         return localAddress;
     }
 
+    public static Inet6Address getLocalAddressV6() {
+        if (LOCAL_ADDRESS_V6 != null) {
+            return LOCAL_ADDRESS_V6;
+        }
+        Inet6Address localAddress = getLocalAddress0V6();
+        LOCAL_ADDRESS_V6 = localAddress;
+        return localAddress;
+    }
+
     private static Optional<InetAddress> toValidAddress(InetAddress address) {
         if (address instanceof Inet6Address) {
             Inet6Address v6Address = (Inet6Address) address;
@@ -324,10 +399,34 @@
             logger.warn(e);
         }
 
+        localAddress = getLocalAddressV6();
 
         return localAddress;
     }
 
+    private static Inet6Address getLocalAddress0V6() {
+        // @since 2.7.6, choose the {@link NetworkInterface} first
+        try {
+            NetworkInterface networkInterface = findNetworkInterface();
+            Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
+            while (addresses.hasMoreElements()) {
+                InetAddress address = addresses.nextElement();
+                if (address instanceof Inet6Address) {
+                    if (!address.isLoopbackAddress() //filter 127.x.x.x
+                        && !address.isAnyLocalAddress() // filter 0.0.0.0
+                        && !address.isLinkLocalAddress() //filter 169.254.0.0/16
+                        && address.getHostAddress().contains(":")) {//filter IPv6
+                        return (Inet6Address) address;
+                    }
+                }
+            }
+        } catch (Throwable e) {
+            logger.warn(e);
+        }
+
+        return null;
+    }
+
     /**
      * Returns {@code true} if the specified {@link NetworkInterface} should be ignored with the given conditions.
      *
@@ -349,10 +448,10 @@
             for (String ignoredInterface : ignoredInterfaces.split(",")) {
                 String trimIgnoredInterface = ignoredInterface.trim();
                 boolean matched = false;
-                try {                     
+                try {
                     matched = networkInterfaceDisplayName.matches(trimIgnoredInterface);
                 } catch (PatternSyntaxException e) {
-                    // if trimIgnoredInterface is a invalid regular expression, a PatternSyntaxException will be thrown out
+                    // if trimIgnoredInterface is an invalid regular expression, a PatternSyntaxException will be thrown out
                     logger.warn("exception occurred: " + networkInterfaceDisplayName + " matches " + trimIgnoredInterface, e);
                 } finally {
                     if (matched) {
@@ -472,6 +571,14 @@
         return address;
     }
 
+    public static String getLocalHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            return getLocalAddress().getHostName();
+        }
+    }
+
     /**
      * @param hostName
      * @return ip address or hostName if UnknownHostException
@@ -513,19 +620,24 @@
         return sb.toString();
     }
 
+    @SuppressWarnings("deprecation")
     public static void joinMulticastGroup(MulticastSocket multicastSocket, InetAddress multicastAddress) throws
         IOException {
         setInterface(multicastSocket, multicastAddress instanceof Inet6Address);
+
+        // For the deprecation notice: the equivalent only appears in JDK 9+.
         multicastSocket.setLoopbackMode(false);
         multicastSocket.joinGroup(multicastAddress);
     }
 
+    @SuppressWarnings("deprecation")
     public static void setInterface(MulticastSocket multicastSocket, boolean preferIpv6) throws IOException {
         boolean interfaceSet = false;
         for (NetworkInterface networkInterface : getValidNetworkInterfaces()) {
-            Enumeration addresses = networkInterface.getInetAddresses();
+            Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
+
             while (addresses.hasMoreElements()) {
-                InetAddress address = (InetAddress) addresses.nextElement();
+                InetAddress address = addresses.nextElement();
                 if (preferIpv6 && address instanceof Inet6Address) {
                     try {
                         if (address.isReachable(100)) {
@@ -595,7 +707,7 @@
             splitCharacter = SPLIT_IPV6_CHARACTER;
         }
         String[] mask = pattern.split(splitCharacter);
-        //check format of pattern
+        // check format of pattern
         checkHostPattern(pattern, mask, isIpv4);
 
         host = inetAddress.getHostAddress();
@@ -610,6 +722,7 @@
         }
 
         String[] ipAddress = host.split(splitCharacter);
+
         for (int i = 0; i < mask.length; i++) {
             if ("*".equals(mask[i]) || mask[i].equals(ipAddress[i])) {
                 continue;
@@ -699,4 +812,28 @@
         return Integer.parseInt(ipSegment, 16);
     }
 
+
+    public static boolean isIPV6URLStdFormat(String ip) {
+        if ((ip.charAt(0) == '[' && ip.indexOf(']') > 2)) {
+            return true;
+        } else if (ip.indexOf(":") != ip.lastIndexOf(":")) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public static String getLegalIP(String ip) {
+        //ipv6 [::FFFF:129.144.52.38]:80
+        int ind;
+        if ((ip.charAt(0) == '[' && (ind = ip.indexOf(']')) > 2)) {
+            String nhost = ip;
+            ip = nhost.substring(0, ind + 1);
+            ip = ip.substring(1, ind);
+            return ip;
+        } else {
+            return ip;
+        }
+    }
+
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
index c6e2eae..b8abfa6 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java
@@ -33,6 +33,9 @@
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.lang.reflect.WildcardType;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -141,6 +144,10 @@
         if (ReflectUtils.isPrimitives(pojo.getClass())) {
             return pojo;
         }
+        
+        if (pojo instanceof LocalDate || pojo instanceof LocalDateTime || pojo instanceof LocalTime) {
+            return pojo.toString();
+        }
 
         if (pojo instanceof Class) {
             return ((Class) pojo).getName();
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SerializeClassChecker.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SerializeClassChecker.java
index cb5c2f0..50ddfdc 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SerializeClassChecker.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SerializeClassChecker.java
@@ -17,6 +17,8 @@
 package org.apache.dubbo.common.utils;
 
 import org.apache.dubbo.common.beanutil.JavaBeanSerializeUtil;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 
@@ -36,6 +38,7 @@
 
     private static volatile SerializeClassChecker INSTANCE = null;
 
+    private final boolean OPEN_CHECK_CLASS;
     private final boolean BLOCK_ALL_CLASS_EXCEPT_ALLOW;
     private final Set<String> CLASS_DESERIALIZE_ALLOWED_SET = new ConcurrentHashSet<>();
     private final Set<String> CLASS_DESERIALIZE_BLOCKED_SET = new ConcurrentHashSet<>();
@@ -47,7 +50,11 @@
     private final AtomicLong counter = new AtomicLong(0);
 
     private SerializeClassChecker() {
-        String blockAllClassExceptAllow = System.getProperty(CLASS_DESERIALIZE_BLOCK_ALL, "false");
+        String openCheckClass = ConfigurationUtils.getProperty(CommonConstants.CLASS_DESERIALIZE_OPEN_CHECK, "true");
+        OPEN_CHECK_CLASS = Boolean.parseBoolean(openCheckClass);
+
+        String blockAllClassExceptAllow = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_BLOCK_ALL, "false");
+        
         BLOCK_ALL_CLASS_EXCEPT_ALLOW = Boolean.parseBoolean(blockAllClassExceptAllow);
 
         String[] lines;
@@ -70,8 +77,8 @@
             logger.error("Failed to load blocked class list! Will ignore default blocked list.", e);
         }
 
-        String allowedClassList = System.getProperty(CLASS_DESERIALIZE_ALLOWED_LIST, "").trim().toLowerCase(Locale.ROOT);
-        String blockedClassList = System.getProperty(CLASS_DESERIALIZE_BLOCKED_LIST, "").trim().toLowerCase(Locale.ROOT);
+        String allowedClassList = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_ALLOWED_LIST, "").trim().toLowerCase(Locale.ROOT);
+        String blockedClassList = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_BLOCKED_LIST, "").trim().toLowerCase(Locale.ROOT);
 
         if (StringUtils.isNotEmpty(allowedClassList)) {
             String[] classStrings = allowedClassList.trim().split(",");
@@ -111,6 +118,10 @@
      * @param name class name ( all are convert to lower case )
      */
     public void validateClass(String name) {
+        if(!OPEN_CHECK_CLASS){
+            return;
+        }
+
         name = name.toLowerCase(Locale.ROOT);
         if (CACHE == CLASS_ALLOW_LFU_CACHE.get(name)) {
             return;
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 f0246c0..0167438 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
@@ -64,9 +64,16 @@
 public class UrlUtils {
 
     /**
+     * Forbids the instantiation.
+     */
+    private UrlUtils() {
+        throw new UnsupportedOperationException("No instance of 'UrlUtils' for you! ");
+    }
+
+    /**
      * in the url string,mark the param begin
      */
-    private final static String URL_PARAM_STARTING_SYMBOL = "?";
+    private static final String URL_PARAM_STARTING_SYMBOL = "?";
 
     public static URL parseURL(String address, Map<String, String> defaults) {
         if (StringUtils.isEmpty(address)) {
@@ -212,7 +219,8 @@
     }
 
     public static Map<String, String> convertSubscribe(Map<String, String> subscribe) {
-        Map<String, String> newSubscribe = new HashMap<String, String>();
+        Map<String, String> newSubscribe = new HashMap<>();
+
         for (Map.Entry<String, String> entry : subscribe.entrySet()) {
             String serviceName = entry.getKey();
             String serviceQuery = entry.getValue();
@@ -234,11 +242,13 @@
                 newSubscribe.put(serviceName, serviceQuery);
             }
         }
+
         return newSubscribe;
     }
 
     public static Map<String, Map<String, String>> revertRegister(Map<String, Map<String, String>> register) {
-        Map<String, Map<String, String>> newRegister = new HashMap<String, Map<String, String>>();
+        Map<String, Map<String, String>> newRegister = new HashMap<>();
+
         for (Map.Entry<String, Map<String, String>> entry : register.entrySet()) {
             String serviceName = entry.getKey();
             Map<String, String> serviceUrls = entry.getValue();
@@ -265,11 +275,14 @@
                 newRegister.put(serviceName, serviceUrls);
             }
         }
+
         return newRegister;
     }
 
     public static Map<String, String> revertSubscribe(Map<String, String> subscribe) {
-        Map<String, String> newSubscribe = new HashMap<String, String>();
+
+        Map<String, String> newSubscribe = new HashMap<>();
+
         for (Map.Entry<String, String> entry : subscribe.entrySet()) {
             String serviceName = entry.getKey();
             String serviceQuery = entry.getValue();
@@ -385,32 +398,47 @@
     public static boolean isMatch(URL consumerUrl, URL providerUrl) {
         String consumerInterface = consumerUrl.getServiceInterface();
         String providerInterface = providerUrl.getServiceInterface();
-        //FIXME accept providerUrl with '*' as interface name, after carefully thought about all possible scenarios I think it's ok to add this condition.
+
+        // FIXME accept providerUrl with '*' as interface name, after carefully thought about all possible scenarios I think it's ok to add this condition.
+
+        // Return false if the consumer interface is not equals the provider interface,
+        // except one of the interface configurations is equals '*' (i.e. any value).
         if (!(ANY_VALUE.equals(consumerInterface)
             || ANY_VALUE.equals(providerInterface)
             || StringUtils.isEquals(consumerInterface, providerInterface))) {
             return false;
         }
 
-        if (!isMatchCategory(providerUrl.getCategory(DEFAULT_CATEGORY),
-            consumerUrl.getCategory(DEFAULT_CATEGORY))) {
+        // If the category of provider URL does not match the category of consumer URL.
+        // Usually, the provider URL's category is empty, and the default category ('providers') is present.
+        // Hence, the category of the provider URL is 'providers'.
+        // Through observing of debugging process, I found that the category of the consumer URL is 'providers,configurators,routers'.
+        if (!isMatchCategory(providerUrl.getCategory(DEFAULT_CATEGORY), consumerUrl.getCategory(DEFAULT_CATEGORY))) {
             return false;
         }
+
+        // If the provider is not enabled, return false.
         if (!providerUrl.getParameter(ENABLED_KEY, true)
             && !ANY_VALUE.equals(consumerUrl.getParameter(ENABLED_KEY))) {
             return false;
         }
 
+        // Obtain consumer's group, version and classifier.
         String consumerGroup = consumerUrl.getGroup();
         String consumerVersion = consumerUrl.getVersion();
         String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
 
+        // Obtain provider's group, version and classifier.
         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))
-            && (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));
+
+        // If Group, Version, Classifier all matches, return true.
+        boolean groupMatches = ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup);
+        boolean versionMatches = ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion);
+        boolean classifierMatches = consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier);
+
+        return groupMatches && versionMatches && classifierMatches;
     }
 
     public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
index b341bdd..9996c91 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java
@@ -52,7 +52,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -364,7 +363,7 @@
      */
     protected static Map<String, String> convert(Map<String, String> parameters, String prefix) {
         if (parameters == null || parameters.isEmpty()) {
-            return Collections.emptyMap();
+            return new HashMap<>();
         }
 
         Map<String, String> result = new HashMap<>();
@@ -1023,7 +1022,7 @@
         List<Method> methods = new ArrayList<>(beanInfo.getMethodDescriptors().length);
         for (MethodDescriptor methodDescriptor : beanInfo.getMethodDescriptors()) {
             Method method = methodDescriptor.getMethod();
-            if (MethodUtils.isGetter(method)) {
+            if (MethodUtils.isGetter(method) || isParametersGetter(method)) {
                 // filter non attribute
                 Parameter parameter = method.getAnnotation(Parameter.class);
                 if (parameter != null && !parameter.attribute()) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
index 0491328..315dc36 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
@@ -235,9 +235,6 @@
         if (this.monitor != null && this.monitor.getScopeModel() != applicationModel) {
             this.monitor.setScopeModel(applicationModel);
         }
-        if (this.metadataReportConfig != null && this.metadataReportConfig.getScopeModel() != applicationModel) {
-            this.metadataReportConfig.setScopeModel(applicationModel);
-        }
         if (CollectionUtils.isNotEmpty(this.registries)) {
             this.registries.forEach(registryConfig -> {
                 if (registryConfig.getScopeModel() != applicationModel) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
index dcada0c..f557f93 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java
@@ -27,6 +27,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.ROUTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.STUB_EVENT_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDED_BY;
+import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDER_PORT;
 
 /**
  * AbstractConsumerConfig
@@ -76,18 +77,26 @@
     protected Boolean stubevent;//= Constants.DEFAULT_STUB_EVENT;
 
 
-
     /**
      * declares which app or service this interface belongs to
      */
     protected String providedBy;
 
+    /**
+     * By VirtualService and DestinationRule, envoy will generate a new route rule,such as 'demo.default.svc.cluster.local:80',the default port is 80.
+     * When you want to specify the provider port,you can use this config.
+     *
+     * @since 3.1.0
+     */
+    protected Integer providerPort;
+
     protected String router;
 
     /**
      * Weather the reference is referred asynchronously
-     * @deprecated
+     *
      * @see ModuleConfig#referAsync
+     * @deprecated
      */
     @Deprecated
     private Boolean referAsync;
@@ -248,7 +257,6 @@
     }
 
 
-
     @Parameter(key = PROVIDED_BY)
     public String getProvidedBy() {
         return providedBy;
@@ -258,6 +266,15 @@
         this.providedBy = providedBy;
     }
 
+    @Parameter(key = PROVIDER_PORT)
+    public Integer getProviderPort() {
+        return providerPort;
+    }
+
+    public void setProviderPort(Integer providerPort) {
+        this.providerPort = providerPort;
+    }
+
     @Parameter(key = ROUTER_KEY, append = true)
     public String getRouter() {
         return router;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ConsumerConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ConsumerConfig.java
index a3c3726..10faac7 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ConsumerConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ConsumerConfig.java
@@ -20,6 +20,7 @@
 import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.rpc.model.ModuleModel;
 
+import static org.apache.dubbo.common.constants.CommonConstants.MESH_ENABLE;
 import static org.apache.dubbo.common.constants.CommonConstants.REFER_BACKGROUND_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.REFER_THREAD_NUM_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.URL_MERGE_PROCESSOR_KEY;
@@ -78,6 +79,12 @@
      */
     private Boolean referBackground;
 
+    /**
+     * enable mesh mode
+     * @since 3.1.0
+     */
+    private Boolean meshEnable;
+
 
     public ConsumerConfig() {
     }
@@ -170,4 +177,13 @@
     public void setReferBackground(Boolean referBackground) {
         this.referBackground = referBackground;
     }
+
+    @Parameter(key = MESH_ENABLE)
+    public Boolean getMeshEnable() {
+        return meshEnable;
+    }
+
+    public void setMeshEnable(Boolean meshEnable) {
+        this.meshEnable = meshEnable;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
index ea8ca77..a379856 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.config;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.config.nested.AggregationConfig;
 import org.apache.dubbo.config.nested.PrometheusConfig;
@@ -27,9 +26,6 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
-import static org.apache.dubbo.common.constants.MetricsConstants.PROTOCOL_PROMETHEUS;
-
 /**
  * MetricsConfig
  */
@@ -40,6 +36,11 @@
     private String protocol;
 
     /**
+     * Enable jvm metrics when collecting.
+     */
+    private Boolean enableJvmMetrics;
+
+    /**
      * @deprecated After metrics config is refactored.
      * This parameter should no longer use and will be deleted in the future.
      */
@@ -69,14 +70,11 @@
         Map<String, String> map = new HashMap<>();
         appendParameters(map, this);
 
-        // use 'prometheus' as the default metrics service.
-        if (StringUtils.isEmpty(map.get(PROTOCOL_KEY))) {
-            map.put(PROTOCOL_KEY, PROTOCOL_PROMETHEUS);
-        }
-
         // ignore address parameter, use specified url in each metrics server config
         // the address "localhost" here is meaningless
-        return UrlUtils.parseURL("localhost", map);
+        URL url = UrlUtils.parseURL("localhost", map);
+        url = url.setScopeModel(getScopeModel());
+        return url;
     }
 
     public String getProtocol() {
@@ -87,6 +85,14 @@
         this.protocol = protocol;
     }
 
+    public Boolean getEnableJvmMetrics() {
+        return enableJvmMetrics;
+    }
+
+    public void setEnableJvmMetrics(Boolean enableJvmMetrics) {
+        this.enableJvmMetrics = enableJvmMetrics;
+    }
+
     public String getPort() {
         return port;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
index f235f44..9ef6daf 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
@@ -200,6 +200,11 @@
 
     private Boolean sslEnabled;
 
+    /*
+     * Extra Protocol for this service, using Port Unification Server
+     */
+    private String extProtocol;
+
     public ProtocolConfig() {
     }
 
@@ -551,4 +556,11 @@
         return StringUtils.isNotEmpty(name);
     }
 
+    public String getExtProtocol() {
+        return extProtocol;
+    }
+
+    public void setExtProtocol(String extProtocol) {
+        this.extProtocol = extProtocol;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
index 3d16ce4..0138e06 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ReferenceConfigBase.java
@@ -38,6 +38,7 @@
 import java.util.Properties;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
+import static org.apache.dubbo.common.constants.CommonConstants.UNLOAD_CLUSTER_RELATED;
 
 /**
  * ReferenceConfig
@@ -66,6 +67,12 @@
      */
     protected ConsumerConfig consumer;
 
+    /**
+     * In the mesh mode, uninstall the directory, router and load balance related to the cluster in the currently invoked invoker.
+     * Delegate retry, load balancing, timeout and other traffic management capabilities to Sidecar.
+     */
+    protected Boolean unloadClusterRelated;
+
     public ReferenceConfigBase() {
         serviceMetadata = new ServiceMetadata();
         serviceMetadata.addAttribute(ORIGIN_CONFIG, this);
@@ -257,6 +264,15 @@
         this.consumer = consumer;
     }
 
+    @Parameter(key = UNLOAD_CLUSTER_RELATED)
+    public Boolean getUnloadClusterRelated() {
+        return unloadClusterRelated;
+    }
+
+    public void setUnloadClusterRelated(Boolean unloadClusterRelated) {
+        this.unloadClusterRelated = unloadClusterRelated;
+    }
+
     public ServiceMetadata getServiceMetadata() {
         return serviceMetadata;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
index 6b5c215..10afb4f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
@@ -19,6 +19,7 @@
 import org.apache.dubbo.common.constants.ClusterRules;
 import org.apache.dubbo.common.constants.LoadbalanceRules;
 import org.apache.dubbo.common.constants.RegistryConstants;
+import org.apache.dubbo.config.AbstractReferenceConfig;
 import org.apache.dubbo.config.ReferenceConfigBase;
 
 import java.lang.annotation.Documented;
@@ -32,7 +33,7 @@
  * <p>
  * <b>It is recommended to use @DubboReference on the @Bean method in the Java-config class, but not on the fields or setter methods to be injected.</b>
  * </p>
- *
+ * <p>
  * Step 1: Register ReferenceBean in Java-config class:
  * <pre class="code">
  * &#64;Configuration
@@ -50,7 +51,7 @@
  *     }
  * }
  * </pre>
- *
+ * <p>
  * Step 2: Inject ReferenceBean by @Autowired
  * <pre class="code">
  * public class FooController {
@@ -62,9 +63,9 @@
  * }
  * </pre>
  *
- * @since 2.7.7
  * @see org.apache.dubbo.config.spring.reference.ReferenceBeanBuilder
  * @see org.apache.dubbo.config.spring.ReferenceBean
+ * @since 2.7.7
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
@@ -103,6 +104,7 @@
 
     /**
      * Whether to enable generic invocation, default value is false
+     *
      * @deprecated Do not need specify generic value, judge by injection type and interface class
      */
     @Deprecated
@@ -110,6 +112,7 @@
 
     /**
      * When enable, prefer to call local service in the same JVM if it's present, default value is true
+     *
      * @deprecated using scope="local" or scope="remote" instead
      */
     @Deprecated
@@ -122,6 +125,7 @@
 
     /**
      * Whether eager initialize the reference bean when all properties are set, default value is true ( null as true)
+     *
      * @see ReferenceConfigBase#shouldInit()
      */
     boolean init() default true;
@@ -270,6 +274,7 @@
 
     /**
      * Application name
+     *
      * @deprecated This attribute was deprecated, use bind application/module of spring ApplicationContext
      */
     @Deprecated
@@ -321,6 +326,7 @@
     /**
      * The id
      * NOTE: The id attribute is ignored when using @DubboReference on @Bean method
+     *
      * @return default value is empty
      * @since 2.7.3
      */
@@ -337,12 +343,22 @@
 
     /**
      * declares which app or service this interface belongs to
+     *
      * @see RegistryConstants#PROVIDED_BY
      */
     String[] providedBy() default {};
 
     /**
+     * The service port of the provider
+     *
+     * @see AbstractReferenceConfig#providerPort
+     * @since 3.1.0
+     */
+    int providerPort() default -1;
+
+    /**
      * the scope for referring/exporting a service, if it's local, it means searching in current JVM only.
+     *
      * @see org.apache.dubbo.rpc.Constants#SCOPE_LOCAL
      * @see org.apache.dubbo.rpc.Constants#SCOPE_REMOTE
      */
@@ -352,4 +368,12 @@
      * Weather the reference is refer asynchronously
      */
     boolean referAsync() default false;
+
+    /**
+     * unload Cluster related in mesh mode
+     *
+     * @see ReferenceConfigBase#unloadClusterRelated
+     * @since 3.1.0
+     */
+    boolean unloadClusterRelated() default false;
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeClassLoaderListener.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeClassLoaderListener.java
new file mode 100644
index 0000000..a2b7529
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeClassLoaderListener.java
@@ -0,0 +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.rpc.model;
+
+public interface ScopeClassLoaderListener<T extends ScopeModel> {
+
+    void onAddClassLoader(T scopeModel, ClassLoader classLoader);
+
+    void onRemoveClassLoader(T scopeModel, ClassLoader classLoader);
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
index b3f016c..514d3ca 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ScopeModel.java
@@ -70,6 +70,8 @@
     private ScopeBeanFactory beanFactory;
     private List<ScopeModelDestroyListener> destroyListeners;
 
+    private List<ScopeClassLoaderListener> classLoaderListeners;
+
     private Map<String, Object> attributes;
     private final AtomicBoolean destroyed = new AtomicBoolean(false);
     private final boolean internalScope;
@@ -94,6 +96,7 @@
         this.extensionDirector.addExtensionPostProcessor(new ScopeModelAwareExtensionProcessor(this));
         this.beanFactory = new ScopeBeanFactory(parent != null ? parent.getBeanFactory() : null, extensionDirector);
         this.destroyListeners = new LinkedList<>();
+        this.classLoaderListeners = new LinkedList<>();
         this.attributes = new ConcurrentHashMap<>();
         this.classLoaders = new ConcurrentHashSet<>();
 
@@ -142,12 +145,28 @@
         }
     }
 
+    protected void notifyClassLoaderAdd(ClassLoader classLoader) {
+        for (ScopeClassLoaderListener classLoaderListener : classLoaderListeners) {
+            classLoaderListener.onAddClassLoader(this, classLoader);
+        }
+    }
+
+    protected void notifyClassLoaderDestroy(ClassLoader classLoader) {
+        for (ScopeClassLoaderListener classLoaderListener : classLoaderListeners) {
+            classLoaderListener.onRemoveClassLoader(this, classLoader);
+        }
+    }
+
     protected abstract void onDestroy();
 
     public final void addDestroyListener(ScopeModelDestroyListener listener) {
         destroyListeners.add(listener);
     }
 
+    public final void addClassLoaderListener(ScopeClassLoaderListener listener) {
+        classLoaderListeners.add(listener);
+    }
+
     public Map<String, Object> getAttributes() {
         return attributes;
     }
@@ -187,6 +206,7 @@
             parent.addClassLoader(classLoader);
         }
         extensionDirector.removeAllCachedLoader();
+        notifyClassLoaderAdd(classLoader);
     }
 
     public void removeClassLoader(ClassLoader classLoader) {
@@ -196,6 +216,7 @@
                 parent.removeClassLoader(classLoader);
             }
             extensionDirector.removeAllCachedLoader();
+            notifyClassLoaderDestroy(classLoader);
         }
     }
 
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyMatcherTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyMatcherTest.java
new file mode 100644
index 0000000..635045c
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyMatcherTest.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.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ProtocolServiceKeyMatcherTest {
+
+    @Test
+    public void testProtocol() {
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo"),
+            new ProtocolServiceKey(null, null, null, null)
+        ));
+
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo"),
+            new ProtocolServiceKey("DemoService", null, null, "dubbo")
+        ));
+
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, null),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, ""),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "*"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "dubbo1")
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "dubbo2")
+        ));
+
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,,dubbo2"),
+            new ProtocolServiceKey(null, null, null, null)
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "")
+        ));
+
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, ",dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, null)
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, ",dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "")
+        ));
+
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2,"),
+            new ProtocolServiceKey(null, null, null, null)
+        ));
+        Assertions.assertTrue(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2,"),
+            new ProtocolServiceKey(null, null, null, "")
+        ));
+
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, ",dubbo1,dubbo2"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+        Assertions.assertFalse(ProtocolServiceKey.Matcher.isMatch(
+            new ProtocolServiceKey(null, null, null, "dubbo1,dubbo2,"),
+            new ProtocolServiceKey(null, null, null, "dubbo")
+        ));
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyTest.java
new file mode 100644
index 0000000..09b2711
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/ProtocolServiceKeyTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ProtocolServiceKeyTest {
+    @Test
+    public void test() {
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol1");
+        Assertions.assertEquals("DemoService", protocolServiceKey.getInterfaceName());
+        Assertions.assertEquals("1.0.0", protocolServiceKey.getVersion());
+        Assertions.assertEquals("group1", protocolServiceKey.getGroup());
+        Assertions.assertEquals("protocol1", protocolServiceKey.getProtocol());
+
+        Assertions.assertEquals("group1/DemoService:1.0.0:protocol1", protocolServiceKey.toString());
+        Assertions.assertEquals("group1/DemoService:1.0.0", protocolServiceKey.getServiceKeyString());
+
+        Assertions.assertEquals(protocolServiceKey, protocolServiceKey);
+
+        ProtocolServiceKey protocolServiceKey1 = new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol1");
+        Assertions.assertEquals(protocolServiceKey, protocolServiceKey1);
+        Assertions.assertEquals(protocolServiceKey.hashCode(), protocolServiceKey1.hashCode());
+
+        ProtocolServiceKey protocolServiceKey2 = new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol2");
+        Assertions.assertNotEquals(protocolServiceKey, protocolServiceKey2);
+
+        ProtocolServiceKey protocolServiceKey3 = new ProtocolServiceKey("DemoService", "1.0.0", "group2", "protocol1");
+        Assertions.assertNotEquals(protocolServiceKey, protocolServiceKey3);
+
+        ProtocolServiceKey protocolServiceKey4 = new ProtocolServiceKey("DemoService", "1.0.1", "group1", "protocol1");
+        Assertions.assertNotEquals(protocolServiceKey, protocolServiceKey4);
+
+        ProtocolServiceKey protocolServiceKey5 = new ProtocolServiceKey("DemoInterface", "1.0.0", "group1", "protocol1");
+        Assertions.assertNotEquals(protocolServiceKey, protocolServiceKey5);
+
+        ServiceKey serviceKey = new ServiceKey("DemoService", "1.0.0", "group1");
+        Assertions.assertNotEquals(protocolServiceKey, serviceKey);
+
+        Assertions.assertTrue(protocolServiceKey.isSameWith(protocolServiceKey));
+        Assertions.assertTrue(protocolServiceKey.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "")));
+        Assertions.assertTrue(protocolServiceKey.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", null)));
+
+        Assertions.assertFalse(protocolServiceKey.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group2", "protocol1")));
+        Assertions.assertFalse(protocolServiceKey.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group2", "")));
+        Assertions.assertFalse(protocolServiceKey.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group2", null)));
+
+
+        ProtocolServiceKey protocolServiceKey6 = new ProtocolServiceKey("DemoService", "1.0.0", "group1", null);
+        Assertions.assertTrue(protocolServiceKey6.isSameWith(protocolServiceKey6));
+        Assertions.assertTrue(protocolServiceKey6.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "")));
+        Assertions.assertTrue(protocolServiceKey6.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol1")));
+        Assertions.assertTrue(protocolServiceKey6.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol2")));
+
+        ProtocolServiceKey protocolServiceKey7 = new ProtocolServiceKey("DemoService", "1.0.0", "group1", "*");
+        Assertions.assertFalse(protocolServiceKey7.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", null)));
+        Assertions.assertFalse(protocolServiceKey7.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "")));
+        Assertions.assertFalse(protocolServiceKey7.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol1")));
+        Assertions.assertFalse(protocolServiceKey7.isSameWith(new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol2")));
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyMatcherTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyMatcherTest.java
new file mode 100644
index 0000000..6d85c46
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyMatcherTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ServiceKeyMatcherTest {
+
+    @Test
+    public void testInterface() {
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey("DemoService", null, null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, null),
+            new ServiceKey("DemoService", null, null)
+        ));
+
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey("*", null, null),
+            new ServiceKey("DemoService", null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey("*", null, null),
+            new ServiceKey(null, null, null)
+        ));
+    }
+
+    @Test
+    public void testVersion() {
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0", null),
+            new ServiceKey(null, "1.0.0", null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, null),
+            new ServiceKey(null, "1.0.0", null)
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1", null),
+            new ServiceKey(null, "1.0.0", null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1", null),
+            new ServiceKey(null, "1.0.2", null)
+        ));
+
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1", null),
+            new ServiceKey(null, null, null)
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, ",1.0.0,1.0.1", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, ",1.0.0,1.0.1", null),
+            new ServiceKey(null, "", null)
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,,1.0.1", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,,1.0.1", null),
+            new ServiceKey(null, "", null)
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1,", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1,", null),
+            new ServiceKey(null, "", null)
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "1.0.0,1.0.1", null),
+            new ServiceKey(null, "", null)
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, ",1.0.0,1.0.1", null),
+            new ServiceKey(null, "1.0.2", null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, ",1.0.0,1.0.1", null),
+            new ServiceKey(null, "1.0.2", null)
+        ));
+
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "*", null),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "*", null),
+            new ServiceKey(null, "", null)
+        ));
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, "*", null),
+            new ServiceKey(null, "1.0.0", null)
+        ));
+    }
+
+    @Test
+    public void testGroup() {
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group1"),
+            new ServiceKey(null, null, "group1")
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group1"),
+            new ServiceKey(null, null, null)
+        ));
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, null),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group1, group2"),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2, group3"),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2, group3"),
+            new ServiceKey(null, null, null)
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2, group3"),
+            new ServiceKey(null, null, "")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, ",group2"),
+            new ServiceKey(null, null, "")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2,"),
+            new ServiceKey(null, null, "")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2, ,group3"),
+            new ServiceKey(null, null, "")
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, ",group2"),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2,"),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertFalse(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "group2, ,group3"),
+            new ServiceKey(null, null, "group1")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "*"),
+            new ServiceKey(null, null, "")
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "*"),
+            new ServiceKey(null, null, null)
+        ));
+
+        Assertions.assertTrue(ServiceKey.Matcher.isMatch(
+            new ServiceKey(null, null, "*"),
+            new ServiceKey(null, null, "group1")
+        ));
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyTest.java
new file mode 100644
index 0000000..5643166
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/ServiceKeyTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ServiceKeyTest {
+    @Test
+    public void test() {
+        ServiceKey serviceKey = new ServiceKey("DemoService", "1.0.0", "group1");
+
+        Assertions.assertEquals("DemoService", serviceKey.getInterfaceName());
+        Assertions.assertEquals("1.0.0", serviceKey.getVersion());
+        Assertions.assertEquals("group1", serviceKey.getGroup());
+
+        Assertions.assertEquals("group1/DemoService:1.0.0", serviceKey.toString());
+        Assertions.assertEquals("DemoService", new ServiceKey("DemoService", null, null).toString());
+        Assertions.assertEquals("DemoService:1.0.0", new ServiceKey("DemoService", "1.0.0", null).toString());
+        Assertions.assertEquals("group1/DemoService", new ServiceKey("DemoService", null, "group1").toString());
+
+        Assertions.assertEquals(serviceKey, serviceKey);
+
+        ServiceKey serviceKey1 = new ServiceKey("DemoService", "1.0.0", "group1");
+        Assertions.assertEquals(serviceKey, serviceKey1);
+        Assertions.assertEquals(serviceKey.hashCode(), serviceKey1.hashCode());
+
+        ServiceKey serviceKey2 = new ServiceKey("DemoService", "1.0.0", "group2");
+        Assertions.assertNotEquals(serviceKey, serviceKey2);
+
+        ServiceKey serviceKey3 = new ServiceKey("DemoService", "1.0.1", "group1");
+        Assertions.assertNotEquals(serviceKey, serviceKey3);
+
+        ServiceKey serviceKey4 = new ServiceKey("DemoInterface", "1.0.0", "group1");
+        Assertions.assertNotEquals(serviceKey, serviceKey4);
+
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey("DemoService", "1.0.0", "group1", "protocol1");
+        Assertions.assertNotEquals(serviceKey, protocolServiceKey);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java
index f295de9..fdd3533 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java
@@ -43,6 +43,7 @@
         testCases.add("dubbo://192.168.1.1/" + RandomString.make(10240));
         testCases.add("file:/path/to/file.txt");
         testCases.add("dubbo://fe80:0:0:0:894:aeec:f37d:23e1%en0/path?abc=abc");
+        testCases.add("dubbo://[fe80:0:0:0:894:aeec:f37d:23e1]:20880/path?abc=abc");
 
         errorDecodedCases.add("dubbo:192.168.1.1");
         errorDecodedCases.add("://192.168.1.1");
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 e78ab5d..033901b 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
@@ -18,7 +18,6 @@
 
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.CollectionUtils;
-
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -1082,4 +1081,13 @@
         URL urlWithoutUserInformation = URL.valueOf("10.20.130.230:20880/context/path?version=1.0.0&application=app1");
         assertEquals("10.20.130.230:20880", urlWithoutUserInformation.getAuthority());
     }
+
+
+    @Test
+    public void testIPV6() {
+        URL url = URL.valueOf("dubbo://[2408:4004:194:8896:3e8a:82ae:814a:398]:20881?name=apache");
+        assertEquals("[2408:4004:194:8896:3e8a:82ae:814a:398]", url.getHost());
+        assertEquals(20881, url.getPort());
+        assertEquals("apache", url.getParameter("name"));
+    }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/Bean.java b/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/Bean.java
index e17a538..4ea53cb 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/Bean.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/Bean.java
@@ -1,12 +1,3 @@
-package org.apache.dubbo.common.beanutil;
-
-import org.apache.dubbo.rpc.model.person.FullAddress;
-import org.apache.dubbo.rpc.model.person.PersonStatus;
-import org.apache.dubbo.rpc.model.person.Phone;
-
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -23,6 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.dubbo.common.beanutil;
+
+import org.apache.dubbo.rpc.model.person.FullAddress;
+import org.apache.dubbo.rpc.model.person.PersonStatus;
+import org.apache.dubbo.rpc.model.person.Phone;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
 public class Bean {
 
     private Class<?> type;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreFactoryTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreFactoryTest.java
index b2c119c..927501f 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreFactoryTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreFactoryTest.java
@@ -25,10 +25,10 @@
 import java.net.URL;
 import java.nio.file.Paths;
 
-public class FileCacheStoreFactoryTest {
+class FileCacheStoreFactoryTest {
 
     @Test
-    public void testSafeName() throws URISyntaxException {
+    void testSafeName() throws URISyntaxException {
         FileCacheStore store1 = FileCacheStoreFactory.getInstance(getDirectoryOfClassPath(), "../../../dubbo");
         Assertions.assertEquals(getDirectoryOfClassPath() + "..%002f..%002f..%002fdubbo.dubbo.cache", store1.getCacheFilePath());
         store1.destroy();
@@ -39,7 +39,7 @@
     }
 
     @Test
-    public void testPathIsFile() throws URISyntaxException, IOException {
+    void testPathIsFile() throws URISyntaxException, IOException {
         String basePath = getDirectoryOfClassPath();
         String filePath = basePath + File.separator + "isFile";
         new File(filePath).createNewFile();
@@ -48,7 +48,7 @@
     }
 
     @Test
-    public void testCacheContains() throws URISyntaxException {
+    void testCacheContains() throws URISyntaxException {
         FileCacheStore store1 = FileCacheStoreFactory.getInstance(getDirectoryOfClassPath(), "testCacheContains");
         Assertions.assertNotNull(store1.getCacheFilePath());
 
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreTest.java
index 6d8bf11..1c2a878 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/cache/FileCacheStoreTest.java
@@ -26,11 +26,11 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-public class FileCacheStoreTest {
-    FileCacheStore cacheStore;
+class FileCacheStoreTest {
+    private FileCacheStore cacheStore;
 
     @Test
-    public void testCache() throws Exception {
+    void testCache() throws Exception {
         String directoryPath = getDirectoryOfClassPath();
         String filePath = "test-cache.dubbo.cache";
         cacheStore = FileCacheStoreFactory.getInstance(directoryPath, filePath);
@@ -54,7 +54,7 @@
     }
 
     @Test
-    public void testFileSizeExceed() throws Exception {
+    void testFileSizeExceed() throws Exception {
         String directoryPath = getDirectoryOfClassPath();
         Map<String, String> newProperties = new HashMap<>();
         newProperties.put("newKey1", "newValue1");
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/InmemoryConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/InmemoryConfigurationTest.java
index b507188..3ac7b90 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/InmemoryConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/InmemoryConfigurationTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.common.config;
 
+import org.apache.dubbo.common.beanutil.JavaBeanAccessor;
+
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -23,9 +25,10 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.NoSuchElementException;
 
 /**
- * The type Inmemory configuration test.
+ * Unit test of class InmemoryConfiguration, and interface Configuration.
  */
 class InmemoryConfigurationTest {
 
@@ -41,7 +44,6 @@
      */
     @BeforeEach
     public void init() {
-
         memConfig = new InmemoryConfiguration();
     }
 
@@ -49,7 +51,7 @@
      * Test get mem property.
      */
     @Test
-    public void testGetMemProperty() {
+    void testGetMemProperty() {
         Assertions.assertNull(memConfig.getInternalProperty(MOCK_KEY));
         Assertions.assertFalse(memConfig.containsKey(MOCK_KEY));
         Assertions.assertNull(memConfig.getString(MOCK_KEY));
@@ -65,7 +67,7 @@
      * Test get properties.
      */
     @Test
-    public void testGetProperties() {
+    void testGetProperties() {
         Assertions.assertNull(memConfig.getInternalProperty(MOCK_ONE_KEY));
         Assertions.assertNull(memConfig.getInternalProperty(MOCK_TWO_KEY));
         Map<String, String> proMap = new HashMap<>();
@@ -84,7 +86,7 @@
     }
 
     @Test
-    public void testGetInt() {
+    void testGetInt() {
         memConfig.addProperty("a", "1");
         Assertions.assertEquals(1, memConfig.getInt("a"));
         Assertions.assertEquals(Integer.valueOf(1), memConfig.getInteger("a", 2));
@@ -92,13 +94,63 @@
     }
 
     @Test
-    public void getBoolean() {
+    void getBoolean() {
         memConfig.addProperty("a", Boolean.TRUE.toString());
         Assertions.assertTrue(memConfig.getBoolean("a"));
         Assertions.assertFalse(memConfig.getBoolean("b", false));
         Assertions.assertTrue(memConfig.getBoolean("b", Boolean.TRUE));
     }
 
+    @Test
+    void testIllegalType() {
+        memConfig.addProperty("it", "aaa");
+
+        Assertions.assertThrows(IllegalStateException.class, () -> memConfig.getInteger("it", 1));
+        Assertions.assertThrows(IllegalStateException.class, () -> memConfig.getInt("it", 1));
+        Assertions.assertThrows(IllegalStateException.class, () -> memConfig.getInt("it"));
+    }
+
+    @Test
+    void testDoesNotExist() {
+        Assertions.assertThrows(NoSuchElementException.class, () -> memConfig.getInt("ne"));
+        Assertions.assertThrows(NoSuchElementException.class, () -> memConfig.getBoolean("ne"));
+    }
+
+    @Test
+    void testConversions() {
+        memConfig.addProperty("long", "2147483648");
+        memConfig.addProperty("byte", "127");
+        memConfig.addProperty("short", "32767");
+        memConfig.addProperty("float", "3.14");
+        memConfig.addProperty("double", "3.14159265358979323846264338327950");
+        memConfig.addProperty("enum", "FIELD");
+
+        Object longObject = memConfig.convert(Long.class, "long", 1L);
+        Object byteObject = memConfig.convert(Byte.class, "byte", (byte) 1);
+        Object shortObject = memConfig.convert(Short.class, "short", (short) 1);
+        Object floatObject = memConfig.convert(Float.class, "float", 3.14F);
+        Object doubleObject = memConfig.convert(Double.class, "double", 3.14159265358979323846264338327950);
+        JavaBeanAccessor javaBeanAccessor = memConfig.convert(JavaBeanAccessor.class, "enum", JavaBeanAccessor.ALL);
+
+        Assertions.assertEquals(Long.class, longObject.getClass());
+        Assertions.assertEquals(2147483648L, longObject);
+
+        Assertions.assertEquals(Byte.class, byteObject.getClass());
+        Assertions.assertEquals((byte) 127, byteObject);
+
+        Assertions.assertEquals(Short.class, shortObject.getClass());
+        Assertions.assertEquals((short) 32767, shortObject);
+
+        Assertions.assertEquals(Float.class, floatObject.getClass());
+        Assertions.assertEquals(3.14F, floatObject);
+
+        Assertions.assertEquals(Double.class, doubleObject.getClass());
+        Assertions.assertEquals(3.14159265358979323846264338327950, doubleObject);
+
+        Assertions.assertEquals(JavaBeanAccessor.class, javaBeanAccessor.getClass());
+        Assertions.assertEquals(JavaBeanAccessor.FIELD, javaBeanAccessor);
+    }
+
     /**
      * Clean.
      */
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
index 2fd6516..cb34f71 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
@@ -42,8 +42,8 @@
 /**
  * {@link FileSystemDynamicConfiguration} Test
  */
-// Test often failed on Github Actions Platform because of file system on Azure
-// Change to Disabled because DisabledIfEnvironmentVariable does not work on Github.
+// Test often failed on GitHub Actions Platform because of file system on Azure
+// Change to Disabled because DisabledIfEnvironmentVariable does not work on GitHub.
 @Disabled
 public class FileSystemDynamicConfigurationTest {
 
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/logger/LoggerFactoryTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/logger/LoggerFactoryTest.java
index c83765f..70a7ce8 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/logger/LoggerFactoryTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/logger/LoggerFactoryTest.java
@@ -68,4 +68,12 @@
 
         assertThat(logger1, is(logger2));
     }
+
+    @Test
+    public void shouldReturnSameErrorTypeAwareLogger() {
+        ErrorTypeAwareLogger logger1 = LoggerFactory.getErrorTypeAwareLogger(this.getClass().getName());
+        ErrorTypeAwareLogger logger2 = LoggerFactory.getErrorTypeAwareLogger(this.getClass().getName());
+
+        assertThat(logger1, is(logger2));
+    }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLoggerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLoggerTest.java
new file mode 100644
index 0000000..6eb3605
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/logger/support/FailsafeErrorTypeAwareLoggerTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.logger.support;
+
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for FailsafeErrorTypeAwareLogger to test whether it 'ignores' exceptions thrown by logger or not.
+ */
+public class FailsafeErrorTypeAwareLoggerTest {
+    @Test
+    public void testFailsafeErrorTypeAwareForLoggingMethod() {
+        Logger failLogger = mock(Logger.class);
+        FailsafeErrorTypeAwareLogger failsafeLogger = new FailsafeErrorTypeAwareLogger(failLogger);
+
+        doThrow(new RuntimeException()).when(failLogger).error(anyString());
+        doThrow(new RuntimeException()).when(failLogger).warn(anyString());
+        doThrow(new RuntimeException()).when(failLogger).info(anyString());
+        doThrow(new RuntimeException()).when(failLogger).debug(anyString());
+        doThrow(new RuntimeException()).when(failLogger).trace(anyString());
+
+        failsafeLogger.error("1-1", "Registry center", "May be it's offline.", "error");
+        failsafeLogger.warn("1-1", "Registry center", "May be it's offline.", "warn");
+
+        doThrow(new RuntimeException()).when(failLogger).error(any(Throwable.class));
+        doThrow(new RuntimeException()).when(failLogger).warn(any(Throwable.class));
+        doThrow(new RuntimeException()).when(failLogger).info(any(Throwable.class));
+        doThrow(new RuntimeException()).when(failLogger).debug(any(Throwable.class));
+        doThrow(new RuntimeException()).when(failLogger).trace(any(Throwable.class));
+
+        failsafeLogger.error("1-1", "Registry center", "May be it's offline.", "error", new Exception("error"));
+        failsafeLogger.warn("1-1", "Registry center", "May be it's offline.", "warn", new Exception("warn"));
+    }
+
+    @Test
+    public void testSuccessLogger() {
+        Logger successLogger = mock(Logger.class);
+        FailsafeErrorTypeAwareLogger failsafeLogger = new FailsafeErrorTypeAwareLogger(successLogger);
+
+        failsafeLogger.error("1-1", "Registry center", "May be it's offline.", "error");
+        failsafeLogger.warn("1-1", "Registry center", "May be it's offline.", "warn");
+
+        verify(successLogger).error(anyString());
+        verify(successLogger).warn(anyString());
+
+        failsafeLogger.error("1-1", "Registry center", "May be it's offline.", "error", new Exception("error"));
+        failsafeLogger.warn("1-1", "Registry center", "May be it's offline.", "warn", new Exception("warn"));
+    }
+
+    @Test
+    public void testGetLogger() {
+        Assertions.assertThrows(RuntimeException.class, () -> {
+            Logger failLogger = mock(Logger.class);
+            FailsafeErrorTypeAwareLogger failsafeLogger = new FailsafeErrorTypeAwareLogger(failLogger);
+
+            doThrow(new RuntimeException()).when(failLogger).error(anyString());
+            failsafeLogger.getLogger().error("should get error");
+        });
+    }
+
+    @Test
+    public void testInstructionShownOrNot() {
+        LoggerFactory.setLoggerAdapter(FrameworkModel.defaultModel(), "jdk");
+
+        ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(FailsafeErrorTypeAwareLoggerTest.class);
+
+        logger.error("1-1", "Registry center", "May be it's offline.",
+            "error message", new Exception("error"));
+
+        logger.error("-1", "Registry center", "May be it's offline.",
+            "error message", new Exception("error"));
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollectorTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollectorTest.java
new file mode 100644
index 0000000..1dcd227
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/collector/DefaultMetricsCollectorTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.metrics.collector;
+
+import org.apache.dubbo.common.metrics.event.MetricsEvent;
+import org.apache.dubbo.common.metrics.event.RTEvent;
+import org.apache.dubbo.common.metrics.event.RequestEvent;
+import org.apache.dubbo.common.metrics.listener.MetricsListener;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_METHOD_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_VERSION_KEY;
+
+public class DefaultMetricsCollectorTest {
+
+    private ApplicationModel applicationModel;
+    private String interfaceName;
+    private String methodName;
+    private String group;
+    private String version;
+
+    @BeforeEach
+    public void setup() {
+        ApplicationConfig config = new ApplicationConfig();
+        config.setName("MockMetrics");
+
+        applicationModel = ApplicationModel.defaultModel();
+        applicationModel.getApplicationConfigManager().setApplication(config);
+
+        interfaceName = "org.apache.dubbo.MockInterface";
+        methodName = "mockMethod";
+        group = "mockGroup";
+        version = "1.0.0";
+    }
+
+    @AfterEach
+    public void teardown() {
+        applicationModel.destroy();
+    }
+
+    @Test
+    public void testRequestsMetrics() {
+        DefaultMetricsCollector collector = new DefaultMetricsCollector(applicationModel);
+        collector.setCollectEnabled(true);
+        collector.increaseTotalRequests(interfaceName, methodName, group, version);
+        collector.increaseProcessingRequests(interfaceName, methodName, group, version);
+        collector.increaseSucceedRequests(interfaceName, methodName, group, version);
+        collector.increaseFailedRequests(interfaceName, methodName, group, version);
+
+        List<MetricSample> samples = collector.collect();
+        for (MetricSample sample : samples) {
+            Assertions.assertTrue(sample instanceof GaugeMetricSample);
+            GaugeMetricSample gaugeSample = (GaugeMetricSample) sample;
+            Map<String, String> tags = gaugeSample.getTags();
+            Supplier<Number> supplier = gaugeSample.getSupplier();
+
+            Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), interfaceName);
+            Assertions.assertEquals(tags.get(TAG_METHOD_KEY), methodName);
+            Assertions.assertEquals(tags.get(TAG_GROUP_KEY), group);
+            Assertions.assertEquals(tags.get(TAG_VERSION_KEY), version);
+            Assertions.assertEquals(supplier.get().longValue(), 1);
+        }
+
+        collector.decreaseProcessingRequests(interfaceName, methodName, group, version);
+        samples = collector.collect();
+        Map<String, Long> sampleMap = samples.stream().collect(Collectors.toMap(MetricSample::getName, k -> {
+            Number number = ((GaugeMetricSample) k).getSupplier().get();
+            return number.longValue();
+        }));
+
+        Assertions.assertEquals(sampleMap.get("requests.processing"), 0L);
+    }
+
+    @Test
+    public void testRTMetrics() {
+        DefaultMetricsCollector collector = new DefaultMetricsCollector(applicationModel);
+        collector.setCollectEnabled(true);
+        collector.addRT(interfaceName, methodName, group, version, 10L);
+        collector.addRT(interfaceName, methodName, group, version, 0L);
+
+        List<MetricSample> samples = collector.collect();
+        for (MetricSample sample : samples) {
+            Map<String, String> tags = sample.getTags();
+
+            Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), interfaceName);
+            Assertions.assertEquals(tags.get(TAG_METHOD_KEY), methodName);
+            Assertions.assertEquals(tags.get(TAG_GROUP_KEY), group);
+            Assertions.assertEquals(tags.get(TAG_VERSION_KEY), version);
+        }
+
+        Map<String, Long> sampleMap = samples.stream().collect(Collectors.toMap(MetricSample::getName, k -> {
+            Number number = ((GaugeMetricSample) k).getSupplier().get();
+            return number.longValue();
+        }));
+
+        Assertions.assertEquals(sampleMap.get("rt.last"), 0L);
+        Assertions.assertEquals(sampleMap.get("rt.min"), 0L);
+        Assertions.assertEquals(sampleMap.get("rt.max"), 10L);
+        Assertions.assertEquals(sampleMap.get("rt.avg"), 5L);
+        Assertions.assertEquals(sampleMap.get("rt.total"), 10L);
+    }
+
+    @Test
+    public void testListener() {
+        DefaultMetricsCollector collector = new DefaultMetricsCollector(applicationModel);
+        collector.setCollectEnabled(true);
+
+        MockListener mockListener = new MockListener();
+        collector.addListener(mockListener);
+
+        collector.increaseTotalRequests(interfaceName, methodName, group, version);
+        Assertions.assertNotNull(mockListener.getCurEvent());
+        Assertions.assertTrue(mockListener.getCurEvent() instanceof RequestEvent);
+        Assertions.assertEquals(((RequestEvent) mockListener.getCurEvent()).getType(), RequestEvent.Type.TOTAL);
+
+        collector.addRT(interfaceName, methodName, group, version, 5L);
+        Assertions.assertTrue(mockListener.getCurEvent() instanceof RTEvent);
+        Assertions.assertEquals(((RTEvent) mockListener.getCurEvent()).getRt(), 5L);
+    }
+
+    static class MockListener implements MetricsListener {
+
+        private MetricsEvent curEvent;
+
+        @Override
+        public void onEvent(MetricsEvent event) {
+            curEvent = event;
+        }
+
+        public MetricsEvent getCurEvent() {
+            return curEvent;
+        }
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RTEventTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RTEventTest.java
new file mode 100644
index 0000000..fea3e0e
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RTEventTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.metrics.event;
+
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class RTEventTest {
+
+    @Test
+    public void testNewEvent() {
+        MethodMetric metric = new MethodMetric();
+        Long rt = 5L;
+        RTEvent event = new RTEvent(metric, rt);
+
+        Assertions.assertEquals(event.getSource(), metric);
+        Assertions.assertEquals(event.getRt(), rt);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RequestEventTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RequestEventTest.java
new file mode 100644
index 0000000..3da2f3a
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/event/RequestEventTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.metrics.event;
+
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class RequestEventTest {
+
+    @Test
+    public void testNewEvent() {
+        MethodMetric metric = new MethodMetric();
+        RequestEvent.Type type = RequestEvent.Type.TOTAL;
+        RequestEvent event = new RequestEvent(metric, type);
+
+        Assertions.assertEquals(event.getSource(), metric);
+        Assertions.assertEquals(event.getType(), type);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/MethodMetricTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/MethodMetricTest.java
new file mode 100644
index 0000000..f4dbcf2
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/MethodMetricTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.metrics.model;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.*;
+import static org.apache.dubbo.common.utils.NetUtils.getLocalHost;
+import static org.apache.dubbo.common.utils.NetUtils.getLocalHostName;
+
+public class MethodMetricTest {
+
+    private static final String applicationName = null;
+    private static String interfaceName;
+    private static String methodName;
+    private static String group;
+    private static String version;
+
+    @BeforeAll
+    public static void setup() {
+        interfaceName = "org.apache.dubbo.MockInterface";
+        methodName = "mockMethod";
+        group = "mockGroup";
+        version = "1.0.0";
+    }
+
+    @Test
+    public void test() {
+        MethodMetric metric = new MethodMetric(applicationName, interfaceName, methodName, group, version);
+        Assertions.assertEquals(metric.getInterfaceName(), interfaceName);
+        Assertions.assertEquals(metric.getMethodName(), methodName);
+        Assertions.assertEquals(metric.getGroup(), group);
+        Assertions.assertEquals(metric.getVersion(), version);
+
+        Map<String, String> tags = metric.getTags();
+        Assertions.assertEquals(tags.get(TAG_IP), getLocalHost());
+        Assertions.assertEquals(tags.get(TAG_HOSTNAME), getLocalHostName());
+        Assertions.assertEquals(tags.get(TAG_APPLICATION_NAME), applicationName);
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), interfaceName);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), methodName);
+        Assertions.assertEquals(tags.get(TAG_GROUP_KEY), group);
+        Assertions.assertEquals(tags.get(TAG_VERSION_KEY), version);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSampleTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSampleTest.java
new file mode 100644
index 0000000..d45ee03
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/GaugeMetricSampleTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.common.metrics.model.sample;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class GaugeMetricSampleTest {
+
+    private static String name;
+    private static String description;
+    private static Map<String, String> tags;
+    private static MetricsCategory category;
+    private static String baseUnit;
+    private static Supplier<Number> supplier;
+
+    @BeforeAll
+    public static void setup() {
+        name = "test";
+        description = "test";
+        tags = new HashMap<>();
+        category = MetricsCategory.REQUESTS;
+        baseUnit = "byte";
+        supplier = () -> 1;
+    }
+
+    @Test
+    public void test() {
+        GaugeMetricSample sample = new GaugeMetricSample(name, description, tags, category, baseUnit, supplier);
+        Assertions.assertEquals(sample.getName(), name);
+        Assertions.assertEquals(sample.getDescription(), description);
+        Assertions.assertEquals(sample.getTags(), tags);
+        Assertions.assertEquals(sample.getType(), MetricSample.Type.GAUGE);
+        Assertions.assertEquals(sample.getCategory(), category);
+        Assertions.assertEquals(sample.getBaseUnit(), baseUnit);
+        Assertions.assertEquals(sample.getSupplier().get(), 1);
+        sample.setSupplier(() -> 2);
+        Assertions.assertEquals(sample.getSupplier().get(), 2);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/MetricSampleTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/MetricSampleTest.java
new file mode 100644
index 0000000..78b5753
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/model/sample/MetricSampleTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.metrics.model.sample;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MetricSampleTest {
+
+    private static String name;
+    private static String description;
+    private static Map<String, String> tags;
+    private static MetricSample.Type type;
+    private static MetricsCategory category;
+    private static String baseUnit;
+
+    @BeforeAll
+    public static void setup() {
+        name = "test";
+        description = "test";
+        tags = new HashMap<>();
+        type = MetricSample.Type.GAUGE;
+        category = MetricsCategory.REQUESTS;
+        baseUnit = "byte";
+    }
+
+    @Test
+    public void test() {
+        MetricSample sample = new MetricSample(name, description, tags, type, category, baseUnit);
+        Assertions.assertEquals(sample.getName(), name);
+        Assertions.assertEquals(sample.getDescription(), description);
+        Assertions.assertEquals(sample.getTags(), tags);
+        Assertions.assertEquals(sample.getType(), type);
+        Assertions.assertEquals(sample.getCategory(), category);
+        Assertions.assertEquals(sample.getBaseUnit(), baseUnit);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/service/MetricsEntityTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/service/MetricsEntityTest.java
new file mode 100644
index 0000000..c2688ce
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/metrics/service/MetricsEntityTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.common.metrics.service;
+
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MetricsEntityTest {
+
+    private static String name;
+    private static Map<String, String> tags;
+    private static MetricsCategory category;
+    private static Object value;
+
+    @BeforeAll
+    public static void setup() {
+        name = "test";
+        tags = new HashMap<>();
+        category = MetricsCategory.REQUESTS;
+        value = 1;
+    }
+
+    @Test
+    public void test() {
+        MetricsEntity entity = new MetricsEntity(name, tags, category, value);
+        Assertions.assertEquals(entity.getName(), name);
+        Assertions.assertEquals(entity.getTags(), tags);
+        Assertions.assertEquals(entity.getCategory(), category);
+        Assertions.assertEquals(entity.getValue(), value);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/NetUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/NetUtilsTest.java
index 9719191..79f01df 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/NetUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/NetUtilsTest.java
@@ -42,51 +42,51 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-public class NetUtilsTest {
+class NetUtilsTest {
 
     @Test
-    public void testGetRandomPort() throws Exception {
+    void testGetRandomPort() throws Exception {
         assertThat(NetUtils.getRandomPort(), greaterThanOrEqualTo(30000));
         assertThat(NetUtils.getRandomPort(), greaterThanOrEqualTo(30000));
         assertThat(NetUtils.getRandomPort(), greaterThanOrEqualTo(30000));
     }
 
     @Test
-    public void testGetAvailablePort() throws Exception {
+    void testGetAvailablePort() throws Exception {
         assertThat(NetUtils.getAvailablePort(), greaterThan(0));
         assertThat(NetUtils.getAvailablePort(12345), greaterThanOrEqualTo(12345));
         assertThat(NetUtils.getAvailablePort(-1), greaterThanOrEqualTo(0));
     }
 
     @Test
-    public void testValidAddress() throws Exception {
+    void testValidAddress() throws Exception {
         assertTrue(NetUtils.isValidAddress("10.20.130.230:20880"));
         assertFalse(NetUtils.isValidAddress("10.20.130.230"));
         assertFalse(NetUtils.isValidAddress("10.20.130.230:666666"));
     }
 
     @Test
-    public void testIsInvalidPort() throws Exception {
+    void testIsInvalidPort() throws Exception {
         assertTrue(NetUtils.isInvalidPort(0));
         assertTrue(NetUtils.isInvalidPort(65536));
         assertFalse(NetUtils.isInvalidPort(1024));
     }
 
     @Test
-    public void testIsLocalHost() throws Exception {
+    void testIsLocalHost() throws Exception {
         assertTrue(NetUtils.isLocalHost("localhost"));
         assertTrue(NetUtils.isLocalHost("127.1.2.3"));
         assertFalse(NetUtils.isLocalHost("128.1.2.3"));
     }
 
     @Test
-    public void testIsAnyHost() throws Exception {
+    void testIsAnyHost() throws Exception {
         assertTrue(NetUtils.isAnyHost("0.0.0.0"));
         assertFalse(NetUtils.isAnyHost("1.1.1.1"));
     }
 
     @Test
-    public void testIsInvalidLocalHost() throws Exception {
+    void testIsInvalidLocalHost() throws Exception {
         assertTrue(NetUtils.isInvalidLocalHost(null));
         assertTrue(NetUtils.isInvalidLocalHost(""));
         assertTrue(NetUtils.isInvalidLocalHost("localhost"));
@@ -97,13 +97,13 @@
     }
 
     @Test
-    public void testIsValidLocalHost() throws Exception {
+    void testIsValidLocalHost() throws Exception {
         assertTrue(NetUtils.isValidLocalHost("1.2.3.4"));
         assertTrue(NetUtils.isValidLocalHost("128.0.0.1"));
     }
 
     @Test
-    public void testGetLocalSocketAddress() throws Exception {
+    void testGetLocalSocketAddress() throws Exception {
         InetSocketAddress address = NetUtils.getLocalSocketAddress("localhost", 12345);
         assertTrue(address.getAddress().isAnyLocalAddress());
         assertEquals(address.getPort(), 12345);
@@ -113,7 +113,7 @@
     }
 
     @Test
-    public void testIsValidAddress() throws Exception {
+    void testIsValidAddress() throws Exception {
         assertFalse(NetUtils.isValidV4Address((InetAddress) null));
         InetAddress address = mock(InetAddress.class);
         when(address.isLoopbackAddress()).thenReturn(true);
@@ -133,19 +133,19 @@
     }
 
     @Test
-    public void testGetLocalHost() throws Exception {
+    void testGetLocalHost() throws Exception {
         assertNotNull(NetUtils.getLocalHost());
     }
 
     @Test
-    public void testGetLocalAddress() throws Exception {
+    void testGetLocalAddress() throws Exception {
         InetAddress address = NetUtils.getLocalAddress();
         assertNotNull(address);
         assertTrue(NetUtils.isValidLocalHost(address.getHostAddress()));
     }
 
     @Test
-    public void testFilterLocalHost() throws Exception {
+    void testFilterLocalHost() throws Exception {
         assertNull(NetUtils.filterLocalHost(null));
         assertEquals(NetUtils.filterLocalHost(""), "");
         String host = NetUtils.filterLocalHost("dubbo://127.0.0.1:8080/foo");
@@ -159,18 +159,18 @@
     }
 
     @Test
-    public void testGetHostName() throws Exception {
+    void testGetHostName() throws Exception {
         assertNotNull(NetUtils.getHostName("127.0.0.1"));
     }
 
     @Test
-    public void testGetIpByHost() throws Exception {
+    void testGetIpByHost() throws Exception {
         assertThat(NetUtils.getIpByHost("localhost"), equalTo("127.0.0.1"));
         assertThat(NetUtils.getIpByHost("dubbo.local"), equalTo("dubbo.local"));
     }
 
     @Test
-    public void testToAddressString() throws Exception {
+    void testToAddressString() throws Exception {
         InetAddress address = mock(InetAddress.class);
         when(address.getHostAddress()).thenReturn("dubbo");
         InetSocketAddress socketAddress = new InetSocketAddress(address, 1234);
@@ -178,7 +178,7 @@
     }
 
     @Test
-    public void testToAddress() throws Exception {
+    void testToAddress() throws Exception {
         InetSocketAddress address = NetUtils.toAddress("localhost:1234");
         assertThat(address.getHostName(), equalTo("localhost"));
         assertThat(address.getPort(), equalTo(1234));
@@ -188,13 +188,13 @@
     }
 
     @Test
-    public void testToURL() throws Exception {
+    void testToURL() throws Exception {
         String url = NetUtils.toURL("dubbo", "host", 1234, "foo");
         assertThat(url, equalTo("dubbo://host:1234/foo"));
     }
 
     @Test
-    public void testIsValidV6Address() {
+    void testIsValidV6Address() {
         String saved = System.getProperty("java.net.preferIPv6Addresses", "false");
         System.setProperty("java.net.preferIPv6Addresses", "true");
 
@@ -212,11 +212,11 @@
      * Mockito starts to support mocking final classes since 2.1.0
      * see https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable
      * But enable it will cause other UT to fail.
-     * Therefore currently disabling this UT.
+     * Therefore, currently disabling this UT.
      */
     @Disabled
     @Test
-    public void testNormalizeV6Address() {
+    void testNormalizeV6Address() {
         Inet6Address address = mock(Inet6Address.class);
         when(address.getHostAddress()).thenReturn("fe80:0:0:0:894:aeec:f37d:23e1%en0");
         when(address.getScopeId()).thenReturn(5);
@@ -225,7 +225,7 @@
     }
 
     @Test
-    public void testMatchIpRangeMatchWhenIpv4() throws UnknownHostException {
+    void testMatchIpRangeMatchWhenIpv4() throws UnknownHostException {
         assertTrue(NetUtils.matchIpRange("*.*.*.*", "192.168.1.63", 90));
         assertTrue(NetUtils.matchIpRange("192.168.1.*", "192.168.1.63", 90));
         assertTrue(NetUtils.matchIpRange("192.168.1.63", "192.168.1.63", 90));
@@ -235,7 +235,7 @@
     }
 
     @Test
-    public void testMatchIpRangeMatchWhenIpv6() throws UnknownHostException {
+    void testMatchIpRangeMatchWhenIpv6() throws UnknownHostException {
         assertTrue(NetUtils.matchIpRange("*.*.*.*", "192.168.1.63", 90));
         assertTrue(NetUtils.matchIpRange("234e:0:4567:0:0:0:3d:*", "234e:0:4567::3d:ff", 90));
         assertTrue(NetUtils.matchIpRange("234e:0:4567:0:0:0:3d:ee", "234e:0:4567::3d:ee", 90));
@@ -248,7 +248,7 @@
     }
 
     @Test
-    public void testMatchIpRangeMatchWhenIpv6Exception() throws UnknownHostException {
+    void testMatchIpRangeMatchWhenIpv6Exception() throws UnknownHostException {
         IllegalArgumentException thrown =
             assertThrows(IllegalArgumentException.class, () ->
                 NetUtils.matchIpRange("234e:0:4567::3d:*", "234e:0:4567::3d:ff", 90));
@@ -265,7 +265,7 @@
     }
 
     @Test
-    public void testMatchIpRangeMatchWhenIpWrongException() throws UnknownHostException {
+    void testMatchIpRangeMatchWhenIpWrongException() throws UnknownHostException {
         UnknownHostException thrown =
             assertThrows(UnknownHostException.class, () ->
                 NetUtils.matchIpRange("192.168.1.63", "192.168.1.ff", 90));
@@ -273,13 +273,13 @@
     }
 
     @Test
-    public void testMatchIpMatch() throws UnknownHostException {
+    void testMatchIpMatch() throws UnknownHostException {
         assertTrue(NetUtils.matchIpExpression("192.168.1.*", "192.168.1.63", 90));
         assertTrue(NetUtils.matchIpExpression("192.168.1.192/26", "192.168.1.199", 90));
     }
 
     @Test
-    public void testMatchIpv6WithIpPort() throws UnknownHostException {
+    void testMatchIpv6WithIpPort() throws UnknownHostException {
         assertTrue(NetUtils.matchIpRange("[234e:0:4567::3d:ee]", "234e:0:4567::3d:ee", 8090));
         assertTrue(NetUtils.matchIpRange("[234e:0:4567:0:0:0:3d:ee]", "234e:0:4567::3d:ee", 8090));
         assertTrue(NetUtils.matchIpRange("[234e:0:4567:0:0:0:3d:ee]:8090", "234e:0:4567::3d:ee", 8090));
@@ -292,7 +292,7 @@
     }
 
     @Test
-    public void testMatchIpv4WithIpPort() throws UnknownHostException {
+    void testMatchIpv4WithIpPort() throws UnknownHostException {
         NumberFormatException thrown =
             assertThrows(NumberFormatException.class, () -> NetUtils.matchIpExpression("192.168.1.192/26:90", "192.168.1.199", 90));
         assertTrue(thrown instanceof NumberFormatException);
@@ -314,25 +314,25 @@
     }
 
     @Test
-    public void testLocalHost() {
+    void testLocalHost() {
         assertEquals(NetUtils.getLocalHost(), NetUtils.getLocalAddress().getHostAddress());
         assertTrue(NetUtils.isValidLocalHost(NetUtils.getLocalHost()));
         assertFalse(NetUtils.isInvalidLocalHost(NetUtils.getLocalHost()));
     }
 
     @Test
-    public void testIsMulticastAddress() {
+    void testIsMulticastAddress() {
         assertTrue(NetUtils.isMulticastAddress("224.0.0.1"));
         assertFalse(NetUtils.isMulticastAddress("127.0.0.1"));
     }
 
     @Test
-    public void testFindNetworkInterface() {
+    void testFindNetworkInterface() {
         assertNotNull(NetUtils.findNetworkInterface());
     }
 
     @Test
-    public void testIgnoreAllInterfaces() {
+    void testIgnoreAllInterfaces() {
         // store the origin ignored interfaces
         String originIgnoredInterfaces = this.getIgnoredInterfaces();
         try {
@@ -346,7 +346,7 @@
     }
 
     @Test
-    public void testIgnoreGivenInterface() {
+    void testIgnoreGivenInterface() {
         // store the origin ignored interfaces
         String originIgnoredInterfaces = this.getIgnoredInterfaces();
         try {
@@ -365,7 +365,7 @@
     }
 
     @Test
-    public void testIgnoreGivenPrefixInterfaceName() {
+    void testIgnoreGivenPrefixInterfaceName() {
         // store the origin ignored interfaces
         String originIgnoredInterfaces = this.getIgnoredInterfaces();
         try {
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
index e615dd9..6a0f1ef 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
@@ -32,6 +32,9 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -760,6 +763,22 @@
         assertEquals(setResult, setStr);
     }
 
+    @Test
+    public void testJava8Time() {
+        
+        Object localDateTimeGen = PojoUtils.generalize(LocalDateTime.now());
+        Object localDateTime = PojoUtils.realize(localDateTimeGen, LocalDateTime.class);
+        assertEquals(localDateTimeGen, localDateTime.toString());
+
+        Object localDateGen = PojoUtils.generalize(LocalDate.now());
+        Object localDate = PojoUtils.realize(localDateGen, LocalDate.class);
+        assertEquals(localDateGen, localDate.toString());
+
+        Object localTimeGen = PojoUtils.generalize(LocalTime.now());
+        Object localTime = PojoUtils.realize(localTimeGen, LocalTime.class);
+        assertEquals(localTimeGen, localTime.toString());
+    }
+
     public enum Day {
         SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
     }
diff --git a/dubbo-compatible/pom.xml b/dubbo-compatible/pom.xml
index d520448..bbadadb 100644
--- a/dubbo-compatible/pom.xml
+++ b/dubbo-compatible/pom.xml
@@ -66,6 +66,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-registry-multicast</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
diff --git a/dubbo-compiler/src/main/java/org/apache/dubbo/gen/tri/reactive/ReactorDubbo3TripleGenerator.java b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/tri/reactive/ReactorDubbo3TripleGenerator.java
new file mode 100644
index 0000000..dfc8d86
--- /dev/null
+++ b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/tri/reactive/ReactorDubbo3TripleGenerator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.gen.tri.reactive;
+
+import com.salesforce.jprotoc.ProtocPlugin;
+import org.apache.dubbo.gen.AbstractGenerator;
+
+public class ReactorDubbo3TripleGenerator extends AbstractGenerator {
+
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            ProtocPlugin.generate(new ReactorDubbo3TripleGenerator());
+        } else {
+            ProtocPlugin.debug(new ReactorDubbo3TripleGenerator(), args[0]);
+        }
+    }
+
+    @Override
+    protected String getClassPrefix() {
+        return "Dubbo";
+    }
+
+    @Override
+    protected String getClassSuffix() {
+        return "Triple";
+    }
+
+    @Override
+    protected String getTemplateFileName() {
+        return "ReactorDubbo3TripleStub.mustache";
+    }
+
+    @Override
+    protected String getInterfaceTemplateFileName() {
+        return "ReactorDubbo3TripleInterfaceStub.mustache";
+    }
+
+    @Override
+    protected String getSingleTemplateFileName() {
+        throw new IllegalStateException("Do not support single template!");
+    }
+
+    @Override
+    protected boolean enableMultipleTemplateFiles() {
+        return true;
+    }
+}
diff --git a/dubbo-compiler/src/main/resources/ReactorDubbo3TripleInterfaceStub.mustache b/dubbo-compiler/src/main/resources/ReactorDubbo3TripleInterfaceStub.mustache
new file mode 100644
index 0000000..b3b9008
--- /dev/null
+++ b/dubbo-compiler/src/main/resources/ReactorDubbo3TripleInterfaceStub.mustache
@@ -0,0 +1,41 @@
+/*
+* 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.
+*/
+
+{{#packageName}}
+package {{packageName}};
+{{/packageName}}
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface {{interfaceClassName}} {
+
+    String JAVA_SERVICE_NAME = "{{packageName}}.{{serviceName}}";
+
+    String SERVICE_NAME = "{{commonPackageName}}.{{serviceName}}";
+
+{{#methods}}
+    {{#javaDoc}}
+        {{{javaDoc}}}
+    {{/javaDoc}}
+    {{#deprecated}}
+        @java.lang.Deprecated
+    {{/deprecated}}
+    {{#isManyOutput}}Flux{{/isManyOutput}}{{^isManyOutput}}Mono{{/isManyOutput}}<{{outputType}}> {{methodName}}({{#isManyInput}}Flux{{/isManyInput}}{{^isManyInput}}Mono{{/isManyInput}}<{{inputType}}> reactorRequest) ;
+
+{{/methods}}
+}
diff --git a/dubbo-compiler/src/main/resources/ReactorDubbo3TripleStub.mustache b/dubbo-compiler/src/main/resources/ReactorDubbo3TripleStub.mustache
new file mode 100644
index 0000000..afc799b
--- /dev/null
+++ b/dubbo-compiler/src/main/resources/ReactorDubbo3TripleStub.mustache
@@ -0,0 +1,180 @@
+/*
+* 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.
+*/
+
+{{#packageName}}
+package {{packageName}};
+{{/packageName}}
+
+import com.google.protobuf.Message;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.PathResolver;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.ServerService;
+import org.apache.dubbo.rpc.TriRpcStatus;
+import org.apache.dubbo.rpc.model.MethodDescriptor;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
+import org.apache.dubbo.rpc.model.StubMethodDescriptor;
+import org.apache.dubbo.rpc.model.StubServiceDescriptor;
+import org.apache.dubbo.reactive.handler.ManyToManyMethodHandler;
+import org.apache.dubbo.reactive.handler.ManyToOneMethodHandler;
+import org.apache.dubbo.reactive.handler.OneToManyMethodHandler;
+import org.apache.dubbo.reactive.calls.ReactorClientCalls;
+import org.apache.dubbo.reactive.handler.OneToOneMethodHandler;
+
+import org.apache.dubbo.rpc.stub.StubInvoker;
+import org.apache.dubbo.rpc.stub.StubMethodHandler;
+import org.apache.dubbo.rpc.stub.StubSuppliers;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class {{className}} {
+
+    private {{className}}() {}
+
+    public static final String SERVICE_NAME = {{interfaceClassName}}.SERVICE_NAME;
+
+    private static final StubServiceDescriptor serviceDescriptor = new StubServiceDescriptor(SERVICE_NAME,{{interfaceClassName}}.class);
+
+    static {
+        StubSuppliers.addSupplier(SERVICE_NAME, {{className}}::newStub);
+        StubSuppliers.addSupplier({{interfaceClassName}}.JAVA_SERVICE_NAME,  {{className}}::newStub);
+        StubSuppliers.addDescriptor(SERVICE_NAME, serviceDescriptor);
+        StubSuppliers.addDescriptor({{interfaceClassName}}.JAVA_SERVICE_NAME, serviceDescriptor);
+    }
+
+    @SuppressWarnings("all")
+    public static {{interfaceClassName}} newStub(Invoker<?> invoker) {
+        return new {{interfaceClassName}}Stub((Invoker<{{interfaceClassName}}>)invoker);
+    }
+
+{{#unaryMethods}}
+    {{#javaDoc}}
+        {{{javaDoc}}}
+    {{/javaDoc}}
+    private static final StubMethodDescriptor {{methodName}}Method = new StubMethodDescriptor("{{originMethodName}}",
+        {{inputType}}.class, {{outputType}}.class, serviceDescriptor, MethodDescriptor.RpcType.UNARY,
+        obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
+        {{outputType}}::parseFrom);
+{{/unaryMethods}}
+
+{{#serverStreamingMethods}}
+    {{#javaDoc}}
+        {{{javaDoc}}}
+    {{/javaDoc}}
+    private static final StubMethodDescriptor {{methodName}}Method = new StubMethodDescriptor("{{originMethodName}}",
+        {{inputType}}.class, {{outputType}}.class, serviceDescriptor, MethodDescriptor.RpcType.SERVER_STREAM,
+        obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
+        {{outputType}}::parseFrom);
+{{/serverStreamingMethods}}
+
+{{#clientStreamingMethods}}
+    {{#javaDoc}}
+        {{{javaDoc}}}
+    {{/javaDoc}}
+    private static final StubMethodDescriptor {{methodName}}Method = new StubMethodDescriptor("{{originMethodName}}",
+        {{inputType}}.class, {{outputType}}.class, serviceDescriptor, MethodDescriptor.RpcType.CLIENT_STREAM,
+        obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
+        {{outputType}}::parseFrom);
+{{/clientStreamingMethods}}
+
+{{#biStreamingWithoutClientStreamMethods}}
+    {{#javaDoc}}
+        {{{javaDoc}}}
+    {{/javaDoc}}
+    private static final StubMethodDescriptor {{methodName}}Method = new StubMethodDescriptor("{{originMethodName}}",
+        {{inputType}}.class, {{outputType}}.class, serviceDescriptor, MethodDescriptor.RpcType.BI_STREAM,
+        obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
+        {{outputType}}::parseFrom);
+{{/biStreamingWithoutClientStreamMethods}}
+
+    public static class {{interfaceClassName}}Stub implements {{interfaceClassName}}{
+
+        private final Invoker<{{interfaceClassName}}> invoker;
+
+        public {{interfaceClassName}}Stub(Invoker<{{interfaceClassName}}> invoker) {
+            this.invoker = invoker;
+        }
+
+    {{#methods}}
+        {{#javaDoc}}
+            {{{javaDoc}}}
+        {{/javaDoc}}
+        {{#deprecated}}
+            @java.lang.Deprecated
+        {{/deprecated}}
+        public {{#isManyOutput}}Flux{{/isManyOutput}}{{^isManyOutput}}Mono{{/isManyOutput}}<{{outputType}}> {{methodName}}({{#isManyInput}}Flux{{/isManyInput}}{{^isManyInput}}Mono{{/isManyInput}}<{{inputType}}> request) {
+            return ReactorClientCalls.{{reactiveCallsMethodName}}(invoker, request, {{methodNameCamelCase}}Method);
+        }
+    {{/methods}}
+    }
+
+    public static abstract class {{interfaceClassName}}ImplBase implements {{interfaceClassName}}, ServerService<{{interfaceClassName}}> {
+
+        @Override
+        public final Invoker<{{interfaceClassName}}> getInvoker(URL url) {
+            PathResolver pathResolver = url.getOrDefaultFrameworkModel()
+            .getExtensionLoader(PathResolver.class)
+            .getDefaultExtension();
+            Map<String,StubMethodHandler<?, ?>> handlers = new HashMap<>();
+
+            {{#methods}}
+                pathResolver.addNativeStub( "/" + SERVICE_NAME + "/{{originMethodName}}" );
+            {{/methods}}
+
+            {{#unaryMethods}}
+                handlers.put({{methodName}}Method.getMethodName(), new OneToOneMethodHandler<>(this::{{methodName}}));
+            {{/unaryMethods}}
+            {{#serverStreamingMethods}}
+                handlers.put({{methodName}}Method.getMethodName(), new OneToManyMethodHandler<>(this::{{methodName}}));
+            {{/serverStreamingMethods}}
+            {{#clientStreamingMethods}}
+                handlers.put({{methodName}}Method.getMethodName(), new ManyToOneMethodHandler<>(this::{{methodName}}));
+            {{/clientStreamingMethods}}
+            {{#biStreamingWithoutClientStreamMethods}}
+                handlers.put({{methodName}}Method.getMethodName(), new ManyToManyMethodHandler<>(this::{{methodName}}));
+            {{/biStreamingWithoutClientStreamMethods}}
+
+            return new StubInvoker<>(this, url, {{interfaceClassName}}.class, handlers);
+        }
+
+    {{#methods}}
+        {{#javaDoc}}
+            {{{javaDoc}}}
+        {{/javaDoc}}
+        {{#deprecated}}
+            @java.lang.Deprecated
+        {{/deprecated}}
+        public {{#isManyOutput}}Flux{{/isManyOutput}}{{^isManyOutput}}Mono{{/isManyOutput}}<{{outputType}}> {{methodName}}({{#isManyInput}}Flux{{/isManyInput}}{{^isManyInput}}Mono{{/isManyInput}}<{{inputType}}> request) {
+            throw unimplementedMethodException({{methodName}}Method);
+        }
+    {{/methods}}
+
+        @Override
+        public final ServiceDescriptor getServiceDescriptor() {
+            return serviceDescriptor;
+        }
+
+        private RpcException unimplementedMethodException(StubMethodDescriptor methodDescriptor) {
+            return TriRpcStatus.UNIMPLEMENTED.withDescription(String.format("Method %s is unimplemented",
+            "/" + serviceDescriptor.getInterfaceName() + "/" + methodDescriptor.getMethodName())).asException();
+        }
+    }
+}
diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml
index 45de7cb..2baac24 100644
--- a/dubbo-config/dubbo-config-api/pom.xml
+++ b/dubbo-config/dubbo-config-api/pom.xml
@@ -98,6 +98,13 @@
 
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-registry-multicast</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
index ffdd3fa..6e023d3 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
@@ -18,13 +18,15 @@
 
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_SHUTDOWN_HOOK;
+
 /**
  * The shutdown hook thread to do the cleanup stuff.
  * This is a singleton in order to ensure there is only one shutdown hook registered.
@@ -33,7 +35,7 @@
  */
 public class DubboShutdownHook extends Thread {
 
-    private static final Logger logger = LoggerFactory.getLogger(DubboShutdownHook.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DubboShutdownHook.class);
 
     private final ApplicationModel applicationModel;
 
@@ -86,9 +88,9 @@
             try {
                 Runtime.getRuntime().addShutdownHook(this);
             } catch (IllegalStateException e) {
-                logger.warn("register shutdown hook failed: " + e.getMessage());
+                logger.warn(CONFIG_FAILED_SHUTDOWN_HOOK, "", "", "register shutdown hook failed: " + e.getMessage(), e);
             } catch (Exception e) {
-                logger.warn("register shutdown hook failed: " + e.getMessage(), e);
+                logger.warn(CONFIG_FAILED_SHUTDOWN_HOOK, "", "", "register shutdown hook failed: " + e.getMessage(), e);
             }
         }
     }
@@ -105,9 +107,9 @@
             try {
                 Runtime.getRuntime().removeShutdownHook(this);
             } catch (IllegalStateException e) {
-                logger.warn("unregister shutdown hook failed: " + e.getMessage());
+                logger.warn(CONFIG_FAILED_SHUTDOWN_HOOK, "", "", "unregister shutdown hook failed: " + e.getMessage(), e);
             } catch (Exception e) {
-                logger.warn("unregister shutdown hook failed: " + e.getMessage(), e);
+                logger.warn(CONFIG_FAILED_SHUTDOWN_HOOK, "", "", "unregister shutdown hook failed: " + e.getMessage(), e);
             }
         }
     }
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 931adce..4f81b5d 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
@@ -21,7 +21,7 @@
 import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.constants.RegistryConstants;
 import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.ArrayUtils;
@@ -59,6 +59,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
@@ -66,14 +67,26 @@
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR_CHAR;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_CLUSTER_DOMAIN;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_MESH_PORT;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.MESH_ENABLE;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROXY_CLASS_REF;
 import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_PATTERN;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SVC;
+import static org.apache.dubbo.common.constants.CommonConstants.TRIPLE;
+import static org.apache.dubbo.common.constants.CommonConstants.UNLOAD_CLUSTER_RELATED;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_NO_VALID_PROVIDER;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_DESTROY_INVOKER;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_NO_METHOD_FOUND;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_LOAD_ENV_VARIABLE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_PROPERTY_CONFLICT;
+import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDED_BY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
 import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
 import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
@@ -91,7 +104,7 @@
  */
 public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
 
-    public static final Logger logger = LoggerFactory.getLogger(ReferenceConfig.class);
+    public static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ReferenceConfig.class);
 
     /**
      * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
@@ -236,7 +249,7 @@
                 invoker.destroy();
             }
         } catch (Throwable t) {
-            logger.warn("Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", t);
+            logger.warn(CONFIG_FAILED_DESTROY_INVOKER, "", "", "Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", t);
         }
         invoker = null;
         ref = null;
@@ -247,7 +260,7 @@
     }
 
     protected synchronized void init() {
-        if (initialized && ref !=null ) {
+        if (initialized && ref != null) {
             return;
         }
         try {
@@ -275,7 +288,7 @@
                 serviceDescriptor = repository.registerService(interfaceClass);
             }
             consumerModel = new ConsumerModel(serviceMetadata.getServiceKey(), proxy, serviceDescriptor,
-                getScopeModel(), serviceMetadata, createAsyncMethodInfo(), interfaceClassLoader);
+                    getScopeModel(), serviceMetadata, createAsyncMethodInfo(), interfaceClassLoader);
 
             // Compatible with dependencies on ServiceModel#getReferenceConfig() , and will be removed in a future version.
             consumerModel.setConfig(this);
@@ -300,7 +313,7 @@
                     invoker.destroy();
                 }
             } catch (Throwable destroy) {
-                logger.warn("Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", destroy);
+                logger.warn(CONFIG_FAILED_DESTROY_INVOKER, "", "", "Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", t);
             }
             if (consumerModel != null) {
                 ModuleServiceRepository repository = getScopeModel().getServiceRepository();
@@ -313,6 +326,14 @@
             serviceMetadata.setTarget(null);
             serviceMetadata.getAttributeMap().remove(PROXY_CLASS_REF);
 
+            // Thrown by checkInvokerAvailable().
+            if (t.getClass() == IllegalStateException.class &&
+                t.getMessage().contains("No provider available for the service")) {
+
+                // 2-2 - No provider available.
+                logger.error(CLUSTER_NO_VALID_PROVIDER, "server crashed", "", "No provider available.", t);
+            }
+
             throw t;
         }
         initialized = true;
@@ -365,7 +386,7 @@
 
             String[] methods = methods(interfaceClass);
             if (methods.length == 0) {
-                logger.warn("No method found in service interface " + interfaceClass.getName());
+                logger.warn(CONFIG_NO_METHOD_FOUND, "", "", "No method found in service interface: " + interfaceClass.getName());
                 map.put(METHODS_KEY, ANY_VALUE);
             } else {
                 map.put(METHODS_KEY, StringUtils.join(new HashSet<>(Arrays.asList(methods)), COMMA_SEPARATOR));
@@ -383,7 +404,7 @@
             hostToRegistry = NetUtils.getLocalHost();
         } else if (isInvalidLocalHost(hostToRegistry)) {
             throw new IllegalArgumentException(
-                "Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
+                    "Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
         }
 
         map.put(REGISTER_IP_KEY, hostToRegistry);
@@ -410,6 +431,9 @@
             createInvokerForLocal(referenceParameters);
         } else {
             urls.clear();
+
+            meshModeHandleUrl(referenceParameters);
+
             if (StringUtils.isNotEmpty(url)) {
                 // user specified URL, could be peer-to-peer address, or register center's address.
                 parseUrl(referenceParameters);
@@ -424,12 +448,12 @@
 
         if (logger.isInfoEnabled()) {
             logger.info("Referred dubbo service: [" + referenceParameters.get(INTERFACE_KEY) + "]." +
-                (Boolean.parseBoolean(referenceParameters.get(GENERIC_KEY)) ?
-                    " it's GenericService reference" : " it's not GenericService reference"));
+                    (Boolean.parseBoolean(referenceParameters.get(GENERIC_KEY)) ?
+                            " it's GenericService reference" : " it's not GenericService reference"));
         }
 
         URL consumerUrl = new ServiceConfigURL(CONSUMER_PROTOCOL, referenceParameters.get(REGISTER_IP_KEY), 0,
-            referenceParameters.get(INTERFACE_KEY), referenceParameters);
+                referenceParameters.get(INTERFACE_KEY), referenceParameters);
         consumerUrl = consumerUrl.setScopeModel(getScopeModel());
         consumerUrl = consumerUrl.setServiceModel(consumerModel);
         MetadataUtils.publishServiceDefinition(consumerUrl, consumerModel.getServiceModel(), getApplicationModel());
@@ -439,6 +463,70 @@
     }
 
     /**
+     * if enable mesh mode, handle url.
+     *
+     * @param referenceParameters referenceParameters
+     */
+    private void meshModeHandleUrl(Map<String, String> referenceParameters) {
+        if (!checkMeshConfig(referenceParameters)) {
+            return;
+        }
+        if (StringUtils.isNotEmpty(url)) {
+            // user specified URL, could be peer-to-peer address, or register center's address.
+            if (logger.isInfoEnabled()) {
+                logger.info("The url already exists, mesh no longer processes url: " + url);
+            }
+            return;
+        }
+        // get pod namespace
+        String podNamespace;
+        if (StringUtils.isEmpty(System.getenv("POD_NAMESPACE"))) {
+            if (logger.isWarnEnabled()) {
+                logger.warn(CONFIG_FAILED_LOAD_ENV_VARIABLE, "", "", "Can not get env variable: POD_NAMESPACE, it may not be running in the K8S environment , " +
+                    "finally use 'default' replace.");
+            }
+            podNamespace = "default";
+        } else {
+            podNamespace = System.getenv("POD_NAMESPACE");
+        }
+
+        // In mesh mode, providedBy equals K8S Service name.
+        String providedBy = referenceParameters.get(PROVIDED_BY);
+        // cluster_domain default is 'cluster.local',generally unchanged.
+        String clusterDomain = Optional.ofNullable(System.getenv("CLUSTER_DOMAIN")).orElse(DEFAULT_CLUSTER_DOMAIN);
+        // By VirtualService and DestinationRule, envoy will generate a new route rule,such as 'demo.default.svc.cluster.local:80',the default port is 80.
+        Integer meshPort = Optional.ofNullable(getProviderPort()).orElse(DEFAULT_MESH_PORT);
+        // DubboReference default is -1, process it.
+        meshPort = meshPort > -1 ? meshPort : DEFAULT_MESH_PORT;
+        // get mesh url.
+        url = TRIPLE + "://" + providedBy + "." + podNamespace + SVC + clusterDomain + ":" + meshPort;
+    }
+
+    /**
+     * check if mesh config is correct
+     *
+     * @param referenceParameters referenceParameters
+     * @return mesh config is correct
+     */
+    private boolean checkMeshConfig(Map<String, String> referenceParameters) {
+        if (!"true".equals(referenceParameters.getOrDefault(MESH_ENABLE, "false"))) {
+            // In mesh mode, unloadClusterRelated can only be false.
+            referenceParameters.put(UNLOAD_CLUSTER_RELATED, "false");
+            return false;
+        }
+
+        getScopeModel().getConfigManager().getProtocol(TRIPLE)
+                .orElseThrow(() -> new IllegalStateException("In mesh mode, a triple protocol must be specified"));
+
+        String providedBy = referenceParameters.get(PROVIDED_BY);
+        if (StringUtils.isEmpty(providedBy)) {
+            throw new IllegalStateException("In mesh mode, the providedBy of ReferenceConfig is must be set");
+        }
+
+        return true;
+    }
+
+    /**
      * Make a local reference, create a local invoker.
      *
      * @param referenceParameters
@@ -498,9 +586,9 @@
         }
         if (urls.isEmpty()) {
             throw new IllegalStateException(
-                "No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() +
-                    " use dubbo version " + Version.getVersion() +
-                    ", please config <dubbo:registry address=\"...\" /> to your spring config.");
+                    "No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() +
+                            " use dubbo version " + Version.getVersion() +
+                            ", please config <dubbo:registry address=\"...\" /> to your spring config.");
         }
     }
 
@@ -513,7 +601,9 @@
         if (urls.size() == 1) {
             URL curUrl = urls.get(0);
             invoker = protocolSPI.refer(interfaceClass, curUrl);
-            if (!UrlUtils.isRegistry(curUrl)) {
+            // registry url, mesh-enable and unloadClusterRelated is true, not need Cluster.
+            if (!UrlUtils.isRegistry(curUrl) &&
+                    !curUrl.getParameter(UNLOAD_CLUSTER_RELATED, false)) {
                 List<Invoker<?>> invokers = new ArrayList<>();
                 invokers.add(invoker);
                 invoker = Cluster.getCluster(scopeModel, Cluster.DEFAULT).join(new StaticDirectory(curUrl, invokers), true);
@@ -553,7 +643,9 @@
 
     private void checkInvokerAvailable() throws IllegalStateException {
         if (shouldCheck() && !invoker.isAvailable()) {
-                throw new IllegalStateException("Failed to check the status of the service "
+            // 2-2 - No provider available.
+
+            IllegalStateException illegalStateException = new IllegalStateException("Failed to check the status of the service "
                     + interfaceName
                     + ". No provider available for the service "
                     + (group == null ? "" : group + "/")
@@ -563,6 +655,10 @@
                     + invoker.getUrl()
                     + " to the consumer "
                     + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
+
+            logger.error(CLUSTER_NO_VALID_PROVIDER, "provider not started", "", "No provider available.", illegalStateException);
+
+            throw illegalStateException;
         }
     }
 
@@ -580,7 +676,7 @@
 
         // init some null configuration.
         List<ConfigInitializer> configInitializers = this.getExtensionLoader(ConfigInitializer.class)
-            .getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
+                .getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
         configInitializers.forEach(e -> e.initReferConfig(this));
 
         if (getGeneric() == null && getConsumer() != null) {
@@ -588,7 +684,7 @@
         }
         if (ProtocolUtils.isGeneric(generic)) {
             if (interfaceClass != null && !interfaceClass.equals(GenericService.class)) {
-                logger.warn(String.format("Found conflicting attributes for interface type: [interfaceClass=%s] and [generic=%s], " +
+                logger.warn(CONFIG_PROPERTY_CONFLICT, "", "", String.format("Found conflicting attributes for interface type: [interfaceClass=%s] and [generic=%s], " +
                         "because the 'generic' attribute has higher priority than 'interfaceClass', so change 'interfaceClass' to '%s'. " +
                         "Note: it will make this reference bean as a candidate bean of type '%s' instead of '%s' when resolving dependency in Spring.",
                     interfaceClass.getName(), generic, GenericService.class.getName(), GenericService.class.getName(), interfaceClass.getName()));
@@ -600,7 +696,7 @@
                     interfaceClass = Class.forName(interfaceName, true, getInterfaceClassLoader());
                 } else if (interfaceClass == null) {
                     interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
-                        .getContextClassLoader());
+                            .getContextClassLoader());
                 }
             } catch (ClassNotFoundException e) {
                 throw new IllegalStateException(e.getMessage(), e);
@@ -657,7 +753,7 @@
 
     private void postProcessConfig() {
         List<ConfigPostProcessor> configPostProcessors = this.getExtensionLoader(ConfigPostProcessor.class)
-            .getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
+                .getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
         configPostProcessors.forEach(component -> component.postProcessReferConfig(this));
     }
 
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 af2f4cc..054f679 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
@@ -21,13 +21,14 @@
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.ClassUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.annotation.Service;
 import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
@@ -70,6 +71,11 @@
 import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_NAME_MAPPING_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_NO_METHOD_FOUND;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_UNEXPORT_ERROR;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_USE_RANDOM_PORT;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_EXPORT_SERVICE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_SERVER_DISCONNECTED;
 import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_PROTOCOL;
 import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
@@ -83,6 +89,7 @@
 import static org.apache.dubbo.registry.Constants.REGISTER_KEY;
 import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
 import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
+import static org.apache.dubbo.remoting.Constants.IS_PU_SERVER_KEY;
 import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
 import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
 import static org.apache.dubbo.rpc.Constants.PROXY_KEY;
@@ -97,7 +104,7 @@
 
     private static final long serialVersionUID = 7868244018230856253L;
 
-    private static final Logger logger = LoggerFactory.getLogger(ServiceConfig.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceConfig.class);
 
     /**
      * A random port cache, the different protocols who have no port specified have different random port
@@ -181,7 +188,7 @@
                 try {
                     exporter.unexport();
                 } catch (Throwable t) {
-                    logger.warn("Unexpected error occured when unexport " + exporter, t);
+                    logger.warn(CONFIG_UNEXPORT_ERROR, "", "", "Unexpected error occurred when unexport " + exporter, t);
                 }
             }
             exporters.clear();
@@ -242,7 +249,7 @@
                 try {
                     doExport();
                 } catch (Exception e) {
-                    logger.error("Failed to export service config: " + interfaceName, e);
+                    logger.error(CONFIG_FAILED_EXPORT_SERVICE, "configuration server disconnected", "", "Failed to (async)export service config: " + interfaceName, e);
                 }
             }, getDelay(), TimeUnit.MILLISECONDS);
     }
@@ -258,10 +265,10 @@
                     if (succeeded) {
                         logger.info("Successfully registered interface application mapping for service " + url.getServiceKey());
                     } else {
-                        logger.error("Failed register interface application mapping for service " + url.getServiceKey());
+                        logger.error(CONFIG_SERVER_DISCONNECTED, "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey());
                     }
                 } catch (Exception e) {
-                    logger.error("Failed register interface application mapping for service " + url.getServiceKey(), e);
+                    logger.error(CONFIG_SERVER_DISCONNECTED, "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey(), e);
                 }
             }
         });
@@ -447,7 +454,7 @@
 
             String[] methods = methods(interfaceClass);
             if (methods.length == 0) {
-                logger.warn("No method found in service interface " + interfaceClass.getName());
+                logger.warn(CONFIG_NO_METHOD_FOUND, "", "", "No method found in service interface: " + interfaceClass.getName());
                 map.put(METHODS_KEY, ANY_VALUE);
             } else {
                 map.put(METHODS_KEY, StringUtils.join(new HashSet<>(Arrays.asList(methods)), ","));
@@ -554,6 +561,15 @@
 
         // export service
         String host = findConfiguredHosts(protocolConfig, provider, params);
+        if (NetUtils.isIPV6URLStdFormat(host)) {
+            if (!host.contains("[")) {
+                host = "[" + host + "]";
+            }
+        } else if (NetUtils.getLocalHostV6() != null) {
+            String ipv6Host = NetUtils.getLocalHostV6();
+            params.put(CommonConstants.IPV6_KEY, ipv6Host);
+        }
+
         Integer port = findConfiguredPort(protocolConfig, provider, this.getExtensionLoader(Protocol.class), name, params);
         URL url = new ServiceConfigURL(name, null, null, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), params);
 
@@ -580,10 +596,36 @@
 
             // export to remote if the config is not local (export to local only when config is local)
             if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
+                // export to extra protocol is used in remote export
+                String extProtocol = url.getParameter("ext.protocol", "");
+                List<String> protocols = new ArrayList<>();
+                // export original url
+                url = URLBuilder.from(url).
+                    addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
+                    removeParameter("ext.protocol").
+                    build();
                 url = exportRemote(url, registryURLs);
                 if (!isGeneric(generic) && !getScopeModel().isInternal()) {
                     MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());
                 }
+
+                if (!extProtocol.equals("")) {
+                    String[] extProtocols = extProtocol.split(",", -1);
+                    protocols.addAll(Arrays.asList(extProtocols));
+                }
+                // export extra protocols
+                for(String protocol : protocols) {
+                    if(!protocol.equals("")){
+                        URL localUrl = URLBuilder.from(url).
+                            setProtocol(protocol).
+                            build();
+                        localUrl = exportRemote(localUrl, registryURLs);
+                        if (!isGeneric(generic) && !getScopeModel().isInternal()) {
+                            MetadataUtils.publishServiceDefinition(localUrl, providerModel.getServiceModel(), getApplicationModel());
+                        }
+                        this.urls.add(localUrl);
+                    }
+                }
             }
         }
         this.urls.add(url);
@@ -831,7 +873,7 @@
         protocol = protocol.toLowerCase();
         if (!RANDOM_PORT_MAP.containsKey(protocol)) {
             RANDOM_PORT_MAP.put(protocol, port);
-            logger.warn("Use random available port(" + port + ") for protocol " + protocol);
+            logger.warn(CONFIG_USE_RANDOM_PORT, "", "", "Use random available port(" + port + ") for protocol " + protocol);
         }
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
index 2d13f58..441598e 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java
@@ -31,7 +31,7 @@
 import org.apache.dubbo.common.deploy.ModuleDeployer;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
@@ -73,6 +73,8 @@
 import static org.apache.dubbo.common.config.ConfigurationUtils.parseProperties;
 import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_SPLIT_PATTERN;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_REFRESH_INSTANCE_ERROR;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_REGISTER_INSTANCE_ERROR;
 import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
 import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_PUBLISH_DELAY;
@@ -84,7 +86,7 @@
  */
 public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationModel> implements ApplicationDeployer {
 
-    private static final Logger logger = LoggerFactory.getLogger(DefaultApplicationDeployer.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DefaultApplicationDeployer.class);
 
     private final ApplicationModel applicationModel;
 
@@ -722,7 +724,7 @@
             registered = true;
             ServiceInstanceMetadataUtils.registerMetadataAndInstance(applicationModel);
         } catch (Exception e) {
-            logger.error("Register instance error", e);
+            logger.error(CONFIG_REGISTER_INSTANCE_ERROR, "configuration server disconnected", "", "Register instance error.", e);
         }
         if (registered) {
             // scheduled task for updating Metadata and ServiceInstance
@@ -738,7 +740,7 @@
                     }
                 } catch (Exception e) {
                     if (!applicationModel.isDestroyed()) {
-                        logger.error("Refresh instance and metadata error", e);
+                        logger.error(CONFIG_REFRESH_INSTANCE_ERROR, "", "", "Refresh instance and metadata error.", e);
                     }
                 }
             }, 0, ConfigurationUtils.get(applicationModel, METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit.MILLISECONDS);
@@ -764,15 +766,14 @@
             }
             onStopping();
 
+            unregisterServiceInstance();
             destroyRegistries();
-            destroyServiceDiscoveries();
             destroyMetadataReports();
 
             unRegisterShutdownHook();
             if (asyncMetadataFuture != null) {
                 asyncMetadataFuture.cancel(true);
             }
-            unregisterServiceInstance();
         }
     }
 
@@ -957,7 +958,7 @@
                     ServiceInstanceMetadataUtils.refreshMetadataAndInstance(applicationModel);
                 }
             } catch (Exception e) {
-                logger.error("refresh meta and instance failed: " + e.getMessage(), e);
+                logger.error(CONFIG_REFRESH_INSTANCE_ERROR, "", "", "Refresh instance and metadata error.", e);
             }
         } finally {
             // complete future
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
index 5b73adb..f692dcd 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultModuleDeployer.java
@@ -22,7 +22,7 @@
 import org.apache.dubbo.common.deploy.DeployState;
 import org.apache.dubbo.common.deploy.ModuleDeployListener;
 import org.apache.dubbo.common.deploy.ModuleDeployer;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
@@ -46,12 +46,17 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_START_MODEL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_UNABLE_DESTROY_MODEL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_REFERENCE_MODEL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_EXPORT_SERVICE;
+
 /**
  * Export/refer services of module
  */
 public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> implements ModuleDeployer {
 
-    private static final Logger logger = LoggerFactory.getLogger(DefaultModuleDeployer.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DefaultModuleDeployer.class);
 
     private final List<CompletableFuture<?>> asyncExportingFutures = new ArrayList<>();
 
@@ -223,7 +228,7 @@
                         consumerModel.getDestroyRunner().run();
                     }
                 } catch (Throwable t) {
-                    logger.error("Unable to destroy consumerModel.", t);
+                    logger.error(CONFIG_UNABLE_DESTROY_MODEL, "there are problems with the custom implementation.", "", "Unable to destroy model: consumerModel.", t);
                 }
             }
 
@@ -234,7 +239,7 @@
                         providerModel.getDestroyRunner().run();
                     }
                 } catch (Throwable t) {
-                    logger.error("Unable to destroy providerModel.", t);
+                    logger.error(CONFIG_UNABLE_DESTROY_MODEL, "there are problems with the custom implementation.", "", "Unable to destroy model: providerModel.", t);
                 }
             }
             serviceRepository.destroy();
@@ -265,7 +270,7 @@
     private void onModuleFailed(String msg, Throwable ex) {
         try {
             setFailed(ex);
-            logger.error(msg, ex);
+            logger.error(CONFIG_FAILED_START_MODEL, "", "", "Model start failed: " + msg, ex);
             applicationDeployer.notifyModuleChanged(moduleModel, DeployState.STARTED);
         } finally {
             completeStartFuture(false);
@@ -333,7 +338,7 @@
                         exportedServices.add(sc);
                     }
                 } catch (Throwable t) {
-                    logger.error(getIdentifier() + " export async catch error : " + t.getMessage(), t);
+                    logger.error(CONFIG_FAILED_EXPORT_SERVICE, "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
                 }
             }, executor);
 
@@ -380,7 +385,7 @@
                             try {
                                 referenceCache.get(rc);
                             } catch (Throwable t) {
-                                logger.error(getIdentifier() + " refer async catch error : " + t.getMessage(), t);
+                                logger.error(CONFIG_FAILED_EXPORT_SERVICE, "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
                             }
                         }, executor);
 
@@ -390,7 +395,7 @@
                     }
                 }
             } catch (Throwable t) {
-                logger.error(getIdentifier() + " refer catch error.");
+                logger.error(CONFIG_FAILED_REFERENCE_MODEL, "", "", "Model reference failed: " + getIdentifier() + " , catch error : " + t.getMessage(), t);
                 referenceCache.destroy(rc);
                 throw t;
             }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
index 778b7c3..d663e8a 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.config.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -28,6 +28,7 @@
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.ProtocolServer;
 import org.apache.dubbo.rpc.model.ApplicationModel;
@@ -45,6 +46,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.METADATA_SERVICE_PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_FIND_PROTOCOL;
 import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
 
 /**
@@ -52,7 +54,7 @@
  */
 public class ConfigurableMetadataServiceExporter {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     private MetadataServiceDelegation metadataService;
 
@@ -138,7 +140,7 @@
                     }
                 }
             } catch (Exception e) {
-                logger.error("Failed to find any valid " + specifiedProtocol + " protocol, will use random port to export metadata service.");
+                logger.error(CONFIG_FAILED_FIND_PROTOCOL, "invalid specified " + specifiedProtocol + "  protocol", "", "Failed to find any valid protocol, will use random port to export metadata service.", e);
             }
         } else {
             protocolConfig.setPort(port);
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ExporterDeployListener.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ExporterDeployListener.java
index 1a0bafc..bff2e29 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ExporterDeployListener.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ExporterDeployListener.java
@@ -19,6 +19,7 @@
 import org.apache.dubbo.common.deploy.ApplicationDeployListener;
 import org.apache.dubbo.common.lang.Prioritized;
 import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
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 5e81a63..17c7363 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.config.PropertiesConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.serialize.Serialization;
 import org.apache.dubbo.common.status.StatusChecker;
@@ -99,6 +99,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_PARAMETER_FORMAT_ERROR;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_REGISTER_MODE_ALL;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_REGISTER_MODE_INSTANCE;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_REGISTER_MODE_INTERFACE;
@@ -143,7 +144,7 @@
 import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
 
 public class ConfigValidationUtils {
-    private static final Logger logger = LoggerFactory.getLogger(ConfigValidationUtils.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ConfigValidationUtils.class);
     /**
      * The maximum length of a <b>parameter's value</b>
      */
@@ -726,12 +727,14 @@
             return;
         }
         if (value.length() > maxlength) {
-            logger.error("Invalid " + property + "=\"" + value + "\" is longer than " + maxlength);
+            logger.error(CONFIG_PARAMETER_FORMAT_ERROR, "the value content is too long", "", "Parameter value format error. Invalid " +
+                property + "=\"" + value + "\" is longer than " + maxlength);
         }
         if (pattern != null) {
             Matcher matcher = pattern.matcher(value);
             if (!matcher.matches()) {
-                logger.error("Invalid " + property + "=\"" + value + "\" contains illegal " +
+                logger.error(CONFIG_PARAMETER_FORMAT_ERROR, "the value content is illegal character", "", "Parameter value format error. Invalid " +
+                    property + "=\"" + value + "\" contains illegal " +
                     "character, only digit, letter, '-', '_' or '.' is legal.");
             }
         }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java
index 6fc328f..c6eb027 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java
@@ -654,6 +654,24 @@
         Assertions.assertNotEquals(protocol1, protocol2);
     }
 
+    @Test
+    void testRegistryConfigEquals() {
+        RegistryConfig hangzhou = new RegistryConfig();
+        hangzhou.setAddress("nacos://localhost:8848");
+        HashMap<String, String> parameters = new HashMap<>();
+        parameters.put("namespace", "hangzhou");
+        hangzhou.setParameters(parameters);
+
+        RegistryConfig shanghai = new RegistryConfig();
+        shanghai.setAddress("nacos://localhost:8848");
+        parameters = new HashMap<>();
+        parameters.put("namespace", "shanghai");
+
+        shanghai.setParameters(parameters);
+
+        Assertions.assertNotEquals(hangzhou, shanghai);
+    }
+
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.ANNOTATION_TYPE})
     public @interface ConfigField {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
index 1c2b154..f27fcd7 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/MultipleRegistryCenterServiceDiscoveryRegistryIntegrationTest.java
@@ -25,8 +25,8 @@
 import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.integration.IntegrationTest;
-import org.apache.dubbo.config.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.registry.RegistryServiceListener;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.test.check.registrycenter.config.ZookeeperConfig;
 import org.apache.dubbo.test.check.registrycenter.config.ZookeeperRegistryCenterConfig;
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
index be60e4e..94b7200 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/multiple/servicediscoveryregistry/ServiceDiscoveryRegistryInfoWrapper.java
@@ -16,8 +16,8 @@
  */
 package org.apache.dubbo.config.integration.multiple.servicediscoveryregistry;
 
-import org.apache.dubbo.config.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 
 /**
  * The instance to wrap {@link org.apache.dubbo.registry.client.ServiceDiscoveryRegistry}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
index 3b5ec69..c4f1e86 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/integration/single/SingleRegistryCenterDubboProtocolIntegrationTest.java
@@ -27,13 +27,13 @@
 import org.apache.dubbo.config.ServiceListener;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.integration.IntegrationTest;
-import org.apache.dubbo.config.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.registry.ListenerRegistryWrapper;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 import org.apache.dubbo.registry.client.migration.MigrationInvoker;
 import org.apache.dubbo.registry.support.RegistryManager;
 import org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery;
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
index 6e60131..dbaf02f 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
@@ -27,7 +27,7 @@
 //import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 //import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter;
 //import org.apache.dubbo.config.metadata.ExporterDeployListener;
-//import org.apache.dubbo.config.metadata.MetadataServiceDelegation;
+//import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
 //import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
 //import org.apache.dubbo.rpc.model.ApplicationModel;
 //import org.apache.dubbo.rpc.model.FrameworkModel;
diff --git a/dubbo-config/dubbo-config-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml
index 7df9344..93aad74 100644
--- a/dubbo-config/dubbo-config-spring/pom.xml
+++ b/dubbo-config/dubbo-config-spring/pom.xml
@@ -111,6 +111,12 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>javax.validation</groupId>
             <artifactId>validation-api</artifactId>
             <scope>test</scope>
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ConfigCenterBean.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ConfigCenterBean.java
index 49640ee..3126a88 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ConfigCenterBean.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ConfigCenterBean.java
@@ -33,7 +33,10 @@
 import java.util.Map;
 
 /**
- * Start from 2.7.0+, export and refer will only be executed when Spring is fully initialized, and each Config bean will get refreshed on the start of the export and refer process.
+ * Starting from 2.7.0+, export and refer will only be executed when Spring is fully initialized.
+ * <p>
+ * Each Config bean will get refreshed on the start of the exporting and referring process.
+ * <p>
  * So it's ok for this bean not to be the first Dubbo Config bean being initialized.
  * <p>
  */
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
index fd7e111..37fe2a9 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
@@ -16,12 +16,13 @@
  */
 package org.apache.dubbo.config.spring.beans.factory.annotation;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.ClassUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.Reference;
-import org.apache.dubbo.config.context.ConfigManager;
 import org.apache.dubbo.config.spring.Constants;
 import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.config.spring.context.event.DubboConfigInitEvent;
@@ -30,9 +31,6 @@
 import org.apache.dubbo.config.spring.reference.ReferenceBeanSupport;
 import org.apache.dubbo.config.spring.util.SpringCompatUtils;
 import org.apache.dubbo.rpc.service.GenericService;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.PropertyValue;
 import org.springframework.beans.PropertyValues;
@@ -509,7 +507,7 @@
 
     /**
      * Gets all beans of {@link ReferenceBean}
-     * @deprecated  use {@link ConfigManager#getReferences()} instead
+     * @deprecated  use {@link ReferenceBeanManager.getReferences()} instead
      */
     @Deprecated
     public Collection<ReferenceBean<?>> getReferenceBeans() {
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/config/DubboConfigDefaultPropertyValueBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/config/DubboConfigDefaultPropertyValueBeanPostProcessor.java
index c96236f..05851fc 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/config/DubboConfigDefaultPropertyValueBeanPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/config/DubboConfigDefaultPropertyValueBeanPostProcessor.java
@@ -52,6 +52,7 @@
      */
     public static final String BEAN_NAME = "dubboConfigDefaultPropertyValueBeanPostProcessor";
 
+    @Override
     protected void processBeforeInitialization(AbstractConfig dubboConfigBean, String beanName) throws BeansException {
         // ignore auto generate bean name
         if (!beanName.contains("#")) {
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
index 60188a4..7aa7dd2 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java
@@ -16,13 +16,14 @@
  */
 package org.apache.dubbo.config.spring.context;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_STOP_DUBBO_ERROR;
 import static org.springframework.util.ObjectUtils.nullSafeEquals;
 
 import java.util.concurrent.Future;
 import org.apache.dubbo.common.deploy.DeployListenerAdapter;
 import org.apache.dubbo.common.deploy.DeployState;
 import org.apache.dubbo.common.deploy.ModuleDeployer;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.config.spring.context.event.DubboApplicationStateEvent;
@@ -44,7 +45,7 @@
  */
 public class DubboDeployApplicationListener implements ApplicationListener<ApplicationContextEvent>, ApplicationContextAware, Ordered {
 
-    private static final Logger logger = LoggerFactory.getLogger(DubboDeployApplicationListener.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DubboDeployApplicationListener.class);
 
     private ApplicationContext applicationContext;
 
@@ -130,7 +131,7 @@
                 moduleModel.destroy();
             }
         } catch (Exception e) {
-            logger.error("An error occurred when stop dubbo module: " + e.getMessage(), e);
+            logger.error(CONFIG_STOP_DUBBO_ERROR, "", "", "Unexpected error occurred when stop dubbo module: " + e.getMessage(), e);
         }
         // remove context bind cache
         DubboSpringInitializer.remove(event.getApplicationContext());
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java
index 2f70012..d9f4f2b 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboInfraBeanRegisterPostProcessor.java
@@ -89,6 +89,11 @@
 
         // register ConfigManager singleton
         beanFactory.registerSingleton(ConfigManager.BEAN_NAME, applicationModel.getApplicationConfigManager());
+
+        // fix https://github.com/apache/dubbo/issues/10278
+        if (registry != null){
+            registry.removeBeanDefinition(BEAN_NAME);
+        }
     }
 
     @Override
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceAttributes.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceAttributes.java
index 7a2d0be..efc13f9 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceAttributes.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceAttributes.java
@@ -52,6 +52,8 @@
 
     String PROVIDED_BY = "providedBy";
 
+    String PROVIDER_PORT = "providerPort";
+
     String URL = "url";
 
     String CLIENT = "client";
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceBeanBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceBeanBuilder.java
index 775d879..36c7418 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceBeanBuilder.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceBeanBuilder.java
@@ -198,6 +198,11 @@
         return this;
     }
 
+    public ReferenceBeanBuilder setProviderPort(Integer providerPort) {
+        attributes.put(ReferenceAttributes.PROVIDER_PORT, providerPort);
+        return this;
+    }
+
 //    public ReferenceBeanBuilder setRouter(String router) {
 //        attributes.put(ReferenceAttributes.ROUTER, router);
 //        return this;
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceCreator.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceCreator.java
index bbab458..68ebdba 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceCreator.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/reference/ReferenceCreator.java
@@ -95,7 +95,9 @@
         configureBean(configBean);
 
         if (logger.isInfoEnabled()) {
-            logger.info("The configBean[type:" + configBean.getClass().getSimpleName() + "] has been built.");
+            logger.info("The configBean[type:" +
+                configBean.getClass().getSimpleName() + "<" + defaultInterfaceClass.getTypeName() + ">" +
+                "] has been built.");
         }
 
         return configBean;
@@ -122,14 +124,6 @@
         }
     }
 
-//    private void configureApplicationConfig(ReferenceConfig configBean) {
-//        String applicationConfigId = getAttribute(attributes, "application");
-//        if (StringUtils.hasText(applicationConfigId)) {
-//            ApplicationConfig applicationConfig = getConfig(applicationConfigId, ApplicationConfig.class);
-//            configBean.setApplication(applicationConfig);
-//        }
-//    }
-
     private void configureModuleConfig(ReferenceConfig configBean) {
         String moduleConfigId = getAttribute(attributes, "module");
         if (StringUtils.hasText(moduleConfigId)) {
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/status/SpringStatusChecker.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/status/SpringStatusChecker.java
index d83398b..ebb8075 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/status/SpringStatusChecker.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/status/SpringStatusChecker.java
@@ -51,20 +51,6 @@
 
     @Override
     public Status check() {
-        // TODO It seems to be ok with GenericWebApplicationContext, need further confirmation
-//        ApplicationContext context = null;
-//        for (ApplicationContext c : SpringExtensionInjector.getContexts()) {
-//            // [Issue] SpringStatusChecker execute errors on non-XML Spring configuration
-//            // issue : https://github.com/apache/dubbo/issues/3615
-//            if(c instanceof GenericWebApplicationContext) { // ignore GenericXmlApplicationContext
-//                continue;
-//            }
-//
-//            if (c != null) {
-//                context = c;
-//                break;
-//            }
-//        }
 
         if (applicationContext == null && applicationModel != null) {
             SpringExtensionInjector springExtensionInjector = SpringExtensionInjector.get(applicationModel);
@@ -110,10 +96,12 @@
                     }
                 }
             }
-        } catch (UnsupportedOperationException t) {
-            logger.debug(t.getMessage(), t);
         } catch (Throwable t) {
-            logger.warn(t.getMessage(), t);
+            if (t.getCause() instanceof UnsupportedOperationException){
+                logger.debug(t.getMessage(), t);
+            }else {
+                logger.warn(t.getMessage(), t);
+            }
         }
         return new Status(level, buf.toString());
     }
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
index b22bd91..99b34ee 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
@@ -239,6 +239,12 @@
                             <![CDATA[ declares which app or service this interface belongs to. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="provider-port" type="xsd:integer">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ declares provider service port. ]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:attribute name="router" type="xsd:string">
                     <xsd:annotation>
                         <xsd:documentation>
@@ -1017,6 +1023,11 @@
                         <xsd:documentation><![CDATA[ The thread pool queue size. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="mesh-enable" type="xsd:boolean">
+                    <xsd:annotation>
+                        <xsd:documentation><![CDATA[ Enable mesh mode. ]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
@@ -1059,6 +1070,14 @@
                         <xsd:documentation><![CDATA[ The service protocol. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="unloadClusterRelated" type="xsd:boolean">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ In the mesh mode, uninstall the directory, router and load balance related to the cluster in the currently invoked invoker.
+                            Delegate retry, load balancing, timeout and other traffic management capabilities to Sidecar. ]]>
+                        </xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
index 9a9a6a7..10bc5c6 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
@@ -239,6 +239,12 @@
                             <![CDATA[ declares which app or service this interface belongs to. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="provider-port" type="xsd:integer">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ declares provider service port. ]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:attribute name="router" type="xsd:string">
                     <xsd:annotation>
                         <xsd:documentation>
@@ -1180,6 +1186,11 @@
                             <![CDATA[ Whether refer should run in background or not, default false. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="mesh-enable" type="xsd:boolean">
+                    <xsd:annotation>
+                        <xsd:documentation><![CDATA[ Enable mesh mode. ]]></xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
@@ -1230,6 +1241,14 @@
                         </xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="unloadClusterRelated" type="xsd:boolean">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ In the mesh mode, uninstall the directory, router and load balance related to the cluster in the currently invoked invoker.
+                            Delegate retry, load balancing, timeout and other traffic management capabilities to Sidecar. ]]>
+                        </xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
@@ -1259,6 +1278,11 @@
                 <xsd:documentation><![CDATA[ The service port. ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:attribute>
+        <xsd:attribute name="ext-protocol" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation><![CDATA[ extra protocol.]]]]></xsd:documentation>
+            </xsd:annotation>
+        </xsd:attribute>
         <xsd:attribute name="threadpool" type="xsd:string">
             <xsd:annotation>
                 <xsd:documentation><![CDATA[ The thread pool type. ]]></xsd:documentation>
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/AbstractRegistryService.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/AbstractRegistryService.java
index c664601..9ac517c 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/AbstractRegistryService.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/AbstractRegistryService.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.config.spring;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -32,13 +32,15 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_NOTIFY_EVENT;
+
 /**
  * AbstractRegistryService
  */
 public abstract class AbstractRegistryService implements RegistryService {
 
     // logger
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     // registered services
     // Map<serviceName, Map<url, queryString>>
@@ -184,7 +186,7 @@
                 try {
                     notify(service, urls, listener);
                 } catch (Throwable t) {
-                    logger.error("Failed to notify registry event, service: " + service + ", urls: " + urls + ", cause: " + t.getMessage(), t);
+                    logger.error(CONFIG_FAILED_NOTIFY_EVENT, "", "", "Failed to notify registry event, service: " + service + ", urls: " + urls + ", cause: " + t.getMessage(), t);
                 }
             }
         }
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java
index bb29825..009f371 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java
@@ -15,11 +15,11 @@
  */
 package org.apache.dubbo.config.spring;
 
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.zookeeper.server.ServerConfig;
 import org.apache.zookeeper.server.ZooKeeperServerMain;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.context.SmartLifecycle;
 import org.springframework.util.ErrorHandler;
 import org.springframework.util.SocketUtils;
@@ -29,6 +29,8 @@
 import java.util.Properties;
 import java.util.UUID;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_ZOOKEEPER_SERVER_ERROR;
+
 /**
  * from: https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java
  * <p>
@@ -43,7 +45,7 @@
     /**
      * Logger.
      */
-    private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(EmbeddedZooKeeper.class);
 
     /**
      * ZooKeeper client port. This will be determined dynamically upon startup.
@@ -238,7 +240,7 @@
                 if (errorHandler != null) {
                     errorHandler.handleError(e);
                 } else {
-                    logger.error("Exception running embedded ZooKeeper", e);
+                    logger.error(CONFIG_ZOOKEEPER_SERVER_ERROR, "ZooKeeper server error", "", "Exception running embedded ZooKeeper.", e);
                 }
             }
         }
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 f0c77b5..b9c6d57 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
@@ -21,7 +21,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 
@@ -47,6 +47,7 @@
 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.CONFIG_NAMESPACE_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_CONNECT_REGISTRY;
 
 /**
  * Apollo implementation, https://github.com/ctripcorp/apollo
@@ -61,7 +62,7 @@
  * Please see http://dubbo.apache.org/zh-cn/docs/user/configuration/config-center.html for details.
  */
 public class ApolloDynamicConfiguration implements DynamicConfiguration {
-    private static final Logger logger = LoggerFactory.getLogger(ApolloDynamicConfiguration.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ApolloDynamicConfiguration.class);
     private static final String APOLLO_ENV_KEY = "env";
     private static final String APOLLO_ADDR_KEY = "apollo.meta";
     private static final String APOLLO_CLUSTER_KEY = "apollo.cluster";
@@ -106,7 +107,10 @@
                 throw new IllegalStateException("Failed to connect to config center, the config center is Apollo, " +
                     "the address is: " + (StringUtils.isNotEmpty(configAddr) ? configAddr : configEnv));
             } else {
-                logger.warn("Failed to connect to config center, the config center is Apollo, " +
+                // 5-1 Failed to connect to configuration center.
+
+                logger.warn(CONFIG_FAILED_CONNECT_REGISTRY, "configuration server offline", "",
+                    "Failed to connect to config center, the config center is Apollo, " +
                     "the address is: " + (StringUtils.isNotEmpty(configAddr) ? configAddr : configEnv) +
                     ", will use the local cache value instead before eventually the connection is established.");
             }
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 17daabb..cd20375 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
@@ -35,6 +35,8 @@
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_CONNECT_REGISTRY;
+
 public class ZookeeperDynamicConfiguration extends TreePathDynamicConfiguration {
 
     private Executor executor;
@@ -60,7 +62,16 @@
         zkClient = zookeeperTransporter.connect(url);
         boolean isConnected = zkClient.isConnected();
         if (!isConnected) {
-            throw new IllegalStateException("Failed to connect with zookeeper, pls check if url " + url + " is correct.");
+
+            IllegalStateException illegalStateException =
+                new IllegalStateException("Failed to connect with zookeeper, pls check if url " + url + " is correct.");
+
+            if (logger != null) {
+                logger.error(CONFIG_FAILED_CONNECT_REGISTRY, "configuration server offline", "",
+                    "Failed to connect with zookeeper", illegalStateException);
+            }
+
+            throw illegalStateException;
         }
     }
 
diff --git a/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-consumer/pom.xml b/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-consumer/pom.xml
index e286141..c7f6fe3 100644
--- a/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-consumer/pom.xml
@@ -87,6 +87,10 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-provider/pom.xml b/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-provider/pom.xml
index 6319d64..526f501 100644
--- a/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-annotation/dubbo-demo-annotation-provider/pom.xml
@@ -89,6 +89,10 @@
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
diff --git a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml
index 997510b..32de339 100644
--- a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml
@@ -87,6 +87,10 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-provider/pom.xml b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-provider/pom.xml
index 67ae69b..e4b2d26 100644
--- a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-provider/pom.xml
@@ -89,6 +89,10 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/dubbo-demo/dubbo-demo-generic-call/pom.xml b/dubbo-demo/dubbo-demo-generic-call/pom.xml
index 1cb5222..1fd332f 100644
--- a/dubbo-demo/dubbo-demo-generic-call/pom.xml
+++ b/dubbo-demo/dubbo-demo-generic-call/pom.xml
@@ -82,6 +82,10 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml
index f70c8dd..d23b171 100644
--- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml
@@ -67,6 +67,10 @@
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.dubbo</groupId>
diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml
index 07fa629..5319a35 100644
--- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml
@@ -64,6 +64,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-filter-cache</artifactId>
         </dependency>
         <dependency>
diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml
index 570ab67..9eb6068 100644
--- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml
@@ -87,6 +87,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
             <version>${spring-boot.version}</version>
diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml
index c106da6..c31ff38 100644
--- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml
@@ -87,6 +87,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
             <version>${spring-boot.version}</version>
diff --git a/dubbo-demo/dubbo-demo-triple/pom.xml b/dubbo-demo/dubbo-demo-triple/pom.xml
index 9a0139b..6597e0c 100644
--- a/dubbo-demo/dubbo-demo-triple/pom.xml
+++ b/dubbo-demo/dubbo-demo-triple/pom.xml
@@ -112,6 +112,10 @@
             <artifactId>dubbo-serialization-hessian2</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java</artifactId>
         </dependency>
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
index 663c76f..6aa8986 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/pom.xml
@@ -85,6 +85,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
         </dependency>
         <dependency>
diff --git a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
index e252cf8..4237066 100644
--- a/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
+++ b/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/pom.xml
@@ -92,6 +92,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
         </dependency>
         <dependency>
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index c8ec8c9..4c34e8b 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -99,6 +99,7 @@
         <httpclient_version>4.5.13</httpclient_version>
         <httpcore_version>4.4.6</httpcore_version>
         <fastjson_version>1.2.83</fastjson_version>
+        <fastjson2_version>2.0.14</fastjson2_version>
         <zookeeper_version>3.4.14</zookeeper_version>
         <curator_version>4.2.0</curator_version>
         <curator_test_version>2.12.0</curator_test_version>
@@ -125,17 +126,22 @@
         <fst_version>2.48-jdk-6</fst_version>
         <avro_version>1.8.2</avro_version>
         <apollo_client_version>1.8.0</apollo_client_version>
-        <snakeyaml_version>1.29</snakeyaml_version>
+        <snakeyaml_version>1.32</snakeyaml_version>
         <commons_lang3_version>3.8.1</commons_lang3_version>
         <protostuff_version>1.5.9</protostuff_version>
         <envoy_api_version>0.1.23</envoy_api_version>
+        <micrometer.version>1.7.4</micrometer.version>
+        <t_digest.version>3.3</t_digest.version>
+        <prometheus_client.version>0.10.0</prometheus_client.version>
+        <reactive.version>1.0.4</reactive.version>
+        <reactor.version>3.4.19</reactor.version>
 
         <rs_api_version>2.0</rs_api_version>
         <resteasy_version>3.0.19.Final</resteasy_version>
         <tomcat_embed_version>8.5.69</tomcat_embed_version>
         <jetcd_version>0.5.3</jetcd_version>
         <nacos_version>2.1.0</nacos_version>
-        <grpc.version>1.44.0</grpc.version>
+        <grpc.version>1.47.0</grpc.version>
         <grpc_contrib_verdion>0.8.1</grpc_contrib_verdion>
         <jprotoc_version>1.2.1</jprotoc_version>
         <!-- Log libs -->
@@ -153,7 +159,7 @@
         <eureka.version>1.9.12</eureka.version>
 
         <!-- Fabric8 for Kubernetes -->
-        <fabric8_kubernetes_version>5.3.2</fabric8_kubernetes_version>
+        <fabric8_kubernetes_version>6.1.1</fabric8_kubernetes_version>
 
         <!-- Alibaba -->
         <alibaba_spring_context_support_version>1.0.8</alibaba_spring_context_support_version>
@@ -175,7 +181,7 @@
         <portlet_version>2.0</portlet_version>
         <maven_flatten_version>1.1.0</maven_flatten_version>
         <commons_compress_version>1.21</commons_compress_version>
-        <revision>3.0.13-SNAPSHOT</revision>
+        <revision>3.1.2-SNAPSHOT</revision>
     </properties>
 
     <dependencyManagement>
@@ -229,6 +235,11 @@
                 <version>${fastjson_version}</version>
             </dependency>
             <dependency>
+                <groupId>com.alibaba.fastjson2</groupId>
+                <artifactId>fastjson2</artifactId>
+                <version>${fastjson2_version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.apache.zookeeper</groupId>
                 <artifactId>zookeeper</artifactId>
                 <version>${zookeeper_version}</version>
@@ -767,6 +778,43 @@
                 <version>${snappy_java_version}</version>
                 <optional>true</optional>
             </dependency>
+            <!-- metrics related dependencies-->
+            <dependency>
+                <groupId>io.micrometer</groupId>
+                <artifactId>micrometer-core</artifactId>
+                <version>${micrometer.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.micrometer</groupId>
+                <artifactId>micrometer-registry-prometheus</artifactId>
+                <version>${micrometer.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.tdunning</groupId>
+                <artifactId>t-digest</artifactId>
+                <version>${t_digest.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.prometheus</groupId>
+                <artifactId>simpleclient</artifactId>
+                <version>${prometheus_client.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.prometheus</groupId>
+                <artifactId>simpleclient_pushgateway</artifactId>
+                <version>${prometheus_client.version}</version>
+            </dependency>
+            <!-- reactive related dependencies -->
+            <dependency>
+                <groupId>org.reactivestreams</groupId>
+                <artifactId>reactive-streams</artifactId>
+                <version>${reactive.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.projectreactor</groupId>
+                <artifactId>reactor-core</artifactId>
+                <version>${reactor.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
index 80ebf73..7f9410f 100644
--- a/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
+++ b/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
@@ -32,7 +32,7 @@
     <packaging>pom</packaging>
 
     <properties>
-        <revision>3.0.13-SNAPSHOT</revision>
+        <revision>3.1.2-SNAPSHOT</revision>
         <maven_flatten_version>1.1.0</maven_flatten_version>
         <curator_version>5.1.0</curator_version>
         <zookeeper_version>3.7.0</zookeeper_version>
diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
index fed04af..3f883d6 100644
--- a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
+++ b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
@@ -32,7 +32,7 @@
     <packaging>pom</packaging>
 
     <properties>
-        <revision>3.0.13-SNAPSHOT</revision>
+        <revision>3.1.2-SNAPSHOT</revision>
         <maven_flatten_version>1.1.0</maven_flatten_version>
         <curator_version>4.2.0</curator_version>
         <zookeeper_version>3.4.14</zookeeper_version>
diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml
index bd5f9ea..09d4ef8 100644
--- a/dubbo-distribution/dubbo-all/pom.xml
+++ b/dubbo-distribution/dubbo-all/pom.xml
@@ -215,6 +215,13 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
             <version>${project.version}</version>
             <scope>compile</scope>
@@ -304,6 +311,27 @@
             <scope>compile</scope>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-kubernetes</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-xds</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-reactive</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
 
 
         <!-- Transitive dependencies -->
@@ -335,6 +363,10 @@
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
 
         <!-- Temporarily add this part to exclude transitive dependency -->
         <dependency>
@@ -407,6 +439,7 @@
                                     <include>org.apache.dubbo:dubbo-native</include>
                                     <include>org.apache.dubbo:dubbo-plugin</include>
                                     <include>org.apache.dubbo:dubbo-qos</include>
+                                    <include>org.apache.dubbo:dubbo-reactive</include>
                                     <include>org.apache.dubbo:dubbo-registry-api</include>
                                     <include>org.apache.dubbo:dubbo-registry-multicast</include>
                                     <include>org.apache.dubbo:dubbo-registry-multiple</include>
@@ -429,9 +462,12 @@
                                     <include>org.apache.dubbo:dubbo-rpc</include>
                                     <include>org.apache.dubbo:dubbo-serialization-api</include>
                                     <include>org.apache.dubbo:dubbo-serialization-hessian2</include>
+                                    <include>org.apache.dubbo:dubbo-serialization-fastjson2</include>
                                     <include>org.apache.dubbo:dubbo-serialization-jdk</include>
                                     <include>org.apache.dubbo:dubbo-serialization</include>
                                     <include>org.apache.dubbo:dubbo-compiler</include>
+                                    <include>org.apache.dubbo:dubbo-kubernetes</include>
+                                    <include>org.apache.dubbo:dubbo-xds</include>
                                 </includes>
                             </artifactSet>
                             <transformers>
diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml
index a477c4dc..013d842 100644
--- a/dubbo-distribution/dubbo-bom/pom.xml
+++ b/dubbo-distribution/dubbo-bom/pom.xml
@@ -173,6 +173,11 @@
                 <artifactId>dubbo-container-spring</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-reactive</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- dubbo plugin -->
             <dependency>
@@ -198,6 +203,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-serialization-fastjson2</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
                 <artifactId>dubbo-serialization-jdk</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -260,6 +270,16 @@
                 <artifactId>dubbo-configcenter-nacos</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-kubernetes</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-xds</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.dubbo</groupId>
diff --git a/dubbo-kubernetes/pom.xml b/dubbo-kubernetes/pom.xml
new file mode 100644
index 0000000..f5250b3
--- /dev/null
+++ b/dubbo-kubernetes/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-parent</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>dubbo-kubernetes</artifactId>
+    <name>${project.artifactId}</name>
+    <description>The Kubernetes Integration</description>
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-server-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>3.8.0</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.jupiter</groupId>
+                    <artifactId>junit-jupiter-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+
+</project>
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java
new file mode 100644
index 0000000..300dae3
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
+
+import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.Watch;
+import io.fabric8.kubernetes.client.Watcher;
+import io.fabric8.kubernetes.client.WatcherException;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class KubernetesMeshEnvListener implements MeshEnvListener {
+    public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListener.class);
+    private volatile static boolean usingApiServer = false;
+    private volatile static KubernetesClient kubernetesClient;
+    private volatile static String namespace;
+
+    private final Map<String, MeshAppRuleListener> appRuleListenerMap = new ConcurrentHashMap<>();
+
+    private final Map<String, Watch> vsAppWatch = new ConcurrentHashMap<>();
+    private final Map<String, Watch> drAppWatch = new ConcurrentHashMap<>();
+
+    private final Map<String, String> vsAppCache = new ConcurrentHashMap<>();
+    private final Map<String, String> drAppCache = new ConcurrentHashMap<>();
+
+    public static void injectKubernetesEnv(KubernetesClient client, String configuredNamespace) {
+        usingApiServer = true;
+        kubernetesClient = client;
+        namespace = configuredNamespace;
+    }
+
+    @Override
+    public boolean isEnable() {
+        return usingApiServer;
+    }
+
+    @Override
+    public void onSubscribe(String appName, MeshAppRuleListener listener) {
+        appRuleListenerMap.put(appName, listener);
+        logger.info("Subscribe Mesh Rule in Kubernetes. AppName: " + appName);
+
+        // subscribe VisualService
+        subscribeVs(appName);
+
+        // subscribe DestinationRule
+        subscribeDr(appName);
+
+        // notify for start
+        notifyOnce(appName);
+    }
+
+    private void subscribeVs(String appName) {
+        if (vsAppWatch.containsKey(appName)) {
+            return;
+        }
+
+        try {
+            Watch watch = kubernetesClient
+                    .genericKubernetesResources(
+                            MeshConstant.getVsDefinition())
+                    .inNamespace(namespace)
+                    .withName(appName)
+                    .watch(new Watcher<GenericKubernetesResource>() {
+                        @Override
+                        public void eventReceived(Action action, GenericKubernetesResource resource) {
+                            if (logger.isInfoEnabled()) {
+                                logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
+                            }
+
+                            if (action == Action.ADDED || action == Action.MODIFIED) {
+                                String vsRule = new Yaml(new SafeConstructor()).dump(resource);
+                                vsAppCache.put(appName, vsRule);
+                                if (drAppCache.containsKey(appName)) {
+                                    notifyListener(vsRule, appName, drAppCache.get(appName));
+                                }
+                            } else {
+                                appRuleListenerMap.get(appName).receiveConfigInfo("");
+                            }
+                        }
+
+                    @Override
+                    public void onClose(WatcherException cause) {
+                        // ignore
+                    }
+                });
+            vsAppWatch.put(appName, watch);
+            try {
+                GenericKubernetesResource vsRule = kubernetesClient
+                        .genericKubernetesResources(
+                                MeshConstant.getVsDefinition())
+                        .inNamespace(namespace)
+                        .withName(appName)
+                        .get();
+                vsAppCache.put(appName, new Yaml(new SafeConstructor()).dump(vsRule));
+            } catch (Throwable ignore) {
+
+            }
+        } catch (Exception e) {
+            logger.error("Error occurred when listen kubernetes crd.", e);
+        }
+    }
+
+    private void notifyListener(String vsRule, String appName, String drRule) {
+        String rule = vsRule + "\n---\n" + drRule;
+        logger.info("Notify App Rule Listener. AppName: " + appName + " Rule:" + rule);
+
+        appRuleListenerMap.get(appName).receiveConfigInfo(rule);
+    }
+
+    private void subscribeDr(String appName) {
+        if (drAppWatch.containsKey(appName)) {
+            return;
+        }
+
+        try {
+            Watch watch = kubernetesClient
+                    .genericKubernetesResources(
+                            MeshConstant.getDrDefinition())
+                    .inNamespace(namespace)
+                    .withName(appName)
+                    .watch(new Watcher<GenericKubernetesResource>() {
+                        @Override
+                        public void eventReceived(Action action, GenericKubernetesResource resource) {
+                            if (logger.isInfoEnabled()) {
+                                logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
+                            }
+
+                            if (action == Action.ADDED || action == Action.MODIFIED) {
+                                String drRule = new Yaml(new SafeConstructor()).dump(resource);
+
+                                drAppCache.put(appName, drRule);
+                                if (vsAppCache.containsKey(appName)) {
+                                    notifyListener(vsAppCache.get(appName), appName, drRule);
+                                }
+                            } else {
+                                appRuleListenerMap.get(appName).receiveConfigInfo("");
+                            }
+                        }
+
+                        @Override
+                        public void onClose(WatcherException cause) {
+                            // ignore
+                        }
+                    });
+            drAppWatch.put(appName, watch);
+            try {
+                GenericKubernetesResource drRule = kubernetesClient
+                        .genericKubernetesResources(
+                                MeshConstant.getDrDefinition())
+                        .inNamespace(namespace)
+                        .withName(appName)
+                        .get();
+                drAppCache.put(appName, new Yaml(new SafeConstructor()).dump(drRule));
+            } catch (Throwable ignore) {
+
+            }
+        } catch (Exception e) {
+            logger.error("Error occurred when listen kubernetes crd.", e);
+        }
+    }
+
+    private void notifyOnce(String appName) {
+        if (vsAppCache.containsKey(appName) && drAppCache.containsKey(appName)) {
+            notifyListener(vsAppCache.get(appName), appName, drAppCache.get(appName));
+        }
+    }
+
+    @Override
+    public void onUnSubscribe(String appName) {
+        appRuleListenerMap.remove(appName);
+
+        if (vsAppWatch.containsKey(appName)) {
+            vsAppWatch.remove(appName).close();
+        }
+        vsAppCache.remove(appName);
+
+        if (drAppWatch.containsKey(appName)) {
+            drAppWatch.remove(appName).close();
+        }
+        drAppCache.remove(appName);
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java
new file mode 100644
index 0000000..9d1c6d0
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListenerFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class KubernetesMeshEnvListenerFactory implements MeshEnvListenerFactory {
+    public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListenerFactory.class);
+    private final AtomicBoolean initialized = new AtomicBoolean(false);
+    private MeshEnvListener listener = null;
+
+    @Override
+    public MeshEnvListener getListener() {
+        try {
+            if (initialized.compareAndSet(false, true)) {
+                listener = new NopKubernetesMeshEnvListener();
+            }
+        } catch (Throwable t) {
+            logger.info("Current Env not support Kubernetes.");
+        }
+        return listener;
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java
new file mode 100644
index 0000000..b221c89
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.NotifyListener;
+import org.apache.dubbo.registry.support.FailbackRegistry;
+
+/**
+ * Empty implements for Kubernetes <br/>
+ * Kubernetes only support `Service Discovery` mode register <br/>
+ * Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
+ * {@link KubernetesServiceDiscovery} is the real implementation of Kubernetes
+ */
+public class KubernetesRegistry extends FailbackRegistry {
+    public KubernetesRegistry(URL url) {
+        super(url);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void doRegister(URL url) {
+
+    }
+
+    @Override
+    public void doUnregister(URL url) {
+
+    }
+
+    @Override
+    public void doSubscribe(URL url, NotifyListener listener) {
+
+    }
+
+    @Override
+    public void doUnsubscribe(URL url, NotifyListener listener) {
+
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
new file mode 100644
index 0000000..fe0e047
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.support.AbstractRegistryFactory;
+
+public class KubernetesRegistryFactory extends AbstractRegistryFactory {
+
+    @Override
+    protected String createRegistryCacheKey(URL url) {
+        return url.toFullString();
+    }
+
+    @Override
+    protected Registry createRegistry(URL url) {
+        return new KubernetesRegistry(url);
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
new file mode 100644
index 0000000..087de56
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
@@ -0,0 +1,444 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
+import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelUtil;
+
+import com.alibaba.fastjson.JSONObject;
+import io.fabric8.kubernetes.api.model.EndpointAddress;
+import io.fabric8.kubernetes.api.model.EndpointPort;
+import io.fabric8.kubernetes.api.model.EndpointSubset;
+import io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
+import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+public class KubernetesServiceDiscovery extends AbstractServiceDiscovery {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private KubernetesClient kubernetesClient;
+
+    private String currentHostname;
+
+    private final URL registryURL;
+
+    private final String namespace;
+
+    private final boolean enableRegister;
+
+    public final static String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
+
+    private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, SharedIndexInformer<Service>> SERVICE_INFORMER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, SharedIndexInformer<Pod>> PODS_INFORMER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, SharedIndexInformer<Endpoints>> ENDPOINTS_INFORMER = new ConcurrentHashMap<>(64);
+
+    public KubernetesServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
+        super(applicationModel, registryURL);
+        Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
+        this.kubernetesClient = new KubernetesClientBuilder().withConfig(config).build();
+        this.currentHostname = System.getenv("HOSTNAME");
+        this.registryURL = registryURL;
+        this.namespace = config.getNamespace();
+        this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
+
+        boolean availableAccess;
+        try {
+            availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
+        } catch (Throwable e) {
+            availableAccess = false;
+        }
+        if (!availableAccess) {
+            String message = "Unable to access api server. " +
+                    "Please check your url config." +
+                    " Master URL: " + config.getMasterUrl() +
+                    " Hostname: " + currentHostname;
+            logger.error(message);
+        } else {
+            KubernetesMeshEnvListener.injectKubernetesEnv(kubernetesClient, namespace);
+        }
+    }
+
+    @Override
+    public void doDestroy() {
+        SERVICE_INFORMER.forEach((k, v) -> v.close());
+        SERVICE_INFORMER.clear();
+
+        PODS_INFORMER.forEach((k, v) -> v.close());
+        PODS_INFORMER.clear();
+
+        ENDPOINTS_INFORMER.forEach((k, v) -> v.close());
+        ENDPOINTS_INFORMER.clear();
+
+        kubernetesClient.close();
+    }
+
+    @Override
+    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
+        if (enableRegister) {
+            kubernetesClient
+                    .pods()
+                    .inNamespace(namespace)
+                    .withName(currentHostname)
+                    .edit(pod ->
+                            new PodBuilder(pod)
+                                    .editOrNewMetadata()
+                                    .addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
+                                    .endMetadata()
+                                    .build());
+            if (logger.isInfoEnabled()) {
+                logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
+                        "Current pod name: " + currentHostname);
+            }
+        }
+    }
+
+    /**
+     * Comparing to {@link AbstractServiceDiscovery#doUpdate(ServiceInstance)}, unregister() is unnecessary here.
+     */
+    @Override
+    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
+        reportMetadata(serviceInstance.getServiceMetadata());
+        this.doRegister(serviceInstance);
+    }
+
+    @Override
+    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
+        if (enableRegister) {
+            kubernetesClient
+                    .pods()
+                    .inNamespace(namespace)
+                    .withName(currentHostname)
+                    .edit(pod ->
+                            new PodBuilder(pod)
+                                    .editOrNewMetadata()
+                                    .removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
+                                    .endMetadata()
+                                    .build());
+            if (logger.isInfoEnabled()) {
+                logger.info("Remove Current Service Instance from Kubernetes pod. Current pod name: " + currentHostname);
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return kubernetesClient
+                .services()
+                .inNamespace(namespace)
+                .list()
+                .getItems()
+                .stream()
+                .map(service -> service.getMetadata().getName())
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
+        Endpoints endpoints = null;
+        SharedIndexInformer<Endpoints> endInformer = ENDPOINTS_INFORMER.get(serviceName);
+        if (endInformer != null) {
+            // get endpoints directly from informer local store
+            List<Endpoints> endpointsList = endInformer.getStore().list();
+            if (endpointsList.size() > 0) {
+                endpoints = endpointsList.get(0);
+            }
+        }
+        if (endpoints == null) {
+            endpoints = kubernetesClient
+                    .endpoints()
+                    .inNamespace(namespace)
+                    .withName(serviceName)
+                    .get();
+        }
+
+        return toServiceInstance(endpoints, serviceName);
+    }
+
+    @Override
+    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
+        listener.getServiceNames().forEach(serviceName -> {
+            SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
+
+            // Watch Service Endpoint Modification
+            watchEndpoints(listener, serviceName);
+
+            // Watch Pods Modification, happens when ServiceInstance updated
+            watchPods(listener, serviceName);
+
+            // Watch Service Modification, happens when Service Selector updated, used to update pods watcher
+            watchService(listener, serviceName);
+        });
+    }
+
+    private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
+        SharedIndexInformer<Endpoints> endInformer = kubernetesClient
+                .endpoints()
+                .inNamespace(namespace)
+                .withName(serviceName)
+                .inform(new ResourceEventHandler<Endpoints>() {
+                    @Override
+                    public void onAdd(Endpoints endpoints) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Endpoint Event. Event type: added. Current pod name: " + currentHostname +
+                                    ". Endpoints is: " + endpoints);
+                        }
+                        notifyServiceChanged(serviceName, listener, toServiceInstance(endpoints, serviceName));
+                    }
+
+                    @Override
+                    public void onUpdate(Endpoints oldEndpoints, Endpoints newEndpoints) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Endpoint Event. Event type: updated. Current pod name: " + currentHostname +
+                                    ". The new Endpoints is: " + newEndpoints);
+                        }
+                        notifyServiceChanged(serviceName, listener, toServiceInstance(newEndpoints, serviceName));
+                    }
+
+                    @Override
+                    public void onDelete(Endpoints endpoints, boolean deletedFinalStateUnknown) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Endpoint Event. Event type: deleted. Current pod name: " + currentHostname +
+                                    ". Endpoints is: " + endpoints);
+                        }
+                        notifyServiceChanged(serviceName, listener, toServiceInstance(endpoints, serviceName));
+                    }
+                });
+
+        ENDPOINTS_INFORMER.put(serviceName, endInformer);
+    }
+
+    private void watchPods(ServiceInstancesChangedListener listener, String serviceName) {
+        Map<String, String> serviceSelector = getServiceSelector(serviceName);
+        if (serviceSelector == null) {
+            return;
+        }
+
+        SharedIndexInformer<Pod> podInformer = kubernetesClient
+                .pods()
+                .inNamespace(namespace)
+                .withLabels(serviceSelector)
+                .inform(new ResourceEventHandler<Pod>() {
+                    @Override
+                    public void onAdd(Pod pod) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Pods Event. Event type: added. Current pod name: " + currentHostname +
+                                    ". Pod is: " + pod);
+                        }
+                    }
+
+                    @Override
+                    public void onUpdate(Pod oldPod, Pod newPod) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Pods Event. Event type: updated. Current pod name: " + currentHostname +
+                                    ". new Pod is: " + newPod);
+                        }
+
+                        notifyServiceChanged(serviceName, listener, getInstances(serviceName));
+                    }
+
+                    @Override
+                    public void onDelete(Pod pod, boolean deletedFinalStateUnknown) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Pods Event. Event type: deleted. Current pod name: " + currentHostname +
+                                    ". Pod is: " + pod);
+                        }
+                    }
+                });
+
+        PODS_INFORMER.put(serviceName, podInformer);
+    }
+
+    private void watchService(ServiceInstancesChangedListener listener, String serviceName) {
+        SharedIndexInformer<Service> serviceInformer = kubernetesClient
+                .services()
+                .inNamespace(namespace)
+                .withName(serviceName)
+                .inform(
+                        new ResourceEventHandler<Service>() {
+                            @Override
+                            public void onAdd(Service service) {
+                                if (logger.isDebugEnabled()) {
+                                    logger.debug("Received Service Added Event. " +
+                                            "Current pod name: " + currentHostname);
+                                }
+                            }
+
+                            @Override
+                            public void onUpdate(Service oldService, Service newService) {
+                                if (logger.isDebugEnabled()) {
+                                    logger.debug("Received Service Update Event. Update Pods Watcher. Current pod name: " + currentHostname +
+                                            ". The new Service is: " + newService);
+                                }
+                                if (PODS_INFORMER.containsKey(serviceName)) {
+                                    PODS_INFORMER.get(serviceName).close();
+                                    PODS_INFORMER.remove(serviceName);
+                                }
+                                watchPods(listener, serviceName);
+                            }
+
+                            @Override
+                            public void onDelete(Service service, boolean deletedFinalStateUnknown) {
+                                if (logger.isDebugEnabled()) {
+                                    logger.debug("Received Service Delete Event. " +
+                                            "Current pod name: " + currentHostname);
+                                }
+                            }
+                        }
+                );
+
+        SERVICE_INFORMER.put(serviceName, serviceInformer);
+    }
+
+    private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> serviceInstanceList) {
+        long receivedTime = System.nanoTime();
+
+        ServiceInstancesChangedEvent event;
+
+        event = new ServiceInstancesChangedEvent(serviceName, serviceInstanceList);
+
+        AtomicLong updateTime = SERVICE_UPDATE_TIME.get(serviceName);
+        long lastUpdateTime = updateTime.get();
+
+        if (lastUpdateTime <= receivedTime) {
+            if (updateTime.compareAndSet(lastUpdateTime, receivedTime)) {
+                listener.onEvent(event);
+                return;
+            }
+        }
+
+        if (logger.isInfoEnabled()) {
+            logger.info("Discard Service Instance Data. " +
+                    "Possible Cause: Newer message has been processed or Failed to update time record by CAS. " +
+                    "Current Data received time: " + receivedTime + ". " +
+                    "Newer Data received time: " + lastUpdateTime + ".");
+        }
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    private Map<String, String> getServiceSelector(String serviceName) {
+        Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get();
+        if (service == null) {
+            return null;
+        }
+        return service.getSpec().getSelector();
+    }
+
+    private List<ServiceInstance> toServiceInstance(Endpoints endpoints, String serviceName) {
+        Map<String, String> serviceSelector = getServiceSelector(serviceName);
+        if (serviceSelector == null) {
+            return new LinkedList<>();
+        }
+        Map<String, Pod> pods = kubernetesClient
+                .pods()
+                .inNamespace(namespace)
+                .withLabels(serviceSelector)
+                .list()
+                .getItems()
+                .stream()
+                .collect(
+                        Collectors.toMap(
+                                pod -> pod.getMetadata().getName(),
+                                pod -> pod));
+
+        List<ServiceInstance> instances = new LinkedList<>();
+        Set<Integer> instancePorts = new HashSet<>();
+
+        for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
+            instancePorts.addAll(
+                    endpointSubset.getPorts()
+                            .stream().map(EndpointPort::getPort)
+                            .collect(Collectors.toSet()));
+        }
+
+        for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
+            for (EndpointAddress address : endpointSubset.getAddresses()) {
+                Pod pod = pods.get(address.getTargetRef().getName());
+                String ip = address.getIp();
+                if (pod == null) {
+                    logger.warn("Unable to match Kubernetes Endpoint address with Pod. " +
+                            "EndpointAddress Hostname: " + address.getTargetRef().getName());
+                    continue;
+                }
+                instancePorts.forEach(port -> {
+                    ServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, ip, port, ScopeModelUtil.getApplicationModel(getUrl().getScopeModel()));
+
+                    String properties = pod.getMetadata().getAnnotations().get(KUBERNETES_PROPERTIES_KEY);
+                    if (StringUtils.isNotEmpty(properties)) {
+                        serviceInstance.getMetadata().putAll(JSONObject.parseObject(properties, Map.class));
+                        instances.add(serviceInstance);
+                    } else {
+                        logger.warn("Unable to find Service Instance metadata in Pod Annotations. " +
+                                "Possibly cause: provider has not been initialized successfully. " +
+                                "EndpointAddress Hostname: " + address.getTargetRef().getName());
+                    }
+                });
+            }
+        }
+
+        return instances;
+    }
+
+    /**
+     * UT used only
+     */
+    @Deprecated
+    public void setCurrentHostname(String currentHostname) {
+        this.currentHostname = currentHostname;
+    }
+
+    /**
+     * UT used only
+     */
+    @Deprecated
+    public void setKubernetesClient(KubernetesClient kubernetesClient) {
+        this.kubernetesClient = kubernetesClient;
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java
new file mode 100644
index 0000000..7d11dfa
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java
@@ -0,0 +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.kubernetes;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+
+public class KubernetesServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
+    @Override
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new KubernetesServiceDiscovery(applicationModel, registryURL);
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/MeshConstant.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/MeshConstant.java
new file mode 100644
index 0000000..813bdd8
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/MeshConstant.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
+
+public class MeshConstant {
+    public static CustomResourceDefinitionContext getVsDefinition() {
+        // TODO cache
+        return new CustomResourceDefinitionContext.Builder()
+            .withGroup("service.dubbo.apache.org")
+            .withVersion("v1alpha1")
+            .withScope("Namespaced")
+            .withName("virtualservices.service.dubbo.apache.org")
+            .withPlural("virtualservices")
+            .withKind("VirtualService").build();
+    }
+
+    public static CustomResourceDefinitionContext getDrDefinition() {
+        // TODO cache
+        return new CustomResourceDefinitionContext.Builder()
+            .withGroup("service.dubbo.apache.org")
+            .withVersion("v1alpha1")
+            .withScope("Namespaced")
+            .withName("destinationrules.service.dubbo.apache.org")
+            .withPlural("destinationrules")
+            .withKind("DestinationRule").build();
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/NopKubernetesMeshEnvListener.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/NopKubernetesMeshEnvListener.java
new file mode 100644
index 0000000..1238efb
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/NopKubernetesMeshEnvListener.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
+
+public class NopKubernetesMeshEnvListener implements MeshEnvListener {
+
+    @Override
+    public boolean isEnable() {
+        return false;
+    }
+
+    @Override
+    public void onSubscribe(String appName, MeshAppRuleListener listener) {
+
+    }
+
+    @Override
+    public void onUnSubscribe(String appName) {
+
+    }
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java
new file mode 100644
index 0000000..d4591a2
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes.util;
+
+public class KubernetesClientConst {
+    public static final String DEFAULT_MASTER_PLACEHOLDER = "DEFAULT_MASTER_HOST";
+    public static final String DEFAULT_MASTER_URL = "https://kubernetes.default.svc";
+
+    public final static String ENABLE_REGISTER = "enableRegister";
+
+    public final static String TRUST_CERTS = "trustCerts";
+
+    public final static String USE_HTTPS = "useHttps";
+
+    public static final String HTTP2_DISABLE = "http2Disable";
+
+    public final static String NAMESPACE = "namespace";
+
+    public final static String API_VERSION = "apiVersion";
+
+    public final static String CA_CERT_FILE = "caCertFile";
+
+    public final static String CA_CERT_DATA = "caCertData";
+
+    public final static String CLIENT_CERT_FILE = "clientCertFile";
+
+    public final static String CLIENT_CERT_DATA = "clientCertData";
+
+    public final static String CLIENT_KEY_FILE = "clientKeyFile";
+
+    public final static String CLIENT_KEY_DATA = "clientKeyData";
+
+    public final static String CLIENT_KEY_ALGO = "clientKeyAlgo";
+
+    public final static String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase";
+
+    public final static String OAUTH_TOKEN = "oauthToken";
+
+    public final static String USERNAME = "username";
+
+    public final static String PASSWORD = "password";
+
+    public final static String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval";
+
+    public final static String WATCH_RECONNECT_LIMIT = "watchReconnectLimit";
+
+    public final static String CONNECTION_TIMEOUT = "connectionTimeout";
+
+    public final static String REQUEST_TIMEOUT = "requestTimeout";
+
+    public final static String ROLLING_TIMEOUT = "rollingTimeout";
+
+    public final static String LOGGING_INTERVAL = "loggingInterval";
+
+    public final static String HTTP_PROXY = "httpProxy";
+
+    public final static String HTTPS_PROXY = "httpsProxy";
+
+    public final static String PROXY_USERNAME = "proxyUsername";
+
+    public final static String PROXY_PASSWORD = "proxyPassword";
+
+    public final static String NO_PROXY = "noProxy";
+}
diff --git a/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java
new file mode 100644
index 0000000..81505ca
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes.util;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+
+import java.util.Base64;
+
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.API_VERSION;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CA_CERT_DATA;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CA_CERT_FILE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_CERT_DATA;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_CERT_FILE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_ALGO;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_DATA;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_FILE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_PASSPHRASE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CONNECTION_TIMEOUT;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.DEFAULT_MASTER_PLACEHOLDER;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.DEFAULT_MASTER_URL;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTP2_DISABLE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTPS_PROXY;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTP_PROXY;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.LOGGING_INTERVAL;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NAMESPACE;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NO_PROXY;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.OAUTH_TOKEN;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PASSWORD;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PROXY_PASSWORD;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PROXY_USERNAME;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.REQUEST_TIMEOUT;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.ROLLING_TIMEOUT;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.TRUST_CERTS;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.USERNAME;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.USE_HTTPS;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.WATCH_RECONNECT_INTERVAL;
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.WATCH_RECONNECT_LIMIT;
+
+public class KubernetesConfigUtils {
+
+    public static Config createKubernetesConfig(URL url) {
+        // Init default config
+        Config base = Config.autoConfigure(null);
+
+        // replace config with parameters if presents
+        return new ConfigBuilder(base) //
+            .withMasterUrl(buildMasterUrl(url)) //
+            .withApiVersion(url.getParameter(API_VERSION, base.getApiVersion())) //
+            .withNamespace(url.getParameter(NAMESPACE, base.getNamespace())) //
+            .withUsername(url.getParameter(USERNAME, base.getUsername())) //
+            .withPassword(url.getParameter(PASSWORD, base.getPassword())) //
+
+            .withOauthToken(url.getParameter(OAUTH_TOKEN, base.getOauthToken())) //
+
+            .withCaCertFile(url.getParameter(CA_CERT_FILE, base.getCaCertFile())) //
+            .withCaCertData(url.getParameter(CA_CERT_DATA, decodeBase64(base.getCaCertData()))) //
+
+            .withClientKeyFile(url.getParameter(CLIENT_KEY_FILE, base.getClientKeyFile())) //
+            .withClientKeyData(url.getParameter(CLIENT_KEY_DATA, decodeBase64(base.getClientKeyData()))) //
+
+            .withClientCertFile(url.getParameter(CLIENT_CERT_FILE, base.getClientCertFile())) //
+            .withClientCertData(url.getParameter(CLIENT_CERT_DATA, decodeBase64(base.getClientCertData()))) //
+
+            .withClientKeyAlgo(url.getParameter(CLIENT_KEY_ALGO, base.getClientKeyAlgo())) //
+            .withClientKeyPassphrase(url.getParameter(CLIENT_KEY_PASSPHRASE, base.getClientKeyPassphrase())) //
+
+            .withConnectionTimeout(url.getParameter(CONNECTION_TIMEOUT, base.getConnectionTimeout())) //
+            .withRequestTimeout(url.getParameter(REQUEST_TIMEOUT, base.getRequestTimeout())) //
+            .withRollingTimeout(url.getParameter(ROLLING_TIMEOUT, base.getRollingTimeout())) //
+
+            .withWatchReconnectInterval(url.getParameter(WATCH_RECONNECT_INTERVAL, base.getWatchReconnectInterval())) //
+            .withWatchReconnectLimit(url.getParameter(WATCH_RECONNECT_LIMIT, base.getWatchReconnectLimit())) //
+            .withLoggingInterval(url.getParameter(LOGGING_INTERVAL, base.getLoggingInterval())) //
+
+            .withTrustCerts(url.getParameter(TRUST_CERTS, base.isTrustCerts())) //
+            .withHttp2Disable(url.getParameter(HTTP2_DISABLE, base.isTrustCerts())) //
+
+            .withHttpProxy(url.getParameter(HTTP_PROXY, base.getHttpProxy())) //
+            .withHttpsProxy(url.getParameter(HTTPS_PROXY, base.getHttpsProxy())) //
+            .withProxyUsername(url.getParameter(PROXY_USERNAME, base.getProxyUsername())) //
+            .withProxyPassword(url.getParameter(PROXY_PASSWORD, base.getProxyPassword())) //
+            .withNoProxy(url.getParameter(NO_PROXY, base.getNoProxy())) //
+            .build();
+    }
+
+    private static String buildMasterUrl(URL url) {
+        if (DEFAULT_MASTER_PLACEHOLDER.equalsIgnoreCase(url.getHost())) {
+            return DEFAULT_MASTER_URL;
+        }
+        return (url.getParameter(USE_HTTPS, true) ?
+            "https://" : "http://")
+            + url.getHost() + ":" + url.getPort();
+    }
+
+    private static String decodeBase64(String str) {
+        return StringUtils.isNotEmpty(str) ?
+            new String(Base64.getDecoder().decode(str)) :
+            null;
+    }
+}
diff --git a/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
new file mode 100644
index 0000000..94177d8
--- /dev/null
+++ b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
@@ -0,0 +1 @@
+kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesRegistryFactory
\ No newline at end of file
diff --git a/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..3e1b88e
--- /dev/null
+++ b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..4301ab8
--- /dev/null
+++ b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory
new file mode 100644
index 0000000..4dfae84
--- /dev/null
+++ b/dubbo-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory
@@ -0,0 +1 @@
+kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesMeshEnvListenerFactory
diff --git a/dubbo-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java b/dubbo-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java
new file mode 100644
index 0000000..28aa002
--- /dev/null
+++ b/dubbo-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelUtil;
+
+import io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.EndpointsBuilder;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodBuilder;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NAMESPACE;
+
+@ExtendWith({MockitoExtension.class})
+public class KubernetesServiceDiscoveryTest {
+    private static final String SERVICE_NAME = "TestService";
+
+    private static final String POD_NAME = "TestServer";
+
+    public KubernetesServer mockServer = new KubernetesServer(false, true);
+
+    private NamespacedKubernetesClient mockClient;
+
+    private ServiceInstancesChangedListener mockListener = Mockito.mock(ServiceInstancesChangedListener.class);
+
+    private URL serverUrl;
+
+    private Map<String, String> selector;
+
+    private KubernetesServiceDiscovery serviceDiscovery;
+
+
+    @BeforeEach
+    public void setUp() {
+        mockServer.before();
+        mockClient = mockServer.getClient().inNamespace("dubbo-demo");
+
+        ApplicationModel applicationModel = ApplicationModel.defaultModel();
+        applicationModel.getApplicationConfigManager().setApplication(new ApplicationConfig());
+
+        serverUrl = URL.valueOf(mockClient.getConfiguration().getMasterUrl())
+                .setProtocol("kubernetes")
+                .addParameter(NAMESPACE, "dubbo-demo")
+                .addParameter(KubernetesClientConst.USE_HTTPS, "false")
+                .addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
+        serverUrl.setScopeModel(applicationModel);
+
+        this.serviceDiscovery = new KubernetesServiceDiscovery(applicationModel, serverUrl);
+
+        System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
+        System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
+
+        selector = new HashMap<>(4);
+        selector.put("l", "v");
+        Pod pod = new PodBuilder()
+                .withNewMetadata().withName(POD_NAME).withLabels(selector).endMetadata()
+                .build();
+
+        Service service = new ServiceBuilder()
+                .withNewMetadata().withName(SERVICE_NAME).endMetadata()
+                .withNewSpec().withSelector(selector).endSpec().build();
+
+        Endpoints endPoints = new EndpointsBuilder()
+                .withNewMetadata().withName(SERVICE_NAME).endMetadata()
+                .addNewSubset()
+                .addNewAddress().withIp("ip1")
+                .withNewTargetRef().withUid("uid1").withName(POD_NAME).endTargetRef().endAddress()
+                .addNewPort("Test", "Test", 12345, "TCP").endSubset()
+                .build();
+
+        mockClient.pods().resource(pod).create();
+        mockClient.services().resource(service).create();
+        mockClient.endpoints().resource(endPoints).create();
+    }
+
+    @AfterEach
+    public void destroy() throws Exception {
+        serviceDiscovery.destroy();
+        mockClient.close();
+        mockServer.after();
+    }
+
+    @Test
+    public void testEndpointsUpdate() throws Exception {
+        serviceDiscovery.setCurrentHostname(POD_NAME);
+        serviceDiscovery.setKubernetesClient(mockClient);
+
+        ServiceInstance serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+
+        serviceDiscovery.doRegister(serviceInstance);
+
+        HashSet<String> serviceList = new HashSet<>(4);
+        serviceList.add(SERVICE_NAME);
+        Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
+        Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
+
+        serviceDiscovery.addServiceInstancesChangedListener(mockListener);
+        mockClient.endpoints().withName(SERVICE_NAME)
+                .edit(endpoints ->
+                        new EndpointsBuilder(endpoints)
+                                .editFirstSubset()
+                                .addNewAddress()
+                                .withIp("ip2")
+                                .withNewTargetRef().withUid("uid2").withName(POD_NAME).endTargetRef()
+                                .endAddress().endSubset()
+                                .build());
+
+        Thread.sleep(2000);
+        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+                ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(mockListener, Mockito.times(2)).onEvent(eventArgumentCaptor.capture());
+        Assertions.assertEquals(2, eventArgumentCaptor.getValue().getServiceInstances().size());
+
+        serviceDiscovery.doUnregister(serviceInstance);
+    }
+
+    @Test
+    public void testPodsUpdate() throws Exception {
+        serviceDiscovery.setCurrentHostname(POD_NAME);
+        serviceDiscovery.setKubernetesClient(mockClient);
+
+        ServiceInstance serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+
+        serviceDiscovery.doRegister(serviceInstance);
+
+        HashSet<String> serviceList = new HashSet<>(4);
+        serviceList.add(SERVICE_NAME);
+        Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
+        Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
+
+        serviceDiscovery.addServiceInstancesChangedListener(mockListener);
+
+        serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "Test12345", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+        serviceDiscovery.doUpdate(serviceInstance);
+
+        Thread.sleep(2000);
+        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+                ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(mockListener, Mockito.times(1)).onEvent(eventArgumentCaptor.capture());
+        Assertions.assertEquals(1, eventArgumentCaptor.getValue().getServiceInstances().size());
+
+        serviceDiscovery.doUnregister(serviceInstance);
+    }
+
+    @Test
+    public void testServiceUpdate() throws Exception {
+        serviceDiscovery.setCurrentHostname(POD_NAME);
+        serviceDiscovery.setKubernetesClient(mockClient);
+
+        ServiceInstance serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+
+        serviceDiscovery.doRegister(serviceInstance);
+
+        HashSet<String> serviceList = new HashSet<>(4);
+        serviceList.add(SERVICE_NAME);
+        Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
+        Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
+
+        serviceDiscovery.addServiceInstancesChangedListener(mockListener);
+
+        selector.put("app", "test");
+        mockClient.services().withName(SERVICE_NAME)
+                .edit(service -> new ServiceBuilder(service)
+                        .editSpec()
+                        .addToSelector(selector)
+                        .endSpec()
+                        .build());
+
+        Thread.sleep(2000);
+        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+                ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(mockListener, Mockito.times(1)).onEvent(eventArgumentCaptor.capture());
+        Assertions.assertEquals(1, eventArgumentCaptor.getValue().getServiceInstances().size());
+
+        serviceDiscovery.doUnregister(serviceInstance);
+    }
+
+    @Test
+    public void testGetInstance() {
+        serviceDiscovery.setCurrentHostname(POD_NAME);
+        serviceDiscovery.setKubernetesClient(mockClient);
+
+        ServiceInstance serviceInstance = new DefaultServiceInstance(SERVICE_NAME, "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+
+        serviceDiscovery.doRegister(serviceInstance);
+
+        serviceDiscovery.doUpdate(serviceInstance);
+
+        Assertions.assertEquals(1, serviceDiscovery.getServices().size());
+        Assertions.assertEquals(1, serviceDiscovery.getInstances(SERVICE_NAME).size());
+
+        serviceDiscovery.doUnregister(serviceInstance);
+    }
+}
diff --git a/dubbo-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/dubbo-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..ca6ee9c
--- /dev/null
+++ b/dubbo-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractCacheManager.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractCacheManager.java
index 4863f3d..f299353 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractCacheManager.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractCacheManager.java
@@ -46,7 +46,7 @@
         try {
             cacheStore = FileCacheStoreFactory.getInstance(filePath, fileName, enableFileCache);
             Map<String, String> properties = cacheStore.loadCache(entrySize);
-            logger.info("Successfully loaded mapping cache from file " + fileName + ", entries " + properties.size());
+            logger.info("Successfully loaded " + getName() + " cache from file " + fileName + ", entries " + properties.size());
             for (Map.Entry<String, String> entry : properties.entrySet()) {
                 String key = entry.getKey();
                 String value = entry.getValue();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
index 534f000..dc4d1c7 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/AbstractServiceNameMapping.java
@@ -24,7 +24,6 @@
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.ScopeModelAware;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -50,7 +49,7 @@
 import static org.apache.dubbo.common.utils.CollectionUtils.toTreeSet;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
-public abstract class AbstractServiceNameMapping implements ServiceNameMapping, ScopeModelAware {
+public abstract class AbstractServiceNameMapping implements ServiceNameMapping {
     protected final Logger logger = LoggerFactory.getLogger(getClass());
     protected ApplicationModel applicationModel;
     private final MappingCacheManager mappingCacheManager;
@@ -73,7 +72,7 @@
             .getBean(FrameworkExecutorRepository.class).getCacheRefreshingScheduledExecutor());
     }
 
-    @Override
+    // just for test
     public void setApplicationModel(ApplicationModel applicationModel) {
         this.applicationModel = applicationModel;
     }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java
index 7687b04..241ba8c 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.extension.Activate;
 
+import static org.apache.dubbo.common.constants.CommonConstants.IPV6_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
@@ -37,9 +38,10 @@
     private final String[] includedInstanceParams;
 
     public DefaultMetadataParamsFilter() {
-        this.includedInstanceParams = new String[]{HEARTBEAT_TIMEOUT_KEY, TIMESTAMP_KEY};
+        this.includedInstanceParams = new String[]{HEARTBEAT_TIMEOUT_KEY, TIMESTAMP_KEY, IPV6_KEY};
         this.excludedServiceParams = new String[]{MONITOR_KEY, BIND_IP_KEY, BIND_PORT_KEY, QOS_ENABLE,
-            QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY, INTERFACES, PID_KEY, TIMESTAMP_KEY, HEARTBEAT_TIMEOUT_KEY};
+            QOS_HOST, QOS_PORT, ACCEPT_FOREIGN_IP, VALIDATION_KEY, INTERFACES, PID_KEY, TIMESTAMP_KEY, HEARTBEAT_TIMEOUT_KEY,
+            IPV6_KEY};
     }
 
     @Override
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingCacheManager.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingCacheManager.java
index 90d44b3..2365f6c 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingCacheManager.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MappingCacheManager.java
@@ -21,7 +21,6 @@
 import org.apache.dubbo.rpc.model.ScopeModel;
 
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -66,10 +65,4 @@
     protected String getName() {
         return "mapping";
     }
-
-    public void update(Map<String, Set<String>> newCache) {
-        for (Map.Entry<String, Set<String>> entry : newCache.entrySet()) {
-            cache.put(entry.getKey(), entry.getValue());
-        }
-    }
 }
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 645eb9e..baa2559 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
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.metadata;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
@@ -44,6 +45,7 @@
 import java.util.concurrent.ConcurrentNavigableMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DOT_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
@@ -253,6 +255,13 @@
         return serviceInfo;
     }
 
+    public List<ServiceInfo> getMatchedServiceInfos(ProtocolServiceKey consumerProtocolServiceKey) {
+        return getServices().values()
+            .stream()
+            .filter(serviceInfo -> serviceInfo.matchProtocolServiceKey(consumerProtocolServiceKey))
+            .collect(Collectors.toList());
+    }
+
     public Map<String, String> getExtendParams() {
         return extendParams;
     }
@@ -444,6 +453,7 @@
         private String group;
         private String version;
         private String protocol;
+        private int port = -1;
         private String path; // most of the time, path is the same with the interface name.
         private Map<String, String> params;
 
@@ -460,12 +470,14 @@
         // service + group + version + protocol
         private volatile transient String matchKey;
 
+        private volatile transient ProtocolServiceKey protocolServiceKey;
+
         private transient URL url;
 
         public ServiceInfo() {}
 
         public ServiceInfo(URL url, List<MetadataParamsFilter> filters) {
-            this(url.getServiceInterface(), url.getGroup(), url.getVersion(), url.getProtocol(), url.getPath(), null);
+            this(url.getServiceInterface(), url.getGroup(), url.getVersion(), url.getProtocol(), url.getPort(), url.getPath(), null);
             this.url = url;
             Map<String, String> params = extractServiceParams(url, filters);
             // initialize method params caches.
@@ -473,11 +485,12 @@
             this.consumerMethodParams = URLParam.initMethodParameters(consumerParams);
         }
 
-        public ServiceInfo(String name, String group, String version, String protocol, String path, Map<String, String> params) {
+        public ServiceInfo(String name, String group, String version, String protocol, int port, String path, Map<String, String> params) {
             this.name = name;
             this.group = group;
             this.version = version;
             this.protocol = protocol;
+            this.port = port;
             this.path = path;
             this.params = params == null ? new ConcurrentHashMap<>() : params;
 
@@ -584,6 +597,18 @@
             return matchKey;
         }
 
+        public boolean matchProtocolServiceKey(ProtocolServiceKey protocolServiceKey) {
+            return ProtocolServiceKey.Matcher.isMatch(protocolServiceKey, getProtocolServiceKey());
+        }
+
+        public ProtocolServiceKey getProtocolServiceKey() {
+            if (protocolServiceKey != null) {
+                return protocolServiceKey;
+            }
+            protocolServiceKey = new ProtocolServiceKey(name, version, group,  protocol);
+            return protocolServiceKey;
+        }
+
         private String buildServiceKey(String name, String group, String version) {
             this.serviceKey = URL.buildKey(name, group, version);
             return this.serviceKey;
@@ -637,6 +662,14 @@
             this.protocol = protocol;
         }
 
+        public int getPort() {
+            return port;
+        }
+
+        public void setPort(int port) {
+            this.port = port;
+        }
+
         public Map<String, String> getParams() {
             if (params == null) {
                 return Collections.emptyMap();
@@ -702,7 +735,7 @@
         }
 
         public String toDescString() {
-            return this.getMatchKey() + path + new TreeMap<>(getParams());
+            return this.getMatchKey() + port + path + new TreeMap<>(getParams());
         }
 
         public void addParameter(String key, String value) {
@@ -767,12 +800,13 @@
                 && Objects.equals(this.getGroup(), serviceInfo.getGroup())
                 && Objects.equals(this.getName(), serviceInfo.getName())
                 && Objects.equals(this.getProtocol(), serviceInfo.getProtocol())
+                && Objects.equals(this.getPort(), serviceInfo.getPort())
                 && this.getParams().equals(serviceInfo.getParams());
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(getVersion(), getGroup(), getName(), getProtocol(), getParams());
+            return Objects.hash(getVersion(), getGroup(), getName(), getProtocol(), getPort(), getParams());
         }
 
         @Override
@@ -786,6 +820,7 @@
                 "group='" + group + "'," +
                 "version='" + version + "'," +
                 "protocol='" + protocol + "'," +
+                "port='" + port + "'," +
                 "params=" + params + "," +
                 "}";
         }
diff --git a/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/model/SimpleTypeModel.java b/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/model/SimpleTypeModel.java
index 4b3523a..c745c92 100644
--- a/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/model/SimpleTypeModel.java
+++ b/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/model/SimpleTypeModel.java
@@ -2,15 +2,15 @@
  * 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 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.
+ * 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.
  */
diff --git a/dubbo-metadata/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
index e8a5bcc..c8c99fc 100644
--- a/dubbo-metadata/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java
@@ -52,8 +52,8 @@
  */
 public class RedisMetadataReport extends AbstractMetadataReport {
 
-    private final static String REDIS_DATABASE_KEY = "database";
-    private final static Logger logger = LoggerFactory.getLogger(RedisMetadataReport.class);
+    private static final String REDIS_DATABASE_KEY = "database";
+    private static final Logger logger = LoggerFactory.getLogger(RedisMetadataReport.class);
 
     // protected , for test
     protected JedisPool pool;
@@ -66,7 +66,7 @@
         super(url);
         timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
         if (url.getParameter(CLUSTER_KEY, false)) {
-            jedisClusterNodes = new HashSet<HostAndPort>();
+            jedisClusterNodes = new HashSet<>();
             List<URL> urls = url.getBackupUrls();
             for (URL tmpUrl : urls) {
                 jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
@@ -103,7 +103,7 @@
         if (StringUtils.isEmpty(content)) {
             return Collections.emptyList();
         }
-        return new ArrayList<String>(Arrays.asList(URL.decode(content)));
+        return new ArrayList<>(Arrays.asList(URL.decode(content)));
     }
 
     @Override
diff --git a/dubbo-metrics/dubbo-metrics-api/pom.xml b/dubbo-metrics/dubbo-metrics-api/pom.xml
index cb6e664..3c31ae3 100644
--- a/dubbo-metrics/dubbo-metrics-api/pom.xml
+++ b/dubbo-metrics/dubbo-metrics-api/pom.xml
@@ -14,7 +14,8 @@
   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">
+<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>
@@ -35,5 +36,18 @@
             <artifactId>dubbo-common</artifactId>
             <version>${project.parent.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tdunning</groupId>
+            <artifactId>t-digest</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporter.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporter.java
new file mode 100644
index 0000000..4e32c7e
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporter.java
@@ -0,0 +1,169 @@
+/*
+ * 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.metrics;
+
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
+import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
+import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
+import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
+import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.collector.MetricsCollector;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.metrics.collector.AggregateMetricsCollector;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.ENABLE_JVM_METRICS_KEY;
+
+/**
+ * AbstractMetricsReporter.
+ */
+public abstract class AbstractMetricsReporter implements MetricsReporter {
+
+    private final Logger logger = LoggerFactory.getLogger(AbstractMetricsReporter.class);
+
+    private final AtomicBoolean initialized = new AtomicBoolean(false);
+
+    protected final URL url;
+    protected final List<MetricsCollector> collectors = new ArrayList<>();
+    protected final CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
+
+    private final ApplicationModel applicationModel;
+    private ScheduledExecutorService collectorSyncJobExecutor = null;
+
+    private static final int DEFAULT_SCHEDULE_INITIAL_DELAY = 5;
+    private static final int DEFAULT_SCHEDULE_PERIOD = 30;
+
+    protected AbstractMetricsReporter(URL url, ApplicationModel applicationModel) {
+        this.url = url;
+        this.applicationModel = applicationModel;
+    }
+
+    @Override
+    public void init() {
+        if (initialized.compareAndSet(false, true)) {
+            addJvmMetrics();
+            initCollectors();
+            scheduleMetricsCollectorSyncJob();
+
+            doInit();
+
+            registerDubboShutdownHook();
+        }
+    }
+
+    protected void addMeterRegistry(MeterRegistry registry) {
+        compositeRegistry.add(registry);
+    }
+
+    protected ApplicationModel getApplicationModel() {
+        return applicationModel;
+    }
+
+    private void addJvmMetrics() {
+        boolean enableJvmMetrics = url.getParameter(ENABLE_JVM_METRICS_KEY, false);
+        if (enableJvmMetrics) {
+            new ClassLoaderMetrics().bindTo(compositeRegistry);
+            new JvmMemoryMetrics().bindTo(compositeRegistry);
+            new JvmGcMetrics().bindTo(compositeRegistry);
+            new ProcessorMetrics().bindTo(compositeRegistry);
+            new JvmThreadMetrics().bindTo(compositeRegistry);
+        }
+    }
+
+    private void initCollectors() {
+        applicationModel.getBeanFactory().getOrRegisterBean(AggregateMetricsCollector.class);
+
+        collectors.add(applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class));
+        collectors.add(applicationModel.getBeanFactory().getBean(AggregateMetricsCollector.class));
+    }
+
+    private void scheduleMetricsCollectorSyncJob() {
+        NamedThreadFactory threadFactory = new NamedThreadFactory("metrics-collector-sync-job", true);
+        collectorSyncJobExecutor = Executors.newScheduledThreadPool(1, threadFactory);
+        collectorSyncJobExecutor.scheduleWithFixedDelay(() -> {
+            collectors.forEach(collector -> {
+                List<MetricSample> samples = collector.collect();
+                for (MetricSample sample : samples) {
+                    try {
+                        switch (sample.getType()) {
+                            case GAUGE:
+                                GaugeMetricSample gaugeSample = (GaugeMetricSample) sample;
+                                List<Tag> tags = new ArrayList<>();
+                                gaugeSample.getTags().forEach((k, v) -> {
+                                    if (v == null) {
+                                        v = "";
+                                    }
+
+                                    tags.add(Tag.of(k, v));
+                                });
+
+                                Gauge.builder(gaugeSample.getName(), gaugeSample.getSupplier())
+                                    .description(gaugeSample.getDescription()).tags(tags).register(compositeRegistry);
+                                break;
+                            case COUNTER:
+                            case TIMER:
+                            case LONG_TASK_TIMER:
+                            case DISTRIBUTION_SUMMARY:
+                                // TODO
+                                break;
+                            default:
+                                break;
+                        }
+                    } catch (Exception e) {
+                        logger.error("error occurred when synchronize metrics collector.", e);
+                    }
+                }
+            });
+        }, DEFAULT_SCHEDULE_INITIAL_DELAY, DEFAULT_SCHEDULE_PERIOD, TimeUnit.SECONDS);
+    }
+
+    private void registerDubboShutdownHook() {
+        applicationModel.getBeanFactory().getBean(ShutdownHookCallbacks.class).addCallback(this::destroy);
+    }
+
+    public void destroy() {
+        if (collectorSyncJobExecutor != null) {
+            collectorSyncJobExecutor.shutdownNow();
+        }
+
+        doDestroy();
+    }
+
+    protected abstract void doInit();
+
+    protected abstract void doDestroy();
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporterFactory.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporterFactory.java
new file mode 100644
index 0000000..59763ed
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/AbstractMetricsReporterFactory.java
@@ -0,0 +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.metrics;
+
+import org.apache.dubbo.common.metrics.MetricsReporterFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+/**
+ * AbstractMetricsReporterFactory.
+ */
+public abstract class AbstractMetricsReporterFactory implements MetricsReporterFactory {
+
+    private final ApplicationModel applicationModel;
+
+    public AbstractMetricsReporterFactory(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+    }
+
+    protected ApplicationModel getApplicationModel() {
+        return applicationModel;
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounter.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounter.java
new file mode 100644
index 0000000..3dc3666
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounter.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.metrics.aggregate;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper around Counter like Long and Integer.
+ * <p>
+ * Maintains a ring buffer of Counter to provide count over a sliding windows of time.
+ */
+public class TimeWindowCounter {
+    private final Long[] ringBuffer;
+    private final Long[] bucketStartTimeMillis;
+    private int currentBucket;
+    private long lastRotateTimestampMillis;
+    private final long durationBetweenRotatesMillis;
+
+    public TimeWindowCounter(int bucketNum, int timeWindowSeconds) {
+        this.ringBuffer = new Long[bucketNum];
+        this.bucketStartTimeMillis = new Long[bucketNum];
+        for (int i = 0; i < bucketNum; i++) {
+            this.ringBuffer[i] = 0L;
+            this.bucketStartTimeMillis[i] = System.currentTimeMillis();
+        }
+
+        this.currentBucket = 0;
+        this.lastRotateTimestampMillis = System.currentTimeMillis();
+        this.durationBetweenRotatesMillis = TimeUnit.SECONDS.toMillis(timeWindowSeconds) / bucketNum;
+    }
+
+    public synchronized double get() {
+        return rotate();
+    }
+
+    public long bucketLivedSeconds() {
+        return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - bucketStartTimeMillis[currentBucket]);
+    }
+
+    public synchronized void increment() {
+        this.increment(1L);
+    }
+
+    public synchronized void increment(Long step) {
+        rotate();
+        for (int i = 0; i < ringBuffer.length; i++) {
+            ringBuffer[i] = ringBuffer[i] + step;
+        }
+    }
+
+    public synchronized void decrement() {
+        this.decrement(1L);
+    }
+
+    public synchronized void decrement(Long step) {
+        rotate();
+        for (int i = 0; i < ringBuffer.length; i++) {
+            ringBuffer[i] = ringBuffer[i] - step;
+        }
+    }
+
+    private Long rotate() {
+        long timeSinceLastRotateMillis = System.currentTimeMillis() - lastRotateTimestampMillis;
+        while (timeSinceLastRotateMillis > durationBetweenRotatesMillis) {
+            ringBuffer[currentBucket] = 0L;
+            bucketStartTimeMillis[currentBucket] = lastRotateTimestampMillis + durationBetweenRotatesMillis;
+            if (++currentBucket >= ringBuffer.length) {
+                currentBucket = 0;
+            }
+            timeSinceLastRotateMillis -= durationBetweenRotatesMillis;
+            lastRotateTimestampMillis += durationBetweenRotatesMillis;
+        }
+        return ringBuffer[currentBucket];
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantile.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantile.java
new file mode 100644
index 0000000..86249c8
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantile.java
@@ -0,0 +1,75 @@
+/*
+ * 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.metrics.aggregate;
+
+import com.tdunning.math.stats.TDigest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper around TDigest.
+ * <p>
+ * Maintains a ring buffer of TDigest to provide quantiles over a sliding windows of time.
+ */
+public class TimeWindowQuantile {
+    private final double compression;
+    private final TDigest[] ringBuffer;
+    private int currentBucket;
+    private long lastRotateTimestampMillis;
+    private final long durationBetweenRotatesMillis;
+
+    public TimeWindowQuantile(double compression, int bucketNum, int timeWindowSeconds) {
+        this.compression = compression;
+        this.ringBuffer = new TDigest[bucketNum];
+        for (int i = 0; i < bucketNum; i++) {
+            this.ringBuffer[i] = TDigest.createDigest(compression);
+        }
+
+        this.currentBucket = 0;
+        this.lastRotateTimestampMillis = System.currentTimeMillis();
+        this.durationBetweenRotatesMillis = TimeUnit.SECONDS.toMillis(timeWindowSeconds) / bucketNum;
+    }
+
+    public synchronized double quantile(double q) {
+        TDigest currentBucket = rotate();
+
+        // This may return Double.NaN, and it's correct behavior.
+        // see: https://github.com/prometheus/client_golang/issues/85
+        return currentBucket.quantile(q);
+    }
+
+    public synchronized void add(double value) {
+        rotate();
+        for (TDigest bucket : ringBuffer) {
+            bucket.add(value);
+        }
+    }
+
+    private TDigest rotate() {
+        long timeSinceLastRotateMillis = System.currentTimeMillis() - lastRotateTimestampMillis;
+        while (timeSinceLastRotateMillis > durationBetweenRotatesMillis) {
+            ringBuffer[currentBucket] = TDigest.createDigest(compression);
+            if (++currentBucket >= ringBuffer.length) {
+                currentBucket = 0;
+            }
+            timeSinceLastRotateMillis -= durationBetweenRotatesMillis;
+            lastRotateTimestampMillis += durationBetweenRotatesMillis;
+        }
+        return ringBuffer[currentBucket];
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollector.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollector.java
new file mode 100644
index 0000000..302c851
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollector.java
@@ -0,0 +1,157 @@
+/*
+ * 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.metrics.collector;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.collector.MetricsCollector;
+import org.apache.dubbo.common.metrics.event.MetricsEvent;
+import org.apache.dubbo.common.metrics.event.RTEvent;
+import org.apache.dubbo.common.metrics.event.RequestEvent;
+import org.apache.dubbo.common.metrics.listener.MetricsListener;
+import org.apache.dubbo.common.metrics.model.MethodMetric;
+import org.apache.dubbo.common.metrics.model.MetricsKey;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.config.MetricsConfig;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.nested.AggregationConfig;
+import org.apache.dubbo.metrics.aggregate.TimeWindowCounter;
+import org.apache.dubbo.metrics.aggregate.TimeWindowQuantile;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.apache.dubbo.common.metrics.model.MetricsCategory.REQUESTS;
+import static org.apache.dubbo.common.metrics.model.MetricsCategory.QPS;
+import static org.apache.dubbo.common.metrics.model.MetricsCategory.RT;
+
+/**
+ * Aggregation metrics collector implementation of {@link MetricsCollector}.
+ * This collector only enabled when metrics aggregation config is enabled.
+ */
+public class AggregateMetricsCollector implements MetricsCollector, MetricsListener {
+    private int bucketNum;
+    private int timeWindowSeconds;
+
+    private final Map<MethodMetric, TimeWindowCounter> totalRequests = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, TimeWindowCounter> succeedRequests = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, TimeWindowCounter> failedRequests = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, TimeWindowCounter> businessFailedRequests = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, TimeWindowCounter> qps = new ConcurrentHashMap<>();
+    private final Map<MethodMetric, TimeWindowQuantile> rt = new ConcurrentHashMap<>();
+
+    private final ApplicationModel applicationModel;
+
+    private static final Integer DEFAULT_COMPRESSION = 100;
+    private static final Integer DEFAULT_BUCKET_NUM = 10;
+    private static final Integer DEFAULT_TIME_WINDOW_SECONDS = 120;
+
+    public AggregateMetricsCollector(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        ConfigManager configManager = applicationModel.getApplicationConfigManager();
+        MetricsConfig config = configManager.getMetrics().orElse(null);
+        if (config != null && config.getAggregation() != null && Boolean.TRUE.equals(config.getAggregation().getEnabled())) {
+            // only registered when aggregation is enabled.
+            registerListener();
+
+            AggregationConfig aggregation = config.getAggregation();
+            this.bucketNum = aggregation.getBucketNum() == null ? DEFAULT_BUCKET_NUM : aggregation.getBucketNum();
+            this.timeWindowSeconds = aggregation.getTimeWindowSeconds() == null ? DEFAULT_TIME_WINDOW_SECONDS : aggregation.getTimeWindowSeconds();
+        }
+    }
+
+    private void registerListener() {
+        applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class).addListener(this);
+    }
+
+    @Override
+    public void onEvent(MetricsEvent event) {
+        if (event instanceof RTEvent) {
+            onRTEvent((RTEvent) event);
+        } else if (event instanceof RequestEvent) {
+            onRequestEvent((RequestEvent) event);
+        }
+    }
+
+    private void onRTEvent(RTEvent event) {
+        MethodMetric metric = (MethodMetric) event.getSource();
+        Long responseTime = event.getRt();
+        TimeWindowQuantile quantile = rt.computeIfAbsent(metric, k -> new TimeWindowQuantile(DEFAULT_COMPRESSION, bucketNum, timeWindowSeconds));
+        quantile.add(responseTime);
+    }
+
+    private void onRequestEvent(RequestEvent event) {
+        MethodMetric metric = (MethodMetric) event.getSource();
+        RequestEvent.Type type = event.getType();
+        TimeWindowCounter counter = null;
+        switch (type) {
+            case TOTAL:
+                counter = totalRequests.computeIfAbsent(metric, k -> new TimeWindowCounter(bucketNum, timeWindowSeconds));
+                TimeWindowCounter qpsCounter = qps.computeIfAbsent(metric, k -> new TimeWindowCounter(bucketNum, timeWindowSeconds));
+                qpsCounter.increment();
+                break;
+            case SUCCEED:
+                counter = succeedRequests.computeIfAbsent(metric, k -> new TimeWindowCounter(bucketNum, timeWindowSeconds));
+                break;
+            case FAILED:
+                counter = failedRequests.computeIfAbsent(metric, k -> new TimeWindowCounter(bucketNum, timeWindowSeconds));
+                break;
+            case BUSINESS_FAILED:
+                counter = businessFailedRequests.computeIfAbsent(metric, k -> new TimeWindowCounter(bucketNum, timeWindowSeconds));
+                break;
+
+            default:
+                break;
+        }
+
+        if (counter != null) {
+            counter.increment();
+        }
+    }
+
+    @Override
+    public List<MetricSample> collect() {
+        List<MetricSample> list = new ArrayList<>();
+        collectRequests(list);
+        collectQPS(list);
+        collectRT(list);
+
+        return list;
+    }
+
+    private void collectRequests(List<MetricSample> list) {
+        totalRequests.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_TOTAL_AGG, k.getTags(), REQUESTS, v::get)));
+        succeedRequests.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_SUCCEED_AGG, k.getTags(), REQUESTS, v::get)));
+        failedRequests.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_FAILED_AGG, k.getTags(), REQUESTS, v::get)));
+        businessFailedRequests.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_REQUESTS_BUSINESS_FAILED_AGG, k.getTags(), REQUESTS, v::get)));
+    }
+
+    private void collectQPS(List<MetricSample> list) {
+        qps.forEach((k, v) -> list.add(new GaugeMetricSample(MetricsKey.METRIC_QPS, k.getTags(), QPS, () -> v.get() / v.bucketLivedSeconds())));
+    }
+
+    private void collectRT(List<MetricSample> list) {
+        rt.forEach((k, v) -> {
+            list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_P99, k.getTags(), RT, () -> v.quantile(0.99)));
+            list.add(new GaugeMetricSample(MetricsKey.METRIC_RT_P95, k.getTags(), RT, () -> v.quantile(0.95)));
+        });
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsCollectExecutor.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsCollectExecutor.java
new file mode 100644
index 0000000..0ae3fdf
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsCollectExecutor.java
@@ -0,0 +1,110 @@
+/*
+ * 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.metrics.filter;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.METRIC_FILTER_START_TIME;
+
+import java.util.function.Supplier;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+
+public class MetricsCollectExecutor {
+
+    private final DefaultMetricsCollector collector;
+    private final Invocation    invocation;
+    private String  interfaceName;
+    private String  methodName;
+    private String  group;
+    private String  version;
+
+
+    public MetricsCollectExecutor(DefaultMetricsCollector collector, Invocation invocation) {
+        init(invocation);
+
+        this.collector = collector;
+
+        this.invocation = invocation;
+    }
+
+    public void beforeExecute() {
+        collector.increaseTotalRequests(interfaceName, methodName, group, version);
+        collector.increaseProcessingRequests(interfaceName, methodName, group, version);
+        invocation.put(METRIC_FILTER_START_TIME, System.currentTimeMillis());
+    }
+
+    public void postExecute(Result result) {
+        if (result.hasException()) {
+            this.throwExecute(result.getException());
+            return;
+        }
+        collector.increaseSucceedRequests(interfaceName, methodName, group, version);
+        endExecute();
+    }
+
+    public void throwExecute(Throwable throwable){
+        if (throwable instanceof RpcException) {
+            RpcException rpcException = (RpcException)throwable;
+            if (rpcException.isBiz()) {
+                collector.businessFailedRequests(interfaceName, methodName, group, version);
+            }else{
+                collector.increaseFailedRequests(interfaceName, methodName, group, version);
+            }
+        }
+        endExecute(()-> throwable instanceof RpcException && ((RpcException) throwable).isBiz());
+    }
+
+    private void endExecute(){
+        this.endExecute(() -> true);
+    }
+
+    private void endExecute(Supplier<Boolean> rtStat){
+        if (rtStat.get()) {
+            Long endTime = System.currentTimeMillis();
+            Long beginTime = (Long) invocation.get(METRIC_FILTER_START_TIME);
+            Long rt = endTime - beginTime;
+            collector.addRT(interfaceName, methodName, group, version, rt);
+        }
+        collector.decreaseProcessingRequests(interfaceName, methodName, group, version);
+    }
+
+    private void init(Invocation invocation) {
+        String serviceUniqueName = invocation.getTargetServiceUniqueName();
+        String methodName = invocation.getMethodName();
+        String group = null;
+        String interfaceAndVersion;
+        String[] arr = serviceUniqueName.split("/");
+        if (arr.length == 2) {
+            group = arr[0];
+            interfaceAndVersion = arr[1];
+        } else {
+            interfaceAndVersion = arr[0];
+        }
+
+        String[] ivArr = interfaceAndVersion.split(":");
+        String interfaceName = ivArr[0];
+        String version = ivArr.length == 2 ? ivArr[1] : null;
+
+        this.interfaceName = interfaceName;
+        this.methodName = methodName;
+        this.group = group;
+        this.version = version;
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsFilter.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsFilter.java
new file mode 100644
index 0000000..4e5cc13
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/filter/MetricsFilter.java
@@ -0,0 +1,72 @@
+/*
+ * 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.metrics.filter;
+
+import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
+
+import java.util.function.Consumer;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.rpc.BaseFilter;
+import org.apache.dubbo.rpc.Filter;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
+
+@Activate(group = PROVIDER, order = -1)
+public class MetricsFilter implements Filter, BaseFilter.Listener, ScopeModelAware {
+
+    private DefaultMetricsCollector collector = null;
+
+    private ApplicationModel applicationModel;
+
+
+    @Override
+    public void setApplicationModel(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        collector = applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class);
+    }
+
+    @Override
+    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
+        if (collector == null || !collector.isCollectEnabled()) {
+            return invoker.invoke(invocation);
+        }
+        collect(invocation, MetricsCollectExecutor::beforeExecute);
+
+        return invoker.invoke(invocation);
+    }
+
+    @Override
+    public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
+        collect(invocation, collector->collector.postExecute(result));
+    }
+
+    @Override
+    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
+        collect(invocation,collector-> collector.throwExecute(t));
+    }
+
+    private void collect(Invocation invocation, Consumer<MetricsCollectExecutor> execute) {
+        MetricsCollectExecutor collectorExecutor = new MetricsCollectExecutor(collector, invocation);
+        execute.accept(collectorExecutor);
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/service/DefaultMetricsService.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/service/DefaultMetricsService.java
new file mode 100644
index 0000000..c91e5f3
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/service/DefaultMetricsService.java
@@ -0,0 +1,97 @@
+/*
+ * 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.metrics.service;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.collector.MetricsCollector;
+import org.apache.dubbo.common.metrics.model.MetricsCategory;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.common.metrics.service.MetricsEntity;
+import org.apache.dubbo.common.metrics.service.MetricsService;
+import org.apache.dubbo.metrics.collector.AggregateMetricsCollector;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Default implementation of {@link MetricsService}
+ */
+public class DefaultMetricsService implements MetricsService {
+
+    protected final List<MetricsCollector> collectors = new ArrayList<>();
+
+    private final ApplicationModel applicationModel;
+
+    public DefaultMetricsService(ApplicationModel applicationModel) {
+        this.applicationModel = applicationModel;
+        collectors.add(applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class));
+        collectors.add(applicationModel.getBeanFactory().getBean(AggregateMetricsCollector.class));
+    }
+
+    @Override
+    public Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(List<MetricsCategory> categories) {
+        return getMetricsByCategories(null, categories);
+    }
+
+    @Override
+    public Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(String serviceUniqueName, List<MetricsCategory> categories) {
+        return getMetricsByCategories(serviceUniqueName, null, null, categories);
+    }
+
+    @Override
+    public Map<MetricsCategory, List<MetricsEntity>> getMetricsByCategories(String serviceUniqueName, String methodName, Class<?>[] parameterTypes, List<MetricsCategory> categories) {
+        Map<MetricsCategory, List<MetricsEntity>> result = new HashMap<>();
+        for (MetricsCollector collector : collectors) {
+            List<MetricSample> samples = collector.collect();
+            for (MetricSample sample : samples) {
+                if (categories.contains(sample.getCategory())) {
+                    List<MetricsEntity> entities = result.computeIfAbsent(sample.getCategory(), k -> new ArrayList<>());
+                    entities.add(sampleToEntity(sample));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private MetricsEntity sampleToEntity(MetricSample sample) {
+        MetricsEntity entity = new MetricsEntity();
+
+        entity.setName(sample.getName());
+        entity.setTags(sample.getTags());
+        entity.setCategory(sample.getCategory());
+        switch (sample.getType()) {
+            case GAUGE:
+                GaugeMetricSample gaugeSample = (GaugeMetricSample) sample;
+                entity.setValue(gaugeSample.getSupplier().get());
+                break;
+            case COUNTER:
+            case LONG_TASK_TIMER:
+            case TIMER:
+            case DISTRIBUTION_SUMMARY:
+            default:
+                break;
+        }
+
+        return entity;
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounterTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounterTest.java
new file mode 100644
index 0000000..0b49925
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowCounterTest.java
@@ -0,0 +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.metrics.aggregate;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TimeWindowCounterTest {
+
+    @Test
+    public void test() throws Exception {
+        TimeWindowCounter counter = new TimeWindowCounter(12, 1);
+        counter.increment();
+        Assertions.assertEquals(counter.get(), 1);
+        counter.decrement();
+        Assertions.assertEquals(counter.get(), 0);
+        counter.increment();
+        Thread.sleep(1000);
+        Assertions.assertEquals(counter.get(), 0);
+        Assertions.assertTrue(counter.bucketLivedSeconds() < 1);
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantileTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantileTest.java
new file mode 100644
index 0000000..f3364be
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/aggregate/TimeWindowQuantileTest.java
@@ -0,0 +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.metrics.aggregate;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TimeWindowQuantileTest {
+
+    @Test
+    public void test() throws Exception {
+        TimeWindowQuantile quantile = new TimeWindowQuantile(100, 12, 1);
+        for (int i = 1; i <= 100; i++) {
+            quantile.add(i);
+        }
+
+        Assertions.assertEquals(quantile.quantile(0.01), 2);
+        Assertions.assertEquals(quantile.quantile(0.99), 100);
+        Thread.sleep(1000);
+        Assertions.assertEquals(quantile.quantile(0.99), Double.NaN);
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java
new file mode 100644
index 0000000..0b3317a
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.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.metrics.collector;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.model.MetricsKey;
+import org.apache.dubbo.common.metrics.model.sample.GaugeMetricSample;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.MetricsConfig;
+import org.apache.dubbo.config.nested.AggregationConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.*;
+
+public class AggregateMetricsCollectorTest {
+
+    private ApplicationModel applicationModel;
+    private DefaultMetricsCollector defaultCollector;
+
+    private String interfaceName;
+    private String methodName;
+    private String group;
+    private String version;
+
+    @BeforeEach
+    public void setup() {
+        ApplicationConfig config = new ApplicationConfig();
+        config.setName("MockMetrics");
+
+        applicationModel = ApplicationModel.defaultModel();
+        applicationModel.getApplicationConfigManager().setApplication(config);
+
+        defaultCollector = new DefaultMetricsCollector(applicationModel);
+        defaultCollector.setCollectEnabled(true);
+        MetricsConfig metricsConfig = new MetricsConfig();
+        AggregationConfig aggregationConfig = new AggregationConfig();
+        aggregationConfig.setEnabled(true);
+        aggregationConfig.setBucketNum(12);
+        aggregationConfig.setTimeWindowSeconds(120);
+        metricsConfig.setAggregation(aggregationConfig);
+        applicationModel.getApplicationConfigManager().setMetrics(metricsConfig);
+        applicationModel.getBeanFactory().registerBean(defaultCollector);
+
+        interfaceName = "org.apache.dubbo.MockInterface";
+        methodName = "mockMethod";
+        group = "mockGroup";
+        version = "1.0.0";
+    }
+
+    @AfterEach
+    public void teardown() {
+        applicationModel.destroy();
+    }
+
+    @Test
+    public void testRequestsMetrics() {
+        AggregateMetricsCollector collector = new AggregateMetricsCollector(applicationModel);
+        defaultCollector.increaseTotalRequests(interfaceName, methodName, group, version);
+        defaultCollector.increaseSucceedRequests(interfaceName, methodName, group, version);
+        defaultCollector.increaseFailedRequests(interfaceName, methodName, group, version);
+        defaultCollector.businessFailedRequests(interfaceName,methodName,group,version);
+
+        List<MetricSample> samples = collector.collect();
+        for (MetricSample sample : samples) {
+            Map<String, String> tags = sample.getTags();
+
+            Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), interfaceName);
+            Assertions.assertEquals(tags.get(TAG_METHOD_KEY), methodName);
+            Assertions.assertEquals(tags.get(TAG_GROUP_KEY), group);
+            Assertions.assertEquals(tags.get(TAG_VERSION_KEY), version);
+        }
+
+        samples = collector.collect();
+        Map<String, Long> sampleMap = samples.stream().collect(Collectors.toMap(MetricSample::getName, k -> {
+            Number number = ((GaugeMetricSample) k).getSupplier().get();
+            return number.longValue();
+        }));
+
+        Assertions.assertEquals(sampleMap.get("requests.total.aggregate"), 1L);
+        Assertions.assertEquals(sampleMap.get("requests.succeed.aggregate"), 1L);
+        Assertions.assertEquals(sampleMap.get("requests.failed.aggregate"), 1L);
+        Assertions.assertEquals(sampleMap.get(MetricsKey.METRIC_REQUESTS_BUSINESS_FAILED_AGG.getName()), 1L);
+
+        Assertions.assertTrue(sampleMap.containsKey("qps"));
+    }
+
+    @Test
+    public void testRTMetrics() {
+        AggregateMetricsCollector collector = new AggregateMetricsCollector(applicationModel);
+        defaultCollector.addRT(interfaceName, methodName, group, version, 10L);
+
+        List<MetricSample> samples = collector.collect();
+        for (MetricSample sample : samples) {
+            Map<String, String> tags = sample.getTags();
+
+            Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), interfaceName);
+            Assertions.assertEquals(tags.get(TAG_METHOD_KEY), methodName);
+            Assertions.assertEquals(tags.get(TAG_GROUP_KEY), group);
+            Assertions.assertEquals(tags.get(TAG_VERSION_KEY), version);
+        }
+
+        Map<String, Long> sampleMap = samples.stream().collect(Collectors.toMap(MetricSample::getName, k -> {
+            Number number = ((GaugeMetricSample) k).getSupplier().get();
+            return number.longValue();
+        }));
+
+        Assertions.assertTrue(sampleMap.containsKey("rt.p99"));
+        Assertions.assertTrue(sampleMap.containsKey("rt.p95"));
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java
new file mode 100644
index 0000000..dbe6806
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.metrics.filter;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_METHOD_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.TAG_VERSION_KEY;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.dubbo.common.metrics.collector.DefaultMetricsCollector;
+import org.apache.dubbo.common.metrics.model.MetricsKey;
+import org.apache.dubbo.common.metrics.model.sample.MetricSample;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.rpc.AppResponse;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class MetricsFilterTest {
+
+    private ApplicationModel applicationModel;
+    private MetricsFilter filter;
+    private DefaultMetricsCollector collector;
+    private RpcInvocation invocation;
+    private final Invoker<?> invoker = mock(Invoker.class);
+
+    private static final String INTERFACE_NAME = "org.apache.dubbo.MockInterface";
+    private static final String METHOD_NAME = "mockMethod";
+    private static final String GROUP = "mockGroup";
+    private static final String VERSION = "1.0.0";
+
+    @BeforeEach
+    public void setup() {
+        ApplicationConfig config = new ApplicationConfig();
+        config.setName("MockMetrics");
+
+        applicationModel = ApplicationModel.defaultModel();
+        applicationModel.getApplicationConfigManager().setApplication(config);
+
+        invocation = new RpcInvocation();
+        filter = new MetricsFilter();
+
+        collector = applicationModel.getBeanFactory().getOrRegisterBean(DefaultMetricsCollector.class);
+        filter.setApplicationModel(applicationModel);
+    }
+
+    @AfterEach
+    public void teardown() {
+        applicationModel.destroy();
+    }
+
+    @Test
+    public void testCollectDisabled() {
+        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
+
+        filter.invoke(invoker, invocation);
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+        Assertions.assertTrue(metricsMap.isEmpty());
+    }
+
+    @Test
+    public void testFailedRequests() {
+        collector.setCollectEnabled(true);
+        given(invoker.invoke(invocation)).willThrow(new RpcException("failed"));
+        initParam();
+
+        try {
+            filter.invoke(invoker, invocation);
+        } catch (Exception e) {
+            Assertions.assertTrue(e instanceof RpcException);
+            filter.onError(e, invoker, invocation);
+        }
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+        Assertions.assertTrue(metricsMap.containsKey("requests.failed"));
+        Assertions.assertFalse(metricsMap.containsKey("requests.succeed"));
+
+        MetricSample sample = metricsMap.get("requests.failed");
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertEquals(tags.get(TAG_GROUP_KEY), GROUP);
+        Assertions.assertEquals(tags.get(TAG_VERSION_KEY), VERSION);
+    }
+
+
+    @Test
+    public void testBusinessFailedRequests() {
+        collector.setCollectEnabled(true);
+
+        given(invoker.invoke(invocation)).willThrow(new RpcException(RpcException.BIZ_EXCEPTION));
+        initParam();
+
+        try {
+            filter.invoke(invoker, invocation);
+        } catch (Exception e) {
+            Assertions.assertTrue(e instanceof RpcException);
+            filter.onError(e, invoker, invocation);
+        }
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+        Assertions.assertTrue(metricsMap.containsKey(MetricsKey.METRIC_REQUEST_BUSINESS_FAILED.getName()));
+        Assertions.assertFalse(metricsMap.containsKey("requests.succeed"));
+
+        MetricSample sample = metricsMap.get(MetricsKey.METRIC_REQUEST_BUSINESS_FAILED.getName());
+
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertEquals(tags.get(TAG_GROUP_KEY), GROUP);
+        Assertions.assertEquals(tags.get(TAG_VERSION_KEY), VERSION);
+    }
+
+    @Test
+    public void testSucceedRequests() {
+        collector.setCollectEnabled(true);
+        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
+        initParam();
+
+        Result result = filter.invoke(invoker, invocation);
+
+        filter.onResponse(result, invoker, invocation);
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+        Assertions.assertFalse(metricsMap.containsKey("requests.failed"));
+        Assertions.assertTrue(metricsMap.containsKey("requests.succeed"));
+
+        MetricSample sample = metricsMap.get("requests.succeed");
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertEquals(tags.get(TAG_GROUP_KEY), GROUP);
+        Assertions.assertEquals(tags.get(TAG_VERSION_KEY), VERSION);
+    }
+
+    @Test
+    public void testMissingGroup() {
+        collector.setCollectEnabled(true);
+        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
+        invocation.setTargetServiceUniqueName(INTERFACE_NAME + ":" + VERSION);
+        invocation.setMethodName(METHOD_NAME);
+        invocation.setParameterTypes(new Class[]{String.class});
+
+        Result result = filter.invoke(invoker, invocation);
+
+        filter.onResponse(result, invoker, invocation);
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+
+        MetricSample sample = metricsMap.get("requests.succeed");
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertNull(tags.get(TAG_GROUP_KEY));
+        Assertions.assertEquals(tags.get(TAG_VERSION_KEY), VERSION);
+    }
+
+    @Test
+    public void testMissingVersion() {
+        collector.setCollectEnabled(true);
+        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
+        invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME);
+        invocation.setMethodName(METHOD_NAME);
+        invocation.setParameterTypes(new Class[]{String.class});
+
+        Result result = filter.invoke(invoker, invocation);
+
+        filter.onResponse(result, invoker, invocation);
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+
+        MetricSample sample = metricsMap.get("requests.succeed");
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertEquals(tags.get(TAG_GROUP_KEY), GROUP);
+        Assertions.assertNull(tags.get(TAG_VERSION_KEY));
+    }
+
+    @Test
+    public void testMissingGroupAndVersion() {
+        collector.setCollectEnabled(true);
+        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
+        invocation.setTargetServiceUniqueName(INTERFACE_NAME);
+        invocation.setMethodName(METHOD_NAME);
+        invocation.setParameterTypes(new Class[]{String.class});
+
+        Result result = filter.invoke(invoker, invocation);
+
+        filter.onResponse(result, invoker, invocation);
+
+        Map<String, MetricSample> metricsMap = getMetricsMap();
+
+        MetricSample sample = metricsMap.get("requests.succeed");
+        Map<String, String> tags = sample.getTags();
+
+        Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);
+        Assertions.assertEquals(tags.get(TAG_METHOD_KEY), METHOD_NAME);
+        Assertions.assertNull(tags.get(TAG_GROUP_KEY));
+        Assertions.assertNull(tags.get(TAG_VERSION_KEY));
+    }
+
+    private void initParam() {
+        invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME + ":" + VERSION);
+        invocation.setMethodName(METHOD_NAME);
+        invocation.setParameterTypes(new Class[]{String.class});
+    }
+
+    private Map<String, MetricSample> getMetricsMap() {
+        List<MetricSample> samples = collector.collect();
+        return samples.stream().collect(Collectors.toMap(MetricSample::getName, Function.identity()));
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/pom.xml b/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
index 0020915..18f90e1 100644
--- a/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
+++ b/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
@@ -14,7 +14,8 @@
   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">
+<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>
@@ -35,5 +36,23 @@
             <artifactId>dubbo-common</artifactId>
             <version>${project.parent.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metrics-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>simpleclient_pushgateway</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java
new file mode 100644
index 0000000..89684e7
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java
@@ -0,0 +1,162 @@
+/*
+ * 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.metrics.prometheus;
+
+import com.sun.net.httpserver.HttpServer;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
+import io.prometheus.client.exporter.PushGateway;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metrics.AbstractMetricsReporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_ENABLED_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_METRICS_PORT_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_METRICS_PORT;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_METRICS_PATH_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_METRICS_PATH;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_ENABLED_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_JOB_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_JOB_NAME;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_PUSH_INTERVAL;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_USERNAME_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY;
+
+/**
+ * Metrics reporter for prometheus.
+ */
+public class PrometheusMetricsReporter extends AbstractMetricsReporter {
+
+    private final Logger logger = LoggerFactory.getLogger(PrometheusMetricsReporter.class);
+
+    private final PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
+    private ScheduledExecutorService pushJobExecutor = null;
+    private HttpServer prometheusExporterHttpServer = null;
+    private Thread httpServerThread = null;
+
+    public PrometheusMetricsReporter(URL url, ApplicationModel applicationModel) {
+        super(url, applicationModel);
+    }
+
+    @Override
+    public void doInit() {
+        addMeterRegistry(prometheusRegistry);
+        exportHttpServer();
+        schedulePushJob();
+    }
+
+    private void exportHttpServer() {
+        boolean exporterEnabled = url.getParameter(PROMETHEUS_EXPORTER_ENABLED_KEY, false);
+        if (exporterEnabled) {
+            int port = url.getParameter(PROMETHEUS_EXPORTER_METRICS_PORT_KEY, PROMETHEUS_DEFAULT_METRICS_PORT);
+            String path = url.getParameter(PROMETHEUS_EXPORTER_METRICS_PATH_KEY, PROMETHEUS_DEFAULT_METRICS_PATH);
+            if (!path.startsWith("/")) {
+                path = "/" + path;
+            }
+
+            try {
+                prometheusExporterHttpServer = HttpServer.create(new InetSocketAddress(port), 0);
+                prometheusExporterHttpServer.createContext(path, httpExchange -> {
+                    String response = prometheusRegistry.scrape();
+                    httpExchange.sendResponseHeaders(200, response.getBytes().length);
+                    try (OutputStream os = httpExchange.getResponseBody()) {
+                        os.write(response.getBytes());
+                    }
+                });
+
+                httpServerThread = new Thread(prometheusExporterHttpServer::start);
+                httpServerThread.start();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private void schedulePushJob() {
+        boolean pushEnabled = url.getParameter(PROMETHEUS_PUSHGATEWAY_ENABLED_KEY, false);
+        if (pushEnabled) {
+            String baseUrl = url.getParameter(PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY);
+            String job = url.getParameter(PROMETHEUS_PUSHGATEWAY_JOB_KEY, PROMETHEUS_DEFAULT_JOB_NAME);
+            int pushInterval = url.getParameter(PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY, PROMETHEUS_DEFAULT_PUSH_INTERVAL);
+            String username = url.getParameter(PROMETHEUS_PUSHGATEWAY_USERNAME_KEY);
+            String password = url.getParameter(PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY);
+
+            NamedThreadFactory threadFactory = new NamedThreadFactory("prometheus-push-job", true);
+            pushJobExecutor = Executors.newScheduledThreadPool(1, threadFactory);
+            PushGateway pushGateway = new PushGateway(baseUrl);
+            if (!StringUtils.isBlank(username)) {
+                pushGateway.setConnectionFactory(new BasicAuthHttpConnectionFactory(username, password));
+            }
+
+            pushJobExecutor.scheduleWithFixedDelay(() -> push(pushGateway, job), pushInterval, pushInterval, TimeUnit.SECONDS);
+        }
+    }
+
+    protected void push(PushGateway pushGateway, String job) {
+        try {
+            pushGateway.pushAdd(prometheusRegistry.getPrometheusRegistry(), job);
+        } catch (IOException e) {
+            logger.error("Error occurred when pushing metrics to prometheus: ", e);
+        }
+    }
+
+    @Override
+    public void doDestroy() {
+        if (prometheusExporterHttpServer != null) {
+            prometheusExporterHttpServer.stop(1);
+        }
+
+        if (httpServerThread != null) {
+            httpServerThread.interrupt();
+        }
+
+        if (pushJobExecutor != null) {
+            pushJobExecutor.shutdownNow();
+        }
+    }
+
+    /**
+     * ut only
+     */
+    @Deprecated
+    public ScheduledExecutorService getPushJobExecutor() {
+        return pushJobExecutor;
+    }
+
+    /**
+     * ut only
+     */
+    @Deprecated
+    public PrometheusMeterRegistry getPrometheusRegistry() {
+        return prometheusRegistry;
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java
new file mode 100644
index 0000000..f28aa74
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.metrics.prometheus;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.metrics.AbstractMetricsReporterFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+/**
+ * MetricsReporterFactory to create PrometheusMetricsReporter.
+ */
+public class PrometheusMetricsReporterFactory extends AbstractMetricsReporterFactory {
+
+    public PrometheusMetricsReporterFactory(ApplicationModel applicationModel) {
+        super(applicationModel);
+    }
+
+    @Override
+    public MetricsReporter createMetricsReporter(URL url) {
+        return new PrometheusMetricsReporter(url, getApplicationModel());
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java
similarity index 60%
copy from dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java
copy to dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java
index 92bcfb5..f0f87a8 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java
@@ -14,25 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+
+package org.apache.dubbo.metrics.prometheus;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.url.component.ServiceConfigURL;
-import org.apache.dubbo.remoting.Constants;
-
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-public class PortUnificationServerTest {
+public class PrometheusMetricsReporterFactoryTest {
 
     @Test
-    public void testBind() {
-        URL url = new ServiceConfigURL(CommonConstants.TRIPLE, "localhost", 8898,
-                new String[]{Constants.BIND_PORT_KEY, String.valueOf(8898)});
+    public void test() {
+        ApplicationModel applicationModel = ApplicationModel.defaultModel();
+        PrometheusMetricsReporterFactory factory = new PrometheusMetricsReporterFactory(applicationModel);
+        MetricsReporter reporter = factory.createMetricsReporter(URL.valueOf("prometheus://localhost:9090/"));
 
-        final PortUnificationServer server = new PortUnificationServer(url);
-        server.bind();
-        Assertions.assertTrue(server.isBound());
+        Assertions.assertTrue(reporter instanceof PrometheusMetricsReporter);
     }
 }
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java
new file mode 100644
index 0000000..a429ce3
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.metrics.prometheus;
+
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.MetricsConfig;
+import org.apache.dubbo.config.nested.PrometheusConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.PROTOCOL_PROMETHEUS;
+
+public class PrometheusMetricsReporterTest {
+
+    private MetricsConfig metricsConfig;
+    private ApplicationModel applicationModel;
+
+    @BeforeEach
+    public void setup() {
+        metricsConfig = new MetricsConfig();
+        applicationModel = ApplicationModel.defaultModel();
+        metricsConfig.setProtocol(PROTOCOL_PROMETHEUS);
+    }
+
+    @AfterEach
+    public void teardown() {
+        applicationModel.destroy();
+    }
+
+    @Test
+    public void testJvmMetrics() {
+        metricsConfig.setEnableJvmMetrics(true);
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        PrometheusMeterRegistry prometheusRegistry = reporter.getPrometheusRegistry();
+        Double d1 = prometheusRegistry.getPrometheusRegistry().getSampleValue("none_exist_metric");
+        Double d2 = prometheusRegistry.getPrometheusRegistry().getSampleValue("jvm_gc_memory_promoted_bytes_total");
+
+        Assertions.assertNull(d1);
+        Assertions.assertNotNull(d2);
+    }
+
+    @Test
+    public void testExporter() {
+        int port = NetUtils.getAvailablePort();
+
+        PrometheusConfig prometheusConfig = new PrometheusConfig();
+        PrometheusConfig.Exporter exporter = new PrometheusConfig.Exporter();
+        exporter.setMetricsPort(port);
+        exporter.setMetricsPath("/metrics");
+        exporter.setEnabled(true);
+        prometheusConfig.setExporter(exporter);
+        metricsConfig.setPrometheus(prometheusConfig);
+        metricsConfig.setEnableJvmMetrics(true);
+
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            HttpGet request = new HttpGet("http://localhost:" + port + "/metrics");
+            CloseableHttpResponse response = client.execute(request);
+            InputStream inputStream = response.getEntity().getContent();
+            String text = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
+            Assertions.assertTrue(text.contains("jvm_gc_memory_promoted_bytes_total"));
+        } catch (Exception e) {
+            Assertions.fail(e);
+        } finally {
+            reporter.destroy();
+        }
+    }
+
+    @Test
+    public void testPushgateway() {
+        PrometheusConfig prometheusConfig = new PrometheusConfig();
+        PrometheusConfig.Pushgateway pushgateway = new PrometheusConfig.Pushgateway();
+        pushgateway.setJob("mock");
+        pushgateway.setBaseUrl("localhost:9091");
+        pushgateway.setEnabled(true);
+        pushgateway.setPushInterval(1);
+        prometheusConfig.setPushgateway(pushgateway);
+        metricsConfig.setPrometheus(prometheusConfig);
+
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        ScheduledExecutorService executor = reporter.getPushJobExecutor();
+        Assertions.assertTrue(executor != null && !executor.isTerminated() && !executor.isShutdown());
+
+        reporter.destroy();
+        Assertions.assertTrue(executor.isTerminated() || executor.isShutdown());
+    }
+}
diff --git a/dubbo-metrics/pom.xml b/dubbo-metrics/pom.xml
index 8c4291a..2b101fc 100644
--- a/dubbo-metrics/pom.xml
+++ b/dubbo-metrics/pom.xml
@@ -14,7 +14,8 @@
   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">
+<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>
     <modules>
         <module>dubbo-metrics-api</module>
diff --git a/dubbo-monitor/dubbo-monitor-api/pom.xml b/dubbo-monitor/dubbo-monitor-api/pom.xml
index 000f1d8..f7fc8dd 100644
--- a/dubbo-monitor/dubbo-monitor-api/pom.xml
+++ b/dubbo-monitor/dubbo-monitor-api/pom.xml
@@ -54,5 +54,12 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-monitor/dubbo-monitor-default/pom.xml b/dubbo-monitor/dubbo-monitor-default/pom.xml
index 4489e84..a230f36 100644
--- a/dubbo-monitor/dubbo-monitor-default/pom.xml
+++ b/dubbo-monitor/dubbo-monitor-default/pom.xml
@@ -66,5 +66,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-plugin/dubbo-qos/pom.xml b/dubbo-plugin/dubbo-qos/pom.xml
index 947fca6..cdc6797 100644
--- a/dubbo-plugin/dubbo-qos/pom.xml
+++ b/dubbo-plugin/dubbo-qos/pom.xml
@@ -64,6 +64,12 @@
             <artifactId>dubbo-serialization-hessian2</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-all</artifactId>
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/decoder/TelnetCommandDecoder.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/decoder/TelnetCommandDecoder.java
index 52e58b5..3c559a5 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/decoder/TelnetCommandDecoder.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/decoder/TelnetCommandDecoder.java
@@ -25,11 +25,15 @@
     public static final CommandContext decode(String str) {
         CommandContext commandContext = null;
         if (!StringUtils.isBlank(str)) {
+            str = str.trim();
             String[] array = str.split("(?<![\\\\]) ");
             if (array.length > 0) {
-                String name = array[0];
                 String[] targetArgs = new String[array.length - 1];
                 System.arraycopy(array, 1, targetArgs, 0, array.length - 1);
+                String name = array[0].trim();
+                if (name.equals("invoke") && array.length > 2) {
+                    targetArgs = reBuildInvokeCmdArgs(str);
+                }
                 commandContext = CommandContextFactory.newInstance( name, targetArgs,false);
                 commandContext.setOriginRequest(str);
             }
@@ -38,4 +42,8 @@
         return commandContext;
     }
 
+    private static String[] reBuildInvokeCmdArgs(String cmd) {
+        return new String[] {cmd.substring(cmd.indexOf(" ") + 1).trim()};
+    }
+
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java
index 306c35c..66a33d0 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java
@@ -23,7 +23,6 @@
 import org.apache.dubbo.rpc.model.ProviderModel;
 
 import java.util.Collection;
-import java.util.List;
 
 @Activate
 public class ProviderReadinessProbe implements ReadinessProbe {
@@ -46,17 +45,20 @@
             return true;
         }
 
-        boolean hasService = false;
+        boolean hasService = false, anyOnline = false;
         for (ProviderModel providerModel : providerModelList) {
-            List<ProviderModel.RegisterStatedURL> statedUrls = providerModel.getStatedUrl();
-            for (ProviderModel.RegisterStatedURL statedUrl : statedUrls) {
-                if (statedUrl.isRegistered()) {
-                    hasService = true;
-                    break;
-                }
+            if (providerModel.getModuleModel().isInternal()) {
+                continue;
             }
+            hasService = true;
+            anyOnline = anyOnline ||
+                providerModel.getStatedUrl().isEmpty() ||
+                providerModel.getStatedUrl().stream().anyMatch(ProviderModel.RegisterStatedURL::isRegistered);
         }
 
-        return hasService;
+        // no service => check pass
+        // has service and any online => check pass
+        // has service and none online => check fail
+        return !(hasService && !anyOnline);
     }
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java
index f0e420c..7fc3693 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java
@@ -21,7 +21,9 @@
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.qos.common.QosConstants;
+import org.apache.dubbo.qos.pu.QosWireProtocol;
 import org.apache.dubbo.qos.server.Server;
+import org.apache.dubbo.remoting.api.WireProtocol;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
@@ -96,6 +98,10 @@
             }
 
             boolean qosEnable = url.getParameter(QOS_ENABLE, true);
+            WireProtocol qosWireProtocol = frameworkModel.getExtensionLoader(WireProtocol.class).getExtension("qos");
+            if(qosWireProtocol != null) {
+                ((QosWireProtocol) qosWireProtocol).setQosEnable(qosEnable);
+            }
             if (!qosEnable) {
                 logger.info("qos won't be started because it is disabled. " +
                     "Please check dubbo.application.qos.enable is configured either in system property, " +
@@ -107,6 +113,11 @@
             int port = url.getParameter(QOS_PORT, QosConstants.DEFAULT_PORT);
             boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP, "false"));
             Server server = frameworkModel.getBeanFactory().getBean(Server.class);
+
+            if (server.isStarted()) {
+                return;
+            }
+
             server.setHost(host);
             server.setPort(port);
             server.setAcceptForeignIp(acceptForeignIp);
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosDetector.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosDetector.java
new file mode 100644
index 0000000..6a91405
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosDetector.java
@@ -0,0 +1,56 @@
+/*
+ * 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.qos.pu;
+
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+
+public class QosDetector implements ProtocolDetector {
+
+    private final QosHTTP1Detector qosHTTP1Detector = new QosHTTP1Detector();
+    private final TelnetDetector telnetDetector;
+    private boolean QosEnableFlag = true;
+
+    public void setQosEnableFlag(boolean qosEnableFlag) {
+        QosEnableFlag = qosEnableFlag;
+    }
+
+    public QosDetector(FrameworkModel frameworkModel) {
+        this.telnetDetector = new TelnetDetector(frameworkModel);
+    }
+
+    @Override
+    public Result detect(ChannelBuffer in) {
+        if(!QosEnableFlag) {
+            return Result.UNRECOGNIZED;
+        }
+        Result h1Res = qosHTTP1Detector.detect(in);
+        if(h1Res.equals(Result.RECOGNIZED)) {
+            return h1Res;
+        }
+        Result telRes = telnetDetector.detect(in);
+        if(telRes.equals(Result.RECOGNIZED)) {
+            return telRes;
+        }
+        if(h1Res.equals(Result.NEED_MORE_DATA) || telRes.equals(Result.NEED_MORE_DATA)) {
+            return Result.NEED_MORE_DATA;
+        }
+        return Result.UNRECOGNIZED;
+    }
+
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosHTTP1Detector.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosHTTP1Detector.java
new file mode 100644
index 0000000..9a62b84
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosHTTP1Detector.java
@@ -0,0 +1,39 @@
+/*
+ * 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.qos.pu;
+
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+
+public class QosHTTP1Detector implements ProtocolDetector {
+    private static boolean isHttp(int magic) {
+        return magic == 'G' || magic == 'P';
+    }
+
+    @Override
+    public Result detect(ChannelBuffer in) {
+        if (in.readableBytes() < 2) {
+            return Result.NEED_MORE_DATA;
+        }
+        final int magic = in.getByte(in.readerIndex());
+        // h2 starts with "PR"
+        if (isHttp(magic) && in.getByte(in.readerIndex()+1) != 'R' ){
+            return Result.RECOGNIZED;
+        }
+        return Result.UNRECOGNIZED;
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosWireProtocol.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosWireProtocol.java
new file mode 100644
index 0000000..71e6633
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/QosWireProtocol.java
@@ -0,0 +1,63 @@
+/*
+ * 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.qos.pu;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.qos.server.DubboLogo;
+import org.apache.dubbo.qos.server.handler.QosProcessHandler;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.api.AbstractWireProtocol;
+import org.apache.dubbo.remoting.api.pu.ChannelHandlerPretender;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ScopeModelAware;
+
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.ssl.SslContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Activate
+public class QosWireProtocol extends AbstractWireProtocol implements ScopeModelAware {
+
+    public QosWireProtocol(FrameworkModel frameworkModel) {
+        super(new QosDetector(frameworkModel));
+    }
+
+    public void setQosEnable(boolean flag) {
+        ((QosDetector)this.detector()).setQosEnableFlag(flag);
+    }
+
+    @Override
+    public void configServerProtocolHandler(URL url, ChannelOperator operator) {
+        // add qosProcess handler
+        QosProcessHandler handler = new QosProcessHandler(url.getOrDefaultFrameworkModel(),
+            DubboLogo.DUBBO, false);
+        List<ChannelHandler> handlers = new ArrayList<>();
+        handlers.add(new ChannelHandlerPretender(handler));
+        operator.configChannelHandler(handlers);
+    }
+
+
+    @Override
+    public void configClientPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext) {
+
+    }
+
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/TelnetDetector.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/TelnetDetector.java
new file mode 100644
index 0000000..8419993
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/pu/TelnetDetector.java
@@ -0,0 +1,95 @@
+/*
+ * 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.qos.pu;
+
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.decoder.TelnetCommandDecoder;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffers;
+import org.apache.dubbo.remoting.buffer.HeapChannelBuffer;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+
+import io.netty.util.CharsetUtil;
+
+import static java.lang.Math.min;
+
+
+public class TelnetDetector implements ProtocolDetector {
+
+    private FrameworkModel frameworkModel;
+    private final int MaxSize = 2048;
+    private final ChannelBuffer AytPreface = new HeapChannelBuffer(new byte[]{(byte) 0xff, (byte) 0xf6});
+
+    public TelnetDetector(FrameworkModel frameworkModel) {
+        this.frameworkModel = frameworkModel;
+    }
+
+    @Override
+    public Result detect(ChannelBuffer in) {
+        if (in.readableBytes() >= MaxSize) {
+            return Result.UNRECOGNIZED;
+        }
+        Result resCommand = commandDetect(in);
+        if (resCommand.equals(Result.RECOGNIZED)){
+            return resCommand;
+        }
+        Result resAyt = telnetAytDetect(in);
+        if (resAyt.equals(Result.RECOGNIZED)) {
+            return resAyt;
+        }
+        if (resAyt.equals(Result.UNRECOGNIZED) && resCommand.equals(Result.UNRECOGNIZED)) {
+            return Result.UNRECOGNIZED;
+        }
+        return Result.NEED_MORE_DATA;
+    }
+
+    private Result commandDetect(ChannelBuffer in) {
+        // detect if remote channel send a qos command to server
+        ChannelBuffer back = in.copy();
+        byte[] backBytes = new byte[back.readableBytes()];
+        back.getBytes(back.readerIndex(), backBytes);
+
+        String s = new String(backBytes, CharsetUtil.UTF_8);
+        // trim /r/n to let parser work for input
+        s = s.trim();
+        CommandContext commandContext = TelnetCommandDecoder.decode(s);
+        if(frameworkModel.getExtensionLoader(BaseCommand.class).hasExtension(commandContext.getCommandName())){
+            return Result.RECOGNIZED;
+        }
+        return Result.UNRECOGNIZED;
+    }
+
+    private Result telnetAytDetect(ChannelBuffer in) {
+        // detect if remote channel send a telnet ayt command to server
+        int prefaceLen = AytPreface.readableBytes();
+        int bytesRead = min(in.readableBytes(), prefaceLen);
+        if(bytesRead == 0 || !ChannelBuffers.prefixEquals(in, AytPreface, bytesRead)) {
+            return Result.UNRECOGNIZED;
+        }
+        if(bytesRead == prefaceLen) {
+            // we need to consume preface because it's not a qos command
+            // consume and remember to mark, pu server handler reset reader index
+            in.readBytes(AytPreface.readableBytes());
+            in.markReaderIndex();
+            return Result.RECOGNIZED;
+        }
+        return Result.NEED_MORE_DATA;
+    }
+
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
new file mode 100644
index 0000000..cd4d62d
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
@@ -0,0 +1 @@
+qos=org.apache.dubbo.qos.pu.QosWireProtocol
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/protocol/QosProtocolWrapperTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/protocol/QosProtocolWrapperTest.java
index 1a45665..05f1edd 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/protocol/QosProtocolWrapperTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/protocol/QosProtocolWrapperTest.java
@@ -17,12 +17,12 @@
 package org.apache.dubbo.qos.protocol;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.server.Server;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.model.FrameworkModel;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -44,6 +44,12 @@
     private Invoker invoker = mock(Invoker.class);
     private Protocol protocol = mock(Protocol.class);
     private QosProtocolWrapper wrapper;
+
+    private URL triUrl = Mockito.mock(URL.class);
+    private Invoker triInvoker = mock(Invoker.class);
+    private Protocol triProtocol = mock(Protocol.class);
+    private QosProtocolWrapper triWrapper;
+
     private Server server;
 
     @BeforeEach
@@ -51,12 +57,25 @@
         when(url.getParameter(QOS_ENABLE, true)).thenReturn(true);
         when(url.getParameter(QOS_HOST)).thenReturn("localhost");
         when(url.getParameter(QOS_PORT, 22222)).thenReturn(12345);
-        when(url.getParameter(ACCEPT_FOREIGN_IP, true)).thenReturn(false);
-        when(invoker.getUrl()).thenReturn(url);
+        when(url.getParameter(ACCEPT_FOREIGN_IP, "false")).thenReturn("false");
         when(url.getProtocol()).thenReturn(REGISTRY_PROTOCOL);
-        server = FrameworkModel.defaultModel().getBeanFactory().getBean(Server.class);
+        when(invoker.getUrl()).thenReturn(url);
+
         wrapper = new QosProtocolWrapper(protocol);
         wrapper.setFrameworkModel(FrameworkModel.defaultModel());
+
+        // url2 use tri protocol and qos.accept.foreign.ip=true
+        when(triUrl.getParameter(QOS_ENABLE, true)).thenReturn(true);
+        when(triUrl.getParameter(QOS_HOST)).thenReturn("localhost");
+        when(triUrl.getParameter(QOS_PORT, 22222)).thenReturn(12345);
+        when(triUrl.getParameter(ACCEPT_FOREIGN_IP, "false")).thenReturn("true");
+        when(triUrl.getProtocol()).thenReturn(CommonConstants.TRIPLE);
+        when(triInvoker.getUrl()).thenReturn(triUrl);
+
+        triWrapper = new QosProtocolWrapper(triProtocol);
+        triWrapper.setFrameworkModel(FrameworkModel.defaultModel());
+
+        server = FrameworkModel.defaultModel().getBeanFactory().getBean(Server.class);
     }
 
     @AfterEach
@@ -86,4 +105,23 @@
         assertThat(server.isAcceptForeignIp(), is(false));
         verify(protocol).refer(BaseCommand.class, url);
     }
+
+    @Test
+    public void testMultiProtocol() throws Exception {
+        //tri protocol start first, acceptForeignIp = true
+        triWrapper.export(triInvoker);
+        assertThat(server.isStarted(), is(true));
+        assertThat(server.getHost(), is("localhost"));
+        assertThat(server.getPort(), is(12345));
+        assertThat(server.isAcceptForeignIp(), is(true));
+        verify(triProtocol).export(triInvoker);
+
+        //next registry protocol server still acceptForeignIp=true even though wrapper invoker url set false
+        wrapper.export(invoker);
+        assertThat(server.isStarted(), is(true));
+        assertThat(server.getHost(), is("localhost"));
+        assertThat(server.getPort(), is(12345));
+        assertThat(server.isAcceptForeignIp(), is(true));
+        verify(protocol).export(invoker);
+    }
 }
diff --git a/dubbo-plugin/dubbo-reactive/pom.xml b/dubbo-plugin/dubbo-reactive/pom.xml
new file mode 100644
index 0000000..9ac35a6
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/pom.xml
@@ -0,0 +1,51 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-plugin</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-reactive</artifactId>
+    <packaging>jar</packaging>
+
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-triple</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.reactivestreams</groupId>
+            <artifactId>reactive-streams</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorPublisher.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorPublisher.java
new file mode 100644
index 0000000..0328c59
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorPublisher.java
@@ -0,0 +1,169 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.CancelableStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * The middle layer between {@link org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver} and Reactive API. <p>
+ * 1. passing the data received by CallStreamObserver to Reactive consumer <br>
+ * 2. passing the request of Reactive API to CallStreamObserver
+ */
+public abstract class AbstractTripleReactorPublisher<T> extends CancelableStreamObserver<T> implements Publisher<T>, Subscription {
+
+    private boolean canRequest;
+
+    private long requested;
+
+    // weather publisher has been subscribed
+    private final AtomicBoolean SUBSCRIBED = new AtomicBoolean();
+
+    private volatile Subscriber<? super T> downstream;
+
+    protected volatile CallStreamObserver<?> subscription;
+
+    private final AtomicBoolean HAS_SUBSCRIPTION = new AtomicBoolean();
+
+    // cancel status
+    private volatile boolean isCancelled;
+
+    // complete status
+    private volatile boolean isDone;
+
+    // to help bind TripleSubscriber
+    private volatile Consumer<CallStreamObserver<?>> onSubscribe;
+
+    private volatile Runnable shutdownHook;
+
+    private final AtomicBoolean CALLED_SHUT_DOWN_HOOK = new AtomicBoolean();
+
+    public AbstractTripleReactorPublisher() {
+    }
+
+    public AbstractTripleReactorPublisher(Consumer<CallStreamObserver<?>> onSubscribe, Runnable shutdownHook) {
+        this.onSubscribe = onSubscribe;
+        this.shutdownHook = shutdownHook;
+    }
+
+    protected void onSubscribe(final CallStreamObserver<?> subscription) {
+        if (subscription != null && this.subscription == null && HAS_SUBSCRIPTION.compareAndSet(false, true)) {
+            this.subscription = subscription;
+            subscription.disableAutoFlowControl();
+            if (onSubscribe != null) {
+                onSubscribe.accept(subscription);
+            }
+            return;
+        }
+
+        throw new IllegalStateException(getClass().getSimpleName() + " supports only a single subscription");
+    }
+
+    @Override
+    public void onNext(T data) {
+        if (isDone || isCancelled) {
+            return;
+        }
+        downstream.onNext(data);
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        if (isDone || isCancelled) {
+            return;
+        }
+        isDone = true;
+        downstream.onError(throwable);
+        doPostShutdown();
+    }
+
+    @Override
+    public void onCompleted() {
+        if (isDone || isCancelled) {
+            return;
+        }
+        isDone = true;
+        downstream.onComplete();
+        doPostShutdown();
+    }
+
+    private void doPostShutdown() {
+        Runnable r = shutdownHook;
+        // CAS to confirm shutdownHook will be run only once.
+        if (r != null && CALLED_SHUT_DOWN_HOOK.compareAndSet(false, true)) {
+            shutdownHook = null;
+            r.run();
+        }
+    }
+
+    @Override
+    public void subscribe(Subscriber<? super T> subscriber) {
+        if (subscriber == null) {
+            throw new NullPointerException();
+        }
+
+        if (SUBSCRIBED.compareAndSet(false, true)) {
+            subscriber.onSubscribe(this);
+            this.downstream = subscriber;
+            if (isCancelled) {
+                this.downstream = null;
+            }
+        }
+    }
+
+    @Override
+    public void request(long l) {
+        synchronized (this) {
+            if (SUBSCRIBED.get() && canRequest) {
+                subscription.request(l >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) l);
+            } else {
+                requested += l;
+            }
+        }
+    }
+
+    @Override
+    public void startRequest() {
+        synchronized (this) {
+            if (!canRequest) {
+                canRequest = true;
+                long count = requested;
+                subscription.request(count >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) count);
+            }
+        }
+    }
+
+    @Override
+    public void cancel() {
+        if (isCancelled) {
+            return;
+        }
+        isCancelled = true;
+        doPostShutdown();
+    }
+
+    public boolean isCancelled() {
+        return isCancelled;
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorSubscriber.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorSubscriber.java
new file mode 100644
index 0000000..96fa648
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/AbstractTripleReactorSubscriber.java
@@ -0,0 +1,106 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import reactor.core.CoreSubscriber;
+import reactor.util.annotation.NonNull;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * The middle layer between {@link org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver} and Reactive API. <br>
+ * Passing the data from Reactive producer to CallStreamObserver.
+ */
+public abstract class AbstractTripleReactorSubscriber<T> implements Subscriber<T>, CoreSubscriber<T> {
+
+    private volatile boolean isCancelled;
+
+    protected volatile CallStreamObserver<T> downstream;
+
+    private final AtomicBoolean SUBSCRIBED = new AtomicBoolean();
+
+    private volatile Subscription subscription;
+
+    private final AtomicBoolean HAS_SUBSCRIBED = new AtomicBoolean();
+
+    // complete status
+    private volatile boolean isDone;
+
+    /**
+     * Binding the downstream, and call subscription#request(1).
+     *
+     * @param downstream downstream
+     */
+    public void subscribe(final CallStreamObserver<T> downstream) {
+        if (downstream == null) {
+            throw new NullPointerException();
+        }
+        if (this.downstream == null && SUBSCRIBED.compareAndSet(false, true)) {
+            this.downstream = downstream;
+            subscription.request(1);
+        }
+    }
+
+    @Override
+    public void onSubscribe(@NonNull final Subscription subscription) {
+        if (this.subscription == null && HAS_SUBSCRIBED.compareAndSet(false, true)) {
+            this.subscription = subscription;
+            return;
+        }
+        // onSubscribe cannot be called repeatedly
+        subscription.cancel();
+    }
+
+    @Override
+    public void onNext(T t) {
+        if (!isDone && !isCanceled()) {
+            downstream.onNext(t);
+            subscription.request(1);
+        }
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        if (!isCanceled()) {
+            isDone = true;
+            downstream.onError(throwable);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (!isCanceled()) {
+            isDone = true;
+            downstream.onCompleted();
+        }
+    }
+
+    public void cancel() {
+        if (!isCancelled && subscription != null) {
+            isCancelled = true;
+            subscription.cancel();
+        }
+    }
+
+    public boolean isCanceled() {
+        return isCancelled;
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorPublisher.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorPublisher.java
new file mode 100644
index 0000000..1233dd8
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorPublisher.java
@@ -0,0 +1,44 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter;
+
+import java.util.function.Consumer;
+
+/**
+ * Used in OneToMany & ManyToOne & ManyToMany in client. <br>
+ * It is a Publisher for user subscriber to subscribe. <br>
+ * It is a StreamObserver for responseStream. <br>
+ * It is a Subscription for user subscriber to request and pass request to requestStream.
+ */
+public class ClientTripleReactorPublisher<T> extends AbstractTripleReactorPublisher<T> {
+
+    public ClientTripleReactorPublisher() {
+    }
+
+    public ClientTripleReactorPublisher(Consumer<CallStreamObserver<?>> onSubscribe, Runnable shutdownHook) {
+        super(onSubscribe, shutdownHook);
+    }
+
+    @Override
+    public void beforeStart(ClientCallToObserverAdapter<T> clientCallToObserverAdapter) {
+        super.onSubscribe(clientCallToObserverAdapter);
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorSubscriber.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorSubscriber.java
new file mode 100644
index 0000000..7d59244
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ClientTripleReactorSubscriber.java
@@ -0,0 +1,34 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter;
+
+/**
+ * The subscriber in client to subscribe user publisher and is subscribed by ClientStreamObserver.
+ */
+public class ClientTripleReactorSubscriber<T> extends AbstractTripleReactorSubscriber<T> {
+
+    @Override
+    public void cancel() {
+        if (!isCanceled()) {
+            super.cancel();
+            ((ClientCallToObserverAdapter<T>) downstream).cancel(new Exception("Cancelled"));
+        }
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorPublisher.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorPublisher.java
new file mode 100644
index 0000000..8f5382e
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorPublisher.java
@@ -0,0 +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.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+
+/**
+ * Used in ManyToOne and ManyToMany in server. <br>
+ * It is a Publisher for user subscriber to subscribe. <br>
+ * It is a StreamObserver for requestStream. <br>
+ * It is a Subscription for user subscriber to request and pass request to responseStream.
+ */
+public class ServerTripleReactorPublisher<T> extends AbstractTripleReactorPublisher<T> {
+
+    public ServerTripleReactorPublisher(CallStreamObserver<?> callStreamObserver) {
+        super.onSubscribe(callStreamObserver);
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorSubscriber.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorSubscriber.java
new file mode 100644
index 0000000..1e8ef5e
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/ServerTripleReactorSubscriber.java
@@ -0,0 +1,44 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.rpc.CancellationContext;
+import org.apache.dubbo.rpc.protocol.tri.CancelableStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+
+/**
+ * The Subscriber in server to passing the data produced by user publisher to responseStream.
+ */
+public class ServerTripleReactorSubscriber<T> extends AbstractTripleReactorSubscriber<T> {
+
+    @Override
+    public void subscribe(CallStreamObserver<T> downstream) {
+        super.subscribe(downstream);
+        if (downstream instanceof CancelableStreamObserver) {
+            CancelableStreamObserver<?> observer = (CancelableStreamObserver<?>) downstream;
+            final CancellationContext context;
+            if (observer.getCancellationContext() == null) {
+                context = new CancellationContext();
+                observer.setCancellationContext(context);
+            } else {
+                context = observer.getCancellationContext();
+            }
+            context.addListener(ctx -> super.cancel());
+        }
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorClientCalls.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorClientCalls.java
new file mode 100644
index 0000000..c23e987
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorClientCalls.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.reactive.calls;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.ClientTripleReactorPublisher;
+import org.apache.dubbo.reactive.ClientTripleReactorSubscriber;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.model.StubMethodDescriptor;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.stub.StubInvocationUtil;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * A collection of methods to convert client-side Reactor calls to stream calls.
+ */
+public final class ReactorClientCalls {
+
+    private ReactorClientCalls() {
+    }
+
+    /**
+     * Implements a unary -> unary call as Mono -> Mono
+     *
+     * @param invoker invoker
+     * @param monoRequest the mono with request
+     * @param methodDescriptor the method descriptor
+     * @return the mono with response
+     */
+    public static <TRequest, TResponse, TInvoker> Mono<TResponse> oneToOne(Invoker<TInvoker> invoker,
+                                                                 Mono<TRequest> monoRequest,
+                                                                 StubMethodDescriptor methodDescriptor) {
+        try {
+            return Mono.create(emitter -> monoRequest.subscribe(
+                    request -> StubInvocationUtil.unaryCall(invoker, methodDescriptor, request, new StreamObserver<TResponse>() {
+                        @Override
+                        public void onNext(TResponse tResponse) {
+                            emitter.success(tResponse);
+                        }
+
+                        @Override
+                        public void onError(Throwable throwable) {
+                            emitter.error(throwable);
+                        }
+
+                        @Override
+                        public void onCompleted() {
+                            // Do nothing
+                        }
+                    }),
+                    emitter::error
+                ));
+        } catch (Throwable throwable) {
+            return Mono.error(throwable);
+        }
+    }
+
+    /**
+     * Implements a unary -> stream call as Mono -> Flux
+     *
+     * @param invoker invoker
+     * @param monoRequest the mono with request
+     * @param methodDescriptor the method descriptor
+     * @return the flux with response
+     */
+    public static <TRequest, TResponse, TInvoker> Flux<TResponse> oneToMany(Invoker<TInvoker> invoker,
+                                                                            Mono<TRequest> monoRequest,
+                                                                            StubMethodDescriptor methodDescriptor) {
+        try {
+            return monoRequest
+                .flatMapMany(request -> {
+                    ClientTripleReactorPublisher<TResponse> clientPublisher = new ClientTripleReactorPublisher<>();
+                    StubInvocationUtil.serverStreamCall(invoker, methodDescriptor, request, clientPublisher);
+                    return clientPublisher;
+                });
+        } catch (Throwable throwable) {
+            return Flux.error(throwable);
+        }
+    }
+
+    /**
+     * Implements a stream -> unary call as Flux -> Mono
+     *
+     * @param invoker invoker
+     * @param requestFlux the flux with request
+     * @param methodDescriptor the method descriptor
+     * @return the mono with response
+     */
+    public static <TRequest, TResponse, TInvoker> Mono<TResponse> manyToOne(Invoker<TInvoker> invoker,
+                                                                            Flux<TRequest> requestFlux,
+                                                                            StubMethodDescriptor methodDescriptor) {
+        try {
+            ClientTripleReactorSubscriber<TRequest> clientSubscriber = requestFlux.subscribeWith(new ClientTripleReactorSubscriber<>());
+            ClientTripleReactorPublisher<TResponse> clientPublisher = new ClientTripleReactorPublisher<>(
+                s -> clientSubscriber.subscribe((CallStreamObserver<TRequest>) s),
+                clientSubscriber::cancel);
+            return Mono.from(clientPublisher).doOnSubscribe(dummy ->
+                StubInvocationUtil.biOrClientStreamCall(invoker, methodDescriptor, clientPublisher));
+        } catch (Throwable throwable) {
+            return Mono.error(throwable);
+        }
+    }
+
+    /**
+     * Implements a stream -> stream call as Flux -> Flux
+     *
+     * @param invoker invoker
+     * @param requestFlux the flux with request
+     * @param methodDescriptor the method descriptor
+     * @return the flux with response
+     */
+    public static <TRequest, TResponse, TInvoker> Flux<TResponse> manyToMany(Invoker<TInvoker> invoker,
+                                                                             Flux<TRequest> requestFlux,
+                                                                             StubMethodDescriptor methodDescriptor) {
+        try {
+            ClientTripleReactorSubscriber<TRequest> clientSubscriber = requestFlux.subscribeWith(new ClientTripleReactorSubscriber<>());
+            ClientTripleReactorPublisher<TResponse> clientPublisher = new ClientTripleReactorPublisher<>(
+                s -> clientSubscriber.subscribe((CallStreamObserver<TRequest>) s),
+                clientSubscriber::cancel);
+            return Flux.from(clientPublisher).doOnSubscribe(dummy ->
+                StubInvocationUtil.biOrClientStreamCall(invoker, methodDescriptor, clientPublisher));
+        } catch (Throwable throwable) {
+            return Flux.error(throwable);
+        }
+    }
+
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorServerCalls.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorServerCalls.java
new file mode 100644
index 0000000..33c54d1
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/calls/ReactorServerCalls.java
@@ -0,0 +1,136 @@
+/*
+ * 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.reactive.calls;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.ServerTripleReactorPublisher;
+import org.apache.dubbo.reactive.ServerTripleReactorSubscriber;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * A collection of methods to convert server-side stream calls to Reactor calls.
+ */
+public final class ReactorServerCalls {
+
+    private ReactorServerCalls() {
+    }
+
+    /**
+     * Implements a unary -> unary call as Mono -> Mono
+     *
+     * @param request request
+     * @param responseObserver response StreamObserver
+     * @param func service implementation
+     */
+    public static <T, R> void oneToOne(T request,
+                                       StreamObserver<R> responseObserver,
+                                       Function<Mono<T>, Mono<R>> func) {
+        func.apply(Mono.just(request)).subscribe(res -> {
+            CompletableFuture.completedFuture(res)
+                .whenComplete((r, t) -> {
+                    if (t != null) {
+                        responseObserver.onError(t);
+                    } else {
+                        responseObserver.onNext(r);
+                        responseObserver.onCompleted();
+                    }
+                });
+        });
+    }
+
+    /**
+     * Implements a unary -> stream call as Mono -> Flux
+     *
+     * @param request request
+     * @param responseObserver response StreamObserver
+     * @param func service implementation
+     */
+    public static <T, R> void oneToMany(T request,
+                                        StreamObserver<R> responseObserver,
+                                        Function<Mono<T>, Flux<R>> func) {
+        try {
+            Flux<R> response = func.apply(Mono.just(request));
+            ServerTripleReactorSubscriber<R> subscriber = response.subscribeWith(new ServerTripleReactorSubscriber<>());
+            subscriber.subscribe((ServerCallToObserverAdapter<R>) responseObserver);
+        } catch (Throwable throwable) {
+            responseObserver.onError(throwable);
+        }
+    }
+
+    /**
+     * Implements a stream -> unary call as Flux -> Mono
+     *
+     * @param responseObserver response StreamObserver
+     * @param func service implementation
+     * @return request StreamObserver
+     */
+    public static <T, R> StreamObserver<T> manyToOne(StreamObserver<R> responseObserver,
+                                                      Function<Flux<T>, Mono<R>> func) {
+        ServerTripleReactorPublisher<T> serverPublisher = new ServerTripleReactorPublisher<T>((CallStreamObserver<R>) responseObserver);
+        try {
+            Mono<R> responseMono = func.apply(Flux.from(serverPublisher));
+            responseMono.subscribe(value -> {
+                    // Don't try to respond if the server has already canceled the request
+                    if (!serverPublisher.isCancelled()) {
+                        responseObserver.onNext(value);
+                    }
+                },
+                throwable -> {
+                    // Don't try to respond if the server has already canceled the request
+                    if (!serverPublisher.isCancelled()) {
+                        responseObserver.onError(throwable);
+                    }
+                },
+                responseObserver::onCompleted
+            );
+            serverPublisher.startRequest();
+        } catch (Throwable throwable) {
+            responseObserver.onError(throwable);
+        }
+        return serverPublisher;
+    }
+
+    /**
+     * Implements a stream -> stream call as Flux -> Flux
+     *
+     * @param responseObserver response StreamObserver
+     * @param func service implementation
+     * @return request StreamObserver
+     */
+    public static <T, R> StreamObserver<T> manyToMany(StreamObserver<R> responseObserver,
+                                                      Function<Flux<T>, Flux<R>> func) {
+        // responseObserver is also a subscription of publisher, we can use it to request more data
+        ServerTripleReactorPublisher<T> serverPublisher = new ServerTripleReactorPublisher<T>((CallStreamObserver<R>) responseObserver);
+        try {
+            Flux<R> responseFlux = func.apply(Flux.from(serverPublisher));
+            ServerTripleReactorSubscriber<R> serverSubscriber = responseFlux.subscribeWith(new ServerTripleReactorSubscriber<>());
+            serverSubscriber.subscribe((CallStreamObserver<R>) responseObserver);
+            serverPublisher.startRequest();
+        } catch (Throwable throwable) {
+            responseObserver.onError(throwable);
+        }
+
+        return serverPublisher;
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToManyMethodHandler.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToManyMethodHandler.java
new file mode 100644
index 0000000..72f9b50
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToManyMethodHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.calls.ReactorServerCalls;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.stub.StubMethodHandler;
+import reactor.core.publisher.Flux;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * The handler of ManyToMany() method for stub invocation.
+ */
+public class ManyToManyMethodHandler<T, R> implements StubMethodHandler<T, R> {
+
+    private final Function<Flux<T>, Flux<R>> func;
+
+    public ManyToManyMethodHandler(Function<Flux<T>, Flux<R>> func) {
+        this.func = func;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public CompletableFuture<StreamObserver<T>> invoke(Object[] arguments) {
+        CallStreamObserver<R> responseObserver = (CallStreamObserver<R>) arguments[0];
+        StreamObserver<T> requestObserver = ReactorServerCalls.manyToMany(responseObserver, func);
+        return CompletableFuture.completedFuture(requestObserver);
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToOneMethodHandler.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToOneMethodHandler.java
new file mode 100644
index 0000000..d74a9ab
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/ManyToOneMethodHandler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.calls.ReactorServerCalls;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.stub.StubMethodHandler;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * The handler of ManyToOne() method for stub invocation.
+ */
+public class ManyToOneMethodHandler<T, R> implements StubMethodHandler<T, R> {
+
+    private final Function<Flux<T>, Mono<R>> func;
+
+    public ManyToOneMethodHandler(Function<Flux<T>, Mono<R>> func) {
+        this.func = func;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public CompletableFuture<StreamObserver<T>> invoke(Object[] arguments) {
+        CallStreamObserver<R> responseObserver = (CallStreamObserver<R>) arguments[0];
+        StreamObserver<T> requestObserver = ReactorServerCalls.manyToOne(responseObserver, func);
+        return CompletableFuture.completedFuture(requestObserver);
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToManyMethodHandler.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToManyMethodHandler.java
new file mode 100644
index 0000000..a672c5b
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToManyMethodHandler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.calls.ReactorServerCalls;
+import org.apache.dubbo.rpc.stub.StubMethodHandler;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * The handler of OneToMany() method for stub invocation.
+ */
+public class OneToManyMethodHandler<T, R> implements StubMethodHandler<T, R> {
+
+    private final Function<Mono<T>, Flux<R>> func;
+
+    public OneToManyMethodHandler(Function<Mono<T>, Flux<R>> func) {
+        this.func = func;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public CompletableFuture<?> invoke(Object[] arguments) {
+        T request = (T) arguments[0];
+        StreamObserver<R> responseObserver = (StreamObserver<R>) arguments[1];
+        ReactorServerCalls.oneToMany(request, responseObserver, func);
+        return CompletableFuture.completedFuture(null);
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToOneMethodHandler.java b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToOneMethodHandler.java
new file mode 100644
index 0000000..f20a3ec
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/main/java/org/apache/dubbo/reactive/handler/OneToOneMethodHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.calls.ReactorServerCalls;
+import org.apache.dubbo.rpc.stub.FutureToObserverAdaptor;
+import org.apache.dubbo.rpc.stub.StubMethodHandler;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * The handler of OneToOne() method for stub invocation.
+ */
+public class OneToOneMethodHandler<T, R> implements StubMethodHandler<T, R> {
+
+    private final Function<Mono<T>, Mono<R>> func;
+
+    public OneToOneMethodHandler(Function<Mono<T>, Mono<R>> func) {
+        this.func = func;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public CompletableFuture<R> invoke(Object[] arguments) {
+        T request = (T) arguments[0];
+        CompletableFuture<R> future = new CompletableFuture<>();
+        StreamObserver<R> responseObserver = new FutureToObserverAdaptor<>(future);
+        ReactorServerCalls.oneToOne(request, responseObserver, func);
+        return future;
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToManyMethodHandlerTest.java b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToManyMethodHandlerTest.java
new file mode 100644
index 0000000..bf80ca9
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToManyMethodHandlerTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.handler.ManyToManyMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+/**
+ * Unit test for ManyToManyMethodHandler
+ */
+public final class ManyToManyMethodHandlerTest {
+
+    @Test
+    public void testInvoke() throws ExecutionException, InterruptedException {
+        AtomicInteger nextCounter = new AtomicInteger();
+        AtomicInteger completeCounter = new AtomicInteger();
+        AtomicInteger errorCounter = new AtomicInteger();
+        ServerCallToObserverAdapter<String> responseObserver = Mockito.mock(ServerCallToObserverAdapter.class);
+        doAnswer(o -> nextCounter.incrementAndGet())
+            .when(responseObserver).onNext(anyString());
+        doAnswer(o -> completeCounter.incrementAndGet())
+            .when(responseObserver).onCompleted();
+        doAnswer(o -> errorCounter.incrementAndGet())
+            .when(responseObserver).onError(any(Throwable.class));
+        ManyToManyMethodHandler<String, String> handler = new ManyToManyMethodHandler<>(requestFlux ->
+            requestFlux.map(r -> r + "0"));
+        CompletableFuture<StreamObserver<String>> future = handler.invoke(new Object[]{responseObserver});
+        StreamObserver<String> requestObserver = future.get();
+        for (int i = 0; i < 10; i++) {
+            requestObserver.onNext(String.valueOf(i));
+        }
+        requestObserver.onCompleted();
+        Assertions.assertEquals(10, nextCounter.get());
+        Assertions.assertEquals(0, errorCounter.get());
+        Assertions.assertEquals(1, completeCounter.get());
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToOneMethodHandlerTest.java b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToOneMethodHandlerTest.java
new file mode 100644
index 0000000..e3e3302
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/ManyToOneMethodHandlerTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.reactive.handler.ManyToOneMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+/**
+ * Unit test for ManyToOneMethodHandler
+ */
+public final class ManyToOneMethodHandlerTest {
+
+    @Test
+    public void testInvoker() throws ExecutionException, InterruptedException {
+        AtomicInteger nextCounter = new AtomicInteger();
+        AtomicInteger completeCounter = new AtomicInteger();
+        AtomicInteger errorCounter = new AtomicInteger();
+        ServerCallToObserverAdapter<String> responseObserver = Mockito.mock(ServerCallToObserverAdapter.class);
+        doAnswer(o -> nextCounter.incrementAndGet())
+            .when(responseObserver).onNext(anyString());
+        doAnswer(o -> completeCounter.incrementAndGet())
+            .when(responseObserver).onCompleted();
+        doAnswer(o -> errorCounter.incrementAndGet())
+            .when(responseObserver).onError(any(Throwable.class));
+        ManyToOneMethodHandler<String, String> handler = new ManyToOneMethodHandler<>(requestFlux ->
+            requestFlux.map(Integer::valueOf).reduce(Integer::sum).map(String::valueOf));
+        CompletableFuture<StreamObserver<String>> future = handler.invoke(new Object[]{responseObserver});
+        StreamObserver<String> requestObserver = future.get();
+        for (int i = 0; i < 10; i++) {
+            requestObserver.onNext(String.valueOf(i));
+        }
+        requestObserver.onCompleted();
+        Assertions.assertEquals(1, nextCounter.get());
+        Assertions.assertEquals(0, errorCounter.get());
+        Assertions.assertEquals(1, completeCounter.get());
+    }
+
+    @Test
+    public void testError() throws ExecutionException, InterruptedException {
+        AtomicInteger nextCounter = new AtomicInteger();
+        AtomicInteger completeCounter = new AtomicInteger();
+        AtomicInteger errorCounter = new AtomicInteger();
+        ServerCallToObserverAdapter<String> responseObserver = Mockito.mock(ServerCallToObserverAdapter.class);
+        doAnswer(o -> nextCounter.incrementAndGet())
+            .when(responseObserver).onNext(anyString());
+        doAnswer(o -> completeCounter.incrementAndGet())
+            .when(responseObserver).onCompleted();
+        doAnswer(o -> errorCounter.incrementAndGet())
+            .when(responseObserver).onError(any(Throwable.class));
+        ManyToOneMethodHandler<String, String> handler = new ManyToOneMethodHandler<>(requestFlux ->
+            requestFlux.map(Integer::valueOf).reduce(Integer::sum).map(String::valueOf));
+        CompletableFuture<StreamObserver<String>> future = handler.invoke(new Object[]{responseObserver});
+        StreamObserver<String> requestObserver = future.get();
+        for (int i = 0; i < 10; i++) {
+            if (i == 6) {
+                requestObserver.onError(new Throwable());
+            }
+            requestObserver.onNext(String.valueOf(i));
+        }
+        requestObserver.onCompleted();
+        Assertions.assertEquals(0, nextCounter.get());
+        Assertions.assertEquals(1, errorCounter.get());
+        Assertions.assertEquals(0, completeCounter.get());
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToManyMethodHandlerTest.java b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToManyMethodHandlerTest.java
new file mode 100644
index 0000000..ac0c8d9
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToManyMethodHandlerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.reactive;
+
+import org.apache.dubbo.reactive.handler.OneToManyMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import reactor.core.publisher.Flux;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+/**
+ * Unit test for OneToManyMethodHandler
+ */
+public final class OneToManyMethodHandlerTest {
+
+    @Test
+    public void testInvoke() {
+        String request = "1,2,3,4,5,6,7";
+        AtomicInteger nextCounter = new AtomicInteger();
+        AtomicInteger completeCounter = new AtomicInteger();
+        AtomicInteger errorCounter = new AtomicInteger();
+        ServerCallToObserverAdapter<String> responseObserver = Mockito.mock(ServerCallToObserverAdapter.class);
+        doAnswer(o -> nextCounter.incrementAndGet())
+            .when(responseObserver).onNext(anyString());
+        doAnswer(o -> completeCounter.incrementAndGet())
+            .when(responseObserver).onCompleted();
+        doAnswer(o -> errorCounter.incrementAndGet())
+            .when(responseObserver).onError(any(Throwable.class));
+        OneToManyMethodHandler<String, String> handler = new OneToManyMethodHandler<>(requestMono ->
+            requestMono.flatMapMany(r -> Flux.fromArray(r.split(","))));
+        CompletableFuture<?> future = handler.invoke(new Object[]{request, responseObserver});
+        Assertions.assertTrue(future.isDone());
+        Assertions.assertEquals(7, nextCounter.get());
+        Assertions.assertEquals(0, errorCounter.get());
+        Assertions.assertEquals(1, completeCounter.get());
+    }
+
+    @Test
+    public void testError() {
+        String request = "1,2,3,4,5,6,7";
+        AtomicInteger nextCounter = new AtomicInteger();
+        AtomicInteger completeCounter = new AtomicInteger();
+        AtomicInteger errorCounter = new AtomicInteger();
+        ServerCallToObserverAdapter<String> responseObserver = Mockito.mock(ServerCallToObserverAdapter.class);
+        doAnswer(o -> nextCounter.incrementAndGet())
+            .when(responseObserver).onNext(anyString());
+        doAnswer(o -> completeCounter.incrementAndGet())
+            .when(responseObserver).onCompleted();
+        doAnswer(o -> errorCounter.incrementAndGet())
+            .when(responseObserver).onError(any(Throwable.class));
+        OneToManyMethodHandler<String, String> handler = new OneToManyMethodHandler<>(requestMono ->
+            Flux.create(emitter -> {
+                for (int i = 0; i < 10; i++) {
+                    if (i == 6) {
+                        emitter.error(new Throwable());
+                    } else {
+                        emitter.next(String.valueOf(i));
+                    }
+                }
+            }));
+        CompletableFuture<?> future = handler.invoke(new Object[]{request, responseObserver});
+        Assertions.assertTrue(future.isDone());
+        Assertions.assertEquals(6, nextCounter.get());
+        Assertions.assertEquals(1, errorCounter.get());
+        Assertions.assertEquals(0, completeCounter.get());
+    }
+}
diff --git a/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToOneMethodHandlerTest.java b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToOneMethodHandlerTest.java
new file mode 100644
index 0000000..6d6a2fc
--- /dev/null
+++ b/dubbo-plugin/dubbo-reactive/src/test/java/org/apache/dubbo/reactive/OneToOneMethodHandlerTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.reactive;
+
+import org.apache.dubbo.reactive.handler.OneToOneMethodHandler;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Unit test for OneToOneMethodHandler
+ */
+public final class OneToOneMethodHandlerTest {
+
+    @Test
+    public void testInvoke() throws ExecutionException, InterruptedException {
+        String request = "request";
+        OneToOneMethodHandler<String, String> handler = new OneToOneMethodHandler<>(requestMono ->
+            requestMono.map(r -> r + "Test"));
+        CompletableFuture<?> future = handler.invoke(new Object[]{request});
+        assertEquals("requestTest", future.get());
+    }
+}
diff --git a/dubbo-plugin/pom.xml b/dubbo-plugin/pom.xml
index ed77204..2df28c3 100644
--- a/dubbo-plugin/pom.xml
+++ b/dubbo-plugin/pom.xml
@@ -31,6 +31,7 @@
     <modules>
         <module>dubbo-qos</module>
         <module>dubbo-auth</module>
+        <module>dubbo-reactive</module>
     </modules>
     <properties>
         <skip_maven_deploy>false</skip_maven_deploy>
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/ListenerRegistryWrapper.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/ListenerRegistryWrapper.java
index 3762387..c9cdc67 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/ListenerRegistryWrapper.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/ListenerRegistryWrapper.java
@@ -18,15 +18,16 @@
 package org.apache.dubbo.registry;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 public class ListenerRegistryWrapper implements Registry {
-    private static final Logger logger = LoggerFactory.getLogger(ListenerRegistryWrapper.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenerRegistryWrapper.class);
 
     private final Registry registry;
     private final List<RegistryServiceListener> listeners;
@@ -59,21 +60,8 @@
                 registry.register(url);
             }
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners) && !UrlUtils.isConsumer(url)) {
-                RuntimeException exception = null;
-                for (RegistryServiceListener listener : listeners) {
-                    if (listener != null) {
-                        try {
-                            listener.onRegister(url, registry);
-                        } catch (RuntimeException t) {
-                            logger.error(t.getMessage(), t);
-                            exception = t;
-                        }
-                    }
-                }
-                if (exception != null) {
-                    throw exception;
-                }
+            if (!UrlUtils.isConsumer(url)) {
+                listenerEvent(serviceListener -> serviceListener.onRegister(url, registry));
             }
         }
     }
@@ -85,21 +73,8 @@
                 registry.unregister(url);
             }
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners) && !UrlUtils.isConsumer(url)) {
-                RuntimeException exception = null;
-                for (RegistryServiceListener listener : listeners) {
-                    if (listener != null) {
-                        try {
-                            listener.onUnregister(url, registry);
-                        } catch (RuntimeException t) {
-                            logger.error(t.getMessage(), t);
-                            exception = t;
-                        }
-                    }
-                }
-                if (exception != null) {
-                    throw exception;
-                }
+            if (!UrlUtils.isConsumer(url)) {
+                listenerEvent(serviceListener -> serviceListener.onUnregister(url, registry));
             }
         }
     }
@@ -111,46 +86,18 @@
                 registry.subscribe(url, listener);
             }
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners)) {
-                RuntimeException exception = null;
-                for (RegistryServiceListener registryListener : listeners) {
-                    if (registryListener != null) {
-                        try {
-                            registryListener.onSubscribe(url, registry);
-                        } catch (RuntimeException t) {
-                            logger.error(t.getMessage(), t);
-                            exception = t;
-                        }
-                    }
-                }
-                if (exception != null) {
-                    throw exception;
-                }
-            }
+            listenerEvent(serviceListener -> serviceListener.onSubscribe(url, registry));
         }
     }
 
+
     @Override
     public void unsubscribe(URL url, NotifyListener listener) {
         try {
             registry.unsubscribe(url, listener);
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners)) {
-                RuntimeException exception = null;
-                for (RegistryServiceListener registryListener : listeners) {
-                    if (registryListener != null) {
-                        try {
-                            registryListener.onUnsubscribe(url, registry);
-                        } catch (RuntimeException t) {
-                            logger.error(t.getMessage(), t);
-                            exception = t;
-                        }
-                    }
-                }
-                if (exception != null) {
-                    throw exception;
-                }
-            }
+            listenerEvent(serviceListener -> serviceListener.onUnsubscribe(url, registry));
+
         }
     }
 
@@ -167,4 +114,23 @@
     public Registry getRegistry() {
         return registry;
     }
+
+    private void listenerEvent(Consumer<RegistryServiceListener> consumer) {
+        if (CollectionUtils.isNotEmpty(listeners)) {
+            RuntimeException exception = null;
+            for (RegistryServiceListener listener : listeners) {
+                if (listener != null) {
+                    try {
+                        consumer.accept(listener);
+                    } catch (RuntimeException t) {
+                        logger.error(t.getMessage(), t);
+                        exception = t;
+                    }
+                }
+            }
+            if (exception != null) {
+                throw exception;
+            }
+        }
+    }
 }
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
index 1714ae5..452f919 100644
--- 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 
@@ -30,7 +30,7 @@
 
 public abstract class RegistryNotifier {
 
-    private static final Logger logger = LoggerFactory.getLogger(RegistryNotifier.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RegistryNotifier.class);
     private volatile long lastExecuteTime;
     private volatile long lastEventTime;
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
index 0b3fea6..0ccaa13 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/AbstractServiceDiscovery.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
@@ -51,7 +51,7 @@
  * Each service discovery is bond to one application.
  */
 public abstract class AbstractServiceDiscovery implements ServiceDiscovery {
-    private final Logger logger = LoggerFactory.getLogger(AbstractServiceDiscovery.class);
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractServiceDiscovery.class);
     private volatile boolean isDestroy;
 
     protected final String serviceName;
@@ -67,31 +67,33 @@
     protected ApplicationModel applicationModel;
 
     public AbstractServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
-        this(applicationModel.getApplicationName(), registryURL);
-        this.applicationModel = applicationModel;
+        this(applicationModel, applicationModel.getApplicationName(), registryURL);
         MetadataReportInstance metadataReportInstance = applicationModel.getBeanFactory().getBean(MetadataReportInstance.class);
         metadataType = metadataReportInstance.getMetadataType();
         this.metadataReport = metadataReportInstance.getMetadataReport(registryURL.getParameter(REGISTRY_CLUSTER_KEY));
-//        if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataReportInstance.getMetadataType())) {
-//            this.metadataReport = metadataReportInstance.getMetadataReport(registryURL.getParameter(REGISTRY_CLUSTER_KEY));
-//        } else {
-//            this.metadataReport = metadataReportInstance.getNopMetadataReport();
-//        }
     }
 
     public AbstractServiceDiscovery(String serviceName, URL registryURL) {
-        this.applicationModel = ApplicationModel.defaultModel();
-        this.registryURL = registryURL;
+        this(ApplicationModel.defaultModel(), serviceName, registryURL);
+    }
+
+    private AbstractServiceDiscovery(ApplicationModel applicationModel, String serviceName, URL registryURL) {
+        this.applicationModel = applicationModel;
         this.serviceName = serviceName;
+        this.registryURL = registryURL;
         this.metadataInfo = new MetadataInfo(serviceName);
         boolean localCacheEnabled = registryURL.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
         this.metaCacheManager = new MetaCacheManager(localCacheEnabled, getCacheNameSuffix(),
             applicationModel.getFrameworkModel().getBeanFactory()
-            .getBean(FrameworkExecutorRepository.class).getCacheRefreshingScheduledExecutor());
+                .getBean(FrameworkExecutorRepository.class).getCacheRefreshingScheduledExecutor());
     }
 
+
     @Override
     public synchronized void register() throws RuntimeException {
+        if (isDestroy) {
+            return;
+        }
         this.serviceInstance = createServiceInstance(this.metadataInfo);
         if (!isValidInstance(this.serviceInstance)) {
             logger.warn("No valid instance found, stop registering instance address to registry.");
@@ -135,6 +137,9 @@
 
     @Override
     public synchronized void unregister() throws RuntimeException {
+        if (isDestroy) {
+            return;
+        }
         // fixme, this metadata info might still being shared by other instances
 //        unReportMetadata(this.metadataInfo);
         if (!isValidInstance(this.serviceInstance)) {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
index 074811b..38c8efd 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java
@@ -30,6 +30,7 @@
 import java.util.Objects;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.ENDPOINTS;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
@@ -71,7 +72,7 @@
     private transient Map<String, String> extendParams;
     private transient List<Endpoint> endpoints;
     private transient ApplicationModel applicationModel;
-    private transient InstanceAddressURL instanceAddressURL = null;
+    private transient Map<String, InstanceAddressURL> instanceAddressURL = new ConcurrentHashMap<>();
 
     public DefaultServiceInstance() {
     }
@@ -245,6 +246,12 @@
         return copyOfInstance;
     }
 
+    public DefaultServiceInstance copyFrom(int port) {
+        DefaultServiceInstance copyOfInstance = new DefaultServiceInstance(this);
+        copyOfInstance.setPort(port);
+        return copyOfInstance;
+    }
+
     @Override
     public Map<String, String> getAllParams() {
         if (extendParams == null) {
@@ -280,15 +287,13 @@
     @Override
     public void setServiceMetadata(MetadataInfo serviceMetadata) {
         this.serviceMetadata = serviceMetadata;
-        this.instanceAddressURL = null;
+        this.instanceAddressURL.clear();
     }
 
     @Override
     public InstanceAddressURL toURL(String protocol) {
-        if (instanceAddressURL == null) {
-            instanceAddressURL = new InstanceAddressURL(this, serviceMetadata, protocol);
-        }
-        return instanceAddressURL;
+        return instanceAddressURL.computeIfAbsent(protocol,
+            key -> new InstanceAddressURL(this, serviceMetadata, protocol));
     }
 
     @Override
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
index 0dcb0e6..d1d46e1 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/FileSystemServiceDiscovery.java
@@ -22,7 +22,7 @@
 //import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 //import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
 //import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
-//import org.apache.dubbo.common.logger.Logger;
+//import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 //import org.apache.dubbo.common.logger.LoggerFactory;
 //import org.apache.dubbo.common.utils.StringUtils;
 //
@@ -53,7 +53,7 @@
 // */
 //public class FileSystemServiceDiscovery extends AbstractServiceDiscovery {
 //
-//    private final Logger logger = LoggerFactory.getLogger(getClass());
+//    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 //
 //    private final Map<File, FileLock> fileLocksCache = new ConcurrentHashMap<>();
 //
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 8e8b2f9..225b6e6 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
@@ -17,6 +17,7 @@
 package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
+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.CollectionUtils;
@@ -127,6 +128,26 @@
     }
 
     @Override
+    public URL setProtocol(String protocol) {
+        return new ServiceConfigURL(protocol, getUsername(), getPassword(), getHost(), getPort(), getPath(), getParameters(), attributes);
+    }
+
+    @Override
+    public URL setHost(String host) {
+        return new ServiceConfigURL(getProtocol(), getUsername(), getPassword(), host, getPort(), getPath(), getParameters(), attributes);
+    }
+
+    @Override
+    public URL setPort(int port) {
+        return new ServiceConfigURL(getProtocol(), getUsername(), getPassword(), getHost(), port, getPath(), getParameters(), attributes);
+    }
+
+    @Override
+    public URL setPath(String path) {
+        return new ServiceConfigURL(getProtocol(), getUsername(), getPassword(), getHost(), getPort(), path, getParameters(), attributes);
+    }
+
+    @Override
     public String getAddress() {
         return instance.getAddress();
     }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ReflectionBasedServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ReflectionBasedServiceDiscovery.java
new file mode 100644
index 0000000..02c8c15
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ReflectionBasedServiceDiscovery.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.RevisionResolver;
+import org.apache.dubbo.registry.Constants;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.client.metadata.MetadataServiceDelegation;
+import org.apache.dubbo.registry.client.metadata.MetadataUtils;
+import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelUtil;
+import org.apache.dubbo.rpc.service.Destroyable;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_NOTIFY_EVENT;
+
+public class ReflectionBasedServiceDiscovery extends AbstractServiceDiscovery {
+
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
+
+    /**
+     * Echo check if consumer is still work
+     * echo task may take a lot of time when consumer offline, create a new ScheduledThreadPool
+     */
+    private final ScheduledExecutorService echoCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-Registry-EchoCheck-Consumer"));
+
+    // =================================== Provider side =================================== //
+    /**
+     * Local {@link ServiceInstance} Metadata's revision
+     */
+    private String lastMetadataRevision;
+
+    // =================================== Consumer side =================================== //
+
+    /**
+     * Local Cache of {@link ServiceInstance} Metadata
+     * <p>
+     * Key - {@link ServiceInstance} ID ( usually ip + port )
+     * Value - Json processed metadata string
+     */
+    private final ConcurrentHashMap<String, String> metadataMap = new ConcurrentHashMap<>();
+
+    /**
+     * Local Cache of {@link ServiceInstance}
+     * <p>
+     * Key - Service Name
+     * Value - List {@link ServiceInstance}
+     */
+    private final ConcurrentHashMap<String, List<ServiceInstance>> cachedServiceInstances = new ConcurrentHashMap<>();
+
+    private final MetadataServiceDelegation metadataService;
+
+    public ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
+
+    /**
+     * Local Cache of Service's {@link ServiceInstance} list revision,
+     * used to check if {@link ServiceInstance} list has been updated
+     * <p>
+     * Key - ServiceName
+     * Value - a revision calculate from {@link List} of {@link ServiceInstance}
+     */
+    private final ConcurrentHashMap<String, String> serviceInstanceRevisionMap = new ConcurrentHashMap<>();
+
+    public ReflectionBasedServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
+        super(applicationModel, registryURL);
+        long echoPollingCycle = registryURL.getParameter(Constants.ECHO_POLLING_CYCLE_KEY, Constants.DEFAULT_ECHO_POLLING_CYCLE);
+
+        this.metadataService = applicationModel.getBeanFactory().getOrRegisterBean(MetadataServiceDelegation.class);
+
+        // Echo check: test if consumer is offline, remove MetadataChangeListener,
+        // reduce the probability of failure when metadata update
+        echoCheckExecutor.scheduleAtFixedRate(() -> {
+            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
+            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
+
+            while (iterator.hasNext()) {
+                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
+                try {
+                    entry.getValue().echo(CommonConstants.DUBBO);
+                } catch (RpcException e) {
+                    if (logger.isInfoEnabled()) {
+                        logger.info("Send echo message to consumer error. Possible cause: consumer is offline.");
+                    }
+                    iterator.remove();
+                }
+            }
+        }, echoPollingCycle, echoPollingCycle, TimeUnit.MILLISECONDS);
+    }
+
+    public void doInitialize(URL registryURL) {
+
+    }
+
+    @Override
+    public void doDestroy() throws Exception {
+        metadataMap.clear();
+        serviceInstanceRevisionMap.clear();
+        echoCheckExecutor.shutdown();
+    }
+
+    private void updateInstanceMetadata(ServiceInstance serviceInstance) {
+        String metadataString = JSONObject.toJSONString(serviceInstance.getMetadata());
+        String metadataRevision = RevisionResolver.calRevision(metadataString);
+
+        // check if metadata updated
+        if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
+            }
+
+            lastMetadataRevision = metadataRevision;
+
+            // save the newest metadata to local
+            metadataService.exportInstanceMetadata(metadataString);
+
+            // notify to consumer
+            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
+            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
+
+            while (iterator.hasNext()) {
+                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
+                try {
+                    entry.getValue().onEvent(metadataString);
+                } catch (RpcException e) {
+                    // 1-7 - Failed to notify registry event.
+                    // The updating of metadata to consumer is a type of registry event.
+
+                    logger.warn(REGISTRY_FAILED_NOTIFY_EVENT, "consumer is offline", "",
+                        "Notify to consumer error, removing listener.");
+
+                    // remove listener if consumer is offline
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
+        updateInstanceMetadata(serviceInstance);
+    }
+
+    @Override
+    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
+        updateInstanceMetadata(serviceInstance);
+    }
+
+    @Override
+    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
+        // notify empty message to consumer
+        metadataService.exportInstanceMetadata("");
+        metadataService.getInstanceMetadataChangedListenerMap().forEach((consumerId, listener) -> listener.onEvent(""));
+        metadataService.getInstanceMetadataChangedListenerMap().clear();
+    }
+
+    @SuppressWarnings("unchecked")
+    public final void fillServiceInstance(DefaultServiceInstance serviceInstance) {
+        String hostId = serviceInstance.getAddress();
+        if (metadataMap.containsKey(hostId)) {
+            // Use cached metadata.
+            // Metadata will be updated by provider callback
+
+            String metadataString = metadataMap.get(hostId);
+            serviceInstance.setMetadata(JSONObject.parseObject(metadataString, Map.class));
+        } else {
+            // refer from MetadataUtils, this proxy is different from the one used to refer exportedURL
+            MetadataService metadataService = getMetadataServiceProxy(serviceInstance);
+
+            String consumerId = ScopeModelUtil.getApplicationModel(registryURL.getScopeModel()).getApplicationName() + NetUtils.getLocalHost();
+            String metadata = metadataService.getAndListenInstanceMetadata(
+                consumerId, metadataString -> {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Receive callback: " + metadataString + serviceInstance);
+                    }
+                    if (StringUtils.isEmpty(metadataString)) {
+                        // provider is shutdown
+                        metadataMap.remove(hostId);
+                    } else {
+                        metadataMap.put(hostId, metadataString);
+                    }
+                });
+            metadataMap.put(hostId, metadata);
+            serviceInstance.setMetadata(JSONObject.parseObject(metadata, Map.class));
+        }
+    }
+
+    public final void notifyListener(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> instances) {
+        String serviceInstanceRevision = RevisionResolver.calRevision(JSONObject.toJSONString(instances));
+        boolean changed = !serviceInstanceRevision.equalsIgnoreCase(
+            serviceInstanceRevisionMap.put(serviceName, serviceInstanceRevision));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Service changed event received (possibly because of DNS polling). " +
+                "Service Instance changed: " + changed + " Service Name: " + serviceName);
+        }
+
+        if (changed) {
+            List<ServiceInstance> oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>());
+
+            // remove expired invoker
+            Set<ServiceInstance> allServiceInstances = new HashSet<>(oldServiceInstances.size() + instances.size());
+            allServiceInstances.addAll(oldServiceInstances);
+            allServiceInstances.addAll(instances);
+
+            oldServiceInstances.forEach(allServiceInstances::remove);
+
+            allServiceInstances.forEach(this::destroyMetadataServiceProxy);
+
+            cachedServiceInstances.put(serviceName, instances);
+            listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
+        }
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
+        return Collections.emptyList();
+    }
+
+    private String computeKey(ServiceInstance serviceInstance) {
+        return serviceInstance.getServiceName() + "##" + serviceInstance.getAddress() + "##" +
+            ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
+    }
+
+    private synchronized MetadataService getMetadataServiceProxy(ServiceInstance instance) {
+        return metadataServiceProxies.computeIfAbsent(computeKey(instance), k -> MetadataUtils.referProxy(instance).getProxy());
+    }
+
+    private synchronized void destroyMetadataServiceProxy(ServiceInstance instance) {
+        String key = computeKey(instance);
+        if (metadataServiceProxies.containsKey(key)) {
+            Object metadataServiceProxy = metadataServiceProxies.remove(key);
+            if (metadataServiceProxy instanceof Destroyable) {
+                ((Destroyable) metadataServiceProxy).$destroy();
+            }
+        }
+    }
+
+    /**
+     * UT used only
+     */
+    @Deprecated
+    public final ConcurrentHashMap<String, List<ServiceInstance>> getCachedServiceInstances() {
+        return cachedServiceInstances;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
index 2332f04..2168095 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java
@@ -35,7 +35,6 @@
      * Get the instance of {@link ServiceDiscovery}
      *
      * @param registryURL the {@link URL} to connect the registry
-     * @param model, the application model context
      * @return non-null
      */
     ServiceDiscovery getServiceDiscovery(URL registryURL);
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 30eb983..098f645 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.metadata.AbstractServiceNameMapping;
@@ -66,7 +66,7 @@
  */
 public class ServiceDiscoveryRegistry extends FailbackRegistry {
 
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     private final ServiceDiscovery serviceDiscovery;
 
@@ -299,8 +299,8 @@
     protected void subscribeURLs(URL url, NotifyListener listener, Set<String> serviceNames) {
         serviceNames = toTreeSet(serviceNames);
         String serviceNamesKey = toStringKeys(serviceNames);
-        String protocolServiceKey = url.getProtocolServiceKey();
-        logger.info(String.format("Trying to subscribe from apps %s for service key %s, ", serviceNamesKey, protocolServiceKey));
+        String serviceKey = url.getServiceKey();
+        logger.info(String.format("Trying to subscribe from apps %s for service key %s, ", serviceNamesKey, serviceKey));
 
         // register ServiceInstancesChangedListener
         Lock appSubscriptionLock = getAppSubscription(serviceNamesKey);
@@ -322,7 +322,7 @@
             if (!serviceInstancesChangedListener.isDestroyed()) {
                 serviceInstancesChangedListener.setUrl(url);
                 listener.addServiceListener(serviceInstancesChangedListener);
-                serviceInstancesChangedListener.addListenerAndNotify(protocolServiceKey, listener);
+                serviceInstancesChangedListener.addListenerAndNotify(url, listener);
                 serviceDiscovery.addServiceInstancesChangedListener(serviceInstancesChangedListener);
             } else {
                 logger.info(String.format("Listener of %s has been destroyed by another thread.", serviceNamesKey));
@@ -348,7 +348,7 @@
     }
 
     private class DefaultMappingListener implements MappingListener {
-        private final Logger logger = LoggerFactory.getLogger(DefaultMappingListener.class);
+        private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DefaultMappingListener.class);
         private final URL url;
         private Set<String> oldApps;
         private NotifyListener listener;
@@ -398,7 +398,7 @@
                             Lock appSubscriptionLock = getAppSubscription(appKey);
                             try {
                                 appSubscriptionLock.lock();
-                                oldListener.removeListener(url.getProtocolServiceKey(), listener);
+                                oldListener.removeListener(url.getServiceKey(), listener);
                                 if (!oldListener.hasListeners()) {
                                     oldListener.destroy();
                                     removeAppSubscriptionLock(appKey);
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
index bfe1b8b..a3e68cd 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java
@@ -16,10 +16,12 @@
  */
 package org.apache.dubbo.registry.client;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -30,26 +32,36 @@
 import org.apache.dubbo.registry.ProviderFirstParams;
 import org.apache.dubbo.registry.integration.AbstractConfiguratorListener;
 import org.apache.dubbo.registry.integration.DynamicDirectory;
+import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
+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.RpcServiceContext;
 import org.apache.dubbo.rpc.cluster.Configurator;
 import org.apache.dubbo.rpc.cluster.RouterChain;
+import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
 import org.apache.dubbo.rpc.cluster.router.state.BitList;
 import org.apache.dubbo.rpc.model.ModuleModel;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DISABLED_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNSUPPORTED;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_HASHMAP_LOAD_FACTOR;
 import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
@@ -58,19 +70,21 @@
 import static org.apache.dubbo.rpc.model.ScopeModelUtil.getModuleModel;
 
 public class ServiceDiscoveryRegistryDirectory<T> extends DynamicDirectory<T> {
-    private static final Logger logger = LoggerFactory.getLogger(ServiceDiscoveryRegistryDirectory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceDiscoveryRegistryDirectory.class);
 
     /**
      * instance address to invoker mapping.
      * The initial value is null and the midway may be assigned to null, please use the local variable reference
      */
-    private volatile Map<String, Invoker<T>> urlInvokerMap;
+    private volatile Map<ProtocolServiceKeyWithAddress, Invoker<T>> urlInvokerMap;
     private volatile ReferenceConfigurationListener referenceConfigurationListener;
     private volatile boolean enableConfigurationListen = true;
     private volatile List<URL> originalUrls = null;
     private volatile Map<String, String> overrideQueryMap;
     private final Set<String> providerFirstParams;
     private final ModuleModel moduleModel;
+    private final ProtocolServiceKey consumerProtocolServiceKey;
+    private final Map<ProtocolServiceKey, URL> customizedConsumerUrlMap = new ConcurrentHashMap<>();
 
     public ServiceDiscoveryRegistryDirectory(Class<T> serviceType, URL url) {
         super(serviceType, url);
@@ -94,6 +108,9 @@
             }
         }
 
+        String protocol = consumerUrl.getParameter(PROTOCOL_KEY, consumerUrl.getProtocol());
+        consumerProtocolServiceKey = new ProtocolServiceKey(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(),
+            !CommonConstants.CONSUMER.equals(protocol) ? protocol : null);
     }
 
     @Override
@@ -224,15 +241,15 @@
             }
 
             // use local reference to avoid NPE as this.urlInvokerMap will be set null concurrently at destroyAllInvokers().
-            Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
+            Map<ProtocolServiceKeyWithAddress, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
             // can't use local reference as oldUrlInvokerMap's mappings might be removed directly at toInvokers().
-            Map<String, Invoker<T>> oldUrlInvokerMap = null;
+            Map<ProtocolServiceKeyWithAddress, Invoker<T>> oldUrlInvokerMap = null;
             if (localUrlInvokerMap != null) {
                 // the initial capacity should be set greater than the maximum number of entries divided by the load factor to avoid resizing.
                 oldUrlInvokerMap = new LinkedHashMap<>(Math.round(1 + localUrlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
                 localUrlInvokerMap.forEach(oldUrlInvokerMap::put);
             }
-            Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
+            Map<ProtocolServiceKeyWithAddress, Invoker<T>> newUrlInvokerMap = toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
             logger.info("Refreshed invoker size " + newUrlInvokerMap.size());
 
             if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
@@ -266,21 +283,27 @@
      * @param urls
      * @return invokers
      */
-    private Map<String, Invoker<T>> toInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, List<URL> urls) {
-        Map<String, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>(urls == null ? 1 : (int) (urls.size() / 0.75f + 1));
+    private Map<ProtocolServiceKeyWithAddress, Invoker<T>> toInvokers(Map<ProtocolServiceKeyWithAddress, Invoker<T>> oldUrlInvokerMap, List<URL> urls) {
+        Map<ProtocolServiceKeyWithAddress, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>(urls == null ? 1 : (int) (urls.size() / 0.75f + 1));
         if (urls == null || urls.isEmpty()) {
             return newUrlInvokerMap;
         }
+
         for (URL url : urls) {
             InstanceAddressURL instanceAddressURL = (InstanceAddressURL) url;
             if (EMPTY_PROTOCOL.equals(instanceAddressURL.getProtocol())) {
                 continue;
             }
             if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(instanceAddressURL.getProtocol())) {
-                logger.error(new IllegalStateException("Unsupported protocol " + instanceAddressURL.getProtocol() +
+
+                // 4-1 - Unsupported protocol
+
+                logger.error(PROTOCOL_UNSUPPORTED, "protocol extension does not installed", "", "Unsupported protocol.",
+                    new IllegalStateException("Unsupported protocol " + instanceAddressURL.getProtocol() +
                     " in notified url: " + instanceAddressURL + " from registry " + getUrl().getAddress() +
                     " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
                     getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).getSupportedExtensions()));
+
                 continue;
             }
 
@@ -291,33 +314,58 @@
                 instanceAddressURL = overrideWithConfigurator(instanceAddressURL);
             }
 
-            Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.get(instanceAddressURL.getAddress());
-            if (invoker == null || urlChanged(invoker, instanceAddressURL)) { // Not in the cache, refer again
-                try {
-                    boolean enabled = true;
-                    if (instanceAddressURL.hasParameter(DISABLED_KEY)) {
-                        enabled = !instanceAddressURL.getParameter(DISABLED_KEY, false);
-                    } else {
-                        enabled = instanceAddressURL.getParameter(ENABLED_KEY, true);
+            // filter all the service available (version wildcard, group wildcard, protocol wildcard)
+            int port = instanceAddressURL.getPort();
+            List<ProtocolServiceKey> matchedProtocolServiceKeys = instanceAddressURL.getMetadataInfo()
+                .getMatchedServiceInfos(consumerProtocolServiceKey)
+                .stream()
+                .filter(serviceInfo -> serviceInfo.getPort() <= 0 || serviceInfo.getPort() == port)
+                .map(MetadataInfo.ServiceInfo::getProtocolServiceKey)
+                .collect(Collectors.toList());
+
+            // see org.apache.dubbo.common.ProtocolServiceKey.isSameWith
+            // check if needed to override the consumer url
+            boolean shouldWrap = matchedProtocolServiceKeys.size() != 1 || !consumerProtocolServiceKey.isSameWith(matchedProtocolServiceKeys.get(0));
+
+            for (ProtocolServiceKey matchedProtocolServiceKey : matchedProtocolServiceKeys) {
+                ProtocolServiceKeyWithAddress protocolServiceKeyWithAddress = new ProtocolServiceKeyWithAddress(matchedProtocolServiceKey, instanceAddressURL.getAddress());
+                Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.get(protocolServiceKeyWithAddress);
+                if (invoker == null || urlChanged(invoker, instanceAddressURL, matchedProtocolServiceKey)) { // Not in the cache, refer again
+                    try {
+                        boolean enabled;
+                        if (instanceAddressURL.hasParameter(DISABLED_KEY)) {
+                            enabled = !instanceAddressURL.getParameter(DISABLED_KEY, false);
+                        } else {
+                            enabled = instanceAddressURL.getParameter(ENABLED_KEY, true);
+                        }
+                        if (enabled) {
+                            if (shouldWrap) {
+                                URL newConsumerUrl = customizedConsumerUrlMap.computeIfAbsent(matchedProtocolServiceKey,
+                                    k -> consumerUrl.setProtocol(k.getProtocol())
+                                        .addParameter(CommonConstants.GROUP_KEY, k.getGroup())
+                                        .addParameter(CommonConstants.VERSION_KEY, k.getVersion()));
+                                RpcContext.getServiceContext().setConsumerUrl(newConsumerUrl);
+                                invoker = new InstanceWrappedInvoker<>(protocol.refer(serviceType, instanceAddressURL), newConsumerUrl, matchedProtocolServiceKey);
+                            } else {
+                                invoker = protocol.refer(serviceType, instanceAddressURL);
+                            }
+                        }
+                    } catch (Throwable t) {
+                        logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + instanceAddressURL + ")" + t.getMessage(), t);
                     }
-                    if (enabled) {
-                        invoker = protocol.refer(serviceType, instanceAddressURL);
+                    if (invoker != null) { // Put new invoker in cache
+                        newUrlInvokerMap.put(protocolServiceKeyWithAddress, invoker);
                     }
-                } catch (Throwable t) {
-                    logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + instanceAddressURL + ")" + t.getMessage(), t);
+                } else {
+                    newUrlInvokerMap.put(protocolServiceKeyWithAddress, invoker);
+                    oldUrlInvokerMap.remove(protocolServiceKeyWithAddress, invoker);
                 }
-                if (invoker != null) { // Put new invoker in cache
-                    newUrlInvokerMap.put(instanceAddressURL.getAddress(), invoker);
-                }
-            } else {
-                newUrlInvokerMap.put(instanceAddressURL.getAddress(), invoker);
-                oldUrlInvokerMap.remove(instanceAddressURL.getAddress(), invoker);
             }
         }
         return newUrlInvokerMap;
     }
 
-    private boolean urlChanged(Invoker<T> invoker, InstanceAddressURL newURL) {
+    private boolean urlChanged(Invoker<T> invoker, InstanceAddressURL newURL, ProtocolServiceKey protocolServiceKey) {
         InstanceAddressURL oldURL = (InstanceAddressURL) invoker.getUrl();
 
         if (!newURL.getInstance().equals(oldURL.getInstance())) {
@@ -335,16 +383,35 @@
             }
         }
 
-        MetadataInfo.ServiceInfo oldServiceInfo = oldURL.getMetadataInfo().getValidServiceInfo(getConsumerUrl().getProtocolServiceKey());
+        MetadataInfo.ServiceInfo oldServiceInfo = oldURL.getMetadataInfo().getValidServiceInfo(protocolServiceKey.toString());
         if (null == oldServiceInfo) {
             return false;
         }
 
-        return !oldServiceInfo.equals(newURL.getMetadataInfo().getValidServiceInfo(getConsumerUrl().getProtocolServiceKey()));
+        return !oldServiceInfo.equals(newURL.getMetadataInfo().getValidServiceInfo(protocolServiceKey.toString()));
     }
 
     private List<Invoker<T>> toMergeInvokerList(List<Invoker<T>> invokers) {
-        return invokers;
+        List<Invoker<T>> mergedInvokers = new ArrayList<>();
+        Map<String, List<Invoker<T>>> groupMap = new HashMap<>();
+        for (Invoker<T> invoker : invokers) {
+            String group = invoker.getUrl().getGroup("");
+            groupMap.computeIfAbsent(group, k -> new ArrayList<>());
+            groupMap.get(group).add(invoker);
+        }
+
+        if (groupMap.size() == 1) {
+            mergedInvokers.addAll(groupMap.values().iterator().next());
+        } else if (groupMap.size() > 1) {
+            for (List<Invoker<T>> groupList : groupMap.values()) {
+                StaticDirectory<T> staticDirectory = new StaticDirectory<>(groupList);
+                staticDirectory.buildRouterChain();
+                mergedInvokers.add(cluster.join(staticDirectory, false));
+            }
+        } else {
+            mergedInvokers = invokers;
+        }
+        return mergedInvokers;
     }
 
     /**
@@ -352,7 +419,7 @@
      */
     @Override
     protected void destroyAllInvokers() {
-        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
+        Map<ProtocolServiceKeyWithAddress, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
         if (localUrlInvokerMap != null) {
             for (Invoker<T> invoker : new ArrayList<>(localUrlInvokerMap.values())) {
                 try {
@@ -375,7 +442,7 @@
      * @param oldUrlInvokerMap
      * @param newUrlInvokerMap
      */
-    private void destroyUnusedInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, Map<String, Invoker<T>> newUrlInvokerMap) {
+    private void destroyUnusedInvokers(Map<ProtocolServiceKeyWithAddress, Invoker<T>> oldUrlInvokerMap, Map<ProtocolServiceKeyWithAddress, Invoker<T>> newUrlInvokerMap) {
         if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
             destroyAllInvokers();
             return;
@@ -385,7 +452,7 @@
             return;
         }
 
-        for (Map.Entry<String, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) {
+        for (Map.Entry<ProtocolServiceKeyWithAddress, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) {
             Invoker<T> invoker = entry.getValue();
             if (invoker != null) {
                 try {
@@ -461,4 +528,88 @@
             });
         }
     }
+
+    public static final class ProtocolServiceKeyWithAddress extends ProtocolServiceKey {
+        private final String address;
+
+        public ProtocolServiceKeyWithAddress(ProtocolServiceKey protocolServiceKey, String address) {
+            super(protocolServiceKey.getInterfaceName(), protocolServiceKey.getVersion(), protocolServiceKey.getGroup(), protocolServiceKey.getProtocol());
+            this.address = address;
+        }
+
+        public String getAddress() {
+            return address;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+            ProtocolServiceKeyWithAddress that = (ProtocolServiceKeyWithAddress) o;
+            return Objects.equals(address, that.address);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(), address);
+        }
+    }
+
+    public static final class InstanceWrappedInvoker<T> implements Invoker<T> {
+        private final Invoker<T> originInvoker;
+        private final URL newConsumerUrl;
+        private final ProtocolServiceKey protocolServiceKey;
+
+        public InstanceWrappedInvoker(Invoker<T> originInvoker, URL newConsumerUrl, ProtocolServiceKey protocolServiceKey) {
+            this.originInvoker = originInvoker;
+            this.newConsumerUrl = newConsumerUrl;
+            this.protocolServiceKey = protocolServiceKey;
+        }
+
+        @Override
+        public Class<T> getInterface() {
+            return originInvoker.getInterface();
+        }
+
+        @Override
+        public Result invoke(Invocation invocation) throws RpcException {
+            // override consumer url with real protocol service key
+            RpcContext.getServiceContext().setConsumerUrl(newConsumerUrl);
+            // recreate invocation due to the protocol service key changed
+            RpcInvocation copiedInvocation = new RpcInvocation(invocation.getTargetServiceUniqueName(),
+                invocation.getServiceModel(), invocation.getMethodName(), invocation.getServiceName(), protocolServiceKey.toString(),
+                invocation.getParameterTypes(), invocation.getArguments(), invocation.getObjectAttachments(),
+                invocation.getInvoker(), invocation.getAttributes(),
+                invocation instanceof RpcInvocation ? ((RpcInvocation) invocation).getInvokeMode() : null);
+            copiedInvocation.setObjectAttachment(CommonConstants.GROUP_KEY, protocolServiceKey.getGroup());
+            copiedInvocation.setObjectAttachment(CommonConstants.VERSION_KEY, protocolServiceKey.getVersion());
+            return originInvoker.invoke(copiedInvocation);
+        }
+
+        @Override
+        public URL getUrl() {
+            RpcContext.getServiceContext().setConsumerUrl(newConsumerUrl);
+            return originInvoker.getUrl();
+        }
+
+        @Override
+        public boolean isAvailable() {
+            RpcContext.getServiceContext().setConsumerUrl(newConsumerUrl);
+            return originInvoker.isAvailable();
+        }
+
+        @Override
+        public void destroy() {
+            RpcContext.getServiceContext().setConsumerUrl(newConsumerUrl);
+            originInvoker.destroy();
+        }
+    }
+
 }
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 f58e9ac..8cb3072 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
@@ -16,14 +16,15 @@
  */
 package org.apache.dubbo.registry.client.event.listener;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
 import org.apache.dubbo.registry.NotifyListener;
@@ -33,12 +34,13 @@
 import org.apache.dubbo.registry.client.event.RetryServiceInstancesChangedEvent;
 import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
+import org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ScopeModelUtil;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -52,9 +54,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static java.util.Collections.emptySet;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_REFRESH_ADDRESS;
 import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static org.apache.dubbo.common.constants.RegistryConstants.ENABLE_EMPTY_PROTECTION_KEY;
 import static org.apache.dubbo.metadata.RevisionResolver.EMPTY_REVISION;
@@ -67,7 +68,7 @@
  */
 public class ServiceInstancesChangedListener {
 
-    private static final Logger logger = LoggerFactory.getLogger(ServiceInstancesChangedListener.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceInstancesChangedListener.class);
 
     protected final Set<String> serviceNames;
     protected final ServiceDiscovery serviceDiscovery;
@@ -77,17 +78,15 @@
     protected AtomicBoolean destroyed = new AtomicBoolean(false);
 
     protected Map<String, List<ServiceInstance>> allInstances;
-    protected Map<String, Object> serviceUrls;
+    protected Map<String, List<ProtocolServiceKeyWithUrls>> serviceUrls;
 
     private volatile long lastRefreshTime;
     private final Semaphore retryPermission;
     private volatile ScheduledFuture<?> retryFuture;
     private final ScheduledExecutorService scheduler;
     private volatile boolean hasEmptyMetadata;
+    private final Set<ServiceInstanceNotificationCustomizer> serviceInstanceNotificationCustomizers;
 
-    // protocols subscribe by default, specify the protocol that should be subscribed through 'consumer.protocol'.
-    private static final String[] SUPPORTED_PROTOCOLS = new String[]{"dubbo", "tri", "rest"};
-    public static final String CONSUMER_PROTOCOL_SUFFIX = ":consumer";
 
     public ServiceInstancesChangedListener(Set<String> serviceNames, ServiceDiscovery serviceDiscovery) {
         this.serviceNames = serviceNames;
@@ -96,8 +95,9 @@
         this.allInstances = new HashMap<>();
         this.serviceUrls = new HashMap<>();
         retryPermission = new Semaphore(1);
-        this.scheduler = ScopeModelUtil.getApplicationModel(serviceDiscovery == null || serviceDiscovery.getUrl() == null ? null : serviceDiscovery.getUrl().getScopeModel())
-            .getBeanFactory().getBean(FrameworkExecutorRepository.class).getMetadataRetryExecutor();
+        ApplicationModel applicationModel = ScopeModelUtil.getApplicationModel(serviceDiscovery == null || serviceDiscovery.getUrl() == null ? null : serviceDiscovery.getUrl().getScopeModel());
+        this.scheduler = applicationModel.getBeanFactory().getBean(FrameworkExecutorRepository.class).getMetadataRetryExecutor();
+        this.serviceInstanceNotificationCustomizers = applicationModel.getExtensionLoader(ServiceInstanceNotificationCustomizer.class).getSupportedExtensionInstances();
     }
 
     /**
@@ -127,7 +127,7 @@
         }
 
         Map<String, List<ServiceInstance>> revisionToInstances = new HashMap<>();
-        Map<String, Map<String, Set<String>>> localServiceToRevisions = new HashMap<>();
+        Map<ServiceInfo, Set<String>> localServiceToRevisions = new HashMap<>();
 
         // grouping all instances of this app(service name) by revision
         for (Map.Entry<String, List<ServiceInstance>> entry : allInstances.entrySet()) {
@@ -149,7 +149,14 @@
         for (Map.Entry<String, List<ServiceInstance>> entry : revisionToInstances.entrySet()) {
             String revision = entry.getKey();
             List<ServiceInstance> subInstances = entry.getValue();
-            MetadataInfo metadata = serviceDiscovery.getRemoteMetadata(revision, subInstances);
+
+            MetadataInfo metadata = subInstances.stream()
+                .map(ServiceInstance::getServiceMetadata)
+                .filter(Objects::nonNull)
+                .filter(m -> revision.equals(m.getRevision()))
+                .findFirst()
+                .orElseGet(() -> serviceDiscovery.getRemoteMetadata(revision, subInstances));
+
             parseMetadata(revision, metadata, localServiceToRevisions);
             // update metadata into each instance, in case new instance created.
             for (ServiceInstance tmpInstance : subInstances) {
@@ -175,63 +182,57 @@
                 }
                 logger.warn("Address refresh try task submitted");
             }
+
             // return if all metadata is empty, this notification will not take effect.
             if (emptyNum == revisionToInstances.size()) {
-                logger.error("Address refresh failed because of Metadata Server failure, wait for retry or new address refresh event.");
+                // 1-17 - Address refresh failed.
+                logger.error(REGISTRY_FAILED_REFRESH_ADDRESS, "metadata Server failure", "",
+                    "Address refresh failed because of Metadata Server failure, wait for retry or new address refresh event.");
+
                 return;
             }
         }
         hasEmptyMetadata = false;
 
-        Map<String, Map<Set<String>, Object>> protocolRevisionsToUrls = new HashMap<>();
-        Map<String, Object> newServiceUrls = new HashMap<>();
-        for (Map.Entry<String, Map<String, Set<String>>> entry : localServiceToRevisions.entrySet()) {
-            String protocol = entry.getKey();
-            entry.getValue().forEach((protocolServiceKey, revisions) -> {
-                Map<Set<String>, Object> revisionsToUrls = protocolRevisionsToUrls.computeIfAbsent(protocol, k -> new HashMap<>());
-                Object urls = revisionsToUrls.get(revisions);
-                if (urls == null) {
-                    urls = getServiceUrlsCache(revisionToInstances, revisions, protocol);
-                    revisionsToUrls.put(revisions, urls);
-                }
+        Map<String, Map<Integer, Map<Set<String>, Object>>> protocolRevisionsToUrls = new HashMap<>();
+        Map<String, List<ProtocolServiceKeyWithUrls>> newServiceUrls = new HashMap<>();
+        for (Map.Entry<ServiceInfo, Set<String>> entry : localServiceToRevisions.entrySet()) {
+            ServiceInfo serviceInfo = entry.getKey();
+            Set<String> revisions = entry.getValue();
 
-                newServiceUrls.put(protocolServiceKey, urls);
-            });
+            Map<Integer, Map<Set<String>, Object>> portToRevisions = protocolRevisionsToUrls.computeIfAbsent(serviceInfo.getProtocol(), k -> new HashMap<>());
+            Map<Set<String>, Object> revisionsToUrls = portToRevisions.computeIfAbsent(serviceInfo.getPort(), k -> new HashMap<>());
+            Object urls = revisionsToUrls.get(revisions);
+            if (urls == null) {
+                urls = getServiceUrlsCache(revisionToInstances, revisions, serviceInfo.getProtocol(), serviceInfo.getPort());
+                revisionsToUrls.put(revisions, urls);
+            }
+
+            List<ProtocolServiceKeyWithUrls> list = newServiceUrls.computeIfAbsent(serviceInfo.getPath(), k -> new LinkedList<>());
+            list.add(new ProtocolServiceKeyWithUrls(serviceInfo.getProtocolServiceKey(), (List<URL>) urls));
         }
 
         this.serviceUrls = newServiceUrls;
         this.notifyAddressChanged();
     }
 
-    public synchronized void addListenerAndNotify(String serviceKey, NotifyListener listener) {
+    public synchronized void addListenerAndNotify(URL url, NotifyListener listener) {
         if (destroyed.get()) {
             return;
         }
 
-        Set<NotifyListenerWithKey> notifyListeners = this.listeners.computeIfAbsent(serviceKey, _k -> new ConcurrentHashSet<>());
-        // {@code protocolServiceKeysToConsume} will be specific protocols configured in reference config or default protocols supported by framework.
-        Set<String> protocolServiceKeysToConsume = getProtocolServiceKeyList(serviceKey, listener);
-        // Add current listener to serviceKey set, there will have more than one listener when multiple references of one same service is configured.
-        NotifyListenerWithKey listenerWithKey = new NotifyListenerWithKey(serviceKey, protocolServiceKeysToConsume, listener);
+        Set<NotifyListenerWithKey> notifyListeners = this.listeners.computeIfAbsent(url.getServiceKey(), _k -> new ConcurrentHashSet<>());
+        String protocol = listener.getConsumerUrl().getParameter(PROTOCOL_KEY, url.getProtocol());
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(url.getServiceInterface(), url.getVersion(), url.getGroup(),
+            !CommonConstants.CONSUMER.equals(protocol) ? protocol : null);
+        NotifyListenerWithKey listenerWithKey = new NotifyListenerWithKey(protocolServiceKey, listener);
         notifyListeners.add(listenerWithKey);
 
         // Aggregate address and notify on subscription.
-        List<URL> urls;
-        if (protocolServiceKeysToConsume.size() > 1) {
-            urls = new ArrayList<>();
-            for (String protocolServiceKey : protocolServiceKeysToConsume) {
-                List<URL> urlsOfProtocol = getAddresses(protocolServiceKey, listener.getConsumerUrl());
-                if (CollectionUtils.isNotEmpty(urlsOfProtocol)) {
-                    logger.info(String.format("Found %s urls of protocol service key %s ", urlsOfProtocol.size(), protocolServiceKey));
-                    urls.addAll(urlsOfProtocol);
-                }
-            }
-        } else {
-            urls = getAddresses(protocolServiceKeysToConsume.iterator().next(), listener.getConsumerUrl());
-        }
+        List<URL> urls = getAddresses(protocolServiceKey, listener.getConsumerUrl());
 
         if (CollectionUtils.isNotEmpty(urls)) {
-            logger.info(String.format("Notify serviceKey: %s, listener: %s with %s urls on subscription", serviceKey, listener, urls.size()));
+            logger.info(String.format("Notify serviceKey: %s, listener: %s with %s urls on subscription", protocolServiceKey, listener, urls.size()));
             listener.notify(urls);
         }
     }
@@ -244,9 +245,7 @@
         // synchronized method, no need to use DCL
         Set<NotifyListenerWithKey> notifyListeners = this.listeners.get(serviceKey);
         if (notifyListeners != null) {
-            NotifyListenerWithKey listenerWithKey = new NotifyListenerWithKey(serviceKey, notifyListener);
-            // Remove from global listeners
-            notifyListeners.remove(listenerWithKey);
+            notifyListeners.removeIf(listener -> listener.getNotifyListener().equals(notifyListener));
 
             // ServiceKey has no listener, remove set
             if (notifyListeners.size() == 0) {
@@ -308,6 +307,9 @@
         String appName = event.getServiceName();
         List<ServiceInstance> appInstances = event.getServiceInstances();
         logger.info("Received instance notification, serviceName: " + appName + ", instances: " + appInstances.size());
+        for (ServiceInstanceNotificationCustomizer serviceInstanceNotificationCustomizer : serviceInstanceNotificationCustomizers) {
+            serviceInstanceNotificationCustomizer.customize(appInstances);
+        }
         allInstances.put(appName, appInstances);
         lastRefreshTime = System.currentTimeMillis();
     }
@@ -345,23 +347,28 @@
         return emptyMetadataNum;
     }
 
-    protected Map<String, Map<String, Set<String>>> parseMetadata(String revision, MetadataInfo metadata, Map<String, Map<String, Set<String>>> localServiceToRevisions) {
+    protected Map<ServiceInfo, Set<String>> parseMetadata(String revision, MetadataInfo metadata, Map<ServiceInfo, Set<String>> localServiceToRevisions) {
         Map<String, ServiceInfo> serviceInfos = metadata.getServices();
         for (Map.Entry<String, ServiceInfo> entry : serviceInfos.entrySet()) {
-            String protocol = entry.getValue().getProtocol();
-            String protocolServiceKey = entry.getValue().getMatchKey();
-            Map<String, Set<String>> map = localServiceToRevisions.computeIfAbsent(protocol, _p -> new HashMap<>());
-            Set<String> set = map.computeIfAbsent(protocolServiceKey, _k -> new TreeSet<>());
+            Set<String> set = localServiceToRevisions.computeIfAbsent(entry.getValue(), _k -> new TreeSet<>());
             set.add(revision);
         }
 
         return localServiceToRevisions;
     }
 
-    protected Object getServiceUrlsCache(Map<String, List<ServiceInstance>> revisionToInstances, Set<String> revisions, String protocol) {
+    protected Object getServiceUrlsCache(Map<String, List<ServiceInstance>> revisionToInstances, Set<String> revisions, String protocol, int port) {
         List<URL> urls = new ArrayList<>();
         for (String r : revisions) {
             for (ServiceInstance i : revisionToInstances.get(r)) {
+                if (port > 0) {
+                    if (i.getPort() == port) {
+                        urls.add(i.toURL(protocol).setScopeModel(i.getApplicationModel()));
+                    } else {
+                        urls.add(((DefaultServiceInstance) i).copyFrom(port).toURL(protocol).setScopeModel(i.getApplicationModel()));
+                    }
+                    continue;
+                }
                 // different protocols may have ports specified in meta
                 if (ServiceInstanceMetadataUtils.hasEndpoints(i)) {
                     DefaultServiceInstance.Endpoint endpoint = ServiceInstanceMetadataUtils.getEndpoint(i, protocol);
@@ -376,8 +383,22 @@
         return urls;
     }
 
-    protected List<URL> getAddresses(String serviceProtocolKey, URL consumerURL) {
-        return (List<URL>) serviceUrls.get(serviceProtocolKey);
+    protected List<URL> getAddresses(ProtocolServiceKey protocolServiceKey, URL consumerURL) {
+        List<ProtocolServiceKeyWithUrls> protocolServiceKeyWithUrlsList = serviceUrls.get(protocolServiceKey.getInterfaceName());
+        List<URL> urls = new ArrayList<>();
+        if (protocolServiceKeyWithUrlsList != null) {
+            for (ProtocolServiceKeyWithUrls protocolServiceKeyWithUrls : protocolServiceKeyWithUrlsList) {
+                if (ProtocolServiceKey.Matcher.isMatch(protocolServiceKey, protocolServiceKeyWithUrls.getProtocolServiceKey())) {
+                    urls.addAll(protocolServiceKeyWithUrls.getUrls());
+                }
+            }
+        }
+        if (serviceUrls.containsKey(CommonConstants.ANY_VALUE)) {
+            for (ProtocolServiceKeyWithUrls protocolServiceKeyWithUrls : serviceUrls.get(CommonConstants.ANY_VALUE)) {
+                urls.addAll(protocolServiceKeyWithUrls.getUrls());
+            }
+        }
+        return urls;
     }
 
     /**
@@ -389,28 +410,10 @@
             // 2 multiple subscription listener of the same service
             for (NotifyListenerWithKey listenerWithKey : listenerSet) {
                 NotifyListener notifyListener = listenerWithKey.getNotifyListener();
-                if (listenerWithKey.getProtocolServiceKeys().size() == 1) {// 2.1 if one specific protocol is specified
-                    String protocolServiceKey = listenerWithKey.getProtocolServiceKeys().iterator().next();
-                    //FIXME, group wildcard match
-                    List<URL> urls = toUrlsWithEmpty(getAddresses(protocolServiceKey, notifyListener.getConsumerUrl()));
-                    logger.info("Notify service " + protocolServiceKey + " with urls " + urls.size());
-                    notifyListener.notify(urls);
-                } else {// 2.2 multiple protocols or no protocol(using default protocols) set
-                    List<URL> urls = new ArrayList<>();
-                    int effectiveProtocolNum = 0;
-                    for (String protocolServiceKey : listenerWithKey.getProtocolServiceKeys()) {
-                        List<URL> tmpUrls = getAddresses(protocolServiceKey, notifyListener.getConsumerUrl());
-                        if (CollectionUtils.isNotEmpty(tmpUrls)) {
-                            logger.info("Found  " + urls.size() + " urls of protocol service key " + protocolServiceKey);
-                            effectiveProtocolNum++;
-                            urls.addAll(tmpUrls);
-                        }
-                    }
 
-                    logger.info("Notify service " + serviceKey + " with " + urls.size() + " urls from " + effectiveProtocolNum + " different protocols");
-                    urls = toUrlsWithEmpty(urls);
-                    notifyListener.notify(urls);
-                }
+                List<URL> urls = toUrlsWithEmpty(getAddresses(listenerWithKey.getProtocolServiceKey(), notifyListener.getConsumerUrl()));
+                logger.info("Notify service " + listenerWithKey.getProtocolServiceKey() + " with urls " + urls.size());
+                notifyListener.notify(urls);
             }
         });
     }
@@ -425,9 +428,7 @@
 
         if (CollectionUtils.isEmpty(urls) && !emptyProtectionEnabled) {
             // notice that the service of this.url may not be the same as notify listener.
-            URL empty = URLBuilder.from(this.url)
-                .setProtocol(EMPTY_PROTOCOL)
-                .build();
+            URL empty = URLBuilder.from(this.url).setProtocol(EMPTY_PROTOCOL).build();
             urls.add(empty);
         }
         return urls;
@@ -472,46 +473,6 @@
         return Objects.hash(getClass(), getServiceNames());
     }
 
-    /**
-     * Calculate the protocol list that the consumer cares about.
-     *
-     * @param serviceKey possible input serviceKey includes
-     *                   1. {group}/{interface}:{version}, if protocol is not specified
-     *                   2. {group}/{interface}:{version}:{user specified protocols}
-     * @param listener   listener also contains the user specified protocols
-     * @return protocol list with the format {group}/{interface}:{version}:{protocol}
-     */
-    protected Set<String> getProtocolServiceKeyList(String serviceKey, NotifyListener listener) {
-        if (StringUtils.isEmpty(serviceKey)) {
-            return emptySet();
-        }
-
-        Set<String> result = new HashSet<>();
-        String protocol = listener.getConsumerUrl().getParameter(PROTOCOL_KEY);
-        if (serviceKey.endsWith(CONSUMER_PROTOCOL_SUFFIX)) {
-            serviceKey = serviceKey.substring(0, serviceKey.indexOf(CONSUMER_PROTOCOL_SUFFIX));
-        }
-
-        if (StringUtils.isNotEmpty(protocol)) {
-            int protocolIndex = serviceKey.indexOf(":" + protocol);
-            if (protocol.contains(",") && protocolIndex != -1) {
-                serviceKey = serviceKey.substring(0, protocolIndex);
-                String[] specifiedProtocols = protocol.split(",");
-                for (String specifiedProtocol : specifiedProtocols) {
-                    result.add(serviceKey + GROUP_CHAR_SEPARATOR + specifiedProtocol);
-                }
-            } else {
-                result.add(serviceKey);
-            }
-        } else {
-            for (String supportedProtocol : SUPPORTED_PROTOCOLS) {
-                result.add(serviceKey + GROUP_CHAR_SEPARATOR + supportedProtocol);
-            }
-        }
-
-        return result;
-    }
-
     protected class AddressRefreshRetryTask implements Runnable {
         private final RetryServiceInstancesChangedEvent retryEvent;
         private final Semaphore retryPermission;
@@ -529,26 +490,16 @@
     }
 
     public static class NotifyListenerWithKey {
-        private final String serviceKey;
-        private final Set<String> protocolServiceKeys;
+        private final ProtocolServiceKey protocolServiceKey;
         private final NotifyListener notifyListener;
 
-        public NotifyListenerWithKey(String protocolServiceKey, Set<String> protocolServiceKeys, NotifyListener notifyListener) {
-            this.serviceKey = protocolServiceKey;
-            this.protocolServiceKeys = (protocolServiceKeys == null ? new ConcurrentHashSet<>() : protocolServiceKeys);
+        public NotifyListenerWithKey(ProtocolServiceKey protocolServiceKey, NotifyListener notifyListener) {
+            this.protocolServiceKey = protocolServiceKey;
             this.notifyListener = notifyListener;
         }
 
-        public NotifyListenerWithKey(String protocolServiceKey, NotifyListener notifyListener) {
-            this(protocolServiceKey, null, notifyListener);
-        }
-
-        public String getServiceKey() {
-            return serviceKey;
-        }
-
-        public Set<String> getProtocolServiceKeys() {
-            return protocolServiceKeys;
+        public ProtocolServiceKey getProtocolServiceKey() {
+            return protocolServiceKey;
         }
 
         public NotifyListener getNotifyListener() {
@@ -564,12 +515,30 @@
                 return false;
             }
             NotifyListenerWithKey that = (NotifyListenerWithKey) o;
-            return Objects.equals(serviceKey, that.serviceKey) && Objects.equals(notifyListener, that.notifyListener);
+            return Objects.equals(protocolServiceKey, that.protocolServiceKey) && Objects.equals(notifyListener, that.notifyListener);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(serviceKey, notifyListener);
+            return Objects.hash(protocolServiceKey, notifyListener);
+        }
+    }
+
+    public static class ProtocolServiceKeyWithUrls {
+        private final ProtocolServiceKey protocolServiceKey;
+        private final List<URL> urls;
+
+        public ProtocolServiceKeyWithUrls(ProtocolServiceKey protocolServiceKey, List<URL> urls) {
+            this.protocolServiceKey = protocolServiceKey;
+            this.urls = urls;
+        }
+
+        public ProtocolServiceKey getProtocolServiceKey() {
+            return protocolServiceKey;
+        }
+
+        public List<URL> getUrls() {
+            return urls;
         }
     }
 }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceDelegation.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceDelegation.java
similarity index 97%
rename from dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceDelegation.java
rename to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceDelegation.java
index 451bf75..5cd7556 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/MetadataServiceDelegation.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceDelegation.java
@@ -14,10 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.metadata;
+package org.apache.dubbo.registry.client.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.resource.Disposable;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -50,7 +50,8 @@
  * Implementation providing remote RPC service to facilitate the query of metadata information.
  */
 public class MetadataServiceDelegation implements MetadataService, Disposable {
-    Logger logger = LoggerFactory.getLogger(getClass());
+    ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
+
     private final ApplicationModel applicationModel;
     private final RegistryManager registryManager;
     private ConcurrentMap<String, InstanceMetadataChangedListener> instanceMetadataChangedListenerMap = new ConcurrentHashMap<>();
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 2850faa..d8cf528 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
@@ -18,7 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -40,7 +40,7 @@
 
 public class MetadataServiceNameMapping extends AbstractServiceNameMapping {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     private static final List<String> IGNORED_SERVICE_INTERFACES = Collections.singletonList(MetadataService.class.getName());
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceHostPortCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceHostPortCustomizer.java
index 3611a62..f5786c2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceHostPortCustomizer.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceHostPortCustomizer.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.metadata.MetadataInfo;
@@ -31,11 +31,13 @@
 import java.util.Set;
 import java.util.SortedSet;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER;
+
 /**
  * The {@link ServiceInstanceCustomizer} to customize the {@link ServiceInstance#getPort() port} of service instance.
  */
 public class ServiceInstanceHostPortCustomizer implements ServiceInstanceCustomizer {
-    private static final Logger logger = LoggerFactory.getLogger(ServiceInstanceHostPortCustomizer.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceInstanceHostPortCustomizer.class);
     
 
     @Override
@@ -71,7 +73,14 @@
                 }
                 
                 if (host == null || port == -1) {
-                    logger.warn("The default preferredProtocol \"" + preferredProtocol + "\" is not found, fall back to the strategy that pick the first found protocol. Please try to modify the config of dubbo.application.protocol");
+
+                    // 4-2 - Can't find an instance URL using the default preferredProtocol.
+
+                    logger.warn(PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER, "typo in preferred protocol", "",
+                        "Can't find an instance URL using the default preferredProtocol \"" + preferredProtocol + "\", " +
+                        "falling back to the strategy that pick the first found protocol. " +
+                        "Please try modifying the config of dubbo.application.protocol");
+
                     URL url = urls.iterator().next();
                     host = url.getHost();
                     port = url.getPort();
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
index 702e636..0803445 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
@@ -18,10 +18,11 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.DefaultServiceInstance.Endpoint;
@@ -35,6 +36,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
@@ -56,7 +58,7 @@
  * @since 2.7.5
  */
 public class ServiceInstanceMetadataUtils {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceInstanceMetadataUtils.class);
+    private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(ServiceInstanceMetadataUtils.class);
 
     /**
      * The prefix of {@link MetadataService} : "dubbo.metadata-service."
@@ -118,8 +120,10 @@
      * @return <code>null</code> if not exits
      */
     public static String getExportedServicesRevision(ServiceInstance serviceInstance) {
-        Map<String, String> metadata = serviceInstance.getMetadata();
-        return metadata.get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME);
+        return Optional.ofNullable(serviceInstance.getServiceMetadata())
+            .map(MetadataInfo::getRevision)
+            .filter(StringUtils::isNotEmpty)
+            .orElse(serviceInstance.getMetadata(EXPORTED_SERVICES_REVISION_PROPERTY_NAME));
     }
 
     /**
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceNotificationCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceNotificationCustomizer.java
new file mode 100644
index 0000000..9a60999
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceNotificationCustomizer.java
@@ -0,0 +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.client.metadata;
+
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.registry.client.ServiceInstance;
+
+import java.util.List;
+
+@SPI
+public interface ServiceInstanceNotificationCustomizer {
+
+    void customize(List<ServiceInstance> serviceInstance);
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SpringCloudServiceInstanceNotificationCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SpringCloudServiceInstanceNotificationCustomizer.java
new file mode 100644
index 0000000..c50dc36
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SpringCloudServiceInstanceNotificationCustomizer.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client.metadata;
+
+import org.apache.dubbo.common.ProtocolServiceKey;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.registry.client.ServiceInstance;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SpringCloudServiceInstanceNotificationCustomizer implements ServiceInstanceNotificationCustomizer {
+    @Override
+    public void customize(List<ServiceInstance> serviceInstance) {
+        for (ServiceInstance instance : serviceInstance) {
+            if ("SPRING_CLOUD".equals(instance.getMetadata("preserved.register.source"))) {
+                MetadataInfo.ServiceInfo serviceInfo = new MetadataInfo.ServiceInfo("*", "*", "*", "rest", instance.getPort(), "*", new HashMap<>());
+                MetadataInfo metadataInfo = new MetadataInfo(instance.getServiceName(), "SPRING_CLOUD", new ConcurrentHashMap<>(Collections.singletonMap("*", serviceInfo))) {
+                    @Override
+                    public List<ServiceInfo> getMatchedServiceInfos(ProtocolServiceKey consumerProtocolServiceKey) {
+                        getServices().putIfAbsent(consumerProtocolServiceKey.getServiceKeyString(),
+                            new MetadataInfo.ServiceInfo(consumerProtocolServiceKey.getInterfaceName(),
+                                consumerProtocolServiceKey.getGroup(), consumerProtocolServiceKey.getVersion(),
+                                consumerProtocolServiceKey.getProtocol(), instance.getPort(), consumerProtocolServiceKey.getInterfaceName(), new HashMap<>()));
+                        return super.getMatchedServiceInfos(consumerProtocolServiceKey);
+                    }
+                };
+
+
+                instance.setServiceMetadata(metadataInfo);
+            }
+        }
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
index f2cc886..b36139a 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.metadata.MetadataService;
@@ -44,6 +44,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.THREADS_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.common.constants.LoggerCodeConstants.REGISTRY_MISSING_METADATA_CONFIG_PORT;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_TIMEOUT_VALUE;
 import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
@@ -58,7 +59,7 @@
  */
 public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuilder {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     public static final String NAME = "standard";
 
@@ -111,8 +112,7 @@
             .addParameter(THREADPOOL_KEY, "cached")
             .addParameter(THREADS_KEY, "100")
             .addParameter(CORE_THREADS_KEY, "2")
-                .addParameter(RETRIES_KEY, 0);
-
+            .addParameter(RETRIES_KEY, 0);
 
         // add parameters
         params.forEach(urlBuilder::addParameter);
@@ -125,17 +125,31 @@
     private URL generateUrlWithoutMetadata(String serviceName, String host, Integer instancePort) {
         Integer port = metadataServicePort;
         if (port == null || port < 1) {
-            logger.warn("Metadata Service Port is not provided, since DNS is not able to negotiate the metadata port " +
-                    "between Provider and Consumer, will try to use instance port as the default metadata port.");
+
+            // 1-18 - Metadata Service Port should be specified for consumer.
+
+            logger.warn(REGISTRY_MISSING_METADATA_CONFIG_PORT, "missing configuration of metadata service port", "",
+                "Metadata Service Port is not provided. Since DNS is not able to negotiate the metadata port " +
+                    "between Provider and Consumer, Dubbo will try using instance port as the default metadata port.");
+
             port = instancePort;
         }
 
         if (port == null || port < 1) {
+
+            // 1-18 - Metadata Service Port should be specified for consumer.
+
             String message = "Metadata Service Port should be specified for consumer. " +
                     "Please set dubbo.application.metadataServicePort and " +
                     "make sure it has been set on provider side. " +
                     "ServiceName: " + serviceName + " Host: " + host;
-            throw new IllegalStateException(message);
+
+            IllegalStateException illegalStateException = new IllegalStateException(message);
+
+            logger.error(REGISTRY_MISSING_METADATA_CONFIG_PORT, "missing configuration of metadata service port", "",
+                message, illegalStateException);
+
+            throw illegalStateException;
         }
 
         URLBuilder urlBuilder = new URLBuilder()
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 331053f..0c3d90a 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client.migration;
 
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -30,7 +30,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 public class DefaultMigrationAddressComparator implements MigrationAddressComparator {
-    private static final Logger logger = LoggerFactory.getLogger(DefaultMigrationAddressComparator.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DefaultMigrationAddressComparator.class);
     private static final String MIGRATION_THRESHOLD = "dubbo.application.migration.threshold";
     private static final String DEFAULT_THRESHOLD_STRING = "0.0";
     private static final float DEFAULT_THREAD = 0f;
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 8f41a30..fc525e9 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
@@ -255,7 +255,7 @@
             try {
                 Thread.sleep(delay * 1000L);
             } catch (InterruptedException e) {
-                logger.error("Interrupter when waiting for address notify!" + e);
+                logger.error("Interrupted when waiting for address notify!" + e);
             }
         } else {
             // do not wait address notify by default
@@ -264,7 +264,7 @@
         try {
             latch.await(delay, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
-            logger.error("Interrupter when waiting for address notify!" + e);
+            logger.error("Interrupted when waiting for address notify!" + e);
         }
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java
index 6d99dae..6ce0f27 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleHandler.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client.migration;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.status.reporter.FrameworkStatusReportService;
 import org.apache.dubbo.registry.client.migration.model.MigrationRule;
@@ -25,7 +25,7 @@
 
 public class MigrationRuleHandler<T> {
     public static final String DUBBO_SERVICEDISCOVERY_MIGRATION = "dubbo.application.migration.step";
-    private static final Logger logger = LoggerFactory.getLogger(MigrationRuleHandler.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MigrationRuleHandler.class);
 
     private MigrationClusterInvoker<T> migrationInvoker;
     private MigrationStep currentStep;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
index c7cbb03..9623e8a 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java
@@ -22,7 +22,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -63,7 +63,7 @@
  */
 @Activate
 public class MigrationRuleListener implements RegistryProtocolListener, ConfigurationListener {
-    private static final Logger logger = LoggerFactory.getLogger(MigrationRuleListener.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MigrationRuleListener.class);
     private static final String DUBBO_SERVICEDISCOVERY_MIGRATION = "DUBBO_SERVICEDISCOVERY_MIGRATION";
     private static final String MIGRATION_DELAY_KEY = "dubbo.application.migration.delay";
     private static final int MIGRATION_DEFAULT_DELAY_TIME = 60000;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java
index 572aa22..b48c79c 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/ServiceDiscoveryMigrationInvoker.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.client.migration;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.migration.model.MigrationRule;
@@ -31,7 +31,7 @@
 import java.util.concurrent.CountDownLatch;
 
 public class ServiceDiscoveryMigrationInvoker<T> extends MigrationInvoker<T> {
-    private static final Logger logger = LoggerFactory.getLogger(ServiceDiscoveryMigrationInvoker.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceDiscoveryMigrationInvoker.class);
 
     public ServiceDiscoveryMigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, Registry registry, Class<T> type, URL url, URL consumerUrl) {
         super(registryProtocol, cluster, registry, type, url, consumerUrl);
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
index 074dc1c..75dbd7d 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
@@ -21,7 +21,7 @@
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.cluster.Configurator;
@@ -35,6 +35,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_PARSE_DYNAMIC_CONFIG;
 import static org.apache.dubbo.rpc.Constants.ACCESS_LOG_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
@@ -45,7 +46,7 @@
  * AbstractConfiguratorListener
  */
 public abstract class AbstractConfiguratorListener implements ConfigurationListener {
-    private static final Logger logger = LoggerFactory.getLogger(AbstractConfiguratorListener.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractConfiguratorListener.class);
 
     protected List<Configurator> configurators = Collections.emptyList();
     protected GovernanceRuleRepository ruleRepository;
@@ -107,8 +108,12 @@
             // parseConfigurators will recognize app/service config automatically.
             urls = ConfigParser.parseConfigurators(rawConfig);
         } catch (Exception e) {
-            logger.warn("Failed to parse raw dynamic config and it will not take effect, the raw config is: "
+            // 1-14 - Failed to parse raw dynamic config.
+
+            logger.warn(REGISTRY_FAILED_PARSE_DYNAMIC_CONFIG, "", "",
+                "Failed to parse raw dynamic config and it will not take effect, the raw config is: "
                     + rawConfig + ", cause: " + e.getMessage());
+
             return false;
         }
         List<URL> safeUrls = urls.stream()
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 bd78c53..167cdd2 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.Version;
 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.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -46,6 +46,9 @@
 import java.util.List;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_DESTROY_SERVICE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_DESTROY_UNREGISTER_URL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_SITE_SELECTION;
 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_KEY;
@@ -59,7 +62,7 @@
  */
 public abstract class DynamicDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
 
-    private static final Logger logger = LoggerFactory.getLogger(DynamicDirectory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DynamicDirectory.class);
 
     protected final Cluster cluster;
 
@@ -204,7 +207,10 @@
             List<Invoker<T>> result = routerChain.route(getConsumerUrl(), invokers, invocation);
             return result == null ? BitList.emptyList() : result;
         } catch (Throwable t) {
-            logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
+            // 2-1 - Failed to execute routing.
+            logger.error(CLUSTER_FAILED_SITE_SELECTION, "", "",
+                "Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
+
             return BitList.emptyList();
         }
     }
@@ -295,15 +301,20 @@
                 registry.unregister(getRegisteredConsumerUrl());
             }
         } catch (Throwable t) {
-            logger.warn("unexpected error when unregister service " + serviceKey + " from registry: " + registry.getUrl(), t);
+            // 1-8: Failed to unregister / unsubscribe url on destroy.
+            logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, "", "",
+                "unexpected error when unregister service " + serviceKey + " from registry: " + registry.getUrl(), t);
         }
+
         // unsubscribe.
         try {
             if (getSubscribeUrl() != null && registry != null && registry.isAvailable()) {
                 registry.unsubscribe(getSubscribeUrl(), this);
             }
         } catch (Throwable t) {
-            logger.warn("unexpected error when unsubscribe service " + serviceKey + " from registry: " + registry.getUrl(), t);
+            // 1-8: Failed to unregister / unsubscribe url on destroy.
+            logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, "", "",
+                "unexpected error when unsubscribe service " + serviceKey + " from registry: " + registry.getUrl(), t);
         }
 
         ExtensionLoader<AddressListener> addressListenerExtensionLoader = getUrl().getOrDefaultModuleModel().getExtensionLoader(AddressListener.class);
@@ -318,7 +329,9 @@
             try {
                 destroyAllInvokers();
             } catch (Throwable t) {
-                logger.warn("Failed to destroy service " + serviceKey, t);
+                // 1-15 - Failed to destroy service.
+                logger.warn(REGISTRY_FAILED_DESTROY_SERVICE, "", "",
+                    "Failed to destroy service " + serviceKey, t);
             }
             routerChain.destroy();
             invokersChangedListener = null;
@@ -333,7 +346,9 @@
         try {
             destroyAllInvokers();
         } catch (Throwable t) {
-            logger.warn("Failed to destroy service " + serviceKey, t);
+            // 1-15 - Failed to destroy service.
+            logger.warn(REGISTRY_FAILED_DESTROY_SERVICE, "", "",
+                "Failed to destroy service " + serviceKey, t);
         }
     }
 
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 d169880..25389e3 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.URLBuilder;
 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.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
 import org.apache.dubbo.common.url.component.ServiceAddressURL;
@@ -34,6 +34,7 @@
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.Configurator;
 import org.apache.dubbo.rpc.cluster.Router;
 import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
@@ -61,6 +62,13 @@
 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.LoggerCodeConstants.REGISTRY_EMPTY_ADDRESS;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_DESTROY_SERVICE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_UNSUPPORTED_CATEGORY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_FAILED_CONVERT_URL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNSUPPORTED;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_REFER_INVOKER;
 import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CONFIG_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY;
@@ -81,7 +89,7 @@
  * RegistryDirectory
  */
 public class RegistryDirectory<T> extends DynamicDirectory<T> {
-    private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RegistryDirectory.class);
 
     private final ConsumerConfigurationListener consumerConfigurationListener;
     private ReferenceConfigurationListener referenceConfigurationListener;
@@ -224,8 +232,13 @@
             // use local reference to avoid NPE as this.cachedInvokerUrls will be set null by destroyAllInvokers().
             Set<URL> localCachedInvokerUrls = this.cachedInvokerUrls;
             if (invokerUrls.isEmpty() && localCachedInvokerUrls != null) {
-                logger.warn("Service" + serviceKey + " received empty address list with no EMPTY protocol set, trigger empty protection.");
+
+                // 1-4 Empty address.
+                logger.warn(REGISTRY_EMPTY_ADDRESS, "configuration ", "",
+                    "Service" + serviceKey + " received empty address list with no EMPTY protocol set, trigger empty protection.");
+
                 invokerUrls.addAll(localCachedInvokerUrls);
+
             } else {
                 localCachedInvokerUrls = new HashSet<>();
                 localCachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
@@ -246,7 +259,7 @@
             }
             Map<URL, Invoker<T>> newUrlInvokerMap = toInvokers(oldUrlInvokerMap, invokerUrls);// Translate url list to Invoker map
 
-            /**
+            /*
              * If the calculation is wrong, it is not processed.
              *
              * 1. The protocol configured by the client is inconsistent with the protocol of the server.
@@ -255,8 +268,16 @@
              *
              */
             if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
-                logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
-                    .toString()));
+
+                // 3-1 - Failed to convert the URL address into Invokers.
+
+                logger.error(
+                    PROXY_FAILED_CONVERT_URL, "inconsistency between the client protocol and the protocol of the server",
+                    "", "urls to invokers error",
+                    new IllegalStateException(
+                        "urls to invokers error. invokerUrls.size :" +
+                            invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
+
                 return;
             }
 
@@ -333,7 +354,7 @@
     }
 
     /**
-     * Turn urls into invokers, and if url has been refer, will not re-reference.
+     * Turn urls into invokers, and if url has been referred, will not re-reference.
      * the items that will be put into newUrlInvokeMap will be removed from oldUrlInvokerMap.
      *
      * @param oldUrlInvokerMap it might be modified during the process.
@@ -347,33 +368,15 @@
         }
         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
-            if (queryProtocols != null && queryProtocols.length() > 0) {
-                boolean accept = false;
-                String[] acceptProtocols = queryProtocols.split(",");
-                for (String acceptProtocol : acceptProtocols) {
-                    if (providerUrl.getProtocol().equals(acceptProtocol)) {
-                        accept = true;
-                        break;
-                    }
-                }
-                if (!accept) {
-                    continue;
-                }
-            }
-            if (EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
+            if (!checkProtocolValid(queryProtocols, providerUrl)) {
                 continue;
             }
-            if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
-                logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +
-                    " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() +
-                    " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
-                    getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).getSupportedExtensions()));
-                continue;
-            }
+
             URL url = mergeUrl(providerUrl);
 
-            // 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
+            // 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
             Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.remove(url);
             if (invoker == null) { // Not in the cache, refer again
                 try {
@@ -387,7 +390,18 @@
                         invoker = protocol.refer(serviceType, url);
                     }
                 } catch (Throwable t) {
-                    logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
+
+                    // Thrown by AbstractProtocol.optimizeSerialization()
+                    if (t instanceof RpcException && t.getMessage().contains("serialization optimizer")) {
+                        // 4-2 - serialization optimizer class initialization failed.
+                        logger.error(PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER, "typo in optimizer class", "",
+                            "Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
+
+                    } else {
+                        // 4-3 - Failed to refer invoker by other reason.
+                        logger.error(PROTOCOL_FAILED_REFER_INVOKER, "", "",
+                            "Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
+                    }
                 }
                 if (invoker != null) { // Put new invoker in cache
                     newUrlInvokerMap.put(url, invoker);
@@ -399,6 +413,44 @@
         return newUrlInvokerMap;
     }
 
+    private boolean checkProtocolValid(String queryProtocols, URL providerUrl) {
+        // If protocol is configured at the reference side, only the matching protocol is selected
+        if (queryProtocols != null && queryProtocols.length() > 0) {
+            boolean accept = false;
+
+            String[] acceptProtocols = queryProtocols.split(",");
+            for (String acceptProtocol : acceptProtocols) {
+                if (providerUrl.getProtocol().equals(acceptProtocol)) {
+                    accept = true;
+                    break;
+                }
+            }
+
+            if (!accept) {
+                return false;
+            }
+        }
+
+        if (EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
+            return false;
+        }
+
+        if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
+
+            // 4-1 - Unsupported protocol
+
+            logger.error(PROTOCOL_UNSUPPORTED, "protocol extension does not installed", "", "Unsupported protocol.",
+                new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +
+                " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() +
+                " to consumer " + NetUtils.getLocalHost() + ", supported protocol: " +
+                getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).getSupportedExtensions()));
+
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Merge url parameters. the order is: override > -D >Consumer > Provider
      *
@@ -494,7 +546,9 @@
                 try {
                     invoker.destroyAll();
                 } catch (Throwable t) {
-                    logger.warn("Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t);
+                    // 1-15 - Failed to destroy service
+                    logger.warn(REGISTRY_FAILED_DESTROY_SERVICE, "", "",
+                        "Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t);
                 }
             }
             localUrlInvokerMap.clear();
@@ -547,8 +601,12 @@
             APP_DYNAMIC_CONFIGURATORS_CATEGORY.equals(category)) {
             return true;
         }
-        logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " +
+
+        // 1-16 - Unsupported category in NotifyListener
+        logger.warn(REGISTRY_UNSUPPORTED_CATEGORY, "", "",
+            "Unsupported category " + category + " in notified url: " + url + " from registry " +
             getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
+
         return false;
     }
 
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 85db0e7..05387f5 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
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.timer.HashedWheelTimer;
@@ -78,6 +78,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.HIDE_KEY_PREFIX;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.IPV6_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
@@ -136,21 +137,21 @@
     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, DUBBO_VERSION_KEY, RELEASE_KEY, SIDE_KEY
+        WEIGHT_KEY, DUBBO_VERSION_KEY, RELEASE_KEY, SIDE_KEY, IPV6_KEY
     };
 
     public static final String[] DEFAULT_REGISTER_CONSUMER_KEYS = {
         APPLICATION_KEY, VERSION_KEY, GROUP_KEY, DUBBO_VERSION_KEY, RELEASE_KEY
     };
 
-    private final static Logger logger = LoggerFactory.getLogger(RegistryProtocol.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RegistryProtocol.class);
+
     private final Map<String, ServiceConfigurationListener> serviceConfigurationListeners = new ConcurrentHashMap<>();
     //To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed.
     //provider url <--> exporter
     private final ConcurrentMap<String, ExporterChangeableWrapper<?>> bounds = new ConcurrentHashMap<>();
     protected Protocol protocol;
     protected ProxyFactory proxyFactory;
-    //protected RegistryFactory registryFactory;
 
     private ConcurrentMap<URL, ReExportTask> reExportFailedTasks = new ConcurrentHashMap<>();
     private HashedWheelTimer retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboReexportTimer", true), DEFAULT_REGISTRY_RETRY_PERIOD, TimeUnit.MILLISECONDS, 128);
@@ -180,11 +181,6 @@
         this.protocol = protocol;
     }
 
-    // Cannot inject registryFactory (application scope) into protocol (framework scope)
-//    public void setRegistryFactory(RegistryFactory registryFactory) {
-//        this.registryFactory = registryFactory;
-//    }
-
     public void setProxyFactory(ProxyFactory proxyFactory) {
         this.proxyFactory = proxyFactory;
     }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/retry/AbstractRetryTask.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/retry/AbstractRetryTask.java
index de790d8..425430b 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/retry/AbstractRetryTask.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/retry/AbstractRetryTask.java
@@ -18,7 +18,7 @@
 package org.apache.dubbo.registry.retry;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.timer.Timeout;
 import org.apache.dubbo.common.timer.Timer;
@@ -28,6 +28,7 @@
 
 import java.util.concurrent.TimeUnit;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_EXECUTE_RETRYING_TASK;
 import static org.apache.dubbo.registry.Constants.DEFAULT_REGISTRY_RETRY_PERIOD;
 import static org.apache.dubbo.registry.Constants.DEFAULT_REGISTRY_RETRY_TIMES;
 import static org.apache.dubbo.registry.Constants.REGISTRY_RETRY_PERIOD_KEY;
@@ -38,7 +39,7 @@
  */
 public abstract class AbstractRetryTask implements TimerTask {
 
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     /**
      * url for retry task
@@ -113,8 +114,12 @@
             return;
         }
         if (times > retryTimes) {
-            // reach the most times of retry.
-            logger.warn("Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
+            // 1-13 - failed to execute the retrying task.
+
+            logger.warn(
+                REGISTRY_EXECUTE_RETRYING_TASK, "registry center offline", "Check the registry server.",
+                "Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
+
             return;
         }
         if (logger.isInfoEnabled()) {
@@ -123,7 +128,12 @@
         try {
             doRetry(url, registry, timeout);
         } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
-            logger.warn("Failed to execute task " + taskName + ", url: " + url + ", waiting for again, cause:" + t.getMessage(), t);
+
+            // 1-13 - failed to execute the retrying task.
+
+            logger.warn(REGISTRY_EXECUTE_RETRYING_TASK, "registry center offline", "Check the registry server.",
+                "Failed to execute task " + taskName + ", url: " + url + ", waiting for again, cause:" + t.getMessage(), t);
+
             // reput this task when catch exception.
             reput(timeout, retryPeriod);
         }
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 516a974..01a3e3e 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
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.utils.CollectionUtils;
@@ -40,6 +40,7 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -60,6 +61,11 @@
 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.LoggerCodeConstants.REGISTRY_EMPTY_ADDRESS;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_NOTIFY_EVENT;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_DESTROY_UNREGISTER_URL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_READ_WRITE_CACHE_FILE;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_DELETE_LOCKFILE;
 import static org.apache.dubbo.common.constants.RegistryConstants.ACCEPTS_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
@@ -70,7 +76,10 @@
 import static org.apache.dubbo.registry.Constants.USER_HOME;
 
 /**
- * AbstractRegistry. (SPI, Prototype, ThreadSafe)
+ * <p>
+ * Provides a fail-safe registry service backed by cache file. The consumer/provider can still find each other when registry center crashed.
+ *
+ * (SPI, Prototype, ThreadSafe)
  */
 public abstract class AbstractRegistry implements Registry {
 
@@ -84,7 +93,8 @@
     private static final long DEFAULT_INTERVAL_SAVE_PROPERTIES = 500L;
 
     // Log output
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
+
     // Local disk cache, where the special key value.registries records the list of registry centers, and the others are the list of notified service providers
     private final Properties properties = new Properties();
     // File cache timing writing
@@ -103,7 +113,9 @@
     protected RegistryManager registryManager;
     protected ApplicationModel applicationModel;
 
-    public AbstractRegistry(URL url) {
+    private static final String CAUSE_MULTI_DUBBO_USING_SAME_FILE = "multiple Dubbo instance are using the same file";
+
+    protected AbstractRegistry(URL url) {
         setUrl(url);
         registryManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
         localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
@@ -112,19 +124,36 @@
         if (localCacheEnabled) {
             // Start file save timer
             syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
+
             String defaultFilename = System.getProperty(USER_HOME) + DUBBO_REGISTRY + url.getApplication() +
                 "-" + url.getAddress().replaceAll(":", "-") + CACHE;
+
             String filename = url.getParameter(FILE_KEY, defaultFilename);
             File file = null;
+
             if (ConfigUtils.isNotEmpty(filename)) {
                 file = new File(filename);
                 if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
                     if (!file.getParentFile().mkdirs()) {
-                        throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
+
+                        IllegalArgumentException illegalArgumentException = new IllegalArgumentException(
+                            "Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
+
+                        if (logger != null) {
+                            // 1-9 failed to read / save registry cache file.
+
+                            logger.error(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, "cache directory inaccessible",
+                                "Try adjusting permission of the directory.",
+                                "failed to create directory", illegalArgumentException);
+                        }
+
+                        throw illegalArgumentException;
                     }
                 }
             }
+
             this.file = file;
+
             // When starting the subscription center,
             // we need to read the local cache file for future Registry fault tolerance processing.
             loadProperties();
@@ -191,13 +220,22 @@
             if (!lockfile.exists()) {
                 lockfile.createNewFile();
             }
+
             try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
-                 FileChannel channel = raf.getChannel()) {
+                FileChannel channel = raf.getChannel()) {
                 FileLock lock = channel.tryLock();
                 if (lock == null) {
-                    throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", " +
+
+                    IOException ioException = new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", " +
                         "ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");
+
+                    // 1-9 failed to read / save registry cache file.
+                    logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                        "Adjust dubbo.registry.file.", ioException);
+
+                    throw ioException;
                 }
+
                 // Save
                 try {
                     if (!file.exists()) {
@@ -228,29 +266,39 @@
             }
         } catch (Throwable e) {
             savePropertiesRetryTimes.incrementAndGet();
+
             if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) {
                 if (e instanceof OverlappingFileLockException) {
                     // fix #9341, ignore OverlappingFileLockException
                     logger.info("Failed to save registry cache file for file overlapping lock exception, file name " + file.getName());
                 } else {
-                    logger.warn("Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e);
+                    // 1-9 failed to read / save registry cache file.
+                    logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                        "Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e);
                 }
+
                 savePropertiesRetryTimes.set(0);
                 return;
             }
+
             if (version < lastCacheChanged.get()) {
                 savePropertiesRetryTimes.set(0);
                 return;
             } else {
                 registryCacheExecutor.schedule(() -> doSaveProperties(lastCacheChanged.incrementAndGet()), DEFAULT_INTERVAL_SAVE_PROPERTIES, TimeUnit.MILLISECONDS);
             }
+
             if (!(e instanceof OverlappingFileLockException)) {
-                logger.warn("Failed to save registry cache file, will retry, cause: " + e.getMessage(), e);
+                logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE,
+                    "However, the retrying count limit is not exceeded. Dubbo will still try.",
+                    "Failed to save registry cache file, will retry, cause: " + e.getMessage(), e);
             }
         } finally {
             if (lockfile != null) {
                 if (!lockfile.delete()) {
-                    logger.warn(String.format("Failed to delete lock file [%s]", lockfile.getName()));
+                    // 1-10 Failed to delete lock file.
+                    logger.warn(REGISTRY_FAILED_DELETE_LOCKFILE, "", "",
+                        String.format("Failed to delete lock file [%s]", lockfile.getName()));
                 }
             }
         }
@@ -266,13 +314,24 @@
                 logger.info("Loaded registry cache file " + file);
             }
         } catch (IOException e) {
-            logger.warn(e.getMessage(), e);
+            // 1-9 failed to read / save registry cache file.
+            logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                e.getMessage(), e);
+
         } catch (Throwable e) {
-            logger.warn("Failed to load registry cache file " + file, e);
+            // 1-9 failed to read / save registry cache file.
+            logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                "Failed to load registry cache file " + file, e);
         }
     }
 
     public List<URL> getCacheUrls(URL url) {
+        Map<String, List<URL>> categoryNotified = notified.get(url);
+        if (CollectionUtils.isNotEmptyMap(categoryNotified)) {
+            List<URL> urls = categoryNotified.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+            return urls;
+        }
+
         for (Map.Entry<Object, Object> entry : properties.entrySet()) {
             String key = (String) entry.getKey();
             String value = (String) entry.getValue();
@@ -422,7 +481,9 @@
                     try {
                         notify(url, listener, filterEmpty(url, urls));
                     } catch (Throwable t) {
-                        logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
+                        // 1-7: Failed to notify registry event.
+                        logger.error(REGISTRY_FAILED_NOTIFY_EVENT, "consumer is offline", "",
+                            "Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
                     }
                 }
             }
@@ -430,7 +491,7 @@
     }
 
     /**
-     * Notify changes from the Provider side.
+     * Notify changes from the provider side.
      *
      * @param url      consumer side url
      * @param listener listener
@@ -444,7 +505,8 @@
             throw new IllegalArgumentException("notify listener == null");
         }
         if ((CollectionUtils.isEmpty(urls)) && !ANY_VALUE.equals(url.getServiceInterface())) {
-            logger.warn("Ignore empty notify urls for subscribe url " + url);
+            // 1-4 Empty address.
+            logger.warn(REGISTRY_EMPTY_ADDRESS, "", "", "Ignore empty notify urls for subscribe url " + url);
             return;
         }
         if (logger.isInfoEnabled()) {
@@ -468,6 +530,7 @@
             List<URL> categoryList = entry.getValue();
             categoryNotified.put(category, categoryList);
             listener.notify(categoryList);
+
             // We will update our cache file after each notification.
             // When our Registry has a subscribed failure due to network jitter, we can return at least the existing cache URL.
             if (localCacheEnabled) {
@@ -521,7 +584,9 @@
                             logger.info("Destroy unregister url " + url);
                         }
                     } catch (Throwable t) {
-                        logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
+                        // 1-8: Failed to unregister / unsubscribe url on destroy.
+                        logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, "", "",
+                            "Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                     }
                 }
             }
@@ -537,7 +602,9 @@
                             logger.info("Destroy unsubscribe url " + url);
                         }
                     } catch (Throwable t) {
-                        logger.warn("Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
+                        // 1-8: Failed to unregister / unsubscribe url on destroy.
+                        logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, "", "",
+                            "Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                     }
                 }
             }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java
index 3933c2c..3400ac1 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java
@@ -14,11 +14,12 @@
  * 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.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.RegistryFactory;
@@ -29,6 +30,7 @@
 import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_CREATE_INSTANCE;
 import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
 
@@ -39,7 +41,7 @@
  */
 public abstract class AbstractRegistryFactory implements RegistryFactory, ScopeModelAware {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegistryFactory.class);
+    private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(AbstractRegistryFactory.class);
 
     private RegistryManager registryManager;
     protected ApplicationModel applicationModel;
@@ -69,9 +71,11 @@
             .removeAttribute(EXPORT_KEY)
             .removeAttribute(REFER_KEY)
             .build();
+
         String key = createRegistryCacheKey(url);
         Registry registry = null;
         boolean check = url.getParameter(CHECK_KEY, true) && url.getPort() != 0;
+
         // Lock the registry access process to ensure a single instance of the registry
         registryManager.getRegistryLock().lock();
         try {
@@ -81,11 +85,13 @@
             if (null != defaultNopRegistry) {
                 return defaultNopRegistry;
             }
+
             registry = registryManager.getRegistry(key);
             if (registry != null) {
                 return registry;
             }
-            //create registry by spi/ioc
+
+            // create registry by spi/ioc
             registry = createRegistry(url);
             if (check && registry == null) {
                 throw new IllegalStateException("Can not create registry " + url);
@@ -98,7 +104,9 @@
             if (check) {
                 throw new RuntimeException("Can not create registry " + url, e);
             } else {
-                LOGGER.warn("Failed to obtain or create registry ", e);
+                // 1-11 Failed to obtain or create registry (service) object.
+                LOGGER.warn(REGISTRY_FAILED_CREATE_INSTANCE, "", "",
+                    "Failed to obtain or create registry ", e);
             }
         } finally {
             // Release the lock
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
index ae26aae..a6e2ee3 100644
--- 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
@@ -20,7 +20,7 @@
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.URLStrParser;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
@@ -56,30 +56,46 @@
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 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.LoggerCodeConstants.REGISTRY_ADDRESS_INVALID;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_EMPTY_ADDRESS;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_URL_EVICTING;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_PROPERTY_MISSPELLING;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_NO_PARAMETERS_URL;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_CLEAR_CACHED_URLS;
 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.ENABLE_EMPTY_PROTECTION_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
 
 /**
- * Useful for registries who's sdk returns raw string as provider instance, for example, zookeeper and etcd.
+ * <p>
+ * Based on FailbackRegistry, it adds a URLAddress and URLParam cache to save RAM space.
+ *
+ * <p>
+ * It's useful for registries whose sdk returns raw string as provider instance. For example, Zookeeper and etcd.
+ *
+ * @see org.apache.dubbo.registry.support.FailbackRegistry
+ * @see org.apache.dubbo.registry.support.AbstractRegistry
  */
 public abstract class CacheableFailbackRegistry extends FailbackRegistry {
-    private static final Logger logger = LoggerFactory.getLogger(CacheableFailbackRegistry.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CacheableFailbackRegistry.class);
+
     private static String[] VARIABLE_KEYS = new String[]{ENCODED_TIMESTAMP_KEY, ENCODED_PID_KEY};
 
     protected Map<String, URLAddress> stringAddress = new ConcurrentHashMap<>();
     protected Map<String, URLParam> stringParam = new ConcurrentHashMap<>();
+
     private ScheduledExecutorService cacheRemovalScheduler;
     private int cacheRemovalTaskIntervalInMillis;
     private int cacheClearWaitingThresholdInMillis;
+
     private Map<ServiceAddressURL, Long> waitForRemove = new ConcurrentHashMap<>();
     private Semaphore semaphore = new Semaphore(1);
 
     private final Map<String, String> extraParameters;
     protected final Map<URL, Map<String, ServiceAddressURL>> stringUrls = new ConcurrentHashMap<>();
 
-    public CacheableFailbackRegistry(URL url) {
+    protected CacheableFailbackRegistry(URL url) {
         super(url);
         extraParameters = new HashMap<>(8);
         extraParameters.put(CHECK_KEY, String.valueOf(false));
@@ -96,7 +112,10 @@
             try {
                 result = Integer.parseInt(str);
             } catch (NumberFormatException e) {
-                logger.warn("Invalid registry properties configuration key " + key + ", value " + str);
+                // 0-2 Property type mismatch.
+
+                logger.warn(COMMON_PROPERTY_MISSPELLING, "typo in property value", "This property requires an integer value.",
+                    "Invalid registry properties configuration key " + key + ", value " + str);
             }
         }
         return result;
@@ -110,7 +129,7 @@
     protected void evictURLCache(URL url) {
         Map<String, ServiceAddressURL> oldURLs = stringUrls.remove(url);
         try {
-            if (oldURLs != null && oldURLs.size() > 0) {
+            if (CollectionUtils.isNotEmptyMap(oldURLs)) {
                 logger.info("Evicting urls for service " + url.getServiceKey() + ", size " + oldURLs.size());
                 Long currentTimestamp = System.currentTimeMillis();
                 for (Map.Entry<String, ServiceAddressURL> entry : oldURLs.entrySet()) {
@@ -123,35 +142,60 @@
                 }
             }
         } catch (Exception e) {
-            logger.warn("Failed to evict url for " + url.getServiceKey(), e);
+            // It seems that the most possible statement that causes exception is the 'schedule()' method.
+
+            // The executor that FrameworkExecutorRepository.nextScheduledExecutor() method returns
+            // is made by Executors.newSingleThreadScheduledExecutor().
+
+            // After observing the code of ScheduledThreadPoolExecutor.delayedExecute,
+            // it seems that it only throws RejectedExecutionException when the thread pool is shutdown.
+
+            // When? FrameworkExecutorRepository gets destroyed.
+
+            // 1-3: URL evicting failed.
+            logger.warn(REGISTRY_FAILED_URL_EVICTING, "thread pool getting destroyed", "",
+                "Failed to evict url for " + url.getServiceKey(), e);
         }
     }
 
     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 = new HashMap<>((int) (providers.size() / 0.75f + 1));
+
+        // remove 'release', 'dubbo', 'methods', timestamp, 'dubbo.tag' parameter
+        // in consumer URL.
         URL copyOfConsumer = removeParamsFromConsumer(consumer);
+
         if (oldURLs == null) {
             for (String rawProvider : providers) {
+                // remove VARIABLE_KEYS(timestamp,pid..) in provider url.
                 rawProvider = stripOffVariableKeys(rawProvider);
+
+                // create DubboServiceAddress object using provider url, consumer url, and extra parameters.
                 ServiceAddressURL cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
                 if (cachedURL == null) {
-                    logger.warn("Invalid address, failed to parse into URL " + rawProvider);
+                    // 1-1: Address invalid.
+                    logger.warn(REGISTRY_ADDRESS_INVALID, "mismatch of service group and version settings", "",
+                        "Invalid address, failed to parse into URL " + rawProvider);
+
                     continue;
                 }
                 newURLs.put(rawProvider, cachedURL);
             }
         } else {
-            // maybe only default , or "env" + default
+            // 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);
+                        logger.warn(REGISTRY_ADDRESS_INVALID, "mismatch of service group and version settings", "",
+                            "Invalid address, failed to parse into URL " + rawProvider);
+
                         continue;
                     }
                 }
@@ -168,6 +212,7 @@
     protected List<URL> toUrlsWithEmpty(URL consumer, String path, Collection<String> providers) {
         List<URL> urls = new ArrayList<>(1);
         boolean isProviderPath = path.endsWith(PROVIDERS_CATEGORY);
+
         if (isProviderPath) {
             if (CollectionUtils.isNotEmpty(providers)) {
                 urls = toUrlsWithoutEmpty(consumer, providers);
@@ -186,7 +231,8 @@
             String category = i < 0 ? path : path.substring(i + 1);
             if (!PROVIDERS_CATEGORY.equals(category) || !getUrl().getParameter(ENABLE_EMPTY_PROTECTION_KEY, true)) {
                 if (PROVIDERS_CATEGORY.equals(category)) {
-                    logger.warn("Service " + consumer.getServiceKey() + " received empty address list and empty protection is disabled, will clear current available addresses");
+                    logger.warn(REGISTRY_EMPTY_ADDRESS, "", "",
+                        "Service " + consumer.getServiceKey() + " received empty address list and empty protection is disabled, will clear current available addresses");
                 }
                 URL empty = URLBuilder.from(consumer)
                     .setProtocol(EMPTY_PROTOCOL)
@@ -199,32 +245,59 @@
         return urls;
     }
 
+    /**
+     * Create DubboServiceAddress object using provider url, consumer url, and extra parameters.
+     *
+     * @param rawProvider provider url string
+     * @param consumerURL URL object of consumer
+     * @param extraParameters extra parameters
+     * @return created DubboServiceAddressURL object
+     */
     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 show, mark as not encoded.
+
+        if (paramStartIdx == -1) {
+            // if ENCODED_QUESTION_MARK does not show, mark as not encoded.
             encoded = false;
         }
+
+        // split by (encoded) question mark.
+        // part[0] => protocol + ip address + interface.
+        // part[1] => parameters (metadata).
         String[] parts = URLStrParser.parseRawURLToArrays(rawProvider, paramStartIdx);
+
         if (parts.length <= 1) {
-            logger.warn("Received url without any parameters " + rawProvider);
+            // 1-5 Received URL without any parameters.
+            logger.warn(REGISTRY_NO_PARAMETERS_URL, "", "",
+                "Received url without any parameters " + rawProvider);
+
             return DubboServiceAddressURL.valueOf(rawProvider, consumerURL);
         }
 
         String rawAddress = parts[0];
         String rawParams = parts[1];
+
+        // Workaround for 'effectively final': duplicate the encoded variable.
         boolean isEncoded = encoded;
+
+        // PathURLAddress if it's using dubbo protocol.
         URLAddress address = stringAddress.computeIfAbsent(rawAddress, k -> URLAddress.parse(k, getDefaultURLProtocol(), isEncoded));
         address.setTimestamp(System.currentTimeMillis());
 
         URLParam param = stringParam.computeIfAbsent(rawParams, k -> URLParam.parse(k, isEncoded, extraParameters));
         param.setTimestamp(System.currentTimeMillis());
 
-        ServiceAddressURL cachedURL = createServiceURL(address, param, consumerURL);
-        if (isMatch(consumerURL, cachedURL)) {
-            return cachedURL;
+        // create service URL using cached address, param, and consumer URL.
+        ServiceAddressURL cachedServiceAddressURL = createServiceURL(address, param, consumerURL);
+
+        if (isMatch(consumerURL, cachedServiceAddressURL)) {
+            return cachedServiceAddressURL;
         }
+
         return null;
     }
 
@@ -314,7 +387,9 @@
 
     protected abstract boolean isMatch(URL subscribeUrl, URL providerUrl);
 
-
+    /**
+     * The cached URL removal task, which will be run on a scheduled thread pool. (It will be run after a delay.)
+     */
     private class RemovalTask implements Runnable {
         @Override
         public void run() {
@@ -341,7 +416,11 @@
                     }
                 }
             } catch (Throwable t) {
-                logger.error("Error occurred when clearing cached URLs", t);
+                // 1-6 Error when clearing cached URLs.
+
+                logger.error(REGISTRY_FAILED_CLEAR_CACHED_URLS, "", "",
+                    "Error occurred when clearing cached URLs", t);
+
             } finally {
                 semaphore.release();
             }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/FailbackRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/FailbackRegistry.java
index 1477a91..d17c0da 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/FailbackRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/FailbackRegistry.java
@@ -40,7 +40,8 @@
 import static org.apache.dubbo.registry.Constants.REGISTRY_RETRY_PERIOD_KEY;
 
 /**
- * FailbackRegistry. (SPI, Prototype, ThreadSafe)
+ * A template implementation of registry service that provides auto-retry ability.
+ * (SPI, Prototype, ThreadSafe)
  */
 public abstract class FailbackRegistry extends AbstractRegistry {
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/RegistryManager.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/RegistryManager.java
index 6e74b7a..7a2e187 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/RegistryManager.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/RegistryManager.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.support;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.Registry;
@@ -36,11 +36,13 @@
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_FETCH_INSTANCE;
+
 /**
  * Application Level, used to collect Registries
  */
 public class RegistryManager {
-    private static final Logger LOGGER = LoggerFactory.getLogger(RegistryManager.class);
+    private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(RegistryManager.class);
 
     private ApplicationModel applicationModel;
 
@@ -124,8 +126,11 @@
 
     protected Registry getDefaultNopRegistryIfDestroyed() {
         if (destroyed.get()) {
-            LOGGER.warn("All registry instances have been destroyed, failed to fetch any instance. " +
+            // 1-12 Failed to fetch (server) instance since the registry instances have been destroyed.
+            LOGGER.warn(REGISTRY_FAILED_FETCH_INSTANCE, "misuse of the methods", "",
+                "All registry instances have been destroyed, failed to fetch any instance. " +
                 "Usually, this means no need to try to do unnecessary redundant resource clearance, all registries has been taken care of.");
+
             return DEFAULT_NOP_REGISTRY;
         }
         return null;
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer
new file mode 100644
index 0000000..7746df6
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer
@@ -0,0 +1 @@
+spring-cloud=org.apache.dubbo.registry.client.metadata.SpringCloudServiceInstanceNotificationCustomizer
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
index 0e52a74..946e0ee 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/CacheableFailbackRegistryTest.java
@@ -34,7 +34,7 @@
 import static org.apache.dubbo.common.constants.RegistryConstants.ENABLE_EMPTY_PROTECTION_KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-public class CacheableFailbackRegistryTest {
+class CacheableFailbackRegistryTest {
 
     static String service;
     static URL serviceUrl;
@@ -70,7 +70,7 @@
     }
 
     @Test
-    public void testFullURLCache() {
+    void testFullURLCache() {
         final AtomicReference<Integer> resCount = new AtomicReference<>(0);
         registry = new MockCacheableRegistryImpl(registryUrl);
         URL url = URLStrParser.parseEncodedStr(urlStr);
@@ -101,7 +101,7 @@
     }
 
     @Test
-    public void testURLAddressCache() {
+    void testURLAddressCache() {
         final AtomicReference<Integer> resCount = new AtomicReference<>(0);
         registry = new MockCacheableRegistryImpl(registryUrl);
         URL url = URLStrParser.parseEncodedStr(urlStr);
@@ -127,7 +127,7 @@
     }
 
     @Test
-    public void testURLParamCache() {
+    void testURLParamCache() {
         final AtomicReference<Integer> resCount = new AtomicReference<>(0);
         registry = new MockCacheableRegistryImpl(registryUrl);
         URL url = URLStrParser.parseEncodedStr(urlStr);
@@ -153,7 +153,7 @@
     }
 
     @Test
-    public void testRemove() {
+    void testRemove() {
         final AtomicReference<Integer> resCount = new AtomicReference<>(0);
         registry = new MockCacheableRegistryImpl(registryUrl);
         URL url = URLStrParser.parseEncodedStr(urlStr);
@@ -193,7 +193,7 @@
     }
 
     @Test
-    public void testEmptyProtection() {
+    void testEmptyProtection() {
         final AtomicReference<Integer> resCount = new AtomicReference<>(0);
         final AtomicReference<List<URL>> currentUrls = new AtomicReference<>();
         final List<URL> EMPTY_LIST = new ArrayList<>();
@@ -239,6 +239,4 @@
         assertEquals(EMPTY_LIST, currentUrls.get());
 
     }
-
-
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryTest.java
index 6ce0410..40546c7 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.registry.client;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.metadata.AbstractServiceNameMapping;
 import org.apache.dubbo.metadata.ServiceNameMapping;
@@ -43,7 +44,6 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.metadata.ServiceNameMapping.toStringKeys;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -220,10 +220,10 @@
         // check different protocol
         Map<String, Set<ServiceInstancesChangedListener.NotifyListenerWithKey>> serviceListeners = multiAppsInstanceListener.getServiceListeners();
         assertEquals(2, serviceListeners.size());
-        assertEquals(1, serviceListeners.get(url.getProtocolServiceKey()).size());
-        assertEquals(1, serviceListeners.get(url2.getProtocolServiceKey()).size());
-        String protocolServiceKey = url2.getServiceKey() + GROUP_CHAR_SEPARATOR + url2.getParameter(PROTOCOL_KEY, DUBBO);
-        assertTrue(serviceListeners.get(url2.getProtocolServiceKey()).contains(new ServiceInstancesChangedListener.NotifyListenerWithKey(protocolServiceKey, testServiceListener2)));
+        assertEquals(1, serviceListeners.get(url.getServiceKey()).size());
+        assertEquals(1, serviceListeners.get(url2.getServiceKey()).size());
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(url2.getServiceInterface(), url2.getVersion(), url2.getGroup(), url2.getParameter(PROTOCOL_KEY, DUBBO));
+        assertTrue(serviceListeners.get(url2.getServiceKey()).contains(new ServiceInstancesChangedListener.NotifyListenerWithKey(protocolServiceKey, testServiceListener2)));
     }
 
     /**
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/MockServiceInstancesChangedListener.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/MockServiceInstancesChangedListener.java
index 7c187ee..26f82b4 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/MockServiceInstancesChangedListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/MockServiceInstancesChangedListener.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.registry.client.event.listener;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
@@ -35,8 +36,8 @@
     }
 
     @Override
-    public List<URL> getAddresses(String serviceProtocolKey, URL consumerURL) {
-        return super.getAddresses(serviceProtocolKey, consumerURL);
+    public List<URL> getAddresses(ProtocolServiceKey protocolServiceKey, URL consumerURL) {
+        return super.getAddresses(protocolServiceKey, consumerURL);
     }
 
     public Map<String, Set<NotifyListenerWithKey>> getServiceListeners() {
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
index b8c6961..7bab0b8 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.registry.client.event.listener;
 
+import org.apache.dubbo.common.ProtocolServiceKey;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.common.utils.LRUCache;
@@ -49,7 +50,6 @@
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -62,7 +62,6 @@
 import static org.apache.dubbo.common.utils.CollectionUtils.isNotEmpty;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.eq;
@@ -219,7 +218,8 @@
         Assertions.assertEquals(1, allInstances.size());
         Assertions.assertEquals(3, allInstances.get("app1").size());
 
-        List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(service1, null, null, "dubbo");
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
         Assertions.assertEquals(3, serviceUrls.size());
         assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
     }
@@ -240,7 +240,8 @@
         Assertions.assertEquals(1, allInstances.size());
         Assertions.assertEquals(3, allInstances.get("app1").size());
 
-        List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(service1, null, null, "dubbo");
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
         Assertions.assertEquals(3, serviceUrls.size());
         assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
 
@@ -271,12 +272,16 @@
         Assertions.assertEquals(3, allInstances.get("app1").size());
         Assertions.assertEquals(4, allInstances.get("app2").size());
 
-        List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+        ProtocolServiceKey protocolServiceKey1 = new ProtocolServiceKey(service1, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey2 = new ProtocolServiceKey(service2, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey3 = new ProtocolServiceKey(service3, null, null, "dubbo");
+
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey1, consumerURL);
         Assertions.assertEquals(7, serviceUrls.size());
-        List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+        List<URL> serviceUrls2 = listener.getAddresses(protocolServiceKey2, consumerURL);
         Assertions.assertEquals(4, serviceUrls2.size());
         assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
-        List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
+        List<URL> serviceUrls3 = listener.getAddresses(protocolServiceKey3, consumerURL);
         Assertions.assertEquals(2, serviceUrls3.size());
         assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
     }
@@ -308,13 +313,17 @@
         Assertions.assertEquals(0, allInstances.get("app1").size());
         Assertions.assertEquals(4, allInstances.get("app2").size());
 
-        List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
+        ProtocolServiceKey protocolServiceKey1 = new ProtocolServiceKey(service1, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey2 = new ProtocolServiceKey(service2, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey3 = new ProtocolServiceKey(service3, null, null, "dubbo");
+
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey1, consumerURL);
         Assertions.assertEquals(4, serviceUrls.size());
         assertTrue(serviceUrls.get(0).getIp().contains("30.10."));
-        List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+        List<URL> serviceUrls2 = listener.getAddresses(protocolServiceKey2, consumerURL);
         Assertions.assertEquals(4, serviceUrls2.size());
         assertTrue(serviceUrls2.get(0).getIp().contains("30.10."));
-        List<URL> serviceUrls3 = listener.getAddresses(service3 + ":dubbo", consumerURL);
+        List<URL> serviceUrls3 = listener.getAddresses(protocolServiceKey3, consumerURL);
         Assertions.assertEquals(2, serviceUrls3.size());
         assertTrue(serviceUrls3.get(0).getIp().contains("30.10."));
 
@@ -328,9 +337,9 @@
         Assertions.assertEquals(0, allInstances_app2.get("app1").size());
         Assertions.assertEquals(0, allInstances_app2.get("app2").size());
 
-        assertTrue(isEmpty(listener.getAddresses(service1 + ":dubbo", consumerURL)));
-        assertTrue(isEmpty(listener.getAddresses(service2 + ":dubbo", consumerURL)));
-        assertTrue(isEmpty(listener.getAddresses(service3 + ":dubbo", consumerURL)));
+        assertTrue(isEmpty(listener.getAddresses(protocolServiceKey1, consumerURL)));
+        assertTrue(isEmpty(listener.getAddresses(protocolServiceKey2, consumerURL)));
+        assertTrue(isEmpty(listener.getAddresses(protocolServiceKey3, consumerURL)));
     }
 
     // 正常场景。检查instance listener -> service listener(Directory)地址推送流程
@@ -345,8 +354,8 @@
         when(demoServiceListener.getConsumerUrl()).thenReturn(consumerURL);
         NotifyListener demoService2Listener = Mockito.mock(NotifyListener.class);
         when(demoService2Listener.getConsumerUrl()).thenReturn(consumerURL2);
-        listener.addListenerAndNotify(consumerURL.getProtocolServiceKey(), demoServiceListener);
-        listener.addListenerAndNotify(consumerURL2.getProtocolServiceKey(), demoService2Listener);
+        listener.addListenerAndNotify(consumerURL, demoServiceListener);
+        listener.addListenerAndNotify(consumerURL2, demoService2Listener);
         // notify app1 instance change
         ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
         listener.onEvent(app1_event);
@@ -378,7 +387,7 @@
         // test service listener still get notified when added after instance notification.
         NotifyListener demoService3Listener = Mockito.mock(NotifyListener.class);
         when(demoService3Listener.getConsumerUrl()).thenReturn(consumerURL3);
-        listener.addListenerAndNotify(consumerURL3.getProtocolServiceKey(), demoService3Listener);
+        listener.addListenerAndNotify(consumerURL3, demoService3Listener);
         Mockito.verify(demoService3Listener, Mockito.times(1)).notify(Mockito.anyList());
     }
 
@@ -397,10 +406,10 @@
         when(demoService2Listener1.getConsumerUrl()).thenReturn(consumerURL2);
         NotifyListener demoService2Listener2 = Mockito.mock(NotifyListener.class);
         when(demoService2Listener2.getConsumerUrl()).thenReturn(consumerURL2);
-        listener.addListenerAndNotify(consumerURL.getProtocolServiceKey(), demoServiceListener1);
-        listener.addListenerAndNotify(consumerURL.getProtocolServiceKey(), demoServiceListener2);
-        listener.addListenerAndNotify(consumerURL2.getProtocolServiceKey(), demoService2Listener1);
-        listener.addListenerAndNotify(consumerURL2.getProtocolServiceKey(), demoService2Listener2);
+        listener.addListenerAndNotify(consumerURL, demoServiceListener1);
+        listener.addListenerAndNotify(consumerURL, demoServiceListener2);
+        listener.addListenerAndNotify(consumerURL2, demoService2Listener1);
+        listener.addListenerAndNotify(consumerURL2, demoService2Listener2);
         // notify app1 instance change
         ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1Instances);
         listener.onEvent(app1_event);
@@ -432,7 +441,7 @@
         // test service listener still get notified when added after instance notification.
         NotifyListener demoService3Listener = Mockito.mock(NotifyListener.class);
         when(demoService3Listener.getConsumerUrl()).thenReturn(consumerURL3);
-        listener.addListenerAndNotify(consumerURL3.getProtocolServiceKey(), demoService3Listener);
+        listener.addListenerAndNotify(consumerURL3, demoService3Listener);
         Mockito.verify(demoService3Listener, Mockito.times(1)).notify(Mockito.anyList());
     }
 
@@ -448,15 +457,15 @@
         // no protocol specified, consume all instances
         NotifyListener demoServiceListener1 = Mockito.mock(NotifyListener.class);
         when(demoServiceListener1.getConsumerUrl()).thenReturn(noProtocolConsumerURL);
-        listener.addListenerAndNotify(noProtocolConsumerURL.getProtocolServiceKey(), demoServiceListener1);
+        listener.addListenerAndNotify(noProtocolConsumerURL, demoServiceListener1);
         // multiple protocols specified
         NotifyListener demoServiceListener2 = Mockito.mock(NotifyListener.class);
         when(demoServiceListener2.getConsumerUrl()).thenReturn(multipleProtocolsConsumerURL);
-        listener.addListenerAndNotify(multipleProtocolsConsumerURL.getProtocolServiceKey(), demoServiceListener2);
+        listener.addListenerAndNotify(multipleProtocolsConsumerURL, demoServiceListener2);
         // one protocol specified
         NotifyListener demoServiceListener3 = Mockito.mock(NotifyListener.class);
         when(demoServiceListener3.getConsumerUrl()).thenReturn(singleProtocolsConsumerURL);
-        listener.addListenerAndNotify(singleProtocolsConsumerURL.getProtocolServiceKey(), demoServiceListener3);
+        listener.addListenerAndNotify(singleProtocolsConsumerURL, demoServiceListener3);
 
         // notify app1 instance change
         ServiceInstancesChangedEvent app1_event = new ServiceInstancesChangedEvent("app1", app1InstancesMultipleProtocols);
@@ -479,9 +488,119 @@
         Assertions.assertEquals(1, single_protocol_notifiedUrls.size());
     }
 
-    // revision 异常场景。第一次启动,完全拿不到metadata,只能通知部分地址
+    /**
+     * Test subscribe multiple groups
+     */
     @Test
     @Order(8)
+    public void testSubscribeMultipleGroups() {
+        Set<String> serviceNames = new HashSet<>();
+        serviceNames.add("app1");
+        listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+
+        // notify instance change
+        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
+        listener.onEvent(event);
+
+        Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
+        Assertions.assertEquals(1, allInstances.size());
+        Assertions.assertEquals(3, allInstances.get("app1").size());
+
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(service1, null, null, "dubbo");
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, ",group1", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "group1,", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "*", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "group1", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(0, serviceUrls.size());
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "group1,group2", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(0, serviceUrls.size());
+
+        protocolServiceKey = new ProtocolServiceKey(service1, null, "group1,,group2", "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+    }
+
+    /**
+     * Test subscribe multiple versions
+     */
+    @Test
+    @Order(9)
+    public void testSubscribeMultipleVersions() {
+        Set<String> serviceNames = new HashSet<>();
+        serviceNames.add("app1");
+        listener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
+
+        // notify instance change
+        ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent("app1", app1Instances);
+        listener.onEvent(event);
+
+        Map<String, List<ServiceInstance>> allInstances = listener.getAllInstances();
+        Assertions.assertEquals(1, allInstances.size());
+        Assertions.assertEquals(3, allInstances.get("app1").size());
+
+        ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(service1, null, null, "dubbo");
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, "", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, "*", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, ",1.0.0", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, "1.0.0,", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, "1.0.0,,1.0.1", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(3, serviceUrls.size());
+        assertTrue(serviceUrls.get(0) instanceof InstanceAddressURL);
+
+        protocolServiceKey = new ProtocolServiceKey(service1, "1.0.1,1.0.0", null, "dubbo");
+        serviceUrls = listener.getAddresses(protocolServiceKey, consumerURL);
+        Assertions.assertEquals(0, serviceUrls.size());
+    }
+
+    // revision 异常场景。第一次启动,完全拿不到metadata,只能通知部分地址
+    @Test
+    @Order(10)
     public void testRevisionFailureOnStartup() {
         Set<String> serviceNames = new HashSet<>();
         serviceNames.add("app1");
@@ -490,8 +609,12 @@
         ServiceInstancesChangedEvent failed_revision_event = new ServiceInstancesChangedEvent("app1", app1FailedInstances);
         listener.onEvent(failed_revision_event);
 
-        List<URL> serviceUrls = listener.getAddresses(service1 + ":dubbo", consumerURL);
-        List<URL> serviceUrls2 = listener.getAddresses(service2 + ":dubbo", consumerURL);
+
+        ProtocolServiceKey protocolServiceKey1 = new ProtocolServiceKey(service1, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey2 = new ProtocolServiceKey(service2, null, null, "dubbo");
+
+        List<URL> serviceUrls = listener.getAddresses(protocolServiceKey1, consumerURL);
+        List<URL> serviceUrls2 = listener.getAddresses(protocolServiceKey2, consumerURL);
 
         assertTrue(isNotEmpty(serviceUrls));
         assertTrue(isNotEmpty(serviceUrls2));
@@ -499,7 +622,7 @@
 
     // revision 异常场景。运行中地址通知,拿不到revision就用老版本revision
     @Test
-    @Order(9)
+    @Order(11)
     public void testRevisionFailureOnNotification() {
         Set<String> serviceNames = new HashSet<>();
         serviceNames.add("app1");
@@ -524,8 +647,11 @@
         listener.onEvent(event2);
 
         // event2 did not really take effect
-        Assertions.assertEquals(3, listener.getAddresses(service1 + ":dubbo", consumerURL).size());
-        assertTrue(isEmpty(listener.getAddresses(service2 + ":dubbo", consumerURL)));
+        ProtocolServiceKey protocolServiceKey1 = new ProtocolServiceKey(service1, null, null, "dubbo");
+        ProtocolServiceKey protocolServiceKey2 = new ProtocolServiceKey(service2, null, null, "dubbo");
+
+        Assertions.assertEquals(3, listener.getAddresses(protocolServiceKey1, consumerURL).size());
+        assertTrue(isEmpty(listener.getAddresses(protocolServiceKey2, consumerURL)));
 
         //
         init();
@@ -536,16 +662,16 @@
             e.printStackTrace();
         }
         // check recovered after retry.
-        List<URL> serviceUrls_after_retry = listener.getAddresses(service1 + ":dubbo", consumerURL);
+        List<URL> serviceUrls_after_retry = listener.getAddresses(protocolServiceKey1, consumerURL);
         Assertions.assertEquals(5, serviceUrls_after_retry.size());
-        List<URL> serviceUrls2_after_retry = listener.getAddresses(service2 + ":dubbo", consumerURL);
+        List<URL> serviceUrls2_after_retry = listener.getAddresses(protocolServiceKey2, consumerURL);
         Assertions.assertEquals(2, serviceUrls2_after_retry.size());
 
     }
 
     // Abnormal case. Instance does not have revision
     @Test
-    @Order(10)
+    @Order(12)
     public void testInstanceWithoutRevision() {
         Set<String> serviceNames = new HashSet<>();
         serviceNames.add("app1");
@@ -559,39 +685,6 @@
         assertTrue(true);
     }
 
-    /**
-     * Test calculation of subscription protocols
-     */
-    @Test
-    public void testGetProtocolServiceKeyList() {
-        NotifyListener listener = Mockito.mock(NotifyListener.class);
-
-        Set<String> serviceNames = new HashSet<>();
-        serviceNames.add("app1");
-        ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);
-        ServiceInstancesChangedListener instancesChangedListener = new ServiceInstancesChangedListener(serviceNames, serviceDiscovery);
-
-        URL url1 = URL.valueOf("tri://localhost/Service?protocol=tri");
-        when(listener.getConsumerUrl()).thenReturn(url1);
-        Set<String> keyList11 = instancesChangedListener.getProtocolServiceKeyList(url1.getProtocolServiceKey(), listener);
-        assertEquals(getExpectedSet(Arrays.asList("Service:tri")), keyList11);
-
-        URL url2 = URL.valueOf("consumer://localhost/Service?group=group&version=1.0");
-        when(listener.getConsumerUrl()).thenReturn(url2);
-        Set<String> keyList12 = instancesChangedListener.getProtocolServiceKeyList(url2.getProtocolServiceKey(), listener);
-        assertEquals(getExpectedSet(Arrays.asList("group/Service:1.0:tri", "group/Service:1.0:dubbo", "group/Service:1.0:rest")), keyList12);
-
-        URL url3 = URL.valueOf("dubbo://localhost/Service?protocol=dubbo&group=group&version=1.0");
-        when(listener.getConsumerUrl()).thenReturn(url3);
-        Set<String> keyList21 = instancesChangedListener.getProtocolServiceKeyList(url3.getProtocolServiceKey(), listener);
-        assertEquals(getExpectedSet(Arrays.asList("group/Service:1.0:dubbo")), keyList21);
-
-        URL url4 = URL.valueOf("dubbo,tri://localhost/Service?protocol=dubbo,tri&group=group&version=1.0");
-        when(listener.getConsumerUrl()).thenReturn(url4);
-        Set<String> keyList23 = instancesChangedListener.getProtocolServiceKeyList(url4.getProtocolServiceKey(), listener);
-        assertEquals(getExpectedSet(Arrays.asList("group/Service:1.0:dubbo", "group/Service:1.0:tri")), keyList23);
-    }
-
     Set<String> getExpectedSet(List<String> list) {
         return new HashSet<>(list);
     }
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastRegistry.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastRegistry.java
index f967915..48b3f66 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastRegistry.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastRegistry.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.multicast;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
@@ -71,7 +71,7 @@
 public class MulticastRegistry extends FailbackRegistry {
 
     // logging output
-    private static final Logger logger = LoggerFactory.getLogger(MulticastRegistry.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MulticastRegistry.class);
 
     private static final int DEFAULT_MULTICAST_PORT = 1234;
 
diff --git a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
index 0da009c..fde87af 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
@@ -39,7 +39,7 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class MulticastRegistryTest {
+class MulticastRegistryTest {
 
     private String service = "org.apache.dubbo.test.injvmServie";
     private URL registryUrl = URL.valueOf("multicast://239.239.239.239/");
@@ -50,7 +50,7 @@
     private MulticastRegistry registry = new MulticastRegistry(registryUrl);
 
     @BeforeEach
-    public void setUp() {
+    void setUp() {
         registry.register(serviceUrl);
     }
 
@@ -58,7 +58,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#MulticastRegistry(URL)}.
      */
     @Test
-    public void testUrlError() {
+    void testUrlError() {
         Assertions.assertThrows(UnknownHostException.class, () -> {
             try {
                 URL errorUrl = URL.valueOf("multicast://mullticast.local/");
@@ -73,7 +73,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#MulticastRegistry(URL)}.
      */
     @Test
-    public void testAnyHost() {
+    void testAnyHost() {
         Assertions.assertThrows(IllegalStateException.class, () -> {
             URL errorUrl = URL.valueOf("multicast://0.0.0.0/");
             new MulticastRegistry(errorUrl);
@@ -84,7 +84,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#MulticastRegistry(URL)}.
      */
     @Test
-    public void testGetCustomPort() {
+    void testGetCustomPort() {
         int port = NetUtils.getAvailablePort(20880 + new Random().nextInt(10000));
         URL customPortUrl = URL.valueOf("multicast://239.239.239.239:" + port);
         MulticastRegistry multicastRegistry = new MulticastRegistry(customPortUrl);
@@ -95,7 +95,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#getRegistered()}.
      */
     @Test
-    public void testRegister() {
+    void testRegister() {
         Set<URL> registered;
         // clear first
         registered = registry.getRegistered();
@@ -117,7 +117,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#unregister(URL)}.
      */
     @Test
-    public void testUnregister() {
+    void testUnregister() {
         Set<URL> registered;
 
         // register first
@@ -137,7 +137,7 @@
      * .
      */
     @Test
-    public void testSubscribe() {
+    void testSubscribe() {
         // verify listener
         final URL[] notifyUrl = new URL[1];
         for (int i = 0; i < 10; i++) {
@@ -159,7 +159,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#unsubscribe(URL, NotifyListener)}
      */
     @Test
-    public void testUnsubscribe() {
+    void testUnsubscribe() {
         // subscribe first
         registry.subscribe(consumerUrl, new NotifyListener() {
             @Override
@@ -186,7 +186,7 @@
      * Test method for {@link MulticastRegistry#isAvailable()}
      */
     @Test
-    public void testAvailability() {
+    void testAvailability() {
         int port = NetUtils.getAvailablePort(20880 + new Random().nextInt(10000));
         MulticastRegistry registry = new MulticastRegistry(URL.valueOf("multicast://224.5.6.8:" + port));
         assertTrue(registry.isAvailable());
@@ -196,7 +196,7 @@
      * Test method for {@link MulticastRegistry#destroy()}
      */
     @Test
-    public void testDestroy() {
+    void testDestroy() {
         MulticastSocket socket = registry.getMulticastSocket();
         assertFalse(socket.isClosed());
 
@@ -210,7 +210,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#MulticastRegistry(URL)}
      */
     @Test
-    public void testDefaultPort() {
+    void testDefaultPort() {
         MulticastRegistry multicastRegistry = new MulticastRegistry(URL.valueOf("multicast://224.5.6.7"));
         try {
             MulticastSocket multicastSocket = multicastRegistry.getMulticastSocket();
@@ -224,7 +224,7 @@
      * Test method for {@link org.apache.dubbo.registry.multicast.MulticastRegistry#MulticastRegistry(URL)}
      */
     @Test
-    public void testCustomedPort() {
+    void testCustomedPort() {
         int port = NetUtils.getAvailablePort(20880 + new Random().nextInt(10000));
         MulticastRegistry multicastRegistry = new MulticastRegistry(URL.valueOf("multicast://224.5.6.7:" + port));
         try {
@@ -236,7 +236,7 @@
     }
 
     @Test
-    public void testMulticastAddress() {
+    void testMulticastAddress() {
         InetAddress multicastAddress = null;
         MulticastSocket multicastSocket = null;
         try {
diff --git a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
index be9c9c9..a28e8ad 100644
--- a/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/main/java/org/apache/dubbo/registry/multiple/MultipleRegistry.java
@@ -19,7 +19,10 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.RegistryFactory;
@@ -35,15 +38,18 @@
 import java.util.stream.Collectors;
 
 import static org.apache.dubbo.common.constants.CommonConstants.CHECK_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
 import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 
 /**
  * MultipleRegistry
  */
 public class MultipleRegistry extends AbstractRegistry {
+    public static final Logger LOGGER = LoggerFactory.getLogger(MultipleRegistry.class);
 
     public static final String REGISTRY_FOR_SERVICE = "service-registry";
     public static final String REGISTRY_FOR_REFERENCE = "reference-registry";
+    public static final String REGISTRY_SEPARATOR = "separator";
     private final Map<String, Registry> serviceRegistries = new ConcurrentHashMap<>(4);
     private final Map<String, Registry> referenceRegistries = new ConcurrentHashMap<>(4);
     private final Map<NotifyListener, MultipleNotifyListenerWrapper> multipleNotifyListenerMap = new ConcurrentHashMap<>(32);
@@ -86,7 +92,9 @@
     }
 
     protected void initServiceRegistry(URL url, Map<String, Registry> registryMap) {
-        origServiceRegistryURLs = url.getParameter(REGISTRY_FOR_SERVICE, new ArrayList<>());
+        String serviceRegistryString = url.getParameter(REGISTRY_FOR_SERVICE);
+        char separator = url.getParameter(REGISTRY_SEPARATOR, COMMA_SEPARATOR).charAt(0);
+        origServiceRegistryURLs = StringUtils.splitToList(serviceRegistryString, separator);
         effectServiceRegistryURLs = this.filterServiceRegistry(origServiceRegistryURLs);
         for (String tmpUrl : effectServiceRegistryURLs) {
             if (registryMap.get(tmpUrl) != null) {
@@ -101,7 +109,9 @@
     }
 
     protected void initReferenceRegistry(URL url, Map<String, Registry> registryMap) {
-        origReferenceRegistryURLs = url.getParameter(REGISTRY_FOR_REFERENCE, new ArrayList<>());
+        String serviceRegistryString = url.getParameter(REGISTRY_FOR_REFERENCE);
+        char separator = url.getParameter(REGISTRY_SEPARATOR, COMMA_SEPARATOR).charAt(0);
+        origReferenceRegistryURLs = StringUtils.splitToList(serviceRegistryString, separator);
         effectReferenceRegistryURLs = this.filterReferenceRegistry(origReferenceRegistryURLs);
         for (String tmpUrl : effectReferenceRegistryURLs) {
             if (registryMap.get(tmpUrl) != null) {
@@ -292,15 +302,56 @@
                     }
                     continue;
                 }
-                notifyURLs.addAll(tmpUrls);
+                URL registryURL = singleNotifyListener.getRegistry().getUrl();
+                aggregateRegistryUrls(notifyURLs, tmpUrls, registryURL);
             }
             // if no notify URL, add empty protocol URL
             if (emptyURL != null && notifyURLs.isEmpty()) {
                 notifyURLs.add(emptyURL);
+                LOGGER.info("No provider after aggregation, notify url with EMPTY protocol.");
+            } else {
+                LOGGER.info("Aggregated provider url size " + notifyURLs.size());
             }
+
             this.notify(notifyURLs);
         }
 
+        /**
+         * Aggregate urls from different registries into one unified list while appending registry specific 'attachments' into each url.
+         *
+         * These 'attachments' can be very useful for traffic management among registries.
+         *
+         * @param notifyURLs unified url list
+         * @param singleURLs single registry url list
+         * @param registryURL single registry configuration url
+         */
+        public static void aggregateRegistryUrls(List<URL> notifyURLs, List<URL> singleURLs, URL registryURL) {
+            String registryAttachments = registryURL.getParameter("attachments");
+            if (StringUtils.isNotBlank(registryAttachments)) {
+                LOGGER.info("Registry attachments " + registryAttachments + " found, will append to provider urls, urls size " + singleURLs.size());
+                String[] pairs = registryAttachments.split(COMMA_SEPARATOR);
+                Map<String, String> attachments = new HashMap<>(pairs.length);
+                for (String rawPair : pairs) {
+                   String[] keyValuePair = rawPair.split("=");
+                   if (keyValuePair.length == 2) {
+                       String key = keyValuePair[0];
+                       String value = keyValuePair[1];
+                       attachments.put(key, value);
+                   }
+                }
+
+                for (URL tmpUrl : singleURLs) {
+                    for (Map.Entry<String, String> entry : attachments.entrySet()) {
+                        tmpUrl = tmpUrl.addParameterIfAbsent(entry.getKey(), entry.getValue());
+                    }
+                    notifyURLs.add(tmpUrl);
+                }
+            } else {
+                LOGGER.info("Single registry " + registryURL + " has url size " + singleURLs.size());
+                notifyURLs.addAll(singleURLs);
+            }
+        }
+
         @Override
         public void notify(List<URL> urls) {
             sourceNotifyListener.notify(urls);
@@ -338,5 +389,11 @@
         public List<URL> getUrlList() {
             return urlList;
         }
+
+        public Registry getRegistry() {
+            return registry;
+        }
+
+
     }
 }
diff --git a/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistry2S2RTest.java b/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistry2S2RTest.java
index 441956a..dfa04ec 100644
--- a/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistry2S2RTest.java
+++ b/dubbo-registry/dubbo-registry-multiple/src/test/java/org/apache/dubbo/registry/multiple/MultipleRegistry2S2RTest.java
@@ -175,4 +175,23 @@
         Assertions.assertEquals("empty", list.get(0).getProtocol());
     }
 
+    @Test
+    public void testAggregation() {
+        List<URL> result = new ArrayList<URL>();
+        List<URL> listToAggregate = new ArrayList<URL>();
+        URL url1= URL.valueOf("dubbo://127.0.0.1:20880/service1");
+        URL url2= URL.valueOf("dubbo://127.0.0.1:20880/service1");
+        listToAggregate.add(url1);
+        listToAggregate.add(url2);
+
+        URL registryURL = URL.valueOf("mock://127.0.0.1/RegistryService?attachments=zone=hangzhou,tag=middleware&enable-empty-protection=false");
+
+        MultipleRegistry.MultipleNotifyListenerWrapper.aggregateRegistryUrls(result, listToAggregate, registryURL);
+
+        Assertions.assertEquals(2, result.size());
+        Assertions.assertEquals(2, result.get(0).getParameters().size());
+        Assertions.assertEquals("hangzhou", result.get(0).getParameter("zone"));
+        Assertions.assertEquals("middleware", result.get(1).getParameter("tag"));
+    }
+
 }
diff --git a/dubbo-registry/dubbo-registry-nacos/pom.xml b/dubbo-registry/dubbo-registry-nacos/pom.xml
index ab914db..c0918d5 100644
--- a/dubbo-registry/dubbo-registry-nacos/pom.xml
+++ b/dubbo-registry/dubbo-registry-nacos/pom.xml
@@ -54,6 +54,13 @@
 
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-rpc-dubbo</artifactId>
             <version>${project.version}</version>
             <scope>test</scope>
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosNamingServiceWrapper.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosNamingServiceWrapper.java
index 7d7a7d8..de4dc69 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosNamingServiceWrapper.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosNamingServiceWrapper.java
@@ -43,10 +43,6 @@
         return namingService.getServerStatus();
     }
 
-    public void subscribe(String serviceName, EventListener eventListener) throws NacosException {
-        namingService.subscribe(handleInnerSymbol(serviceName), eventListener);
-    }
-
     public void subscribe(String serviceName, String group, EventListener eventListener) throws NacosException {
         namingService.subscribe(handleInnerSymbol(serviceName), group, eventListener);
     }
@@ -72,12 +68,8 @@
         namingService.deregisterInstance(handleInnerSymbol(serviceName), group, instance);
     }
 
-    public ListView<String> getServicesOfServer(int pageNo, int pageSize, String parameter) throws NacosException {
-        return namingService.getServicesOfServer(pageNo, pageSize, parameter);
-    }
-
-    public List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException {
-        return namingService.selectInstances(handleInnerSymbol(serviceName), healthy);
+    public ListView<String> getServicesOfServer(int pageNo, int pageSize, String group) throws NacosException {
+        return namingService.getServicesOfServer(pageNo, pageSize, group);
     }
 
     public List<Instance> selectInstances(String serviceName, String group, boolean healthy) throws NacosException {
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 d2f749c..fa47d05 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
@@ -19,7 +19,7 @@
 
 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.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.url.component.DubboServiceAddressURL;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
@@ -125,7 +125,7 @@
      * The interval in second of lookup Nacos service names(only for Dubbo-OPS)
      */
     private static final long LOOKUP_INTERVAL = Long.getLong("nacos.service.names.lookup.interval", 30);
-    private static final Logger logger = LoggerFactory.getLogger(NacosRegistry.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NacosRegistry.class);
     private final NacosNamingServiceWrapper namingService;
     /**
      * {@link ScheduledExecutorService} lookup Nacos service names(only for Dubbo-OPS)
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
index 1bf83cc..6435111 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java
@@ -17,9 +17,11 @@
 package org.apache.dubbo.registry.nacos;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.function.ThrowableFunction;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
 import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceInstance;
@@ -28,8 +30,9 @@
 import org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
-import com.alibaba.nacos.api.common.Constants;
 import com.alibaba.nacos.api.exception.NacosException;
+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;
@@ -37,10 +40,13 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP;
 import static org.apache.dubbo.common.function.ThrowableConsumer.execute;
 import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.createNamingService;
+import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.getGroup;
 import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.toInstance;
 
 /**
@@ -53,26 +59,33 @@
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
-    private NacosNamingServiceWrapper namingService;
+    private final String group;
+
+    private final NacosNamingServiceWrapper namingService;
+
+    private static final String NACOS_SD_USE_DEFAULT_GROUP_KEY = "dubbo.nacos-service-discovery.use-default-group";
+
+    private final ConcurrentHashMap<String, NacosEventListener> eventListeners = new ConcurrentHashMap<>();
 
     public NacosServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
         super(applicationModel, registryURL);
         this.namingService = createNamingService(registryURL);
+        // backward compatibility for 3.0.x
+        this.group = Boolean.parseBoolean(ConfigurationUtils.getProperty(applicationModel, NACOS_SD_USE_DEFAULT_GROUP_KEY, "false")) ?
+            DEFAULT_GROUP: getGroup(registryURL);
     }
 
     @Override
     public void doDestroy() throws Exception {
         this.namingService.shutdown();
+        this.eventListeners.clear();
     }
 
     @Override
     public void doRegister(ServiceInstance serviceInstance) {
         execute(namingService, service -> {
             Instance instance = toInstance(serviceInstance);
-            // Should not register real group for ServiceInstance
-            // Or will cause consumer unable to fetch all of the providers from every group
-            // Provider's group is invisible for consumer
-            service.registerInstance(instance.getServiceName(), Constants.DEFAULT_GROUP, instance);
+            service.registerInstance(instance.getServiceName(), group, instance);
         });
     }
 
@@ -80,20 +93,14 @@
     public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
         execute(namingService, service -> {
             Instance instance = toInstance(serviceInstance);
-            // Should not register real group for ServiceInstance
-            // Or will cause consumer unable to fetch all of the providers from every group
-            // Provider's group is invisible for consumer
-            service.deregisterInstance(instance.getServiceName(), Constants.DEFAULT_GROUP, instance);
+            service.deregisterInstance(instance.getServiceName(), group, instance);
         });
     }
 
     @Override
     public Set<String> getServices() {
         return ThrowableFunction.execute(namingService, service -> {
-            // Should not register real group for ServiceInstance
-            // Or will cause consumer unable to fetch all of the providers from every group
-            // Provider's group is invisible for consumer
-            ListView<String> view = service.getServicesOfServer(0, Integer.MAX_VALUE, Constants.DEFAULT_GROUP);
+            ListView<String> view = service.getServicesOfServer(0, Integer.MAX_VALUE, group);
             return new LinkedHashSet<>(view.getData());
         });
     }
@@ -101,7 +108,7 @@
     @Override
     public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
         return ThrowableFunction.execute(namingService, service ->
-            service.selectInstances(serviceName, Constants.DEFAULT_GROUP, true)
+            service.selectInstances(serviceName, group, true)
                 .stream().map((i) -> NacosNamingServiceUtils.toServiceInstance(registryURL, i))
                 .collect(Collectors.toList())
         );
@@ -114,18 +121,68 @@
         if (!instanceListeners.add(listener)) {
             return;
         }
-        execute(namingService, service -> listener.getServiceNames().forEach(serviceName -> {
-            try {
-                service.subscribe(serviceName, Constants.DEFAULT_GROUP, e -> { // Register Nacos EventListener
-                    if (e instanceof NamingEvent) {
-                        NamingEvent event = (NamingEvent) e;
-                        handleEvent(event, listener);
-                    }
-                });
-            } catch (NacosException e) {
-                logger.error("add nacos service instances changed listener fail ", e);
+        for (String serviceName : listener.getServiceNames()) {
+            NacosEventListener nacosEventListener = eventListeners.get(serviceName);
+            if (nacosEventListener != null) {
+                nacosEventListener.addListener(listener);
+            } else {
+                try {
+                    nacosEventListener = new NacosEventListener();
+                    nacosEventListener.addListener(listener);
+                    namingService.subscribe(serviceName, group, nacosEventListener);
+                    eventListeners.put(serviceName, nacosEventListener);
+                } catch (NacosException e) {
+                    logger.error("add nacos service instances changed listener fail ", e);
+                }
             }
-        }));
+        }
+    }
+
+    @Override
+    public void removeServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws IllegalArgumentException {
+        if (!instanceListeners.remove(listener)) {
+            return;
+        }
+        for (String serviceName : listener.getServiceNames()) {
+            NacosEventListener nacosEventListener = eventListeners.get(serviceName);
+            if (nacosEventListener != null) {
+                nacosEventListener.removeListener(listener);
+                if (nacosEventListener.isEmpty()) {
+                    eventListeners.remove(serviceName);
+                    try {
+                        namingService.unsubscribe(serviceName, group, nacosEventListener);
+                    } catch (NacosException e) {
+                        logger.error("remove nacos service instances changed listener fail ", e);
+                    }
+                }
+            }
+        }
+    }
+
+    public class NacosEventListener implements EventListener {
+        private final Set<ServiceInstancesChangedListener> listeners = new ConcurrentHashSet<>();
+
+        @Override
+        public void onEvent(Event e) {
+            if (e instanceof NamingEvent) {
+                for (ServiceInstancesChangedListener listener : listeners) {
+                    NamingEvent event = (NamingEvent) e;
+                    handleEvent(event, listener);
+                }
+            }
+        }
+
+        public void addListener(ServiceInstancesChangedListener listener) {
+            listeners.add(listener);
+        }
+
+        public void removeListener(ServiceInstancesChangedListener listener) {
+            listeners.remove(listener);
+        }
+
+        public boolean isEmpty() {
+            return listeners.isEmpty();
+        }
     }
 
     @Override
diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java
index 33a8da4..6771bf0 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java
@@ -17,7 +17,7 @@
 package org.apache.dubbo.registry.nacos.util;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
@@ -39,7 +39,9 @@
 import static com.alibaba.nacos.api.PropertyKeyConst.PASSWORD;
 import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR;
 import static com.alibaba.nacos.api.PropertyKeyConst.USERNAME;
+import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP;
 import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY;
 import static org.apache.dubbo.common.utils.StringConstantFieldValuePredicate.of;
 
@@ -50,8 +52,8 @@
  */
 public class NacosNamingServiceUtils {
 
-    private static final Logger logger = LoggerFactory.getLogger(NacosNamingServiceUtils.class);
-    private static String NACOS_GROUP_KEY = "nacos.group";
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NacosNamingServiceUtils.class);
+    private static final String NACOS_GROUP_KEY = "nacos.group";
 
     /**
      * Convert the {@link ServiceInstance} to {@link Instance}
@@ -91,6 +93,19 @@
     }
 
     /**
+     * The group of {@link NamingService} to register
+     *
+     * @param connectionURL {@link URL connection url}
+     * @return non-null, "default" as default
+     * @since 2.7.5
+     */
+    public static String getGroup(URL connectionURL) {
+        // Compatible with nacos grouping via group.
+        String group = connectionURL.getParameter(GROUP_KEY, DEFAULT_GROUP);
+        return connectionURL.getParameter(NACOS_GROUP_KEY, group);
+    }
+
+    /**
      * Create an instance of {@link NamingService} from specified {@link URL connection url}
      *
      * @param connectionURL {@link URL connection url}
diff --git a/dubbo-registry/dubbo-registry-nacos/src/test/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-nacos/src/test/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryTest.java
index 77cd1f6..87f8cd1 100644
--- a/dubbo-registry/dubbo-registry-nacos/src/test/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryTest.java
+++ b/dubbo-registry/dubbo-registry-nacos/src/test/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryTest.java
@@ -29,7 +29,9 @@
 import com.alibaba.nacos.api.exception.NacosException;
 import com.alibaba.nacos.api.naming.pojo.Instance;
 import com.alibaba.nacos.api.naming.pojo.ListView;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
@@ -41,11 +43,12 @@
 import java.util.List;
 import java.util.Set;
 
+import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -60,22 +63,59 @@
 
     private static final String LOCALHOST = "127.0.0.1";
 
-    private URL registryUrl;
+    protected URL registryUrl = URL.valueOf("nacos://127.0.0.1:" + NetUtils.getAvailablePort());
 
     private NacosServiceDiscovery nacosServiceDiscovery;
 
     private NacosNamingServiceWrapper namingServiceWrapper;
 
+    protected String group = DEFAULT_GROUP;
+
     private DefaultServiceInstance createServiceInstance(String serviceName, String host, int port) {
         return new DefaultServiceInstance(serviceName, host, port, ScopeModelUtil.getApplicationModel(registryUrl.getScopeModel()));
     }
 
+    public static class NacosServiceDiscoveryGroupTest1 extends NacosServiceDiscoveryTest {
+        public NacosServiceDiscoveryGroupTest1() {
+            super();
+            group = "test-group1";
+            registryUrl = URL.valueOf("nacos://127.0.0.1:" + NetUtils.getAvailablePort()).addParameter("group", group);
+        }
+    }
+
+    public static class NacosServiceDiscoveryGroupTest2 extends NacosServiceDiscoveryTest {
+        public NacosServiceDiscoveryGroupTest2() {
+            super();
+            group = "test-group2";
+            registryUrl = URL.valueOf("nacos://127.0.0.1:" + NetUtils.getAvailablePort()).addParameter("group", group);
+        }
+    }
+
+
+
+    public static class NacosServiceDiscoveryGroupTest3 extends NacosServiceDiscoveryTest {
+        public NacosServiceDiscoveryGroupTest3() {
+            super();
+            group = DEFAULT_GROUP;
+            registryUrl = URL.valueOf("nacos://127.0.0.1:" + NetUtils.getAvailablePort()).addParameter("group", "test-group3");
+        }
+
+        @BeforeAll
+        public static void beforeClass() {
+            System.setProperty("dubbo.nacos-service-discovery.use-default-group", "true");
+        }
+
+        @AfterAll
+        public static void afterClass() {
+            System.clearProperty("dubbo.nacos-service-discovery.use-default-group");
+        }
+    }
+
     @BeforeEach
     public void init() throws Exception {
         ApplicationModel applicationModel = ApplicationModel.defaultModel();
         applicationModel.getApplicationConfigManager().setApplication(new ApplicationConfig(SERVICE_NAME));
 
-        this.registryUrl = URL.valueOf("nacos://127.0.0.1:" + NetUtils.getAvailablePort());
         registryUrl.setScopeModel(applicationModel);
 
 //        this.nacosServiceDiscovery = new NacosServiceDiscovery(SERVICE_NAME, registryUrl);
@@ -97,45 +137,32 @@
         // register
         nacosServiceDiscovery.doRegister(serviceInstance);
 
-        List<Instance> instances = new ArrayList<>();
-        Instance instance1 = new Instance();
-        Instance instance2 = new Instance();
-        Instance instance3 = new Instance();
+        ArgumentCaptor<Instance> instanceCaptor = ArgumentCaptor.forClass(Instance.class);
+        verify(namingServiceWrapper, times(1)).registerInstance(any(), eq(group), instanceCaptor.capture());
 
-        instances.add(instance1);
-        instances.add(instance2);
-        instances.add(instance3);
-
-        ArgumentCaptor<Instance> instance = ArgumentCaptor.forClass(Instance.class);
-        verify(namingServiceWrapper, times(1)).registerInstance(any(), any(), instance.capture());
-
-        when(namingServiceWrapper.getAllInstances(anyString(), anyString())).thenReturn(instances);
-        assertEquals(3, namingServiceWrapper.getAllInstances(anyString(), anyString()).size());
+        Instance capture = instanceCaptor.getValue();
+        assertEquals(SERVICE_NAME, capture.getServiceName());
+        assertEquals(LOCALHOST, capture.getIp());
+        assertEquals(serviceInstance.getPort(), capture.getPort());
     }
 
     @Test
     public void testDoUnRegister() throws NacosException {
         // register
-        nacosServiceDiscovery.register(URL.valueOf("dubbo://10.0.2.3:20880/DemoService?interface=DemoService"));
-        nacosServiceDiscovery.register();
-
-        List<Instance> instances = new ArrayList<>();
-        Instance instance1 = new Instance();
-        Instance instance2 = new Instance();
-        Instance instance3 = new Instance();
-
-        instances.add(instance1);
-        instances.add(instance2);
-        instances.add(instance3);
-
-        ArgumentCaptor<Instance> instance = ArgumentCaptor.forClass(Instance.class);
-        verify(namingServiceWrapper, times(1)).registerInstance(any(), any(), instance.capture());
-
-        when(namingServiceWrapper.getAllInstances(anyString(), anyString())).thenReturn(instances);
-        assertEquals(3, namingServiceWrapper.getAllInstances(anyString(), anyString()).size());
+        DefaultServiceInstance serviceInstance = createServiceInstance(SERVICE_NAME, LOCALHOST, NetUtils.getAvailablePort());
+        // register
+        nacosServiceDiscovery.doRegister(serviceInstance);
 
         // unRegister
-        nacosServiceDiscovery.unregister();
+        nacosServiceDiscovery.doUnregister(serviceInstance);
+
+        ArgumentCaptor<Instance> instanceCaptor = ArgumentCaptor.forClass(Instance.class);
+        verify(namingServiceWrapper, times(1)).deregisterInstance(any(), eq(group), instanceCaptor.capture());
+
+        Instance capture = instanceCaptor.getValue();
+        assertEquals(SERVICE_NAME, capture.getServiceName());
+        assertEquals(LOCALHOST, capture.getIp());
+        assertEquals(serviceInstance.getPort(), capture.getPort());
     }
 
     @Test
@@ -145,7 +172,7 @@
         nacosServiceDiscovery.doRegister(serviceInstance);
 
         ArgumentCaptor<Instance> instance = ArgumentCaptor.forClass(Instance.class);
-        verify(namingServiceWrapper, times(1)).registerInstance(any(), any(), instance.capture());
+        verify(namingServiceWrapper, times(1)).registerInstance(any(), eq(group), instance.capture());
 
         String serviceNameWithoutVersion = "providers:org.apache.dubbo.registry.nacos.NacosService:default";
         String serviceName = "providers:org.apache.dubbo.registry.nacos.NacosService:1.0.0:default";
@@ -154,7 +181,7 @@
         serviceNames.add(serviceName);
         ListView<String> result = new ListView<>();
         result.setData(serviceNames);
-        when(namingServiceWrapper.getServicesOfServer(anyInt(), anyInt(), anyString())).thenReturn(result);
+        when(namingServiceWrapper.getServicesOfServer(anyInt(), anyInt(), eq(group))).thenReturn(result);
         Set<String> services = nacosServiceDiscovery.getServices();
         assertEquals(2, services.size());
     }
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 9bba269..d427cea 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
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.dubbo.registry.zookeeper;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
@@ -32,6 +33,7 @@
 import org.apache.dubbo.rpc.RpcException;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -56,7 +58,7 @@
  */
 public class ZookeeperRegistry extends CacheableFailbackRegistry {
 
-    private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ZookeeperRegistry.class);
 
     private static final String DEFAULT_ROOT = "dubbo";
 
@@ -70,23 +72,28 @@
 
     public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
         super(url);
+
         if (url.isAnyHost()) {
             throw new IllegalStateException("registry address == null");
         }
+
         String group = url.getGroup(DEFAULT_ROOT);
         if (!group.startsWith(PATH_SEPARATOR)) {
             group = PATH_SEPARATOR + group;
         }
+
         this.root = group;
         this.zkClient = zookeeperTransporter.connect(url);
+
         this.zkClient.addStateListener((state) -> {
             if (state == StateListener.RECONNECTED) {
-                logger.warn("Trying to fetch the latest urls, in case there're provider changes during connection loss.\n" +
+                logger.warn("Trying to fetch the latest urls, in case there are provider changes during connection loss.\n" +
                     " Since ephemeral ZNode will not get deleted for a connection lose, " +
                     "there's no need to re-register url of this instance.");
                 ZookeeperRegistry.this.fetchLatestAddresses();
             } else if (state == StateListener.NEW_SESSION_CREATED) {
                 logger.warn("Trying to re-register urls and re-subscribe listeners of this instance to registry...");
+
                 try {
                     ZookeeperRegistry.this.recover();
                 } catch (Exception e) {
@@ -111,6 +118,29 @@
     @Override
     public void destroy() {
         super.destroy();
+
+        // remove child listener
+        Set<URL> urls = zkListeners.keySet();
+        for (URL url : urls) {
+            ConcurrentMap<NotifyListener, ChildListener> map = zkListeners.get(url);
+            if (CollectionUtils.isEmptyMap(map)) {
+                continue;
+            }
+            Collection<ChildListener> childListeners = map.values();
+            if (CollectionUtils.isEmpty(childListeners)) {
+                continue;
+            }
+            if (ANY_VALUE.equals(url.getServiceInterface())) {
+                String root = toRootPath();
+                childListeners.stream().forEach(childListener -> zkClient.removeChildListener(root, childListener));
+            } else {
+                for (String path : toCategoriesPath(url)) {
+                    childListeners.stream().forEach(childListener -> zkClient.removeChildListener(path, childListener));
+                }
+            }
+        }
+        zkListeners.clear();
+
         // Just release zkClient reference, but can not close zk client here for zk client is shared somewhere else.
         // See org.apache.dubbo.remoting.zookeeper.AbstractZookeeperTransporter#destroy()
         zkClient = null;
@@ -150,6 +180,7 @@
                 String root = toRootPath();
                 boolean check = url.getParameter(CHECK_KEY, false);
                 ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
+
                 ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChildren) -> {
                     for (String child : currentChildren) {
                         child = URL.decode(child);
@@ -160,7 +191,9 @@
                         }
                     }
                 });
+
                 zkClient.create(root, false);
+
                 List<String> services = zkClient.addChildListener(root, zkListener);
                 if (CollectionUtils.isNotEmpty(services)) {
                     for (String service : services) {
@@ -172,20 +205,37 @@
                 }
             } else {
                 CountDownLatch latch = new CountDownLatch(1);
+
                 try {
                     List<URL> urls = new ArrayList<>();
+
+                    /*
+                        Iterate over the category value in URL.
+                        With default settings, the path variable can be when url is a consumer URL:
+
+                            /dubbo/[service name]/providers,
+                            /dubbo/[service name]/configurators
+                            /dubbo/[service name]/routers
+                    */
                     for (String path : toCategoriesPath(url)) {
                         ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                         ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));
+
                         if (zkListener instanceof RegistryChildListenerImpl) {
                             ((RegistryChildListenerImpl) zkListener).setLatch(latch);
                         }
+
+                        // create "directories".
                         zkClient.create(path, false);
+
+                        // Add children (i.e. service items).
                         List<String> children = zkClient.addChildListener(path, zkListener);
                         if (children != null) {
+                            // The invocation point that may cause 1-1.
                             urls.addAll(toUrlsWithEmpty(url, path, children));
                         }
                     }
+
                     notify(url, listener, urls);
                 } finally {
                     // tells the listener to run only after the sync notification of main thread finishes.
@@ -199,6 +249,7 @@
 
     @Override
     public void doUnsubscribe(URL url, NotifyListener listener) {
+        super.doUnsubscribe(url, listener);
         checkDestroyed();
         ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
         if (listeners != null) {
@@ -282,12 +333,12 @@
     }
 
     /**
-     * When zookeeper connection recovered from a connection loss, it need to fetch the latest provider list.
+     * When zookeeper connection recovered from a connection loss, it needs to fetch the latest provider list.
      * re-register watcher is only a side effect and is not mandate.
      */
     private void fetchLatestAddresses() {
         // subscribe
-        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
+        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<>(getSubscribed());
         if (!recoverSubscribed.isEmpty()) {
             if (logger.isInfoEnabled()) {
                 logger.info("Fetching the latest urls of " + recoverSubscribed.keySet());
@@ -307,6 +358,11 @@
         return UrlUtils.isMatch(subscribeUrl, providerUrl);
     }
 
+    /**
+     * Triggered when children get changed. It will be invoked by implementation of CuratorWatcher.
+     *
+     * 'org.apache.dubbo.remoting.zookeeper.curator5.Curator5ZookeeperClient.CuratorWatcherImpl' (Curator 5)
+     */
     private class RegistryChildListenerImpl implements ChildListener {
         private final ZookeeperRegistryNotifier notifier;
         private volatile CountDownLatch latch;
@@ -322,11 +378,13 @@
 
         @Override
         public void childChanged(String path, List<String> children) {
+            // Notify 'notifiers' one by one.
             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(path, 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 11276f4..9e2aea2 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
@@ -192,9 +192,9 @@
                 logger.error("Trying to recover from new zkClient session failed, path is " + path + ", error msg: " + e.getMessage());
             }
 
-            List<ServiceInstance> instances = this.getInstances(serviceName);
+            List<ServiceInstance> instances = this.getInstances(watcher.getServiceName());
             for (ServiceInstancesChangedListener listener : listeners) {
-                listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
+                listener.onEvent(new ServiceInstancesChangedEvent(watcher.getServiceName(), instances));
             }
             latch.countDown();
         });
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
index 465d7dd..681019c 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java
@@ -117,4 +117,8 @@
     public void setLatch(CountDownLatch latch) {
         this.latch = latch;
     }
+
+    public String getServiceName() {
+        return serviceName;
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java
index 7a36a74..12e6fa0 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java
@@ -17,8 +17,9 @@
 package org.apache.dubbo.registry.zookeeper.util;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.zookeeper.ZookeeperInstance;
@@ -77,7 +78,7 @@
             .connectString(connectionURL.getBackupAddress())
             .retryPolicy(buildRetryPolicy(connectionURL));
         String userInformation = connectionURL.getUserInformation();
-        if (userInformation != null && userInformation.length() > 0) {
+        if (StringUtils.isNotEmpty(userInformation)) {
             builder = builder.authorization("digest", userInformation.getBytes());
             builder.aclProvider(new ACLProvider() {
                 @Override
@@ -169,7 +170,7 @@
     }
 
     private static class CuratorConnectionStateListener implements ConnectionStateListener {
-        private static final Logger logger = LoggerFactory.getLogger(CuratorConnectionStateListener.class);
+        private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CuratorConnectionStateListener.class);
         private final long UNKNOWN_SESSION_ID = -1L;
         protected final int DEFAULT_CONNECTION_TIMEOUT_MS = 30 * 1000;
         protected final int DEFAULT_SESSION_TIMEOUT_MS = 60 * 1000;
diff --git a/dubbo-remoting/dubbo-remoting-api/pom.xml b/dubbo-remoting/dubbo-remoting-api/pom.xml
index 56f6b4f..c9b0682 100644
--- a/dubbo-remoting/dubbo-remoting-api/pom.xml
+++ b/dubbo-remoting/dubbo-remoting-api/pom.xml
@@ -50,5 +50,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Constants.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Constants.java
index 5bd80e7..02186be 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Constants.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Constants.java
@@ -79,7 +79,7 @@
 
     String SERIALIZATION_KEY = "serialization";
 
-    String DEFAULT_REMOTING_SERIALIZATION = "hessian2";
+    String DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY = "DUBBO_DEFAULT_SERIALIZATION";
 
     String CODEC_KEY = "codec";
 
@@ -87,6 +87,8 @@
 
     String SERVER_KEY = "server";
 
+    String IS_PU_SERVER_KEY = "ispuserver";
+
     String CLIENT_KEY = "client";
 
     String DEFAULT_REMOTING_CLIENT = "netty";
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2WireProtocol.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/AbstractWireProtocol.java
similarity index 68%
rename from dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2WireProtocol.java
rename to dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/AbstractWireProtocol.java
index 90e617e..278712b 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2WireProtocol.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/AbstractWireProtocol.java
@@ -16,14 +16,18 @@
  */
 package org.apache.dubbo.remoting.api;
 
-import io.netty.handler.codec.http2.Http2FrameLogger;
+import org.apache.dubbo.common.URL;
 
-import static io.netty.handler.logging.LogLevel.DEBUG;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.ssl.SslContext;
 
-public abstract class Http2WireProtocol implements WireProtocol {
-    public static final Http2FrameLogger CLIENT_LOGGER = new Http2FrameLogger(DEBUG, "H2_CLIENT");
-    public static final Http2FrameLogger SERVER_LOGGER = new Http2FrameLogger(DEBUG, "H2_SERVER");
-    private final ProtocolDetector detector = new Http2ProtocolDetector();
+public abstract class AbstractWireProtocol implements WireProtocol {
+
+    private final ProtocolDetector detector;
+
+    public AbstractWireProtocol(ProtocolDetector detector) {
+        this.detector = detector;
+    }
 
     @Override
     public ProtocolDetector detector() {
@@ -31,6 +35,11 @@
     }
 
     @Override
+    public void configClientPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext) {
+
+    }
+
+    @Override
     public void close() {
     }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java
deleted file mode 100644
index 634b406..0000000
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.remoting.api;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.io.Bytes;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.group.ChannelGroup;
-import io.netty.handler.codec.ByteToMessageDecoder;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslHandler;
-
-import java.util.List;
-import java.util.Set;
-
-public class PortUnificationServerHandler extends ByteToMessageDecoder {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(
-        PortUnificationServerHandler.class);
-
-    private final ChannelGroup channels;
-
-    private final SslContext sslCtx;
-    private final URL url;
-    private final boolean detectSsl;
-    private final List<WireProtocol> protocols;
-
-    public PortUnificationServerHandler(URL url, SslContext sslCtx, boolean detectSsl,
-        List<WireProtocol> protocols, ChannelGroup channels) {
-        this.url = url;
-        this.sslCtx = sslCtx;
-        this.protocols = protocols;
-        this.detectSsl = detectSsl;
-        this.channels = channels;
-    }
-
-    @Override
-    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
-        LOGGER.error("Unexpected exception from downstream before protocol detected.", cause);
-    }
-
-    @Override
-    public void channelActive(ChannelHandlerContext ctx) throws Exception {
-        super.channelActive(ctx);
-        channels.add(ctx.channel());
-    }
-
-    @Override
-    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
-        throws Exception {
-        // Will use the first five bytes to detect a protocol.
-        if (in.readableBytes() < 5) {
-            return;
-        }
-
-        if (isSsl(in)) {
-            enableSsl(ctx);
-        } else {
-            for (final WireProtocol protocol : protocols) {
-                in.markReaderIndex();
-                final ProtocolDetector.Result result = protocol.detector().detect(ctx, in);
-                in.resetReaderIndex();
-                switch (result) {
-                    case UNRECOGNIZED:
-                        continue;
-                    case RECOGNIZED:
-                        protocol.configServerPipeline(url, ctx.pipeline(), sslCtx);
-                        ctx.pipeline().remove(this);
-                    case NEED_MORE_DATA:
-                        return;
-                    default:
-                        return;
-                }
-            }
-            byte[] preface = new byte[in.readableBytes()];
-            in.readBytes(preface);
-            Set<String> supported = url.getApplicationModel()
-                .getExtensionLoader(WireProtocol.class)
-                .getSupportedExtensions();
-            LOGGER.error(String.format("Can not recognize protocol from downstream=%s . "
-                    + "preface=%s protocols=%s", ctx.channel().remoteAddress(),
-                Bytes.bytes2hex(preface),
-                supported));
-
-            // Unknown protocol; discard everything and close the connection.
-            in.clear();
-            ctx.close();
-        }
-    }
-
-    private void enableSsl(ChannelHandlerContext ctx) {
-        ChannelPipeline p = ctx.pipeline();
-        p.addLast("ssl", sslCtx.newHandler(ctx.alloc()));
-        p.addLast("unificationA",
-            new PortUnificationServerHandler(url, sslCtx, false, protocols, channels));
-        p.remove(this);
-    }
-
-    private boolean isSsl(ByteBuf buf) {
-        if (detectSsl) {
-            return SslHandler.isEncrypted(buf);
-        }
-        return false;
-    }
-
-
-}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ProtocolDetector.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ProtocolDetector.java
index b868b7a..856d0f1 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ProtocolDetector.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ProtocolDetector.java
@@ -17,8 +17,8 @@
 package org.apache.dubbo.remoting.api;
 
 
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+
 
 /**
  * Determine incoming bytes belong to the specific protocol.
@@ -26,7 +26,7 @@
  */
 public interface ProtocolDetector {
 
-    Result detect(final ChannelHandlerContext ctx, final ByteBuf in);
+    Result detect(ChannelBuffer in);
 
     enum Result {
         RECOGNIZED, UNRECOGNIZED, NEED_MORE_DATA
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/WireProtocol.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/WireProtocol.java
index 96f7459..fe615f8 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/WireProtocol.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/WireProtocol.java
@@ -19,6 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
 
 import io.netty.channel.ChannelPipeline;
 import io.netty.handler.ssl.SslContext;
@@ -28,7 +29,7 @@
 
     ProtocolDetector detector();
 
-    void configServerPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext);
+    void configServerProtocolHandler(URL url, ChannelOperator operator);
 
     void configClientPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext);
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java
new file mode 100644
index 0000000..9a6ac62
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java
@@ -0,0 +1,73 @@
+/*
+ * 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.remoting.api.pu;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.api.WireProtocol;
+import org.apache.dubbo.remoting.transport.AbstractServer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class AbstractPortUnificationServer extends AbstractServer {
+    private final List<WireProtocol> protocols;
+
+    /*
+    protocol name --> URL object
+    wire protocol will get url object to config server pipeline for channel
+     */
+    private final Map<String, URL> supportedUrls = new ConcurrentHashMap<>();
+
+    /*
+    protocol name --> ChannelHandler object
+    wire protocol will get handler to config server pipeline for channel
+    (for triple protocol, it's a default handler that do nothing)
+     */
+    private final Map<String, ChannelHandler> supportedHandlers = new ConcurrentHashMap<>();
+
+    public AbstractPortUnificationServer(URL url, ChannelHandler handler) throws RemotingException {
+        super(url, handler);
+        this.protocols = url.getOrDefaultFrameworkModel().getExtensionLoader(WireProtocol.class).getActivateExtension(url, new String[0]);
+    }
+
+    public List<WireProtocol> getProtocols() {
+        return protocols;
+    }
+
+    /*
+    This method registers URL object and corresponding channel handler to pu server.
+    In PuServerExchanger.bind, this method is called with ConcurrentHashMap.computeIfPresent to register messages to
+    this supportedUrls and supportedHandlers
+     */
+    public void addSupportedProtocol(URL url, ChannelHandler handler) {
+        this.supportedUrls.put(url.getProtocol(), url);
+        this.supportedHandlers.put(url.getProtocol(), handler);
+    }
+
+    protected Map<String, URL> getSupportedUrls() {
+        // this getter is just used by implementation of this class
+        return supportedUrls;
+    }
+
+    public Map<String, ChannelHandler> getSupportedHandlers() {
+        // this getter is just used by implementation of this class
+        return supportedHandlers;
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelHandlerPretender.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelHandlerPretender.java
new file mode 100644
index 0000000..b17dd7c
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelHandlerPretender.java
@@ -0,0 +1,32 @@
+/*
+ * 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.remoting.api.pu;
+
+import org.apache.dubbo.remoting.transport.ChannelHandlerAdapter;
+
+public class ChannelHandlerPretender extends ChannelHandlerAdapter {
+    private final Object realHandler;
+
+    public ChannelHandlerPretender(Object realHandler) {
+        this.realHandler = realHandler;
+    }
+
+    public Object getRealHandler() {
+        return realHandler;
+    }
+
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelOperator.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelOperator.java
new file mode 100644
index 0000000..82a3bde
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/ChannelOperator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.remoting.api.pu;
+
+import org.apache.dubbo.remoting.ChannelHandler;
+
+import java.util.List;
+
+public interface ChannelOperator {
+    void configChannelHandler(List<ChannelHandler> handlerList);
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultCodec.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultCodec.java
new file mode 100644
index 0000000..4756dd3
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultCodec.java
@@ -0,0 +1,35 @@
+/*
+ * 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.remoting.api.pu;
+
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.Codec2;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+
+import java.io.IOException;
+
+public class DefaultCodec implements Codec2 {
+    @Override
+    public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
+
+    }
+
+    @Override
+    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
+        return null;
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultPuHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultPuHandler.java
new file mode 100644
index 0000000..0e021f9
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/DefaultPuHandler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.remoting.api.pu;
+
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.RemotingException;
+
+public class DefaultPuHandler implements ChannelHandler {
+    @Override
+    public void connected(Channel channel) throws RemotingException {
+
+    }
+
+    @Override
+    public void disconnected(Channel channel) throws RemotingException {
+
+    }
+
+    @Override
+    public void sent(Channel channel, Object message) throws RemotingException {
+
+    }
+
+    @Override
+    public void received(Channel channel, Object message) throws RemotingException {
+
+    }
+
+    @Override
+    public void caught(Channel channel, Throwable exception) throws RemotingException {
+
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/PortUnificationTransporter.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/PortUnificationTransporter.java
new file mode 100644
index 0000000..655e91f
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/PortUnificationTransporter.java
@@ -0,0 +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.remoting.api.pu;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Adaptive;
+import org.apache.dubbo.common.extension.ExtensionScope;
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.Client;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.remoting.RemotingException;
+
+@SPI(value = "netty", scope = ExtensionScope.FRAMEWORK)
+public interface PortUnificationTransporter {
+
+    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
+    AbstractPortUnificationServer bind(URL url, ChannelHandler handler) throws RemotingException;
+
+    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
+    Client connect(URL url, ChannelHandler handler) throws RemotingException;
+
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/buffer/ChannelBuffers.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/buffer/ChannelBuffers.java
index 1c0d970..cc62925 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/buffer/ChannelBuffers.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/buffer/ChannelBuffers.java
@@ -114,6 +114,28 @@
         return true;
     }
 
+    // prefix
+    public static boolean prefixEquals(ChannelBuffer bufferA, ChannelBuffer bufferB, int count) {
+        final int aLen = bufferA.readableBytes();
+        final int bLen = bufferB.readableBytes();
+        if (aLen < count || bLen < count) {
+            return false;
+        }
+
+        int aIndex = bufferA.readerIndex();
+        int bIndex = bufferB.readerIndex();
+
+        for (int i = count; i > 0; i--) {
+            if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) {
+                return false;
+            }
+            aIndex++;
+            bIndex++;
+        }
+
+        return true;
+    }
+
     public static int hasCode(ChannelBuffer buffer) {
         final int aLen = buffer.readableBytes();
         final int byteCount = aLen & 7;
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
index fc00d4c..10fec10 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
@@ -19,7 +19,11 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.remoting.api.PortUnificationServer;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.RemotingServer;
+import org.apache.dubbo.remoting.api.pu.AbstractPortUnificationServer;
+import org.apache.dubbo.remoting.api.pu.PortUnificationTransporter;
 
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
@@ -28,20 +32,31 @@
 public class PortUnificationExchanger {
 
     private static final Logger log = LoggerFactory.getLogger(PortUnificationExchanger.class);
-    private static final ConcurrentMap<String, PortUnificationServer> servers = new ConcurrentHashMap<>();
+    private static final ConcurrentMap<String, RemotingServer> servers = new ConcurrentHashMap<>();
 
-    public static void bind(URL url) {
+    public static RemotingServer bind(URL url, ChannelHandler handler) {
         servers.computeIfAbsent(url.getAddress(), addr -> {
-            final PortUnificationServer server = new PortUnificationServer(url);
-            server.bind();
+            final AbstractPortUnificationServer server;
+            try {
+                server = getTransporter(url).bind(url, handler);
+            } catch (RemotingException e) {
+                throw new RuntimeException(e);
+            }
+            // server.bind();
             return server;
         });
+
+        servers.computeIfPresent(url.getAddress(), (addr, server) -> {
+            ((AbstractPortUnificationServer) server).addSupportedProtocol(url, handler);
+            return server;
+        });
+        return servers.get(url.getAddress());
     }
 
     public static void close() {
-        final ArrayList<PortUnificationServer> toClose = new ArrayList<>(servers.values());
+        final ArrayList<RemotingServer> toClose = new ArrayList<>(servers.values());
         servers.clear();
-        for (PortUnificationServer server : toClose) {
+        for (RemotingServer server : toClose) {
             try {
                 server.close();
             } catch (Throwable throwable) {
@@ -51,7 +66,13 @@
     }
 
     // for test
-    public static ConcurrentMap<String, PortUnificationServer> getServers() {
+    public static ConcurrentMap<String, RemotingServer> getServers() {
         return servers;
     }
+
+    public static PortUnificationTransporter getTransporter(URL url) {
+        return url.getOrDefaultFrameworkModel().getExtensionLoader(PortUnificationTransporter.class)
+            .getAdaptiveExtension();
+    }
+
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchanger.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchanger.java
index 90f7c98..67f2440 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchanger.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchanger.java
@@ -23,8 +23,11 @@
 import org.apache.dubbo.remoting.exchange.ExchangeHandler;
 import org.apache.dubbo.remoting.exchange.ExchangeServer;
 import org.apache.dubbo.remoting.exchange.Exchanger;
+import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
 import org.apache.dubbo.remoting.transport.DecodeHandler;
 
+import static org.apache.dubbo.remoting.Constants.IS_PU_SERVER_KEY;
+
 /**
  * DefaultMessenger
  *
@@ -41,7 +44,14 @@
 
     @Override
     public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
-        return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
+        ExchangeServer server;
+        boolean isPuServerKey = url.getParameter(IS_PU_SERVER_KEY, false);
+        if(isPuServerKey) {
+            server = new HeaderExchangeServer(PortUnificationExchanger.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
+        }else {
+            server = new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
+        }
+        return server;
     }
 
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/StatusTelnetHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/StatusTelnetHandler.java
index 8d46767..946362e 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/StatusTelnetHandler.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/telnet/support/command/StatusTelnetHandler.java
@@ -27,6 +27,8 @@
 import org.apache.dubbo.remoting.telnet.TelnetHandler;
 import org.apache.dubbo.remoting.telnet.support.Help;
 import org.apache.dubbo.remoting.telnet.support.TelnetUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelUtil;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -43,10 +45,10 @@
 @Help(parameter = "[-l]", summary = "Show status.", detail = "Show status.")
 public class StatusTelnetHandler implements TelnetHandler {
 
-    private final ExtensionLoader<StatusChecker> extensionLoader = ExtensionLoader.getExtensionLoader(StatusChecker.class);
-
     @Override
     public String telnet(Channel channel, String message) {
+        ApplicationModel applicationModel = ScopeModelUtil.getApplicationModel(channel.getUrl().getScopeModel());
+        ExtensionLoader<StatusChecker> extensionLoader = applicationModel.getExtensionLoader(StatusChecker.class);
         if ("-l".equals(message)) {
             List<StatusChecker> checkers = extensionLoader.getActivateExtension(channel.getUrl(), STATUS_KEY);
             String[] header = new String[]{"resource", "status", "message"};
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java
index dee60da..2835326 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java
@@ -44,4 +44,9 @@
     public String toString() {
         return getLocalAddress() + " -> " + getRemoteAddress();
     }
+
+    @Override
+    protected void setUrl(URL url) {
+        super.setUrl(url);
+    }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractEndpoint.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractEndpoint.java
index fbba378..4e0af74 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractEndpoint.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractEndpoint.java
@@ -56,9 +56,11 @@
         FrameworkModel frameworkModel = getFrameworkModel(url.getScopeModel());
         if (frameworkModel.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
             return frameworkModel.getExtensionLoader(Codec2.class).getExtension(codecName);
-        } else {
+        } else if(frameworkModel.getExtensionLoader(Codec.class).hasExtension(codecName)){
             return new CodecAdapter(frameworkModel.getExtensionLoader(Codec.class)
                 .getExtension(codecName));
+        }else {
+            return frameworkModel.getExtensionLoader(Codec2.class).getExtension("default");
         }
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/CodecSupport.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/CodecSupport.java
index a963f2c..5f27886 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/CodecSupport.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/CodecSupport.java
@@ -24,6 +24,7 @@
 import org.apache.dubbo.common.serialize.ObjectInput;
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -83,7 +84,7 @@
 
     public static Serialization getSerialization(URL url) {
         return url.getOrDefaultFrameworkModel().getExtensionLoader(Serialization.class).getExtension(
-                url.getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION));
+                url.getParameter(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization()));
     }
 
     public static Serialization getSerialization(URL url, Byte id) throws IOException {
@@ -168,7 +169,7 @@
         } else {
             boolean match = false;
             for (URL url : urls) {
-                String serializationName = url.getParameter(org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+                String serializationName = url.getParameter(org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
                 Byte localId = SERIALIZATIONNAME_ID_MAP.get(serializationName);
                 if (localId != null && localId.equals(id)) {
                     match = true;
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
index 4f0936d..a82a634 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/dispatcher/WrappedChannelHandler.java
@@ -75,15 +75,19 @@
     }
 
     protected void sendFeedback(Channel channel, Request request, Throwable t) throws RemotingException {
-        if (request.isTwoWay()) {
-            String msg = "Server side(" + url.getIp() + "," + url.getPort()
-                    + ") thread pool is exhausted, detail msg:" + t.getMessage();
-            Response response = new Response(request.getId(), request.getVersion());
-            response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
-            response.setErrorMessage(msg);
-            channel.send(response);
+
+        if (!request.isTwoWay()) {
             return;
         }
+
+        String msg = "Server side(" + url.getIp() + "," + url.getPort()
+                + ") thread pool is exhausted, detail msg:" + t.getMessage();
+
+        Response response = new Response(request.getId(), request.getVersion());
+        response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
+        response.setErrorMessage(msg);
+
+        channel.send(response);
     }
 
     @Override
@@ -140,12 +144,16 @@
 
         // note: url.getOrDefaultApplicationModel() may create new application model
         ApplicationModel applicationModel = url.getOrDefaultApplicationModel();
+
         ExecutorRepository executorRepository =
                 applicationModel.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+
         ExecutorService executor = executorRepository.getExecutor(url);
+
         if (executor == null) {
             executor = executorRepository.createExecutorIfAbsent(url);
         }
+
         return executor;
     }
 
@@ -154,5 +162,4 @@
         return getSharedExecutorService();
     }
 
-
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
index fdb6d88..bb001da 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java
@@ -22,10 +22,24 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
+/**
+ * Common abstraction of Zookeeper client.
+ */
 public interface ZookeeperClient {
 
+    /**
+     * Create ZNode in Zookeeper.
+     *
+     * @param path path to ZNode
+     * @param ephemeral specify create mode of ZNode creation. true - EPHEMERAL, false - PERSISTENT.
+     */
     void create(String path, boolean ephemeral);
 
+    /**
+     * Delete ZNode.
+     *
+     * @param path path to ZNode
+     */
     void delete(String path);
 
     List<String> getChildren(String path);
@@ -33,18 +47,28 @@
     List<String> addChildListener(String path, ChildListener listener);
 
     /**
-     * @param path:    directory. All child of path will be listened.
-     * @param listener
+     * Attach data listener to current Zookeeper client.
+     *
+     * @param path    directory. All children of path will be listened.
+     * @param listener The data listener object.
      */
     void addDataListener(String path, DataListener listener);
 
     /**
-     * @param path:    directory. All child of path will be listened.
-     * @param listener
-     * @param executor another thread
+     * Attach data listener to current Zookeeper client. The listener will be executed using the given executor.
+     *
+     * @param path    directory. All children of path will be listened.
+     * @param listener The data listener object.
+     * @param executor the executor that will execute the listener.
      */
     void addDataListener(String path, DataListener listener, Executor executor);
 
+    /**
+     * Detach data listener.
+     *
+     * @param path    directory. All listener of children of the path will be detached.
+     * @param listener The data listener object.
+     */
     void removeDataListener(String path, DataListener listener);
 
     void removeChildListener(String path, ChildListener listener);
@@ -53,16 +77,37 @@
 
     void removeStateListener(StateListener listener);
 
+    /**
+     * Check the Zookeeper client whether connected to server or not.
+     *
+     * @return true if connected
+     */
     boolean isConnected();
 
+    /**
+     * Close connection to Zookeeper server (cluster).
+     */
     void close();
 
     URL getUrl();
 
+    /**
+     * Create ZNode in Zookeeper with content specified.
+     *
+     * @param path path to ZNode
+     * @param content the content of ZNode
+     * @param ephemeral specify create mode of ZNode creation. true - EPHEMERAL, false - PERSISTENT.
+     */
     void create(String path, String content, boolean ephemeral);
 
     void createOrUpdate(String path, String content, boolean ephemeral, int ticket);
 
+    /**
+     * Obtain the content of a ZNode.
+     *
+     * @param path path to ZNode
+     * @return content of ZNode
+     */
     String getContent(String path);
 
     ConfigItem getConfigItem(String path);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2 b/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2
index b4ba960..98046f8 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2
@@ -1,3 +1,4 @@
 transport=org.apache.dubbo.remoting.transport.codec.TransportCodec
 telnet=org.apache.dubbo.remoting.telnet.codec.TelnetCodec
-exchange=org.apache.dubbo.remoting.exchange.codec.ExchangeCodec
\ No newline at end of file
+exchange=org.apache.dubbo.remoting.exchange.codec.ExchangeCodec
+default=org.apache.dubbo.remoting.api.pu.DefaultCodec
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/ChanelHandlerTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/ChanelHandlerTest.java
index 4d78c7d..0045415 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/ChanelHandlerTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/ChanelHandlerTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.exchange.ExchangeClient;
 import org.apache.dubbo.remoting.exchange.Exchangers;
 import org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter;
@@ -81,7 +82,7 @@
         }
         final String server = System.getProperty("server", "127.0.0.1:9911");
         final String transporter = PerformanceUtils.getProperty(Constants.TRANSPORTER_KEY, Constants.DEFAULT_TRANSPORTER);
-        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
         final int timeout = PerformanceUtils.getIntProperty(TIMEOUT_KEY, DEFAULT_TIMEOUT);
         int sleep = PerformanceUtils.getIntProperty("sleep", 60 * 1000 * 60);
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientCloseTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientCloseTest.java
index e7f53f6..f81272a 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientCloseTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientCloseTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.exchange.ExchangeClient;
 import org.apache.dubbo.remoting.exchange.Exchangers;
 
@@ -45,7 +46,7 @@
         }
         final String server = System.getProperty("server", "127.0.0.1:9911");
         final String transporter = PerformanceUtils.getProperty(Constants.TRANSPORTER_KEY, Constants.DEFAULT_TRANSPORTER);
-        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
         final int timeout = PerformanceUtils.getIntProperty(TIMEOUT_KEY, DEFAULT_TIMEOUT);
         final int concurrent = PerformanceUtils.getIntProperty("concurrent", 1);
         final int runs = PerformanceUtils.getIntProperty("runs", Integer.MAX_VALUE);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientFixedTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientFixedTest.java
index 0b1a04e..82f0140 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientFixedTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientFixedTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.exchange.ExchangeClient;
 import org.apache.dubbo.remoting.exchange.Exchangers;
 
@@ -43,7 +44,7 @@
         }
         final String server = System.getProperty("server", "127.0.0.1:9911");
         final String transporter = PerformanceUtils.getProperty(Constants.TRANSPORTER_KEY, Constants.DEFAULT_TRANSPORTER);
-        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
         final int timeout = PerformanceUtils.getIntProperty(TIMEOUT_KEY, DEFAULT_TIMEOUT);
         //final int length = PerformanceUtils.getIntProperty("length", 1024);
         final int connectionCount = PerformanceUtils.getIntProperty(CONNECTIONS_KEY, 1);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientTest.java
index 67a1676..d5cffb2 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceClientTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.exchange.ExchangeClient;
 import org.apache.dubbo.remoting.exchange.Exchangers;
 import org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter;
@@ -55,7 +56,7 @@
         }
         final String server = System.getProperty("server", "127.0.0.1:9911");
         final String transporter = PerformanceUtils.getProperty(Constants.TRANSPORTER_KEY, Constants.DEFAULT_TRANSPORTER);
-        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
         final int timeout = PerformanceUtils.getIntProperty(TIMEOUT_KEY, DEFAULT_TIMEOUT);
         final int length = PerformanceUtils.getIntProperty("length", 1024);
         final int connections = PerformanceUtils.getIntProperty(CONNECTIONS_KEY, 1);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceServerTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceServerTest.java
index 08e26c9..11e378d 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceServerTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/PerformanceServerTest.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.exchange.ExchangeChannel;
 import org.apache.dubbo.remoting.exchange.ExchangeServer;
 import org.apache.dubbo.remoting.exchange.Exchangers;
@@ -68,7 +69,7 @@
     private static ExchangeServer statServer() throws Exception {
         final int port = PerformanceUtils.getIntProperty("port", 9911);
         final String transporter = PerformanceUtils.getProperty(Constants.TRANSPORTER_KEY, Constants.DEFAULT_TRANSPORTER);
-        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION);
+        final String serialization = PerformanceUtils.getProperty(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization());
         final String threadpool = PerformanceUtils.getProperty(THREADPOOL_KEY, DEFAULT_THREADPOOL);
         final int threads = PerformanceUtils.getIntProperty(THREADS_KEY, DEFAULT_THREADS);
         final int iothreads = PerformanceUtils.getIntProperty(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/ConnectionTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/ConnectionTest.java
index 9f46576..5b6bd48 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/ConnectionTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/ConnectionTest.java
@@ -17,14 +17,11 @@
 package org.apache.dubbo.remoting.api;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.NetUtils;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 class ConnectionTest {
 
@@ -59,67 +56,4 @@
         Assertions.assertEquals(0, latch.getCount());
     }
 
-    @Test
-    public void connectSyncTest() throws Throwable {
-        int port = NetUtils.getAvailablePort();
-        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
-        PortUnificationServer server = null;
-        try {
-            server = new PortUnificationServer(url);
-            server.bind();
-
-            Connection connection = new Connection(url);
-            Assertions.assertTrue(connection.isAvailable());
-
-            server.close();
-            Assertions.assertFalse(connection.isAvailable());
-
-            server.bind();
-            // auto reconnect
-            Assertions.assertTrue(connection.isAvailable());
-
-            connection.close();
-            Assertions.assertFalse(connection.isAvailable());
-        } finally {
-            try {
-                server.close();
-            } catch (Throwable e) {
-                // ignored
-            }
-        }
-
-
-    }
-
-    @Test
-    public void testMultiConnect() throws Throwable {
-        int port = NetUtils.getAvailablePort();
-        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
-        PortUnificationServer server = null;
-        try {
-            server = new PortUnificationServer(url);
-            server.close();
-
-            Connection connection = new Connection(url);
-            ExecutorService service = Executors.newFixedThreadPool(10);
-            final CountDownLatch latch = new CountDownLatch(10);
-            for (int i = 0; i < 10; i++) {
-                Runnable runnable = () -> {
-                    try {
-                        Assertions.assertTrue(connection.isAvailable());
-                        latch.countDown();
-                    } catch (Exception e) {
-                        // ignore
-                    }
-                };
-                service.execute(runnable);
-            }
-        } finally {
-            try {
-                server.close();
-            } catch (Throwable e) {
-                // ignored
-            }
-        }
-    }
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/EmptyProtocol.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/EmptyProtocol.java
index 7d3b5cf..5c69d9b 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/EmptyProtocol.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/EmptyProtocol.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.remoting.api;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
 
 import io.netty.channel.ChannelPipeline;
 import io.netty.handler.ssl.SslContext;
@@ -28,7 +29,7 @@
     }
 
     @Override
-    public void configServerPipeline(URL url, ChannelPipeline pipeline,SslContext sslContext) {
+    public void configServerProtocolHandler(URL url, ChannelOperator operator) {
 
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/buffer/ChannelBuffersTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/buffer/ChannelBuffersTest.java
index e6811f2..9e71910 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/buffer/ChannelBuffersTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/buffer/ChannelBuffersTest.java
@@ -41,6 +41,15 @@
     }
 
     @Test
+    public void testPrefixEquals(){
+        ChannelBuffer bufA = ChannelBuffers.wrappedBuffer("abcedfaf".getBytes());
+        ChannelBuffer bufB = ChannelBuffers.wrappedBuffer("abcedfaa".getBytes());
+        Assertions.assertTrue(ChannelBuffers.equals(bufA, bufB));
+        Assertions.assertTrue(ChannelBuffers.prefixEquals(bufA, bufB, 7));
+        Assertions.assertFalse(ChannelBuffers.prefixEquals(bufA, bufB, 8));
+    }
+
+    @Test
     public void testBuffer() {
         ChannelBuffer channelBuffer = ChannelBuffers.buffer(DEFAULT_CAPACITY);
         Assertions.assertTrue(channelBuffer instanceof HeapChannelBuffer);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/codec/ExchangeCodecTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/codec/ExchangeCodecTest.java
index 495092d..fd130bb 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/codec/ExchangeCodecTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/codec/ExchangeCodecTest.java
@@ -23,6 +23,7 @@
 import org.apache.dubbo.common.io.UnsafeByteArrayOutputStream;
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.Channel;
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.buffer.ChannelBuffer;
@@ -31,6 +32,7 @@
 import org.apache.dubbo.remoting.exchange.Response;
 import org.apache.dubbo.remoting.exchange.codec.ExchangeCodec;
 import org.apache.dubbo.remoting.telnet.codec.TelnetCodec;
+import org.apache.dubbo.rpc.model.FrameworkModel;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -43,6 +45,7 @@
 import java.util.Map;
 
 import static org.apache.dubbo.common.constants.CommonConstants.READONLY_EVENT;
+import static org.apache.dubbo.common.serialize.Constants.FASTJSON2_SERIALIZATION_ID;
 
 /**
  *
@@ -64,7 +67,8 @@
     private static final short MAGIC = (short) 0xdabb;
     private static final byte MAGIC_HIGH = (byte) Bytes.short2bytes(MAGIC)[0];
     private static final byte MAGIC_LOW = (byte) Bytes.short2bytes(MAGIC)[1];
-    Serialization serialization = getSerialization(Constants.DEFAULT_REMOTING_SERIALIZATION);
+    Serialization serialization = getSerialization(DefaultSerializationSelector.getDefaultRemotingSerialization());
+    private static final byte SERIALIZATION_BYTE = FrameworkModel.defaultModel().getExtension(Serialization.class, DefaultSerializationSelector.getDefaultRemotingSerialization()).getContentTypeId();
 
     private static Serialization getSerialization(String name) {
         Serialization serialization = ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(name);
@@ -137,7 +141,7 @@
 
     @Test
     public void test_Decode_Error_Length() throws IOException {
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 0x02, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
@@ -153,7 +157,7 @@
     @Test
     public void test_Decode_Error_Response_Object() throws IOException {
         //00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 0x02, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
         //bad object
@@ -227,7 +231,7 @@
     @Test
     public void test_Decode_Return_Response_Person() throws IOException {
         //00000010-response/oneway/hearbeat=false/hessian |20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
@@ -239,7 +243,7 @@
 
     @Test //The status input has a problem, and the read information is wrong when the serialization is serialized.
     public void test_Decode_Return_Response_Error() throws IOException {
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 2, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         String errorString = "encode request data error ";
         byte[] request = getRequestBytes(errorString, header);
         Response obj = (Response) decode(request);
@@ -250,7 +254,7 @@
     @Test
     public void test_Decode_Return_Request_Event_Object() throws IOException {
         //|10011111|20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0xe2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) (SERIALIZATION_BYTE | (byte) 0xe0), 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
@@ -267,7 +271,7 @@
     @Test
     public void test_Decode_Return_Request_Event_String() throws IOException {
         //|10011111|20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0xe2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) (SERIALIZATION_BYTE | (byte) 0xe0), 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         String event = READONLY_EVENT;
         byte[] request = getRequestBytes(event, header);
 
@@ -282,7 +286,7 @@
     @Test
     public void test_Decode_Return_Request_Heartbeat_Object() throws IOException {
         //|10011111|20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0xe2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) (SERIALIZATION_BYTE | (byte) 0xe0), 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         byte[] request = getRequestBytes(null, header);
         Request obj = (Request) decode(request);
         Assertions.assertNull(obj.getData());
@@ -295,22 +299,24 @@
     @Test
     public void test_Decode_Return_Request_Object() throws IOException {
         //|10011111|20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0xc2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) (SERIALIZATION_BYTE | (byte) 0xe0), 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
+        System.setProperty("deserialization.event.size", "100");
         Request obj = (Request) decode(request);
         Assertions.assertEquals(person, obj.getData());
         Assertions.assertTrue(obj.isTwoWay());
         Assertions.assertFalse(obj.isHeartbeat());
         Assertions.assertEquals(Version.getProtocolVersion(), obj.getVersion());
         System.out.println(obj);
+        System.clearProperty("deserialization.event.size");
     }
 
     @Test
     public void test_Decode_Error_Request_Object() throws IOException {
         //00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0xe2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) (SERIALIZATION_BYTE | (byte) 0xe0), 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
         //bad object
@@ -325,7 +331,7 @@
     @Test
     public void test_Header_Response_NoSerializationFlag() throws IOException {
         //00000010-response/oneway/hearbeat=false/noset |20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, (byte) 0x02, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
@@ -338,7 +344,7 @@
     @Test
     public void test_Header_Response_Heartbeat() throws IOException {
         //00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
-        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 0x02, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, SERIALIZATION_BYTE, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
         Person person = new Person();
         byte[] request = getRequestBytes(person, header);
 
@@ -471,7 +477,8 @@
             codec.encode(channel, encodeBuffer, request);
             Assertions.fail();
         } catch (IOException e) {
-            Assertions.assertTrue(e.getMessage().startsWith("Data length too large: " + 6));
+            Assertions.assertTrue(e.getMessage().startsWith("Data length too large: "));
+            Assertions.assertTrue(e.getMessage().contains("max payload: 4, channel: org.apache.dubbo.remoting.codec.AbstractMockChannel"));
         }
 
         Response response = new Response(1L);
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/transport/CodecSupportTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/transport/CodecSupportTest.java
index f3e86a6..b8ab5e4 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/transport/CodecSupportTest.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/transport/CodecSupportTest.java
@@ -18,6 +18,8 @@
 
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
+import org.apache.dubbo.remoting.Constants;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -31,7 +33,7 @@
 
     @Test
     public void testHeartbeat() throws Exception {
-        Byte proto = CodecSupport.getIDByName("hessian2");
+        Byte proto = CodecSupport.getIDByName(DefaultSerializationSelector.getDefaultRemotingSerialization());
         Serialization serialization = CodecSupport.getSerializationById(proto);
         byte[] nullBytes = CodecSupport.getNullBytesOf(serialization);
 
diff --git a/dubbo-remoting/dubbo-remoting-netty/pom.xml b/dubbo-remoting/dubbo-remoting-netty/pom.xml
index d048f5b..5d7cefa 100644
--- a/dubbo-remoting/dubbo-remoting-netty/pom.xml
+++ b/dubbo-remoting/dubbo-remoting-netty/pom.xml
@@ -45,5 +45,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyClient.java b/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyClient.java
index 4a163ae..f5e0dc6 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyClient.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyClient.java
@@ -18,7 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.Version;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -39,12 +39,15 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_CLIENT_CONNECT_TIMEOUT;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CONNECT_PROVIDER;
+
 /**
  * NettyClient.
  */
 public class NettyClient extends AbstractClient {
 
-    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NettyClient.class);
 
     // ChannelFactory's closure has a DirectMemory leak, using static to avoid
     // https://issues.jboss.org/browse/NETTY-424
@@ -121,13 +124,26 @@
                     }
                 }
             } else if (future.getCause() != null) {
-                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
-                        + getRemoteAddress() + ", error message is:" + future.getCause().getMessage(), future.getCause());
+                Throwable cause = future.getCause();
+
+                RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+                        + getRemoteAddress() + ", error message is:" + cause.getMessage(), cause);
+
+                // 6-1 - Failed to connect to provider server by other reason.
+                logger.error(TRANSPORT_FAILED_CONNECT_PROVIDER, "network disconnected", "", "Failed to connect to provider server by other reason.", cause);
+
+                throw remotingException;
             } else {
-                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+
+                RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
                         + getRemoteAddress() + " client-side timeout "
                         + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
                         + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
+
+                // 6-2 - Client-side timeout.
+                logger.error(TRANSPORT_CLIENT_CONNECT_TIMEOUT, "provider crash", "", "Client-side timeout.", remotingException);
+
+                throw remotingException;
             }
         } finally {
             if (!isConnected()) {
diff --git a/dubbo-remoting/dubbo-remoting-netty4/pom.xml b/dubbo-remoting/dubbo-remoting-netty4/pom.xml
index 99dd144..af48662 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/pom.xml
+++ b/dubbo-remoting/dubbo-remoting-netty4/pom.xml
@@ -50,5 +50,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java
index 780ae9d..7022146 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java
@@ -94,6 +94,8 @@
             if (ret == null) {
                 ret = nettyChannel;
             }
+        } else {
+            ret.markActive(true);
         }
         return ret;
     }
@@ -231,6 +233,7 @@
         attributes.remove(key);
     }
 
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -240,6 +243,11 @@
     }
 
     @Override
+    protected void setUrl(URL url) {
+        super.setUrl(url);
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (this == obj) {
             return true;
@@ -273,4 +281,7 @@
         return "NettyChannel [channel=" + channel + "]";
     }
 
+    public Channel getNioChannel() {
+        return channel;
+    }
 }
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
index 7298c48..f664e8e 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java
@@ -16,11 +16,11 @@
  */
 package org.apache.dubbo.remoting.transport.netty4;
 
-import io.netty.util.concurrent.EventExecutorGroup;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.config.ConfigurationUtils;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -42,11 +42,14 @@
 import io.netty.channel.socket.SocketChannel;
 import io.netty.handler.proxy.Socks5ProxyHandler;
 import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.concurrent.EventExecutorGroup;
 
 import java.net.InetSocketAddress;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.apache.dubbo.common.constants.CommonConstants.SSL_ENABLED_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_CLIENT_CONNECT_TIMEOUT;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CONNECT_PROVIDER;
 import static org.apache.dubbo.remoting.Constants.DEFAULT_CONNECT_TIMEOUT;
 import static org.apache.dubbo.remoting.api.NettyEventLoopFactory.eventLoopGroup;
 import static org.apache.dubbo.remoting.api.NettyEventLoopFactory.socketChannelClass;
@@ -62,7 +65,7 @@
 
     private static final String DEFAULT_SOCKS_PROXY_PORT = "1080";
 
-    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NettyClient.class);
 
     /**
      * netty client bootstrap
@@ -149,8 +152,33 @@
 
     @Override
     protected void doConnect() throws Throwable {
+        try {
+            String ipv6Address = NetUtils.getLocalHostV6();
+            InetSocketAddress connectAddress;
+            //first try ipv6 address
+            if (ipv6Address != null && getUrl().getParameter(CommonConstants.IPV6_KEY) != null) {
+                connectAddress = new InetSocketAddress(getUrl().getParameter(CommonConstants.IPV6_KEY), getUrl().getPort());
+                try {
+                    doConnect(connectAddress);
+                    return;
+                } catch (Throwable throwable) {
+                    //ignore
+                }
+            }
+
+            connectAddress = getConnectAddress();
+            doConnect(connectAddress);
+        } finally {
+            // just add new valid channel to NettyChannel's cache
+            if (!isConnected()) {
+                //future.cancel(true);
+            }
+        }
+    }
+
+    private void doConnect(InetSocketAddress serverAddress) throws RemotingException {
         long start = System.currentTimeMillis();
-        ChannelFuture future = bootstrap.connect(getConnectAddress());
+        ChannelFuture future = bootstrap.connect(serverAddress);
         try {
             boolean ret = future.awaitUninterruptibly(getConnectTimeout(), MILLISECONDS);
 
@@ -186,13 +214,32 @@
                     }
                 }
             } else if (future.cause() != null) {
-                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
-                        + getRemoteAddress() + ", error message is:" + future.cause().getMessage(), future.cause());
+
+                Throwable cause = future.cause();
+
+                // 6-1 Failed to connect to provider server by other reason.
+
+                RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+                        + serverAddress + ", error message is:" + cause.getMessage(), cause);
+
+                logger.error(TRANSPORT_FAILED_CONNECT_PROVIDER, "network disconnected", "",
+                    "Failed to connect to provider server by other reason.", cause);
+
+                throw remotingException;
+
             } else {
-                throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
-                        + getRemoteAddress() + " client-side timeout "
+
+                // 6-2 Client-side timeout
+
+                RemotingException remotingException = new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+                        + serverAddress + " client-side timeout "
                         + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
                         + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
+
+                logger.error(TRANSPORT_CLIENT_CONNECT_TIMEOUT, "provider crash", "",
+                    "Client-side timeout.", remotingException);
+
+                throw remotingException;
             }
         } finally {
             // just add new valid channel to NettyChannel's cache
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConfigOperator.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConfigOperator.java
new file mode 100644
index 0000000..dca821e
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConfigOperator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.Codec;
+import org.apache.dubbo.remoting.Codec2;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.remoting.api.pu.ChannelHandlerPretender;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
+import org.apache.dubbo.remoting.api.pu.DefaultCodec;
+import org.apache.dubbo.remoting.transport.codec.CodecAdapter;
+
+import java.util.List;
+
+public class NettyConfigOperator implements ChannelOperator {
+
+    private final Channel channel;
+    private ChannelHandler handler;
+
+    public NettyConfigOperator(NettyChannel channel, ChannelHandler handler) {
+        this.channel = channel;
+        this.handler = handler;
+    }
+
+    @Override
+    public void configChannelHandler(List<ChannelHandler> handlerList) {
+        URL url = channel.getUrl();
+        Codec2 codec2;
+        String codecName = url.getParameter(Constants.CODEC_KEY);
+        if (StringUtils.isEmpty(codecName)) {
+            // codec extension name must stay the same with protocol name
+            codecName = url.getProtocol();
+        }
+        if (url.getOrDefaultFrameworkModel().getExtensionLoader(Codec2.class).hasExtension(codecName)) {
+            codec2 = url.getOrDefaultFrameworkModel().getExtensionLoader(Codec2.class).getExtension(codecName);
+        } else if(url.getOrDefaultFrameworkModel().getExtensionLoader(Codec.class).hasExtension(codecName)){
+            codec2 = new CodecAdapter(url.getOrDefaultFrameworkModel().getExtensionLoader(Codec.class)
+                .getExtension(codecName));
+        }else {
+            codec2 = url.getOrDefaultFrameworkModel().getExtensionLoader(Codec2.class).getExtension("default");
+        }
+
+        if (!(codec2 instanceof DefaultCodec)){
+            NettyCodecAdapter codec = new NettyCodecAdapter(codec2, channel.getUrl(), handler);
+            ((NettyChannel) channel).getNioChannel().pipeline().addLast(
+                codec.getDecoder()
+            ).addLast(
+                codec.getEncoder()
+            );
+        }
+
+        for (ChannelHandler handler: handlerList) {
+            if (handler instanceof ChannelHandlerPretender) {
+                Object realHandler = ((ChannelHandlerPretender) handler).getRealHandler();
+                if(realHandler instanceof io.netty.channel.ChannelHandler) {
+                    ((NettyChannel) channel).getNioChannel().pipeline().addLast(
+                        (io.netty.channel.ChannelHandler) realHandler
+                    );
+                }
+            }
+        }
+
+        // todo distinguish between client and server channel
+        if( isClientSide(channel)){
+            //todo config client channel handler
+        }else {
+            NettyServerHandler sh = new NettyServerHandler(channel.getUrl(), handler);
+            ((NettyChannel) channel).getNioChannel().pipeline().addLast(
+                sh
+            );
+        }
+    }
+
+    private boolean isClientSide(Channel channel) {
+        return channel.getUrl().getSide("").equalsIgnoreCase(CommonConstants.CONSUMER);
+    }
+
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
similarity index 66%
rename from dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServer.java
rename to dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
index 51125bd..6a3b926 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServer.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
@@ -14,33 +14,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+package org.apache.dubbo.remoting.transport.netty4;
 
 import org.apache.dubbo.common.URL;
 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.utils.ExecutorUtil;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.ChannelHandler;
 import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.api.NettyEventLoopFactory;
+import org.apache.dubbo.remoting.api.SslContexts;
+import org.apache.dubbo.remoting.api.WireProtocol;
+import org.apache.dubbo.remoting.api.pu.AbstractPortUnificationServer;
+import org.apache.dubbo.remoting.transport.dispatcher.ChannelHandlers;
 
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.buffer.PooledByteBufAllocator;
-import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.EventLoopGroup;
-import io.netty.channel.group.DefaultChannelGroup;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.handler.ssl.SslContext;
 import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GlobalEventExecutor;
 
 import java.net.InetSocketAddress;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
@@ -53,14 +60,9 @@
 /**
  * PortUnificationServer.
  */
-public class PortUnificationServer {
+public class NettyPortUnificationServer extends AbstractPortUnificationServer {
 
-    private static final Logger logger = LoggerFactory.getLogger(PortUnificationServer.class);
-    private final List<WireProtocol> protocols;
-    private final URL url;
-
-    private final DefaultChannelGroup channels = new DefaultChannelGroup(
-        GlobalEventExecutor.INSTANCE);
+    private static final Logger logger = LoggerFactory.getLogger(NettyPortUnificationServer.class);
 
     private final int serverShutdownTimeoutMills;
     /**
@@ -70,41 +72,42 @@
     /**
      * the boss channel that receive connections and dispatch these to worker channel.
      */
-    private Channel channel;
+    private io.netty.channel.Channel channel;
     private EventLoopGroup bossGroup;
     private EventLoopGroup workerGroup;
+    private final Map<String, Channel> dubboChannels = new ConcurrentHashMap<>();
 
-    public PortUnificationServer(URL url) {
+
+    public NettyPortUnificationServer(URL url, ChannelHandler handler) throws RemotingException {
+        super(url, ChannelHandlers.wrap(handler, url));
+
         // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
         // the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler
-        this.url = ExecutorUtil.setThreadName(url, "DubboPUServerHandler");
-        this.protocols = ExtensionLoader.getExtensionLoader(WireProtocol.class)
-            .getActivateExtension(url, new String[0]);
         // read config before destroy
         serverShutdownTimeoutMills = ConfigurationUtils.getServerShutdownTimeout(
             getUrl().getOrDefaultModuleModel());
     }
 
-    public URL getUrl() {
-        return url;
+    @Override
+    public void addSupportedProtocol(URL url, ChannelHandler handler) {
+        super.addSupportedProtocol(url, ChannelHandlers.wrap(handler, url));
     }
 
-    public void bind() {
-        if (channel == null) {
-            doOpen();
-        }
-    }
-
-    public void close() throws Throwable {
+    @Override
+    public void close() {
         if (channel != null) {
             doClose();
         }
     }
 
-    /**
-     * Init and start netty server
-     */
-    protected void doOpen() {
+    public void bind(){
+        if(channel == null) {
+            doOpen();
+        }
+    }
+
+    @Override
+    public void doOpen() {
         bootstrap = new ServerBootstrap();
 
         bossGroup = NettyEventLoopFactory.eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME);
@@ -115,7 +118,7 @@
         final boolean enableSsl = getUrl().getParameter(SSL_ENABLED_KEY, false);
         final SslContext sslContext;
         if (enableSsl) {
-            sslContext = SslContexts.buildServerSslContext(url);
+            sslContext = SslContexts.buildServerSslContext(getUrl());
         } else {
             sslContext = null;
         }
@@ -129,9 +132,10 @@
                 protected void initChannel(SocketChannel ch) throws Exception {
                     // Do not add idle state handler here, because it should be added in the protocol handler.
                     final ChannelPipeline p = ch.pipeline();
-                    final PortUnificationServerHandler puHandler;
-                    puHandler = new PortUnificationServerHandler(url, sslContext, true, protocols,
-                        channels);
+                    final NettyPortUnificationServerHandler puHandler;
+                    puHandler = new NettyPortUnificationServerHandler(getUrl(), sslContext, true, getProtocols(),
+                        NettyPortUnificationServer.this, NettyPortUnificationServer.this.dubboChannels,
+                        getSupportedUrls(), getSupportedHandlers());
                     p.addLast("negotiation-protocol", puHandler);
                 }
             });
@@ -139,7 +143,7 @@
 
         String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
         int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
-        if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
+        if (getUrl().getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
             bindIp = ANYHOST_VALUE;
         }
         InetSocketAddress bindAddress = new InetSocketAddress(bindIp, bindPort);
@@ -148,8 +152,8 @@
         channel = channelFuture.channel();
     }
 
-    protected void doClose() throws Throwable {
-        final long st = System.currentTimeMillis();
+    @Override
+    public void doClose(){
 
         try {
             if (channel != null) {
@@ -158,14 +162,26 @@
                 channel = null;
             }
 
-            channels.close().await(serverShutdownTimeoutMills);
-            final long cost = System.currentTimeMillis() - st;
-            logger.info("Port unification server closed. cost:" + cost);
-        } catch (InterruptedException e) {
+        } catch (Throwable e) {
             logger.warn("Interrupted while shutting down", e);
         }
 
-        for (WireProtocol protocol : protocols) {
+        try {
+            Collection<Channel> channels = getChannels();
+            if (CollectionUtils.isNotEmpty(channels)) {
+                for (Channel channel : channels) {
+                    try {
+                        channel.close();
+                    } catch (Throwable e) {
+                        logger.warn(e.getMessage(), e);
+                    }
+                }
+            }
+        } catch (Throwable e) {
+            logger.warn(e.getMessage(), e);
+        }
+
+        for (WireProtocol protocol : getProtocols()) {
             protocol.close();
         }
 
@@ -189,8 +205,25 @@
         return channel.isActive();
     }
 
+    @Override
+    public Collection<Channel> getChannels() {
+        Collection<Channel> chs = new ArrayList<>(this.dubboChannels.size());
+        chs.addAll(this.dubboChannels.values());
+        return chs;
+    }
+
+    @Override
+    public Channel getChannel(InetSocketAddress remoteAddress) {
+        return dubboChannels.get(NetUtils.toAddressString(remoteAddress));
+    }
+
     public InetSocketAddress getLocalAddress() {
         return (InetSocketAddress) channel.localAddress();
     }
 
+    @Override
+    public boolean canHandleIdle() {
+        return true;
+    }
+
 }
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java
new file mode 100644
index 0000000..e6fcb1a
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java
@@ -0,0 +1,155 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.io.Bytes;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.api.WireProtocol;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslHandler;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NettyPortUnificationServerHandler extends ByteToMessageDecoder {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(
+        NettyPortUnificationServerHandler.class);
+
+    private final SslContext sslCtx;
+    private final URL url;
+    private final ChannelHandler handler;
+    private final boolean detectSsl;
+    private final List<WireProtocol> protocols;
+    private final Map<String, Channel> dubboChannels;
+    private final Map<String, URL> urlMapper;
+    private final Map<String, ChannelHandler> handlerMapper;
+
+
+    public NettyPortUnificationServerHandler(URL url, SslContext sslCtx, boolean detectSsl,
+                                             List<WireProtocol> protocols, ChannelHandler handler,
+                                             Map<String, Channel> dubboChannels, Map<String, URL> urlMapper, Map<String, ChannelHandler> handlerMapper) {
+        this.url = url;
+        this.sslCtx = sslCtx;
+        this.protocols = protocols;
+        this.detectSsl = detectSsl;
+        this.handler = handler;
+        this.dubboChannels = dubboChannels;
+        this.urlMapper = urlMapper;
+        this.handlerMapper = handlerMapper;
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        LOGGER.error("Unexpected exception from downstream before protocol detected.", cause);
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        super.channelActive(ctx);
+        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
+        if (channel != null) {
+            // this is needed by some test cases
+            dubboChannels.put(NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel);
+        }
+    }
+
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
+        throws Exception {
+        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
+        // Will use the first five bytes to detect a protocol.
+        // size of telnet command ls is 2 bytes
+        if (in.readableBytes() < 2) {
+            return;
+        }
+
+        if (isSsl(in)) {
+            enableSsl(ctx);
+        } else {
+            for (final WireProtocol protocol : protocols) {
+                in.markReaderIndex();
+                ChannelBuffer buf = new NettyBackedChannelBuffer(in);
+                final ProtocolDetector.Result result = protocol.detector().detect(buf);
+                in.resetReaderIndex();
+                switch (result) {
+                    case UNRECOGNIZED:
+                        continue;
+                    case RECOGNIZED:
+                        String protocolName = url.getOrDefaultFrameworkModel().getExtensionLoader(WireProtocol.class)
+                            .getExtensionName(protocol);
+                        ChannelHandler localHandler = this.handlerMapper.getOrDefault(protocolName, handler);
+                        URL localURL = this.urlMapper.getOrDefault(protocolName, url);
+                        channel.setUrl(localURL);
+                        NettyConfigOperator operator = new NettyConfigOperator(channel, localHandler);
+                        protocol.configServerProtocolHandler(url, operator);
+                        ctx.pipeline().remove(this);
+                    case NEED_MORE_DATA:
+                        return;
+                    default:
+                        return;
+                }
+            }
+            byte[] preface = new byte[in.readableBytes()];
+            in.readBytes(preface);
+            Set<String> supported = url.getApplicationModel()
+                .getExtensionLoader(WireProtocol.class)
+                .getSupportedExtensions();
+            LOGGER.error(String.format("Can not recognize protocol from downstream=%s . "
+                    + "preface=%s protocols=%s", ctx.channel().remoteAddress(),
+                Bytes.bytes2hex(preface),
+                supported));
+
+            // Unknown protocol; discard everything and close the connection.
+            in.clear();
+            ctx.close();
+        }
+    }
+
+    private void enableSsl(ChannelHandlerContext ctx) {
+        ChannelPipeline p = ctx.pipeline();
+        p.addLast("ssl", sslCtx.newHandler(ctx.alloc()));
+        p.addLast("unificationA",
+            new NettyPortUnificationServerHandler(url, sslCtx, false, protocols,
+                handler, dubboChannels, urlMapper, handlerMapper));
+        p.remove(this);
+    }
+
+    private boolean isSsl(ByteBuf buf) {
+        // at least 5 bytes to determine if data is encrypted
+        if (detectSsl && buf.readableBytes() >= 5) {
+            return SslHandler.isEncrypted(buf);
+        }
+        return false;
+    }
+
+
+}
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationTransporter.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationTransporter.java
new file mode 100644
index 0000000..b13dc0f
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationTransporter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.Client;
+import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.api.pu.AbstractPortUnificationServer;
+import org.apache.dubbo.remoting.api.pu.PortUnificationTransporter;
+
+public class NettyPortUnificationTransporter implements PortUnificationTransporter {
+
+    public static final String NAME = "netty";
+
+    @Override
+    public AbstractPortUnificationServer bind(URL url, ChannelHandler handler) throws RemotingException {
+        return new NettyPortUnificationServer(url, handler);
+    }
+
+    @Override
+    public Client connect(URL url, ChannelHandler handler) throws RemotingException {
+        return null;
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java
index 8eaeb94..7ea3733 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java
@@ -95,6 +95,8 @@
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
         NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
         handler.received(channel, msg);
+        // trigger qos handler
+        ctx.fireChannelRead(msg);
     }
 
 
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter b/dubbo-remoting/dubbo-remoting-netty4/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter
new file mode 100644
index 0000000..69a9814
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter
@@ -0,0 +1 @@
+netty=org.apache.dubbo.remoting.transport.netty4.NettyPortUnificationTransporter
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ConnectionTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ConnectionTest.java
new file mode 100644
index 0000000..55aa550
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ConnectionTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.remoting.api.Connection;
+import org.apache.dubbo.remoting.api.pu.DefaultPuHandler;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class ConnectionTest {
+    @Test
+    public void connectSyncTest() throws Throwable {
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
+        NettyPortUnificationServer server = null;
+        try {
+            server = new NettyPortUnificationServer(url, new DefaultPuHandler());
+            server.bind();
+
+            Connection connection = new Connection(url);
+            Assertions.assertTrue(connection.isAvailable());
+
+            server.close();
+            Assertions.assertFalse(connection.isAvailable());
+
+            server.bind();
+            // auto reconnect
+            Assertions.assertTrue(connection.isAvailable());
+
+            connection.close();
+            Assertions.assertFalse(connection.isAvailable());
+        } finally {
+            try {
+                server.close();
+            } catch (Throwable e) {
+                // ignored
+            }
+        }
+
+
+    }
+
+    @Test
+    public void testMultiConnect() throws Throwable {
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
+        NettyPortUnificationServer server = null;
+        try {
+            server = new NettyPortUnificationServer(url, new DefaultPuHandler());
+            server.close();
+
+            Connection connection = new Connection(url);
+            ExecutorService service = Executors.newFixedThreadPool(10);
+            final CountDownLatch latch = new CountDownLatch(10);
+            for (int i = 0; i < 10; i++) {
+                Runnable runnable = () -> {
+                    try {
+                        Assertions.assertTrue(connection.isAvailable());
+                        latch.countDown();
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                };
+                service.execute(runnable);
+            }
+        } finally {
+            try {
+                server.close();
+            } catch (Throwable e) {
+                // ignored
+            }
+        }
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/DefaultCodec.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/DefaultCodec.java
new file mode 100644
index 0000000..9f33bfa
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/DefaultCodec.java
@@ -0,0 +1,35 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.Codec2;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+
+import java.io.IOException;
+
+public class DefaultCodec implements Codec2 {
+    @Override
+    public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
+
+    }
+
+    @Override
+    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
+        return null;
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/EmptyWireProtocol.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/EmptyWireProtocol.java
new file mode 100644
index 0000000..9482287
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/EmptyWireProtocol.java
@@ -0,0 +1,47 @@
+/*
+ * 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.remoting.transport.netty4;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.api.WireProtocol;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
+
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.ssl.SslContext;
+
+public class EmptyWireProtocol implements WireProtocol {
+    @Override
+    public ProtocolDetector detector() {
+        return null;
+    }
+
+    @Override
+    public void configServerProtocolHandler(URL url, ChannelOperator operator) {
+
+    }
+
+    @Override
+    public void configClientPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/PortUnificationExchangerTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationExchangerTest.java
similarity index 71%
rename from dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/PortUnificationExchangerTest.java
rename to dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationExchangerTest.java
index a842cbb..2fd8232 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/PortUnificationExchangerTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationExchangerTest.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.exchange;
+package org.apache.dubbo.remoting.transport.netty4;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.url.component.ServiceConfigURL;
-import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.remoting.api.pu.DefaultPuHandler;
+import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -28,10 +28,10 @@
 
     @Test
     public void test() {
-        URL url = new ServiceConfigURL(CommonConstants.TRIPLE, "localhost", 9103,
-            new String[]{Constants.BIND_PORT_KEY, String.valueOf(9103)});
-        PortUnificationExchanger.bind(url);
-        PortUnificationExchanger.bind(url);
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
+        PortUnificationExchanger.bind(url, new DefaultPuHandler());
+        PortUnificationExchanger.bind(url, new DefaultPuHandler());
         Assertions.assertEquals(PortUnificationExchanger.getServers().size(), 1);
 
         PortUnificationExchanger.close();
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationServerTest.java
similarity index 64%
rename from dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java
rename to dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationServerTest.java
index 92bcfb5..23fdcb9 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/PortUnificationServerTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/PortUnificationServerTest.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+package org.apache.dubbo.remoting.transport.netty4;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.common.url.component.ServiceConfigURL;
-import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.api.pu.DefaultPuHandler;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -27,11 +27,12 @@
 public class PortUnificationServerTest {
 
     @Test
-    public void testBind() {
-        URL url = new ServiceConfigURL(CommonConstants.TRIPLE, "localhost", 8898,
-                new String[]{Constants.BIND_PORT_KEY, String.valueOf(8898)});
+    public void testBind() throws RemotingException {
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("empty://127.0.0.1:" + port + "?foo=bar");
 
-        final PortUnificationServer server = new PortUnificationServer(url);
+        // abstract endpoint need to get codec of url(which is in triple package)
+        final NettyPortUnificationServer server = new NettyPortUnificationServer(url, new DefaultPuHandler());
         server.bind();
         Assertions.assertTrue(server.isBound());
     }
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2 b/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2
new file mode 100644
index 0000000..58df523
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2
@@ -0,0 +1 @@
+empty=org.apache.dubbo.remoting.transport.netty4.DefaultCodec
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol b/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
new file mode 100644
index 0000000..74a5075
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
@@ -0,0 +1 @@
+empty=org.apache.dubbo.remoting.transport.netty4.EmptyWireProtocol
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
index 52c1d9f..a3c719f 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/main/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClient.java
@@ -18,7 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient;
 import org.apache.dubbo.remoting.zookeeper.ChildListener;
@@ -55,11 +55,12 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.SESSION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_CONNECT_REGISTRY;
 
 
 public class Curator5ZookeeperClient extends AbstractZookeeperClient<Curator5ZookeeperClient.NodeCacheListenerImpl, Curator5ZookeeperClient.CuratorWatcherImpl> {
 
-    protected static final Logger logger = LoggerFactory.getLogger(Curator5ZookeeperClient.class);
+    protected static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(Curator5ZookeeperClient.class);
 
     private static final Charset CHARSET = StandardCharsets.UTF_8;
     private final CuratorFramework client;
@@ -94,9 +95,17 @@
             client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
             client.start();
             boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
+
             if (!connected) {
-                throw new IllegalStateException("zookeeper not connected");
+                IllegalStateException illegalStateException = new IllegalStateException("zookeeper not connected");
+
+                // 5-1 Failed to connect to configuration center.
+                logger.error(CONFIG_FAILED_CONNECT_REGISTRY, "Zookeeper server offline", "",
+                    "Failed to connect with zookeeper", illegalStateException);
+
+                throw illegalStateException;
             }
+
         } catch (Exception e) {
             throw new IllegalStateException(e.getMessage(), e);
         }
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
index 13a0018..7a98cce 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
+++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java
@@ -18,7 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigItem;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -59,11 +59,12 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.SESSION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_CONNECT_REGISTRY;
 
 
 public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorZookeeperClient.NodeCacheListenerImpl, CuratorZookeeperClient.CuratorWatcherImpl> {
 
-    protected static final Logger logger = LoggerFactory.getLogger(CuratorZookeeperClient.class);
+    protected static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CuratorZookeeperClient.class);
 
     private static final Charset CHARSET = StandardCharsets.UTF_8;
     private final CuratorFramework client;
@@ -97,10 +98,18 @@
             client = builder.build();
             client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
             client.start();
+
             boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
             if (!connected) {
-                throw new IllegalStateException("zookeeper not connected");
+                IllegalStateException illegalStateException = new IllegalStateException("zookeeper not connected");
+
+                // 5-1 Failed to connect to configuration center.
+                logger.error(CONFIG_FAILED_CONNECT_REGISTRY, "Zookeeper server offline", "",
+                    "Failed to connect with zookeeper", illegalStateException);
+
+                throw illegalStateException;
             }
+
             CuratorWatcherImpl.closed = false;
         } catch (Exception e) {
             close();
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Protocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Protocol.java
index 838801d..0bbc24f 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Protocol.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Protocol.java
@@ -25,7 +25,35 @@
 import java.util.List;
 
 /**
- * Protocol. (API/SPI, Singleton, ThreadSafe)
+ * RPC Protocol extension interface, which encapsulates the details of remote invocation. <br /><br />
+ *
+ * <p>Conventions:
+ *
+ * <li>
+ *     When user invokes the 'invoke()' method in object that the method 'refer()' returns,
+ *     the protocol needs to execute the 'invoke()' method of Invoker object that received by 'export()' method,
+ *     which should have the same URL.
+ * </li>
+ *
+ * <li>
+ *     Invoker that returned by 'refer()' is implemented by the protocol. The remote invocation request should be sent by that Invoker.
+ * </li>
+ *
+ * <li>
+ *     The invoker that 'export()' receives will be implemented by framework. Protocol implementation should not care with that.
+ * </li>
+ *
+ * <p>Attentions:
+ *
+ * <li>
+ *     The Protocol implementation does not care the transparent proxy. The invoker will be converted to business interface by other layer.
+ * </li>
+ *
+ * <li>
+ *     The protocol doesn't need to be backed by TCP connection. It can also be backed by file sharing or inter-process communication.
+ * </li>
+ *
+ * (API/SPI, Singleton, ThreadSafe)
  */
 @SPI(value = "dubbo", scope = ExtensionScope.FRAMEWORK)
 public interface Protocol {
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java
index 93d5f2b..deb7d52 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java
@@ -97,7 +97,9 @@
 
         RpcContext context = RpcContext.getServerAttachment();
 //                .setAttachments(attachments)  // merged from dubbox
-        context.setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort());
+        if (context.getLocalAddress() == null) {
+            context.setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort());
+        }
 
         String remoteApplication = invocation.getAttachment(REMOTE_APPLICATION_KEY);
         if (StringUtils.isNotEmpty(remoteApplication)) {
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericFilter.java
index a09e285..75dcd5c 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericFilter.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/GenericFilter.java
@@ -97,7 +97,7 @@
                 String generic = inv.getAttachment(GENERIC_KEY);
 
                 if (StringUtils.isBlank(generic)) {
-                    generic = RpcContext.getClientAttachment().getAttachment(GENERIC_KEY);
+                    generic = getGenericValueFromRpcContext();
                 }
 
                 if (StringUtils.isEmpty(generic)
@@ -205,6 +205,14 @@
         }).toArray();
     }
 
+    private String getGenericValueFromRpcContext(){
+        String generic = RpcContext.getServerAttachment().getAttachment(GENERIC_KEY);
+        if (StringUtils.isBlank(generic)){
+            generic = RpcContext.getClientAttachment().getAttachment(GENERIC_KEY);
+        }
+        return generic;
+    }
+
     @Override
     public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
         if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
@@ -214,7 +222,7 @@
 
             String generic = inv.getAttachment(GENERIC_KEY);
             if (StringUtils.isBlank(generic)) {
-                generic = RpcContext.getClientAttachment().getAttachment(GENERIC_KEY);
+                generic = getGenericValueFromRpcContext();
             }
 
             if (appResponse.hasException()) {
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerExporterWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerExporterWrapper.java
index df3497f..021af67 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerExporterWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerExporterWrapper.java
@@ -24,6 +24,7 @@
 import org.apache.dubbo.rpc.Invoker;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * ListenerExporter
@@ -42,22 +43,7 @@
         }
         this.exporter = exporter;
         this.listeners = listeners;
-        if (CollectionUtils.isNotEmpty(listeners)) {
-            RuntimeException exception = null;
-            for (ExporterListener listener : listeners) {
-                if (listener != null) {
-                    try {
-                        listener.exported(this);
-                    } catch (RuntimeException t) {
-                        logger.error(t.getMessage(), t);
-                        exception = t;
-                    }
-                }
-            }
-            if (exception != null) {
-                throw exception;
-            }
-        }
+        listenerEvent(listener -> listener.exported(this));
     }
 
     @Override
@@ -70,23 +56,26 @@
         try {
             exporter.unexport();
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners)) {
-                RuntimeException exception = null;
-                for (ExporterListener listener : listeners) {
-                    if (listener != null) {
-                        try {
-                            listener.unexported(this);
-                        } catch (RuntimeException t) {
-                            logger.error(t.getMessage(), t);
-                            exception = t;
-                        }
-                    }
-                }
-                if (exception != null) {
-                    throw exception;
-                }
-            }
+            listenerEvent(listener -> listener.unexported(this));
         }
     }
 
+    private void listenerEvent(Consumer<ExporterListener> consumer) {
+        if (CollectionUtils.isNotEmpty(listeners)) {
+            RuntimeException exception = null;
+            for (ExporterListener listener : listeners) {
+                if (listener != null) {
+                    try {
+                        consumer.accept(listener);
+                    } catch (RuntimeException t) {
+                        logger.error(t.getMessage(), t);
+                        exception = t;
+                    }
+                }
+            }
+            if (exception != null) {
+                throw exception;
+            }
+        }
+    }
 }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerInvokerWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerInvokerWrapper.java
index 14254c7..2addb81 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerInvokerWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/listener/ListenerInvokerWrapper.java
@@ -27,6 +27,7 @@
 import org.apache.dubbo.rpc.RpcException;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * ListenerInvoker
@@ -45,19 +46,10 @@
         }
         this.invoker = invoker;
         this.listeners = listeners;
-        if (CollectionUtils.isNotEmpty(listeners)) {
-            for (InvokerListener listener : listeners) {
-                if (listener != null) {
-                    try {
-                        listener.referred(invoker);
-                    } catch (Throwable t) {
-                        logger.error(t.getMessage(), t);
-                    }
-                }
-            }
-        }
+        listenerEvent(listener -> listener.referred(invoker));
     }
 
+
     @Override
     public Class<T> getInterface() {
         return invoker.getInterface();
@@ -88,17 +80,7 @@
         try {
             invoker.destroy();
         } finally {
-            if (CollectionUtils.isNotEmpty(listeners)) {
-                for (InvokerListener listener : listeners) {
-                    if (listener != null) {
-                        try {
-                            listener.destroyed(invoker);
-                        } catch (Throwable t) {
-                            logger.error(t.getMessage(), t);
-                        }
-                    }
-                }
-            }
+            listenerEvent(listener -> listener.destroyed(invoker));
         }
     }
 
@@ -109,4 +91,23 @@
     public List<InvokerListener> getListeners() {
         return listeners;
     }
+
+    private void listenerEvent(Consumer<InvokerListener> consumer) {
+        if (CollectionUtils.isNotEmpty(listeners)) {
+            RuntimeException exception = null;
+            for (InvokerListener listener : listeners) {
+                if (listener != null) {
+                    try {
+                        consumer.accept(listener);
+                    } catch (RuntimeException t) {
+                        logger.error(t.getMessage(), t);
+                        exception = t;
+                    }
+                }
+            }
+            if (exception != null) {
+                throw exception;
+            }
+        }
+    }
 }
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 e1582fe..be56015 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
@@ -22,6 +22,7 @@
 import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.common.threadpool.ThreadlessExecutor;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.common.utils.ArrayUtils;
@@ -50,7 +51,6 @@
 import java.util.concurrent.TimeUnit;
 
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
-import static org.apache.dubbo.remoting.Constants.DEFAULT_REMOTING_SERIALIZATION;
 import static org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY;
 import static org.apache.dubbo.rpc.Constants.SERIALIZATION_ID_KEY;
 
@@ -196,7 +196,7 @@
 
         RpcUtils.attachInvocationIdIfAsync(getUrl(), inv);
 
-        Byte serializationId = CodecSupport.getIDByName(getUrl().getParameter(SERIALIZATION_KEY, DEFAULT_REMOTING_SERIALIZATION));
+        Byte serializationId = CodecSupport.getIDByName(getUrl().getParameter(SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization()));
         if (serializationId != null) {
             inv.put(SERIALIZATION_ID_KEY, serializationId);
         }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/javassist/JavassistProxyFactory.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/javassist/JavassistProxyFactory.java
index 9088cce..12d48f5 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/javassist/JavassistProxyFactory.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/javassist/JavassistProxyFactory.java
@@ -19,7 +19,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.bytecode.Proxy;
 import org.apache.dubbo.common.bytecode.Wrapper;
-import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.proxy.AbstractProxyFactory;
@@ -33,7 +33,7 @@
  * JavassistRpcProxyFactory
  */
 public class JavassistProxyFactory extends AbstractProxyFactory {
-    private final static Logger logger = LoggerFactory.getLogger(JavassistProxyFactory.class);
+    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(JavassistProxyFactory.class);
     private final JdkProxyFactory jdkProxyFactory = new JdkProxyFactory();
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubInvocationUtil.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubInvocationUtil.java
index 4e88fb7..cb52327 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubInvocationUtil.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubInvocationUtil.java
@@ -33,7 +33,13 @@
 
     public static <T, R> void unaryCall(Invoker<?> invoker, MethodDescriptor method, T request,
         StreamObserver<R> responseObserver) {
-        call(invoker, method, new Object[]{request, responseObserver});
+        try {
+            Object res = unaryCall(invoker, method, request);
+            responseObserver.onNext((R) res);
+        } catch (Exception e) {
+            responseObserver.onError(e);
+        }
+        responseObserver.onCompleted();
     }
 
     public static <T, R> StreamObserver<T> biOrClientStreamCall(Invoker<?> invoker,
diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/AppResponseTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/AppResponseTest.java
index 7549692..aee142a 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/AppResponseTest.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/AppResponseTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.rpc;
 
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -39,9 +40,7 @@
     @Test
     public void testAppResponseWithEmptyStackTraceException() {
         Throwable throwable = buildEmptyStackTraceException();
-        if (throwable == null) {
-            return;
-        }
+        assumeFalse(throwable == null);
         AppResponse appResponse = new AppResponse(throwable);
 
         StackTraceElement[] stackTrace = appResponse.getException().getStackTrace();
@@ -66,9 +65,7 @@
     @Test
     public void testSetExceptionWithEmptyStackTraceException() {
         Throwable throwable = buildEmptyStackTraceException();
-        if (throwable == null) {
-            return;
-        }
+        assumeFalse(throwable == null);
         AppResponse appResponse = new AppResponse();
         appResponse.setException(throwable);
 
diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/stub/StubInvocationUtilTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/stub/StubInvocationUtilTest.java
index 476911f..e5196f3 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/stub/StubInvocationUtilTest.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/stub/StubInvocationUtilTest.java
@@ -136,13 +136,8 @@
         Result result = Mockito.mock(Result.class);
         String response = "response";
         when(invoker.invoke(any(Invocation.class)))
-            .then(invocationOnMock -> {
-                Invocation invocation = (Invocation) invocationOnMock.getArguments()[0];
-                StreamObserver<Object> observer = (StreamObserver<Object>) invocation.getArguments()[1];
-                observer.onNext(response);
-                observer.onCompleted();
-                return result;
-            });
+            .then(invocationOnMock -> result);
+        when(result.recreate()).thenReturn(response);
         MethodDescriptor method = Mockito.mock(MethodDescriptor.class);
         when(method.getParameterClasses())
             .thenReturn(new Class[]{String.class});
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/pom.xml b/dubbo-rpc/dubbo-rpc-dubbo/pom.xml
index 42a0196..0570e62 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/pom.xml
+++ b/dubbo-rpc/dubbo-rpc-dubbo/pom.xml
@@ -64,6 +64,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodecSupport.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodecSupport.java
index e422685..d0914c0 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodecSupport.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodecSupport.java
@@ -18,6 +18,7 @@
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.transport.CodecSupport;
 import org.apache.dubbo.rpc.AppResponse;
@@ -34,7 +35,7 @@
             return CodecSupport.getSerializationById((byte) serializationTypeObj);
         }
         return url.getOrDefaultFrameworkModel().getExtensionLoader(Serialization.class).getExtension(
-                url.getParameter(org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION));
+                url.getParameter(org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization()));
     }
 
     public static Serialization getResponseSerialization(URL url, AppResponse appResponse) {
@@ -47,6 +48,6 @@
             }
         }
         return url.getOrDefaultFrameworkModel().getExtensionLoader(Serialization.class).getExtension(
-                url.getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION));
+                url.getParameter(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization()));
     }
 }
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 b2ccfbe..c4e238c 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
@@ -33,6 +33,7 @@
 import org.apache.dubbo.remoting.exchange.ExchangeHandler;
 import org.apache.dubbo.remoting.exchange.ExchangeServer;
 import org.apache.dubbo.remoting.exchange.Exchangers;
+import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
 import org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invocation;
@@ -334,6 +335,7 @@
         String key = url.getAddress();
         // client can export a service which only for server to invoke
         boolean isServer = url.getParameter(IS_SERVER_KEY, true);
+
         if (isServer) {
             ProtocolServer server = serverMap.get(key);
             if (server == null) {
@@ -653,6 +655,7 @@
                 }
             }
         }
+        PortUnificationExchanger.close();
         referenceClientMap.clear();
 
         super.destroy();
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboDetector.java
similarity index 60%
copy from dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java
copy to dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboDetector.java
index ae5e4c6..88d146c 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboDetector.java
@@ -14,32 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+package org.apache.dubbo.rpc.protocol.dubbo.pu;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http2.Http2CodecUtil;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ByteBufferBackedChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffers;
+
+import java.nio.ByteBuffer;
 
 import static java.lang.Math.min;
 
-public class Http2ProtocolDetector implements ProtocolDetector {
-    private final ByteBuf clientPrefaceString = Http2CodecUtil.connectionPrefaceBuf();
+public class DubboDetector implements ProtocolDetector {
+    private final ChannelBuffer Preface = new ByteBufferBackedChannelBuffer(
+        ByteBuffer.wrap(new byte[]{(byte)0xda, (byte)0xbb})
+    );
 
     @Override
-    public Result detect(ChannelHandlerContext ctx, ByteBuf in) {
-        int prefaceLen = clientPrefaceString.readableBytes();
+    public Result detect(ChannelBuffer in) {
+        int prefaceLen = Preface.readableBytes();
         int bytesRead = min(in.readableBytes(), prefaceLen);
 
-        // If the input so far doesn't match the preface, break the connection.
-        if (bytesRead == 0 || !ByteBufUtil.equals(in, 0,
-                clientPrefaceString, 0, bytesRead)) {
-
+        if (bytesRead ==0 || !ChannelBuffers.prefixEquals(in,  Preface,  bytesRead)) {
             return Result.UNRECOGNIZED;
         }
         if (bytesRead == prefaceLen) {
             return Result.RECOGNIZED;
         }
+
         return Result.NEED_MORE_DATA;
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboWireProtocol.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboWireProtocol.java
new file mode 100644
index 0000000..ba83394
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/pu/DubboWireProtocol.java
@@ -0,0 +1,46 @@
+/*
+ * 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.dubbo.pu;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.api.AbstractWireProtocol;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Activate
+public class DubboWireProtocol extends AbstractWireProtocol {
+    public DubboWireProtocol() {
+        super(new DubboDetector());
+    }
+
+
+    @Override
+    public void configServerProtocolHandler(URL url, ChannelOperator operator) {
+        List<ChannelHandler> handlers = new ArrayList<>();
+        // operator(for now nettyOperator)'s duties
+        // 1. config codec2 for the protocol(load by extension loader)
+        // 2. config handlers passed by wire protocol
+        // ( for triple, some h2 netty handler and logic handler to handle connection;
+        //   for dubbo, nothing, an empty handlers is used to trigger operator logic)
+        // 3. config Dubbo Inner handler(for dubbo protocol, this handler handles connection)
+        operator.configChannelHandler(handlers);
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol b/dubbo-rpc/dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
new file mode 100644
index 0000000..06f029b
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol
@@ -0,0 +1 @@
+dubbo=org.apache.dubbo.rpc.protocol.dubbo.pu.DubboWireProtocol
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocationTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocationTest.java
index 0bc522a..d3b0316 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocationTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocationTest.java
@@ -19,9 +19,11 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.buffer.ChannelBuffer;
 import org.apache.dubbo.remoting.buffer.ChannelBufferInputStream;
 import org.apache.dubbo.remoting.buffer.ChannelBufferOutputStream;
@@ -61,7 +63,7 @@
         inv.setObjectAttachment("k2", "v2");
         inv.setTargetServiceUniqueName(url.getServiceKey());
         // Write the data of inv to the buffer
-        Byte proto = CodecSupport.getIDByName("hessian2");
+        Byte proto = CodecSupport.getIDByName(DefaultSerializationSelector.getDefaultRemotingSerialization());
         ChannelBuffer buffer = writeBuffer(url, inv, proto);
 
         FrameworkModel frameworkModel = new FrameworkModel();
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcResultTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcResultTest.java
index 3c7ea54..04dc51f 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcResultTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcResultTest.java
@@ -20,9 +20,11 @@
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.buffer.ChannelBuffer;
 import org.apache.dubbo.remoting.buffer.ChannelBufferInputStream;
 import org.apache.dubbo.remoting.buffer.ChannelBufferOutputStream;
@@ -76,7 +78,7 @@
     @Test
     public void test() throws Exception {
         // Mock a rpcInvocation, this rpcInvocation is usually generated by the client request, and stored in Request#data
-        Byte proto = CodecSupport.getIDByName("hessian2");
+        Byte proto = CodecSupport.getIDByName(DefaultSerializationSelector.getDefaultRemotingSerialization());
         URL url = new ServiceConfigURL("dubbo", "127.0.0.1", 9103, DemoService.class.getName(), VERSION_KEY, "1.0.0");
         ServiceDescriptor serviceDescriptor = repository.registerService(DemoService.class);
         ProviderModel providerModel = new ProviderModel(url.getServiceKey(), new DemoServiceImpl(), serviceDescriptor, null, null);
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCountCodecTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCountCodecTest.java
index b531bb1..a069227 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCountCodecTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCountCodecTest.java
@@ -42,7 +42,7 @@
     @Test
     public void test() throws Exception {
         DubboCountCodec dubboCountCodec = new DubboCountCodec(FrameworkModel.defaultModel());
-        ChannelBuffer buffer = ChannelBuffers.buffer(1024);
+        ChannelBuffer buffer = ChannelBuffers.buffer(2048);
         Channel channel = new MockChannel();
         Assertions.assertEquals(Codec2.DecodeResult.NEED_MORE_INPUT, dubboCountCodec.decode(channel, buffer));
 
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
index d9760e4..8097285 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocolTest.java
@@ -42,6 +42,7 @@
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
@@ -199,6 +200,7 @@
     }
 
     @Test
+    @Disabled
     public void testNonSerializedParameter() throws Exception {
         DemoService service = new DemoServiceImpl();
         int port = NetUtils.getAvailablePort();
@@ -214,6 +216,7 @@
     }
 
     @Test
+    @Disabled
     public void testReturnNonSerialized() throws Exception {
         DemoService service = new DemoServiceImpl();
         int port = NetUtils.getAvailablePort();
diff --git a/dubbo-rpc/dubbo-rpc-injvm/pom.xml b/dubbo-rpc/dubbo-rpc-injvm/pom.xml
index 3346ed0..4f5f9ad 100644
--- a/dubbo-rpc/dubbo-rpc-injvm/pom.xml
+++ b/dubbo-rpc/dubbo-rpc-injvm/pom.xml
@@ -46,5 +46,11 @@
             <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/DefaultParamDeepCopyUtil.java b/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/DefaultParamDeepCopyUtil.java
index 8e3f6ca..232b586 100644
--- a/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/DefaultParamDeepCopyUtil.java
+++ b/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/DefaultParamDeepCopyUtil.java
@@ -22,6 +22,7 @@
 import org.apache.dubbo.common.serialize.ObjectInput;
 import org.apache.dubbo.common.serialize.ObjectOutput;
 import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.remoting.Constants;
 
 import java.io.ByteArrayInputStream;
@@ -37,7 +38,7 @@
     @SuppressWarnings({"unchecked"})
     public <T> T copy(URL url, Object src, Class<T> targetClass) {
         Serialization serialization = url.getOrDefaultFrameworkModel().getExtensionLoader(Serialization.class).getExtension(
-            url.getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION));
+            url.getParameter(Constants.SERIALIZATION_KEY, DefaultSerializationSelector.getDefaultRemotingSerialization()));
 
         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
             ObjectOutput objectOutput = serialization.serialize(url, outputStream);
diff --git a/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/InjvmInvoker.java b/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/InjvmInvoker.java
index 18c08e8..972efa5 100644
--- a/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/InjvmInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-injvm/src/main/java/org/apache/dubbo/rpc/protocol/injvm/InjvmInvoker.java
@@ -221,7 +221,7 @@
                 if (pts != null && args != null && pts.length == args.length) {
                     realArgument = new Object[pts.length];
                     for (int i = 0; i < pts.length; i++) {
-                        realArgument[i] = paramDeepCopyUtil.copy(getUrl(), args[i], pts[i]);
+                        realArgument[i] = paramDeepCopyUtil.copy(invoker.getUrl(), args[i], pts[i]);
                     }
                 }
                 if (realArgument == null) {
diff --git a/dubbo-rpc/dubbo-rpc-injvm/src/test/java/demo/Empty.java b/dubbo-rpc/dubbo-rpc-injvm/src/test/java/demo/Empty.java
new file mode 100644
index 0000000..6a9678c
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-injvm/src/test/java/demo/Empty.java
@@ -0,0 +1,20 @@
+/*
+ * 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 demo;
+
+public class Empty {
+}
diff --git a/dubbo-rpc/dubbo-rpc-injvm/src/test/java/org/apache/dubbo/rpc/protocol/injvm/InjvmClassLoaderTest.java b/dubbo-rpc/dubbo-rpc-injvm/src/test/java/org/apache/dubbo/rpc/protocol/injvm/InjvmClassLoaderTest.java
index c91f505..765f689 100644
--- a/dubbo-rpc/dubbo-rpc-injvm/src/test/java/org/apache/dubbo/rpc/protocol/injvm/InjvmClassLoaderTest.java
+++ b/dubbo-rpc/dubbo-rpc-injvm/src/test/java/org/apache/dubbo/rpc/protocol/injvm/InjvmClassLoaderTest.java
@@ -32,11 +32,13 @@
 import org.apache.dubbo.rpc.model.ProviderModel;
 import org.apache.dubbo.rpc.model.ServiceDescriptor;
 
+import demo.Empty;
 import demo.MultiClassLoaderService;
 import demo.MultiClassLoaderServiceImpl;
 import demo.MultiClassLoaderServiceRequest;
 import demo.MultiClassLoaderServiceResult;
 import javassist.CannotCompileException;
+import javassist.ClassPool;
 import javassist.CtClass;
 import javassist.NotFoundException;
 import org.junit.jupiter.api.Assertions;
@@ -128,20 +130,28 @@
         applicationModel.destroy();
     }
 
-    private Class<?> compileCustomRequest(ClassLoader classLoader) throws NotFoundException, CannotCompileException {
+    private Class<?> compileCustomRequest(ClassLoader classLoader) throws NotFoundException, CannotCompileException, ClassNotFoundException {
         CtClassBuilder builder = new CtClassBuilder();
         builder.setClassName(MultiClassLoaderServiceRequest.class.getName() + "A");
         builder.setSuperClassName(MultiClassLoaderServiceRequest.class.getName());
         CtClass cls = builder.build(classLoader);
-        return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
+        ClassPool cp = cls.getClassPool();
+        if (classLoader == null) {
+            classLoader = cp.getClassLoader();
+        }
+        return cp.toClass(cls, classLoader.loadClass(Empty.class.getName()), classLoader, JavassistCompiler.class.getProtectionDomain());
     }
 
-    private Class<?> compileCustomResult(ClassLoader classLoader) throws NotFoundException, CannotCompileException {
+    private Class<?> compileCustomResult(ClassLoader classLoader) throws NotFoundException, CannotCompileException, ClassNotFoundException {
         CtClassBuilder builder = new CtClassBuilder();
         builder.setClassName(MultiClassLoaderServiceResult.class.getName() + "A");
         builder.setSuperClassName(MultiClassLoaderServiceResult.class.getName());
         CtClass cls = builder.build(classLoader);
-        return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
+        ClassPool cp = cls.getClassPool();
+        if (classLoader == null) {
+            classLoader = cp.getClassLoader();
+        }
+        return cp.toClass(cls, classLoader.loadClass(Empty.class.getName()), classLoader, JavassistCompiler.class.getProtectionDomain());
     }
 
     private static class TestClassLoader1 extends ClassLoader {
@@ -169,6 +179,9 @@
                 return loadedClass.get(name);
             }
             if (name.startsWith("demo")) {
+                if (name.equals(MultiClassLoaderServiceRequest.class.getName()) || name.equals(MultiClassLoaderServiceResult.class.getName())) {
+                    return super.loadClass(name, resolve);
+                }
                 Class<?> aClass = this.findClass(name);
                 this.loadedClass.put(name, aClass);
                 if (resolve) {
@@ -217,7 +230,7 @@
                 byte[] bytes = loadClassData(name);
                 return defineClass(name, bytes, 0, bytes.length);
             } catch (Exception e) {
-                throw new ClassNotFoundException();
+                return testClassLoader.loadClass(name, false);
             }
         }
 
@@ -226,7 +239,7 @@
             if (loadedClass.containsKey(name)) {
                 return loadedClass.get(name);
             }
-            if (name.startsWith("demo.MultiClassLoaderServiceRe")) {
+            if (name.startsWith("demo.MultiClassLoaderServiceRe") || name.startsWith("demo.Empty")) {
                 Class<?> aClass = this.findClass(name);
                 this.loadedClass.put(name, aClass);
                 if (resolve) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml
index f2b08cd..17f141a 100644
--- a/dubbo-rpc/dubbo-rpc-triple/pom.xml
+++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml
@@ -30,7 +30,8 @@
     <properties>
         <skip_maven_deploy>false</skip_maven_deploy>
         <dubbo.compiler.version>0.0.4.1-SNAPSHOT</dubbo.compiler.version>
-
+        <reactive.version>1.0.4</reactive.version>
+        <reactor.version>3.4.19</reactor.version>
     </properties>
     <dependencies>
         <dependency>
@@ -39,6 +40,12 @@
             <version>${project.parent.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-remoting-netty4</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java</artifactId>
         </dependency>
@@ -54,9 +61,9 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>io.projectreactor</groupId>
-            <artifactId>reactor-core</artifactId>
-            <version>3.4.16</version>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${project.parent.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -73,6 +80,12 @@
             <groupId>org.xerial.snappy</groupId>
             <artifactId>snappy-java</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+            <version>${reactor.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/CancelableStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/CancelableStreamObserver.java
index 3a059bc..76ae697 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/CancelableStreamObserver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/CancelableStreamObserver.java
@@ -19,6 +19,7 @@
 
 import org.apache.dubbo.common.stream.StreamObserver;
 import org.apache.dubbo.rpc.CancellationContext;
+import org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter;
 
 public abstract class CancelableStreamObserver<T> implements StreamObserver<T> {
 
@@ -28,8 +29,19 @@
         this.cancellationContext = cancellationContext;
     }
 
+    public CancellationContext getCancellationContext() {
+        return cancellationContext;
+    }
+
     public void cancel(Throwable throwable) {
         cancellationContext.cancel(throwable);
     }
 
+    public void beforeStart(final ClientCallToObserverAdapter<T> clientCallToObserverAdapter) {
+        // do nothing
+    }
+
+    public void startRequest() {
+        // do nothing
+    }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamObserver.java
index b28499a..d20d2f5 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamObserver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamObserver.java
@@ -29,6 +29,8 @@
      * request()} may not be called before the call is started, a number of initial requests may be
      * specified.
      */
-    void disableAutoRequest();
+    default void disableAutoRequest() {
+        disableAutoFlowControl();
+    }
 
 }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetector.java
similarity index 70%
rename from dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java
rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetector.java
index ae5e4c6..9071ca4 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Http2ProtocolDetector.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetector.java
@@ -14,27 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+package org.apache.dubbo.rpc.protocol.tri;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.channel.ChannelHandlerContext;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ByteBufferBackedChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffers;
+
 import io.netty.handler.codec.http2.Http2CodecUtil;
 
 import static java.lang.Math.min;
 
 public class Http2ProtocolDetector implements ProtocolDetector {
-    private final ByteBuf clientPrefaceString = Http2CodecUtil.connectionPrefaceBuf();
+    private final ChannelBuffer clientPrefaceString = new ByteBufferBackedChannelBuffer(
+        Http2CodecUtil.connectionPrefaceBuf().nioBuffer());
 
     @Override
-    public Result detect(ChannelHandlerContext ctx, ByteBuf in) {
+    public Result detect(ChannelBuffer in) {
         int prefaceLen = clientPrefaceString.readableBytes();
         int bytesRead = min(in.readableBytes(), prefaceLen);
 
         // If the input so far doesn't match the preface, break the connection.
-        if (bytesRead == 0 || !ByteBufUtil.equals(in, 0,
-                clientPrefaceString, 0, bytesRead)) {
-
+        if (bytesRead == 0 || !ChannelBuffers.prefixEquals(in, clientPrefaceString, bytesRead)) {
             return Result.UNRECOGNIZED;
         }
         if (bytesRead == prefaceLen) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
index 57b2edd..7ee9e0e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
@@ -20,6 +20,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.serialize.MultipleSerialization;
+import org.apache.dubbo.common.serialize.support.DefaultSerializationSelector;
 import org.apache.dubbo.common.stream.StreamObserver;
 import org.apache.dubbo.config.Constants;
 import org.apache.dubbo.rpc.model.MethodDescriptor;
@@ -38,7 +39,6 @@
 
 import static org.apache.dubbo.common.constants.CommonConstants.$ECHO;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOBUF_MESSAGE_CLASS_NAME;
-import static org.apache.dubbo.remoting.Constants.DEFAULT_REMOTING_SERIALIZATION;
 import static org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY;
 import static org.apache.dubbo.rpc.protocol.tri.TripleProtocol.METHOD_ATTR_PACK;
 
@@ -105,7 +105,7 @@
 
     public static ReflectionPackableMethod init(MethodDescriptor methodDescriptor, URL url) {
         final String serializeName = url.getParameter(SERIALIZATION_KEY,
-            DEFAULT_REMOTING_SERIALIZATION);
+            DefaultSerializationSelector.getDefaultRemotingSerialization());
         Object stored = methodDescriptor.getAttribute(METHOD_ATTR_PACK);
         if (stored != null) {
             return (ReflectionPackableMethod) stored;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamObserver.java
index 3ca3f1d..9d3a9b2 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamObserver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamObserver.java
@@ -21,7 +21,8 @@
 
 public interface ServerStreamObserver<T> extends CallStreamObserver<T> {
 
-
-    void disableAutoInboundFlowControl();
+    default void disableAutoInboundFlowControl() {
+        disableAutoFlowControl();
+    }
 
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java
index 881303f..6fba538 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java
@@ -23,7 +23,10 @@
 import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
-import org.apache.dubbo.remoting.api.Http2WireProtocol;
+import org.apache.dubbo.remoting.ChannelHandler;
+import org.apache.dubbo.remoting.api.AbstractWireProtocol;
+import org.apache.dubbo.remoting.api.pu.ChannelHandlerPretender;
+import org.apache.dubbo.remoting.api.pu.ChannelOperator;
 import org.apache.dubbo.rpc.HeaderFilter;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -39,10 +42,13 @@
 import io.netty.channel.ChannelPipeline;
 import io.netty.handler.codec.http2.Http2FrameCodec;
 import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
+import io.netty.handler.codec.http2.Http2FrameLogger;
 import io.netty.handler.codec.http2.Http2MultiplexHandler;
 import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.logging.LogLevel;
 import io.netty.handler.ssl.SslContext;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -56,7 +62,7 @@
 import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_MAX_HEADER_LIST_SIZE_KEY;
 
 @Activate
-public class TripleHttp2Protocol extends Http2WireProtocol implements ScopeModelAware {
+public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeModelAware {
 
     // 1 MiB
     private static final int MIB_1 = 1 << 20;
@@ -67,11 +73,19 @@
     private static final int DEFAULT_MAX_FRAME_SIZE = MIB_8;
     private static final int DEFAULT_WINDOW_INIT_SIZE = MIB_8;
 
+    public static final Http2FrameLogger CLIENT_LOGGER = new Http2FrameLogger(LogLevel.DEBUG, "H2_CLIENT");
+
+    public static final Http2FrameLogger SERVER_LOGGER = new Http2FrameLogger(LogLevel.DEBUG, "H2_SERVER");
+
     private ExtensionLoader<HeaderFilter> filtersLoader;
     private FrameworkModel frameworkModel;
     private Configuration config = ConfigurationUtils.getGlobalConfiguration(
         ApplicationModel.defaultModel());
 
+    public TripleHttp2Protocol() {
+        super(new Http2ProtocolDetector());
+    }
+
     @Override
     public void setFrameworkModel(FrameworkModel frameworkModel) {
         this.frameworkModel = frameworkModel;
@@ -89,7 +103,8 @@
     }
 
     @Override
-    public void configServerPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext) {
+    public void configServerProtocolHandler(URL url, ChannelOperator operator) {
+
         final List<HeaderFilter> headFilters;
         if (filtersLoader != null) {
             headFilters = filtersLoader.getActivateExtension(url,
@@ -120,8 +135,14 @@
                         headFilters));
                 }
             });
-        pipeline.addLast(codec, new TripleServerConnectionHandler(), handler,
-            new TripleTailHandler());
+        List<ChannelHandler> handlers = new ArrayList<>();
+        handlers.add(new ChannelHandlerPretender(codec));
+        handlers.add(new ChannelHandlerPretender(new TripleServerConnectionHandler()));
+        handlers.add(new ChannelHandlerPretender(handler));
+        handlers.add(new ChannelHandlerPretender(new TripleTailHandler()));
+        operator.configChannelHandler(handlers);
+
+
     }
 
 
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
index 2e9146c..16b4373 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
@@ -46,6 +46,7 @@
 import org.apache.dubbo.rpc.protocol.tri.call.TripleClientCall;
 import org.apache.dubbo.rpc.protocol.tri.call.UnaryClientCallListener;
 import org.apache.dubbo.rpc.protocol.tri.compressor.Compressor;
+import org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter;
 import org.apache.dubbo.rpc.support.RpcUtils;
 
 import io.netty.util.AsciiString;
@@ -174,14 +175,18 @@
     StreamObserver<Object> streamCall(ClientCall call,
         RequestMetadata metadata,
         StreamObserver<Object> responseObserver) {
-        if (responseObserver instanceof CancelableStreamObserver) {
-            final CancellationContext context = new CancellationContext();
-            ((CancelableStreamObserver<Object>) responseObserver).setCancellationContext(context);
-            context.addListener(context1 -> call.cancelByLocal(new IllegalStateException("Canceled by app")));
-        }
         ObserverToClientCallListenerAdapter listener = new ObserverToClientCallListenerAdapter(
             responseObserver);
-        return call.start(metadata, listener);
+        StreamObserver<Object> streamObserver = call.start(metadata, listener);
+        if (responseObserver instanceof CancelableStreamObserver) {
+            final CancellationContext context = new CancellationContext();
+            CancelableStreamObserver<Object> cancelableStreamObserver = (CancelableStreamObserver<Object>) responseObserver;
+            cancelableStreamObserver.setCancellationContext(context);
+            context.addListener(context1 -> call.cancelByLocal(new IllegalStateException("Canceled by app")));
+            listener.setOnStartConsumer(dummy -> cancelableStreamObserver.startRequest());
+            cancelableStreamObserver.beforeStart((ClientCallToObserverAdapter<Object>) streamObserver);
+        }
+        return streamObserver;
     }
 
     AsyncRpcResult invokeUnary(MethodDescriptor methodDescriptor, Invocation invocation,
@@ -196,35 +201,16 @@
         RequestMetadata request = createRequest(methodDescriptor, invocation, timeout);
 
         final Object pureArgument;
-        if (methodDescriptor.getParameterClasses().length == 2
-            && methodDescriptor.getParameterClasses()[1].isAssignableFrom(
-            StreamObserver.class)) {
-            StreamObserver<Object> observer = (StreamObserver<Object>) invocation.getArguments()[1];
-            future.whenComplete((r, t) -> {
-                if (t != null) {
-                    observer.onError(t);
-                    return;
-                }
-                if (r.hasException()) {
-                    observer.onError(r.getException());
-                    return;
-                }
-                observer.onNext(r.getValue());
-                observer.onCompleted();
-            });
+
+        if (methodDescriptor instanceof StubMethodDescriptor) {
             pureArgument = invocation.getArguments()[0];
-            result = new AsyncRpcResult(CompletableFuture.completedFuture(new AppResponse()),
-                invocation);
         } else {
-            if (methodDescriptor instanceof StubMethodDescriptor) {
-                pureArgument = invocation.getArguments()[0];
-            } else {
-                pureArgument = invocation.getArguments();
-            }
-            result = new AsyncRpcResult(future, invocation);
-            result.setExecutor(callbackExecutor);
-            FutureContext.getContext().setCompatibleFuture(future);
+            pureArgument = invocation.getArguments();
         }
+        result = new AsyncRpcResult(future, invocation);
+        FutureContext.getContext().setCompatibleFuture(future);
+
+        result.setExecutor(callbackExecutor);
         ClientCall.Listener callListener = new UnaryClientCallListener(future);
 
         final StreamObserver<Object> requestObserver = call.start(request, callListener);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
index 7e5a5b6..a7b0683 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java
@@ -18,11 +18,11 @@
 package org.apache.dubbo.rpc.protocol.tri;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
 import org.apache.dubbo.remoting.api.ConnectionManager;
+import org.apache.dubbo.remoting.api.pu.DefaultPuHandler;
 import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
@@ -48,20 +48,10 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_CLIENT_THREADPOOL;
-import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.THREAD_NAME_KEY;
-
 public class TripleProtocol extends AbstractProtocol {
 
 
     public static final String METHOD_ATTR_PACK = "pack";
-    private static final String CLIENT_THREAD_POOL_NAME = "DubboTriClientHandler";
-    private static final URL THREAD_POOL_URL = new URL(CommonConstants.TRIPLE,
-        CommonConstants.LOCALHOST_VALUE, 50051)
-        .addParameter(THREAD_NAME_KEY, CLIENT_THREAD_POOL_NAME)
-        .addParameterIfAbsent(THREADPOOL_KEY, DEFAULT_CLIENT_THREADPOOL);
-
     private static final Logger logger = LoggerFactory.getLogger(TripleProtocol.class);
     private final PathResolver pathResolver;
     private final TriBuiltinService triBuiltinService;
@@ -117,12 +107,12 @@
             .setStatus(url.getServiceKey(), HealthCheckResponse.ServingStatus.SERVING);
         triBuiltinService.getHealthStatusManager()
             .setStatus(url.getServiceInterface(), HealthCheckResponse.ServingStatus.SERVING);
-
         // init
         url.getOrDefaultApplicationModel().getExtensionLoader(ExecutorRepository.class)
             .getDefaultExtension()
             .createExecutorIfAbsent(url);
-        PortUnificationExchanger.bind(url);
+
+        PortUnificationExchanger.bind(url, new DefaultPuHandler());
         optimizeSerialization(url);
         return exporter;
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/ObserverToClientCallListenerAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/ObserverToClientCallListenerAdapter.java
index 5fac300..1c934ce 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/ObserverToClientCallListenerAdapter.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/ObserverToClientCallListenerAdapter.java
@@ -21,16 +21,22 @@
 import org.apache.dubbo.rpc.TriRpcStatus;
 
 import java.util.Map;
+import java.util.function.Consumer;
 
 public class ObserverToClientCallListenerAdapter implements ClientCall.Listener {
 
     private final StreamObserver<Object> delegate;
     private ClientCall call;
+    private Consumer<ClientCall> onStartConsumer = clientCall -> { };
 
     public ObserverToClientCallListenerAdapter(StreamObserver<Object> delegate) {
         this.delegate = delegate;
     }
 
+    public void setOnStartConsumer(Consumer<ClientCall> onStartConsumer) {
+        this.onStartConsumer = onStartConsumer;
+    }
+
     @Override
     public void onMessage(Object message) {
         delegate.onNext(message);
@@ -54,5 +60,7 @@
         if (call.isAutoRequest()) {
             call.request(1);
         }
+
+        onStartConsumer.accept(call);
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/CallStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/CallStreamObserver.java
index 8f0037f..362ab9e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/CallStreamObserver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/CallStreamObserver.java
@@ -43,5 +43,12 @@
      */
     void setCompression(String compression);
 
+    /**
+     * Swaps to manual flow control where no message will be delivered to {@link
+     * StreamObserver#onNext(Object)} unless it is {@link #request request()}ed. Since {@code
+     * request()} may not be called before the call is started, a number of initial requests may be
+     * specified.
+     */
+    void disableAutoFlowControl();
 
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ClientCallToObserverAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ClientCallToObserverAdapter.java
index 8002d2e..37f1769 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ClientCallToObserverAdapter.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ClientCallToObserverAdapter.java
@@ -76,7 +76,7 @@
     }
 
     @Override
-    public void disableAutoRequest() {
+    public void disableAutoFlowControl() {
         call.setAutoRequest(false);
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ServerCallToObserverAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ServerCallToObserverAdapter.java
index 8329dda..18bb578 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ServerCallToObserverAdapter.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/observer/ServerCallToObserverAdapter.java
@@ -47,7 +47,7 @@
     }
 
 
-    private boolean isTerminated() {
+    public boolean isTerminated() {
         return terminated;
     }
 
@@ -105,7 +105,7 @@
     }
 
     @Override
-    public void disableAutoInboundFlowControl() {
+    public void disableAutoFlowControl() {
         call.disableAutoRequestN();
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/Http2ProtocolDetectorTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetectorTest.java
similarity index 74%
rename from dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/Http2ProtocolDetectorTest.java
rename to dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetectorTest.java
index 702bf10..e336db6 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/api/Http2ProtocolDetectorTest.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/Http2ProtocolDetectorTest.java
@@ -14,7 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.remoting.api;
+package org.apache.dubbo.rpc.protocol.tri;
+
+import org.apache.dubbo.remoting.Channel;
+import org.apache.dubbo.remoting.api.ProtocolDetector;
+import org.apache.dubbo.remoting.buffer.ByteBufferBackedChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
+import org.apache.dubbo.remoting.buffer.ChannelBuffers;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
@@ -36,16 +42,17 @@
 
         ByteBuf connectionPrefaceBuf = Http2CodecUtil.connectionPrefaceBuf();
         ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
-        ProtocolDetector.Result result = detector.detect(ctx, byteBuf);
+        ChannelBuffer in = new ByteBufferBackedChannelBuffer(byteBuf.nioBuffer());
+        ProtocolDetector.Result result = detector.detect(in);
         Assertions.assertEquals(result, ProtocolDetector.Result.UNRECOGNIZED);
 
         byteBuf.writeBytes(connectionPrefaceBuf);
-        result = detector.detect(ctx, byteBuf);
+        result = detector.detect(new ByteBufferBackedChannelBuffer(byteBuf.nioBuffer()));
         Assertions.assertEquals(result, ProtocolDetector.Result.RECOGNIZED);
 
         byteBuf.clear();
         byteBuf.writeBytes(connectionPrefaceBuf, 0, 1);
-        result = detector.detect(ctx, byteBuf);
+        result = detector.detect(new ByteBufferBackedChannelBuffer(byteBuf.nioBuffer()));
         Assertions.assertEquals(result, ProtocolDetector.Result.NEED_MORE_DATA);
 
     }
diff --git a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Constants.java b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Constants.java
index 3715437..9dda745 100644
--- a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Constants.java
+++ b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Constants.java
@@ -32,6 +32,7 @@
     byte PROTOBUF_JSON_SERIALIZATION_ID = 21;
 
     byte PROTOBUF_SERIALIZATION_ID = 22;
+    byte FASTJSON2_SERIALIZATION_ID = 23;
     byte KRYO_SERIALIZATION2_ID = 25;
     byte CUSTOM_MESSAGE_PACK_ID = 31;
 }
diff --git a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Serialization.java b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Serialization.java
index bb67639..d0314c5 100644
--- a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Serialization.java
+++ b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/Serialization.java
@@ -33,7 +33,7 @@
  *     e.g. &lt;dubbo:protocol serialization="xxx" /&gt;
  * </pre>
  */
-@SPI(value = "hessian2", scope = ExtensionScope.FRAMEWORK)
+@SPI(scope = ExtensionScope.FRAMEWORK)
 public interface Serialization {
 
     /**
diff --git a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/support/DefaultSerializationSelector.java b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/support/DefaultSerializationSelector.java
new file mode 100644
index 0000000..86c0825
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/support/DefaultSerializationSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.serialize.support;
+
+
+public class DefaultSerializationSelector {
+
+    private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY = "DUBBO_DEFAULT_SERIALIZATION";
+
+    private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY = "hessian2";
+
+    private final static String DEFAULT_REMOTING_SERIALIZATION;
+
+    static {
+        String fromProperty = System.getProperty(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
+        if (fromProperty != null) {
+            DEFAULT_REMOTING_SERIALIZATION = fromProperty;
+        } else {
+            String fromEnv = System.getenv(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
+            if (fromEnv != null) {
+                DEFAULT_REMOTING_SERIALIZATION = fromEnv;
+            } else {
+                DEFAULT_REMOTING_SERIALIZATION = DEFAULT_REMOTING_SERIALIZATION_PROPERTY;
+            }
+        }
+    }
+
+    public static String getDefaultRemotingSerialization() {
+        return DEFAULT_REMOTING_SERIALIZATION;
+    }
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/pom.xml b/dubbo-serialization/dubbo-serialization-fastjson2/pom.xml
new file mode 100644
index 0000000..683e099
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-serialization</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>dubbo-serialization-fastjson2</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>The fastjson2 serialization module of dubbo project</description>
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectInput.java b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectInput.java
new file mode 100644
index 0000000..10b14dd
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectInput.java
@@ -0,0 +1,152 @@
+/*
+ * 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.serialize.fastjson2;
+
+import org.apache.dubbo.common.serialize.ObjectInput;
+
+import com.alibaba.fastjson2.JSONB;
+import com.alibaba.fastjson2.JSONReader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+
+/**
+ * FastJson object input implementation
+ */
+public class FastJson2ObjectInput implements ObjectInput {
+
+    private final Fastjson2CreatorManager fastjson2CreatorManager;
+
+    private volatile ClassLoader classLoader;
+    private final InputStream is;
+
+    public FastJson2ObjectInput(Fastjson2CreatorManager fastjson2CreatorManager, InputStream in) {
+        this.fastjson2CreatorManager = fastjson2CreatorManager;
+        this.classLoader = Thread.currentThread().getContextClassLoader();
+        this.is = in;
+        fastjson2CreatorManager.setCreator(classLoader);
+    }
+
+    @Override
+    public boolean readBool() throws IOException {
+        return readObject(boolean.class);
+    }
+
+    @Override
+    public byte readByte() throws IOException {
+        return readObject(byte.class);
+    }
+
+    @Override
+    public short readShort() throws IOException {
+        return readObject(short.class);
+    }
+
+    @Override
+    public int readInt() throws IOException {
+        return readObject(int.class);
+    }
+
+    @Override
+    public long readLong() throws IOException {
+        return readObject(long.class);
+    }
+
+    @Override
+    public float readFloat() throws IOException {
+        return readObject(float.class);
+    }
+
+    @Override
+    public double readDouble() throws IOException {
+        return readObject(double.class);
+    }
+
+    @Override
+    public String readUTF() throws IOException {
+        return readObject(String.class);
+    }
+
+    @Override
+    public byte[] readBytes() throws IOException {
+        int length = is.read();
+        byte[] bytes = new byte[length];
+        int read = is.read(bytes, 0, length);
+        if (read != length) {
+            throw new IllegalArgumentException("deserialize failed. expected read length: " + length + " but actual read: " + read);
+        }
+        return bytes;
+    }
+
+    @Override
+    public Object readObject() throws IOException, ClassNotFoundException {
+        return readObject(Object.class);
+    }
+
+    @Override
+    public <T> T readObject(Class<T> cls) throws IOException {
+        updateClassLoaderIfNeed();
+        int length = readLength();
+        byte[] bytes = new byte[length];
+        int read = is.read(bytes, 0, length);
+        if (read != length) {
+            throw new IllegalArgumentException("deserialize failed. expected read length: " + length + " but actual read: " + read);
+        }
+        return (T) JSONB.parseObject(bytes, Object.class, JSONReader.Feature.SupportAutoType,
+            JSONReader.Feature.UseDefaultConstructorAsPossible,
+            JSONReader.Feature.UseNativeObject,
+            JSONReader.Feature.FieldBased);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(Class<T> cls, Type type) throws IOException, ClassNotFoundException {
+        updateClassLoaderIfNeed();
+        int length = readLength();
+        byte[] bytes = new byte[length];
+        int read = is.read(bytes, 0, length);
+        if (read != length) {
+            throw new IllegalArgumentException("deserialize failed. expected read length: " + length + " but actual read: " + read);
+        }
+        return (T) JSONB.parseObject(bytes, Object.class, JSONReader.Feature.SupportAutoType,
+            JSONReader.Feature.UseDefaultConstructorAsPossible,
+            JSONReader.Feature.UseNativeObject,
+            JSONReader.Feature.FieldBased);
+    }
+
+    private void updateClassLoaderIfNeed() {
+        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+        if (currentClassLoader != classLoader) {
+            fastjson2CreatorManager.setCreator(currentClassLoader);
+            classLoader = currentClassLoader;
+        }
+    }
+
+    private int readLength() throws IOException {
+        byte[] bytes = new byte[Integer.BYTES];
+        int read = is.read(bytes, 0, Integer.BYTES);
+        if (read != Integer.BYTES) {
+            throw new IllegalArgumentException("deserialize failed. expected read length: " + Integer.BYTES + " but actual read: " + read);
+        }
+        int value = 0;
+        for (byte b : bytes) {
+            value = (value << 8) + (b & 0xFF);
+        }
+        return value;
+    }
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectOutput.java b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectOutput.java
new file mode 100644
index 0000000..fdf15ae
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2ObjectOutput.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.serialize.fastjson2;
+
+import org.apache.dubbo.common.serialize.ObjectOutput;
+
+import com.alibaba.fastjson2.JSONB;
+import com.alibaba.fastjson2.JSONWriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * FastJson object output implementation
+ */
+public class FastJson2ObjectOutput implements ObjectOutput {
+
+    private final Fastjson2CreatorManager fastjson2CreatorManager;
+
+    private volatile ClassLoader classLoader;
+    private final OutputStream os;
+
+    public FastJson2ObjectOutput(Fastjson2CreatorManager fastjson2CreatorManager, OutputStream out) {
+        this.fastjson2CreatorManager = fastjson2CreatorManager;
+        this.classLoader = Thread.currentThread().getContextClassLoader();
+        this.os = out;
+        fastjson2CreatorManager.setCreator(classLoader);
+    }
+
+    @Override
+    public void writeBool(boolean v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeByte(byte v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeShort(short v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeInt(int v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeLong(long v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeFloat(float v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeDouble(double v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeUTF(String v) throws IOException {
+        writeObject(v);
+    }
+
+    @Override
+    public void writeBytes(byte[] b) throws IOException {
+        os.write(b.length);
+        os.write(b);
+    }
+
+    @Override
+    public void writeBytes(byte[] b, int off, int len) throws IOException {
+        os.write(len);
+        os.write(b, off, len);
+    }
+
+    @Override
+    public void writeObject(Object obj) throws IOException {
+        updateClassLoaderIfNeed();
+        byte[] bytes = JSONB.toBytes(obj, JSONWriter.Feature.WriteClassName,
+            JSONWriter.Feature.FieldBased,
+            JSONWriter.Feature.ReferenceDetection,
+            JSONWriter.Feature.WriteNulls,
+            JSONWriter.Feature.NotWriteDefaultValue,
+            JSONWriter.Feature.NotWriteHashMapArrayListClassName,
+            JSONWriter.Feature.WriteNameAsSymbol);
+        writeLength(bytes.length);
+        os.write(bytes);
+        os.flush();
+    }
+
+    private void updateClassLoaderIfNeed() {
+        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+        if (currentClassLoader != classLoader) {
+            fastjson2CreatorManager.setCreator(currentClassLoader);
+            classLoader = currentClassLoader;
+        }
+    }
+
+    private void writeLength(int value) throws IOException {
+        byte[] bytes = new byte[Integer.BYTES];
+        int length = bytes.length;
+        for (int i = 0; i < length; i++) {
+            bytes[length - i - 1] = (byte) (value & 0xFF);
+            value >>= 8;
+        }
+        os.write(bytes);
+    }
+
+    @Override
+    public void flushBuffer() throws IOException {
+        os.flush();
+    }
+
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2Serialization.java b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2Serialization.java
new file mode 100644
index 0000000..4c20abc
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/FastJson2Serialization.java
@@ -0,0 +1,66 @@
+/*
+ * 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.serialize.fastjson2;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.serialize.ObjectInput;
+import org.apache.dubbo.common.serialize.ObjectOutput;
+import org.apache.dubbo.common.serialize.Serialization;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static org.apache.dubbo.common.serialize.Constants.FASTJSON2_SERIALIZATION_ID;
+
+/**
+ * FastJson serialization implementation
+ *
+ * <pre>
+ *     e.g. &lt;dubbo:protocol serialization="fastjson" /&gt;
+ * </pre>
+ */
+public class FastJson2Serialization implements Serialization {
+
+    private final Fastjson2CreatorManager fastjson2CreatorManager;
+
+    public FastJson2Serialization(FrameworkModel frameworkModel) {
+        this.fastjson2CreatorManager = frameworkModel.getBeanFactory().getBean(Fastjson2CreatorManager.class);
+    }
+
+    @Override
+    public byte getContentTypeId() {
+        return FASTJSON2_SERIALIZATION_ID;
+    }
+
+    @Override
+    public String getContentType() {
+        return "text/jsonb";
+    }
+
+    @Override
+    public ObjectOutput serialize(URL url, OutputStream output) throws IOException {
+        return new FastJson2ObjectOutput(fastjson2CreatorManager, output);
+    }
+
+    @Override
+    public ObjectInput deserialize(URL url, InputStream input) throws IOException {
+        return new FastJson2ObjectInput(fastjson2CreatorManager, input);
+    }
+
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2CreatorManager.java b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2CreatorManager.java
new file mode 100644
index 0000000..8168aee
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2CreatorManager.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.serialize.fastjson2;
+
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ScopeClassLoaderListener;
+
+import com.alibaba.fastjson2.JSONFactory;
+import com.alibaba.fastjson2.reader.ObjectReaderCreatorASM;
+import com.alibaba.fastjson2.writer.ObjectWriterCreatorASM;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Fastjson2CreatorManager implements ScopeClassLoaderListener<FrameworkModel> {
+
+    /**
+     * An empty classLoader used when classLoader is system classLoader. Prevent the NPE.
+     */
+    private static final ClassLoader SYSTEM_CLASSLOADER_KEY = new ClassLoader() {};
+
+    private final Map<ClassLoader, ObjectReaderCreatorASM> readerMap = new ConcurrentHashMap<>();
+    private final Map<ClassLoader, ObjectWriterCreatorASM> writerMap = new ConcurrentHashMap<>();
+
+    public Fastjson2CreatorManager(FrameworkModel frameworkModel) {
+        frameworkModel.addClassLoaderListener(this);
+    }
+
+    public void setCreator(ClassLoader classLoader) {
+        if (classLoader == null) {
+            classLoader = SYSTEM_CLASSLOADER_KEY;
+        }
+        JSONFactory.setContextReaderCreator(readerMap.computeIfAbsent(classLoader, ObjectReaderCreatorASM::new));
+        JSONFactory.setContextWriterCreator(writerMap.computeIfAbsent(classLoader, ObjectWriterCreatorASM::new));
+    }
+
+    @Override
+    public void onAddClassLoader(FrameworkModel scopeModel, ClassLoader classLoader) {
+        // nop
+    }
+
+    @Override
+    public void onRemoveClassLoader(FrameworkModel scopeModel, ClassLoader classLoader) {
+        readerMap.remove(classLoader);
+        writerMap.remove(classLoader);
+    }
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2ScopeModelInitializer.java b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2ScopeModelInitializer.java
new file mode 100644
index 0000000..d9c9d25
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/java/org/apache/dubbo/common/serialize/fastjson2/Fastjson2ScopeModelInitializer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.serialize.fastjson2;
+
+import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ModuleModel;
+import org.apache.dubbo.rpc.model.ScopeModelInitializer;
+
+public class Fastjson2ScopeModelInitializer implements ScopeModelInitializer {
+    @Override
+    public void initializeFrameworkModel(FrameworkModel frameworkModel) {
+        ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory();
+        beanFactory.registerBean(Fastjson2CreatorManager.class);
+    }
+
+    @Override
+    public void initializeApplicationModel(ApplicationModel applicationModel) {
+
+    }
+
+    @Override
+    public void initializeModuleModel(ModuleModel moduleModel) {
+
+    }
+}
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization
new file mode 100644
index 0000000..6cda42c
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization
@@ -0,0 +1 @@
+fastjson2=org.apache.dubbo.common.serialize.fastjson2.FastJson2Serialization
diff --git a/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer
new file mode 100644
index 0000000..186e56b
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-fastjson2/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer
@@ -0,0 +1 @@
+fastjson2=org.apache.dubbo.common.serialize.fastjson2.Fastjson2ScopeModelInitializer
diff --git a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaObjectInput.java b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaObjectInput.java
index 936ab26..b7a3294 100644
--- a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaObjectInput.java
+++ b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaObjectInput.java
@@ -27,7 +27,7 @@
  * Java object input implementation
  */
 public class JavaObjectInput extends NativeJavaObjectInput {
-    public final static int MAX_BYTE_ARRAY_LENGTH = 8 * 1024 * 1024;
+    public static final int MAX_BYTE_ARRAY_LENGTH = 8 * 1024 * 1024;
 
     public JavaObjectInput(InputStream is) throws IOException {
         super(new ObjectInputStream(is));
diff --git a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaSerialization.java b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaSerialization.java
index 996fe6e..e166f33 100644
--- a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaSerialization.java
+++ b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/java/JavaSerialization.java
@@ -39,7 +39,7 @@
  */
 public class JavaSerialization implements Serialization {
     private static final Logger logger = LoggerFactory.getLogger(JavaSerialization.class);
-    private final static AtomicBoolean warn = new AtomicBoolean(false);
+    private static final AtomicBoolean warn = new AtomicBoolean(false);
 
     @Override
     public byte getContentTypeId() {
diff --git a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/nativejava/NativeJavaSerialization.java b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/nativejava/NativeJavaSerialization.java
index 20d9d0a..854fb0e 100644
--- a/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/nativejava/NativeJavaSerialization.java
+++ b/dubbo-serialization/dubbo-serialization-jdk/src/main/java/org/apache/dubbo/common/serialize/nativejava/NativeJavaSerialization.java
@@ -41,7 +41,7 @@
  */
 public class NativeJavaSerialization implements Serialization {
     private static final Logger logger = LoggerFactory.getLogger(JavaSerialization.class);
-    private final static AtomicBoolean warn = new AtomicBoolean(false);
+    private static final AtomicBoolean warn = new AtomicBoolean(false);
 
     @Override
     public byte getContentTypeId() {
diff --git a/dubbo-serialization/pom.xml b/dubbo-serialization/pom.xml
index 6f75995..dbfa18c 100644
--- a/dubbo-serialization/pom.xml
+++ b/dubbo-serialization/pom.xml
@@ -33,6 +33,7 @@
         <module>dubbo-serialization-api</module>
         <module>dubbo-serialization-hessian2</module>
         <module>dubbo-serialization-jdk</module>
+        <module>dubbo-serialization-fastjson2</module>
     </modules>
 
     <dependencies>
diff --git a/dubbo-spring-boot/dubbo-spring-boot-actuator/pom.xml b/dubbo-spring-boot/dubbo-spring-boot-actuator/pom.xml
index 355b032..9246831 100644
--- a/dubbo-spring-boot/dubbo-spring-boot-actuator/pom.xml
+++ b/dubbo-spring-boot/dubbo-spring-boot-actuator/pom.xml
@@ -114,6 +114,13 @@
 
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+            <version>${revision}</version>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-rpc-dubbo</artifactId>
             <version>${project.version}</version>
             <optional>true</optional>
diff --git a/dubbo-test/dubbo-test-spring/pom.xml b/dubbo-test/dubbo-test-spring/pom.xml
index c496d16..6cbf254 100644
--- a/dubbo-test/dubbo-test-spring/pom.xml
+++ b/dubbo-test/dubbo-test-spring/pom.xml
@@ -111,6 +111,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-serialization-jdk</artifactId>
         </dependency>
         <dependency>
diff --git a/dubbo-xds/pom.xml b/dubbo-xds/pom.xml
new file mode 100644
index 0000000..d162c2e
--- /dev/null
+++ b/dubbo-xds/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-parent</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>dubbo-xds</artifactId>
+    <name>${project.artifactId}</name>
+    <description>The xDS Integration</description>
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty-shaded</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.envoyproxy.controlplane</groupId>
+            <artifactId>api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-ext-jdk15on</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.6.2</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>0.6.1</version>
+                <configuration>
+                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
+                    <pluginId>grpc-java</pluginId>
+                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.31.1:exe:${os.detected.classifier}</pluginArtifact>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>compile-custom</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsCertificateSigner.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsCertificateSigner.java
new file mode 100644
index 0000000..1ef1a8b
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsCertificateSigner.java
@@ -0,0 +1,58 @@
+/*
+ * 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.xds;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Adaptive;
+import org.apache.dubbo.common.extension.SPI;
+
+@SPI
+public interface XdsCertificateSigner {
+
+    @Adaptive(value = "signer")
+    CertPair GenerateCert(URL url);
+
+    class CertPair {
+        private final String privateKey;
+        private final String publicKey;
+        private final long createTime;
+        private final long expireTime;
+
+        public CertPair(String privateKey, String publicKey, long createTime, long expireTime) {
+            this.privateKey = privateKey;
+            this.publicKey = publicKey;
+            this.createTime = createTime;
+            this.expireTime = expireTime;
+        }
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public long getCreateTime() {
+            return createTime;
+        }
+
+        public boolean isExpire() {
+            return System.currentTimeMillis() < expireTime;
+        }
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsEnv.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsEnv.java
new file mode 100644
index 0000000..c09ea80
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsEnv.java
@@ -0,0 +1,22 @@
+/*
+ * 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.xds;
+
+public interface XdsEnv {
+
+    String getCluster();
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsInitializationException.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsInitializationException.java
new file mode 100644
index 0000000..b79f55c
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsInitializationException.java
@@ -0,0 +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.xds;
+
+public final class XdsInitializationException extends Exception {
+
+  public XdsInitializationException(String message) {
+    super(message);
+  }
+
+  public XdsInitializationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistry.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistry.java
new file mode 100644
index 0000000..a4bd51e
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistry.java
@@ -0,0 +1,58 @@
+/*
+ * 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.xds;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.NotifyListener;
+import org.apache.dubbo.registry.support.FailbackRegistry;
+
+/**
+ * Empty implements for xDS <br/>
+ * xDS only support `Service Discovery` mode register <br/>
+ * Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
+ * {@link XdsServiceDiscovery} is the real implementation of xDS
+ */
+public class XdsRegistry extends FailbackRegistry {
+    public XdsRegistry(URL url) {
+        super(url);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void doRegister(URL url) {
+
+    }
+
+    @Override
+    public void doUnregister(URL url) {
+
+    }
+
+    @Override
+    public void doSubscribe(URL url, NotifyListener listener) {
+
+    }
+
+    @Override
+    public void doUnsubscribe(URL url, NotifyListener listener) {
+
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistryFactory.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistryFactory.java
new file mode 100644
index 0000000..8fa130b
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsRegistryFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.xds;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.support.AbstractRegistryFactory;
+
+public class XdsRegistryFactory extends AbstractRegistryFactory {
+
+    @Override
+    protected String createRegistryCacheKey(URL url) {
+        return url.toFullString();
+    }
+
+    @Override
+    protected Registry createRegistry(URL url) {
+        return new XdsRegistry(url);
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscovery.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscovery.java
new file mode 100644
index 0000000..28945f8
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscovery.java
@@ -0,0 +1,97 @@
+/*
+ * 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.xds;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.ReflectionBasedServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.xds.util.PilotExchanger;
+import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ScopeModelUtil;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class XdsServiceDiscovery extends ReflectionBasedServiceDiscovery {
+
+    private static final Logger logger = LoggerFactory.getLogger(XdsServiceDiscovery.class);
+
+    private PilotExchanger exchanger;
+
+    public XdsServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
+        super(applicationModel, registryURL);
+    }
+
+    @Override
+    public void doInitialize(URL registryURL) {
+        try {
+            exchanger = PilotExchanger.initialize(registryURL);
+        } catch (Throwable t) {
+            logger.error(t);
+        }
+    }
+
+    @Override
+    public void doDestroy() {
+        try {
+            exchanger.destroy();
+        } catch (Throwable t) {
+            logger.error(t);
+        }
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return exchanger.getServices();
+    }
+
+    @Override
+    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
+        Set<Endpoint> endpoints = exchanger.getEndpoints(serviceName);
+        return changedToInstances(serviceName, endpoints);
+    }
+
+    @Override
+    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
+        listener.getServiceNames().forEach(serviceName -> exchanger.observeEndpoints(serviceName,
+            (endpoints -> notifyListener(serviceName, listener, changedToInstances(serviceName, endpoints)))));
+    }
+
+    private List<ServiceInstance> changedToInstances(String serviceName, Collection<Endpoint> endpoints) {
+        List<ServiceInstance> instances = new LinkedList<>();
+        endpoints.forEach(endpoint -> {
+            try {
+                DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, endpoint.getAddress(), endpoint.getPortValue(), ScopeModelUtil.getApplicationModel(getUrl().getScopeModel()));
+                // fill metadata by SelfHostMetaServiceDiscovery, will be fetched by RPC request
+                fillServiceInstance(serviceInstance);
+                instances.add(serviceInstance);
+            } catch (Throwable t) {
+                logger.error("Error occurred when parsing endpoints. Endpoints List:" + endpoints, t);
+            }
+        });
+        instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
+        return instances;
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscoveryFactory.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscoveryFactory.java
new file mode 100644
index 0000000..41eba21
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/XdsServiceDiscoveryFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.xds;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+public class XdsServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
+
+    private static final Logger logger = LoggerFactory.getLogger(XdsServiceDiscoveryFactory.class);
+
+    @Override
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        XdsServiceDiscovery xdsServiceDiscovery = new XdsServiceDiscovery(ApplicationModel.defaultModel() ,registryURL);
+        try {
+            xdsServiceDiscovery.doInitialize(registryURL);
+        } catch (Exception e) {
+            logger.error("Error occurred when initialize xDS service discovery impl.", e);
+        }
+        return xdsServiceDiscovery;
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioCitadelCertificateSigner.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioCitadelCertificateSigner.java
new file mode 100644
index 0000000..cf91f1e
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioCitadelCertificateSigner.java
@@ -0,0 +1,251 @@
+/*
+ * 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.xds.istio;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.XdsCertificateSigner;
+import org.apache.dubbo.rpc.RpcException;
+
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.grpc.stub.MetadataUtils;
+import io.grpc.stub.StreamObserver;
+import istio.v1.auth.IstioCertificateRequest;
+import istio.v1.auth.IstioCertificateResponse;
+import istio.v1.auth.IstioCertificateServiceGrpc;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.util.io.pem.PemObject;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.ECGenParameterSpec;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class IstioCitadelCertificateSigner implements XdsCertificateSigner {
+
+    private static final Logger logger = LoggerFactory.getLogger(IstioCitadelCertificateSigner.class);
+
+    private final org.apache.dubbo.registry.xds.istio.IstioEnv istioEnv;
+
+    private CertPair certPair;
+
+    public IstioCitadelCertificateSigner() {
+        // watch cert, Refresh every 30s
+        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
+        scheduledThreadPool.scheduleAtFixedRate(new GenerateCertTask(), 0, 30, TimeUnit.SECONDS);
+        istioEnv = IstioEnv.getInstance();
+    }
+
+    @Override
+    public CertPair GenerateCert(URL url) {
+
+        if (certPair != null && !certPair.isExpire()) {
+            return certPair;
+        }
+        return doGenerateCert();
+    }
+
+    private class GenerateCertTask implements Runnable {
+        @Override
+        public void run() {
+            doGenerateCert();
+        }
+    }
+
+    private CertPair doGenerateCert() {
+        synchronized (this) {
+            if (certPair == null || certPair.isExpire()) {
+                try {
+                    certPair = createCert();
+                } catch (IOException e) {
+                    logger.error("Generate Cert from Istio failed.", e);
+                    throw new RpcException("Generate Cert from Istio failed.", e);
+                }
+            }
+        }
+        return certPair;
+    }
+
+    public CertPair createCert() throws IOException {
+        PublicKey publicKey = null;
+        PrivateKey privateKey = null;
+        ContentSigner signer = null;
+
+        if (istioEnv.isECCFirst()) {
+            try {
+                ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+                KeyPairGenerator g = KeyPairGenerator.getInstance("EC");
+                g.initialize(ecSpec, new SecureRandom());
+                KeyPair keypair = g.generateKeyPair();
+                publicKey = keypair.getPublic();
+                privateKey = keypair.getPrivate();
+                signer = new JcaContentSignerBuilder("SHA256withECDSA").build(keypair.getPrivate());
+            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | OperatorCreationException e) {
+                logger.error("Generate Key with secp256r1 algorithm failed. Please check if your system support. "
+                    + "Will attempt to generate with RSA2048.", e);
+            }
+        }
+
+        if (publicKey == null) {
+            try {
+                KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA");
+                kpGenerator.initialize(istioEnv.getRasKeySize());
+                KeyPair keypair = kpGenerator.generateKeyPair();
+                publicKey = keypair.getPublic();
+                privateKey = keypair.getPrivate();
+                signer = new JcaContentSignerBuilder("SHA256WithRSA").build(keypair.getPrivate());
+            } catch (NoSuchAlgorithmException | OperatorCreationException e) {
+                logger.error("Generate Key with SHA256WithRSA algorithm failed. Please check if your system support.", e);
+                throw new RpcException(e);
+            }
+        }
+
+        String csr = generateCsr(publicKey, signer);
+        ManagedChannel channel = NettyChannelBuilder.forTarget(istioEnv.getCaAddr())
+            .sslContext(GrpcSslContexts.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build()).build();
+
+        Metadata header = new Metadata();
+        Metadata.Key<String> key = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
+        header.put(key, "Bearer " + istioEnv.getServiceAccount());
+
+        key = Metadata.Key.of("ClusterID", Metadata.ASCII_STRING_MARSHALLER);
+        header.put(key, istioEnv.getIstioMetaClusterId());
+
+        IstioCertificateServiceGrpc.IstioCertificateServiceStub stub = IstioCertificateServiceGrpc.newStub(channel);
+
+        stub = MetadataUtils.attachHeaders(stub, header);
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        StringBuffer publicKeyBuilder = new StringBuffer();
+        AtomicBoolean failed = new AtomicBoolean(false);
+        stub.createCertificate(generateRequest(csr), generateResponseObserver(countDownLatch, publicKeyBuilder, failed));
+
+        long expireTime = System.currentTimeMillis() + (long) (istioEnv.getSecretTTL() * istioEnv.getSecretGracePeriodRatio());
+
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            throw new RpcException("Generate Cert Failed. Wait for cert failed.", e);
+        }
+
+        if (failed.get()) {
+            throw new RpcException("Generate Cert Failed. Send csr request failed. Please check log above.");
+        }
+
+        String privateKeyPem = generatePrivatePemKey(privateKey);
+        CertPair certPair = new CertPair(privateKeyPem, publicKeyBuilder.toString(), System.currentTimeMillis(), expireTime);
+
+        channel.shutdown();
+        return certPair;
+    }
+
+    private IstioCertificateRequest generateRequest(String csr) {
+        return IstioCertificateRequest.newBuilder().setCsr(csr).setValidityDuration(istioEnv.getSecretTTL()).build();
+    }
+
+    private StreamObserver<IstioCertificateResponse> generateResponseObserver(CountDownLatch countDownLatch,
+                                                                              StringBuffer publicKeyBuilder,
+                                                                              AtomicBoolean failed) {
+        return new StreamObserver<IstioCertificateResponse>() {
+            @Override
+            public void onNext(IstioCertificateResponse istioCertificateResponse) {
+                for (int i = 0; i < istioCertificateResponse.getCertChainCount(); i++) {
+                    publicKeyBuilder.append(istioCertificateResponse.getCertChainBytes(i).toStringUtf8());
+                }
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Receive Cert chain from Istio Citadel. \n" + publicKeyBuilder);
+                }
+                countDownLatch.countDown();
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+                failed.set(true);
+                logger.error("Receive error message from Istio Citadel grpc stub.", throwable);
+                countDownLatch.countDown();
+            }
+
+            @Override
+            public void onCompleted() {
+                countDownLatch.countDown();
+            }
+        };
+    }
+
+    private String generatePrivatePemKey(PrivateKey privateKey) throws IOException {
+        String key = generatePemKey("RSA PRIVATE KEY", privateKey.getEncoded());
+        if (logger.isDebugEnabled()) {
+            logger.debug("Generated Private Key. \n" + key);
+        }
+        return key;
+    }
+
+    private String generatePemKey(String type, byte[] content) throws IOException {
+        PemObject pemObject = new PemObject(type, content);
+        StringWriter str = new StringWriter();
+        JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(str);
+        jcaPEMWriter.writeObject(pemObject);
+        jcaPEMWriter.close();
+        str.close();
+        return str.toString();
+    }
+
+    private String generateCsr(PublicKey publicKey, ContentSigner signer) throws IOException {
+        GeneralNames subjectAltNames = new GeneralNames(new GeneralName[]{new GeneralName(6, istioEnv.getCsrHost())});
+
+        ExtensionsGenerator extGen = new ExtensionsGenerator();
+        extGen.addExtension(Extension.subjectAlternativeName, true, subjectAltNames);
+
+        PKCS10CertificationRequest request = new JcaPKCS10CertificationRequestBuilder(
+            new X500Name("O=" + istioEnv.getTrustDomain()), publicKey).addAttribute(
+            PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()).build(signer);
+
+        String csr = generatePemKey("CERTIFICATE REQUEST", request.getEncoded());
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("CSR Request to Istio Citadel. \n" + csr);
+        }
+        return csr;
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioConstant.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioConstant.java
new file mode 100644
index 0000000..d14aca0
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioConstant.java
@@ -0,0 +1,93 @@
+/*
+ * 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.xds.istio;
+
+public class IstioConstant {
+    /**
+     * Address of the spiffe certificate provider. Defaults to discoveryAddress
+     */
+    public final static String CA_ADDR_KEY = "CA_ADDR";
+
+    /**
+     * CA and xDS services
+     */
+    public final static String DEFAULT_CA_ADDR = "istiod.istio-system.svc:15012";
+
+    /**
+     * The trust domain for spiffe certificates
+     */
+    public final static String TRUST_DOMAIN_KEY = "TRUST_DOMAIN";
+
+    /**
+     * The trust domain for spiffe certificates default value
+     */
+    public final static String DEFAULT_TRUST_DOMAIN = "cluster.local";
+
+    public final static String WORKLOAD_NAMESPACE_KEY = "WORKLOAD_NAMESPACE";
+
+    public final static String DEFAULT_WORKLOAD_NAMESPACE = "default";
+
+    /**
+     * k8s jwt token
+     */
+    public final static String KUBERNETES_SA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
+
+    public final static String KUBERNETES_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
+
+    public final static String KUBERNETES_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
+
+    public final static String RSA_KEY_SIZE_KEY = "RSA_KEY_SIZE";
+
+    public final static String DEFAULT_RSA_KEY_SIZE = "2048";
+
+    /**
+     * The type of ECC signature algorithm to use when generating private keys
+     */
+    public final static String ECC_SIG_ALG_KEY = "ECC_SIGNATURE_ALGORITHM";
+
+    public final static String DEFAULT_ECC_SIG_ALG = "ECDSA";
+
+    /**
+     * The cert lifetime requested by istio agent
+     */
+    public final static String SECRET_TTL_KEY = "SECRET_TTL";
+
+    /**
+     * The cert lifetime default value 24h0m0s
+     */
+    public final static String DEFAULT_SECRET_TTL = "86400"; //24 * 60 * 60
+
+    /**
+     * The grace period ratio for the cert rotation
+     */
+    public final static String SECRET_GRACE_PERIOD_RATIO_KEY = "SECRET_GRACE_PERIOD_RATIO";
+
+    /**
+     * The grace period ratio for the cert rotation, by default 0.5
+     */
+    public final static String DEFAULT_SECRET_GRACE_PERIOD_RATIO = "0.5";
+
+    public final static String ISTIO_META_CLUSTER_ID_KEY = "ISTIO_META_CLUSTER_ID";
+
+    public final static String DEFAULT_ISTIO_META_CLUSTER_ID = "Kubernetes";
+
+    public final static String SPIFFE = "spiffe://";
+
+    public final static String NS = "/ns/";
+
+    public final static String SA = "/sa/";
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioEnv.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioEnv.java
new file mode 100644
index 0000000..0bb0e06
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/istio/IstioEnv.java
@@ -0,0 +1,165 @@
+/*
+ * 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.xds.istio;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.XdsEnv;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import static org.apache.dubbo.registry.xds.istio.IstioConstant.NS;
+import static org.apache.dubbo.registry.xds.istio.IstioConstant.SA;
+import static org.apache.dubbo.registry.xds.istio.IstioConstant.SPIFFE;
+
+public class IstioEnv implements XdsEnv {
+    private static final Logger logger = LoggerFactory.getLogger(IstioEnv.class);
+
+    private static final IstioEnv INSTANCE = new IstioEnv();
+
+    private String podName;
+
+    private String caAddr;
+
+    private String serviceAccount = null;
+
+    private String csrHost;
+
+    private String trustDomain;
+
+    private String workloadNameSpace;
+
+    private int rasKeySize;
+
+    private String eccSigAlg;
+
+    private int secretTTL;
+
+    private float secretGracePeriodRatio;
+
+    private String istioMetaClusterId;
+
+    private String caCert;
+
+    private IstioEnv() {
+        // read k8s jwt token
+        File saFile = new File(IstioConstant.KUBERNETES_SA_PATH);
+        if (saFile.canRead()) {
+            try {
+                podName = System.getenv("HOSTNAME");
+                serviceAccount = FileUtils.readFileToString(saFile, StandardCharsets.UTF_8);
+                trustDomain = Optional.ofNullable(System.getenv(IstioConstant.TRUST_DOMAIN_KEY)).orElse(IstioConstant.DEFAULT_TRUST_DOMAIN);
+                workloadNameSpace = Optional.ofNullable(System.getenv(IstioConstant.WORKLOAD_NAMESPACE_KEY))
+                    .orElseGet(()->{
+                        File namespaceFile = new File(IstioConstant.KUBERNETES_NAMESPACE_PATH);
+                        if (namespaceFile.canRead()) {
+                            try {
+                                return FileUtils.readFileToString(namespaceFile, StandardCharsets.UTF_8);
+                            } catch (IOException e) {
+                                logger.error("read namespace file error", e);
+                            }
+                        }
+                        return IstioConstant.DEFAULT_WORKLOAD_NAMESPACE;
+                    });
+                // spiffe://<trust_domain>/ns/<namespace>/sa/<service_account>
+                csrHost = SPIFFE + trustDomain + NS + workloadNameSpace + SA + serviceAccount;
+                caAddr = Optional.ofNullable(System.getenv(IstioConstant.CA_ADDR_KEY)).orElse(IstioConstant.DEFAULT_CA_ADDR);
+                rasKeySize = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.RSA_KEY_SIZE_KEY)).orElse(IstioConstant.DEFAULT_RSA_KEY_SIZE));
+                eccSigAlg = Optional.ofNullable(System.getenv(IstioConstant.ECC_SIG_ALG_KEY)).orElse(IstioConstant.DEFAULT_ECC_SIG_ALG);
+                secretTTL = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.SECRET_TTL_KEY)).orElse(IstioConstant.DEFAULT_SECRET_TTL));
+                secretGracePeriodRatio = Float.parseFloat(Optional.ofNullable(System.getenv(IstioConstant.SECRET_GRACE_PERIOD_RATIO_KEY)).orElse(IstioConstant.DEFAULT_SECRET_GRACE_PERIOD_RATIO));
+                istioMetaClusterId = Optional.ofNullable(System.getenv(IstioConstant.ISTIO_META_CLUSTER_ID_KEY)).orElse(IstioConstant.DEFAULT_ISTIO_META_CLUSTER_ID);
+                File caFile = new File(IstioConstant.KUBERNETES_CA_PATH);
+                if (caFile.canRead()) {
+                    try {
+                        caCert = FileUtils.readFileToString(caFile, StandardCharsets.UTF_8);
+                    } catch (IOException e) {
+                        logger.error("read ca file error", e);
+                    }
+                }
+            } catch (IOException e) {
+                logger.error("Unable to read token file.", e);
+            }
+        }
+        if (serviceAccount == null) {
+            throw new UnsupportedOperationException("Unable to found kubernetes service account token file. " +
+                "Please check if work in Kubernetes and mount service account token file correctly.");
+        }
+    }
+
+    public static IstioEnv getInstance() {
+        return INSTANCE;
+    }
+
+    public String getPodName() {
+        return podName;
+    }
+
+    public String getCaAddr() {
+        return caAddr;
+    }
+
+    public String getServiceAccount() {
+        return serviceAccount;
+    }
+
+    public String getCsrHost() {
+        return csrHost;
+    }
+
+    public String getTrustDomain() {
+        return trustDomain;
+    }
+
+    public String getWorkloadNameSpace() {
+        return workloadNameSpace;
+    }
+
+    @Override
+    public String getCluster() {
+        return null;
+    }
+
+    public int getRasKeySize() {
+        return rasKeySize;
+    }
+
+    public boolean isECCFirst() {
+        return IstioConstant.DEFAULT_ECC_SIG_ALG.equals(eccSigAlg);
+    }
+
+    public int getSecretTTL() {
+        return secretTTL;
+    }
+
+    public float getSecretGracePeriodRatio() {
+        return secretGracePeriodRatio;
+    }
+
+    public String getIstioMetaClusterId() {
+        return istioMetaClusterId;
+    }
+
+    public String getCaCert() {
+        return caCert;
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/NodeBuilder.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/NodeBuilder.java
new file mode 100644
index 0000000..e353bec
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/NodeBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.xds.util;
+
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.registry.xds.istio.IstioEnv;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+
+public class NodeBuilder {
+
+    private final static String SVC_CLUSTER_LOCAL = ".svc.cluster.local";
+
+    public static Node build() {
+//        String podName = System.getenv("metadata.name");
+//        String podNamespace = System.getenv("metadata.namespace");
+
+        String podName = IstioEnv.getInstance().getPodName();
+        String podNamespace = IstioEnv.getInstance().getWorkloadNameSpace();
+        String svcName = IstioEnv.getInstance().getIstioMetaClusterId();
+
+        // id -> sidecar~ip~{POD_NAME}~{NAMESPACE_NAME}.svc.cluster.local
+        // cluster -> {SVC_NAME}
+        return Node.newBuilder()
+            .setId("sidecar~" + NetUtils.getLocalHost() + "~" +podName + "~" + podNamespace + SVC_CLUSTER_LOCAL)
+            .setCluster(svcName)
+            .build();
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/PilotExchanger.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/PilotExchanger.java
new file mode 100644
index 0000000..dbb30e7
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/PilotExchanger.java
@@ -0,0 +1,162 @@
+/*
+ * 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.xds.util;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.registry.xds.util.protocol.impl.EdsProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.impl.LdsProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.impl.RdsProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
+import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
+import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
+import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+public class PilotExchanger {
+
+    private final XdsChannel xdsChannel;
+
+    private final RdsProtocol rdsProtocol;
+
+    private final EdsProtocol edsProtocol;
+
+    private ListenerResult listenerResult;
+
+    private RouteResult routeResult;
+
+    private final AtomicLong observeRouteRequest = new AtomicLong(-1);
+
+    private final Map<String, Long> domainObserveRequest = new ConcurrentHashMap<>();
+
+    private final Map<String, Set<Consumer<Set<Endpoint>>>> domainObserveConsumer = new ConcurrentHashMap<>();
+
+    private PilotExchanger(URL url) {
+        xdsChannel = new XdsChannel(url);
+        int pollingPoolSize = url.getParameter("pollingPoolSize", 10);
+        int pollingTimeout = url.getParameter("pollingTimeout", 10);
+        LdsProtocol ldsProtocol = new LdsProtocol(xdsChannel, NodeBuilder.build(), pollingPoolSize, pollingTimeout);
+        this.rdsProtocol = new RdsProtocol(xdsChannel, NodeBuilder.build(), pollingPoolSize, pollingTimeout);
+        this.edsProtocol = new EdsProtocol(xdsChannel, NodeBuilder.build(), pollingPoolSize, pollingTimeout);
+
+        this.listenerResult = ldsProtocol.getListeners();
+        this.routeResult = rdsProtocol.getResource(listenerResult.getRouteConfigNames());
+
+        // Observer RDS update
+        if (CollectionUtils.isNotEmpty(listenerResult.getRouteConfigNames())) {
+            this.observeRouteRequest.set(createRouteObserve());
+        }
+        // Observe LDS updated
+        ldsProtocol.observeListeners((newListener) -> {
+            // update local cache
+            if (!newListener.equals(listenerResult)) {
+                this.listenerResult = newListener;
+                // update RDS observation
+                synchronized (observeRouteRequest) {
+                    if (observeRouteRequest.get() == -1) {
+                        this.observeRouteRequest.set(createRouteObserve());
+                    } else {
+                        rdsProtocol.updateObserve(observeRouteRequest.get(), newListener.getRouteConfigNames());
+                    }
+                }
+            }
+        });
+    }
+
+    private long createRouteObserve() {
+        return rdsProtocol.observeResource(listenerResult.getRouteConfigNames(), (newResult) -> {
+            // check if observed domain update ( will update endpoint observation )
+            domainObserveConsumer.forEach((domain, consumer) -> {
+                Set<String> newRoute = newResult.searchDomain(domain);
+                if (!routeResult.searchDomain(domain).equals(newRoute)) {
+                    // routers in observed domain has been updated
+                    Long domainRequest = domainObserveRequest.get(domain);
+                    if (domainRequest == null) {
+                        // router list is empty when observeEndpoints() called and domainRequest has not been created yet
+                        // create new observation
+                        doObserveEndpoints(domain);
+                    } else {
+                        // update observation by domainRequest
+                        edsProtocol.updateObserve(domainRequest, newRoute);
+                    }
+                }
+            });
+            // update local cache
+            routeResult = newResult;
+        });
+    }
+
+    public static PilotExchanger initialize(URL url) {
+        return new PilotExchanger(url);
+    }
+
+    public void destroy() {
+        xdsChannel.destroy();
+    }
+
+    public Set<String> getServices() {
+        return routeResult.getDomains();
+    }
+
+    public Set<Endpoint> getEndpoints(String domain) {
+        Set<String> cluster = routeResult.searchDomain(domain);
+        if (CollectionUtils.isNotEmpty(cluster)) {
+            EndpointResult endpoint = edsProtocol.getResource(cluster);
+            return endpoint.getEndpoints();
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    public void observeEndpoints(String domain, Consumer<Set<Endpoint>> consumer) {
+        // store Consumer
+        domainObserveConsumer.compute(domain, (k, v) -> {
+            if (v == null) {
+                v = new ConcurrentHashSet<>();
+            }
+            // support multi-consumer
+            v.add(consumer);
+            return v;
+        });
+        if (!domainObserveRequest.containsKey(domain)) {
+            doObserveEndpoints(domain);
+        }
+    }
+
+    private void doObserveEndpoints(String domain) {
+        Set<String> router = routeResult.searchDomain(domain);
+        // if router is empty, do nothing
+        // observation will be created when RDS updates
+        if (CollectionUtils.isNotEmpty(router)) {
+            long endpointRequest =
+                edsProtocol.observeResource(
+                    router,
+                    endpointResult ->
+                        // notify consumers
+                        domainObserveConsumer.get(domain).forEach(
+                            consumer1 -> consumer1.accept(endpointResult.getEndpoints())));
+            domainObserveRequest.put(domain, endpointRequest);
+        }
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/XdsChannel.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/XdsChannel.java
new file mode 100644
index 0000000..9019d38
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/XdsChannel.java
@@ -0,0 +1,102 @@
+/*
+ * 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.xds.util;
+
+import io.grpc.ManagedChannel;
+import io.grpc.netty.shaded.io.netty.channel.epoll.EpollDomainSocketChannel;
+import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup;
+import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
+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.url.component.URLAddress;
+import org.apache.dubbo.registry.xds.XdsCertificateSigner;
+import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc;
+import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest;
+import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
+import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.grpc.stub.StreamObserver;
+import org.apache.dubbo.registry.xds.util.bootstrap.Bootstrapper;
+import org.apache.dubbo.registry.xds.util.bootstrap.BootstrapperImpl;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+public class XdsChannel {
+
+    private static final Logger logger = LoggerFactory.getLogger(XdsChannel.class);
+
+    private static final String USE_AGENT = "use-agent";
+
+    private final ManagedChannel channel;
+
+    protected XdsChannel(URL url) {
+        ManagedChannel managedChannel = null;
+        try {
+            if(!url.getParameter(USE_AGENT,false)) {
+                XdsCertificateSigner signer = url.getOrDefaultApplicationModel().getExtensionLoader(XdsCertificateSigner.class)
+                    .getExtension(url.getParameter("signer", "istio"));
+                XdsCertificateSigner.CertPair certPair = signer.GenerateCert(url);
+                SslContext context = GrpcSslContexts.forClient()
+                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
+                    .keyManager(new ByteArrayInputStream(certPair.getPublicKey().getBytes(StandardCharsets.UTF_8)),
+                        new ByteArrayInputStream(certPair.getPrivateKey().getBytes(StandardCharsets.UTF_8)))
+                    .build();
+                managedChannel = NettyChannelBuilder.forAddress(url.getHost(), url.getPort()).sslContext(context)
+                    .build();
+            }
+            else {
+                BootstrapperImpl bootstrapper = new BootstrapperImpl();
+                Bootstrapper.BootstrapInfo bootstrapInfo = bootstrapper.bootstrap();
+                URLAddress address =URLAddress.parse(bootstrapInfo.servers().get(0).target(),null, false);
+                EpollEventLoopGroup elg = new EpollEventLoopGroup();
+                managedChannel = NettyChannelBuilder.forAddress(new DomainSocketAddress("/" + address.getPath()))
+                    .eventLoopGroup(elg)
+                    .channelType(EpollDomainSocketChannel.class)
+                    .usePlaintext()
+                    .build();
+            }
+        } catch (Exception e) {
+            logger.error("Error occurred when creating gRPC channel to control panel.", e);
+        }
+        channel = managedChannel;
+    }
+
+    public StreamObserver<DeltaDiscoveryRequest> observeDeltaDiscoveryRequest(StreamObserver<DeltaDiscoveryResponse> observer) {
+        return AggregatedDiscoveryServiceGrpc.newStub(channel).deltaAggregatedResources(observer);
+    }
+
+    public StreamObserver<DiscoveryRequest> createDeltaDiscoveryRequest(StreamObserver<DiscoveryResponse> observer) {
+        return AggregatedDiscoveryServiceGrpc.newStub(channel).streamAggregatedResources(observer);
+    }
+
+    public StreamObserver<io.envoyproxy.envoy.api.v2.DeltaDiscoveryRequest> observeDeltaDiscoveryRequestV2(StreamObserver<io.envoyproxy.envoy.api.v2.DeltaDiscoveryResponse> observer) {
+        return io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel).deltaAggregatedResources(observer);
+    }
+
+    public StreamObserver<io.envoyproxy.envoy.api.v2.DiscoveryRequest> createDeltaDiscoveryRequestV2(StreamObserver<io.envoyproxy.envoy.api.v2.DiscoveryResponse> observer) {
+        return io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel).streamAggregatedResources(observer);
+    }
+
+    public void destroy() {
+        channel.shutdown();
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapInfoImpl.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapInfoImpl.java
new file mode 100644
index 0000000..a4e4451
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapInfoImpl.java
@@ -0,0 +1,129 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+
+import javax.annotation.Nullable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public final class BootstrapInfoImpl extends Bootstrapper.BootstrapInfo {
+
+    private final List<Bootstrapper.ServerInfo> servers;
+
+    private final String serverListenerResourceNameTemplate;
+
+    private final Map<String, Bootstrapper.CertificateProviderInfo> certProviders;
+
+    private final Node node;
+
+    BootstrapInfoImpl(List<Bootstrapper.ServerInfo> servers, String serverListenerResourceNameTemplate, Map<String, Bootstrapper.CertificateProviderInfo> certProviders, Node node) {
+        this.servers = servers;
+        this.serverListenerResourceNameTemplate = serverListenerResourceNameTemplate;
+        this.certProviders = certProviders;
+        this.node = node;
+    }
+
+    @Override
+    public List<Bootstrapper.ServerInfo> servers() {
+        return servers;
+    }
+
+    public Map<String, Bootstrapper.CertificateProviderInfo> certProviders() {
+        return certProviders;
+    }
+
+    @Override
+    public Node node() {
+        return node;
+    }
+
+    @Override
+    public String serverListenerResourceNameTemplate() {
+        return serverListenerResourceNameTemplate;
+    }
+
+    @Override
+    public String toString() {
+        return "BootstrapInfo{"
+            + "servers=" + servers + ", "
+            + "serverListenerResourceNameTemplate=" + serverListenerResourceNameTemplate + ", "
+            + "node=" + node + ", "
+            + "}";
+    }
+
+    public static final class Builder extends Bootstrapper.BootstrapInfo.Builder {
+        private List<Bootstrapper.ServerInfo> servers;
+        private Node node;
+
+        private Map<String, Bootstrapper.CertificateProviderInfo> certProviders;
+
+        private String serverListenerResourceNameTemplate;
+        Builder() {
+        }
+        @Override
+        Bootstrapper.BootstrapInfo.Builder servers(List<Bootstrapper.ServerInfo> servers) {
+            this.servers = new LinkedList<>(servers);
+            return this;
+        }
+
+        @Override
+        Bootstrapper.BootstrapInfo.Builder node(Node node) {
+            if (node == null) {
+                throw new NullPointerException("Null node");
+            }
+            this.node = node;
+            return this;
+        }
+
+        @Override
+        Bootstrapper.BootstrapInfo.Builder certProviders(@Nullable Map<String, Bootstrapper.CertificateProviderInfo> certProviders) {
+            this.certProviders = certProviders;
+            return this;
+        }
+
+        @Override
+        Bootstrapper.BootstrapInfo.Builder serverListenerResourceNameTemplate(@Nullable String serverListenerResourceNameTemplate) {
+            this.serverListenerResourceNameTemplate = serverListenerResourceNameTemplate;
+            return this;
+        }
+
+        @Override
+        Bootstrapper.BootstrapInfo build() {
+            if (this.servers == null
+                || this.node == null) {
+                StringBuilder missing = new StringBuilder();
+                if (this.servers == null) {
+                    missing.append(" servers");
+                }
+                if (this.node == null) {
+                    missing.append(" node");
+                }
+                throw new IllegalStateException("Missing required properties:" + missing);
+            }
+            return new BootstrapInfoImpl(
+                this.servers,
+                this.serverListenerResourceNameTemplate,
+                this.certProviders,
+                this.node);
+        }
+    }
+
+}
+
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/Bootstrapper.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/Bootstrapper.java
new file mode 100644
index 0000000..5e018c4
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/Bootstrapper.java
@@ -0,0 +1,75 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.grpc.ChannelCredentials;
+import org.apache.dubbo.registry.xds.XdsInitializationException;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+
+public abstract class Bootstrapper {
+
+    public abstract BootstrapInfo bootstrap() throws XdsInitializationException;
+
+    BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
+        throw new UnsupportedOperationException();
+    }
+
+    public abstract static class ServerInfo {
+        public abstract String target();
+
+        abstract ChannelCredentials channelCredentials();
+
+        abstract boolean useProtocolV3();
+
+        abstract boolean ignoreResourceDeletion();
+
+    }
+
+    public abstract static class CertificateProviderInfo {
+        public abstract String pluginName();
+
+        public abstract Map<String, ?> config();
+    }
+
+    public abstract static class BootstrapInfo {
+        public abstract List<ServerInfo> servers();
+
+        public abstract Map<String, CertificateProviderInfo> certProviders();
+
+        public abstract Node node();
+
+        public abstract String serverListenerResourceNameTemplate();
+
+        abstract static class Builder {
+
+            abstract Builder servers(List<ServerInfo> servers);
+
+            abstract Builder node(Node node);
+
+            abstract Builder certProviders(@Nullable Map<String, CertificateProviderInfo> certProviders);
+
+            abstract Builder serverListenerResourceNameTemplate(
+                @Nullable String serverListenerResourceNameTemplate);
+
+            abstract BootstrapInfo build();
+        }
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperImpl.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperImpl.java
new file mode 100644
index 0000000..d9e2997
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperImpl.java
@@ -0,0 +1,180 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.grpc.ChannelCredentials;
+import io.grpc.internal.JsonParser;
+import io.grpc.internal.JsonUtil;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.XdsInitializationException;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class BootstrapperImpl extends Bootstrapper {
+
+    static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP";
+    static String bootstrapPathFromEnvVar = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR);
+
+    private static final Logger logger = LoggerFactory.getLogger(BootstrapperImpl.class);
+    private FileReader reader = LocalFileReader.INSTANCE;
+
+    private static final String SERVER_FEATURE_XDS_V3 = "xds_v3";
+    private static final String SERVER_FEATURE_IGNORE_RESOURCE_DELETION = "ignore_resource_deletion";
+
+    public BootstrapInfo bootstrap() throws XdsInitializationException {
+        String filePath = bootstrapPathFromEnvVar;
+        String fileContent = null;
+        if (filePath != null) {
+            try {
+                fileContent = reader.readFile(filePath);
+            } catch (IOException e) {
+                throw new XdsInitializationException("Fail to read bootstrap file", e);
+            }
+        }
+        if (fileContent == null) throw new XdsInitializationException("Cannot find bootstrap configuration");
+
+        Map<String, ?> rawBootstrap;
+        try {
+            rawBootstrap = (Map<String, ?>) JsonParser.parse(fileContent);
+        } catch (IOException e) {
+            throw new XdsInitializationException("Failed to parse JSON", e);
+        }
+        return bootstrap(rawBootstrap);
+    }
+
+    @Override
+    BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
+        BootstrapInfo.Builder builder = new BootstrapInfoImpl.Builder();
+
+        List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
+        if (rawServerConfigs == null) {
+            throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
+        }
+        List<ServerInfo> servers = parseServerInfos(rawServerConfigs);
+        builder.servers(servers);
+
+        Node.Builder nodeBuilder = Node.newBuilder();
+        Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
+        if (rawNode != null) {
+            String id = JsonUtil.getString(rawNode, "id");
+            if (id != null) {
+                nodeBuilder.setId(id);
+            }
+            String cluster = JsonUtil.getString(rawNode, "cluster");
+            if (cluster != null) {
+                nodeBuilder.setCluster(cluster);
+            }
+            Map<String, ?> metadata = JsonUtil.getObject(rawNode, "metadata");
+            Map<String, ?> rawLocality = JsonUtil.getObject(rawNode, "locality");
+        }
+        builder.node(nodeBuilder.build());
+
+        Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
+        if (certProvidersBlob != null) {
+            Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
+            for (String name : certProvidersBlob.keySet()) {
+                Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
+                String pluginName =
+                    checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
+                Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
+                CertificateProviderInfoImpl certificateProviderInfo =
+                    new CertificateProviderInfoImpl(pluginName, config);
+                certProviders.put(name, certificateProviderInfo);
+            }
+            builder.certProviders(certProviders);
+        }
+
+        return builder.build();
+    }
+
+    private static List<ServerInfo> parseServerInfos(List<?> rawServerConfigs)
+        throws XdsInitializationException {
+        List<ServerInfo> servers = new LinkedList<>();
+        List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
+        for (Map<String, ?> serverConfig : serverConfigList) {
+            String serverUri = JsonUtil.getString(serverConfig, "server_uri");
+            if (serverUri == null) {
+                throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
+            }
+            List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
+            if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
+                throw new XdsInitializationException(
+                    "Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
+            }
+            ChannelCredentials channelCredentials =
+                parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
+//            if (channelCredentials == null) {
+//                throw new XdsInitializationException(
+//                    "Server " + serverUri + ": no supported channel credentials found");
+//            }
+
+            boolean useProtocolV3 = false;
+            boolean ignoreResourceDeletion = false;
+            List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
+            if (serverFeatures != null) {
+                useProtocolV3 = serverFeatures.contains(SERVER_FEATURE_XDS_V3);
+                ignoreResourceDeletion = serverFeatures.contains(SERVER_FEATURE_IGNORE_RESOURCE_DELETION);
+            }
+            servers.add(
+                new ServerInfoImpl(serverUri, channelCredentials, useProtocolV3, ignoreResourceDeletion));
+        }
+        return servers;
+    }
+
+    void setFileReader(FileReader reader) {
+        this.reader = reader;
+    }
+
+    /**
+     * Reads the content of the file with the given path in the file system.
+     */
+    interface FileReader {
+        String readFile(String path) throws IOException;
+    }
+
+    private enum LocalFileReader implements FileReader {
+        INSTANCE;
+
+        @Override
+        public String readFile(String path) throws IOException {
+            return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
+        }
+    }
+
+    private static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
+        if (value == null) {
+            throw new XdsInitializationException(
+                "Invalid bootstrap: '" + fieldName + "' does not exist.");
+        }
+        return value;
+    }
+
+    @Nullable
+    private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList, String serverUri) throws XdsInitializationException {
+        return null;
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/CertificateProviderInfoImpl.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/CertificateProviderInfoImpl.java
new file mode 100644
index 0000000..2c3da74
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/CertificateProviderInfoImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import java.util.Map;
+
+final class CertificateProviderInfoImpl extends Bootstrapper.CertificateProviderInfo {
+
+    private final String pluginName;
+    private final Map<String, ?> config;
+
+    CertificateProviderInfoImpl(String pluginName, Map<String, ?> config) {
+        this.pluginName = pluginName;
+        this.config = config;
+    }
+
+    @Override
+    public String pluginName() {
+        return pluginName;
+    }
+
+    @Override
+    public Map<String, ?> config() {
+        return config;
+    }
+
+    @Override
+    public String toString() {
+        return "CertificateProviderInfo{"
+            + "pluginName=" + pluginName + ", "
+            + "config=" + config
+            + "}";
+    }
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/ServerInfoImpl.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/ServerInfoImpl.java
new file mode 100644
index 0000000..001008a
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/bootstrap/ServerInfoImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import io.grpc.ChannelCredentials;
+
+final class ServerInfoImpl extends Bootstrapper.ServerInfo {
+
+    private final String target;
+
+    private final ChannelCredentials channelCredentials;
+
+    private final boolean useProtocolV3;
+
+    private final boolean ignoreResourceDeletion;
+
+    ServerInfoImpl(String target, ChannelCredentials channelCredentials, boolean useProtocolV3, boolean ignoreResourceDeletion) {
+        this.target = target;
+        this.channelCredentials = channelCredentials;
+        this.useProtocolV3 = useProtocolV3;
+        this.ignoreResourceDeletion = ignoreResourceDeletion;
+    }
+
+    @Override
+    public String target() {
+        return target;
+    }
+
+    @Override
+    ChannelCredentials channelCredentials() {
+        return channelCredentials;
+    }
+
+    @Override
+    boolean useProtocolV3() {
+        return useProtocolV3;
+    }
+
+    @Override
+    boolean ignoreResourceDeletion() {
+        return ignoreResourceDeletion;
+    }
+
+    @Override
+    public String toString() {
+        return "ServerInfo{"
+            + "target=" + target + ", "
+            + "channelCredentials=" + channelCredentials + ", "
+            + "useProtocolV3=" + useProtocolV3 + ", "
+            + "ignoreResourceDeletion=" + ignoreResourceDeletion
+            + "}";
+    }
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/AbstractProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/AbstractProtocol.java
new file mode 100644
index 0000000..9b6687f
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/AbstractProtocol.java
@@ -0,0 +1,242 @@
+/*
+ * 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.xds.util.protocol;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.registry.xds.util.XdsChannel;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+import io.grpc.stub.StreamObserver;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+public abstract class AbstractProtocol<T, S extends DeltaResource<T>> implements XdsProtocol<T> {
+
+    private static final Logger logger = LoggerFactory.getLogger(AbstractProtocol.class);
+
+    protected final XdsChannel xdsChannel;
+
+    protected final Node node;
+
+    /**
+     * Store Request Parameter ( resourceNames )
+     * K - requestId, V - resourceNames
+     */
+    protected final Map<Long, Set<String>> requestParam = new ConcurrentHashMap<>();
+
+    /**
+     * Store ADS Request Observer ( StreamObserver in Streaming Request )
+     * K - requestId, V - StreamObserver
+     */
+    private final Map<Long, StreamObserver<DiscoveryRequest>> requestObserverMap = new ConcurrentHashMap<>();
+
+    /**
+     * Store Delta-ADS Request Observer ( StreamObserver in Streaming Request )
+     * K - requestId, V - StreamObserver
+     */
+    private final Map<Long, ScheduledFuture<?>> observeScheduledMap = new ConcurrentHashMap<>();
+
+    /**
+     * Store CompletableFuture for Request ( used to fetch async result in ResponseObserver )
+     * K - requestId, V - CompletableFuture
+     */
+    private final Map<Long, CompletableFuture<T>> streamResult = new ConcurrentHashMap<>();
+
+    private final ScheduledExecutorService pollingExecutor;
+
+    private final int pollingTimeout;
+
+    protected final static AtomicLong requestId = new AtomicLong(0);
+
+    public AbstractProtocol(XdsChannel xdsChannel, Node node, int pollingPoolSize, int pollingTimeout) {
+        this.xdsChannel = xdsChannel;
+        this.node = node;
+        this.pollingExecutor = new ScheduledThreadPoolExecutor(pollingPoolSize, new NamedThreadFactory("Dubbo-registry-xds"));
+        this.pollingTimeout = pollingTimeout;
+    }
+
+    /**
+     * Abstract method to obtain Type-URL from sub-class
+     *
+     * @return Type-URL of xDS
+     */
+    public abstract String getTypeUrl();
+
+    @Override
+    public T getResource(Set<String> resourceNames) {
+        long request = requestId.getAndIncrement();
+        resourceNames = resourceNames == null ? Collections.emptySet() : resourceNames;
+
+        // Store Request Parameter, which will be used for ACK
+        requestParam.put(request, resourceNames);
+
+        // create observer
+        StreamObserver<DiscoveryRequest> requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(request));
+
+        // use future to get async result
+        CompletableFuture<T> future = new CompletableFuture<>();
+        requestObserverMap.put(request, requestObserver);
+        streamResult.put(request, future);
+
+        // send request to control panel
+        requestObserver.onNext(buildDiscoveryRequest(resourceNames));
+
+        try {
+            // get result
+            return future.get();
+        } catch (InterruptedException | ExecutionException e) {
+            logger.error("Error occur when request control panel.");
+            return null;
+        } finally {
+            // close observer
+            //requestObserver.onCompleted();
+
+            // remove temp
+            streamResult.remove(request);
+            requestObserverMap.remove(request);
+            requestParam.remove(request);
+        }
+    }
+
+    @Override
+    public long observeResource(Set<String> resourceNames, Consumer<T> consumer) {
+        long request = requestId.getAndIncrement();
+        resourceNames = resourceNames == null ? Collections.emptySet() : resourceNames;
+
+        // Store Request Parameter, which will be used for ACK
+        requestParam.put(request, resourceNames);
+
+        // call once for full data
+        consumer.accept(getResource(resourceNames));
+
+        // channel reused
+        StreamObserver<DiscoveryRequest> requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(request));
+        requestObserverMap.put(request, requestObserver);
+
+        ScheduledFuture<?> scheduledFuture = pollingExecutor.scheduleAtFixedRate(() -> {
+            try {
+                // origin request, may changed by updateObserve
+                Set<String> names = requestParam.get(request);
+
+                // use future to get async result
+                CompletableFuture<T> future = new CompletableFuture<>();
+                streamResult.put(request, future);
+
+                // observer reused
+                StreamObserver<DiscoveryRequest> observer = requestObserverMap.get(request);
+
+                // send request to control panel
+                observer.onNext(buildDiscoveryRequest(names));
+
+                try {
+                    // get result
+                    consumer.accept(future.get());
+                } catch (InterruptedException | ExecutionException e) {
+                    logger.error("Error occur when request control panel.");
+                } finally {
+                    // close observer
+                    //requestObserver.onCompleted();
+
+                    // remove temp
+                    streamResult.remove(request);
+                }
+            } catch (Throwable t) {
+                logger.error("Error when requesting observe data. Type: " + getTypeUrl(), t);
+            }
+        }, pollingTimeout, pollingTimeout, TimeUnit.SECONDS);
+
+        observeScheduledMap.put(request, scheduledFuture);
+
+        return request;
+    }
+
+    @Override
+    public void updateObserve(long request, Set<String> resourceNames) {
+        // send difference in resourceNames
+        requestParam.put(request, resourceNames);
+    }
+
+    protected DiscoveryRequest buildDiscoveryRequest(Set<String> resourceNames) {
+        return DiscoveryRequest.newBuilder()
+            .setNode(node)
+            .setTypeUrl(getTypeUrl())
+            .addAllResourceNames(resourceNames)
+            .build();
+    }
+
+    protected DiscoveryRequest buildDiscoveryRequest(Set<String> resourceNames, DiscoveryResponse response) {
+        // for ACK
+        return DiscoveryRequest.newBuilder()
+            .setNode(node)
+            .setTypeUrl(response.getTypeUrl())
+            .setVersionInfo(response.getVersionInfo())
+            .setResponseNonce(response.getNonce())
+            .build();
+    }
+
+    protected abstract T decodeDiscoveryResponse(DiscoveryResponse response);
+
+    private class ResponseObserver implements StreamObserver<DiscoveryResponse> {
+        private final long requestId;
+
+        public ResponseObserver(long requestId) {
+            this.requestId = requestId;
+        }
+
+        @Override
+        public void onNext(DiscoveryResponse value) {
+            logger.info("receive notification from xds server, type: " + getTypeUrl() + " requestId: " + requestId);
+            T result = decodeDiscoveryResponse(value);
+            StreamObserver<DiscoveryRequest> observer = requestObserverMap.get(requestId);
+            if (observer == null) {
+                return;
+            }
+            observer.onNext(buildDiscoveryRequest(Collections.emptySet(), value));
+            CompletableFuture<T> future = streamResult.get(requestId);
+            if (future == null) {
+                return;
+            }
+            future.complete(result);
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            logger.error("xDS Client received error message! detail:", t);
+        }
+
+        @Override
+        public void onCompleted() {
+            // ignore
+        }
+    }
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/DeltaResource.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/DeltaResource.java
new file mode 100644
index 0000000..bcae39c
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/DeltaResource.java
@@ -0,0 +1,32 @@
+/*
+ * 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.xds.util.protocol;
+
+/**
+ * A interface for resources in xDS, which can be updated by ADS delta stream
+ * <br/>
+ * This interface is design to unify the way of fetching data in delta stream
+ * in {@link org.apache.dubbo.registry.xds.util.PilotExchanger}
+ */
+public interface DeltaResource<T> {
+    /**
+     * Get resource from delta stream
+     *
+     * @return the newest resource from stream
+     */
+    T getResource();
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/XdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/XdsProtocol.java
new file mode 100644
index 0000000..847ec04
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/XdsProtocol.java
@@ -0,0 +1,48 @@
+/*
+ * 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.xds.util.protocol;
+
+import java.util.Set;
+import java.util.function.Consumer;
+
+public interface XdsProtocol<T> {
+    /**
+     * Gets all {@link T resource} by the specified resource name.
+     * For LDS, the {@param resourceNames} is ignored
+     *
+     * @param resourceNames specified resource name
+     * @return resources, null if request failed
+     */
+    T getResource(Set<String> resourceNames);
+
+    /**
+     * Add a observer resource with {@link Consumer}
+     *
+     * @param resourceNames specified resource name
+     * @param consumer      resource notifier, will be called when resource updated
+     * @return requestId, used when resourceNames update with {@link XdsProtocol#updateObserve(long, Set)}
+     */
+    long observeResource(Set<String> resourceNames, Consumer<T> consumer);
+
+    /**
+     * Update observed resource list in {@link XdsProtocol#observeResource(Set, Consumer)}
+     *
+     * @param request       requestId returned by {@link XdsProtocol#observeResource(Set, Consumer)}
+     * @param resourceNames new resource name list to observe
+     */
+    void updateObserve(long request, Set<String> resourceNames);
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaEndpoint.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaEndpoint.java
new file mode 100644
index 0000000..9e192cf
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaEndpoint.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.xds.util.protocol.delta;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
+import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
+import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+public class DeltaEndpoint implements DeltaResource<EndpointResult> {
+    private final Map<String, Set<Endpoint>> data = new ConcurrentHashMap<>();
+
+    public void addResource(String resourceName, Set<Endpoint> endpoints) {
+        data.put(resourceName, endpoints);
+    }
+
+    public void removeResource(Collection<String> resourceName) {
+        if (CollectionUtils.isNotEmpty(resourceName)) {
+            resourceName.forEach(data::remove);
+        }
+    }
+
+    @Override
+    public EndpointResult getResource() {
+        Set<Endpoint> set = data.values().stream()
+            .flatMap(Set::stream)
+            .collect(Collectors.toSet());
+        return new EndpointResult(set);
+    }
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaListener.java
new file mode 100644
index 0000000..73b2c9a
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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.xds.util.protocol.delta;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
+import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+public class DeltaListener implements DeltaResource<ListenerResult> {
+    private final Map<String, Set<String>> data = new ConcurrentHashMap<>();
+
+    public void addResource(String resourceName, Set<String> listeners) {
+        data.put(resourceName, listeners);
+    }
+
+    public void removeResource(Collection<String> resourceName) {
+        if (CollectionUtils.isNotEmpty(resourceName)) {
+            resourceName.forEach(data::remove);
+        }
+    }
+
+    @Override
+    public ListenerResult getResource() {
+        Set<String> set = data.values().stream()
+            .flatMap(Set::stream)
+            .collect(Collectors.toSet());
+        return new ListenerResult(set);
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaRoute.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaRoute.java
new file mode 100644
index 0000000..71fdb47
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/delta/DeltaRoute.java
@@ -0,0 +1,47 @@
+/*
+ * 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.xds.util.protocol.delta;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
+import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DeltaRoute implements DeltaResource<RouteResult> {
+    private final Map<String, Map<String, Set<String>>> data = new ConcurrentHashMap<>();
+
+    public void addResource(String resourceName, Map<String, Set<String>> route) {
+        data.put(resourceName, route);
+    }
+
+    public void removeResource(Collection<String> resourceName) {
+        if (CollectionUtils.isNotEmpty(resourceName)) {
+            resourceName.forEach(data::remove);
+        }
+    }
+
+    @Override
+    public RouteResult getResource() {
+        Map<String, Set<String>> result = new ConcurrentHashMap<>();
+        data.values().forEach(result::putAll);
+        return new RouteResult(result);
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/EdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/EdsProtocol.java
new file mode 100644
index 0000000..510370a
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/EdsProtocol.java
@@ -0,0 +1,93 @@
+/*
+ * 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.xds.util.protocol.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.util.XdsChannel;
+import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaEndpoint;
+import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
+import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.envoyproxy.envoy.config.core.v3.HealthStatus;
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.envoyproxy.envoy.config.core.v3.SocketAddress;
+import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
+import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class EdsProtocol extends AbstractProtocol<EndpointResult, DeltaEndpoint> {
+
+    private static final Logger logger = LoggerFactory.getLogger(EdsProtocol.class);
+
+    public EdsProtocol(XdsChannel xdsChannel, Node node, int pollingPoolSize, int pollingTimeout) {
+        super(xdsChannel, node, pollingPoolSize, pollingTimeout);
+    }
+
+    @Override
+    public String getTypeUrl() {
+        return "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
+    }
+
+    @Override
+    protected EndpointResult decodeDiscoveryResponse(DiscoveryResponse response) {
+        if (getTypeUrl().equals(response.getTypeUrl())) {
+            Set<Endpoint> set = response.getResourcesList().stream()
+                .map(EdsProtocol::unpackClusterLoadAssignment)
+                .filter(Objects::nonNull)
+                .flatMap((e) -> decodeResourceToEndpoint(e).stream())
+                .collect(Collectors.toSet());
+            return new EndpointResult(set);
+        }
+        return new EndpointResult();
+    }
+
+    private static Set<Endpoint> decodeResourceToEndpoint(ClusterLoadAssignment resource) {
+        return resource.getEndpointsList().stream()
+            .flatMap((e) -> e.getLbEndpointsList().stream())
+            .map(EdsProtocol::decodeLbEndpointToEndpoint)
+            .collect(Collectors.toSet());
+    }
+
+    private static Endpoint decodeLbEndpointToEndpoint(LbEndpoint lbEndpoint) {
+        Endpoint endpoint = new Endpoint();
+        SocketAddress address = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
+        endpoint.setAddress(address.getAddress());
+        endpoint.setPortValue(address.getPortValue());
+        boolean healthy = HealthStatus.HEALTHY.equals(lbEndpoint.getHealthStatus()) ||
+            HealthStatus.UNKNOWN.equals(lbEndpoint.getHealthStatus());
+        endpoint.setHealthy(healthy);
+        endpoint.setWeight(lbEndpoint.getLoadBalancingWeight().getValue());
+        return endpoint;
+    }
+
+    private static ClusterLoadAssignment unpackClusterLoadAssignment(Any any) {
+        try {
+            return any.unpack(ClusterLoadAssignment.class);
+        } catch (InvalidProtocolBufferException e) {
+            logger.error("Error occur when decode xDS response.", e);
+            return null;
+        }
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/LdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/LdsProtocol.java
new file mode 100644
index 0000000..b9a59a5
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/LdsProtocol.java
@@ -0,0 +1,106 @@
+/*
+ * 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.xds.util.protocol.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.util.XdsChannel;
+import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaListener;
+import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.envoyproxy.envoy.config.listener.v3.Filter;
+import io.envoyproxy.envoy.config.listener.v3.Listener;
+import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
+import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class LdsProtocol extends AbstractProtocol<ListenerResult, DeltaListener> {
+
+    private static final Logger logger = LoggerFactory.getLogger(LdsProtocol.class);
+
+    public LdsProtocol(XdsChannel xdsChannel, Node node, int pollingPoolSize, int pollingTimeout) {
+        super(xdsChannel, node, pollingPoolSize, pollingTimeout);
+    }
+
+    @Override
+    public String getTypeUrl() {
+        return "type.googleapis.com/envoy.config.listener.v3.Listener";
+    }
+
+    public ListenerResult getListeners() {
+        return getResource(null);
+    }
+
+    public void observeListeners(Consumer<ListenerResult> consumer) {
+        observeResource(Collections.emptySet(), consumer);
+    }
+
+    @Override
+    protected ListenerResult decodeDiscoveryResponse(DiscoveryResponse response) {
+        if (getTypeUrl().equals(response.getTypeUrl())) {
+            Set<String> set = response.getResourcesList().stream()
+                .map(LdsProtocol::unpackListener)
+                .filter(Objects::nonNull)
+                .flatMap((e) -> decodeResourceToListener(e).stream())
+                .collect(Collectors.toSet());
+            return new ListenerResult(set);
+        }
+        return new ListenerResult();
+    }
+
+    private Set<String> decodeResourceToListener(Listener resource) {
+        return resource.getFilterChainsList().stream()
+            .flatMap((e) -> e.getFiltersList().stream())
+            .map(Filter::getTypedConfig)
+            .map(LdsProtocol::unpackHttpConnectionManager)
+            .filter(Objects::nonNull)
+            .map(HttpConnectionManager::getRds)
+            .map(Rds::getRouteConfigName)
+            .collect(Collectors.toSet());
+    }
+
+    private static Listener unpackListener(Any any) {
+        try {
+            return any.unpack(Listener.class);
+        } catch (InvalidProtocolBufferException e) {
+            logger.error("Error occur when decode xDS response.", e);
+            return null;
+        }
+    }
+
+    private static HttpConnectionManager unpackHttpConnectionManager(Any any) {
+        try {
+            if (!any.is(HttpConnectionManager.class)) {
+                return null;
+            }
+            return any.unpack(HttpConnectionManager.class);
+        } catch (InvalidProtocolBufferException e) {
+            logger.error("Error occur when decode xDS response.", e);
+            return null;
+        }
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/RdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/RdsProtocol.java
new file mode 100644
index 0000000..ff9ba67
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/impl/RdsProtocol.java
@@ -0,0 +1,94 @@
+/*
+ * 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.xds.util.protocol.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.registry.xds.util.XdsChannel;
+import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
+import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaRoute;
+import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.envoyproxy.envoy.config.core.v3.Node;
+import io.envoyproxy.envoy.config.route.v3.Route;
+import io.envoyproxy.envoy.config.route.v3.RouteAction;
+import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
+import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class RdsProtocol extends AbstractProtocol<RouteResult, DeltaRoute> {
+
+    private static final Logger logger = LoggerFactory.getLogger(RdsProtocol.class);
+
+    public RdsProtocol(XdsChannel xdsChannel, Node node, int pollingPoolSize, int pollingTimeout) {
+        super(xdsChannel, node, pollingPoolSize, pollingTimeout);
+    }
+
+    @Override
+    public String getTypeUrl() {
+        return "type.googleapis.com/envoy.config.route.v3.RouteConfiguration";
+    }
+
+    @Override
+    protected RouteResult decodeDiscoveryResponse(DiscoveryResponse response) {
+        if (getTypeUrl().equals(response.getTypeUrl())) {
+            Map<String, Set<String>> map = response.getResourcesList().stream()
+                .map(RdsProtocol::unpackRouteConfiguration)
+                .filter(Objects::nonNull)
+                .map(RdsProtocol::decodeResourceToListener)
+                .reduce((a, b) -> {
+                    a.putAll(b);
+                    return a;
+                }).orElse(new HashMap<>());
+            return new RouteResult(map);
+        }
+        return new RouteResult();
+    }
+
+    private static Map<String, Set<String>> decodeResourceToListener(RouteConfiguration resource) {
+        Map<String, Set<String>> map = new HashMap<>();
+        resource.getVirtualHostsList()
+            .forEach(virtualHost -> {
+                Set<String> cluster = virtualHost.getRoutesList().stream()
+                    .map(Route::getRoute)
+                    .map(RouteAction::getCluster)
+                    .collect(Collectors.toSet());
+                for (String domain : virtualHost.getDomainsList()) {
+                    map.put(domain, cluster);
+                }
+            });
+        return map;
+    }
+
+    private static RouteConfiguration unpackRouteConfiguration(Any any) {
+        try {
+            return any.unpack(RouteConfiguration.class);
+        } catch (InvalidProtocolBufferException e) {
+            logger.error("Error occur when decode xDS response.", e);
+            return null;
+        }
+    }
+
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/Endpoint.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/Endpoint.java
new file mode 100644
index 0000000..acbe919
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/Endpoint.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.xds.util.protocol.message;
+
+import java.util.Objects;
+
+public class Endpoint {
+    private String address;
+    private int portValue;
+    private boolean healthy;
+    private int weight;
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public int getPortValue() {
+        return portValue;
+    }
+
+    public void setPortValue(int portValue) {
+        this.portValue = portValue;
+    }
+
+    public boolean isHealthy() {
+        return healthy;
+    }
+
+    public void setHealthy(boolean healthy) {
+        this.healthy = healthy;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public void setWeight(int weight) {
+        this.weight = weight;
+    }
+
+    @Override
+    public String toString() {
+        return "Endpoint{" +
+            "address='" + address + '\'' +
+            ", portValue='" + portValue + '\'' +
+            ", healthy=" + healthy +
+            ", weight=" + weight +
+            '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Endpoint endpoint = (Endpoint) o;
+        return healthy == endpoint.healthy &&
+            weight == endpoint.weight &&
+            Objects.equals(address, endpoint.address) &&
+            Objects.equals(portValue, endpoint.portValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(address, portValue, healthy, weight);
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/EndpointResult.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/EndpointResult.java
new file mode 100644
index 0000000..4f89078
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/EndpointResult.java
@@ -0,0 +1,62 @@
+/*
+ * 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.xds.util.protocol.message;
+
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+
+import java.util.Objects;
+import java.util.Set;
+
+public class EndpointResult {
+    private Set<Endpoint> endpoints;
+
+    public EndpointResult() {
+        this.endpoints = new ConcurrentHashSet<>();
+    }
+
+    public EndpointResult(Set<Endpoint> endpoints) {
+        this.endpoints = endpoints;
+    }
+
+    public Set<Endpoint> getEndpoints() {
+        return endpoints;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        EndpointResult that = (EndpointResult) o;
+        return Objects.equals(endpoints, that.endpoints);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(endpoints);
+    }
+
+    @Override
+    public String toString() {
+        return "EndpointResult{" +
+            "endpoints=" + endpoints +
+            '}';
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/ListenerResult.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/ListenerResult.java
new file mode 100644
index 0000000..61d717e
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/ListenerResult.java
@@ -0,0 +1,70 @@
+/*
+ * 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.xds.util.protocol.message;
+
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+
+import java.util.Objects;
+import java.util.Set;
+
+public class ListenerResult {
+    private Set<String> routeConfigNames;
+
+    public ListenerResult() {
+        this.routeConfigNames = new ConcurrentHashSet<>();
+    }
+
+    public ListenerResult(Set<String> routeConfigNames) {
+        this.routeConfigNames = routeConfigNames;
+    }
+
+    public Set<String> getRouteConfigNames() {
+        return routeConfigNames;
+    }
+
+    public void setRouteConfigNames(Set<String> routeConfigNames) {
+        this.routeConfigNames = routeConfigNames;
+    }
+
+    public void mergeRouteConfigNames(Set<String> names) {
+        this.routeConfigNames.addAll(names);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ListenerResult listenerResult = (ListenerResult) o;
+        return Objects.equals(routeConfigNames, listenerResult.routeConfigNames);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(routeConfigNames);
+    }
+
+    @Override
+    public String toString() {
+        return "ListenerResult{" +
+            "routeConfigNames=" + routeConfigNames +
+            '}';
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/RouteResult.java b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/RouteResult.java
new file mode 100644
index 0000000..2355b3d
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/protocol/message/RouteResult.java
@@ -0,0 +1,73 @@
+/*
+ * 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.xds.util.protocol.message;
+
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RouteResult {
+    private final Map<String, Set<String>> domainMap;
+
+    public RouteResult() {
+        this.domainMap = new ConcurrentHashMap<>();
+    }
+
+    public RouteResult(Map<String, Set<String>> domainMap) {
+        this.domainMap = domainMap;
+    }
+
+    public boolean isNotEmpty() {
+        return !domainMap.isEmpty();
+    }
+
+    public Set<String> searchDomain(String domain) {
+        return domainMap.getOrDefault(domain, new ConcurrentHashSet<>());
+    }
+
+    public Set<String> getDomains() {
+        return Collections.unmodifiableSet(domainMap.keySet());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        RouteResult that = (RouteResult) o;
+        return Objects.equals(domainMap, that.domainMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(domainMap);
+    }
+
+    @Override
+    public String toString() {
+        return "RouteResult{" +
+            "domainMap=" + domainMap +
+            '}';
+    }
+}
diff --git a/dubbo-xds/src/main/proto/ca.proto b/dubbo-xds/src/main/proto/ca.proto
new file mode 100644
index 0000000..41e6add
--- /dev/null
+++ b/dubbo-xds/src/main/proto/ca.proto
@@ -0,0 +1,62 @@
+// Copyright Istio Authors
+//
+// Licensed 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.
+
+// The canonical version of this proto can be found at
+// https://github.com/istio/api/blob/9abf4c87205f6ad04311fa021ce60803d8b95f78/security/v1alpha1/ca.proto
+
+syntax = "proto3";
+
+import "google/protobuf/struct.proto";
+
+// Keep this package for backward compatibility.
+package istio.v1.auth;
+
+option go_package = "istio.io/api/security/v1alpha1";
+option java_generic_services = true;
+option java_multiple_files = true;
+
+// Certificate request message. The authentication should be based on:
+// 1. Bearer tokens carried in the side channel;
+// 2. Client-side certificate via Mutual TLS handshake.
+// Note: the service implementation is REQUIRED to verify the authenticated caller is authorize to
+// all SANs in the CSR. The server side may overwrite any requested certificate field based on its
+// policies.
+message IstioCertificateRequest {
+  // PEM-encoded certificate request.
+  // The public key in the CSR is used to generate the certificate,
+  // and other fields in the generated certificate may be overwritten by the CA.
+  string csr = 1;
+  // Optional: requested certificate validity period, in seconds.
+  int64 validity_duration = 3;
+
+  // $hide_from_docs
+  // Optional: Opaque metadata provided by the XDS node to Istio.
+  // Supported metadata: WorkloadName, WorkloadIP, ClusterID
+  google.protobuf.Struct metadata = 4;
+}
+
+// Certificate response message.
+message IstioCertificateResponse {
+  // PEM-encoded certificate chain.
+  // The leaf cert is the first element, and the root cert is the last element.
+  repeated string cert_chain = 1;
+}
+
+// Service for managing certificates issued by the CA.
+service IstioCertificateService {
+  // Using provided CSR, returns a signed certificate.
+  rpc CreateCertificate(IstioCertificateRequest)
+      returns (IstioCertificateResponse) {
+  }
+}
diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
new file mode 100644
index 0000000..2ee954e
--- /dev/null
+++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
@@ -0,0 +1 @@
+xds=org.apache.dubbo.registry.xds.XdsRegistryFactory
diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..daf1bb1
--- /dev/null
+++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+xds=org.apache.dubbo.registry.xds.XdsServiceDiscovery
diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..e6dfce2
--- /dev/null
+++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+xds=org.apache.dubbo.registry.xds.XdsServiceDiscoveryFactory
diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner
new file mode 100644
index 0000000..bbfc0fb
--- /dev/null
+++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner
@@ -0,0 +1 @@
+istio=org.apache.dubbo.registry.xds.istio.IstioCitadelCertificateSigner
diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperTest.java b/dubbo-xds/src/test/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperTest.java
new file mode 100644
index 0000000..180e260
--- /dev/null
+++ b/dubbo-xds/src/test/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.xds.util.bootstrap;
+
+import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.url.component.URLAddress;
+import org.apache.dubbo.registry.xds.XdsInitializationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class BootstrapperTest {
+    @Test
+    public void testParse() throws XdsInitializationException {
+        String rawData = "{\n" +
+            "  \"xds_servers\": [\n" +
+            "    {\n" +
+            "      \"server_uri\": \"unix:///etc/istio/proxy/XDS\",\n" +
+            "      \"channel_creds\": [\n" +
+            "        {\n" +
+            "          \"type\": \"insecure\"\n" +
+            "        }\n" +
+            "      ],\n" +
+            "      \"server_features\": [\n" +
+            "        \"xds_v3\"\n" +
+            "      ]\n" +
+            "    }\n" +
+            "  ],\n" +
+            "  \"node\": {\n" +
+            "    \"id\": \"sidecar~172.17.0.4~dubbo-demo-consumer-deployment-grpc-agent-58585cb9cd-gp79p.dubbo-demo~dubbo-demo.svc.cluster.local\",\n" +
+            "    \"metadata\": {\n" +
+            "      \"ANNOTATIONS\": {\n" +
+            "        \"inject.istio.io/templates\": \"grpc-agent\",\n" +
+            "        \"kubernetes.io/config.seen\": \"2022-07-19T12:53:29.742565722Z\",\n" +
+            "        \"kubernetes.io/config.source\": \"api\",\n" +
+            "        \"prometheus.io/path\": \"/stats/prometheus\",\n" +
+            "        \"prometheus.io/port\": \"15020\",\n" +
+            "        \"prometheus.io/scrape\": \"true\",\n" +
+            "        \"proxy.istio.io/config\": \"{\\\"holdApplicationUntilProxyStarts\\\": true}\",\n" +
+            "        \"proxy.istio.io/overrides\": \"{\\\"containers\\\":[{\\\"name\\\":\\\"app\\\",\\\"image\\\":\\\"gcr.io/istio-testing/app:latest\\\",\\\"args\\\":[\\\"--metrics=15014\\\",\\\"--port\\\",\\\"18080\\\",\\\"--tcp\\\",\\\"19090\\\",\\\"--xds-grpc-server=17070\\\",\\\"--grpc\\\",\\\"17070\\\",\\\"--grpc\\\",\\\"17171\\\",\\\"--port\\\",\\\"3333\\\",\\\"--port\\\",\\\"8080\\\",\\\"--version\\\",\\\"v1\\\",\\\"--crt=/cert.crt\\\",\\\"--key=/cert.key\\\"],\\\"ports\\\":[{\\\"containerPort\\\":17070,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":17171,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":8080,\\\"protocol\\\":\\\"TCP\\\"},{\\\"name\\\":\\\"tcp-health-port\\\",\\\"containerPort\\\":3333,\\\"protocol\\\":\\\"TCP\\\"}],\\\"env\\\":[{\\\"name\\\":\\\"INSTANCE_IP\\\",\\\"valueFrom\\\":{\\\"fieldRef\\\":{\\\"apiVersion\\\":\\\"v1\\\",\\\"fieldPath\\\":\\\"status.podIP\\\"}}}],\\\"resources\\\":{},\\\"volumeMounts\\\":[{\\\"name\\\":\\\"kube-api-access-2tknx\\\",\\\"readOnly\\\":true,\\\"mountPath\\\":\\\"/var/run/secrets/kubernetes.io/serviceaccount\\\"}],\\\"livenessProbe\\\":{\\\"tcpSocket\\\":{\\\"port\\\":\\\"tcp-health-port\\\"},\\\"initialDelaySeconds\\\":10,\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":10,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"readinessProbe\\\":{\\\"httpGet\\\":{\\\"path\\\":\\\"/\\\",\\\"port\\\":8080,\\\"scheme\\\":\\\"HTTP\\\"},\\\"initialDelaySeconds\\\":1,\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":2,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"startupProbe\\\":{\\\"tcpSocket\\\":{\\\"port\\\":\\\"tcp-health-port\\\"},\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":10,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"terminationMessagePath\\\":\\\"/dev/termination-log\\\",\\\"terminationMessagePolicy\\\":\\\"File\\\",\\\"imagePullPolicy\\\":\\\"Always\\\",\\\"securityContext\\\":{\\\"runAsUser\\\":1338,\\\"runAsGroup\\\":1338}},{\\\"name\\\":\\\"dubbo-demo-consumer\\\",\\\"image\\\":\\\"dockeddocking/dubbo:consumer.v1.0\\\",\\\"command\\\":[\\\"sh\\\",\\\"-c\\\",\\\"java $JAVA_OPTS -jar dubbo-demo-consumer.jar \\\"],\\\"resources\\\":{},\\\"volumeMounts\\\":[{\\\"name\\\":\\\"kube-api-access-2tknx\\\",\\\"readOnly\\\":true,\\\"mountPath\\\":\\\"/var/run/secrets/kubernetes.io/serviceaccount\\\"}],\\\"terminationMessagePath\\\":\\\"/dev/termination-log\\\",\\\"terminationMessagePolicy\\\":\\\"File\\\",\\\"imagePullPolicy\\\":\\\"Always\\\"}]}\",\n" +
+            "        \"sidecar.istio.io/rewriteAppHTTPProbers\": \"false\",\n" +
+            "        \"sidecar.istio.io/status\": \"{\\\"initContainers\\\":null,\\\"containers\\\":[\\\"app\\\",\\\"dubbo-demo-consumer\\\",\\\"istio-proxy\\\"],\\\"volumes\\\":[\\\"workload-socket\\\",\\\"workload-certs\\\",\\\"istio-xds\\\",\\\"istio-data\\\",\\\"istio-podinfo\\\",\\\"istio-token\\\",\\\"istiod-ca-cert\\\"],\\\"imagePullSecrets\\\":null,\\\"revision\\\":\\\"default\\\"}\"\n" +
+            "      },\n" +
+            "      \"APP_CONTAINERS\": \"app,dubbo-demo-consumer\",\n" +
+            "      \"CLUSTER_ID\": \"Kubernetes\",\n" +
+            "      \"ENVOY_PROMETHEUS_PORT\": 15090,\n" +
+            "      \"ENVOY_STATUS_PORT\": 15021,\n" +
+            "      \"GENERATOR\": \"grpc\",\n" +
+            "      \"INSTANCE_IPS\": \"172.17.0.4\",\n" +
+            "      \"INTERCEPTION_MODE\": \"REDIRECT\",\n" +
+            "      \"ISTIO_PROXY_SHA\": \"2b6009118109b480e1d5abf3188fd7d9c0c0acf0\",\n" +
+            "      \"ISTIO_VERSION\": \"1.14.1\",\n" +
+            "      \"LABELS\": {\n" +
+            "        \"app\": \"dubbo-demo-consumer-dev\",\n" +
+            "        \"pod-template-hash\": \"58585cb9cd\",\n" +
+            "        \"service.istio.io/canonical-name\": \"dubbo-demo-consumer-dev\",\n" +
+            "        \"service.istio.io/canonical-revision\": \"v1\",\n" +
+            "        \"version\": \"v1\"\n" +
+            "      },\n" +
+            "      \"MESH_ID\": \"cluster.local\",\n" +
+            "      \"NAME\": \"dubbo-demo-consumer-deployment-grpc-agent-58585cb9cd-gp79p\",\n" +
+            "      \"NAMESPACE\": \"dubbo-demo\",\n" +
+            "      \"OWNER\": \"kubernetes://apis/apps/v1/namespaces/dubbo-demo/deployments/dubbo-demo-consumer-deployment-grpc-agent\",\n" +
+            "      \"PILOT_SAN\": [\n" +
+            "        \"istiod.istio-system.svc\"\n" +
+            "      ],\n" +
+            "      \"POD_PORTS\": \"[{\\\"containerPort\\\":17070,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":17171,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":8080,\\\"protocol\\\":\\\"TCP\\\"},{\\\"name\\\":\\\"tcp-health-port\\\",\\\"containerPort\\\":3333,\\\"protocol\\\":\\\"TCP\\\"}]\",\n" +
+            "      \"PROV_CERT\": \"var/run/secrets/istio/root-cert.pem\",\n" +
+            "      \"PROXY_CONFIG\": {\n" +
+            "        \"binaryPath\": \"/usr/local/bin/envoy\",\n" +
+            "        \"concurrency\": 2,\n" +
+            "        \"configPath\": \"./etc/istio/proxy\",\n" +
+            "        \"controlPlaneAuthPolicy\": \"MUTUAL_TLS\",\n" +
+            "        \"discoveryAddress\": \"istiod.istio-system.svc:15012\",\n" +
+            "        \"drainDuration\": \"45s\",\n" +
+            "        \"holdApplicationUntilProxyStarts\": true,\n" +
+            "        \"parentShutdownDuration\": \"60s\",\n" +
+            "        \"proxyAdminPort\": 15000,\n" +
+            "        \"serviceCluster\": \"istio-proxy\",\n" +
+            "        \"statNameLength\": 189,\n" +
+            "        \"statusPort\": 15020,\n" +
+            "        \"terminationDrainDuration\": \"5s\",\n" +
+            "        \"tracing\": {\n" +
+            "          \"zipkin\": {\n" +
+            "            \"address\": \"zipkin.istio-system:9411\"\n" +
+            "          }\n" +
+            "        }\n" +
+            "      },\n" +
+            "      \"SERVICE_ACCOUNT\": \"default\",\n" +
+            "      \"WORKLOAD_NAME\": \"dubbo-demo-consumer-deployment-grpc-agent\"\n" +
+            "    },\n" +
+            "    \"locality\": {},\n" +
+            "    \"UserAgentVersionType\": null\n" +
+            "  },\n" +
+            "  \"certificate_providers\": {\n" +
+            "    \"default\": {\n" +
+            "      \"plugin_name\": \"file_watcher\",\n" +
+            "      \"config\": {\n" +
+            "        \"certificate_file\": \"/var/lib/istio/data/cert-chain.pem\",\n" +
+            "        \"private_key_file\": \"/var/lib/istio/data/key.pem\",\n" +
+            "        \"ca_certificate_file\": \"/var/lib/istio/data/root-cert.pem\",\n" +
+            "        \"refresh_interval\": \"900s\"\n" +
+            "      }\n" +
+            "    }\n" +
+            "  },\n" +
+            "  \"server_listener_resource_name_template\": \"xds.istio.io/grpc/lds/inbound/%s\"\n" +
+            "}";
+        BootstrapperImpl.bootstrapPathFromEnvVar = "";
+        BootstrapperImpl bootstrapper = new BootstrapperImpl();
+        bootstrapper.setFileReader(createFileReader(rawData));
+        Bootstrapper.BootstrapInfo info = bootstrapper.bootstrap();
+        List<Bootstrapper.ServerInfo> serverInfoList = info.servers();
+        Assertions.assertEquals(serverInfoList.get(0).target(), "unix:///etc/istio/proxy/XDS");
+        URLAddress address =URLAddress.parse(serverInfoList.get(0).target(),null, false);
+        Assertions.assertEquals(new DomainSocketAddress(address.getPath()).path(), "etc/istio/proxy/XDS");
+    }
+
+    @Test
+    public void testUrl() {
+        URL url = URL.valueOf("dubbo://127.0.0.1:23456/TestService?useAgent=true");
+        Assertions.assertTrue(url.getParameter("useAgent", false));
+    }
+
+    private static BootstrapperImpl.FileReader createFileReader(final String rawData) {
+        return new BootstrapperImpl.FileReader() {
+            @Override
+            public String readFile(String path) throws IOException {
+                return rawData;
+            }
+        };
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index 6546479..ed0a923 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,7 +129,7 @@
         <checkstyle_unix.skip>true</checkstyle_unix.skip>
         <rat.skip>true</rat.skip>
         <jacoco.skip>true</jacoco.skip>
-        <revision>3.0.13-SNAPSHOT</revision>
+        <revision>3.1.2-SNAPSHOT</revision>
     </properties>
 
     <modules>
@@ -157,6 +157,8 @@
         <module>dubbo-spring-boot</module>
         <module>dubbo-native</module>
         <module>dubbo-test</module>
+        <module>dubbo-kubernetes</module>
+        <module>dubbo-xds</module>
         <module>dubbo-native-plugin</module>
     </modules>
 
@@ -326,10 +328,10 @@
                                         **/org/apache/dubbo/common/serialize/protobuf/support/wrapper/MapValue.java,
                                         **/org/apache/dubbo/common/serialize/protobuf/support/wrapper/ThrowablePB.java,
                                         **/org/apache/dubbo/triple/TripleWrapper.java,
-                                        **/istio/v1/auth/Ca.java,
-                                        **/istio/v1/auth/IstioCertificateServiceGrpc.java,
+                                        **/istio/v1/auth/**/*,
                                         **/com/google/rpc/*,
                                         **/generated/**/*,
+                                        **/generated-sources/**/*,
                                         **/grpc/health/**/*,
                                         **/grpc/reflection/**/*,
                                         **/target/**/*,
@@ -512,6 +514,7 @@
                             <forkMode>once</forkMode>
                             <argLine>${argline} ${jacocoArgLine}
                                 --add-opens java.base/java.lang=ALL-UNNAMED
+                                --add-opens java.base/java.math=ALL-UNNAMED
                                 --add-opens java.base/java.util=ALL-UNNAMED
                             </argLine>
                             <systemProperties>