You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2021/01/15 15:48:45 UTC

[dubbo] branch 3.0 updated: merge k8s feature (#7104)

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

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


The following commit(s) were added to refs/heads/3.0 by this push:
     new 43e5eda  merge k8s feature (#7104)
43e5eda is described below

commit 43e5edaefec52df4653f0af4cb034165fdac4b3d
Author: ken.lj <ke...@gmail.com>
AuthorDate: Fri Jan 15 23:48:28 2021 +0800

    merge k8s feature (#7104)
---
 .../support/wrapper/MockClusterInvokerTest.java    |  42 ++-
 .../wrapper/MockProviderRpcExceptionTest.java      |   8 +-
 .../dubbo/common/constants/CommonConstants.java    |   6 +
 .../org/apache/dubbo/config/ApplicationConfig.java |  50 +++
 .../org/apache/dubbo/config/ServiceConfig.java     |   7 +-
 .../dubbo/config/bootstrap/DubboBootstrap.java     |  21 +-
 .../bootstrap/builders/ApplicationBuilder.java     |  28 ++
 .../ConfigurableMetadataServiceExporter.java       |  72 +++-
 .../dubbo/config/AbstractInterfaceConfigTest.java  |   3 +
 .../dubbo/config/bootstrap/DubboBootstrapTest.java |   4 +-
 .../bootstrap/builders/ApplicationBuilderTest.java |  35 +-
 .../PublishingServiceDefinitionListenerTest.java   |  22 +-
 .../dubbo/config/mock/MockServiceDiscovery.java    |  69 ++++
 .../metadata/MetadataServiceExporterTest.java      |  32 +-
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 .../spring/registry/MockServiceDiscovery.java      |  69 ++++
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 dubbo-dependencies-bom/pom.xml                     |  29 +-
 dubbo-distribution/dubbo-all/pom.xml               |  16 +
 .../metadata/InstanceMetadataChangedListener.java  |  27 +-
 .../org/apache/dubbo/metadata/MetadataService.java |  42 +++
 .../apache/dubbo/qos/command/CommandContext.java   |   9 +
 .../org/apache/dubbo/qos/command/impl/Live.java    |  52 +++
 .../org/apache/dubbo/qos/command/impl/Ready.java   |  27 +-
 .../org/apache/dubbo/qos/command/impl/Startup.java |  52 +++
 .../impl/Ready.java => probe/LivenessProbe.java}   |  32 +-
 .../impl/Ready.java => probe/ReadinessProbe.java}  |  32 +-
 .../impl/Ready.java => probe/StartupProbe.java}    |  32 +-
 .../impl/BootstrapReadinessProbe.java}             |  17 +-
 .../impl/BootstrapStartupProbe.java}               |  16 +-
 .../qos/probe/impl/ProviderReadinessProbe.java     |  52 +++
 .../qos/server/handler/HttpProcessHandler.java     |  51 ++-
 .../org.apache.dubbo.qos.command.BaseCommand       |   2 +
 .../org.apache.dubbo.qos.probe.ReadinessProbe      |   2 +
 .../org.apache.dubbo.qos.probe.StartupProbe        |   1 +
 .../dubbo/qos/command/util/CommandHelperTest.java  |   6 +-
 .../java/org/apache/dubbo/registry/Constants.java  |  10 +
 .../client/SelfHostMetaServiceDiscovery.java       | 290 +++++++++++++++
 .../registry/client/metadata/MetadataUtils.java    |  90 +++--
 .../StandardMetadataServiceURLBuilder.java         |  82 ++++-
 .../store/InMemoryWritableMetadataService.java     |  23 +-
 .../metadata/store/RemoteMetadataServiceImpl.java  |  37 +-
 .../client/migration/MigrationClusterInvoker.java  |   2 +-
 .../registry/client/InMemoryServiceDiscovery.java  |   9 +-
 .../support/ServiceOrientedRegistryTest.java       |   5 +
 dubbo-registry/dubbo-registry-dns/pom.xml          |  90 +++++
 .../org/apache/dubbo/registry/dns/DNSRegistry.java |  58 +++
 .../dubbo/registry/dns/DNSRegistryFactory.java     |  20 +-
 .../dubbo/registry/dns/DNSServiceDiscovery.java    | 152 ++++++++
 .../registry/dns/DNSServiceDiscoveryFactory.java   |  18 +-
 .../dubbo/registry/dns/util/DNSClientConst.java    |  47 +++
 .../dubbo/registry/dns/util/DNSResolver.java       | 120 ++++++
 .../dubbo/registry/dns/util/ResolveResult.java     |  66 ++++
 .../org.apache.dubbo.registry.RegistryFactory      |   1 +
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |   1 +
 .../registry/dns/DNSServiceDiscoveryTest.java      | 191 ++++++++++
 .../dubbo/registry/dns/util/DNSResolverTest.java   |  23 +-
 dubbo-registry/dubbo-registry-kubernetes/pom.xml   |  73 ++++
 .../registry/kubernetes/KubernetesRegistry.java    |  58 +++
 .../kubernetes/KubernetesRegistryFactory.java      |  20 +-
 .../kubernetes/KubernetesServiceDiscovery.java     | 404 +++++++++++++++++++++
 .../KubernetesServiceDiscoveryFactory.java         |  18 +-
 .../kubernetes/util/KubernetesClientConst.java     |  76 ++++
 .../kubernetes/util/KubernetesConfigUtils.java     | 111 ++++++
 .../org.apache.dubbo.registry.RegistryFactory      |   1 +
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |   1 +
 .../kubernetes/KubernetesServiceDiscoveryTest.java | 191 ++++++++++
 .../org.mockito.plugins.MockMaker                  |   1 +
 .../multicast/MulticastServiceDiscovery.java       |  72 ++++
 .../MulticastServiceDiscoveryFactory.java          |  18 +-
 ...g.apache.dubbo.registry.client.ServiceDiscovery |   1 +
 ...e.dubbo.registry.client.ServiceDiscoveryFactory |   1 +
 .../registry/multicast/MulticastRegistryTest.java  |  20 +-
 .../zookeeper/ZookeeperServiceDiscoveryTest.java   |   4 +-
 dubbo-registry/pom.xml                             |   2 +
 .../support/header/HeartbeatHandlerTest.java       |  45 ++-
 .../transport/netty/ClientReconnectTest.java       |   5 +-
 .../remoting/transport/netty/ThreadNameTest.java   |   4 +-
 .../transport/netty4/NettyTransporterTest.java     |   4 +-
 .../transport/netty4/ReplierDispatcherTest.java    |  13 +-
 pom.xml                                            |   2 +
 83 files changed, 3144 insertions(+), 305 deletions(-)

diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java
index feff78e..df1240c 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java
@@ -38,7 +38,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
 import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
 
 public class MockClusterInvokerTest {
 
@@ -55,7 +57,8 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerInvoke_normal() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
-        url = url.addParameter(MOCK_KEY, "fail");
+        url = url.addParameter(MOCK_KEY, "fail")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
                 + "?getSomething.mock=return aa");
@@ -84,6 +87,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerInvoke_failmock() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter(MOCK_KEY, "fail:return null")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
                 + "?getSomething.mock=return aa").addParameters(url.getParameters());
@@ -118,7 +122,8 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerInvoke_forcemock() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
-        url = url.addParameter(MOCK_KEY, "force:return null");
+        url = url.addParameter(MOCK_KEY, "force:return null")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
 
         URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
                 + "?getSomething.mock=return aa&getSomething3xx.mock=return xx")
@@ -150,7 +155,9 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerInvoke_forcemock_defaultreturn() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
-        url = url.addParameter(MOCK_KEY, "force");
+        url = url.addParameter(MOCK_KEY, "force")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
+
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
                 + "?getSomething.mock=return aa&getSomething3xx.mock=return xx&sayHello.mock=return ")
@@ -173,7 +180,8 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_Fock_someMethods() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getSomething.mock", "fail:return x")
-                .addParameter("getSomething2.mock", "force:return y");
+                .addParameter("getSomething2.mock", "force:return y")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
         RpcInvocation invocation = new RpcInvocation();
@@ -208,6 +216,7 @@ public class MockClusterInvokerTest {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getSomething.mock", "fail:return x")
                 .addParameter("getSomething2.mock", "force:return y")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -242,6 +251,7 @@ public class MockClusterInvokerTest {
                 .addParameter("mock", "fail:return null")
                 .addParameter("getSomething.mock", "fail:return x")
                 .addParameter("getSomething2.mock", "force:return y")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -278,6 +288,7 @@ public class MockClusterInvokerTest {
                 .addParameter("mock", "fail:return z")
                 .addParameter("getSomething.mock", "fail:return x")
                 .addParameter("getSomething2.mock", "force:return y")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -314,6 +325,7 @@ public class MockClusterInvokerTest {
                 .addParameter("mock", "force:return z")
                 .addParameter("getSomething.mock", "fail:return x")
                 .addParameter("getSomething2.mock", "force:return y")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -348,6 +360,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_Fock_Default() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("mock", "fail:return x")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -376,6 +389,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_checkCompatible_return() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getSomething.mock", "return x")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -402,6 +416,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("mock", "true")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -418,6 +433,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock2() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("mock", "fail")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -433,6 +449,7 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock3() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("mock", "force");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -446,6 +463,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_String() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getSomething.mock", "force:return 1688")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -459,6 +477,7 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerFromOverride_Invoke_check_int() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("getInt1.mock", "force:return 1688")
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
@@ -473,6 +492,7 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerFromOverride_Invoke_check_boolean() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("getBoolean1.mock", "force:return true")
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
@@ -487,6 +507,7 @@ public class MockClusterInvokerTest {
     @Test
     public void testMockInvokerFromOverride_Invoke_check_Boolean() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("getBoolean2.mock", "force:return true")
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
@@ -502,6 +523,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_ListString_empty() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getListString.mock", "force:return empty")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -516,6 +538,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_ListString() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getListString.mock", "force:return [\"hi\",\"hi2\"]")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -532,6 +555,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_ListPojo_empty() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getUsers.mock", "force:return empty")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -546,6 +570,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_ListPojo() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getUsers.mock", "force:return [{id:1, name:\"hi1\"}, {id:2, name:\"hi2\"}]")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -562,6 +587,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_check_ListPojo_error() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getUsers.mock", "force:return [{id:x, name:\"hi1\"}]")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -577,6 +603,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_force_throw() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getBoolean2.mock", "force:throw ")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -594,6 +621,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_force_throwCustemException() throws Throwable {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getBoolean2.mock", "force:throw org.apache.dubbo.rpc.cluster.support.wrapper.MyMockException")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -611,6 +639,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_force_throwCustemExceptionNotFound() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("getBoolean2.mock", "force:throw java.lang.RuntimeException2")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -628,6 +657,7 @@ public class MockClusterInvokerTest {
     public void testMockInvokerFromOverride_Invoke_mock_false() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
                 .addParameter("mock", "false")
+                .addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
                 .addParameter("invoke_return_error", "true");
         Invoker<IHelloService> cluster = getClusterInvoker(url);
         //Configured with mock
@@ -708,7 +738,7 @@ public class MockClusterInvokerTest {
             return "something3";
         }
 
-        public String getSomething4(){
+        public String getSomething4() {
             throw new RpcException("getSomething4|RpcException");
         }
 
@@ -754,7 +784,7 @@ public class MockClusterInvokerTest {
             return "something3mock";
         }
 
-        public String getSomething4(){
+        public String getSomething4() {
             return "something4mock";
         }
 
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java
index 39459fb..6a46312 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java
@@ -37,6 +37,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
 
 public class MockProviderRpcExceptionTest {
 
@@ -53,7 +54,8 @@ public class MockProviderRpcExceptionTest {
     @Test
     public void testMockInvokerProviderRpcException() {
         URL url = URL.valueOf("remote://1.2.3.4/" + IHelloRpcService.class.getName());
-        url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true");
+        url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true")
+                .addParameter(REFER_KEY, "path%3dorg.apache.dubbo.rpc.cluster.support.wrapper.MockProviderRpcExceptionTest%24IHelloRpcService");
         Invoker<IHelloRpcService> cluster = getClusterInvoker(url);
         RpcInvocation invocation = new RpcInvocation();
         invocation.setMethodName("getSomething4");
@@ -130,7 +132,7 @@ public class MockProviderRpcExceptionTest {
             return "something3";
         }
 
-        public String getSomething4(){
+        public String getSomething4() {
             throw new RpcException("getSomething4|RpcException");
         }
 
@@ -176,7 +178,7 @@ public class MockProviderRpcExceptionTest {
             return "something3mock";
         }
 
-        public String getSomething4(){
+        public String getSomething4() {
             return "something4mock";
         }
 
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 4f831b4..1145f9e 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
@@ -363,6 +363,12 @@ public interface CommonConstants {
      */
     String DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH = "META-INF/dubbo/service-name-mapping.properties";
 
+    String QOS_LIVE_PROBE_EXTENSION = "dubbo.application.liveness-probe";
+
+    String QOS_READY_PROBE_EXTENSION = "dubbo.application.readiness-probe";
+
+    String QOS_STARTUP_PROBE_EXTENSION = "dubbo.application.startup-probe";
+
     String REGISTRY_DELAY_NOTIFICATION_KEY = "delay-notification";
 
     String CACHE_CLEAR_TASK_INTERVAL = "dubbo.application.url.cache.task.interval";
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
index f77d3f1..3ecef21 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
@@ -172,6 +172,20 @@ public class ApplicationConfig extends AbstractConfig {
      */
     private String protocol;
 
+    /**
+     * Metadata Service, used in Service Discovery
+     */
+    private Integer metadataServicePort;
+
+    /**
+     * used to set extensions of probe in qos
+     */
+    private String livenessProbe;
+
+    private String readinessProbe;
+
+    private String startupProbe;
+
     public ApplicationConfig() {
     }
 
@@ -485,6 +499,42 @@ public class ApplicationConfig extends AbstractConfig {
         this.protocol = protocol;
     }
 
+    @Parameter(key = "metadata-service-port")
+    public Integer getMetadataServicePort() {
+        return metadataServicePort;
+    }
+
+    public void setMetadataServicePort(Integer metadataServicePort) {
+        this.metadataServicePort = metadataServicePort;
+    }
+
+    @Parameter(key = "liveness-probe")
+    public String getLivenessProbe() {
+        return livenessProbe;
+    }
+
+    public void setLivenessProbe(String livenessProbe) {
+        this.livenessProbe = livenessProbe;
+    }
+
+    @Parameter(key = "readiness-probe")
+    public String getReadinessProbe() {
+        return readinessProbe;
+    }
+
+    public void setReadinessProbe(String readinessProbe) {
+        this.readinessProbe = readinessProbe;
+    }
+
+    @Parameter(key = "startup-probe")
+    public String getStartupProbe() {
+        return startupProbe;
+    }
+
+    public void setStartupProbe(String startupProbe) {
+        this.startupProbe = startupProbe;
+    }
+
     @Override
     public void refresh() {
         super.refresh();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
index 3b5edc1..44f7730 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
@@ -217,7 +217,8 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
     public void exported() {
         List<URL> exportedURLs = this.getExportedUrls();
         exportedURLs.forEach(url -> {
-            ServiceNameMapping.getExtension(getApplication().getParameters().get(MAPPING_KEY)).map(url);
+            Map<String, String> parameters = getApplication().getParameters();
+            ServiceNameMapping.getExtension(parameters != null ? parameters.get(MAPPING_KEY) : null).map(url);
         });
         // dispatch a ServiceConfigExportedEvent since 2.7.4
         dispatch(new ServiceConfigExportedEvent(this));
@@ -435,7 +436,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
         /**
          * Here the token value configured by the provider is used to assign the value to ServiceConfig#token
          */
-        if(ConfigUtils.isEmpty(token) && provider != null) {
+        if (ConfigUtils.isEmpty(token) && provider != null) {
             token = provider.getToken();
         }
 
@@ -713,7 +714,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
     }
 
     private void postProcessConfig() {
-        List<ConfigPostProcessor> configPostProcessors =ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
+        List<ConfigPostProcessor> configPostProcessors = ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
                 .getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
         configPostProcessors.forEach(component -> component.postProcessServiceConfig(this));
     }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
index 48bc281..a4116f4 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
@@ -170,10 +170,12 @@ public class DubboBootstrap extends GenericEventListener {
 
     private AtomicBoolean started = new AtomicBoolean(false);
 
-    private AtomicBoolean ready = new AtomicBoolean(true);
+    private AtomicBoolean startup = new AtomicBoolean(true);
 
     private AtomicBoolean destroyed = new AtomicBoolean(false);
 
+    private AtomicBoolean shutdown = new AtomicBoolean(false);
+
     private volatile ServiceInstance serviceInstance;
 
     private volatile MetadataService metadataService;
@@ -878,7 +880,7 @@ public class DubboBootstrap extends GenericEventListener {
      */
     public DubboBootstrap start() {
         if (started.compareAndSet(false, true)) {
-            ready.set(false);
+            startup.set(false);
             initialize();
             if (logger.isInfoEnabled()) {
                 logger.info(NAME + " is starting...");
@@ -902,13 +904,13 @@ public class DubboBootstrap extends GenericEventListener {
                     } catch (Exception e) {
                         logger.warn(NAME + " exportAsync occurred an exception.");
                     }
-                    ready.set(true);
+                    startup.set(true);
                     if (logger.isInfoEnabled()) {
                         logger.info(NAME + " is ready.");
                     }
                 }).start();
             } else {
-                ready.set(true);
+                startup.set(true);
                 if (logger.isInfoEnabled()) {
                     logger.info(NAME + " is ready.");
                 }
@@ -973,8 +975,12 @@ public class DubboBootstrap extends GenericEventListener {
         return started.get();
     }
 
-    public boolean isReady() {
-        return ready.get();
+    public boolean isStartup() {
+        return startup.get();
+    }
+
+    public boolean isShutdown() {
+        return shutdown.get();
     }
 
     public DubboBootstrap stop() throws IllegalStateException {
@@ -1197,7 +1203,8 @@ public class DubboBootstrap extends GenericEventListener {
     }
 
     public void destroy() {
-        if (destroyLock.tryLock()) {
+        if (destroyLock.tryLock()
+                && shutdown.compareAndSet(false, true)) {
             try {
                 DubboShutdownHook.destroyAll();
 
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java
index 33e6f54..9c17afa 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java
@@ -51,6 +51,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
     private Boolean qosAcceptForeignIp;
     private Map<String, String> parameters;
     private String shutwait;
+    private Integer metadataServicePort;
+    private String livenessProbe;
+    private String readinessProbe;
+    private String startupProbe;
 
     public static ApplicationBuilder newBuilder() {
         return new ApplicationBuilder();
@@ -172,6 +176,26 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
         return getThis();
     }
 
+    public ApplicationBuilder metadataServicePort(Integer metadataServicePort) {
+        this.metadataServicePort = metadataServicePort;
+        return getThis();
+    }
+
+    public ApplicationBuilder livenessProbe(String livenessProbe) {
+        this.livenessProbe = livenessProbe;
+        return getThis();
+    }
+
+    public ApplicationBuilder readinessProbe(String readinessProbe) {
+        this.readinessProbe = readinessProbe;
+        return getThis();
+    }
+
+    public ApplicationBuilder startupProbe(String startupProbe) {
+        this.startupProbe = startupProbe;
+        return getThis();
+    }
+
     public ApplicationConfig build() {
         ApplicationConfig config = new ApplicationConfig();
         super.build(config);
@@ -193,6 +217,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
         config.setQosEnable(this.qosEnable);
         config.setQosPort(this.qosPort);
         config.setQosAcceptForeignIp(this.qosAcceptForeignIp);
+        config.setMetadataServicePort(this.metadataServicePort);
+        config.setLivenessProbe(this.livenessProbe);
+        config.setReadinessProbe(this.readinessProbe);
+        config.setStartupProbe(this.startupProbe);
         config.setParameters(this.parameters);
         if (!StringUtils.isEmpty(shutwait)) {
             config.setShutwait(shutwait);
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 edb2d0c..faae7d7 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
@@ -19,7 +19,10 @@ package org.apache.dubbo.config.metadata;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ArgumentConfig;
+import org.apache.dubbo.config.MethodConfig;
 import org.apache.dubbo.config.ProtocolConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
@@ -28,10 +31,12 @@ import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.MetadataServiceExporter;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import static java.util.Collections.emptyList;
-import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
 
 /**
  * {@link MetadataServiceExporter} implementation based on {@link ConfigManager Dubbo configurations}, the clients
@@ -73,6 +78,7 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
             serviceConfig.setRef(metadataService);
             serviceConfig.setGroup(getApplicationConfig().getName());
             serviceConfig.setVersion(metadataService.version());
+            serviceConfig.setMethods(generateMethodConfig());
 
             // export
             serviceConfig.export();
@@ -92,6 +98,28 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
         return this;
     }
 
+    /**
+     * Generate Method Config for Service Discovery Metadata <p/>
+     * <p>
+     * Make {@link MetadataService} support argument callback,
+     * used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
+     * metadata change event
+     *
+     * @since 3.0
+     */
+    private List<MethodConfig> generateMethodConfig() {
+        MethodConfig methodConfig = new MethodConfig();
+        methodConfig.setName("getAndListenInstanceMetadata");
+
+        ArgumentConfig argumentConfig = new ArgumentConfig();
+        argumentConfig.setIndex(1);
+        argumentConfig.setCallback(true);
+
+        methodConfig.setArguments(Collections.singletonList(argumentConfig));
+
+        return Collections.singletonList(methodConfig);
+    }
+
     @Override
     public ConfigurableMetadataServiceExporter unexport() {
         if (isExported()) {
@@ -115,10 +143,44 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
 
     private ProtocolConfig generateMetadataProtocol() {
         ProtocolConfig defaultProtocol = new ProtocolConfig();
-        defaultProtocol.setName(DUBBO);
-        // defaultProtocol.setHost() ?
-        // auto-increment port
-        defaultProtocol.setPort(-1);
+        Integer port = getApplicationConfig().getMetadataServicePort();
+
+        if (port == null || port < -1) {
+            if (logger.isInfoEnabled()) {
+                logger.info("Metadata Service Port hasn't been set will use default protocol defined in protocols.");
+            }
+            List<ProtocolConfig> defaultProtocols = ApplicationModel.getConfigManager().getDefaultProtocols();
+
+            ProtocolConfig dubboProtocol = findDubboProtocol(defaultProtocols);
+            if (dubboProtocol != null) {
+                logger.info("Using dubbo protocol " + dubboProtocol + " to export MetadataService.");
+                return dubboProtocol;
+            } else {
+                defaultProtocol.setName(DUBBO_PROTOCOL);
+                defaultProtocol.setPort(-1);
+            }
+
+        } else {
+            defaultProtocol.setName(DUBBO_PROTOCOL);
+            defaultProtocol.setPort(port);
+        }
+
+        logger.info("Using dubbo protocol " + defaultProtocol + " to export MetadataService.");
+
         return defaultProtocol;
     }
+
+    private ProtocolConfig findDubboProtocol(List<ProtocolConfig> protocolConfigs) {
+        if (CollectionUtils.isEmpty(protocolConfigs)) {
+            return null;
+        }
+
+        for (ProtocolConfig protocolConfig : protocolConfigs) {
+            if (DUBBO_PROTOCOL.equalsIgnoreCase(protocolConfig.getName())) {
+                return protocolConfig;
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java
index 2bcd960..3a4a83c 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.config.mock.GreetingLocal3;
 import org.apache.dubbo.config.mock.GreetingMock1;
 import org.apache.dubbo.config.mock.GreetingMock2;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -41,12 +42,14 @@ public class AbstractInterfaceConfigTest {
 
     @BeforeAll
     public static void setUp(@TempDir Path folder) {
+        ApplicationModel.reset();
         dubboProperties = folder.resolve(CommonConstants.DUBBO_PROPERTIES_KEY).toFile();
         System.setProperty(CommonConstants.DUBBO_PROPERTIES_KEY, dubboProperties.getAbsolutePath());
     }
 
     @AfterAll
     public static void tearDown() {
+        ApplicationModel.reset();
         System.clearProperty(CommonConstants.DUBBO_PROPERTIES_KEY);
     }
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
index 6136dd4..e329f23 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
@@ -25,6 +25,7 @@ import org.apache.dubbo.config.MonitorConfig;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
 import org.apache.dubbo.monitor.MonitorService;
 import org.apache.dubbo.registry.RegistryService;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -55,13 +56,14 @@ public class DubboBootstrapTest {
 
     @BeforeAll
     public static void setUp(@TempDir Path folder) {
+        ApplicationModel.reset();
         dubboProperties = folder.resolve(CommonConstants.DUBBO_PROPERTIES_KEY).toFile();
         System.setProperty(CommonConstants.DUBBO_PROPERTIES_KEY, dubboProperties.getAbsolutePath());
     }
 
     @AfterEach
     public void tearDown() throws IOException {
-
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java
index 6c37eae..0ced7cf 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java
@@ -214,6 +214,34 @@ class ApplicationBuilderTest {
     }
 
     @Test
+    void metadataServicePort() {
+        ApplicationBuilder builder = new ApplicationBuilder();
+        builder.metadataServicePort(12345);
+        Assertions.assertEquals(12345, builder.build().getMetadataServicePort());
+    }
+
+    @Test
+    void livenessProbe() {
+        ApplicationBuilder builder = new ApplicationBuilder();
+        builder.livenessProbe("TestProbe");
+        Assertions.assertEquals("TestProbe", builder.build().getLivenessProbe());
+    }
+
+    @Test
+    void readinessProbe() {
+        ApplicationBuilder builder = new ApplicationBuilder();
+        builder.readinessProbe("TestProbe");
+        Assertions.assertEquals("TestProbe", builder.build().getReadinessProbe());
+    }
+
+    @Test
+    void startupProbe() {
+        ApplicationBuilder builder = new ApplicationBuilder();
+        builder.startupProbe("TestProbe");
+        Assertions.assertEquals("TestProbe", builder.build().getStartupProbe());
+    }
+
+    @Test
     void build() {
         MonitorConfig monitor = new MonitorConfig("monitor-addr");
         RegistryConfig registry = new RegistryConfig();
@@ -223,7 +251,8 @@ class ApplicationBuilderTest {
                 .environment("develop").compiler("compiler").logger("log4j").monitor(monitor).isDefault(false)
                 .dumpDirectory("dumpDirectory").qosEnable(true).qosPort(8080).qosAcceptForeignIp(false)
                 .shutwait("shutwait").registryIds("registryIds").addRegistry(registry)
-                .appendParameter("default.num", "one");
+                .appendParameter("default.num", "one").metadataServicePort(12345)
+                .livenessProbe("liveness").readinessProbe("readiness").startupProbe("startup");
 
         ApplicationConfig config = builder.build();
         ApplicationConfig config2 = builder.build();
@@ -249,6 +278,10 @@ class ApplicationBuilderTest {
         Assertions.assertSame(registry, config.getRegistry());
         Assertions.assertTrue(config.getParameters().containsKey("default.num"));
         Assertions.assertEquals("one", config.getParameters().get("default.num"));
+        Assertions.assertEquals(12345, config.getMetadataServicePort());
+        Assertions.assertEquals("liveness", config.getLivenessProbe());
+        Assertions.assertEquals("readiness", config.getReadinessProbe());
+        Assertions.assertEquals("startup", config.getStartupProbe());
 
         Assertions.assertNotSame(config, config2);
     }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
index e7e5376..3190f34 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.config.event.listener;
 
-import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
@@ -25,7 +24,8 @@ import org.apache.dubbo.config.bootstrap.EchoServiceImpl;
 import org.apache.dubbo.config.context.ConfigManager;
 import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
 import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import com.google.gson.Gson;
@@ -33,14 +33,7 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.util.List;
-
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
-import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
-import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
-import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
-import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
@@ -80,15 +73,8 @@ public class PublishingServiceDefinitionListenerTest {
 
         String serviceDefinition = writableMetadataService.getServiceDefinition(EchoService.class.getName());
 
-        List<URL> exportedUrls = serviceConfig.getExportedUrls();
-
-        FullServiceDefinition fullServiceDefinition = buildFullDefinition(
-                serviceConfig.getInterfaceClass(),
-                exportedUrls.get(0)
-                        .removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY)
-                        .getParameters()
-        );
+        ServiceDefinition serviceDefinitionBuild = ServiceDefinitionBuilder.build(serviceConfig.getInterfaceClass());
 
-        assertEquals(serviceDefinition, new Gson().toJson(fullServiceDefinition));
+        assertEquals(serviceDefinition, new Gson().toJson(serviceDefinitionBuild));
     }
 }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java
new file mode 100644
index 0000000..33f1a16
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.mock;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MockServiceDiscovery implements ServiceDiscovery {
+    private URL registryURL;
+    private ServiceInstance serviceInstance;
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        this.registryURL = registryURL;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+
+    }
+
+    @Override
+    public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = null;
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return new HashSet<>();
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+}
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 8096276..aaabe02 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
@@ -16,12 +16,20 @@
  */
 package org.apache.dubbo.metadata;
 
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 
 import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -31,10 +39,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public class MetadataServiceExporterTest {
 
+    @BeforeAll
+    public static void init() {
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("Test"));
+        ApplicationModel.getConfigManager().addRegistry(new RegistryConfig("multicast://224.5.6.7:1234"));
+        ApplicationModel.getConfigManager().addProtocol(new ProtocolConfig("injvm"));
+    }
+
+    @AfterAll
+    public static void destroy() {
+        ApplicationModel.getConfigManager().setApplication(null);
+        ApplicationModel.reset();
+    }
+
     @Test
     public void test() {
-        MetadataServiceExporter exporter = MetadataServiceExporter.getExtension(null);
-        assertEquals(exporter, MetadataServiceExporter.getDefaultExtension());
+        MetadataService metadataService = Mockito.mock(MetadataService.class);
+        MetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter(metadataService);
+
+        exporter.export();
+        assertTrue(exporter.isExported());
+        exporter.unexport();
+
         assertTrue(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
         assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
         assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
diff --git a/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..22ceb16
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+mockregistry=org.apache.dubbo.config.mock.MockServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java
new file mode 100644
index 0000000..1511b0e
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.spring.registry;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MockServiceDiscovery implements ServiceDiscovery {
+    private URL registryURL;
+    private ServiceInstance serviceInstance;
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        this.registryURL = registryURL;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+
+    }
+
+    @Override
+    public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = null;
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return new HashSet<>();
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..cea6b84
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+mock=org.apache.dubbo.config.spring.registry.MockServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index 634586d..8d84b38 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -92,9 +92,9 @@
         <spring_version>4.3.16.RELEASE</spring_version>
         <javassist_version>3.20.0-GA</javassist_version>
         <netty_version>3.2.5.Final</netty_version>
-        <netty4_version>4.1.25.Final</netty4_version>
+        <netty4_version>4.1.51.Final</netty4_version>
         <mina_version>1.1.7</mina_version>
-        <grizzly_version>2.1.4</grizzly_version>
+        <grizzly_version>2.4.4</grizzly_version>
         <httpclient_version>4.5.3</httpclient_version>
         <httpcore_version>4.4.6</httpcore_version>
         <fastjson_version>1.2.70</fastjson_version>
@@ -129,9 +129,9 @@
         <rs_api_version>2.0</rs_api_version>
         <resteasy_version>3.0.19.Final</resteasy_version>
         <tomcat_embed_version>8.5.31</tomcat_embed_version>
-        <jetcd_version>0.4.1</jetcd_version>
+        <jetcd_version>0.5.3</jetcd_version>
         <nacos_version>1.3.1</nacos_version>
-        <grpc.version>1.22.1</grpc.version>
+        <grpc.version>1.31.1</grpc.version>
         <!-- Log libs -->
         <slf4j_version>1.7.25</slf4j_version>
         <jcl_version>1.2</jcl_version>
@@ -145,13 +145,16 @@
         <!-- Eureka -->
         <eureka.version>1.9.12</eureka.version>
 
+        <!-- Fabric8 for Kubernetes -->
+        <fabric8_kubernetes_version>4.10.3</fabric8_kubernetes_version>
+
         <!-- Alibaba -->
         <alibaba_spring_context_support_version>1.0.8</alibaba_spring_context_support_version>
 
         <jaxb_version>2.2.7</jaxb_version>
         <activation_version>1.2.0</activation_version>
         <test_container_version>1.11.2</test_container_version>
-        <etcd_launcher_version>0.3.0</etcd_launcher_version>
+        <etcd_launcher_version>0.5.3</etcd_launcher_version>
         <hessian_lite_version>3.2.8</hessian_lite_version>
         <swagger_version>1.5.19</swagger_version>
         <spring_test_version>4.3.16.RELEASE</spring_test_version>
@@ -468,11 +471,6 @@
                     </exclusion>
                 </exclusions>
             </dependency>
-            <dependency>
-                <groupId>io.etcd</groupId>
-                <artifactId>jetcd-launcher</artifactId>
-                <version>${jetcd_version}</version>
-            </dependency>
             <!-- Log libs -->
             <dependency>
                 <groupId>org.slf4j</groupId>
@@ -703,6 +701,17 @@
                 <artifactId>grpc-grpclb</artifactId>
                 <version>${grpc.version}</version>
             </dependency>
+            <dependency>
+                <groupId>io.fabric8</groupId>
+                <artifactId>kubernetes-client</artifactId>
+                <version>${fabric8_kubernetes_version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.fabric8</groupId>
+                <artifactId>kubernetes-server-mock</artifactId>
+                <scope>test</scope>
+                <version>${fabric8_kubernetes_version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml
index 5f67930..e8e4406 100644
--- a/dubbo-distribution/dubbo-all/pom.xml
+++ b/dubbo-distribution/dubbo-all/pom.xml
@@ -159,6 +159,20 @@
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-kubernetes</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-dns</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-monitor-api</artifactId>
             <version>${project.version}</version>
             <scope>compile</scope>
@@ -358,6 +372,8 @@
                                     <include>org.apache.dubbo:dubbo-registry-zookeeper</include>
                                     <include>org.apache.dubbo:dubbo-registry-nacos</include>
                                     <include>org.apache.dubbo:dubbo-registry-multiple</include>
+                                    <include>org.apache.dubbo:dubbo-registry-kubernetes</include>
+                                    <include>org.apache.dubbo:dubbo-registry-dns</include>
                                     <include>org.apache.dubbo:dubbo-monitor-api</include>
                                     <include>org.apache.dubbo:dubbo-monitor-default</include>
                                     <include>org.apache.dubbo:dubbo-container-api</include>
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java
similarity index 60%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java
index 06457f6..5536e53 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java
@@ -14,19 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.metadata;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
+public interface InstanceMetadataChangedListener {
+    /**
+     * Call when metadata in provider side update <p/>
+     * Used to notify consumer to update metadata of ServiceInstance
+     *
+     * @param metadata latest metadata
+     */
+    void onEvent(String metadata);
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
-
-    @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    /**
+     * Echo test
+     * Used to check consumer still online
+     */
+    default String echo(String msg) {
+        return msg;
     }
-
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
index 24dc82d..c945d73 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
@@ -233,4 +233,46 @@ public interface MetadataService {
     static SortedSet<String> toSortedStrings(Stream<URL> stream) {
         return unmodifiableSortedSet(stream.map(URL::toFullString).collect(TreeSet::new, Set::add, Set::addAll));
     }
+
+    /**
+     * Export Metadata in Service Instance of Service Discovery
+     * <p>
+     * Used for consumer to get Service Instance Metadata
+     * if Registry is unsupported with publishing metadata
+     *
+     * @param instanceMetadata {@link Map} of provider Service Instance Metadata
+     * @since 3.0
+     */
+    default void exportInstanceMetadata(String instanceMetadata) {
+        throw new UnsupportedOperationException("This operation is not supported for consumer.");
+    }
+
+    /**
+     * Get all Metadata listener from local
+     * <p>
+     * Used for consumer to get Service Instance Metadata
+     * if Registry is unsupported with publishing metadata
+     *
+     * @return {@link Map} of {@link InstanceMetadataChangedListener}
+     * @since 3.0
+     */
+    default Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
+        throw new UnsupportedOperationException("This operation is not supported for consumer.");
+    }
+
+    /**
+     * 1. Fetch Metadata in Service Instance of Service Discovery
+     * 2. Add a metadata change listener
+     * <p>
+     * Used for consumer to get Service Instance Metadata
+     * if Registry is unsupported with publishing metadata
+     *
+     * @param consumerId consumerId
+     * @param listener   {@link InstanceMetadataChangedListener} used to notify event
+     * @return {@link Map} of provider Service Instance Metadata
+     * @since 3.0
+     */
+    default String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
+        throw new UnsupportedOperationException("This operation is not supported for consumer.");
+    }
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java
index 0bd427b..0c2612a 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java
@@ -25,6 +25,7 @@ public class CommandContext {
     private Channel remote;
     private boolean isHttp;
     private Object originRequest;
+    private int httpCode = 200;
 
     public CommandContext(String commandName) {
         this.commandName = commandName;
@@ -75,4 +76,12 @@ public class CommandContext {
     public void setOriginRequest(Object originRequest) {
         this.originRequest = originRequest;
     }
+
+    public int getHttpCode() {
+        return httpCode;
+    }
+
+    public void setHttpCode(int httpCode) {
+        this.httpCode = httpCode;
+    }
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java
new file mode 100644
index 0000000..d355845
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.command.impl;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.qos.probe.LivenessProbe;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.List;
+
+@Cmd(name = "live", summary = "Judge if service is alive? ")
+public class Live implements BaseCommand {
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        URL url = URL.valueOf("application://")
+                .addParameter(CommonConstants.QOS_LIVE_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getLivenessProbe());
+        List<LivenessProbe> livenessProbes = ExtensionLoader.getExtensionLoader(LivenessProbe.class)
+                .getActivateExtension(url, CommonConstants.QOS_LIVE_PROBE_EXTENSION);
+        if (!livenessProbes.isEmpty()) {
+            for (LivenessProbe livenessProbe : livenessProbes) {
+                if (!livenessProbe.check()) {
+                    // 503 Service Unavailable
+                    commandContext.setHttpCode(503);
+                    return "false";
+                }
+            }
+        }
+        // 200 OK
+        commandContext.setHttpCode(200);
+        return "true";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
index 06457f6..109adbc 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
@@ -16,17 +16,38 @@
  */
 package org.apache.dubbo.qos.command.impl;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.qos.command.BaseCommand;
 import org.apache.dubbo.qos.command.CommandContext;
 import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.qos.probe.ReadinessProbe;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
+import java.util.List;
+
+@Cmd(name = "ready", summary = "Judge if service is ready to work? ")
 public class Ready implements BaseCommand {
 
     @Override
     public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+        URL url = URL.valueOf("application://")
+                .addParameter(CommonConstants.QOS_READY_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getReadinessProbe());
+        List<ReadinessProbe> readinessProbes = ExtensionLoader.getExtensionLoader(ReadinessProbe.class)
+                .getActivateExtension(url, CommonConstants.QOS_READY_PROBE_EXTENSION);
+        if (!readinessProbes.isEmpty()) {
+            for (ReadinessProbe readinessProbe : readinessProbes) {
+                if (!readinessProbe.check()) {
+                    // 503 Service Unavailable
+                    commandContext.setHttpCode(503);
+                    return "false";
+                }
+            }
+        }
+        // 200 OK
+        commandContext.setHttpCode(200);
+        return "true";
     }
 
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java
new file mode 100644
index 0000000..e9a5b54
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.command.impl;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.qos.probe.StartupProbe;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.util.List;
+
+@Cmd(name = "startup", summary = "Judge if service has started? ")
+public class Startup implements BaseCommand {
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        URL url = URL.valueOf("application://")
+                .addParameter(CommonConstants.QOS_STARTUP_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getStartupProbe());
+        List<StartupProbe> startupProbes = ExtensionLoader.getExtensionLoader(StartupProbe.class)
+                .getActivateExtension(url, CommonConstants.QOS_STARTUP_PROBE_EXTENSION);
+        if (!startupProbes.isEmpty()) {
+            for (StartupProbe startupProbe : startupProbes) {
+                if (!startupProbe.check()) {
+                    // 503 Service Unavailable
+                    commandContext.setHttpCode(503);
+                    return "false";
+                }
+            }
+        }
+        // 200 OK
+        commandContext.setHttpCode(200);
+        return "true";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java
similarity index 60%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java
index 06457f6..15484d3 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java
@@ -14,19 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.qos.probe;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
-
-    @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
-    }
+import org.apache.dubbo.common.extension.SPI;
 
+/**
+ * A probe to indicate whether program is alive
+ * </p>
+ * If one or more spi return false, 'live' command in dubbo-qos
+ * will return false. This can be extend with custom program and developers
+ * can implement this to customize life cycle.
+ *
+ * @since 3.0
+ */
+@SPI
+public interface LivenessProbe {
+    /**
+     * Check if program is alive
+     *
+     * @return {@link boolean} result of probe
+     */
+    boolean check();
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java
similarity index 60%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java
index 06457f6..d988f38 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java
@@ -14,19 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.qos.probe;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
-
-    @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
-    }
+import org.apache.dubbo.common.extension.SPI;
 
+/**
+ * A probe to indicate whether program is ready
+ * </p>
+ * If one or more spi return false, 'ready' command in dubbo-qos
+ * will return false. This can be extend with custom program and developers
+ * can implement this to customize life cycle.
+ *
+ * @since 3.0
+ */
+@SPI
+public interface ReadinessProbe {
+    /**
+     * Check if program is Ready
+     *
+     * @return {@link boolean} result of probe
+     */
+    boolean check();
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java
similarity index 60%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java
index 06457f6..45bdd4b 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java
@@ -14,19 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.qos.probe;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
-
-    @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
-    }
+import org.apache.dubbo.common.extension.SPI;
 
+/**
+ * A probe to indicate whether program is startup
+ * </p>
+ * If one or more spi return false, 'startup' command in dubbo-qos
+ * will return false. This can be extend with custom program and developers
+ * can implement this to customize life cycle.
+ *
+ * @since 3.0
+ */
+@SPI
+public interface StartupProbe {
+    /**
+     * Check if program has been startup
+     *
+     * @return {@link boolean} result of probe
+     */
+    boolean check();
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java
similarity index 66%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java
index 06457f6..da80702 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java
@@ -14,19 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.qos.probe.impl;
 
+import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+import org.apache.dubbo.qos.probe.ReadinessProbe;
 
+@Activate
+public class BootstrapReadinessProbe implements ReadinessProbe {
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    public boolean check() {
+        return !DubboBootstrap.getInstance().isShutdown();
     }
-
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java
similarity index 66%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java
index 06457f6..74105e7 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java
@@ -14,19 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.qos.probe.impl;
 
+import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.qos.probe.StartupProbe;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+@Activate
+public class BootstrapStartupProbe implements StartupProbe {
 
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    public boolean check() {
+        return DubboBootstrap.getInstance().isStartup();
     }
-
 }
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
new file mode 100644
index 0000000..5825107
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.probe.impl;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.qos.probe.ReadinessProbe;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.model.ServiceRepository;
+
+import java.util.Collection;
+import java.util.List;
+
+@Activate
+public class ProviderReadinessProbe implements ReadinessProbe {
+    private static ServiceRepository serviceRepository = ApplicationModel.getServiceRepository();
+
+    @Override
+    public boolean check() {
+        Collection<ProviderModel> providerModelList = serviceRepository.getExportedServices();
+        if (providerModelList.isEmpty()) {
+            return true;
+        }
+
+        boolean hasService = false;
+        for (ProviderModel providerModel : providerModelList) {
+            List<ProviderModel.RegisterStatedURL> statedUrls = providerModel.getStatedUrl();
+            for (ProviderModel.RegisterStatedURL statedUrl : statedUrls) {
+                if (statedUrl.isRegistered()) {
+                    hasService = true;
+                    break;
+                }
+            }
+        }
+
+        return hasService;
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java
index 408e185..07af64d 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java
@@ -30,6 +30,7 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponseStatus;
@@ -52,19 +53,37 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
     private static CommandExecutor commandExecutor = new DefaultCommandExecutor();
 
 
+    private static FullHttpResponse http(int httpCode, String result) {
+        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(httpCode)
+                , Unpooled.wrappedBuffer(result.getBytes()));
+        HttpHeaders httpHeaders = response.headers();
+        httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
+        httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
+        return response;
+    }
+
+    private static FullHttpResponse http404() {
+        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
+        HttpHeaders httpHeaders = response.headers();
+        httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
+        httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
+        return response;
+    }
+
     @Override
     protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
         CommandContext commandContext = HttpCommandDecoder.decode(msg);
         // return 404 when fail to construct command context
         if (commandContext == null) {
-            log.warn("can not found commandContext url: " + msg.getUri());
+            log.warn("can not found commandContext url: " + msg.uri());
             FullHttpResponse response = http404();
             ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
         } else {
             commandContext.setRemote(ctx.channel());
             try {
                 String result = commandExecutor.execute(commandContext);
-                FullHttpResponse response = http200(result);
+                int httpCode = commandContext.getHttpCode();
+                FullHttpResponse response = http(httpCode, result);
                 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
             } catch (NoSuchCommandException ex) {
                 log.error("can not find commandContext: " + commandContext, ex);
@@ -72,36 +91,10 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
                 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
             } catch (Exception qosEx) {
                 log.error("execute commandContext: " + commandContext + " got exception", qosEx);
-                FullHttpResponse response = http500(qosEx.getMessage());
+                FullHttpResponse response = http(500, qosEx.getMessage());
                 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
             }
         }
     }
 
-    private static final FullHttpResponse http200(String result) {
-        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
-                Unpooled.wrappedBuffer(result.getBytes()));
-        HttpHeaders httpHeaders = response.headers();
-        httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
-        httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
-        return response;
-    }
-
-    private static final FullHttpResponse http404() {
-        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
-        HttpHeaders httpHeaders = response.headers();
-        httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
-        httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
-        return response;
-    }
-
-    private static final FullHttpResponse http500(String errorMessage) {
-        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR
-                , Unpooled.wrappedBuffer(errorMessage.getBytes()));
-        HttpHeaders httpHeaders = response.headers();
-        httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
-        httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
-        return response;
-    }
-
 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
index b4f0322..089762d 100644
--- a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
+++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
@@ -8,5 +8,7 @@ offline=org.apache.dubbo.qos.command.impl.Offline
 offlineApp=org.apache.dubbo.qos.command.impl.OfflineApp
 offlineInterface=org.apache.dubbo.qos.command.impl.OfflineInterface
 ready=org.apache.dubbo.qos.command.impl.Ready
+startup=org.apache.dubbo.qos.command.impl.Startup
+live=org.apache.dubbo.qos.command.impl.Live
 version=org.apache.dubbo.qos.command.impl.Version
 publish-metadata=org.apache.dubbo.qos.command.impl.PublishMetadata
diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe
new file mode 100644
index 0000000..702ab02
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe
@@ -0,0 +1,2 @@
+bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapReadinessProbe
+provider = org.apache.dubbo.qos.probe.impl.ProviderReadinessProbe
\ No newline at end of file
diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe
new file mode 100644
index 0000000..1095150
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe
@@ -0,0 +1 @@
+bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapStartupProbe
\ No newline at end of file
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
index 812d5c7..a1e03a2 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
@@ -18,12 +18,16 @@ package org.apache.dubbo.qos.command.util;
 
 import org.apache.dubbo.qos.command.GreetingCommand;
 import org.apache.dubbo.qos.command.impl.Help;
+import org.apache.dubbo.qos.command.impl.Live;
 import org.apache.dubbo.qos.command.impl.Ls;
 import org.apache.dubbo.qos.command.impl.Offline;
 import org.apache.dubbo.qos.command.impl.Online;
+import org.apache.dubbo.qos.command.impl.PublishMetadata;
 import org.apache.dubbo.qos.command.impl.Quit;
 import org.apache.dubbo.qos.command.impl.Ready;
+import org.apache.dubbo.qos.command.impl.Startup;
 import org.apache.dubbo.qos.command.impl.Version;
+
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
@@ -47,7 +51,7 @@ public class CommandHelperTest {
         List<Class<?>> classes = CommandHelper.getAllCommandClass();
         assertThat(classes,
                 containsInAnyOrder(GreetingCommand.class, Help.class, Ls.class, Offline.class, Online.class, Quit.class,
-                        Ready.class, Version.class));
+                        Live.class, Ready.class, Startup.class, Version.class, PublishMetadata.class));
     }
 
     @Test
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
index 1bf4168..eee0665 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java
@@ -88,4 +88,14 @@ public interface Constants {
     String REGISTRY_RETRY_PERIOD_KEY = "retry.period";
 
     String SESSION_TIMEOUT_KEY = "session";
+
+    /**
+     * To decide the frequency of checking Distributed Service Discovery Registry callback hook (in ms)
+     */
+    String ECHO_POLLING_CYCLE_KEY = "echoPollingCycle";
+
+    /**
+     * Default value for check frequency: 60000 (ms)
+     */
+    int DEFAULT_ECHO_POLLING_CYCLE = 60000;
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java
new file mode 100644
index 0000000..25ab77e
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.RevisionResolver;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.registry.Constants;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.client.metadata.MetadataUtils;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public abstract class SelfHostMetaServiceDiscovery implements ServiceDiscovery {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private URL registryURL;
+
+    /**
+     * Echo check if consumer is still work
+     * echo task may take a lot of time when consumer offline, create a new ScheduledThreadPool
+     */
+    private final ScheduledExecutorService echoCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-Registry-EchoCheck-Consumer"));
+
+    // =================================== Provider side =================================== //
+
+    private ServiceInstance serviceInstance;
+
+    /**
+     * Local {@link ServiceInstance} Metadata's revision
+     */
+    private String lastMetadataRevision;
+
+    // =================================== Consumer side =================================== //
+
+    /**
+     * Local Cache of {@link ServiceInstance} Metadata
+     * <p>
+     * Key - {@link ServiceInstance} ID ( usually ip + port )
+     * Value - Json processed metadata string
+     */
+    private final ConcurrentHashMap<String, String> metadataMap = new ConcurrentHashMap<>();
+
+    /**
+     * Local Cache of {@link ServiceInstance}
+     * <p>
+     * Key - Service Name
+     * Value - List {@link ServiceInstance}
+     */
+    private final ConcurrentHashMap<String, List<ServiceInstance>> cachedServiceInstances = new ConcurrentHashMap<>();
+
+    /**
+     * Local Cache of Service's {@link ServiceInstance} list revision,
+     * used to check if {@link ServiceInstance} list has been updated
+     * <p>
+     * Key - ServiceName
+     * Value - a revision calculate from {@link List} of {@link ServiceInstance}
+     */
+    private final ConcurrentHashMap<String, String> serviceInstanceRevisionMap = new ConcurrentHashMap<>();
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        this.registryURL = registryURL;
+        doInitialize(registryURL);
+        long echoPollingCycle = registryURL.getParameter(Constants.ECHO_POLLING_CYCLE_KEY, Constants.DEFAULT_ECHO_POLLING_CYCLE);
+
+        // Echo check: test if consumer is offline, remove MetadataChangeListener,
+        // reduce the probability of failure when metadata update
+        echoCheckExecutor.scheduleAtFixedRate(() -> {
+            WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
+            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
+            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
+
+            while (iterator.hasNext()) {
+                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
+                try {
+                    entry.getValue().echo(CommonConstants.DUBBO);
+                } catch (RpcException e) {
+                    if (logger.isInfoEnabled()) {
+                        logger.info("Send echo message to consumer error. Possible cause: consumer is offline.");
+                    }
+                    iterator.remove();
+                }
+            }
+        }, echoPollingCycle, echoPollingCycle, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void destroy() throws Exception {
+        doDestroy();
+        metadataMap.clear();
+        serviceInstanceRevisionMap.clear();
+        echoCheckExecutor.shutdown();
+    }
+
+    private void updateMetadata(ServiceInstance serviceInstance) {
+        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
+        String metadataString = JSONObject.toJSONString(serviceInstance.getMetadata());
+        String metadataRevision = RevisionResolver.calRevision(metadataString);
+
+        // check if metadata updated
+        if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) {
+            logger.info("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
+            if (logger.isDebugEnabled()) {
+                logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
+            }
+
+            lastMetadataRevision = metadataRevision;
+
+            // save newest metadata to local
+            metadataService.exportInstanceMetadata(metadataString);
+
+            // notify to consumer
+            Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
+            Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
+
+            while (iterator.hasNext()) {
+                Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
+                try {
+                    entry.getValue().onEvent(metadataString);
+                } catch (RpcException e) {
+                    logger.warn("Notify to consumer error. Possible cause: consumer is offline.");
+                    // remove listener if consumer is offline
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+
+        updateMetadata(serviceInstance);
+
+        doRegister(serviceInstance);
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+
+        updateMetadata(serviceInstance);
+
+        doUpdate(serviceInstance);
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        doUnregister(serviceInstance);
+
+        this.serviceInstance = null;
+
+        // notify empty message to consumer
+        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
+        metadataService.exportInstanceMetadata("");
+        metadataService.getInstanceMetadataChangedListenerMap().forEach((consumerId, listener) -> listener.onEvent(""));
+        metadataService.getInstanceMetadataChangedListenerMap().clear();
+    }
+
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @SuppressWarnings("unchecked")
+    public final void fillServiceInstance(DefaultServiceInstance serviceInstance) {
+        String hostId = serviceInstance.getId();
+        if (metadataMap.containsKey(hostId)) {
+            // Use cached metadata.
+            // Metadata will be updated by provider callback
+
+            String metadataString = metadataMap.get(hostId);
+            serviceInstance.setMetadata(JSONObject.parseObject(metadataString, Map.class));
+        } else {
+            // refer from MetadataUtils, this proxy is different from the one used to refer exportedURL
+            MetadataService metadataService = MetadataUtils.getMetadataServiceProxy(serviceInstance, this);
+
+            String consumerId = ApplicationModel.getName() + NetUtils.getLocalHost();
+            String metadata = metadataService.getAndListenInstanceMetadata(
+                    consumerId, metadataString -> {
+                        logger.info("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("Poll DNS data. Service Instance changed: " + changed + " Service Name: " + serviceName);
+        }
+
+        if (changed) {
+            List<ServiceInstance> oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>());
+
+            // remove expired invoker
+            Set<ServiceInstance> allServiceInstances = new HashSet<>(oldServiceInstances.size() + instances.size());
+            allServiceInstances.addAll(oldServiceInstances);
+            allServiceInstances.addAll(instances);
+
+            allServiceInstances.removeAll(oldServiceInstances);
+
+            allServiceInstances.forEach(removedServiceInstance -> {
+                MetadataUtils.destroyMetadataServiceProxy(removedServiceInstance, this);
+            });
+
+            cachedServiceInstances.put(serviceName, instances);
+            listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
+        }
+    }
+
+    public void doInitialize(URL registryURL) throws Exception {
+    }
+
+    public void doDestroy() throws Exception {
+    }
+
+    public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
+
+    }
+
+    public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
+
+    }
+
+    public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
+
+    }
+
+    /**
+     * UT used only
+     */
+    @Deprecated
+    public final ConcurrentHashMap<String, List<ServiceInstance>> getCachedServiceInstances() {
+        return cachedServiceInstances;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
index 0321a16..27f66f5 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
@@ -33,7 +33,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
+import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
 
 public class MetadataUtils {
@@ -42,6 +46,10 @@ public class MetadataUtils {
 
     public static ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
 
+    public static ConcurrentMap<String, Invoker<?>> metadataServiceInvokers = new ConcurrentHashMap<>();
+
+    public static ConcurrentMap<String, Lock> metadataServiceLocks = new ConcurrentHashMap<>();
+
     private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
 
     private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
@@ -72,39 +80,69 @@ public class MetadataUtils {
         // store in local
         WritableMetadataService.getDefaultExtension().publishServiceDefinition(url);
         // send to remote
-//        if (REMOTE_METADATA_STORAGE_TYPE.equals(url.getParameter(METADATA_KEY))) {
-        getRemoteMetadataService().publishServiceDefinition(url);
-//        }
+        if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(url.getParameter(METADATA_KEY))) {
+            getRemoteMetadataService().publishServiceDefinition(url);
+        }
+    }
+
+    public static String computeKey(ServiceInstance serviceInstance) {
+        return serviceInstance.getServiceName() + "##" + serviceInstance.getId() + "##" +
+                ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
     }
 
     public static MetadataService getMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
-        String key = instance.getServiceName() + "##" +
-                ServiceInstanceMetadataUtils.getExportedServicesRevision(instance);
-        return metadataServiceProxies.computeIfAbsent(key, k -> {
-            MetadataServiceURLBuilder builder = null;
-            ExtensionLoader<MetadataServiceURLBuilder> loader
-                    = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
-
-            Map<String, String> metadata = instance.getMetadata();
-            // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
-            String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
-            if (StringUtils.isNotEmpty(dubboURLsJSON)) {
-                builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
-            } else {
-                builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
-            }
+        String key = computeKey(instance);
+        Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
+
+        lock.lock();
+        try {
+            return metadataServiceProxies.computeIfAbsent(key, k -> referProxy(k, instance));
+        } finally {
+            lock.unlock();
+        }
+    }
 
-            List<URL> urls = builder.build(instance);
-            if (CollectionUtils.isEmpty(urls)) {
-                throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
-                        + instance + ", but no metadata service can build from it.");
+    public static void destroyMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
+        String key = computeKey(instance);
+        Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
+
+        lock.lock();
+        try {
+            if (metadataServiceProxies.containsKey(key)) {
+                metadataServiceProxies.remove(key);
+                Invoker<?> invoker = metadataServiceInvokers.remove(key);
+                invoker.destroy();
             }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private static MetadataService referProxy(String key, ServiceInstance instance) {
+        MetadataServiceURLBuilder builder = null;
+        ExtensionLoader<MetadataServiceURLBuilder> loader
+                = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
+
+        Map<String, String> metadata = instance.getMetadata();
+        // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
+        String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
+        if (metadata.isEmpty() || StringUtils.isEmpty(dubboURLsJSON)) {
+            builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
+        } else {
+            builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
+        }
+
+        List<URL> urls = builder.build(instance);
+        if (CollectionUtils.isEmpty(urls)) {
+            throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
+                    + instance + ", but no metadata service can build from it.");
+        }
 
-            // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
-            Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
+        // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
+        Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
+        metadataServiceInvokers.put(key, invoker);
 
-            return proxyFactory.getProxy(invoker);
-        });
+        return proxyFactory.getProxy(invoker);
     }
 
     public static void saveMetadataURL(URL url) {
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 8adf71a..a7dda4b 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,19 +19,25 @@ package org.apache.dubbo.registry.client.metadata;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_TIMEOUT_VALUE;
 import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams;
@@ -43,7 +49,9 @@ import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataU
  * @since 2.7.5
  */
 public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuilder {
-    
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
     public static final String NAME = "standard";
 
     /**
@@ -61,24 +69,66 @@ public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuil
         String serviceName = serviceInstance.getServiceName();
 
         String host = serviceInstance.getHost();
-        URLBuilder urlBuilder = new URLBuilder();
-        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
-            String key = entry.getKey();
-            String value = entry.getValue();
-            if (key.equals(PORT_KEY)) {
-                urlBuilder.setPort(Integer.parseInt(value));
-            } else if (key.equals(PROTOCOL_KEY)) {
-                urlBuilder.setProtocol(value);
-            } else {
-                urlBuilder.addParameter(key, value);
-            }
+
+        if (paramsMap.isEmpty()) {
+            // ServiceInstance Metadata is empty. Happened when registry not support metadata write.
+            urls.add(generateUrlWithoutMetadata(serviceName, host, serviceInstance.getPort()));
+        } else {
+            urls.add(generateWithMetadata(serviceName, host, paramsMap));
+        }
+
+        return urls;
+    }
+
+    private URL generateWithMetadata(String serviceName, String host, Map<String, String> params) {
+        String protocol = params.get(PROTOCOL_KEY);
+        int port = Integer.parseInt(params.get(PORT_KEY));
+        URLBuilder urlBuilder = new URLBuilder()
+                .setHost(host)
+                .setPort(port)
+                .setProtocol(protocol)
+                .setPath(MetadataService.class.getName())
+                .addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
+                .addParameter(SIDE_KEY, CONSUMER);
+
+        // add parameters
+        params.forEach(urlBuilder::addParameter);
+
+        // add the default parameters
+        urlBuilder.addParameter(GROUP_KEY, serviceName);
+        return urlBuilder.build();
+    }
+
+    private URL generateUrlWithoutMetadata(String serviceName, String host, Integer instancePort) {
+        Integer port = ApplicationModel.getApplicationConfig().getMetadataServicePort();
+        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.");
+            port = instancePort;
         }
-        urlBuilder.setHost(host).setPath(MetadataService.class.getName())
+
+        if (port == null || port < 1) {
+            String message = "Metadata Service Port should be specified for consumer. " +
+                    "Please set dubbo.application.metadataServicePort and " +
+                    "make sure it has been set on provider side. " +
+                    "ServiceName: " + serviceName + " Host: " + host;
+            throw new IllegalStateException(message);
+        }
+
+        URLBuilder urlBuilder = new URLBuilder()
+                .setHost(host)
+                .setPort(port)
+                .setProtocol(DUBBO_PROTOCOL)
+                .setPath(MetadataService.class.getName())
                 .addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
+                .addParameter(Constants.RECONNECT_KEY, false)
                 .addParameter(SIDE_KEY, CONSUMER)
-                .addParameter(GROUP_KEY, serviceName);
+                .addParameter(GROUP_KEY, serviceName)
+                .addParameter(VERSION_KEY, MetadataService.VERSION);
 
-        urls.add(urlBuilder.build());
-        return urls;
+        // add ServiceInstance Metadata notify support
+        urlBuilder.addParameter("getAndListenInstanceMetadata.1.callback", true);
+
+        return urlBuilder.build();
     }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
index e31745a..fdecb76 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
 import org.apache.dubbo.metadata.MetadataService;
@@ -82,9 +83,12 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
     URL metadataServiceURL;
     ConcurrentMap<String, MetadataInfo> metadataInfos;
     final Semaphore metadataSemaphore = new Semaphore(0);
-
     final Map<String, Set<String>> serviceToAppsMapping = new HashMap<>();
 
+    String instanceMetadata;
+    ConcurrentMap<String, InstanceMetadataChangedListener> instanceMetadataChangedListenerMap = new ConcurrentHashMap<>();
+
+
     // ==================================================================================== //
 
     // =================================== Subscription =================================== //
@@ -241,6 +245,22 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
     }
 
     @Override
+    public void exportInstanceMetadata(String metadata) {
+        this.instanceMetadata = metadata;
+    }
+
+    @Override
+    public Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
+        return instanceMetadataChangedListenerMap;
+    }
+
+    @Override
+    public String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
+        instanceMetadataChangedListenerMap.put(consumerId, listener);
+        return instanceMetadata;
+    }
+
+    @Override
     public MetadataInfo getDefaultMetadataInfo() {
         if (CollectionUtils.isEmptyMap(metadataInfos)) {
             return null;
@@ -379,5 +399,4 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
             return o1.toFullString().compareTo(o2.toFullString());
         }
     }
-
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
index 25bc03c..bca273b 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
@@ -60,15 +60,22 @@ public class RemoteMetadataServiceImpl {
                 SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(serviceName, metadataInfo.calAndGetRevision());
                 metadataInfo.calAndGetRevision();
                 metadataInfo.getExtendParams().put(REGISTRY_CLUSTER_KEY, registryCluster);
-                MetadataReport metadataReport = getMetadataReports().get(registryCluster);
-                if (metadataReport == null) {
-                    metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
+                if (getMetadataReports().size() > 0) {
+                    MetadataReport metadataReport = getMetadataReports().get(registryCluster);
+                    if (metadataReport == null) {
+                        metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
+                    }
+                    logger.info("Publishing metadata to " + metadataReport.getClass().getSimpleName());
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(metadataInfo.toString());
+                    }
+                    metadataReport.publishAppMetadata(identifier, metadataInfo);
+                } else {
+                    if (logger.isInfoEnabled()) {
+                        logger.info("Remote Metadata Report Server not hasn't been configured. " +
+                                "Only publish Metadata to local.");
+                    }
                 }
-                logger.info("Publishing metadata to " + metadataReport.getClass().getSimpleName());
-                if (logger.isDebugEnabled()) {
-                    logger.debug(metadataInfo.toString());
-                }
-                metadataReport.publishAppMetadata(identifier, metadataInfo);
                 metadataInfo.markReported();
             }
         });
@@ -80,6 +87,8 @@ public class RemoteMetadataServiceImpl {
 
         String registryCluster = instance.getExtendParams().get(REGISTRY_CLUSTER_KEY);
 
+        checkRemoteConfigured();
+
         MetadataReport metadataReport = getMetadataReports().get(registryCluster);
         if (metadataReport == null) {
             metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
@@ -87,8 +96,20 @@ public class RemoteMetadataServiceImpl {
         return metadataReport.getAppMetadata(identifier, instance.getExtendParams());
     }
 
+    private void checkRemoteConfigured() {
+        if (getMetadataReports().size() == 0) {
+            String msg = "Remote Metadata Report Server not hasn't been configured. " +
+                    "Unable to get Metadata from remote!";
+            logger.error(msg);
+            throw new IllegalStateException(msg);
+        }
+    }
+
     public void publishServiceDefinition(URL url) {
         String side = url.getSide();
+
+        checkRemoteConfigured();
+
         if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
             //TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
             publishProvider(url);
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java
index 91fa942..2efae4c 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java
@@ -47,4 +47,4 @@ public interface MigrationClusterInvoker<T> extends ClusterInvoker<T> {
     void refreshServiceDiscoveryInvokerOnMappingCallback(boolean forceMigrate);
 
     void reRefer(URL newSubscribeUrl);
-}
+}
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java
index 93043ba..20eeed3 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java
@@ -44,6 +44,8 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
 
     private ServiceInstance serviceInstance;
 
+    private URL registryURL;
+
     @Override
     public Set<String> getServices() {
         return repository.keySet();
@@ -71,6 +73,11 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
     }
 
     @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @Override
     public ServiceInstance getLocalInstance() {
         return serviceInstance;
     }
@@ -104,7 +111,7 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
 
     @Override
     public void initialize(URL registryURL) throws Exception {
-
+        this.registryURL = registryURL;
     }
 
     @Override
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java
index e65a271..bc0906f 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java
@@ -18,9 +18,11 @@ package org.apache.dubbo.registry.support;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -38,6 +40,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
 import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
+import static org.apache.dubbo.rpc.Constants.ID_KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -51,6 +54,7 @@ public class ServiceOrientedRegistryTest {
 
     private static final URL registryURL = valueOf("in-memory://localhost:12345")
             .addParameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)
+            .addParameter(ID_KEY, "org.apache.dubbo.config.RegistryConfig#0")
             .addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "a, b , c,d,e ,");
 
     private static final String SERVICE_INTERFACE = "org.apache.dubbo.metadata.MetadataService";
@@ -81,6 +85,7 @@ public class ServiceOrientedRegistryTest {
         registry = ServiceDiscoveryRegistry.create(registryURL);
         metadataService = WritableMetadataService.getDefaultExtension();
         notifyListener = new MyNotifyListener();
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("Test"));
     }
 
     @Test
diff --git a/dubbo-registry/dubbo-registry-dns/pom.xml b/dubbo-registry/dubbo-registry-dns/pom.xml
new file mode 100644
index 0000000..54e526c
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-registry</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>dubbo-registry-dns</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>The DNS registry module of Dubbo project</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-config-api</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-dubbo</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-serialization-hessian2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-remoting-netty4</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-multicast</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java
new file mode 100644
index 0000000..79fa0cd
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.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.dns;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.NotifyListener;
+import org.apache.dubbo.registry.support.FailbackRegistry;
+
+/**
+ * Empty implements for DNS <br/>
+ * DNS only support `Service Discovery` mode register <br/>
+ * Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
+ * {@link DNSServiceDiscovery} is the real implementation of DNS
+ */
+public class DNSRegistry extends FailbackRegistry {
+    public DNSRegistry(URL url) {
+        super(url);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void doRegister(URL url) {
+
+    }
+
+    @Override
+    public void doUnregister(URL url) {
+
+    }
+
+    @Override
+    public void doSubscribe(URL url, NotifyListener listener) {
+
+    }
+
+    @Override
+    public void doUnsubscribe(URL url, NotifyListener listener) {
+
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java
similarity index 61%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java
index 06457f6..870485e 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java
@@ -14,19 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.dns;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+public class DNSRegistryFactory extends AbstractRegistryFactory {
 
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    protected String createRegistryCacheKey(URL url) {
+        return url.toFullString();
     }
 
+    @Override
+    protected Registry createRegistry(URL url) {
+        return new DNSRegistry(url);
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java
new file mode 100644
index 0000000..a4d30d6
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.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.registry.dns;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.SelfHostMetaServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.dns.util.DNSClientConst;
+import org.apache.dubbo.registry.dns.util.DNSResolver;
+import org.apache.dubbo.registry.dns.util.ResolveResult;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class DNSServiceDiscovery extends SelfHostMetaServiceDiscovery {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    /**
+     * DNS properties
+     */
+
+    private String addressPrefix;
+    private String addressSuffix;
+    private long pollingCycle;
+    private DNSResolver dnsResolver;
+
+    /**
+     * Polling task ScheduledFuture, used to stop task when destroy
+     */
+    private final ConcurrentHashMap<String, ScheduledFuture<?>> pollingExecutorMap = new ConcurrentHashMap<>();
+
+    /**
+     * Polling check provider ExecutorService
+     */
+    private ScheduledExecutorService pollingExecutorService;
+
+    @Override
+    public void doInitialize(URL registryURL) throws Exception {
+        this.addressPrefix = registryURL.getParameter(DNSClientConst.ADDRESS_PREFIX, "");
+        this.addressSuffix = registryURL.getParameter(DNSClientConst.ADDRESS_SUFFIX, "");
+        this.pollingCycle = registryURL.getParameter(DNSClientConst.DNS_POLLING_CYCLE, DNSClientConst.DEFAULT_DNS_POLLING_CYCLE);
+
+        String nameserver = registryURL.getHost();
+        int port = registryURL.getPort();
+        int maxQueriesPerResolve = registryURL.getParameter(DNSClientConst.MAX_QUERIES_PER_RESOLVE, 10);
+        this.dnsResolver = new DNSResolver(nameserver, port, maxQueriesPerResolve);
+
+
+        int scheduledThreadPoolSize = registryURL.getParameter(DNSClientConst.DNS_POLLING_POOL_SIZE_KEY, DNSClientConst.DEFAULT_DNS_POLLING_POOL_SIZE);
+
+        // polling task may take a lot of time, create a new ScheduledThreadPool
+        pollingExecutorService = Executors.newScheduledThreadPool(scheduledThreadPoolSize, new NamedThreadFactory("Dubbo-DNS-Poll"));
+
+    }
+
+    @Override
+    public void doDestroy() throws Exception {
+        dnsResolver.destroy();
+        pollingExecutorMap.forEach((serviceName, scheduledFuture) -> scheduledFuture.cancel(true));
+        pollingExecutorMap.clear();
+        pollingExecutorService.shutdown();
+    }
+
+    @Override
+    public Set<String> getServices() {
+        // it is impossible for dns to discover service names
+        return Collections.singleton("Unsupported Method");
+    }
+
+    @Override
+    public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
+
+        String serviceAddress = addressPrefix + serviceName + addressSuffix;
+
+        ResolveResult resolveResult = dnsResolver.resolve(serviceAddress);
+
+        return toServiceInstance(serviceName, resolveResult);
+    }
+
+    @Override
+    public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
+        listener.getServiceNames().forEach(serviceName -> {
+            ScheduledFuture<?> scheduledFuture = pollingExecutorService.scheduleAtFixedRate(() -> {
+                        List<ServiceInstance> instances = getInstances(serviceName);
+                        instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
+                        notifyListener(serviceName, listener, instances);
+                    },
+                    pollingCycle, pollingCycle, TimeUnit.MILLISECONDS);
+
+            pollingExecutorMap.put(serviceName, scheduledFuture);
+        });
+    }
+
+    /**
+     * UT used only
+     */
+    @Deprecated
+    public void setDnsResolver(DNSResolver dnsResolver) {
+        this.dnsResolver = dnsResolver;
+    }
+
+    private List<ServiceInstance> toServiceInstance(String serviceName, ResolveResult resolveResult) {
+
+        int port;
+
+        if (resolveResult.getPort().size() > 0) {
+            // use first as default
+            port = resolveResult.getPort().get(0);
+        } else {
+            // not support SRV record
+            port = 20880;
+        }
+
+        List<ServiceInstance> instanceList = new LinkedList<>();
+
+        for (String host : resolveResult.getHostnameList()) {
+            DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, host, port);
+            fillServiceInstance(serviceInstance);
+            instanceList.add(serviceInstance);
+        }
+
+        return instanceList;
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java
similarity index 61%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java
index 06457f6..57eba84 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java
@@ -14,19 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.dns;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
 
+public class DNSServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new DNSServiceDiscovery();
     }
-
 }
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java
new file mode 100644
index 0000000..0fc8ada
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.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.dns.util;
+
+public class DNSClientConst {
+
+    public final static String ADDRESS_PREFIX = "addressPrefix";
+
+    public final static String ADDRESS_SUFFIX = "addressSuffix";
+
+    public final static String MAX_QUERIES_PER_RESOLVE = "maxQueriesPerResolve";
+
+    /**
+     * To decide the frequency of execute DNS poll (in ms)
+     */
+    public final static String DNS_POLLING_CYCLE = "dnsPollingCycle";
+
+    /**
+     * Default value for check frequency: 60000 (ms)
+     */
+    public final static int DEFAULT_DNS_POLLING_CYCLE = 60000;
+
+    /**
+     * To decide how many threads used to execute DNS poll
+     */
+    public final static String DNS_POLLING_POOL_SIZE_KEY = "dnsPollingPoolSize";
+
+    /**
+     * Default value for DNS pool thread: 1
+     */
+    public final static int DEFAULT_DNS_POLLING_POOL_SIZE = 1;
+
+}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java
new file mode 100644
index 0000000..7745873
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.dns.util;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.AddressedEnvelope;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.handler.codec.dns.DefaultDnsQuestion;
+import io.netty.handler.codec.dns.DnsRawRecord;
+import io.netty.handler.codec.dns.DnsRecordType;
+import io.netty.handler.codec.dns.DnsResponse;
+import io.netty.handler.codec.dns.DnsSection;
+import io.netty.resolver.ResolvedAddressTypes;
+import io.netty.resolver.dns.DnsNameResolver;
+import io.netty.resolver.dns.DnsNameResolverBuilder;
+import io.netty.util.concurrent.Future;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static io.netty.resolver.dns.DnsServerAddresses.sequential;
+
+public class DNSResolver {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final DnsNameResolver resolver;
+
+    private static final EventLoopGroup GROUP = new NioEventLoopGroup(1);
+
+    public DNSResolver(String nameserver, int port, int maxQueriesPerResolve) {
+        this.resolver = newResolver(nameserver, port, maxQueriesPerResolve);
+    }
+
+    public ResolveResult resolve(String path) {
+        ResolveResult recordList = new ResolveResult();
+
+        try {
+            Future<List<InetAddress>> hostFuture = resolver.resolveAll(path);
+            Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> srvFuture =
+                    resolver.query(new DefaultDnsQuestion(path, DnsRecordType.SRV));
+
+            try {
+                recordList.getHostnameList()
+                        .addAll(hostFuture
+                                .sync().getNow()
+                                .stream()
+                                .map(InetAddress::getHostAddress)
+                                .collect(Collectors.toList()));
+
+                DnsResponse srvResponse = srvFuture.sync().getNow().content();
+                for (int i = 0; i < srvResponse.count(DnsSection.ANSWER); i++) {
+                    DnsRawRecord record = srvResponse.recordAt(DnsSection.ANSWER, i);
+                    ByteBuf buf = record.content();
+                    // Priority
+                    buf.readUnsignedShort();
+                    // Weight
+                    buf.readUnsignedShort();
+                    // Port
+                    int port = buf.readUnsignedShort();
+                    recordList.getPort().add(port);
+                }
+
+            } catch (InterruptedException e) {
+                logger.warn("Waiting DNS resolve interrupted. " + e.getLocalizedMessage());
+            }
+        } catch (Throwable t) {
+            if (t instanceof UnknownHostException) {
+                if (logger.isInfoEnabled()) {
+                    logger.info(t.getLocalizedMessage());
+                }
+            } else {
+                logger.error(t.getLocalizedMessage());
+            }
+        }
+
+
+        return recordList;
+    }
+
+    public void destroy() {
+        resolver.close();
+    }
+
+    private static DnsNameResolver newResolver(String nameserver, int port, int maxQueriesPerResolve) {
+        return new DnsNameResolverBuilder(GROUP.next())
+                .channelType(NioDatagramChannel.class)
+                .maxQueriesPerResolve(maxQueriesPerResolve)
+                .decodeIdn(true)
+                .optResourceEnabled(false)
+                .ndots(1)
+                .resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED)
+                // ignore cache
+                .ttl(0, 1)
+                .nameServerProvider((hostname) -> sequential(new InetSocketAddress(nameserver, port)).stream())
+                .build();
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java
new file mode 100644
index 0000000..a02b122
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.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.registry.dns.util;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+public class ResolveResult {
+
+    private List<String> hostnameList = new LinkedList<>();
+
+    private List<Integer> port = new LinkedList<>();
+
+    public List<String> getHostnameList() {
+        return hostnameList;
+    }
+
+    public void setHostnameList(List<String> hostnameList) {
+        this.hostnameList = hostnameList;
+    }
+
+    public List<Integer> getPort() {
+        return port;
+    }
+
+    public void setPort(List<Integer> port) {
+        this.port = port;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ResolveResult that = (ResolveResult) o;
+        return Objects.equals(hostnameList, that.hostnameList) &&
+                Objects.equals(port, that.port);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(hostnameList, port);
+    }
+
+    @Override
+    public String toString() {
+        return "ResolveResult{" +
+                "hostnameList=" + hostnameList +
+                ", port=" + port +
+                '}';
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
new file mode 100644
index 0000000..69ca007
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
@@ -0,0 +1 @@
+dns=org.apache.dubbo.registry.dns.DNSRegistryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..3adb9f4
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+dns=org.apache.dubbo.registry.dns.DNSServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..900e78c
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+dns=org.apache.dubbo.registry.dns.DNSServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java
new file mode 100644
index 0000000..cc6610b
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.dns;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ArgumentConfig;
+import org.apache.dubbo.config.MethodConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.registry.Constants;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.dns.util.DNSClientConst;
+import org.apache.dubbo.registry.dns.util.DNSResolver;
+import org.apache.dubbo.registry.dns.util.ResolveResult;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.alibaba.fastjson.JSONObject;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
+import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DNSServiceDiscoveryTest {
+
+    @BeforeEach
+    public void setup() {
+        ApplicationModel.reset();
+        ApplicationConfig applicationConfig = new ApplicationConfig("Test");
+        ApplicationModel.getConfigManager().setApplication(applicationConfig);
+    }
+
+    @AfterEach
+    public void destroy() {
+        ApplicationModel.reset();
+    }
+
+    @Test
+    public void testProvider() throws Exception {
+        ServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
+
+        URL registryURL = URL.valueOf("dns://");
+        dnsServiceDiscovery.initialize(registryURL);
+
+        assertEquals(registryURL, dnsServiceDiscovery.getUrl());
+
+        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
+        serviceInstance.getMetadata().put("a", "b");
+
+        dnsServiceDiscovery.register(serviceInstance);
+
+        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
+        InstanceMetadataChangedListener changeListener = Mockito.mock(InstanceMetadataChangedListener.class);
+
+        String metadataString = metadataService
+                .getAndListenInstanceMetadata("test", changeListener);
+
+        assertEquals(JSONObject.toJSONString(serviceInstance.getMetadata()), metadataString);
+        assertEquals(serviceInstance, dnsServiceDiscovery.getLocalInstance());
+
+        dnsServiceDiscovery.unregister(serviceInstance);
+
+        Mockito.verify(changeListener, Mockito.times(1)).onEvent(Mockito.any());
+
+        metadataService.getInstanceMetadataChangedListenerMap().clear();
+        metadataService.exportInstanceMetadata(null);
+
+        dnsServiceDiscovery.destroy();
+
+    }
+
+    @Test
+    public void testConsumer() throws Exception {
+        DNSServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
+
+        URL registryURL = URL.valueOf("dns://")
+                .addParameter(DNSClientConst.DNS_POLLING_CYCLE, 100)
+                .addParameter(Constants.ECHO_POLLING_CYCLE_KEY, 100);
+        ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
+                .put(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(500));
+        dnsServiceDiscovery.initialize(registryURL);
+
+        WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
+        ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
+        serviceInstance.getMetadata().put("a", "b");
+
+        dnsServiceDiscovery.register(serviceInstance);
+
+        int port = NetUtils.getAvailablePort();
+        ApplicationModel.getApplicationConfig().setMetadataServicePort(port);
+
+        WritableMetadataService spiedMetadataService = Mockito.spy(metadataService);
+
+        ServiceConfig<MetadataService> serviceConfig = exportMockMetadataService(spiedMetadataService, port);
+
+        DNSResolver dnsResolver = Mockito.mock(DNSResolver.class);
+        ResolveResult resolveResult = new ResolveResult();
+        resolveResult.getHostnameList().add("127.0.0.1");
+        Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(resolveResult);
+        dnsServiceDiscovery.setDnsResolver(dnsResolver);
+
+        List<ServiceInstance> serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
+        assertEquals("b", serviceInstances.get(0).getMetadata("a"));
+
+        Set<String> serviceNames = new HashSet<>();
+        serviceNames.add("Test.Service.");
+        ServiceInstancesChangedListener changedListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, null));
+        Mockito.doNothing().when(changedListener).onEvent(Mockito.any());
+
+        serviceInstance.getMetadata().put("a", "c");
+        dnsServiceDiscovery.update(serviceInstance);
+
+        serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
+        assertEquals("c", serviceInstances.get(0).getMetadata("a"));
+
+        dnsServiceDiscovery.addServiceInstancesChangedListener(changedListener);
+        ArgumentCaptor<ServiceInstancesChangedEvent> argument = ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(changedListener, Mockito.timeout(1000)).onEvent(argument.capture());
+        assertEquals("c", argument.getValue().getServiceInstances().get(0).getMetadata("a"));
+
+        Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(new ResolveResult());
+
+        Thread.sleep(1000);
+        assertTrue(dnsServiceDiscovery.getCachedServiceInstances().get("Test.Service.").isEmpty());
+
+        metadataService.exportInstanceMetadata(null);
+        metadataService.getInstanceMetadataChangedListenerMap().clear();
+        serviceConfig.unexport();
+
+        dnsServiceDiscovery.destroy();
+        ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
+                .remove(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(100));
+    }
+
+    private ServiceConfig<MetadataService> exportMockMetadataService(MetadataService metadataService, int port) {
+        ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setProtocol(new ProtocolConfig(DUBBO_PROTOCOL, port));
+        serviceConfig.setRegistry(new RegistryConfig("239.255.255.255", "multicast"));
+        serviceConfig.setInterface(MetadataService.class);
+        serviceConfig.setRef(metadataService);
+        serviceConfig.setGroup("Test.Service.");
+        serviceConfig.setVersion(MetadataService.VERSION);
+        MethodConfig methodConfig = new MethodConfig();
+        methodConfig.setName("getAndListenInstanceMetadata");
+
+        ArgumentConfig argumentConfig = new ArgumentConfig();
+        argumentConfig.setIndex(1);
+        argumentConfig.setCallback(true);
+
+        methodConfig.setArguments(Collections.singletonList(argumentConfig));
+        serviceConfig.setMethods(Collections.singletonList(methodConfig));
+
+        serviceConfig.export();
+
+        return serviceConfig;
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java
similarity index 59%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java
index 06457f6..8624a7f 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java
@@ -14,19 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.dns.util;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+public class DNSResolverTest {
 
-    @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
-    }
+    @Test
+    public void testResolve() {
+        DNSResolver dnsResolver = new DNSResolver("8.8.8.8", 53, 1);
+        ResolveResult resolve = dnsResolver.resolve("aliyun.com");
+        Assertions.assertTrue(resolve.getHostnameList().size() > 0);
 
+        resolve = dnsResolver.resolve("unknowhost.unknowhost.unknowhost");
+        Assertions.assertEquals(0, resolve.getHostnameList().size());
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-kubernetes/pom.xml b/dubbo-registry/dubbo-registry-kubernetes/pom.xml
new file mode 100644
index 0000000..799e45e
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-kubernetes/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo</groupId>
+        <artifactId>dubbo-registry</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>dubbo-registry-kubernetes</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>The Kubernetes registry module of Dubbo project</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-server-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.4.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>3.4.6</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java
new file mode 100644
index 0000000..b221c89
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-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-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
similarity index 61%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
index 06457f6..ab9af54 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java
@@ -14,19 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.kubernetes;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+public class KubernetesRegistryFactory  extends AbstractRegistryFactory {
 
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    protected String createRegistryCacheKey(URL url) {
+        return url.toFullString();
     }
 
+    @Override
+    protected Registry createRegistry(URL url) {
+        return new KubernetesRegistry(url);
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java
new file mode 100644
index 0000000..f75837a
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-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.DefaultServiceInstance;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
+import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
+
+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.Service;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import io.fabric8.kubernetes.client.Watch;
+import io.fabric8.kubernetes.client.Watcher;
+
+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 implements ServiceDiscovery {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private KubernetesClient kubernetesClient;
+
+    private String currentHostname;
+
+    private ServiceInstance localServiceInstance;
+
+    private URL registryURL;
+
+    private String namespace;
+
+    private boolean enableRegister;
+
+    public final static String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
+
+    private final static ConcurrentHashMap<String, Watch> SERVICE_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, Watch> PODS_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, Watch> ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64);
+
+    private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
+        this.kubernetesClient = new DefaultKubernetesClient(config);
+        this.currentHostname = System.getenv("HOSTNAME");
+        this.registryURL = registryURL;
+        this.namespace = config.getNamespace();
+        this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
+
+        boolean availableAccess;
+        try {
+            availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
+        } catch (Throwable e) {
+            availableAccess = false;
+        }
+        if (!availableAccess) {
+            String message = "Unable to access api server. " +
+                    "Please check your url config." +
+                    " Master URL: " + config.getMasterUrl() +
+                    " Hostname: " + currentHostname;
+            logger.error(message);
+        }
+    }
+
+    @Override
+    public void destroy() 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 register(ServiceInstance serviceInstance) throws RuntimeException {
+        localServiceInstance = serviceInstance;
+
+        if (enableRegister) {
+            kubernetesClient
+                    .pods()
+                    .inNamespace(namespace)
+                    .withName(currentHostname)
+                    .edit()
+                    .editOrNewMetadata()
+                    .addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
+                    .endMetadata()
+                    .done();
+            if (logger.isInfoEnabled()) {
+                logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
+                        "Current pod name: " + currentHostname);
+            }
+        }
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        register(serviceInstance);
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        localServiceInstance = null;
+
+        if (enableRegister) {
+            kubernetesClient
+                    .pods()
+                    .inNamespace(namespace)
+                    .withName(currentHostname)
+                    .edit()
+                    .editOrNewMetadata()
+                    .removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
+                    .endMetadata()
+                    .done();
+            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 ServiceInstance getLocalInstance() {
+        return localServiceInstance;
+    }
+
+    @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(KubernetesClientException 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(KubernetesClientException 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(KubernetesClientException 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);
+
+                    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-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java
similarity index 61%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java
index 06457f6..88d50e2 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java
@@ -14,19 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.kubernetes;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+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
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    protected ServiceDiscovery createDiscovery(URL registryURL) {
+        return new KubernetesServiceDiscovery();
     }
-
 }
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java
new file mode 100644
index 0000000..527d2fa
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.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.registry.kubernetes.util;
+
+public class KubernetesClientConst {
+
+    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-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java
new file mode 100644
index 0000000..83b7eb0
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+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(KubernetesClientConst.API_VERSION,
+                        base.getApiVersion()))
+                .withNamespace(url.getParameter(KubernetesClientConst.NAMESPACE,
+                        base.getNamespace()))
+                .withUsername(url.getParameter(KubernetesClientConst.USERNAME,
+                        base.getUsername()))
+                .withPassword(url.getParameter(KubernetesClientConst.PASSWORD,
+                        base.getPassword()))
+
+                .withOauthToken(url.getParameter(KubernetesClientConst.OAUTH_TOKEN,
+                        base.getOauthToken()))
+
+                .withCaCertFile(url.getParameter(KubernetesClientConst.CA_CERT_FILE,
+                        base.getCaCertFile()))
+                .withCaCertData(url.getParameter(KubernetesClientConst.CA_CERT_DATA,
+                        decodeBase64(base.getCaCertData())))
+
+                .withClientKeyFile(url.getParameter(KubernetesClientConst.CLIENT_KEY_FILE,
+                        base.getClientKeyFile()))
+                .withClientKeyData(url.getParameter(KubernetesClientConst.CLIENT_KEY_DATA,
+                        decodeBase64(base.getClientKeyData())))
+
+                .withClientCertFile(url.getParameter(KubernetesClientConst.CLIENT_CERT_FILE,
+                        base.getClientCertFile()))
+                .withClientCertData(url.getParameter(KubernetesClientConst.CLIENT_CERT_DATA,
+                        decodeBase64(base.getClientCertData())))
+
+                .withClientKeyAlgo(url.getParameter(KubernetesClientConst.CLIENT_KEY_ALGO,
+                        base.getClientKeyAlgo()))
+                .withClientKeyPassphrase(url.getParameter(KubernetesClientConst.CLIENT_KEY_PASSPHRASE,
+                        base.getClientKeyPassphrase()))
+
+                .withConnectionTimeout(url.getParameter(KubernetesClientConst.CONNECTION_TIMEOUT,
+                        base.getConnectionTimeout()))
+                .withRequestTimeout(url.getParameter(KubernetesClientConst.REQUEST_TIMEOUT,
+                        base.getRequestTimeout()))
+                .withRollingTimeout(url.getParameter(KubernetesClientConst.ROLLING_TIMEOUT,
+                        base.getRollingTimeout()))
+
+                .withWatchReconnectInterval(url.getParameter(KubernetesClientConst.WATCH_RECONNECT_INTERVAL,
+                        base.getWatchReconnectInterval()))
+                .withWatchReconnectLimit(url.getParameter(KubernetesClientConst.WATCH_RECONNECT_LIMIT,
+                        base.getWatchReconnectLimit()))
+                .withLoggingInterval(url.getParameter(KubernetesClientConst.LOGGING_INTERVAL,
+                        base.getLoggingInterval()))
+
+                .withTrustCerts(url.getParameter(KubernetesClientConst.TRUST_CERTS,
+                        base.isTrustCerts()))
+                .withHttp2Disable(url.getParameter(KubernetesClientConst.HTTP2_DISABLE,
+                        base.isTrustCerts()))
+
+                .withHttpProxy(url.getParameter(KubernetesClientConst.HTTP_PROXY,
+                        base.getHttpProxy()))
+                .withHttpsProxy(url.getParameter(KubernetesClientConst.HTTPS_PROXY,
+                        base.getHttpsProxy()))
+                .withProxyUsername(url.getParameter(KubernetesClientConst.PROXY_USERNAME,
+                        base.getProxyUsername()))
+                .withProxyPassword(url.getParameter(KubernetesClientConst.PROXY_PASSWORD,
+                        base.getProxyPassword()))
+                .withNoProxy(url.getParameter(KubernetesClientConst.NO_PROXY,
+                        base.getNoProxy()))
+                .build();
+    }
+
+    private static String buildMasterUrl(URL url) {
+        return (url.getParameter(KubernetesClientConst.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)) :
+                "";
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory
new file mode 100644
index 0000000..94177d8
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-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-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-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-registry/dubbo-registry-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-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-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-registry/dubbo-registry-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-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java
new file mode 100644
index 0000000..d3d8c9b
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.KubernetesClient;
+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(true, true);
+
+    private KubernetesClient 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.TRUST_CERTS, "true")
+                .addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
+
+        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);
+        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().editFirstSubset()
+                .addNewAddress().withIp("ip2")
+                .withNewTargetRef().withUid("uid2").withName("TestServer").endTargetRef().endAddress()
+                .addNewPort("Test", "Test", 12345, "TCP").endSubset()
+                .done();
+
+        Thread.sleep(5000);
+        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+                ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(mockListener).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);
+        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);
+        serviceDiscovery.update(serviceInstance);
+
+        Thread.sleep(5000);
+        ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
+                ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
+        Mockito.verify(mockListener).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);
+        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-registry/dubbo-registry-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/dubbo-registry/dubbo-registry-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..ca6ee9c
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-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-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java
new file mode 100644
index 0000000..234fdcb
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.multicast;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceInstance;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * TODO: make multicast protocol support Service Discovery
+ */
+public class MulticastServiceDiscovery implements ServiceDiscovery {
+    private URL registryURL;
+    private ServiceInstance serviceInstance;
+
+    @Override
+    public void initialize(URL registryURL) throws Exception {
+        this.registryURL = registryURL;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+
+    }
+
+    @Override
+    public void register(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void update(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = serviceInstance;
+    }
+
+    @Override
+    public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
+        this.serviceInstance = null;
+    }
+
+    @Override
+    public Set<String> getServices() {
+        return Collections.singleton("Unsupported Operation");
+    }
+
+    @Override
+    public URL getUrl() {
+        return registryURL;
+    }
+
+    @Override
+    public ServiceInstance getLocalInstance() {
+        return serviceInstance;
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
similarity index 61%
copy from dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
copy to dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
index 06457f6..7bef1f5 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java
@@ -14,19 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.qos.command.impl;
+package org.apache.dubbo.registry.multicast;
 
-import org.apache.dubbo.config.bootstrap.DubboBootstrap;
-import org.apache.dubbo.qos.command.BaseCommand;
-import org.apache.dubbo.qos.command.CommandContext;
-import org.apache.dubbo.qos.command.annotation.Cmd;
-
-@Cmd(name = "start",summary = "Judge if service has started? ")
-public class Ready implements BaseCommand {
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
+import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
 
+public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
     @Override
-    public String execute(CommandContext commandContext, String[] args) {
-        return DubboBootstrap.getInstance().isReady() ? "true" : "false";
+    public ServiceDiscovery getServiceDiscovery(URL registryURL) {
+        return new MulticastServiceDiscovery();
     }
-
 }
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
new file mode 100644
index 0000000..091b549
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery
@@ -0,0 +1 @@
+multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscovery
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
new file mode 100644
index 0000000..03eb6fa
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory
@@ -0,0 +1 @@
+multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscoveryFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
index 76eece0..f0b9f1e 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -39,7 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 public class MulticastRegistryTest {
 
     private String service = "org.apache.dubbo.test.injvmServie";
-    private URL registryUrl = URL.valueOf("multicast://239.255.255.255/");
+    private URL registryUrl = URL.valueOf("multicast://239.239.239.239/");
     private URL serviceUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/" + service
             + "?methods=test1,test2");
     private URL adminUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/*");
@@ -79,7 +80,7 @@ public class MulticastRegistryTest {
     @Test
     public void testGetCustomPort() {
         int port = NetUtils.getAvailablePort();
-        URL customPortUrl = URL.valueOf("multicast://239.255.255.255:" + port);
+        URL customPortUrl = URL.valueOf("multicast://239.239.239.239:" + port);
         MulticastRegistry multicastRegistry = new MulticastRegistry(customPortUrl);
         assertThat(multicastRegistry.getUrl().getPort(), is(port));
     }
@@ -132,15 +133,20 @@ public class MulticastRegistryTest {
     @Test
     public void testSubscribe() {
         // verify listener
-        registry.subscribe(consumerUrl, new NotifyListener() {
-            @Override
-            public void notify(List<URL> urls) {
-                assertEquals(serviceUrl.toFullString(), urls.get(0).toFullString());
+        final URL[] notifyUrl = new URL[1];
+        for (int i = 0; i < 10; i++) {
+            registry.register(serviceUrl);
+            registry.subscribe(consumerUrl, urls -> {
+                notifyUrl[0] = urls.get(0);
 
                 Map<URL, Set<NotifyListener>> subscribed = registry.getSubscribed();
                 assertEquals(consumerUrl, subscribed.keySet().iterator().next());
+            });
+            if (!EMPTY_PROTOCOL.equalsIgnoreCase(notifyUrl[0].getProtocol())) {
+                break;
             }
-        });
+        }
+        assertEquals(serviceUrl.toFullString(), notifyUrl[0].toFullString());
     }
 
     /**
diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
index 0a30a0a..5f3b680 100644
--- a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
+++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java
@@ -33,6 +33,7 @@ import java.util.Map;
 
 import static java.util.Arrays.asList;
 import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.INSTANCE_REVISION_UPDATED_KEY;
 import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.generateId;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -73,7 +74,7 @@ public class ZookeeperServiceDiscoveryTest {
     }
 
     @Test
-    public void testRegistration() {
+    public void testRegistration() throws InterruptedException {
 
         DefaultServiceInstance serviceInstance = createServiceInstance(SERVICE_NAME, LOCALHOST, NetUtils.getAvailablePort());
 
@@ -87,6 +88,7 @@ public class ZookeeperServiceDiscoveryTest {
         Map<String, String> metadata = new HashMap<>();
         metadata.put("message", "Hello,World");
         serviceInstance.setMetadata(metadata);
+        serviceInstance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true");
 
         discovery.update(serviceInstance);
 
diff --git a/dubbo-registry/pom.xml b/dubbo-registry/pom.xml
index 3f9b247..db3ee35 100644
--- a/dubbo-registry/pom.xml
+++ b/dubbo-registry/pom.xml
@@ -35,5 +35,7 @@
         <module>dubbo-registry-zookeeper</module>
         <module>dubbo-registry-nacos</module>
         <module>dubbo-registry-multiple</module>
+        <module>dubbo-registry-kubernetes</module>
+        <module>dubbo-registry-dns</module>
     </modules>
 </project>
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java
index 7e88e7c..eda4b60 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java
@@ -20,6 +20,7 @@ package org.apache.dubbo.remoting.exchange.support.header;
 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.NetUtils;
 import org.apache.dubbo.remoting.Channel;
 import org.apache.dubbo.remoting.Constants;
 import org.apache.dubbo.remoting.RemotingException;
@@ -35,6 +36,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 
 public class HeartbeatHandlerTest {
 
@@ -61,9 +63,13 @@ public class HeartbeatHandlerTest {
 
     @Test
     public void testServerHeartbeat() throws Exception {
-        URL serverURL = URL.valueOf("header://localhost:55555?transporter=netty3");
-        serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
-        TestHeartbeatHandler handler = new TestHeartbeatHandler();
+        URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56780))
+                .addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
+                .addParameter(Constants.TRANSPORTER_KEY, "netty3")
+                .addParameter(Constants.HEARTBEAT_KEY, 1000);
+        CountDownLatch connect = new CountDownLatch(1);
+        CountDownLatch disconnect = new CountDownLatch(1);
+        TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
         server = Exchangers.bind(serverURL, handler);
         System.out.println("Server bind successfully");
 
@@ -75,21 +81,25 @@ public class HeartbeatHandlerTest {
         serverURL = serverURL.addParameter(Constants.RECONNECT_KEY, false);
 
         client = Exchangers.connect(serverURL);
-        Thread.sleep(10000);
+        disconnect.await();
         Assertions.assertTrue(handler.disconnectCount > 0);
         System.out.println("disconnect count " + handler.disconnectCount);
     }
 
     @Test
     public void testHeartbeat() throws Exception {
-        URL serverURL = URL.valueOf("header://localhost:55556?transporter=netty3");
-        serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
-        TestHeartbeatHandler handler = new TestHeartbeatHandler();
+        URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56785))
+                .addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
+                .addParameter(Constants.TRANSPORTER_KEY, "netty3")
+                .addParameter(Constants.HEARTBEAT_KEY, 1000);
+        CountDownLatch connect = new CountDownLatch(1);
+        CountDownLatch disconnect = new CountDownLatch(1);
+        TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
         server = Exchangers.bind(serverURL, handler);
         System.out.println("Server bind successfully");
 
         client = Exchangers.connect(serverURL);
-        Thread.sleep(10000);
+        connect.await();
         System.err.println("++++++++++++++ disconnect count " + handler.disconnectCount);
         System.err.println("++++++++++++++ connect count " + handler.connectCount);
         Assertions.assertEquals(0, handler.disconnectCount);
@@ -99,15 +109,19 @@ public class HeartbeatHandlerTest {
     @Test
     public void testClientHeartbeat() throws Exception {
         FakeChannelHandlers.setTestingChannelHandlers();
-        URL serverURL = URL.valueOf("header://localhost:55557?transporter=netty3");
-        TestHeartbeatHandler handler = new TestHeartbeatHandler();
+        URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56790))
+                .addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
+                .addParameter(Constants.TRANSPORTER_KEY, "netty3");
+        CountDownLatch connect = new CountDownLatch(1);
+        CountDownLatch disconnect = new CountDownLatch(1);
+        TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
         server = Exchangers.bind(serverURL, handler);
         System.out.println("Server bind successfully");
 
         FakeChannelHandlers.resetChannelHandlers();
         serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
         client = Exchangers.connect(serverURL);
-        Thread.sleep(10000);
+        connect.await();
         Assertions.assertTrue(handler.connectCount > 0);
         System.out.println("connect count " + handler.connectCount);
     }
@@ -116,6 +130,13 @@ public class HeartbeatHandlerTest {
 
         public int disconnectCount = 0;
         public int connectCount = 0;
+        private CountDownLatch connectCountDownLatch;
+        private CountDownLatch disconnectCountDownLatch;
+
+        public TestHeartbeatHandler(CountDownLatch connectCountDownLatch, CountDownLatch disconnectCountDownLatch) {
+            this.connectCountDownLatch = connectCountDownLatch;
+            this.disconnectCountDownLatch = disconnectCountDownLatch;
+        }
 
         public CompletableFuture<Object> reply(ExchangeChannel channel, Object request) throws RemotingException {
             return CompletableFuture.completedFuture(request);
@@ -124,11 +145,13 @@ public class HeartbeatHandlerTest {
         @Override
         public void connected(Channel channel) throws RemotingException {
             ++connectCount;
+            connectCountDownLatch.countDown();
         }
 
         @Override
         public void disconnected(Channel channel) throws RemotingException {
             ++disconnectCount;
+            disconnectCountDownLatch.countDown();
         }
 
         @Override
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java
index 65fa6a0..a75a429 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java
@@ -47,7 +47,7 @@ public class ClientReconnectTest {
             Client client = startClient(port, 200);
             Assertions.assertFalse(client.isConnected());
             RemotingServer server = startServer(port);
-            for (int i = 0; i < 100 && !client.isConnected(); i++) {
+            for (int i = 0; i < 1000 && !client.isConnected(); i++) {
                 Thread.sleep(10);
             }
             Assertions.assertTrue(client.isConnected());
@@ -70,7 +70,8 @@ public class ClientReconnectTest {
 
 
     public Client startClient(int port, int heartbeat) throws RemotingException {
-        final String url = "exchange://127.0.0.1:" + port + "/client.reconnect.test?check=false&client=netty3&" + Constants.HEARTBEAT_KEY + "=" + heartbeat;
+        final String url = "exchange://127.0.0.1:" + port + "/client.reconnect.test?check=false&client=netty3&" +
+                Constants.HEARTBEAT_KEY + "=" + heartbeat;
         return Exchangers.connect(url);
     }
 
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
index 1228fe0..001b56a 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java
@@ -44,8 +44,8 @@ public class ThreadNameTest {
     @BeforeEach
     public void before() throws Exception {
         int port = NetUtils.getAvailablePort();
-        serverURL = URL.valueOf("netty://localhost?side=provider").setPort(port);
-        clientURL = URL.valueOf("netty://localhost?side=consumer").setPort(port);
+        serverURL = URL.valueOf("telnet://localhost?side=provider").setPort(port);
+        clientURL = URL.valueOf("telnet://localhost?side=consumer").setPort(port);
 
         serverHandler = new ThreadNameVerifyHandler(serverRegex, false);
         clientHandler = new ThreadNameVerifyHandler(clientRegex, true);
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java
index 74e263e..0b13de1 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java
@@ -35,7 +35,7 @@ public class NettyTransporterTest {
     @Test
     public void shouldAbleToBindNetty4() throws Exception {
         int port = NetUtils.getAvailablePort();
-        URL url = new URL("http", "localhost", port,
+        URL url = new URL("telnet", "localhost", port,
                 new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)});
 
         RemotingServer server = new NettyTransporter().bind(url, new ChannelHandlerAdapter());
@@ -48,7 +48,7 @@ public class NettyTransporterTest {
         final CountDownLatch lock = new CountDownLatch(1);
 
         int port = NetUtils.getAvailablePort();
-        URL url = new URL("http", "localhost", port,
+        URL url = new URL("telnet", "localhost", port,
                 new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)});
 
         new NettyTransporter().bind(url, new ChannelHandlerAdapter() {
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java
index 3ed2aca..37940e6 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.remoting.exchange.ExchangeChannel;
 import org.apache.dubbo.remoting.exchange.ExchangeServer;
 import org.apache.dubbo.remoting.exchange.Exchangers;
 import org.apache.dubbo.remoting.exchange.support.ReplierDispatcher;
+
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -31,7 +32,11 @@ import org.junit.jupiter.api.Test;
 
 import java.io.Serializable;
 import java.util.Random;
-import java.util.concurrent.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -54,13 +59,13 @@ public class ReplierDispatcherTest {
         ReplierDispatcher dispatcher = new ReplierDispatcher();
         dispatcher.addReplier(RpcMessage.class, new RpcMessageHandler());
         dispatcher.addReplier(Data.class, (channel, msg) -> new StringMessage("hello world"));
-        exchangeServer = Exchangers.bind(URL.valueOf("dubbo://localhost:" + port), dispatcher);
+        exchangeServer = Exchangers.bind(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=60000"), dispatcher);
     }
 
 
     @Test
     public void testDataPackage() throws Exception {
-        ExchangeChannel client = Exchangers.connect(URL.valueOf("dubbo://localhost:" + port));
+        ExchangeChannel client = Exchangers.connect(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=60000"));
         Random random = new Random();
         for (int i = 5; i < 100; i++) {
             StringBuilder sb = new StringBuilder();
@@ -91,7 +96,7 @@ public class ReplierDispatcherTest {
     }
 
     void clientExchangeInfo(int port) throws Exception {
-        ExchangeChannel client = Exchangers.connect(URL.valueOf("dubbo://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=5000"));
+        ExchangeChannel client = Exchangers.connect(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=5000"));
         clients.put(Thread.currentThread().getName(), client);
         MockResult result = (MockResult) client.request(new RpcMessage(DemoService.class.getName(), "plus", new Class<?>[]{int.class, int.class}, new Object[]{55, 25})).get();
         Assertions.assertEquals(result.getResult(), 80);
diff --git a/pom.xml b/pom.xml
index e2c22aa..0fdcc22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -581,6 +581,8 @@
                                 </exclude>
                                 <exclude>.github/**</exclude>
                                 <exclude>compiler/**</exclude>
+                                <!-- exclude mockito extensions spi files -->
+                                <exclude>**/mockito-extensions/*</exclude>
                             </excludes>
                         </configuration>
                     </execution>