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/.github/workflows/build-and-test-3.yml b/.github/workflows/build-and-test-3.1.yml
similarity index 84%
rename from .github/workflows/build-and-test-3.yml
rename to .github/workflows/build-and-test-3.1.yml
index 3a9b654..b2e83ad 100644
--- a/.github/workflows/build-and-test-3.yml
+++ b/.github/workflows/build-and-test-3.1.yml
@@ -1,4 +1,4 @@
-name: Build and Test For Dubbo 3
+name: Build and Test For Dubbo 3.1
 
 on: [push, pull_request, workflow_dispatch]
 
@@ -151,6 +151,43 @@
       - 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-18.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
     env:
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/directory/AbstractDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
index c190fa5..fd2c467 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;
@@ -71,7 +71,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 +193,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("2-2", "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-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/config/Configuration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/Configuration.java
index f7f5bc8..8e7dc07 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,6 +16,9 @@
  */
 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;
@@ -24,6 +27,9 @@
  * 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 +72,11 @@
         try {
             return convert(Integer.class, key, defaultValue);
         } catch (NumberFormatException e) {
+            // 0-2 Property type mismatch.
+            interfaceLevelLogger.error("0-2", "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/configcenter/AbstractDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
index 0cc6af2..b5feab0 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,7 +75,7 @@
     /**
      * Logger
      */
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
 
     /**
      * The thread pool for workers who executes the tasks
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..a238e7d 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
@@ -546,4 +546,26 @@
 
     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";
+
 }
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/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/threadpool/manager/DefaultExecutorRepository.java b/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/manager/DefaultExecutorRepository.java
index 0942d88..26fef8d 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));
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..bd15785 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;
@@ -47,7 +48,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 +83,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("0-1", "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/utils/UrlUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java
index f0246c0..11b1e3f 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 the Zookeeper registry, I found that the category of the consumer URL is 'consumers'.
+        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..09bc1a4 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
@@ -1023,7 +1023,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 5e9b8b0..42a785b 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
@@ -240,9 +240,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/annotation/DubboReference.java b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
index 6b5c215..e33797e 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
      */
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/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/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-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..43b715e
--- /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.rpc.protocol.tri.reactive.handler.ManyToManyMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.reactive.handler.ManyToOneMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.reactive.handler.OneToManyMethodHandler;
+import org.apache.dubbo.rpc.protocol.tri.reactive.calls.ReactorClientCalls;
+import org.apache.dubbo.rpc.protocol.tri.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/ReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
index 0c57145..8dd76c0 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 java.util.concurrent.Callable;
 
@@ -67,14 +68,20 @@
 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.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;
@@ -92,7 +99,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.
@@ -248,7 +255,7 @@
     }
 
     protected synchronized void init() {
-        if (initialized && ref !=null ) {
+        if (initialized && ref != null) {
             return;
         }
         try {
@@ -276,7 +283,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);
@@ -314,6 +321,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("2-2", "server crashed", "", "No provider available.", t);
+            }
+
             throw t;
         }
         initialized = true;
@@ -384,7 +399,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);
@@ -411,6 +426,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);
@@ -425,12 +443,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());
@@ -440,6 +458,68 @@
     }
 
     /**
+     * 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("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"))) {
+            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
@@ -503,9 +583,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.");
         }
     }
 
@@ -558,7 +638,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 + "/")
@@ -568,6 +650,10 @@
                     + invoker.getUrl()
                     + " to the consumer "
                     + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
+
+            logger.error("2-2", "provider not started", "", "No provider available.", illegalStateException);
+
+            throw illegalStateException;
         }
     }
 
@@ -585,7 +671,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) {
@@ -594,9 +680,9 @@
         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], " +
-                        "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()));
+                                "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()));
             }
             interfaceClass = GenericService.class;
         } else {
@@ -605,7 +691,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);
@@ -662,7 +748,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/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
index 778b7c3..dac760b 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
@@ -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;
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/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/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/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/resources/META-INF/compat/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd
index b22bd91..ed55e23 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>
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..f2a38c4 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>
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..4141823 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;
 
@@ -61,7 +61,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 +106,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("5-1", "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..e30c3be 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
@@ -60,7 +60,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("5-1", "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 fd7d0ee..fb3b390 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.7</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>
@@ -175,7 +176,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.12-SNAPSHOT</revision>
+        <revision>3.1.1-SNAPSHOT</revision>
     </properties>
 
     <dependencyManagement>
@@ -229,6 +230,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>
diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
index bda20ce..e84de5f 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.12-SNAPSHOT</revision>
+        <revision>3.1.1-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 b023a54..cad323e 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.12-SNAPSHOT</revision>
+        <revision>3.1.1-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..81f17eb 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>
@@ -335,6 +342,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>
@@ -429,6 +440,7 @@
                                     <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>
diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml
index a477c4dc..d38fb8f 100644
--- a/dubbo-distribution/dubbo-bom/pom.xml
+++ b/dubbo-distribution/dubbo-bom/pom.xml
@@ -198,6 +198,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>
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..1a0c1fa
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesMeshEnvListener.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.kubernetes;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
+import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
+
+import com.google.gson.Gson;
+import io.fabric8.kubernetes.api.model.ListOptionsBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.Watch;
+import io.fabric8.kubernetes.client.Watcher;
+import io.fabric8.kubernetes.client.WatcherException;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class KubernetesMeshEnvListener implements MeshEnvListener {
+    public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListener.class);
+    private volatile static boolean usingApiServer = false;
+    private volatile static KubernetesClient kubernetesClient;
+    private volatile static String namespace;
+
+    private final Map<String, MeshAppRuleListener> appRuleListenerMap = new ConcurrentHashMap<>();
+
+    private final Map<String, Watch> vsAppWatch = new ConcurrentHashMap<>();
+    private final Map<String, Watch> drAppWatch = new ConcurrentHashMap<>();
+
+    private final Map<String, String> vsAppCache = new ConcurrentHashMap<>();
+    private final Map<String, String> drAppCache = new ConcurrentHashMap<>();
+
+    public static void injectKubernetesEnv(KubernetesClient client, String configuredNamespace) {
+        usingApiServer = true;
+        kubernetesClient = client;
+        namespace = configuredNamespace;
+    }
+
+    @Override
+    public boolean isEnable() {
+        return usingApiServer;
+    }
+
+    @Override
+    public void onSubscribe(String appName, MeshAppRuleListener listener) {
+        appRuleListenerMap.put(appName, listener);
+        logger.info("Subscribe Mesh Rule in Kubernetes. AppName: " + appName);
+
+        // subscribe VisualService
+        subscribeVs(appName);
+
+        // subscribe DestinationRule
+        subscribeDr(appName);
+
+        // notify for start
+        notifyOnce(appName);
+    }
+
+    private void subscribeVs(String appName) {
+        if (vsAppWatch.containsKey(appName)) {
+            return;
+        }
+
+        try {
+            Watch watch = kubernetesClient
+                .customResource(
+                    MeshConstant.getVsDefinition())
+                .watch(namespace, appName, null, new ListOptionsBuilder().build(), new Watcher<String>() {
+                    @Override
+                    public void eventReceived(Action action, String resource) {
+                        logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
+
+                        if (action == Action.ADDED || action == Action.MODIFIED) {
+                            Map drRuleMap = new Gson().fromJson(resource, Map.class);
+                            String vsRule = new Yaml(new SafeConstructor()).dump(drRuleMap);
+                            vsAppCache.put(appName, vsRule);
+                            if (drAppCache.containsKey(appName)) {
+                                notifyListener(vsRule, appName, drAppCache.get(appName));
+                            }
+                        } else {
+                            appRuleListenerMap.get(appName).receiveConfigInfo("");
+                        }
+                    }
+
+                    @Override
+                    public void onClose(WatcherException cause) {
+                        // ignore
+                    }
+                });
+            vsAppWatch.put(appName, watch);
+            try {
+                Map<String, Object> vsRule = kubernetesClient
+                    .customResource(
+                        MeshConstant.getVsDefinition())
+                    .get(namespace, appName);
+                vsAppCache.put(appName, new Yaml(new SafeConstructor()).dump(vsRule));
+            } catch (Throwable ignore) {
+
+            }
+        } catch (IOException e) {
+            logger.error("Error occurred when listen kubernetes crd.", e);
+        }
+    }
+
+    private void notifyListener(String vsRule, String appName, String drRule) {
+        String rule = vsRule + "\n---\n" + drRule;
+        logger.info("Notify App Rule Listener. AppName: " + appName + " Rule:" + rule);
+
+        appRuleListenerMap.get(appName).receiveConfigInfo(rule);
+    }
+
+    private void subscribeDr(String appName) {
+        if (drAppWatch.containsKey(appName)) {
+            return;
+        }
+
+        try {
+            Watch watch = kubernetesClient
+                .customResource(
+                    MeshConstant.getDrDefinition())
+                .watch(namespace, appName, null, new ListOptionsBuilder().build(), new Watcher<String>() {
+                    @Override
+                    public void eventReceived(Action action, String resource) {
+                        logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action + " Resource:" + resource);
+
+                        if (action == Action.ADDED || action == Action.MODIFIED) {
+                            Map drRuleMap = new Gson().fromJson(resource, Map.class);
+                            String drRule = new Yaml(new SafeConstructor()).dump(drRuleMap);
+
+                            drAppCache.put(appName, drRule);
+                            if (vsAppCache.containsKey(appName)) {
+                                notifyListener(vsAppCache.get(appName), appName, drRule);
+                            }
+                        } else {
+                            appRuleListenerMap.get(appName).receiveConfigInfo("");
+                        }
+                    }
+
+                    @Override
+                    public void onClose(WatcherException cause) {
+                        // ignore
+                    }
+                });
+            drAppWatch.put(appName, watch);
+            try {
+                Map<String, Object> drRule = kubernetesClient
+                    .customResource(
+                        MeshConstant.getDrDefinition())
+                    .get(namespace, appName);
+                drAppCache.put(appName, new Yaml(new SafeConstructor()).dump(drRule));
+            } catch (Throwable ignore) {
+
+            }
+        } catch (IOException e) {
+            logger.error("Error occurred when listen kubernetes crd.", e);
+        }
+    }
+
+    private void notifyOnce(String appName) {
+        if (vsAppCache.containsKey(appName) && drAppCache.containsKey(appName)) {
+            notifyListener(vsAppCache.get(appName), appName, drAppCache.get(appName));
+        }
+    }
+
+    @Override
+    public void onUnSubscribe(String appName) {
+        appRuleListenerMap.remove(appName);
+
+        if (vsAppWatch.containsKey(appName)) {
+            vsAppWatch.remove(appName).close();
+        }
+        vsAppCache.remove(appName);
+
+        if (drAppWatch.containsKey(appName)) {
+            drAppWatch.remove(appName).close();
+        }
+        drAppCache.remove(appName);
+    }
+}
diff --git a/dubbo-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..e0f47f1
--- /dev/null
+++ b/dubbo-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
@@ -0,0 +1,404 @@
+/*
+ * 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.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.Watch;
+import io.fabric8.kubernetes.client.Watcher;
+import io.fabric8.kubernetes.client.WatcherException;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+public class KubernetesServiceDiscovery extends AbstractServiceDiscovery {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private KubernetesClient kubernetesClient;
+
+    private String currentHostname;
+
+    private 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, Watch> SERVICE_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, Watch> PODS_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, Watch> ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
+
+    public KubernetesServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
+        super(applicationModel, registryURL);
+        Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
+        this.kubernetesClient = new DefaultKubernetesClient(config);
+        this.currentHostname = System.getenv("HOSTNAME");
+        this.registryURL = registryURL;
+        this.namespace = config.getNamespace();
+        this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
+
+        boolean availableAccess;
+        try {
+            availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
+        } catch (Throwable e) {
+            availableAccess = false;
+        }
+        if (!availableAccess) {
+            String message = "Unable to access api server. " +
+                "Please check your url config." +
+                " Master URL: " + config.getMasterUrl() +
+                " Hostname: " + currentHostname;
+            logger.error(message);
+        } else {
+            KubernetesMeshEnvListener.injectKubernetesEnv(kubernetesClient, namespace);
+        }
+    }
+
+    @Override
+    public void doDestroy() throws Exception {
+        SERVICE_WATCHER.forEach((k, v) -> v.close());
+        SERVICE_WATCHER.clear();
+
+        PODS_WATCHER.forEach((k, v) -> v.close());
+        PODS_WATCHER.clear();
+
+        ENDPOINTS_WATCHER.forEach((k, v) -> v.close());
+        ENDPOINTS_WATCHER.clear();
+
+        kubernetesClient.close();
+    }
+
+    @Override
+    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
+        if (enableRegister) {
+            kubernetesClient
+                .pods()
+                .inNamespace(namespace)
+                .withName(currentHostname)
+                .edit(pod ->
+                    new PodBuilder(pod)
+                        .editOrNewMetadata()
+                        .addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
+                        .endMetadata()
+                        .build());
+            if (logger.isInfoEnabled()) {
+                logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
+                    "Current pod name: " + currentHostname);
+            }
+        }
+    }
+
+    /**
+     * 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 =
+            kubernetesClient
+                .endpoints()
+                .inNamespace(namespace)
+                .withName(serviceName)
+                .get();
+
+        return toServiceInstance(endpoints, serviceName);
+    }
+
+    @Override
+    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
+        listener.getServiceNames().forEach(serviceName -> {
+            SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
+
+            // Watch Service Endpoint Modification
+            watchEndpoints(listener, serviceName);
+
+            // Watch Pods Modification, happens when ServiceInstance updated
+            watchPods(listener, serviceName);
+
+            // Watch Service Modification, happens when Service Selector updated, used to update pods watcher
+            watchService(listener, serviceName);
+        });
+    }
+
+    private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
+        Watch watch = kubernetesClient
+            .endpoints()
+            .inNamespace(namespace)
+            .withName(serviceName)
+            .watch(new Watcher<Endpoints>() {
+                @Override
+                public void eventReceived(Action action, Endpoints resource) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Received Endpoint Event. Event type: " + action.name() +
+                            ". Current pod name: " + currentHostname);
+                    }
+
+                    notifyServiceChanged(serviceName, listener);
+                }
+
+                @Override
+                public void onClose(WatcherException cause) {
+                    // ignore
+                }
+            });
+
+        ENDPOINTS_WATCHER.put(serviceName, watch);
+    }
+
+    private void watchPods(ServiceInstancesChangedListener listener, String serviceName) {
+        Map<String, String> serviceSelector = getServiceSelector(serviceName);
+        if (serviceSelector == null) {
+            return;
+        }
+
+        Watch watch = kubernetesClient
+            .pods()
+            .inNamespace(namespace)
+            .withLabels(serviceSelector)
+            .watch(new Watcher<Pod>() {
+                @Override
+                public void eventReceived(Action action, Pod resource) {
+                    if (Action.MODIFIED.equals(action)) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Pods Update Event. Current pod name: " + currentHostname);
+                        }
+
+                        notifyServiceChanged(serviceName, listener);
+                    }
+                }
+
+                @Override
+                public void onClose(WatcherException cause) {
+                    // ignore
+                }
+            });
+
+        PODS_WATCHER.put(serviceName, watch);
+    }
+
+    private void watchService(ServiceInstancesChangedListener listener, String serviceName) {
+        Watch watch = kubernetesClient
+            .services()
+            .inNamespace(namespace)
+            .withName(serviceName)
+            .watch(new Watcher<Service>() {
+                @Override
+                public void eventReceived(Action action, Service resource) {
+                    if (Action.MODIFIED.equals(action)) {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Received Service Update Event. Update Pods Watcher. " +
+                                "Current pod name: " + currentHostname);
+                        }
+
+                        if (PODS_WATCHER.containsKey(serviceName)) {
+                            PODS_WATCHER.get(serviceName).close();
+                            PODS_WATCHER.remove(serviceName);
+                        }
+                        watchPods(listener, serviceName);
+                    }
+                }
+
+                @Override
+                public void onClose(WatcherException cause) {
+                    // ignore
+                }
+            });
+
+        SERVICE_WATCHER.put(serviceName, watch);
+    }
+
+    private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener) {
+        long receivedTime = System.nanoTime();
+
+        ServiceInstancesChangedEvent event;
+
+        event = new ServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
+
+        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..6b1a1b0
--- /dev/null
+++ b/dubbo-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.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;
+//
+//@ExtendWith({MockitoExtension.class})
+//public class KubernetesServiceDiscoveryTest {
+//    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;
+//
+//    @BeforeEach
+//    public void setUp() {
+//        mockServer.before();
+//        mockClient = mockServer.getClient();
+//
+//        serverUrl = URL.valueOf(mockClient.getConfiguration().getMasterUrl())
+//            .setProtocol("kubernetes")
+//            .addParameter(KubernetesClientConst.USE_HTTPS, "false")
+//            .addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
+//        serverUrl.setScopeModel(ApplicationModel.defaultModel());
+//
+//        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("TestServer").withLabels(selector).endMetadata()
+//            .build();
+//
+//        Service service = new ServiceBuilder()
+//            .withNewMetadata().withName("TestService").endMetadata()
+//            .withNewSpec().withSelector(selector).endSpec().build();
+//
+//        Endpoints endPoints = new EndpointsBuilder()
+//            .withNewMetadata().withName("TestService").endMetadata()
+//            .addNewSubset()
+//            .addNewAddress().withIp("ip1")
+//            .withNewTargetRef().withUid("uid1").withName("TestServer").endTargetRef().endAddress()
+//            .addNewPort("Test", "Test", 12345, "TCP").endSubset()
+//            .build();
+//
+//        mockClient.pods().create(pod);
+//        mockClient.services().create(service);
+//        mockClient.endpoints().create(endPoints);
+//    }
+//
+//    @AfterEach
+//    public void destroy() {
+//        mockServer.after();
+//    }
+//
+//    @Test
+//    public void testEndpointsUpdate() throws Exception {
+//
+//        KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
+//        serviceDiscovery.initialize(serverUrl);
+//
+//        serviceDiscovery.setCurrentHostname("TestServer");
+//        serviceDiscovery.setKubernetesClient(mockClient);
+//
+//        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+//        serviceDiscovery.register(serviceInstance);
+//
+//        HashSet<String> serviceList = new HashSet<>(4);
+//        serviceList.add("TestService");
+//        Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
+//        Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
+//
+//        serviceDiscovery.addServiceInstancesChangedListener(mockListener);
+//        mockClient.endpoints().withName("TestService")
+//            .edit(endpoints ->
+//                new EndpointsBuilder(endpoints)
+//                    .editFirstSubset()
+//                    .addNewAddress()
+//                    .withIp("ip2")
+//                    .withNewTargetRef().withUid("uid2").withName("TestServer").endTargetRef()
+//                    .endAddress().endSubset()
+//                    .build());
+//
+//        Thread.sleep(5000);
+//        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+//            ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+//        Mockito.verify(mockListener, Mockito.times(2)).onEvent(eventArgumentCaptor.capture());
+//        Assertions.assertEquals(2, eventArgumentCaptor.getValue().getServiceInstances().size());
+//
+//        serviceDiscovery.unregister(serviceInstance);
+//
+//        serviceDiscovery.destroy();
+//    }
+//
+//    @Test
+//    public void testPodsUpdate() throws Exception {
+//
+//        KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
+//        serviceDiscovery.initialize(serverUrl);
+//
+//        serviceDiscovery.setCurrentHostname("TestServer");
+//        serviceDiscovery.setKubernetesClient(mockClient);
+//
+//        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+//        serviceDiscovery.register(serviceInstance);
+//
+//        HashSet<String> serviceList = new HashSet<>(4);
+//        serviceList.add("TestService");
+//        Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
+//        Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
+//
+//        serviceDiscovery.addServiceInstancesChangedListener(mockListener);
+//
+//        serviceInstance = new DefaultServiceInstance("TestService", "Test12345", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+//        serviceDiscovery.update(serviceInstance);
+//
+//        Thread.sleep(5000);
+//        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+//            ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+//        Mockito.verify(mockListener, Mockito.times(1)).onEvent(eventArgumentCaptor.capture());
+//        Assertions.assertEquals(1, eventArgumentCaptor.getValue().getServiceInstances().size());
+//
+//        serviceDiscovery.unregister(serviceInstance);
+//
+//        serviceDiscovery.destroy();
+//    }
+//
+//    @Test
+//    public void testGetInstance() throws Exception {
+//        KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
+//        serviceDiscovery.initialize(serverUrl);
+//
+//        serviceDiscovery.setCurrentHostname("TestServer");
+//        serviceDiscovery.setKubernetesClient(mockClient);
+//
+//        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345, ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
+//        serviceDiscovery.register(serviceInstance);
+//
+//        serviceDiscovery.update(serviceInstance);
+//
+//        Assertions.assertEquals(1, serviceDiscovery.getServices().size());
+//        Assertions.assertEquals(1, serviceDiscovery.getInstances("TestService").size());
+//
+//        Assertions.assertEquals(serviceInstance, serviceDiscovery.getLocalInstance());
+//
+//        serviceDiscovery.unregister(serviceInstance);
+//
+//        serviceDiscovery.destroy();
+//    }
+//}
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/MetadataInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
index 645eb9e..51b8d2e 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();
@@ -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-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-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/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..5f4651d 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, " +
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-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..c230075 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,7 +18,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.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
@@ -26,7 +26,7 @@
 import java.util.List;
 
 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;
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..a3a73e2 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;
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..432a777 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
@@ -245,6 +245,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) {
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..8112a6b
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ReflectionBasedServiceDiscovery.java
@@ -0,0 +1,288 @@
+/*
+ * 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;
+
+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("1-7", "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/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..30daeb7 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,35 @@
 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.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 +69,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 +107,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 +240,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,11 +282,12 @@
      * @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())) {
@@ -291,33 +308,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 +377,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 +413,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 +436,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 +446,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 +522,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..cc311fa 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,8 +54,6 @@
 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.RegistryConstants.EMPTY_PROTOCOL;
 import static org.apache.dubbo.common.constants.RegistryConstants.ENABLE_EMPTY_PROTECTION_KEY;
@@ -67,7 +67,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 +77,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 +94,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 +126,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 +148,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 +181,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("1-17", "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 +244,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 +306,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 +346,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 +382,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 +409,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 +427,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 +472,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 +489,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 +514,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..9e5a207 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;
@@ -35,7 +35,7 @@
  * 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 +71,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("4-2", "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..509a85c 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;
@@ -58,7 +58,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 +111,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 +124,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("1-18", "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("1-18", "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..bd57dc5 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;
@@ -45,7 +45,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 +107,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("1-14", "", "",
+                "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..922bfba 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;
@@ -59,7 +59,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 +204,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("2-1", "", "",
+                "Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
+
             return BitList.emptyList();
         }
     }
@@ -295,15 +298,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("1-8", "", "",
+                "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("1-8", "", "",
+                "unexpected error when unsubscribe service " + serviceKey + " from registry: " + registry.getUrl(), t);
         }
 
         ExtensionLoader<AddressListener> addressListenerExtensionLoader = getUrl().getOrDefaultModuleModel().getExtensionLoader(AddressListener.class);
@@ -318,7 +326,9 @@
             try {
                 destroyAllInvokers();
             } catch (Throwable t) {
-                logger.warn("Failed to destroy service " + serviceKey, t);
+                // 1-15 - Failed to destroy service.
+                logger.warn("1-15", "", "",
+                    "Failed to destroy service " + serviceKey, t);
             }
             routerChain.destroy();
             invokersChangedListener = null;
@@ -333,7 +343,9 @@
         try {
             destroyAllInvokers();
         } catch (Throwable t) {
-            logger.warn("Failed to destroy service " + serviceKey, t);
+            // 1-15 - Failed to destroy service.
+            logger.warn("1-15", "", "",
+                "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..e3573de 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;
@@ -81,7 +82,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 +225,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("1-4", "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 +252,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 +261,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(
+                    "3-1", "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 +347,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.
@@ -361,19 +375,28 @@
                     continue;
                 }
             }
+
             if (EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
                 continue;
             }
+
             if (!getUrl().getOrDefaultFrameworkModel().getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
-                logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() +
+
+                // 4-1 - Unsupported protocol
+
+                logger.error("4-1", "typo in URL", "", "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()));
+
                 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 +410,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("4-2", "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("4-3", "", "",
+                            "Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
+                    }
                 }
                 if (invoker != null) { // Put new invoker in cache
                     newUrlInvokerMap.put(url, invoker);
@@ -494,7 +528,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("1-15", "", "",
+                        "Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t);
                 }
             }
             localUrlInvokerMap.clear();
@@ -547,8 +583,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("1-16", "", "",
+            "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..19cc45f 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;
@@ -143,7 +143,8 @@
         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
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..bcfe7d4 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;
@@ -38,7 +38,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 +113,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(
+                "1-13", "registry center offline", "Check the registry server.",
+                "Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
+
             return;
         }
         if (logger.isInfoEnabled()) {
@@ -123,7 +127,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("1-13", "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..c65e519 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;
@@ -70,7 +70,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 +87,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 +107,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 +118,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("1-9", "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 +214,22 @@
             if (!lockfile.exists()) {
                 lockfile.createNewFile();
             }
+
             try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
                  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("1-9", CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                        "Adjust dubbo.registry.file.", ioException);
+
+                    throw ioException;
                 }
+
                 // Save
                 try {
                     if (!file.exists()) {
@@ -228,29 +260,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("1-9", 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("1-9", 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("1-10", "", "",
+                        String.format("Failed to delete lock file [%s]", lockfile.getName()));
                 }
             }
         }
@@ -266,9 +308,15 @@
                 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("1-9", 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("1-9", CAUSE_MULTI_DUBBO_USING_SAME_FILE, "",
+                "Failed to load registry cache file " + file, e);
         }
     }
 
@@ -422,7 +470,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("1-7", "consumer is offline", "",
+                            "Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
                     }
                 }
             }
@@ -430,7 +480,7 @@
     }
 
     /**
-     * Notify changes from the Provider side.
+     * Notify changes from the provider side.
      *
      * @param url      consumer side url
      * @param listener listener
@@ -444,7 +494,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("1-4", "", "", "Ignore empty notify urls for subscribe url " + url);
             return;
         }
         if (logger.isInfoEnabled()) {
@@ -468,6 +519,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 +573,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("1-8", "", "",
+                            "Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t);
                     }
                 }
             }
@@ -537,7 +591,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("1-8", "", "",
+                            "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..2429822 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;
@@ -39,7 +40,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 +70,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 +84,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 +103,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("1-11", "", "",
+                    "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..b1cccf2 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;
@@ -62,24 +62,34 @@
 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 +106,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("0-2", "typo in property value", "This property requires an integer value.",
+                    "Invalid registry properties configuration key " + key + ", value " + str);
             }
         }
         return result;
@@ -110,7 +123,7 @@
     protected void evictURLCache(URL url) {
         Map<String, ServiceAddressURL> oldURLs = stringUrls.remove(url);
         try {
-            if (oldURLs != null && oldURLs.size() > 0) {
+            if (oldURLs != null && !oldURLs.isEmpty()) {
                 logger.info("Evicting urls for service " + url.getServiceKey() + ", size " + oldURLs.size());
                 Long currentTimestamp = System.currentTimeMillis();
                 for (Map.Entry<String, ServiceAddressURL> entry : oldURLs.entrySet()) {
@@ -123,22 +136,39 @@
                 }
             }
         } 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("1-3", "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));
         URL copyOfConsumer = removeParamsFromConsumer(consumer);
+
         if (oldURLs == null) {
             for (String rawProvider : providers) {
                 rawProvider = stripOffVariableKeys(rawProvider);
                 ServiceAddressURL cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
                 if (cachedURL == null) {
-                    logger.warn("Invalid address, failed to parse into URL " + rawProvider);
+                    // 1-1: Address invalid.
+                    logger.warn("1-1", "", "",
+                        "Invalid address, failed to parse into URL " + rawProvider);
+
                     continue;
                 }
                 newURLs.put(rawProvider, cachedURL);
@@ -151,7 +181,9 @@
                 if (cachedURL == null) {
                     cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
                     if (cachedURL == null) {
-                        logger.warn("Invalid address, failed to parse into URL " + rawProvider);
+                        logger.warn("1-1", "", "",
+                            "Invalid address, failed to parse into URL " + rawProvider);
+
                         continue;
                     }
                 }
@@ -168,6 +200,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 +219,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("1-4", "", "",
+                        "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)
@@ -201,20 +235,28 @@
 
     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.
             encoded = false;
         }
+
         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("1-5", "", "",
+                "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;
+
         URLAddress address = stringAddress.computeIfAbsent(rawAddress, k -> URLAddress.parse(k, getDefaultURLProtocol(), isEncoded));
         address.setTimestamp(System.currentTimeMillis());
 
@@ -222,9 +264,11 @@
         param.setTimestamp(System.currentTimeMillis());
 
         ServiceAddressURL cachedURL = createServiceURL(address, param, consumerURL);
+
         if (isMatch(consumerURL, cachedURL)) {
             return cachedURL;
         }
+
         return null;
     }
 
@@ -314,7 +358,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 +387,11 @@
                     }
                 }
             } catch (Throwable t) {
-                logger.error("Error occurred when clearing cached URLs", t);
+                // 1-6 Error when clearing cached URLs.
+
+                logger.error("1-6", "", "",
+                    "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..8ff00d8 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;
@@ -40,7 +40,7 @@
  * 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 +124,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("1-12", "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-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 021102c..e96fce2 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..1fa01d4 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,11 +59,20 @@
 
     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
@@ -69,10 +84,7 @@
     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 +92,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 +107,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 +120,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..e4af8f8 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;
@@ -56,7 +57,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 +71,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) {
@@ -150,6 +156,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 +167,9 @@
                         }
                     }
                 });
+
                 zkClient.create(root, false);
+
                 List<String> services = zkClient.addChildListener(root, zkListener);
                 if (CollectionUtils.isNotEmpty(services)) {
                     for (String service : services) {
@@ -172,20 +181,25 @@
                 }
             } else {
                 CountDownLatch latch = new CountDownLatch(1);
+
                 try {
                     List<URL> urls = new ArrayList<>();
+
                     for (String path : toCategoriesPath(url)) {
                         ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                         ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));
+
                         if (zkListener instanceof RegistryChildListenerImpl) {
                             ((RegistryChildListenerImpl) zkListener).setLatch(latch);
                         }
+
                         zkClient.create(path, false);
                         List<String> children = zkClient.addChildListener(path, zkListener);
                         if (children != null) {
                             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.
@@ -282,12 +296,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());
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..9f10fa2 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,7 +17,7 @@
 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.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstance;
@@ -169,7 +169,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..ac18f2a 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";
 
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..675e6f5 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,13 @@
  */
 package org.apache.dubbo.remoting.api;
 
-import io.netty.handler.codec.http2.Http2FrameLogger;
+public abstract class AbstractWireProtocol implements WireProtocol {
 
-import static io.netty.handler.logging.LogLevel.DEBUG;
+    private final ProtocolDetector detector;
 
-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 AbstractWireProtocol(ProtocolDetector detector) {
+        this.detector = detector;
+    }
 
     @Override
     public ProtocolDetector detector() {
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..a38648b
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.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.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;
+
+public abstract class AbstractPortUnificationServer extends AbstractServer {
+    private final List<WireProtocol> protocols;
+
+    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;
+    }
+
+}
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..f817855 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,25 @@
 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 void 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;
         });
     }
 
     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 +60,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/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/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..3573a65 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;
@@ -44,7 +44,7 @@
  */
 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 +121,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("6-1", "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("6-2", "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..4493bb3 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
@@ -273,4 +273,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..1f0c3f7 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,10 @@
  */
 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.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.resource.GlobalResourceInitializer;
 import org.apache.dubbo.common.utils.NetUtils;
@@ -42,6 +41,7 @@
 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;
 
@@ -62,7 +62,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
@@ -186,13 +186,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 "
+                        + getRemoteAddress() + ", error message is:" + cause.getMessage(), cause);
+
+                logger.error("6-1", "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 "
+
+                // 6-2 Client-side timeout
+
+                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());
+
+                logger.error("6-2", "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 80%
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..e732f96 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,20 +14,24 @@
  * 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.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 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;
@@ -40,7 +44,7 @@
 import io.netty.util.concurrent.GlobalEventExecutor;
 
 import java.net.InetSocketAddress;
-import java.util.List;
+import java.util.Collection;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
@@ -53,11 +57,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 static final Logger logger = LoggerFactory.getLogger(NettyPortUnificationServer.class);
 
     private final DefaultChannelGroup channels = new DefaultChannelGroup(
         GlobalEventExecutor.INSTANCE);
@@ -70,41 +72,35 @@
     /**
      * 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;
 
-    public PortUnificationServer(URL url) {
+    public NettyPortUnificationServer(URL url, ChannelHandler handler) throws RemotingException {
+        super(url, handler);
+
         // 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;
-    }
-
-    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 +111,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 +125,9 @@
                 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(),
+                        channels, NettyPortUnificationServer.this);
                     p.addLast("negotiation-protocol", puHandler);
                 }
             });
@@ -139,7 +135,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,7 +144,8 @@
         channel = channelFuture.channel();
     }
 
-    protected void doClose() throws Throwable {
+    @Override
+    public void doClose(){
         final long st = System.currentTimeMillis();
 
         try {
@@ -165,7 +162,7 @@
             logger.warn("Interrupted while shutting down", e);
         }
 
-        for (WireProtocol protocol : protocols) {
+        for (WireProtocol protocol : getProtocols()) {
             protocol.close();
         }
 
@@ -189,6 +186,16 @@
         return channel.isActive();
     }
 
+    @Override
+    public Collection<Channel> getChannels() {
+        return null;
+    }
+
+    @Override
+    public Channel getChannel(InetSocketAddress remoteAddress) {
+        return null;
+    }
+
     public InetSocketAddress getLocalAddress() {
         return (InetSocketAddress) channel.localAddress();
     }
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java
similarity index 73%
rename from dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java
rename to dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java
index 634b406..7605798 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/PortUnificationServerHandler.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServerHandler.java
@@ -14,12 +14,17 @@
  * 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.io.Bytes;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+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.api.pu.ChannelOperator;
+import org.apache.dubbo.remoting.buffer.ChannelBuffer;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
@@ -32,25 +37,28 @@
 import java.util.List;
 import java.util.Set;
 
-public class PortUnificationServerHandler extends ByteToMessageDecoder {
+public class NettyPortUnificationServerHandler extends ByteToMessageDecoder {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(
-        PortUnificationServerHandler.class);
+        NettyPortUnificationServerHandler.class);
 
     private final ChannelGroup channels;
 
     private final SslContext sslCtx;
     private final URL url;
+    private final ChannelHandler handler;
     private final boolean detectSsl;
     private final List<WireProtocol> protocols;
 
-    public PortUnificationServerHandler(URL url, SslContext sslCtx, boolean detectSsl,
-        List<WireProtocol> protocols, ChannelGroup channels) {
+    public NettyPortUnificationServerHandler(URL url, SslContext sslCtx, boolean detectSsl,
+                                             List<WireProtocol> protocols, ChannelGroup channels,
+                                             ChannelHandler handler) {
         this.url = url;
         this.sslCtx = sslCtx;
         this.protocols = protocols;
         this.detectSsl = detectSsl;
         this.channels = channels;
+        this.handler = handler;
     }
 
     @Override
@@ -67,8 +75,10 @@
     @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.
-        if (in.readableBytes() < 5) {
+        // size of telnet command ls is 2 bytes
+        if (in.readableBytes() < 2) {
             return;
         }
 
@@ -77,13 +87,15 @@
         } else {
             for (final WireProtocol protocol : protocols) {
                 in.markReaderIndex();
-                final ProtocolDetector.Result result = protocol.detector().detect(ctx, in);
+                ChannelBuffer buf = new NettyBackedChannelBuffer(in);
+                final ProtocolDetector.Result result = protocol.detector().detect(buf);
                 in.resetReaderIndex();
                 switch (result) {
                     case UNRECOGNIZED:
                         continue;
                     case RECOGNIZED:
-                        protocol.configServerPipeline(url, ctx.pipeline(), sslCtx);
+                        ChannelOperator operator = new NettyConfigOperator(channel, handler);
+                        protocol.configServerProtocolHandler(url, operator);
                         ctx.pipeline().remove(this);
                     case NEED_MORE_DATA:
                         return;
@@ -111,12 +123,13 @@
         ChannelPipeline p = ctx.pipeline();
         p.addLast("ssl", sslCtx.newHandler(ctx.alloc()));
         p.addLast("unificationA",
-            new PortUnificationServerHandler(url, sslCtx, false, protocols, channels));
+            new NettyPortUnificationServerHandler(url, sslCtx, false, protocols, channels, handler));
         p.remove(this);
     }
 
     private boolean isSsl(ByteBuf buf) {
-        if (detectSsl) {
+        // 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..604f29d 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;
@@ -59,7 +59,7 @@
 
 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 +94,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("5-1", "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..30cc4d5 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;
@@ -63,7 +63,7 @@
 
 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 +97,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("5-1", "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/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/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/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/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 719e036..499fcf8 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
@@ -224,7 +224,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..81e46d1 100644
--- a/dubbo-rpc/dubbo-rpc-triple/pom.xml
+++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml
@@ -30,15 +30,34 @@
     <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>
+            <groupId>org.reactivestreams</groupId>
+            <artifactId>reactive-streams</artifactId>
+            <version>${reactive.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+            <version>${reactor.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-rpc-api</artifactId>
             <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 +73,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>
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 c6a534d..e358eff 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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/AbstractTripleReactorPublisher.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/AbstractTripleReactorPublisher.java
new file mode 100644
index 0000000..9eab281
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/AbstractTripleReactorSubscriber.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/AbstractTripleReactorSubscriber.java
new file mode 100644
index 0000000..40acec2
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ClientTripleReactorPublisher.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ClientTripleReactorPublisher.java
new file mode 100644
index 0000000..1deab5a
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ClientTripleReactorSubscriber.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ClientTripleReactorSubscriber.java
new file mode 100644
index 0000000..3d5936d
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ServerTripleReactorPublisher.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ServerTripleReactorPublisher.java
new file mode 100644
index 0000000..33addf5
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ServerTripleReactorSubscriber.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/ServerTripleReactorSubscriber.java
new file mode 100644
index 0000000..32453ac
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/calls/ReactorClientCalls.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/calls/ReactorClientCalls.java
new file mode 100644
index 0000000..4ae6d45
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.calls;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+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.protocol.tri.reactive.ClientTripleReactorPublisher;
+import org.apache.dubbo.rpc.protocol.tri.reactive.ClientTripleReactorSubscriber;
+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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/calls/ReactorServerCalls.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/calls/ReactorServerCalls.java
new file mode 100644
index 0000000..6454d26
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.calls;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.apache.dubbo.rpc.protocol.tri.reactive.ServerTripleReactorPublisher;
+import org.apache.dubbo.rpc.protocol.tri.reactive.ServerTripleReactorSubscriber;
+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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/ManyToManyMethodHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/ManyToManyMethodHandler.java
new file mode 100644
index 0000000..df583f6
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.reactive.calls.ReactorServerCalls;
+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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/ManyToOneMethodHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/ManyToOneMethodHandler.java
new file mode 100644
index 0000000..133f92a
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.CallStreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.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 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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/OneToManyMethodHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/OneToManyMethodHandler.java
new file mode 100644
index 0000000..ef94d6d
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.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-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/OneToOneMethodHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/reactive/handler/OneToOneMethodHandler.java
new file mode 100644
index 0000000..0a8b0a7
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive.handler;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.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-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-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/ManyToManyMethodHandlerTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/ManyToManyMethodHandlerTest.java
new file mode 100644
index 0000000..6669eaa
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.apache.dubbo.rpc.protocol.tri.reactive.handler.ManyToManyMethodHandler;
+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-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/ManyToOneMethodHandlerTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/ManyToOneMethodHandlerTest.java
new file mode 100644
index 0000000..6cdde8d
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.apache.dubbo.rpc.protocol.tri.reactive.handler.ManyToOneMethodHandler;
+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-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/OneToManyMethodHandlerTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/OneToManyMethodHandlerTest.java
new file mode 100644
index 0000000..42b6453
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter;
+import org.apache.dubbo.rpc.protocol.tri.reactive.handler.OneToManyMethodHandler;
+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-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/OneToOneMethodHandlerTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/reactive/OneToOneMethodHandlerTest.java
new file mode 100644
index 0000000..5ba898c
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/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.rpc.protocol.tri.reactive;
+
+import org.apache.dubbo.rpc.protocol.tri.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-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..15295fe
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/PilotExchanger.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.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..4f4d8c7
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/registry/xds/util/XdsChannel.java
@@ -0,0 +1,84 @@
+/*
+ * 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.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+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.ManagedChannel;
+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 javax.net.ssl.SSLException;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+public class XdsChannel {
+
+    private static final Logger logger = LoggerFactory.getLogger(XdsChannel.class);
+
+    private final ManagedChannel channel;
+
+    protected XdsChannel(URL url) {
+        ManagedChannel managedChannel = null;
+        try {
+            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();
+        } catch (SSLException 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..4946166
--- /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;
+
+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..68473be
--- /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();
+    }
+
+    abstract static class ServerInfo {
+        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 {
+        abstract List<ServerInfo> servers();
+
+        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..ce6490b
--- /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;
+
+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..de0402e
--- /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
+    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..e1a871d
--- /dev/null
+++ b/dubbo-xds/src/test/java/org/apache/dubbo/registry/xds/util/bootstrap/BootstrapperTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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 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");
+    }
+
+    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 44c6af6..f8c7a0e 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.12-SNAPSHOT</revision>
+        <revision>3.1.1-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>