diff --git a/.gitignore b/.gitignore
index 568e9f24541dd6f02dd8670436fd48db481b7f21..f7622f8ac9cc1ae42ea5203df70c5327f09bf300 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,15 +20,13 @@ classes
 
 # go mod, go test
 vendor/
-coverage.txt
-
 logs/
 .vscode/
-coverage.txt
 
 # unit test
 remoting/zookeeper/zookeeper-4unittest/
 config_center/zookeeper/zookeeper-4unittest/
 registry/zookeeper/zookeeper-4unittest/
+metadata/report/zookeeper/zookeeper-4unittest/
 registry/consul/agent*
 config_center/apollo/mockDubbog.properties.json
diff --git a/CHANGE.md b/CHANGE.md
index 00b074d284971d779c84792951262879098fc18b..3e48db1d3bfe3f0222dab7a222519aff70dbc27f 100644
--- a/CHANGE.md
+++ b/CHANGE.md
@@ -1,6 +1,55 @@
 # Release Notes
 ---
 
+## 1.5.0
+
+### New Features
+- [Application-Level Registry Model](https://github.com/apache/dubbo-go/pull/604)
+    - [DelegateMetadataReport & RemoteMetadataService](https://github.com/apache/dubbo-go/pull/505)
+    - [Nacos MetadataReport implementation](https://github.com/apache/dubbo-go/pull/522)
+    - [Nacos service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go)
+    - [Zk metadata service](https://github.com/apache/dubbo-go/pull/633)
+    - [Zk service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/zookeeper/service_discovery.go)
+    - [Etcd metadata report](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/metadata/report/etcd/report.go)
+    - [Etcd metadata service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/etcdv3/service_discovery.go)
+- [Support grpc json protocol](https://github.com/apache/dubbo-go/pull/582)
+
+### Enhancement
+- [Optimize err handling ](https://github.com/apache/dubbo-go/pull/536/)
+- [Add attribute method into Invocation and RpcInvocation](https://github.com/apache/dubbo-go/pull/537)
+- [Optimize lock for zookeeper registry](https://github.com/apache/dubbo-go/pull/578)
+- [Improve code coverage of zookeeper config center](https://github.com/apache/dubbo-go/pull/549)
+- [Improve code coverage of nacos config center and configuration parser](https://github.com/apache/dubbo-go/pull/587)
+- [Kubernetes as registry enhance](https://github.com/apache/dubbo-go/pull/577)
+- [Optimize zk client's lock and tests](https://github.com/apache/dubbo-go/pull/601)
+- [Add setInvoker method for invocation](https://github.com/apache/dubbo-go/pull/612)
+- [Upgrade getty & hessian2](https://github.com/apache/dubbo-go/pull/626)
+- [Optimize router design: Extract priority router](https://github.com/apache/dubbo-go/pull/630)
+- [NamespaceId config for nacos](https://github.com/apache/dubbo-go/pull/641)
+
+
+### Bugfixes
+- [Fix Gitee problem](https://github.com/apache/dubbo-go/pull/590)
+- [Gitee quality analyses -- common](https://github.com/apache/dubbo-go/issues/616)
+- [Nacos client logDir path seperator for Windows](https://github.com/apache/dubbo-go/pull/591)
+- [Fix various linter warnings](https://github.com/apache/dubbo-go/pull/624)
+- [Fixed some issues in config folder that reported by sonar-qube](https://github.com/apache/dubbo-go/pull/634)
+- [Zk disconnected, dubbo-go panic when subscribe](https://github.com/apache/dubbo-go/pull/613)
+- [Enhancement cluster code analysis](https://github.com/apache/dubbo-go/pull/632)
+
+### Document & Comment
+- [Add comment for common directory](https://github.com/apache/dubbo-go/pull/530)
+- [Add comments for config_center](https://github.com/apache/dubbo-go/pull/545)
+- [Update the comments in metrics](https://github.com/apache/dubbo-go/pull/547)
+- [Add comments for config](https://github.com/apache/dubbo-go/pull/579)
+- [Updated the dubbo-go-ext image](https://github.com/apache/dubbo-go/pull/581)
+- [Add comment for cluster](https://github.com/apache/dubbo-go/pull/584)
+- [Update the comments in filter directory](https://github.com/apache/dubbo-go/pull/586)
+- [Add comment for metadata](https://github.com/apache/dubbo-go/pull/588)
+- [Update the comments in protocol directory](https://github.com/apache/dubbo-go/pull/602)
+- [Add comments for remoting](https://github.com/apache/dubbo-go/pull/605)
+- [Update the comments in registy directory](https://github.com/apache/dubbo-go/pull/589)
+
 ## 1.4.0
 ### New Features
 
diff --git a/before_ut.bat b/before_ut.bat
index dc51008dadaad21af6fcb6021863ff4102b0afa2..5d2b9e4682d0ac24efe4ae68be8d977933db103f 100644
--- a/before_ut.bat
+++ b/before_ut.bat
@@ -34,4 +34,7 @@ md cluster\router\chain\zookeeper-4unittest\contrib\fatjar
 xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/"
 
 md cluster\router\condition\zookeeper-4unittest\contrib\fatjar
-xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/"
\ No newline at end of file
+xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/"
+
+md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar
+xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/"
\ No newline at end of file
diff --git a/before_ut.sh b/before_ut.sh
index 7ee92e57a26cbdbb1d1a0b3e792726ad5e1954f8..210e9e723ba9e2118cf642729359808b78fddb8d 100755
--- a/before_ut.sh
+++ b/before_ut.sh
@@ -25,13 +25,16 @@ if [ ! -f "${zkJar}" ]; then
 fi
 
 mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar
-cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/
+cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar
 
 mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar
-cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar/
+cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar
 
 mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar
 cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar
 
 mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar
-cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar
\ No newline at end of file
+cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar
+
+mkdir -p metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar
+cp ${zkJar} metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar
\ No newline at end of file
diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go
index 7865b807ad4bedfd045aadffbfdbf2f19186e293..8a7e0c0e8359c69faf0e504adeb1778c38a08e04 100644
--- a/cluster/directory/base_directory.go
+++ b/cluster/directory/base_directory.go
@@ -22,7 +22,6 @@ import (
 )
 
 import (
-	"github.com/dubbogo/gost/container/set"
 	"go.uber.org/atomic"
 )
 
@@ -35,8 +34,6 @@ import (
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-var routerURLSet = gxset.NewSet()
-
 // BaseDirectory Abstract implementation of Directory: Invoker list returned from this Directory's list method have been filtered by Routers
 type BaseDirectory struct {
 	url       *common.URL
@@ -120,14 +117,3 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) {
 func (dir *BaseDirectory) IsAvailable() bool {
 	return !dir.destroyed.Load()
 }
-
-// GetRouterURLSet Return router URL
-func GetRouterURLSet() *gxset.HashSet {
-	return routerURLSet
-}
-
-// AddRouterURLSet Add router URL
-// Router URL will init in config/config_loader.go
-func AddRouterURLSet(url *common.URL) {
-	routerURLSet.Add(url)
-}
diff --git a/common/constant/default.go b/common/constant/default.go
index c69989b4fbc3e95cb42c7f5e403989b9cff9215b..c1c404e089ea90899d2b599b01cd5980c3e92ab1 100644
--- a/common/constant/default.go
+++ b/common/constant/default.go
@@ -76,3 +76,12 @@ const (
 const (
 	COMMA_SPLIT_PATTERN = "\\s*[,]+\\s*"
 )
+
+const (
+	SIMPLE_METADATA_SERVICE_NAME = "MetadataService"
+	DEFAULT_REVIESION            = "N/A"
+)
+
+const (
+	SERVICE_DISCOVERY_DEFAULT_GROUP = "DEFAULT_GROUP"
+)
diff --git a/common/constant/key.go b/common/constant/key.go
index 8123d66557157a0cd3a267df36ee8a9d56df1aab..cd23dd0f1ad3e000fe54c251d9563bcf12ba86c7 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -22,10 +22,12 @@ const (
 )
 
 const (
+	PORT_KEY               = "port"
 	GROUP_KEY              = "group"
 	VERSION_KEY            = "version"
 	INTERFACE_KEY          = "interface"
 	PATH_KEY               = "path"
+	PROTOCOL_KEY           = "protocol"
 	SERVICE_KEY            = "service"
 	METHODS_KEY            = "methods"
 	TIMEOUT_KEY            = "timeout"
@@ -40,6 +42,7 @@ const (
 	TOKEN_KEY              = "token"
 	LOCAL_ADDR             = "local-addr"
 	REMOTE_ADDR            = "remote-addr"
+	PATH_SEPARATOR         = "/"
 	DUBBO_KEY              = "dubbo"
 	RELEASE_KEY            = "release"
 	ANYHOST_KEY            = "anyhost"
@@ -77,6 +80,11 @@ const (
 	EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler"
 	PROVIDER_SHUTDOWN_FILTER               = "pshutdown"
 	CONSUMER_SHUTDOWN_FILTER               = "cshutdown"
+	PID_KEY                                = "pid"
+	SYNC_REPORT_KEY                        = "sync.report"
+	RETRY_PERIOD_KEY                       = "retry.period"
+	RETRY_TIMES_KEY                        = "retry.times"
+	CYCLE_REPORT_KEY                       = "cycle.report"
 )
 
 const (
@@ -155,6 +163,14 @@ const (
 	NACOS_USERNAME               = "username"
 )
 
+const (
+	ZOOKEEPER_KEY = "zookeeper"
+)
+
+const (
+	ETCDV3_KEY = "etcdv3"
+)
+
 const (
 	TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx"
 )
@@ -230,7 +246,6 @@ const (
 	KEY_SEPARATOR      = ":"
 	DEFAULT_PATH_TAG   = "metadata"
 	KEY_REVISON_PREFIX = "revision"
-	PATH_SEPARATOR     = "/"
 
 	// metadata service
 	METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService"
@@ -259,7 +274,19 @@ const (
 )
 
 // service discovery
-
 const (
-	NACOS_GROUP = "nacos.group"
+	SUBSCRIBED_SERVICE_NAMES_KEY               = "subscribed-services"
+	PROVIDER_BY                                = "provided-by"
+	EXPORTED_SERVICES_REVISION_PROPERTY_NAME   = "dubbo.exported-services.revision"
+	SUBSCRIBED_SERVICES_REVISION_PROPERTY_NAME = "dubbo.subscribed-services.revision"
+	SERVICE_INSTANCE_SELECTOR                  = "service-instance-selector"
+	METADATA_STORAGE_TYPE_PROPERTY_NAME        = "dubbo.metadata.storage-type"
+	DEFAULT_METADATA_STORAGE_TYPE              = "local"
+	SERVICE_INSTANCE_ENDPOINTS                 = "dubbo.endpoints"
+	METADATA_SERVICE_PREFIX                    = "dubbo.metadata-service."
+	METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME  = METADATA_SERVICE_PREFIX + "url-params"
+	METADATA_SERVICE_URLS_PROPERTY_NAME        = METADATA_SERVICE_PREFIX + "urls"
+
+	// SERVICE_DISCOVERY_KEY indicate which service discovery instance will be used
+	SERVICE_DISCOVERY_KEY = "service_discovery"
 )
diff --git a/common/extension/event_dispatcher.go b/common/extension/event_dispatcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..f0503e05422844e129a81212beed6af414612b6b
--- /dev/null
+++ b/common/extension/event_dispatcher.go
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+var (
+	globalEventDispatcher observer.EventDispatcher
+	initEventListeners    []func() observer.EventListener
+	initEventOnce         sync.Once
+)
+
+var (
+	dispatchers = make(map[string]func() observer.EventDispatcher, 8)
+)
+
+// SetEventDispatcher, actually, it doesn't really init the global dispatcher
+func SetEventDispatcher(name string, v func() observer.EventDispatcher) {
+	dispatchers[name] = v
+}
+
+// SetAndInitGlobalDispatcher will actually init the global dispatcher
+// if there is already a global dispatcher,
+// it will be override
+// if the dispatcher with the name not found, it will panic
+func SetAndInitGlobalDispatcher(name string) {
+	if len(name) == 0 {
+		name = "direct"
+	}
+	if globalEventDispatcher != nil {
+		logger.Warnf("EventDispatcher has been initialized. It will be replaced")
+	}
+
+	if dp, ok := dispatchers[name]; !ok || dp == nil {
+		panic("EventDispatcher for " + name + " is not found, make sure you have import the package, " +
+			"like import _ github.com/apache/dubbo-go/common/observer/dispatcher ")
+	}
+	globalEventDispatcher = dispatchers[name]()
+}
+
+// GetGlobalDispatcher will init all listener and then return dispatcher
+func GetGlobalDispatcher() observer.EventDispatcher {
+	initEventOnce.Do(func() {
+		// we should delay to add the listeners to avoid some listeners left
+		for _, l := range initEventListeners {
+			globalEventDispatcher.AddEventListener(l())
+		}
+	})
+	return globalEventDispatcher
+}
+
+// AddEventListener it will be added in global event dispatcher
+func AddEventListener(creator func() observer.EventListener) {
+	initEventListeners = append(initEventListeners, creator)
+}
diff --git a/common/extension/event_dispatcher_test.go b/common/extension/event_dispatcher_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..472360cea5a04c2cd70f0df6ea4db23f6be88f1a
--- /dev/null
+++ b/common/extension/event_dispatcher_test.go
@@ -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 extension
+
+import (
+	"reflect"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+import (
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+func TestSetAndInitGlobalDispatcher(t *testing.T) {
+	mock := &mockEventDispatcher{}
+	SetEventDispatcher("mock", func() observer.EventDispatcher {
+		return mock
+	})
+
+	SetAndInitGlobalDispatcher("mock")
+	dispatcher := GetGlobalDispatcher()
+	assert.NotNil(t, dispatcher)
+	assert.Equal(t, mock, dispatcher)
+
+	mock1 := &mockEventDispatcher{}
+
+	SetEventDispatcher("mock1", func() observer.EventDispatcher {
+		return mock1
+	})
+
+	SetAndInitGlobalDispatcher("mock1")
+	dispatcher = GetGlobalDispatcher()
+	assert.NotNil(t, dispatcher)
+	assert.Equal(t, mock1, dispatcher)
+}
+
+func TestAddEventListener(t *testing.T) {
+	AddEventListener(func() observer.EventListener {
+		return &mockEventListener{}
+	})
+
+	AddEventListener(func() observer.EventListener {
+		return &mockEventListener{}
+	})
+
+	assert.Equal(t, 2, len(initEventListeners))
+}
+
+type mockEventListener struct {
+}
+
+func (m mockEventListener) GetPriority() int {
+	panic("implement me")
+}
+
+func (m mockEventListener) OnEvent(e observer.Event) error {
+	panic("implement me")
+}
+
+func (m mockEventListener) GetEventType() reflect.Type {
+	panic("implement me")
+}
+
+type mockEventDispatcher struct {
+}
+
+func (m mockEventDispatcher) AddEventListener(listener observer.EventListener) {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) RemoveEventListener(listener observer.EventListener) {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) GetAllEventListeners() []observer.EventListener {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) RemoveAllEventListeners() {
+	panic("implement me")
+}
+
+func (m mockEventDispatcher) Dispatch(event observer.Event) {
+	panic("implement me")
+}
diff --git a/common/extension/metadata_report_factory.go b/common/extension/metadata_report_factory.go
index c55f8617fadd9d09d68547b05341d127716ce73c..593318d01beec1f89d8194c6d4bd18a15c798a0e 100644
--- a/common/extension/metadata_report_factory.go
+++ b/common/extension/metadata_report_factory.go
@@ -18,20 +18,20 @@
 package extension
 
 import (
-	"github.com/apache/dubbo-go/metadata"
+	"github.com/apache/dubbo-go/metadata/report/factory"
 )
 
 var (
-	metaDataReportFactories = make(map[string]func() metadata.MetadataReportFactory, 8)
+	metaDataReportFactories = make(map[string]func() factory.MetadataReportFactory, 8)
 )
 
 // SetMetadataReportFactory sets the MetadataReportFactory with @name
-func SetMetadataReportFactory(name string, v func() metadata.MetadataReportFactory) {
+func SetMetadataReportFactory(name string, v func() factory.MetadataReportFactory) {
 	metaDataReportFactories[name] = v
 }
 
 // GetMetadataReportFactory finds the MetadataReportFactory with @name
-func GetMetadataReportFactory(name string) metadata.MetadataReportFactory {
+func GetMetadataReportFactory(name string) factory.MetadataReportFactory {
 	if metaDataReportFactories[name] == nil {
 		panic("metadata report for " + name + " is not existing, make sure you have import the package.")
 	}
diff --git a/common/extension/metadata_service.go b/common/extension/metadata_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..1823273b8f7f86b2d96abf990359e14b569abddb
--- /dev/null
+++ b/common/extension/metadata_service.go
@@ -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 extension
+
+import (
+	"fmt"
+)
+
+import (
+	"github.com/apache/dubbo-go/metadata/service"
+)
+
+var (
+	// there will be two types: local or remote
+	metadataServiceInsMap = make(map[string]func() (service.MetadataService, error), 2)
+)
+
+// SetMetadataService will store the msType => creator pair
+func SetMetadataService(msType string, creator func() (service.MetadataService, error)) {
+	metadataServiceInsMap[msType] = creator
+}
+
+// GetMetadataService will create a MetadataService instance
+// it will panic if msType not found
+func GetMetadataService(msType string) (service.MetadataService, error) {
+	if creator, ok := metadataServiceInsMap[msType]; ok {
+		return creator()
+	}
+	panic(fmt.Sprintf("could not find the metadata service creator for metadataType: %s, please check whether you have imported relative packages, \n"+
+		"local - github.com/apache/dubbo-go/metadata/service/inmemory, \n"+
+		"remote - github.com/apache/dubbo-go/metadata/service/remote", msType))
+}
diff --git a/common/extension/metadata_service_proxy_factory.go b/common/extension/metadata_service_proxy_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b88d37c9a5145ddca81930cd76ccac1a7184318
--- /dev/null
+++ b/common/extension/metadata_service_proxy_factory.go
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	"fmt"
+)
+
+import (
+	"github.com/apache/dubbo-go/metadata/service"
+)
+
+var (
+	metadataServiceProxyFactoryMap = make(map[string]func() service.MetadataServiceProxyFactory, 2)
+)
+
+type MetadataServiceProxyFactoryFunc func() service.MetadataServiceProxyFactory
+
+// SetMetadataServiceProxyFactory store the name-creator pair
+func SetMetadataServiceProxyFactory(name string, creator MetadataServiceProxyFactoryFunc) {
+	metadataServiceProxyFactoryMap[name] = creator
+}
+
+// GetMetadataServiceProxyFactory will create an instance.
+// it will panic if the factory with name not found
+func GetMetadataServiceProxyFactory(name string) service.MetadataServiceProxyFactory {
+	if f, ok := metadataServiceProxyFactoryMap[name]; ok {
+		return f()
+	}
+	panic(fmt.Sprintf("could not find the metadata service factory creator for name: %s, please check whether you have imported relative packages, \n"+
+		"local - github.com/apache/dubbo-go/metadata/service/inmemory, \n"+
+		"remote - github.com/apache/dubbo-go/metadata/service/remote", name))
+}
diff --git a/common/extension/service_discovery.go b/common/extension/service_discovery.go
index 1d891c2189e55fbd6fa03e4c6e5ea6ec5b654e46..0227920dc64a7b5f6ad1939fcccbb7384c43f68d 100644
--- a/common/extension/service_discovery.go
+++ b/common/extension/service_discovery.go
@@ -21,25 +21,28 @@ import (
 	perrors "github.com/pkg/errors"
 )
 import (
-	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/registry"
 )
 
 var (
-	discoveryCreatorMap = make(map[string]func(url *common.URL) (registry.ServiceDiscovery, error), 4)
+	discoveryCreatorMap = make(map[string]func(name string) (registry.ServiceDiscovery, error), 4)
 )
 
 // SetServiceDiscovery will store the @creator and @name
-func SetServiceDiscovery(name string, creator func(_ *common.URL) (registry.ServiceDiscovery, error)) {
-	discoveryCreatorMap[name] = creator
+// protocol indicate the implementation, like nacos
+// the name like nacos-1...
+func SetServiceDiscovery(protocol string, creator func(name string) (registry.ServiceDiscovery, error)) {
+	discoveryCreatorMap[protocol] = creator
 }
 
 // GetServiceDiscovery will return the registry.ServiceDiscovery
+// protocol indicate the implementation, like nacos
+// the name like nacos-1...
 // if not found, or initialize instance failed, it will return error.
-func GetServiceDiscovery(name string, url *common.URL) (registry.ServiceDiscovery, error) {
-	creator, ok := discoveryCreatorMap[name]
+func GetServiceDiscovery(protocol string, name string) (registry.ServiceDiscovery, error) {
+	creator, ok := discoveryCreatorMap[protocol]
 	if !ok {
-		return nil, perrors.New("Could not find the service discovery with name: " + name)
+		return nil, perrors.New("Could not find the service discovery with discovery protocol: " + protocol)
 	}
-	return creator(url)
+	return creator(name)
 }
diff --git a/common/extension/service_instance_customizer.go b/common/extension/service_instance_customizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ebb3e40f5851ad4799e7cee5966edd77cea2f9e
--- /dev/null
+++ b/common/extension/service_instance_customizer.go
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	"sort"
+)
+
+import (
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	customizers = make([]registry.ServiceInstanceCustomizer, 0, 8)
+)
+
+// AddCustomizers will put the customizer into slices and then sort them;
+// this method will be invoked several time, so we sort them here.
+func AddCustomizers(cus registry.ServiceInstanceCustomizer) {
+	customizers = append(customizers, cus)
+	sort.Stable(customizerSlice(customizers))
+}
+
+// GetCustomizers will return the sorted customizer
+// the result won't be nil
+func GetCustomizers() []registry.ServiceInstanceCustomizer {
+	return customizers
+}
+
+type customizerSlice []registry.ServiceInstanceCustomizer
+
+// nolint
+func (c customizerSlice) Len() int {
+	return len(c)
+}
+
+// nolint
+func (c customizerSlice) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
+
+// nolint
+func (c customizerSlice) Less(i, j int) bool { return c[i].GetPriority() < c[j].GetPriority() }
diff --git a/common/extension/service_instance_selector_factory.go b/common/extension/service_instance_selector_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..66d3e7646e6c26639901fe0a9c8994233aa7c567
--- /dev/null
+++ b/common/extension/service_instance_selector_factory.go
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/registry/servicediscovery/instance"
+)
+
+var (
+	serviceInstanceSelectorMappings = make(map[string]func() instance.ServiceInstanceSelector, 2)
+)
+
+// nolint
+func SetServiceInstanceSelector(name string, f func() instance.ServiceInstanceSelector) {
+	serviceInstanceSelectorMappings[name] = f
+}
+
+// GetServiceInstanceSelector will create an instance
+// it will panic if selector with the @name not found
+func GetServiceInstanceSelector(name string) (instance.ServiceInstanceSelector, error) {
+	serviceInstanceSelector, ok := serviceInstanceSelectorMappings[name]
+	if !ok {
+		return nil, perrors.New("Could not find service instance selector with" +
+			"name:" + name)
+	}
+	return serviceInstanceSelector(), nil
+}
diff --git a/common/extension/service_name_mapping.go b/common/extension/service_name_mapping.go
new file mode 100644
index 0000000000000000000000000000000000000000..99fd4c25e93d6f3d085a8bcab50482535e8c9019
--- /dev/null
+++ b/common/extension/service_name_mapping.go
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	"github.com/apache/dubbo-go/metadata/mapping"
+)
+
+type ServiceNameMappingCreator func() mapping.ServiceNameMapping
+
+var (
+	globalNameMappingCreator ServiceNameMappingCreator
+)
+
+func SetGlobalServiceNameMapping(nameMappingCreator ServiceNameMappingCreator) {
+	globalNameMappingCreator = nameMappingCreator
+}
+
+func GetGlobalServiceNameMapping() mapping.ServiceNameMapping {
+	return globalNameMappingCreator()
+}
diff --git a/common/observer/dispatcher/direct_event_dispatcher.go b/common/observer/dispatcher/direct_event_dispatcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..a2d334ce2ca9461a8e14e28bb0f3ecf21971751f
--- /dev/null
+++ b/common/observer/dispatcher/direct_event_dispatcher.go
@@ -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 dispatcher
+
+import (
+	"reflect"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+func init() {
+	extension.SetEventDispatcher("direct", NewDirectEventDispatcher)
+}
+
+// DirectEventDispatcher is align with DirectEventDispatcher interface in Java.
+// it's the top abstraction
+// Align with 2.7.5
+// Dispatcher event to listener direct
+type DirectEventDispatcher struct {
+	observer.BaseListener
+}
+
+// NewDirectEventDispatcher ac constructor of DirectEventDispatcher
+func NewDirectEventDispatcher() observer.EventDispatcher {
+	return &DirectEventDispatcher{
+		BaseListener: observer.NewBaseListener(),
+	}
+}
+
+// Dispatch event directly
+// it lookup the listener by event's type.
+// if listener not found, it just return and do nothing
+func (ded *DirectEventDispatcher) Dispatch(event observer.Event) {
+	if event == nil {
+		logger.Warnf("[DirectEventDispatcher] dispatch event nil")
+		return
+	}
+	eventType := reflect.TypeOf(event).Elem()
+	ded.Mutex.RLock()
+	defer ded.Mutex.RUnlock()
+	listenersSlice, loaded := ded.ListenersCache[eventType]
+	if !loaded {
+		return
+	}
+	for _, listener := range listenersSlice {
+		if err := listener.OnEvent(event); err != nil {
+			logger.Warnf("[DirectEventDispatcher] dispatch event error:%v", err)
+		}
+	}
+}
diff --git a/common/observer/dispatcher/direct_event_dispatcher_test.go b/common/observer/dispatcher/direct_event_dispatcher_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..12facbb9c4bd1b33fa071a50beae87b77e1be968
--- /dev/null
+++ b/common/observer/dispatcher/direct_event_dispatcher_test.go
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dispatcher
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+func TestDirectEventDispatcher_Dispatch(t *testing.T) {
+	ded := NewDirectEventDispatcher()
+	ded.AddEventListener(&TestEventListener{
+		BaseListener: observer.NewBaseListener(),
+	})
+	ded.AddEventListener(&TestEventListener1{})
+	ded.Dispatch(&TestEvent{})
+	ded.Dispatch(nil)
+}
+
+type TestEvent struct {
+	observer.BaseEvent
+}
+
+type TestEventListener struct {
+	observer.BaseListener
+	observer.EventListener
+}
+
+func (tel *TestEventListener) OnEvent(e observer.Event) error {
+	fmt.Println("TestEventListener")
+	return nil
+}
+
+func (tel *TestEventListener) GetPriority() int {
+	return -1
+}
+
+func (tel *TestEventListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(&TestEvent{})
+}
+
+type TestEventListener1 struct {
+	observer.EventListener
+}
+
+func (tel *TestEventListener1) OnEvent(e observer.Event) error {
+	fmt.Println("TestEventListener1")
+	return nil
+}
+
+func (tel *TestEventListener1) GetPriority() int {
+	return 1
+}
+
+func (tel *TestEventListener1) GetEventType() reflect.Type {
+	return reflect.TypeOf(TestEvent{})
+}
diff --git a/common/observer/dispatcher/mock_event_dispatcher.go b/common/observer/dispatcher/mock_event_dispatcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..45cdaa71a257ff78293e1a1bdc15232c36a9aa26
--- /dev/null
+++ b/common/observer/dispatcher/mock_event_dispatcher.go
@@ -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 dispatcher
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+// MockEventDispatcher will do nothing.
+// It is only used by tests
+// Now the implementation doing nothing,
+// But you can modify this if needed
+type MockEventDispatcher struct {
+}
+
+// AddEventListener do nothing
+func (m MockEventDispatcher) AddEventListener(listener observer.EventListener) {
+}
+
+// AddEventListeners do nothing
+func (m MockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) {
+}
+
+// RemoveEventListener do nothing
+func (m MockEventDispatcher) RemoveEventListener(listener observer.EventListener) {
+}
+
+// RemoveEventListeners do nothing
+func (m MockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) {
+}
+
+// GetAllEventListeners return empty list
+func (m MockEventDispatcher) GetAllEventListeners() []observer.EventListener {
+	return make([]observer.EventListener, 0)
+}
+
+// RemoveAllEventListeners do nothing
+func (m MockEventDispatcher) RemoveAllEventListeners() {
+}
+
+// Dispatch do nothing
+func (m MockEventDispatcher) Dispatch(event observer.Event) {
+}
diff --git a/common/observer/event.go b/common/observer/event.go
new file mode 100644
index 0000000000000000000000000000000000000000..209a50c78a36c13d789f6f9fbc81a73a5d9a535f
--- /dev/null
+++ b/common/observer/event.go
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package observer
+
+import (
+	"fmt"
+	"math/rand"
+	"time"
+)
+
+func init() {
+	rand.Seed(time.Now().UnixNano())
+}
+
+// Event is align with Event interface in Java.
+// it's the top abstraction
+// Align with 2.7.5
+type Event interface {
+	fmt.Stringer
+	GetSource() interface{}
+	GetTimestamp() time.Time
+}
+
+// BaseEvent is the base implementation of Event
+// You should never use it directly
+type BaseEvent struct {
+	Source    interface{}
+	Timestamp time.Time
+}
+
+// GetSource return the source
+func (b *BaseEvent) GetSource() interface{} {
+	return b.Source
+}
+
+// GetTimestamp return the Timestamp when the event is created
+func (b *BaseEvent) GetTimestamp() time.Time {
+	return b.Timestamp
+}
+
+// String return a human readable string representing this event
+func (b *BaseEvent) String() string {
+	return fmt.Sprintf("BaseEvent[source = %#v]", b.Source)
+}
+
+// NewBaseEvent create an BaseEvent instance
+// and the Timestamp will be current timestamp
+func NewBaseEvent(source interface{}) *BaseEvent {
+	return &BaseEvent{
+		Source:    source,
+		Timestamp: time.Now(),
+	}
+}
diff --git a/common/observer/event_dispatcher.go b/common/observer/event_dispatcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..17745e68c07065f954d456914fb276fadf6d975d
--- /dev/null
+++ b/common/observer/event_dispatcher.go
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package observer
+
+// EventDispatcher is align with EventDispatcher interface in Java.
+// it's the top abstraction
+// Align with 2.7.5
+type EventDispatcher interface {
+	Listenable
+	// Dispatch event
+	Dispatch(event Event)
+}
diff --git a/common/observer/event_listener.go b/common/observer/event_listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..faa8705a4292b1441b9605852017256a9305f624
--- /dev/null
+++ b/common/observer/event_listener.go
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package observer
+
+import (
+	"reflect"
+)
+
+import (
+	gxsort "github.com/dubbogo/gost/sort"
+)
+
+// EventListener is an new interface used to align with dubbo 2.7.5
+// It contains the Prioritized means that the listener has its priority
+// Usually the priority of your custom implementation should be between [100, 9000]
+// the number outside the range will be though as system reserve number
+// usually implementation should be singleton
+type EventListener interface {
+	gxsort.Prioritizer
+	// OnEvent handle this event
+	OnEvent(e Event) error
+	// GetEventType listen which event type
+	GetEventType() reflect.Type
+}
+
+// ConditionalEventListener only handle the event which it can handle
+type ConditionalEventListener interface {
+	EventListener
+	// Accept will make the decision whether it should handle this event
+	Accept(e Event) bool
+}
+
+type ChangedNotify interface {
+	Notify(e Event)
+}
diff --git a/common/observer/listenable.go b/common/observer/listenable.go
new file mode 100644
index 0000000000000000000000000000000000000000..bf6ae7219bfd3bd981cf5cd2c87a57c57d7df5d8
--- /dev/null
+++ b/common/observer/listenable.go
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package observer
+
+import (
+	"reflect"
+	"sort"
+	"sync"
+)
+
+// Listenable could add and remove the event listener
+type Listenable interface {
+	AddEventListener(listener EventListener)
+	AddEventListeners(listenersSlice []EventListener)
+	RemoveEventListener(listener EventListener)
+	RemoveEventListeners(listenersSlice []EventListener)
+	GetAllEventListeners() []EventListener
+	RemoveAllEventListeners()
+}
+
+// BaseListener base listenable
+type BaseListener struct {
+	Listenable
+	ListenersCache map[reflect.Type][]EventListener
+	Mutex          sync.RWMutex
+}
+
+// NewBaseListener a constructor of base listenable
+func NewBaseListener() BaseListener {
+	return BaseListener{
+		ListenersCache: make(map[reflect.Type][]EventListener, 8),
+	}
+}
+
+// AddEventListener add event listener
+func (bl *BaseListener) AddEventListener(listener EventListener) {
+	eventType := listener.GetEventType()
+	if eventType.Kind() == reflect.Ptr {
+		eventType = eventType.Elem()
+	}
+	bl.Mutex.Lock()
+	defer bl.Mutex.Unlock()
+	listenersSlice, loaded := bl.ListenersCache[eventType]
+	if !loaded {
+		listenersSlice = make([]EventListener, 0, 8)
+	}
+	// return if listenersSlice already has this listener
+	if loaded && containListener(listenersSlice, listener) {
+		return
+	}
+	listenersSlice = append(listenersSlice, listener)
+	sort.Slice(listenersSlice, func(i, j int) bool {
+		return listenersSlice[i].GetPriority() < listenersSlice[j].GetPriority()
+	})
+	bl.ListenersCache[eventType] = listenersSlice
+}
+
+// AddEventListeners add the slice of event listener
+func (bl *BaseListener) AddEventListeners(listenersSlice []EventListener) {
+	for _, listener := range listenersSlice {
+		bl.AddEventListener(listener)
+	}
+}
+
+// RemoveEventListener remove the event listener
+func (bl *BaseListener) RemoveEventListener(listener EventListener) {
+	eventType := listener.GetEventType()
+	if eventType.Kind() == reflect.Ptr {
+		eventType = eventType.Elem()
+	}
+	bl.Mutex.Lock()
+	defer bl.Mutex.Unlock()
+	listenersSlice, loaded := bl.ListenersCache[eventType]
+	if !loaded {
+		return
+	}
+	for i, l := range listenersSlice {
+		if l == listener {
+			listenersSlice = append(listenersSlice[:i], listenersSlice[i+1:]...)
+		}
+	}
+	bl.ListenersCache[eventType] = listenersSlice
+}
+
+// RemoveEventListeners remove the slice of event listener
+// it will iterate all listener and remove it one by one
+func (bl *BaseListener) RemoveEventListeners(listenersSlice []EventListener) {
+	for _, listener := range listenersSlice {
+		bl.RemoveEventListener(listener)
+	}
+}
+
+// RemoveAllEventListeners remove all
+// using Lock
+func (bl *BaseListener) RemoveAllEventListeners() {
+	bl.Mutex.Lock()
+	defer bl.Mutex.Unlock()
+	bl.ListenersCache = make(map[reflect.Type][]EventListener, 8)
+}
+
+// GetAllEventListeners get all listener
+// using RLock
+func (bl *BaseListener) GetAllEventListeners() []EventListener {
+	allListenersSlice := make([]EventListener, 0, 16)
+
+	bl.Mutex.RLock()
+	defer bl.Mutex.RUnlock()
+	for _, listenersSlice := range bl.ListenersCache {
+		allListenersSlice = append(allListenersSlice, listenersSlice...)
+	}
+	sort.Slice(allListenersSlice, func(i, j int) bool {
+		return allListenersSlice[i].GetPriority() < allListenersSlice[j].GetPriority()
+	})
+	return allListenersSlice
+}
+
+// containListener true if contain listener
+// it's not thread safe
+// usually it should be use in lock scope
+func containListener(listenersSlice []EventListener, listener EventListener) bool {
+	for _, loadListener := range listenersSlice {
+		if loadListener == listener {
+			return true
+		}
+	}
+	return false
+}
diff --git a/common/observer/listenable_test.go b/common/observer/listenable_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5a03382a937fe925b6e17b495d066b86c8d2161d
--- /dev/null
+++ b/common/observer/listenable_test.go
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package observer
+
+import (
+	"reflect"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestListenable(t *testing.T) {
+	el := &TestEventListener{}
+	bl := NewBaseListener()
+	b := &bl
+	b.AddEventListener(el)
+	b.AddEventListener(el)
+	al := b.GetAllEventListeners()
+	assert.Equal(t, len(al), 1)
+	assert.Equal(t, al[0].GetEventType(), reflect.TypeOf(TestEvent{}))
+	b.RemoveEventListener(el)
+	assert.Equal(t, len(b.GetAllEventListeners()), 0)
+	var ts []EventListener
+	ts = append(ts, el)
+	b.AddEventListeners(ts)
+	assert.Equal(t, len(al), 1)
+
+}
+
+type TestEvent struct {
+	BaseEvent
+}
+
+type TestEventListener struct {
+	EventListener
+}
+
+func (tel *TestEventListener) OnEvent(e Event) error {
+	return nil
+}
+
+func (tel *TestEventListener) GetPriority() int {
+	return -1
+}
+
+func (tel *TestEventListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(TestEvent{})
+}
diff --git a/common/rpc_service.go b/common/rpc_service.go
index f8b8e0761e86a3038d90e4cf457ac49df66bc92a..9ef2b956aa955f4fc79c6f75bd060ccfee2d02ca 100644
--- a/common/rpc_service.go
+++ b/common/rpc_service.go
@@ -131,6 +131,11 @@ func (s *Service) Method() map[string]*MethodType {
 	return s.methods
 }
 
+// Name will return service name
+func (s *Service) Name() string {
+	return s.name
+}
+
 // RcvrType gets @s.rcvrType.
 func (s *Service) RcvrType() reflect.Type {
 	return s.rcvrType
@@ -337,6 +342,16 @@ func suiteMethod(method reflect.Method) *MethodType {
 		argsType           []reflect.Type
 	)
 
+	// this method is in RPCService
+	// we force users must implement RPCService interface in their provider
+	// and RPCService has only one method "Reference"
+	// In general, this method should not be exported to client
+	// so we ignore this method
+	// see RPCService
+	if mname == "Reference" {
+		return nil
+	}
+
 	if outNum != 1 && outNum != 2 {
 		logger.Warnf("method %s of mtype %v has wrong number of in out parameters %d; needs exactly 1/2",
 			mname, mtype.String(), outNum)
diff --git a/common/url.go b/common/url.go
index d5b9792f537fd92e60beff72e6c2fa294e64b483..807d0ed5eff4ecb70d3adeb8524b841d0ec92a58 100644
--- a/common/url.go
+++ b/common/url.go
@@ -36,6 +36,7 @@ import (
 
 import (
 	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
 )
 
 // ///////////////////////////////
@@ -178,10 +179,12 @@ func WithToken(token string) option {
 		if len(token) > 0 {
 			value := token
 			if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" {
-				UUID, err := uuid.NewV4()
-				if err == nil {
-					value = UUID.String()
+				u, err := uuid.NewV4()
+				if err != nil {
+					logger.Errorf("could not generator UUID: %v", err)
+					return
 				}
+				value = u.String()
 			}
 			url.SetParam(constant.TOKEN_KEY, value)
 		}
@@ -654,3 +657,22 @@ func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []f
 	}
 	return methodConfigMergeFcn
 }
+
+// URLSlice will be used to sort URL instance
+// Instances will be order by URL.String()
+type URLSlice []URL
+
+// nolint
+func (s URLSlice) Len() int {
+	return len(s)
+}
+
+// nolint
+func (s URLSlice) Less(i, j int) bool {
+	return s[i].String() < s[j].String()
+}
+
+// nolint
+func (s URLSlice) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
diff --git a/config/application_config.go b/config/application_config.go
index 16e841792c7bffaa6f847d73c336c6a43431bc49..ef99664fa298c28365ed7acc54d0c18a88c9b5c2 100644
--- a/config/application_config.go
+++ b/config/application_config.go
@@ -33,7 +33,8 @@ type ApplicationConfig struct {
 	Version      string `yaml:"version" json:"version,omitempty" property:"version"`
 	Owner        string `yaml:"owner" json:"owner,omitempty" property:"owner"`
 	Environment  string `yaml:"environment" json:"environment,omitempty" property:"environment"`
-	MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"` //field for metadata report
+	// the metadata type. remote or local
+	MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"`
 }
 
 // nolint
diff --git a/config/base_config.go b/config/base_config.go
index fd749a934b8344a5ed00343f1b18f10bc41fd820..0ba5bc7ef98cb30a13890b93a659c467adcbf73b 100644
--- a/config/base_config.go
+++ b/config/base_config.go
@@ -43,13 +43,33 @@ type multiConfiger interface {
 // BaseConfig is the common configuration for provider and consumer
 type BaseConfig struct {
 	ConfigCenterConfig *ConfigCenterConfig `yaml:"config_center" json:"config_center,omitempty"`
-	configCenterUrl    *common.URL
-	prefix             string
-	fatherConfig       interface{}
 
-	MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"`
+	// since 1.5.0 version
+	Remotes              map[string]*RemoteConfig           `yaml:"remote" json:"remote,omitempty"`
+	ServiceDiscoveries   map[string]*ServiceDiscoveryConfig `yaml:"service_discovery" json:"service_discovery,omitempty"`
+	MetadataReportConfig *MetadataReportConfig              `yaml:"metadata_report" json:"metadata_report,omitempty" property:"metadata_report"`
 
-	fileStream *bytes.Buffer
+	// application config
+	ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"`
+
+	configCenterUrl     *common.URL
+	prefix              string
+	fatherConfig        interface{}
+	EventDispatcherType string        `default:"direct" yaml:"event_dispatcher_type" json:"event_dispatcher_type,omitempty"`
+	MetricConfig        *MetricConfig `yaml:"metrics" json:"metrics,omitempty"`
+	fileStream          *bytes.Buffer
+}
+
+// nolint
+func (c *BaseConfig) GetServiceDiscoveries(name string) (config *ServiceDiscoveryConfig, ok bool) {
+	config, ok = c.ServiceDiscoveries[name]
+	return
+}
+
+// GetRemoteConfig will return the remote's config with the name if found
+func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool) {
+	config, ok = c.Remotes[name]
+	return
 }
 
 // startConfigCenter will start the config center.
@@ -64,7 +84,7 @@ func (c *BaseConfig) startConfigCenter() error {
 	if c.prepareEnvironment() != nil {
 		return perrors.WithMessagef(err, "start config center error!")
 	}
-	//c.fresh()
+	// c.fresh()
 	return err
 }
 
@@ -101,14 +121,14 @@ func (c *BaseConfig) prepareEnvironment() error {
 			return perrors.WithStack(err)
 		}
 	}
-	//global config file
+	// global config file
 	mapContent, err := dynamicConfig.Parser().Parse(content)
 	if err != nil {
 		return perrors.WithStack(err)
 	}
 	config.GetEnvInstance().UpdateExternalConfigMap(mapContent)
 
-	//appGroup config file
+	// appGroup config file
 	if len(appContent) != 0 {
 		appMapConent, err := dynamicConfig.Parser().Parse(appContent)
 		if err != nil {
@@ -263,7 +283,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 				if f.Kind() == reflect.Map {
 
 					if f.Type().Elem().Kind() == reflect.Ptr {
-						//initiate config
+						// initiate config
 						s := reflect.New(f.Type().Elem().Elem())
 						prefix := s.MethodByName("Prefix").Call(nil)[0].String()
 						for _, pfx := range strings.Split(prefix, "|") {
@@ -278,7 +298,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 
 					}
 
-					//iter := f.MapRange()
+					// iter := f.MapRange()
 
 					for _, k := range f.MapKeys() {
 						v := f.MapIndex(k)
@@ -313,7 +333,7 @@ func (c *BaseConfig) fresh() {
 }
 
 func (c *BaseConfig) freshInternalConfig(config *config.InmemoryConfiguration) {
-	//reflect to init struct
+	// reflect to init struct
 	tp := reflect.ValueOf(c.fatherConfig).Elem().Type()
 	initializeStruct(tp, reflect.ValueOf(c.fatherConfig).Elem())
 
diff --git a/config/base_config_test.go b/config/base_config_test.go
index ca2875fb95e9bdabcc430edc15bd4a56c440bc45..15b468753ddfd99e77b3d99c0994c0599c649793 100644
--- a/config/base_config_test.go
+++ b/config/base_config_test.go
@@ -116,10 +116,12 @@ func TestRefresh(t *testing.T) {
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 
 	father := &ConsumerConfig{
-		Check:             &[]bool{true}[0],
-		ApplicationConfig: baseAppConfig,
-		Registries:        baseRegistries,
-		References:        baseMockRef,
+		Check: &[]bool{true}[0],
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 		ShutdownConfig: &ShutdownConfig{
 			Timeout:              "12s",
 			StepTimeout:          "2s",
@@ -148,10 +150,12 @@ func TestAppExternalRefresh(t *testing.T) {
 	mockMap["dubbo.consumer.check"] = "true"
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 	father := &ConsumerConfig{
-		Check:             &[]bool{true}[0],
-		ApplicationConfig: baseAppConfig,
-		Registries:        baseRegistries,
-		References:        baseMockRef,
+		Check: &[]bool{true}[0],
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -174,10 +178,12 @@ func TestAppExternalWithoutIDRefresh(t *testing.T) {
 	mockMap["dubbo.consumer.check"] = "true"
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 	father := &ConsumerConfig{
-		Check:             &[]bool{true}[0],
-		ApplicationConfig: baseAppConfig,
-		Registries:        baseRegistries,
-		References:        baseMockRef,
+		Check: &[]bool{true}[0],
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -202,11 +208,13 @@ func TestRefreshSingleRegistry(t *testing.T) {
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 
 	father := &ConsumerConfig{
-		Check:             &[]bool{true}[0],
-		ApplicationConfig: baseAppConfig,
-		Registries:        map[string]*RegistryConfig{},
-		Registry:          &RegistryConfig{},
-		References:        baseMockRef,
+		Check: &[]bool{true}[0],
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
+		Registries: map[string]*RegistryConfig{},
+		Registry:   &RegistryConfig{},
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -231,8 +239,10 @@ func TestRefreshProvider(t *testing.T) {
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 
 	father := &ProviderConfig{
-		ApplicationConfig: baseAppConfig,
-		Registries:        baseRegistries,
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
+		Registries: baseRegistries,
 		Services: map[string]*ServiceConfig{
 			"MockService": {
 				InterfaceName: "com.MockService",
@@ -299,7 +309,7 @@ func TestInitializeStruct(t *testing.T) {
 	reflect.ValueOf(testConsumerConfig).Elem().Set(v.Elem())
 
 	assert.Condition(t, func() (success bool) {
-		return testConsumerConfig.ApplicationConfig != nil
+		return testConsumerConfig.Registry != nil
 	})
 	assert.Condition(t, func() (success bool) {
 		return testConsumerConfig.Registries != nil
diff --git a/config/config_loader.go b/config/config_loader.go
index f8f83533d4796cc6df3e2d4c2d77f42be56e5933..d5f8c68c1bf35c40c09d7d15bae4b6b9f161e9e7 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"sync"
 	"time"
 )
 
@@ -33,15 +34,21 @@ import (
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/common/logger"
+	_ "github.com/apache/dubbo-go/common/observer/dispatcher"
 )
 
 var (
-	consumerConfig    *ConsumerConfig
-	providerConfig    *ProviderConfig
-	metricConfig      *MetricConfig
-	applicationConfig *ApplicationConfig
-	maxWait           = 3
-	confRouterFile    string
+	consumerConfig *ConsumerConfig
+	providerConfig *ProviderConfig
+	// baseConfig = providerConfig.BaseConfig or consumerConfig
+	baseConfig *BaseConfig
+
+	// configAccessMutex is used to make sure that xxxxConfig will only be created once if needed.
+	// it should be used combine with double-check to avoid the race condition
+	configAccessMutex sync.Mutex
+
+	maxWait        = 3
+	confRouterFile string
 )
 
 // loaded consumer & provider config from xxx.yml, and log config from xxx.xml
@@ -59,11 +66,19 @@ func init() {
 	if errCon := ConsumerInit(confConFile); errCon != nil {
 		log.Printf("[consumerInit] %#v", errCon)
 		consumerConfig = nil
+	} else {
+		// Even though baseConfig has been initialized, we override it
+		// because we think read from config file is correct config
+		baseConfig = &consumerConfig.BaseConfig
 	}
 
 	if errPro := ProviderInit(confProFile); errPro != nil {
 		log.Printf("[providerInit] %#v", errPro)
 		providerConfig = nil
+	} else {
+		// Even though baseConfig has been initialized, we override it
+		// because we think read from config file is correct config
+		baseConfig = &providerConfig.BaseConfig
 	}
 }
 
@@ -99,9 +114,6 @@ func loadConsumerConfig() {
 		}
 	}
 
-	metricConfig = consumerConfig.MetricConfig
-	applicationConfig = consumerConfig.ApplicationConfig
-
 	checkApplicationName(consumerConfig.ApplicationConfig)
 	if err := configCenterRefreshConsumer(); err != nil {
 		logger.Errorf("[consumer config center refresh] %#v", err)
@@ -122,14 +134,14 @@ func loadConsumerConfig() {
 		ref.Implement(rpcService)
 	}
 
-	//wait for invoker is available, if wait over default 3s, then panic
+	// wait for invoker is available, if wait over default 3s, then panic
 	var count int
 	checkok := true
 	for {
 		for _, refconfig := range consumerConfig.References {
 			if (refconfig.Check != nil && *refconfig.Check) ||
 				(refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) ||
-				(refconfig.Check == nil && consumerConfig.Check == nil) { //default to true
+				(refconfig.Check == nil && consumerConfig.Check == nil) { // default to true
 
 				if refconfig.invoker != nil &&
 					!refconfig.invoker.IsAvailable() {
@@ -174,15 +186,12 @@ func loadProviderConfig() {
 		}
 	}
 
-	// so, you should know that the consumer's config will be override
-	metricConfig = providerConfig.MetricConfig
-	applicationConfig = providerConfig.ApplicationConfig
-
 	checkApplicationName(providerConfig.ApplicationConfig)
 	if err := configCenterRefreshProvider(); err != nil {
 		logger.Errorf("[provider config center refresh] %#v", err)
 	}
 	checkRegistries(providerConfig.Registries, providerConfig.Registry)
+
 	for key, svs := range providerConfig.Services {
 		rpcService := GetProviderService(key)
 		if rpcService == nil {
@@ -191,6 +200,7 @@ func loadProviderConfig() {
 		}
 		svs.id = key
 		svs.Implement(rpcService)
+		svs.Protocols = providerConfig.Protocols
 		if err := svs.Export(); err != nil {
 			panic(fmt.Sprintf("service %s export failed! err: %#v", key, err))
 		}
@@ -211,6 +221,15 @@ func Load() {
 	// init router
 	initRouter()
 
+	// init the global event dispatcher
+	extension.SetAndInitGlobalDispatcher(GetBaseConfig().EventDispatcherType)
+
+	// start the metadata report if config set
+	if err := startMetadataReport(GetApplicationConfig().MetadataType, GetBaseConfig().MetadataReportConfig); err != nil {
+		logger.Errorf("Provider starts metadata report error, and the error is {%#v}", err)
+		return
+	}
+
 	// reference config
 	loadConsumerConfig()
 
@@ -233,39 +252,79 @@ func RPCService(service common.RPCService) {
 
 // GetMetricConfig find the MetricConfig
 // if it is nil, create a new one
+// we use double-check to reduce race condition
+// In general, it will be locked 0 or 1 time.
+// So you don't need to worry about the race condition
 func GetMetricConfig() *MetricConfig {
-	if metricConfig == nil {
-		metricConfig = &MetricConfig{}
+	if GetBaseConfig().MetricConfig == nil {
+		configAccessMutex.Lock()
+		defer configAccessMutex.Unlock()
+		if GetBaseConfig().MetricConfig == nil {
+			GetBaseConfig().MetricConfig = &MetricConfig{}
+		}
 	}
-	return metricConfig
+	return GetBaseConfig().MetricConfig
 }
 
 // GetApplicationConfig find the application config
 // if not, we will create one
 // Usually applicationConfig will be initialized when system start
+// we use double-check to reduce race condition
+// In general, it will be locked 0 or 1 time.
+// So you don't need to worry about the race condition
 func GetApplicationConfig() *ApplicationConfig {
-	if applicationConfig == nil {
-		applicationConfig = &ApplicationConfig{}
+	if GetBaseConfig().ApplicationConfig == nil {
+		configAccessMutex.Lock()
+		defer configAccessMutex.Unlock()
+		if GetBaseConfig().ApplicationConfig == nil {
+			GetBaseConfig().ApplicationConfig = &ApplicationConfig{}
+		}
 	}
-	return applicationConfig
+	return GetBaseConfig().ApplicationConfig
 }
 
 // GetProviderConfig find the provider config
 // if not found, create new one
 func GetProviderConfig() ProviderConfig {
 	if providerConfig == nil {
-		logger.Warnf("providerConfig is nil!")
-		return ProviderConfig{}
+		if providerConfig == nil {
+			return ProviderConfig{}
+		}
 	}
 	return *providerConfig
 }
 
 // GetConsumerConfig find the consumer config
 // if not found, create new one
+// we use double-check to reduce race condition
+// In general, it will be locked 0 or 1 time.
+// So you don't need to worry about the race condition
 func GetConsumerConfig() ConsumerConfig {
 	if consumerConfig == nil {
-		logger.Warnf("consumerConfig is nil!")
-		return ConsumerConfig{}
+		if consumerConfig == nil {
+			return ConsumerConfig{}
+		}
 	}
 	return *consumerConfig
 }
+
+func GetBaseConfig() *BaseConfig {
+	if baseConfig == nil {
+		configAccessMutex.Lock()
+		defer configAccessMutex.Unlock()
+		if baseConfig == nil {
+			baseConfig = &BaseConfig{
+				MetricConfig:       &MetricConfig{},
+				ConfigCenterConfig: &ConfigCenterConfig{},
+				Remotes:            make(map[string]*RemoteConfig, 0),
+				ApplicationConfig:  &ApplicationConfig{},
+				ServiceDiscoveries: make(map[string]*ServiceDiscoveryConfig, 0),
+			}
+		}
+	}
+	return baseConfig
+}
+
+func IsProvider() bool {
+	return providerConfig != nil
+}
diff --git a/config/config_loader_test.go b/config/config_loader_test.go
index a21a4998aae9eb6e0bd0632c7248cc39e7bbd9fa..2cbf526a70b52f184f500bad98edb01b97d239ec 100644
--- a/config/config_loader_test.go
+++ b/config/config_loader_test.go
@@ -24,6 +24,7 @@ import (
 
 import (
 	"github.com/stretchr/testify/assert"
+	"go.uber.org/atomic"
 )
 
 import (
@@ -93,7 +94,7 @@ func TestLoad(t *testing.T) {
 
 func TestLoadWithSingleReg(t *testing.T) {
 	doInitConsumerWithSingleRegistry()
-	doInitProviderWithSingleRegistry()
+	mockInitProviderWithSingleRegistry()
 
 	ms := &MockService{}
 	SetConsumerService(ms)
@@ -236,3 +237,66 @@ func TestConfigLoaderWithConfigCenterSingleRegistry(t *testing.T) {
 	assert.Equal(t, "mock://127.0.0.1:2182", consumerConfig.Registries[constant.DEFAULT_KEY].Address)
 
 }
+
+func TestGetBaseConfig(t *testing.T) {
+	bc := GetBaseConfig()
+	assert.NotNil(t, bc)
+	_, found := bc.GetRemoteConfig("mock")
+	assert.False(t, found)
+}
+
+// mockInitProviderWithSingleRegistry will init a mocked providerConfig
+func mockInitProviderWithSingleRegistry() {
+	providerConfig = &ProviderConfig{
+		BaseConfig: BaseConfig{
+			ApplicationConfig: &ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "1.0.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
+		},
+
+		Registry: &RegistryConfig{
+			Address:  "mock://127.0.0.1:2181",
+			Username: "user1",
+			Password: "pwd1",
+		},
+		Registries: map[string]*RegistryConfig{},
+
+		Services: map[string]*ServiceConfig{
+			"MockService": {
+				InterfaceName: "com.MockService",
+				Protocol:      "mock",
+				Cluster:       "failover",
+				Loadbalance:   "random",
+				Retries:       "3",
+				Group:         "huadong_idc",
+				Version:       "1.0.0",
+				Methods: []*MethodConfig{
+					{
+						Name:        "GetUser",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+					{
+						Name:        "GetUser1",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+				},
+				exported: new(atomic.Bool),
+			},
+		},
+		Protocols: map[string]*ProtocolConfig{
+			"mock": {
+				Name: "mock",
+				Ip:   "127.0.0.1",
+				Port: "20000",
+			},
+		},
+	}
+}
diff --git a/config/consumer_config.go b/config/consumer_config.go
index 1453628ab5e24a3607183c349d44e9440868ab60..f8b671bf3cf8304d57211f2f8b7c7c35f2aa6b9e 100644
--- a/config/consumer_config.go
+++ b/config/consumer_config.go
@@ -42,20 +42,18 @@ import (
 type ConsumerConfig struct {
 	BaseConfig `yaml:",inline"`
 	Filter     string `yaml:"filter" json:"filter,omitempty" property:"filter"`
-	// application
-	ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"`
-
 	// client
 	Connect_Timeout string `default:"100ms"  yaml:"connect_timeout" json:"connect_timeout,omitempty" property:"connect_timeout"`
 	ConnectTimeout  time.Duration
 
+	Registry   *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
+	Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
+
 	Request_Timeout string `yaml:"request_timeout" default:"5s" json:"request_timeout,omitempty" property:"request_timeout"`
 	RequestTimeout  time.Duration
 	ProxyFactory    string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
 	Check           *bool  `yaml:"check"  json:"check,omitempty" property:"check"`
 
-	Registry       *RegistryConfig             `yaml:"registry" json:"registry,omitempty" property:"registry"`
-	Registries     map[string]*RegistryConfig  `yaml:"registries" json:"registries,omitempty" property:"registries"`
 	References     map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"`
 	ProtocolConf   interface{}                 `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"`
 	FilterConf     interface{}                 `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
diff --git a/config/instance/metadata_report.go b/config/instance/metadata_report.go
index 4c935d733283e6a79523dd0f35be60f092351dbb..8e833dd70bcc0db8e65cd8703f2bc1859432a887 100644
--- a/config/instance/metadata_report.go
+++ b/config/instance/metadata_report.go
@@ -24,18 +24,35 @@ import (
 import (
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/extension"
-	"github.com/apache/dubbo-go/metadata"
+	"github.com/apache/dubbo-go/metadata/report"
 )
 
 var (
-	instance metadata.MetadataReport
-	once     sync.Once
+	instance  report.MetadataReport
+	reportUrl common.URL
+	once      sync.Once
 )
 
-// GetMetadataReportInstance gets metadata report instance by @url
-func GetMetadataReportInstance(url *common.URL) metadata.MetadataReport {
+// GetMetadataReportInstance will return the instance in lazy mode. Be careful the instance create will only
+// execute once.
+func GetMetadataReportInstance(selectiveUrl ...*common.URL) report.MetadataReport {
 	once.Do(func() {
-		instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url)
+		var url *common.URL
+		if len(selectiveUrl) > 0 {
+			url = selectiveUrl[0]
+			instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url)
+			reportUrl = *url
+		}
 	})
 	return instance
 }
+
+// GetMetadataReportUrl will return the report instance url
+func GetMetadataReportUrl() common.URL {
+	return reportUrl
+}
+
+// SetMetadataReportUrl will only can be used by unit test to mock url
+func SetMetadataReportUrl(url common.URL) {
+	reportUrl = url
+}
diff --git a/config/instance/metadata_report_test.go b/config/instance/metadata_report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d489af048055b362f2fa68a8963dd2cdf9632945
--- /dev/null
+++ b/config/instance/metadata_report_test.go
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package instance
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+)
+
+func TestGetMetadataReportInstance(t *testing.T) {
+	extension.SetMetadataReportFactory("mock", func() factory.MetadataReportFactory {
+		return &mockMetadataReportFactory{}
+	})
+	u, _ := common.NewURL("mock://127.0.0.1")
+	rpt := GetMetadataReportInstance(&u)
+	assert.NotNil(t, rpt)
+}
+
+type mockMetadataReportFactory struct {
+}
+
+func (m *mockMetadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport {
+	return &mockMetadataReport{}
+}
+
+type mockMetadataReport struct {
+}
+
+func (m mockMetadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) {
+	panic("implement me")
+}
diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go
index 11fb0cd65cf5500c09c51268115e5f7c60916657..6d319e5ecb8007e06dcf790fff145bfab754df3d 100644
--- a/config/metadata_report_config.go
+++ b/config/metadata_report_config.go
@@ -34,13 +34,10 @@ import (
 
 // MethodConfig is method level configuration
 type MetadataReportConfig struct {
-	Protocol   string            `required:"true"  yaml:"protocol"  json:"protocol,omitempty"`
-	Address    string            `yaml:"address" json:"address,omitempty" property:"address"`
-	Username   string            `yaml:"username" json:"username,omitempty" property:"username"`
-	Password   string            `yaml:"password" json:"password,omitempty"  property:"password"`
-	Params     map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
-	TimeoutStr string            `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second
-	Group      string            `yaml:"group" json:"group,omitempty" property:"group"`
+	Protocol  string            `required:"true"  yaml:"protocol"  json:"protocol,omitempty"`
+	RemoteRef string            `required:"true"  yaml:"remote_ref"  json:"remote_ref,omitempty"`
+	Params    map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
+	Group     string            `yaml:"group" json:"group,omitempty" property:"group"`
 }
 
 // nolint
@@ -70,18 +67,24 @@ func (c *MetadataReportConfig) ToUrl() (*common.URL, error) {
 		}
 	}
 
-	url, err := common.NewURL(c.Address,
+	rc, ok := GetBaseConfig().GetRemoteConfig(c.RemoteRef)
+
+	if !ok {
+		return nil, perrors.New("Could not find out the remote ref config, name: " + c.RemoteRef)
+	}
+
+	res, err := common.NewURL(rc.Address,
 		common.WithParams(urlMap),
-		common.WithUsername(c.Username),
-		common.WithPassword(c.Password),
-		common.WithLocation(c.Address),
+		common.WithUsername(rc.Username),
+		common.WithPassword(rc.Password),
+		common.WithLocation(rc.Address),
 		common.WithProtocol(c.Protocol),
 	)
-	if err != nil || len(url.Protocol) == 0 {
+	if err != nil || len(res.Protocol) == 0 {
 		return nil, perrors.New("Invalid MetadataReportConfig.")
 	}
-	url.SetParam("metadata", url.Protocol)
-	return &url, nil
+	res.SetParam("metadata", res.Protocol)
+	return &res, nil
 }
 
 func (c *MetadataReportConfig) IsValid() bool {
@@ -90,14 +93,12 @@ func (c *MetadataReportConfig) IsValid() bool {
 
 // StartMetadataReport: The entry of metadata report start
 func startMetadataReport(metadataType string, metadataReportConfig *MetadataReportConfig) error {
-	if metadataReportConfig == nil || metadataReportConfig.IsValid() {
+	if metadataReportConfig == nil || !metadataReportConfig.IsValid() {
 		return nil
 	}
 
-	if metadataType == constant.METACONFIG_REMOTE {
-		return perrors.New("No MetadataConfig found, you must specify the remote Metadata Center address when 'metadata=remote' is enabled.")
-	} else if metadataType == constant.METACONFIG_REMOTE && len(metadataReportConfig.Address) == 0 {
-		return perrors.New("MetadataConfig address can not be empty.")
+	if metadataType == constant.METACONFIG_REMOTE && len(metadataReportConfig.RemoteRef) == 0 {
+		return perrors.New("MetadataConfig remote ref can not be empty.")
 	}
 
 	if url, err := metadataReportConfig.ToUrl(); err == nil {
diff --git a/config/metadata_report_config_test.go b/config/metadata_report_config_test.go
index b80a77657568d0821fddca2fc16a0ccc90d7f96e..1c585ee79d58826b227df52574b3403639856306 100644
--- a/config/metadata_report_config_test.go
+++ b/config/metadata_report_config_test.go
@@ -23,13 +23,16 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestMetadataReportConfigToUrl(t *testing.T) {
-	metadataReportConfig := MetadataReportConfig{
-		Protocol:   "mock",
+func TestMetadataReportConfig_ToUrl(t *testing.T) {
+	GetBaseConfig().Remotes["mock"] = &RemoteConfig{
 		Address:    "127.0.0.1:2181",
 		Username:   "test",
 		Password:   "test",
 		TimeoutStr: "3s",
+	}
+	metadataReportConfig := MetadataReportConfig{
+		Protocol:  "mock",
+		RemoteRef: "mock",
 		Params: map[string]string{
 			"k": "v",
 		},
diff --git a/config/provider_config.go b/config/provider_config.go
index 97d037ede008b5fc8cab2febb2177a4e8c3dbd1b..9d8a2429d2fc1d88b01c436ae96d55bcd1c729aa 100644
--- a/config/provider_config.go
+++ b/config/provider_config.go
@@ -28,7 +28,6 @@ import (
 
 import (
 	"github.com/apache/dubbo-go/common/constant"
-	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/common/yaml"
 )
 
@@ -38,21 +37,18 @@ import (
 
 // ProviderConfig is the default configuration of service provider
 type ProviderConfig struct {
-	BaseConfig   `yaml:",inline"`
-	Filter       string `yaml:"filter" json:"filter,omitempty" property:"filter"`
-	ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
-	// metadata-report
-	MetadataReportConfig *MetadataReportConfig `yaml:"metadata_report" json:"metadata_report,omitempty" property:"metadata_report"`
+	BaseConfig     `yaml:",inline"`
+	Filter         string                     `yaml:"filter" json:"filter,omitempty" property:"filter"`
+	ProxyFactory   string                     `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
+	Services       map[string]*ServiceConfig  `yaml:"services" json:"services,omitempty" property:"services"`
+	Protocols      map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"`
+	ProtocolConf   interface{}                `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" `
+	FilterConf     interface{}                `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
+	ShutdownConfig *ShutdownConfig            `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
+	ConfigType     map[string]string          `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
 
-	ApplicationConfig *ApplicationConfig         `yaml:"application" json:"application,omitempty" property:"application"`
-	Registry          *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
-	Registries        map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
-	Services          map[string]*ServiceConfig  `yaml:"services" json:"services,omitempty" property:"services"`
-	Protocols         map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"`
-	ProtocolConf      interface{}                `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" `
-	FilterConf        interface{}                `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
-	ShutdownConfig    *ShutdownConfig            `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
-	ConfigType        map[string]string          `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
+	Registry   *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
+	Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
 }
 
 // UnmarshalYAML unmarshals the ProviderConfig by @unmarshal function
@@ -89,25 +85,20 @@ func ProviderInit(confProFile string) error {
 	}
 
 	providerConfig.fileStream = bytes.NewBuffer(fileStream)
-	//set method interfaceId & interfaceName
+	// set method interfaceId & interfaceName
 	for k, v := range providerConfig.Services {
-		//set id for reference
+		// set id for reference
 		for _, n := range providerConfig.Services[k].Methods {
 			n.InterfaceName = v.InterfaceName
 			n.InterfaceId = k
 		}
 	}
-	//start the metadata report if config set
-	if err := startMetadataReport(providerConfig.ApplicationConfig.MetadataType, providerConfig.MetadataReportConfig); err != nil {
-		return perrors.WithMessagef(err, "Provider starts metadata report error, and the error is {%#v}", err)
-	}
-	logger.Debugf("provider config{%#v}\n", providerConfig)
 
 	return nil
 }
 
 func configCenterRefreshProvider() error {
-	//fresh it
+	// fresh it
 	if providerConfig.ConfigCenterConfig != nil {
 		providerConfig.fatherConfig = providerConfig
 		if err := providerConfig.startConfigCenter(); err != nil {
diff --git a/config/reference_config.go b/config/reference_config.go
index 5b7a8e9eac676e10276775cab327ae4de1eddf86..748b2d403fe315f879c817c4e0a4cf3197d807da 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -55,6 +55,7 @@ type ReferenceConfig struct {
 	Retries        string            `yaml:"retries"  json:"retries,omitempty" property:"retries"`
 	Group          string            `yaml:"group"  json:"group,omitempty" property:"group"`
 	Version        string            `yaml:"version"  json:"version,omitempty" property:"version"`
+	ProvideBy      string            `yaml:"provide_by"  json:"provide_by,omitempty" property:"provide_by"`
 	Methods        []*MethodConfig   `yaml:"methods"  json:"methods,omitempty" property:"methods"`
 	Async          bool              `yaml:"async"  json:"async,omitempty" property:"async"`
 	Params         map[string]string `yaml:"params"  json:"params,omitempty" property:"params"`
@@ -144,6 +145,8 @@ func (c *ReferenceConfig) Refer(_ interface{}) {
 				regUrl = u
 			}
 		}
+
+		// TODO(decouple from directory, config should not depend on directory module)
 		if regUrl != nil {
 			cluster := extension.GetCluster("registryAware")
 			c.invoker = cluster.Join(directory.NewStaticDirectory(invokers))
@@ -188,6 +191,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values {
 	urlMap.Set(constant.VERSION_KEY, c.Version)
 	urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic))
 	urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
+	urlMap.Set(constant.PROVIDER_BY, c.ProvideBy)
 
 	urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version)
 	urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.CONSUMER)).Role())
diff --git a/config/reference_config_test.go b/config/reference_config_test.go
index e43f5aa40af84b9f15a5595ce23696b6a1bae9a4..3fbf8da44ca7d00e335260cf107e99dae3a2fa8a 100644
--- a/config/reference_config_test.go
+++ b/config/reference_config_test.go
@@ -38,13 +38,16 @@ var regProtocol protocol.Protocol
 
 func doInitConsumer() {
 	consumerConfig = &ConsumerConfig{
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: &ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "2.6.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
+		},
+
 		Registries: map[string]*RegistryConfig{
 			"shanghai_reg1": {
 				Protocol:   "mock",
@@ -79,6 +82,7 @@ func doInitConsumer() {
 				Password:   "pwd1",
 			},
 		},
+
 		References: map[string]*ReferenceConfig{
 			"MockService": {
 				id: "MockProvider",
@@ -136,19 +140,23 @@ func doInitConsumerAsync() {
 
 func doInitConsumerWithSingleRegistry() {
 	consumerConfig = &ConsumerConfig{
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: &ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "2.6.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
+		},
+
 		Registry: &RegistryConfig{
 			Address:  "mock://27.0.0.1:2181",
 			Username: "user1",
 			Password: "pwd1",
 		},
 		Registries: map[string]*RegistryConfig{},
+
 		References: map[string]*ReferenceConfig{
 			"MockService": {
 				Params: map[string]string{
diff --git a/config/remote_config.go b/config/remote_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..5e0330c571715d99e63688ee944c61f8e48117bb
--- /dev/null
+++ b/config/remote_config.go
@@ -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 config
+
+import (
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/logger"
+)
+
+// RemoteConfig: usually we need some middleware, including nacos, zookeeper
+// this represents an instance of this middleware
+// so that other module, like config center, registry could reuse the config
+// but now, only metadata report, metadata service, service discovery use this structure
+type RemoteConfig struct {
+	Address    string            `yaml:"address" json:"address,omitempty"`
+	TimeoutStr string            `default:"5s" yaml:"timeout" json:"timeout,omitempty"`
+	Username   string            `yaml:"username" json:"username,omitempty" property:"username"`
+	Password   string            `yaml:"password" json:"password,omitempty"  property:"password"`
+	Params     map[string]string `yaml:"params" json:"address,omitempty"`
+}
+
+// Timeout return timeout duration.
+// if the configure is invalid, or missing, the default value 5s will be returned
+func (rc *RemoteConfig) Timeout() time.Duration {
+	if res, err := time.ParseDuration(rc.TimeoutStr); err == nil {
+		return res
+	}
+	logger.Errorf("Could not parse the timeout string to Duration: %s, the default value will be returned", rc.TimeoutStr)
+	return 5 * time.Second
+}
+
+// GetParam will return the value of the key. If not found, def will be return;
+// def => default value
+func (rc *RemoteConfig) GetParam(key string, def string) string {
+	param, ok := rc.Params[key]
+	if !ok {
+		return def
+	}
+	return param
+}
diff --git a/config/remote_config_test.go b/config/remote_config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..82535fd60b932aecb7c6c3ee8206130fad9e7161
--- /dev/null
+++ b/config/remote_config_test.go
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+	"testing"
+)
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRemoteConfig_GetParam(t *testing.T) {
+	rc := &RemoteConfig{
+		Params: make(map[string]string, 1),
+	}
+
+	def := "default value"
+	key := "key"
+	value := rc.GetParam(key, def)
+	assert.Equal(t, def, value)
+
+	actualVal := "actual value"
+	rc.Params[key] = actualVal
+
+	value = rc.GetParam(key, def)
+	assert.Equal(t, actualVal, value)
+}
diff --git a/config/router_config.go b/config/router_config.go
index 0670ee9c20f618021d1d574344a0df85d837bd66..16943d96be76f93c2d540e2ccf16670b7424298f 100644
--- a/config/router_config.go
+++ b/config/router_config.go
@@ -18,16 +18,20 @@
 package config
 
 import (
+	gxset "github.com/dubbogo/gost/container/set"
 	perrors "github.com/pkg/errors"
 )
 
 import (
-	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/common/yaml"
 )
 
+var (
+	routerURLSet = gxset.NewSet()
+)
+
 // RouterInit Load config file to init router config
 func RouterInit(confRouterFile string) error {
 	fileRouterFactories := extension.GetFileRouterFactories()
@@ -40,10 +44,14 @@ func RouterInit(confRouterFile string) error {
 		r, e := factory.NewFileRouter(bytes)
 		if e == nil {
 			url := r.URL()
-			directory.AddRouterURLSet(&url)
+			routerURLSet.Add(url)
 			return nil
 		}
 		logger.Warnf("router config type %s create fail {%v}\n", k, e)
 	}
 	return perrors.Errorf("no file router exists for parse %s , implement router.FIleRouterFactory please.", confRouterFile)
 }
+
+func GetRouterURLSet() *gxset.HashSet {
+	return routerURLSet
+}
diff --git a/config/router_config_test.go b/config/router_config_test.go
index 2f0a38b2fdf59578c77076680c05b3eca5c26a1c..bf189b600f1135e4059c8833a3de042bba5427ff 100644
--- a/config/router_config_test.go
+++ b/config/router_config_test.go
@@ -27,7 +27,6 @@ import (
 )
 
 import (
-	"github.com/apache/dubbo-go/cluster/directory"
 	_ "github.com/apache/dubbo-go/cluster/router/condition"
 )
 
@@ -53,15 +52,3 @@ func TestString(t *testing.T) {
 	assert.Equal(t, n2[0], "a1")
 	assert.Equal(t, n2[1], "")
 }
-
-func TestRouterInit(t *testing.T) {
-	errPro := RouterInit(errorTestYML)
-	assert.Error(t, errPro)
-
-	assert.Equal(t, 0, directory.GetRouterURLSet().Size())
-
-	errPro = RouterInit(testYML)
-	assert.NoError(t, errPro)
-
-	assert.Equal(t, 1, directory.GetRouterURLSet().Size())
-}
diff --git a/config/service_config.go b/config/service_config.go
index 70a344c7b8c858a25e9a556ac6f4e899b1626ab4..57fce028fa59893979fc6f50671fe8681770a2b8 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -73,14 +73,18 @@ type ServiceConfig struct {
 	ParamSign                   string            `yaml:"param.sign" json:"param.sign,omitempty" property:"param.sign"`
 	Tag                         string            `yaml:"tag" json:"tag,omitempty" property:"tag"`
 
+	Protocols     map[string]*ProtocolConfig
 	unexported    *atomic.Bool
 	exported      *atomic.Bool
 	rpcService    common.RPCService
-	cacheProtocol protocol.Protocol
 	cacheMutex    sync.Mutex
+	cacheProtocol protocol.Protocol
+
+	exportersLock sync.Mutex
+	exporters     []protocol.Exporter
 }
 
-// nolint
+// Prefix returns dubbo.service.${interface}.
 func (c *ServiceConfig) Prefix() string {
 	return constant.ServiceConfigPrefix + c.InterfaceName + "."
 }
@@ -94,6 +98,8 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := unmarshal((*plain)(c)); err != nil {
 		return err
 	}
+	c.exported = atomic.NewBool(false)
+	c.unexported = atomic.NewBool(false)
 	return nil
 }
 
@@ -107,6 +113,16 @@ func NewServiceConfig(id string, context context.Context) *ServiceConfig {
 	}
 }
 
+// InitExported will set exported as false atom bool
+func (c *ServiceConfig) InitExported() {
+	c.exported = atomic.NewBool(false)
+}
+
+// IsExport will return whether the service config is exported or not
+func (c *ServiceConfig) IsExport() bool {
+	return c.exported.Load()
+}
+
 // Get Random Port
 func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List {
 	ports := list.New()
@@ -125,7 +141,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List {
 	return ports
 }
 
-// Export ...
+// Export exports the service
 func (c *ServiceConfig) Export() error {
 	// TODO: config center start here
 
@@ -142,7 +158,7 @@ func (c *ServiceConfig) Export() error {
 
 	regUrls := loadRegistries(c.Registry, providerConfig.Registries, common.PROVIDER)
 	urlMap := c.getUrlMap()
-	protocolConfigs := loadProtocol(c.Protocol, providerConfig.Protocols)
+	protocolConfigs := loadProtocol(c.Protocol, c.Protocols)
 	if len(protocolConfigs) == 0 {
 		logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs ", c.InterfaceName, c.Protocol)
 		return nil
@@ -178,6 +194,9 @@ func (c *ServiceConfig) Export() error {
 		if len(c.Tag) > 0 {
 			ivkURL.AddParam(constant.Tagkey, c.Tag)
 		}
+
+		var exporter protocol.Exporter
+
 		if len(regUrls) > 0 {
 			for _, regUrl := range regUrls {
 				regUrl.SubURL = ivkURL
@@ -190,23 +209,47 @@ func (c *ServiceConfig) Export() error {
 				c.cacheMutex.Unlock()
 
 				invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl)
-				exporter := c.cacheProtocol.Export(invoker)
+				exporter = c.cacheProtocol.Export(invoker)
 				if exporter == nil {
 					panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, ivkURL)))
 				}
 			}
 		} else {
 			invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL)
-			exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker)
+			exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker)
 			if exporter == nil {
 				panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL)))
 			}
 		}
+		c.exporters = append(c.exporters, exporter)
 	}
+	c.exported.Store(true)
 	return nil
 }
 
-// Implement ...
+// Unexport will call unexport of all exporters service config exported
+func (c *ServiceConfig) Unexport() {
+	if !c.exported.Load() {
+		return
+	}
+	if c.unexported.Load() {
+		return
+	}
+
+	func() {
+		c.exportersLock.Lock()
+		defer c.exportersLock.Unlock()
+		for _, exporter := range c.exporters {
+			exporter.Unexport()
+		}
+		c.exporters = nil
+	}()
+
+	c.exported.Store(false)
+	c.unexported.Store(true)
+}
+
+// Implement only store the @s and return
 func (c *ServiceConfig) Implement(s common.RPCService) {
 	c.rpcService = s
 }
@@ -275,3 +318,16 @@ func (c *ServiceConfig) getUrlMap() url.Values {
 
 	return urlMap
 }
+
+// GetExportedUrls will return the url in service config's exporter
+func (c *ServiceConfig) GetExportedUrls() []*common.URL {
+	if c.exported.Load() {
+		var urls []*common.URL
+		for _, exporter := range c.exporters {
+			url := exporter.GetInvoker().GetUrl()
+			urls = append(urls, &url)
+		}
+		return urls
+	}
+	return nil
+}
diff --git a/config/service_config_test.go b/config/service_config_test.go
index 798984874a7983f4236a3e60d369b0dc9b255357..e7d55077beb56b0ccf8da833f5aeebfac07a9e3f 100644
--- a/config/service_config_test.go
+++ b/config/service_config_test.go
@@ -24,6 +24,7 @@ import (
 import (
 	gxnet "github.com/dubbogo/gost/net"
 	"github.com/stretchr/testify/assert"
+	"go.uber.org/atomic"
 )
 
 import (
@@ -32,46 +33,14 @@ import (
 
 func doInitProvider() {
 	providerConfig = &ProviderConfig{
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registries: map[string]*RegistryConfig{
-			"shanghai_reg1": {
-				Protocol:   "mock",
-				TimeoutStr: "2s",
-				Group:      "shanghai_idc",
-				Address:    "127.0.0.1:2181",
-				Username:   "user1",
-				Password:   "pwd1",
-			},
-			"shanghai_reg2": {
-				Protocol:   "mock",
-				TimeoutStr: "2s",
-				Group:      "shanghai_idc",
-				Address:    "127.0.0.2:2181",
-				Username:   "user1",
-				Password:   "pwd1",
-			},
-			"hangzhou_reg1": {
-				Protocol:   "mock",
-				TimeoutStr: "2s",
-				Group:      "hangzhou_idc",
-				Address:    "127.0.0.3:2181",
-				Username:   "user1",
-				Password:   "pwd1",
-			},
-			"hangzhou_reg2": {
-				Protocol:   "mock",
-				TimeoutStr: "2s",
-				Group:      "hangzhou_idc",
-				Address:    "127.0.0.4:2181",
-				Username:   "user1",
-				Password:   "pwd1",
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: &ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "2.6.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
 		},
 		Services: map[string]*ServiceConfig{
 			"MockService": {
@@ -97,6 +66,7 @@ func doInitProvider() {
 						Weight:      200,
 					},
 				},
+				exported: new(atomic.Bool),
 			},
 			"MockServiceNoRightProtocol": {
 				InterfaceName: "com.MockService",
@@ -121,58 +91,45 @@ func doInitProvider() {
 						Weight:      200,
 					},
 				},
+				exported: new(atomic.Bool),
 			},
 		},
-		Protocols: map[string]*ProtocolConfig{
-			"mock": {
-				Name: "mock",
-				Ip:   "127.0.0.1",
-				Port: "20000",
-			},
-		},
-	}
-}
 
-func doInitProviderWithSingleRegistry() {
-	providerConfig = &ProviderConfig{
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registry: &RegistryConfig{
-			Address:  "mock://127.0.0.1:2181",
-			Username: "user1",
-			Password: "pwd1",
-		},
-		Registries: map[string]*RegistryConfig{},
-		Services: map[string]*ServiceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Cluster:       "failover",
-				Loadbalance:   "random",
-				Retries:       "3",
-				Group:         "huadong_idc",
-				Version:       "1.0.0",
-				Methods: []*MethodConfig{
-					{
-						Name:        "GetUser",
-						Retries:     "2",
-						Loadbalance: "random",
-						Weight:      200,
-					},
-					{
-						Name:        "GetUser1",
-						Retries:     "2",
-						Loadbalance: "random",
-						Weight:      200,
-					},
-				},
+		Registries: map[string]*RegistryConfig{
+			"shanghai_reg1": {
+				Protocol:   "mock",
+				TimeoutStr: "2s",
+				Group:      "shanghai_idc",
+				Address:    "127.0.0.1:2181",
+				Username:   "user1",
+				Password:   "pwd1",
+			},
+			"shanghai_reg2": {
+				Protocol:   "mock",
+				TimeoutStr: "2s",
+				Group:      "shanghai_idc",
+				Address:    "127.0.0.2:2181",
+				Username:   "user1",
+				Password:   "pwd1",
+			},
+			"hangzhou_reg1": {
+				Protocol:   "mock",
+				TimeoutStr: "2s",
+				Group:      "hangzhou_idc",
+				Address:    "127.0.0.3:2181",
+				Username:   "user1",
+				Password:   "pwd1",
+			},
+			"hangzhou_reg2": {
+				Protocol:   "mock",
+				TimeoutStr: "2s",
+				Group:      "hangzhou_idc",
+				Address:    "127.0.0.4:2181",
+				Username:   "user1",
+				Password:   "pwd1",
 			},
 		},
+
 		Protocols: map[string]*ProtocolConfig{
 			"mock": {
 				Name: "mock",
@@ -190,6 +147,7 @@ func TestExport(t *testing.T) {
 	for i := range providerConfig.Services {
 		service := providerConfig.Services[i]
 		service.Implement(&MockService{})
+		service.Protocols = providerConfig.Protocols
 		service.Export()
 	}
 	providerConfig = nil
diff --git a/config/service_discovery_config.go b/config/service_discovery_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..d3dc697a9b31e6949375d74aa92234093da22378
--- /dev/null
+++ b/config/service_discovery_config.go
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+// ServiceDiscoveryConfig will be used to create
+type ServiceDiscoveryConfig struct {
+	// Protocol indicate which implementation will be used.
+	// for example, if the Protocol is nacos, it means that we will use nacosServiceDiscovery
+	Protocol string `yaml:"protocol" json:"protocol,omitempty"`
+	// Group, usually you don't need to config this field.
+	// you can use this to do some isolation
+	Group string `yaml:"group" json:"group,omitempty"`
+	// RemoteRef is the reference point to RemoteConfig which will be used to create remotes instances.
+	RemoteRef string `yaml:"remote_ref" json:"remote_ref,omitempty"`
+}
diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go
index 856522587d97f8be7a51c59ae36721ad88db04e8..01319f362b956a0a15be8d5d4dde2a8b2be57c89 100644
--- a/config_center/nacos/client_test.go
+++ b/config_center/nacos/client_test.go
@@ -66,7 +66,7 @@ func TestSetNacosClient(t *testing.T) {
 	client = &NacosClient{
 		name:       nacosClientName,
 		NacosAddrs: []string{nacosURL},
-		Timeout:    15,
+		Timeout:    15 * time.Second,
 		exit:       make(chan struct{}),
 		onceClose: func() {
 			close(client.exit)
diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go
index 007b8be142274b63ceb56dd00399cdaf29c3746d..bbf707b93811663d0a259c6704e1008bfa91c5c1 100644
--- a/config_center/nacos/impl.go
+++ b/config_center/nacos/impl.go
@@ -36,8 +36,16 @@ import (
 	"github.com/apache/dubbo-go/config_center/parser"
 )
 
-const nacosClientName = "nacos config_center"
+const (
+	nacosClientName = "nacos config_center"
+	// the number is a little big tricky
+	// it will be used in query which looks up all keys with the target group
+	// now, one key represents one application
+	// so only a group has more than 9999 applications will failed
+	maxKeysNum = 9999
+)
 
+// nacosDynamicConfiguration is the implementation of DynamicConfiguration based on nacos
 type nacosDynamicConfiguration struct {
 	url          *common.URL
 	rootPath     string
@@ -108,9 +116,23 @@ func (n *nacosDynamicConfiguration) PublishConfig(key string, group string, valu
 
 // GetConfigKeysByGroup will return all keys with the group
 func (n *nacosDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
-	// TODO (the golang client of nacos does not support batch API)
-	// we should build a issue and then think about how to resolve this problem
-	return nil, perrors.New("unsupport operation, wait for implement")
+	group = n.resolvedGroup(group)
+	page, err := (*n.client.Client()).SearchConfig(vo.SearchConfigParm{
+		Search: "accurate",
+		Group:  group,
+		PageNo: 1,
+		// actually it's impossible for user to create 9999 application under one group
+		PageSize: maxKeysNum,
+	})
+
+	result := gxset.NewSet()
+	if err != nil {
+		return result, perrors.WithMessage(err, "can not find the client config")
+	}
+	for _, itm := range page.PageItems {
+		result.Add(itm.DataId)
+	}
+	return result, nil
 }
 
 // GetRule Get router rule
diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go
index 453fa11f955c83ae2925a959e7a2465a2a0e7796..88d200edc927c62967975dff28e19c03743125f0 100644
--- a/config_center/nacos/impl_test.go
+++ b/config_center/nacos/impl_test.go
@@ -89,6 +89,34 @@ func TestGetConfig(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestNacosDynamicConfiguration_GetConfigKeysByGroup(t *testing.T) {
+	data := `
+{
+    "PageItems": [
+        {
+            "dataId": "application"
+        }
+    ]
+}
+`
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte(data))
+	}))
+
+	nacosURL := strings.ReplaceAll(ts.URL, "http", "registry")
+	regurl, _ := common.NewURL(nacosURL)
+	nacosConfiguration, err := newNacosDynamicConfiguration(&regurl)
+	assert.NoError(t, err)
+
+	nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{})
+
+	configs, err := nacosConfiguration.GetConfigKeysByGroup("dubbo")
+	assert.Nil(t, err)
+	assert.Equal(t, 1, configs.Size())
+	assert.True(t, configs.Contains("application"))
+
+}
+
 func TestNacosDynamicConfigurationPublishConfig(t *testing.T) {
 	nacos, err := initNacosData(t)
 	assert.Nil(t, err)
@@ -105,8 +133,6 @@ func TestAddListener(t *testing.T) {
 	listener := &mockDataListener{}
 	time.Sleep(time.Second * 2)
 	nacos.AddListener("dubbo.properties", listener)
-	listener.wg.Add(1)
-	listener.wg.Wait()
 }
 
 func TestRemoveListener(_ *testing.T) {
diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go
index 4c995389d38e1a39670aff26025f030bd4bfb1ec..3118a9d0529bf8762dc7ee864af32b044b74fd49 100644
--- a/config_center/nacos/listener.go
+++ b/config_center/nacos/listener.go
@@ -46,7 +46,9 @@ func (n *nacosDynamicConfiguration) addListener(key string, listener config_cent
 				go callback(listener, namespace, group, dataId, data)
 			},
 		})
-		logger.Errorf("nacos : listen config fail, error:%v ", err)
+		if err != nil {
+			logger.Errorf("nacos : listen config fail, error:%v ", err)
+		}
 		newListener := make(map[config_center.ConfigurationListener]context.CancelFunc)
 		newListener[listener] = cancel
 		n.keyListeners.Store(key, newListener)
diff --git a/go.mod b/go.mod
index 76fb862b00bd757080c7effd2b7ae0ba813b39a1..9e98e4ef0989e828be13fcb72b7ddd64f05d9df0 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,9 @@ module github.com/apache/dubbo-go
 require (
 	github.com/Workiva/go-datastructures v1.0.50
 	github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
+	github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect
 	github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279
+	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	github.com/coreos/bbolt v1.3.3 // indirect
 	github.com/coreos/etcd v3.3.13+incompatible
 	github.com/coreos/go-semver v0.3.0 // indirect
@@ -11,9 +13,10 @@ require (
 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
 	github.com/creasty/defaults v1.3.0
 	github.com/dubbogo/getty v1.3.7
-	github.com/dubbogo/go-zookeeper v1.0.0
+	github.com/dubbogo/go-zookeeper v1.0.1
 	github.com/dubbogo/gost v1.9.0
 	github.com/emicklei/go-restful/v3 v3.0.0
+	github.com/go-co-op/gocron v0.1.1
 	github.com/go-resty/resty/v2 v2.1.0
 	github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
 	github.com/golang/mock v1.3.1
@@ -25,13 +28,14 @@ require (
 	github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
 	github.com/hashicorp/consul v1.5.3
 	github.com/hashicorp/consul/api v1.1.0
+	github.com/hashicorp/vault v0.10.3
 	github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
 	github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
 	github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect
 	github.com/magiconair/properties v1.8.1
 	github.com/mitchellh/mapstructure v1.1.2
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
-	github.com/nacos-group/nacos-sdk-go v0.3.2
+	github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f
 	github.com/opentracing/opentracing-go v1.1.0
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.1.0
diff --git a/go.sum b/go.sum
index d426cc60b553a56c195c831834fff7ce8afab88b..eaeae0a22622a65dcf95b9b65c36289035aa3afd 100644
--- a/go.sum
+++ b/go.sum
@@ -7,7 +7,6 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
 github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY=
 github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4=
 github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@@ -36,8 +35,14 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
 github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
+github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/apache/dubbo-go-hessian2 v1.5.0 h1:fzulDG5G7nX0ccgKdiN9XipJ7tZ4WXKgmk4stdlDS6s=
+github.com/apache/dubbo-go-hessian2 v1.5.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
 github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279 h1:1g3IJdaUjXWs++NA9Ail8+r6WgrkfhjS6hD/YXvRzjk=
 github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
@@ -51,6 +56,7 @@ github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL6
 github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro=
 github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -105,10 +111,13 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF
 github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dubbogo/getty v1.3.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw=
+github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY=
 github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M=
 github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY=
-github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM=
-github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
+github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8=
+github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
+github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
 github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk=
 github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
 github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M=
@@ -134,6 +143,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU=
+github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M=
 github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
@@ -149,6 +160,7 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE=
 github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
 github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4 h1:1LlmVz15APoKz9dnm5j2ePptburJlwEH+/v/pUuoxck=
@@ -203,6 +215,8 @@ github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1:
 github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
@@ -388,6 +402,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c=
 github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw=
+github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw=
+github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk=
 github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s=
 github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk=
 github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0=
@@ -395,10 +411,14 @@ github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
 github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
@@ -452,7 +472,10 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
 github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
+github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
+github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@@ -506,12 +529,17 @@ github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz7
 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY=
 go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
 go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -523,7 +551,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -540,6 +567,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -548,6 +577,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -581,7 +612,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w=
@@ -626,7 +656,6 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI=
 k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
diff --git a/metadata/definition/definition.go b/metadata/definition/definition.go
index ead984345efde1ddd1d54b7599fd9d5584947ea2..dbbc0c8f1685edf1a26ab1fe4ad091c501e76f5f 100644
--- a/metadata/definition/definition.go
+++ b/metadata/definition/definition.go
@@ -17,6 +17,24 @@
 
 package definition
 
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+// ServiceDefiner is a interface of service's definition
+type ServiceDefiner interface {
+	ToBytes() ([]byte, error)
+}
+
+// ServiceDefinition is the describer of service definition
 type ServiceDefinition struct {
 	CanonicalName string
 	CodeSource    string
@@ -24,6 +42,39 @@ type ServiceDefinition struct {
 	Types         []TypeDefinition
 }
 
+// ToBytes convert ServiceDefinition to json string
+func (def *ServiceDefinition) ToBytes() ([]byte, error) {
+	return json.Marshal(def)
+}
+
+// String will iterate all methods and parameters and convert them to json string
+func (def *ServiceDefinition) String() string {
+	var methodStr strings.Builder
+	for _, m := range def.Methods {
+		var paramType strings.Builder
+		for _, p := range m.ParameterTypes {
+			paramType.WriteString(fmt.Sprintf("{type:%v}", p))
+		}
+		var param strings.Builder
+		for _, d := range m.Parameters {
+			param.WriteString(fmt.Sprintf("{id:%v,type:%v,builderName:%v}", d.Id, d.Type, d.TypeBuilderName))
+		}
+		methodStr.WriteString(fmt.Sprintf("{name:%v,parameterTypes:[%v],returnType:%v,params:[%v] }", m.Name, paramType.String(), m.ReturnType, param.String()))
+	}
+	var types strings.Builder
+	for _, d := range def.Types {
+		types.WriteString(fmt.Sprintf("{id:%v,type:%v,builderName:%v}", d.Id, d.Type, d.TypeBuilderName))
+	}
+	return fmt.Sprintf("{canonicalName:%v, codeSource:%v, methods:[%v], types:[%v]}", def.CanonicalName, def.CodeSource, methodStr.String(), types.String())
+}
+
+// FullServiceDefinition is the describer of service definition with parameters
+type FullServiceDefinition struct {
+	ServiceDefinition
+	Params map[string]string
+}
+
+// MethodDefinition is the describer of method definition
 type MethodDefinition struct {
 	Name           string
 	ParameterTypes []string
@@ -31,6 +82,7 @@ type MethodDefinition struct {
 	Parameters     []TypeDefinition
 }
 
+// TypeDefinition is the describer of type definition
 type TypeDefinition struct {
 	Id              string
 	Type            string
@@ -39,3 +91,47 @@ type TypeDefinition struct {
 	Properties      map[string]TypeDefinition
 	TypeBuilderName string
 }
+
+// BuildServiceDefinition can build service definition which will be used to describe a service
+func BuildServiceDefinition(service common.Service, url common.URL) *ServiceDefinition {
+	sd := &ServiceDefinition{}
+	sd.CanonicalName = url.Service()
+
+	for k, m := range service.Method() {
+		var paramTypes []string
+		if len(m.ArgsType()) > 0 {
+			for _, t := range m.ArgsType() {
+				paramTypes = append(paramTypes, t.Kind().String())
+			}
+		}
+
+		var returnType string
+		if m.ReplyType() != nil {
+			returnType = m.ReplyType().Kind().String()
+		}
+
+		methodD := MethodDefinition{
+			Name:           k,
+			ParameterTypes: paramTypes,
+			ReturnType:     returnType,
+		}
+		sd.Methods = append(sd.Methods, methodD)
+	}
+
+	return sd
+}
+
+// ServiceDescriperBuild: build the service key, format is `group/serviceName:version` which be same as URL's service key
+func ServiceDescriperBuild(serviceName string, group string, version string) string {
+	buf := &bytes.Buffer{}
+	if group != "" {
+		buf.WriteString(group)
+		buf.WriteString(constant.PATH_SEPARATOR)
+	}
+	buf.WriteString(serviceName)
+	if version != "" && version != "0.0.0" {
+		buf.WriteString(constant.KEY_SEPARATOR)
+		buf.WriteString(version)
+	}
+	return buf.String()
+}
diff --git a/metadata/definition/definition_test.go b/metadata/definition/definition_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..958f9324d0f9f4a5027792bd8e54b238a5f56feb
--- /dev/null
+++ b/metadata/definition/definition_test.go
@@ -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 definition
+
+import (
+	"fmt"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+func TestBuildServiceDefinition(t *testing.T) {
+	serviceName := "com.ikurento.user.UserProvider"
+	group := "group1"
+	version := "0.0.1"
+	protocol := "dubbo"
+	beanName := "UserProvider"
+	url, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, serviceName, group, version, beanName))
+	assert.NoError(t, err)
+	_, err = common.ServiceMap.Register(serviceName, protocol, &UserProvider{})
+	assert.NoError(t, err)
+	service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service()))
+	sd := BuildServiceDefinition(*service, url)
+	assert.Equal(t, "{canonicalName:com.ikurento.user.UserProvider, codeSource:, methods:[{name:GetUser,parameterTypes:[{type:slice}],returnType:ptr,params:[] }], types:[]}", sd.String())
+}
diff --git a/metadata/definition/mock.go b/metadata/definition/mock.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca9e125a7480c2b6ff57d0b7cc820b537eb908f2
--- /dev/null
+++ b/metadata/definition/mock.go
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package definition
+
+import (
+	"context"
+	"time"
+)
+
+type User struct {
+	Id   string
+	Name string
+	Age  int32
+	Time time.Time
+}
+
+type UserProvider struct {
+}
+
+func (u *UserProvider) GetUser(ctx context.Context, req []interface{}) (*User, error) {
+	rsp := User{"A001", "Alex Stocks", 18, time.Now()}
+	return &rsp, nil
+}
+
+func (u *UserProvider) Reference() string {
+	return "UserProvider"
+}
+
+func (u User) JavaClassName() string {
+	return "com.ikurento.user.User"
+}
diff --git a/metadata/identifier/base_metadata_identifier.go b/metadata/identifier/base_metadata_identifier.go
index 5f3df4c607e69d2b56e1258d081c148524cd7aca..2371f7ca02f403a11251b9b0cbb23369b27683e2 100644
--- a/metadata/identifier/base_metadata_identifier.go
+++ b/metadata/identifier/base_metadata_identifier.go
@@ -25,21 +25,21 @@ import (
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-// BaseMetadataIdentifier defined for description the Metadata base identify
-type BaseMetadataIdentifier interface {
-	getFilePathKey(params ...string) string
-	getIdentifierKey(params ...string) string
+// BaseMetadataIdentifier defined for describe the Metadata base identify
+type IMetadataIdentifier interface {
+	GetFilePathKey() string
+	GetIdentifierKey() string
 }
 
 // BaseMetadataIdentifier is the base implement of BaseMetadataIdentifier interface
-type BaseServiceMetadataIdentifier struct {
-	serviceInterface string
-	version          string
-	group            string
-	side             string
+type BaseMetadataIdentifier struct {
+	ServiceInterface string
+	Version          string
+	Group            string
+	Side             string
 }
 
-// joinParams will join the specified char in slice, and return a string
+// joinParams will join the specified char in slice, and build as string
 func joinParams(joinChar string, params []string) string {
 	var joinedStr string
 	for _, param := range params {
@@ -50,27 +50,28 @@ func joinParams(joinChar string, params []string) string {
 }
 
 // getIdentifierKey returns string that format is service:Version:Group:Side:param1:param2...
-func (mdi *BaseServiceMetadataIdentifier) getIdentifierKey(params ...string) string {
-	return mdi.serviceInterface +
-		constant.KEY_SEPARATOR + mdi.version +
-		constant.KEY_SEPARATOR + mdi.group +
-		constant.KEY_SEPARATOR + mdi.side +
+func (mdi *BaseMetadataIdentifier) getIdentifierKey(params ...string) string {
+	return mdi.ServiceInterface +
+		constant.KEY_SEPARATOR + mdi.Version +
+		constant.KEY_SEPARATOR + mdi.Group +
+		constant.KEY_SEPARATOR + mdi.Side +
 		joinParams(constant.KEY_SEPARATOR, params)
 }
 
 // getFilePathKey returns string that format is metadata/path/Version/Group/Side/param1/param2...
-func (mdi *BaseServiceMetadataIdentifier) getFilePathKey(params ...string) string {
-	path := serviceToPath(mdi.serviceInterface)
+func (mdi *BaseMetadataIdentifier) getFilePathKey(params ...string) string {
+	path := serviceToPath(mdi.ServiceInterface)
 
 	return constant.DEFAULT_PATH_TAG +
 		withPathSeparator(path) +
-		withPathSeparator(mdi.version) +
-		withPathSeparator(mdi.group) +
-		withPathSeparator(mdi.side) +
+		withPathSeparator(mdi.Version) +
+		withPathSeparator(mdi.Group) +
+		withPathSeparator(mdi.Side) +
 		joinParams(constant.PATH_SEPARATOR, params)
 
 }
 
+// serviceToPath uss URL encode to decode the @serviceInterface
 func serviceToPath(serviceInterface string) string {
 	if serviceInterface == constant.ANY_VALUE {
 		return ""
@@ -84,6 +85,7 @@ func serviceToPath(serviceInterface string) string {
 
 }
 
+// withPathSeparator return "/" + @path
 func withPathSeparator(path string) string {
 	if len(path) != 0 {
 		path = constant.PATH_SEPARATOR + path
diff --git a/metadata/identifier/base_metadata_identifier_test.go b/metadata/identifier/base_metadata_identifier_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5b60992ab6132ecb306245af31bba7e3d0f09117
--- /dev/null
+++ b/metadata/identifier/base_metadata_identifier_test.go
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+var baseId = &BaseMetadataIdentifier{
+	ServiceInterface: "org.apache.pkg.mockService",
+	Version:          "1.0.0",
+	Group:            "Group",
+	Side:             "provider",
+}
+
+func TestBaseGetFilePathKey(t *testing.T) {
+	assert.Equal(t, "metadata/1.0.0/Group/provider/a/b/c", baseId.getFilePathKey("a", "b", "c"))
+}
+
+func TestBaseGetIdentifierKey(t *testing.T) {
+	assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:a:b:c", baseId.getIdentifierKey("a", "b", "c"))
+}
diff --git a/metadata/identifier/metadata_identifier.go b/metadata/identifier/metadata_identifier.go
index 7e72c10da9c088ca167fa4fbc4dcb57f44b8c06d..7e50c4c6b9427bd9d439daa7464d96a2ea94fd39 100644
--- a/metadata/identifier/metadata_identifier.go
+++ b/metadata/identifier/metadata_identifier.go
@@ -19,16 +19,16 @@ package identifier
 
 // MetadataIdentifier is inherit baseMetaIdentifier with Application name
 type MetadataIdentifier struct {
-	application string
+	Application string
 	BaseMetadataIdentifier
 }
 
 // GetIdentifierKey returns string that format is service:Version:Group:Side:Application
-func (mdi *MetadataIdentifier) getIdentifierKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.application)
+func (mdi *MetadataIdentifier) GetIdentifierKey() string {
+	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Application)
 }
 
 // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Application
-func (mdi *MetadataIdentifier) getFilePathKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.application)
+func (mdi *MetadataIdentifier) GetFilePathKey() string {
+	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Application)
 }
diff --git a/metadata/identifier/metadata_identifier_test.go b/metadata/identifier/metadata_identifier_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cba3c0dd76a01f2125b87db4478f99501bf2c284
--- /dev/null
+++ b/metadata/identifier/metadata_identifier_test.go
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+var metadataId = &MetadataIdentifier{
+	Application: "app",
+	BaseMetadataIdentifier: BaseMetadataIdentifier{
+		ServiceInterface: "org.apache.pkg.mockService",
+		Version:          "1.0.0",
+		Group:            "Group",
+		Side:             "provider",
+	},
+}
+
+func TestGetFilePathKey(t *testing.T) {
+	assert.Equal(t, "metadata/1.0.0/Group/provider/app", metadataId.GetFilePathKey())
+}
+
+func TestGetIdentifierKey(t *testing.T) {
+	assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:app", metadataId.GetIdentifierKey())
+}
diff --git a/metadata/identifier/service_metadata_identifier.go b/metadata/identifier/service_metadata_identifier.go
index ccc149f7306c125b19a25373d4da660a154cc84e..b9e65967e0f707a6efcc9f8ded2ce5dec4f058b8 100644
--- a/metadata/identifier/service_metadata_identifier.go
+++ b/metadata/identifier/service_metadata_identifier.go
@@ -18,22 +18,38 @@
 package identifier
 
 import (
+	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 )
 
 // ServiceMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision and Protocol
 type ServiceMetadataIdentifier struct {
-	revision string
-	protocol string
+	Revision string
+	Protocol string
 	BaseMetadataIdentifier
 }
 
+// NewServiceMetadataIdentifier create instance.
+// The ServiceInterface is the @url.Service()
+// other parameters are read from @url
+func NewServiceMetadataIdentifier(url common.URL) *ServiceMetadataIdentifier {
+	return &ServiceMetadataIdentifier{
+		BaseMetadataIdentifier: BaseMetadataIdentifier{
+			ServiceInterface: url.Service(),
+			Version:          url.GetParam(constant.VERSION_KEY, ""),
+			Group:            url.GetParam(constant.GROUP_KEY, ""),
+			Side:             url.GetParam(constant.SIDE_KEY, ""),
+		},
+		Protocol: url.Protocol,
+	}
+}
+
 // GetIdentifierKey returns string that format is service:Version:Group:Side:Protocol:"revision"+Revision
-func (mdi *ServiceMetadataIdentifier) getIdentifierKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision)
+func (mdi *ServiceMetadataIdentifier) GetIdentifierKey() string {
+	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Protocol, constant.KEY_REVISON_PREFIX+mdi.Revision)
 }
 
 // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Protocol/"revision"+Revision
-func (mdi *ServiceMetadataIdentifier) getFilePathKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision)
+func (mdi *ServiceMetadataIdentifier) GetFilePathKey() string {
+	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Protocol, constant.KEY_REVISON_PREFIX+mdi.Revision)
 }
diff --git a/metadata/identifier/service_metadata_identifier_test.go b/metadata/identifier/service_metadata_identifier_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d7ef44a4bbc7611b6391122f8f5841db349eb036
--- /dev/null
+++ b/metadata/identifier/service_metadata_identifier_test.go
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+var serviceMetadataId = &ServiceMetadataIdentifier{
+	Revision: "1.0",
+	Protocol: "dubbo",
+	BaseMetadataIdentifier: BaseMetadataIdentifier{
+		ServiceInterface: "org.apache.pkg.mockService",
+		Version:          "1.0.0",
+		Group:            "Group",
+		Side:             "provider",
+	},
+}
+
+func TestServiceGetFilePathKey(t *testing.T) {
+	assert.Equal(t, "metadata/1.0.0/Group/provider/dubbo/revision1.0", serviceMetadataId.GetFilePathKey())
+}
+
+func TestServiceGetIdentifierKey(t *testing.T) {
+	assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:dubbo:revision1.0", serviceMetadataId.GetIdentifierKey())
+}
diff --git a/metadata/identifier/subscribe_metadata_identifier.go b/metadata/identifier/subscribe_metadata_identifier.go
index 38f3ebbd462338b581d83cd19403a00a5064b5a4..b1e37db971ada56a77bc3b716606b6fc8d137d34 100644
--- a/metadata/identifier/subscribe_metadata_identifier.go
+++ b/metadata/identifier/subscribe_metadata_identifier.go
@@ -19,16 +19,16 @@ package identifier
 
 // SubscriberMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision
 type SubscriberMetadataIdentifier struct {
-	revision string
-	BaseMetadataIdentifier
+	Revision string
+	MetadataIdentifier
 }
 
 // GetIdentifierKey returns string that format is service:Version:Group:Side:Revision
-func (mdi *SubscriberMetadataIdentifier) getIdentifierKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.revision)
+func (mdi *SubscriberMetadataIdentifier) GetIdentifierKey() string {
+	return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Revision)
 }
 
 // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Revision
-func (mdi *SubscriberMetadataIdentifier) getFilePathKey(params ...string) string {
-	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.revision)
+func (mdi *SubscriberMetadataIdentifier) GetFilePathKey() string {
+	return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Revision)
 }
diff --git a/metadata/identifier/subscribe_metadata_identifier_test.go b/metadata/identifier/subscribe_metadata_identifier_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..215aa3c5691f20d7790029093372389ce620398c
--- /dev/null
+++ b/metadata/identifier/subscribe_metadata_identifier_test.go
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+var subscribeMetadataId = &SubscriberMetadataIdentifier{
+	Revision: "1.0",
+	MetadataIdentifier: MetadataIdentifier{
+		BaseMetadataIdentifier: BaseMetadataIdentifier{
+			ServiceInterface: "org.apache.pkg.mockService",
+			Version:          "1.0.0",
+			Group:            "Group",
+			Side:             "provider",
+		},
+	},
+}
+
+func TestSubscribeGetFilePathKey(t *testing.T) {
+	assert.Equal(t, "metadata/1.0.0/Group/provider/1.0", subscribeMetadataId.GetFilePathKey())
+}
+
+func TestSubscribeGetIdentifierKey(t *testing.T) {
+	assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:1.0", subscribeMetadataId.GetIdentifierKey())
+}
diff --git a/metadata/namemapping/dynamic/service_name_mapping.go b/metadata/mapping/dynamic/service_name_mapping.go
similarity index 75%
rename from metadata/namemapping/dynamic/service_name_mapping.go
rename to metadata/mapping/dynamic/service_name_mapping.go
index e93c256fe093b4a3e3c431e1d012038b2bb7976b..84039ace9a2d56eca96bf36afc46d28e2a5ebe60 100644
--- a/metadata/namemapping/dynamic/service_name_mapping.go
+++ b/metadata/mapping/dynamic/service_name_mapping.go
@@ -19,6 +19,7 @@ package dynamic
 
 import (
 	"strconv"
+	"sync"
 	"time"
 )
 
@@ -28,18 +29,26 @@ import (
 )
 
 import (
+	commonCfg "github.com/apache/dubbo-go/common/config"
 	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/config"
 	"github.com/apache/dubbo-go/config_center"
-	"github.com/apache/dubbo-go/metadata"
+	"github.com/apache/dubbo-go/metadata/mapping"
 )
 
 const (
-	defaultGroup = config_center.DEFAULT_GROUP
+	defaultGroup = "mapping"
 	slash        = "/"
 )
 
+func init() {
+	extension.SetGlobalServiceNameMapping(GetNameMappingInstance)
+}
+
 // DynamicConfigurationServiceNameMapping is the implementation based on config center
+// it's a singleton
 type DynamicConfigurationServiceNameMapping struct {
 	dc config_center.DynamicConfiguration
 }
@@ -48,7 +57,8 @@ type DynamicConfigurationServiceNameMapping struct {
 func (d *DynamicConfigurationServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
 	// metadata service is admin service, should not be mapped
 	if constant.METADATA_SERVICE_NAME == serviceInterface {
-		return perrors.New("try to map the metadata service, will be ignored")
+		logger.Info("try to map the metadata service, will be ignored")
+		return nil
 	}
 
 	appName := config.GetApplicationConfig().Name
@@ -76,7 +86,16 @@ func (d *DynamicConfigurationServiceNameMapping) buildGroup(serviceInterface str
 	return defaultGroup + slash + serviceInterface
 }
 
-// NewServiceNameMapping will create an instance of DynamicConfigurationServiceNameMapping
-func NewServiceNameMapping(dc config_center.DynamicConfiguration) metadata.ServiceNameMapping {
-	return &DynamicConfigurationServiceNameMapping{dc: dc}
+var (
+	serviceNameMappingInstance *DynamicConfigurationServiceNameMapping
+	serviceNameMappingOnce     sync.Once
+)
+
+// GetNameMappingInstance return an instance, if not found, it creates one
+func GetNameMappingInstance() mapping.ServiceNameMapping {
+	serviceNameMappingOnce.Do(func() {
+		dc := commonCfg.GetEnvInstance().GetDynamicConfiguration()
+		serviceNameMappingInstance = &DynamicConfigurationServiceNameMapping{dc: dc}
+	})
+	return serviceNameMappingInstance
 }
diff --git a/metadata/namemapping/dynamic/service_name_mapping_test.go b/metadata/mapping/dynamic/service_name_mapping_test.go
similarity index 95%
rename from metadata/namemapping/dynamic/service_name_mapping_test.go
rename to metadata/mapping/dynamic/service_name_mapping_test.go
index e3d620cd738421c256d8fd232b1afcfd425ca989..2896b0fd4aa4fb6bada132c276c70a1653e59f99 100644
--- a/metadata/namemapping/dynamic/service_name_mapping_test.go
+++ b/metadata/mapping/dynamic/service_name_mapping_test.go
@@ -41,14 +41,14 @@ func TestDynamicConfigurationServiceNameMapping(t *testing.T) {
 	}).GetDynamicConfiguration(nil)
 	config.GetApplicationConfig().Name = appName
 
-	mapping := NewServiceNameMapping(dc)
+	mapping := &DynamicConfigurationServiceNameMapping{dc: dc}
 	intf := constant.METADATA_SERVICE_NAME
 	group := "myGroup"
 	version := "myVersion"
 	protocol := "myProtocol"
 
 	err = mapping.Map(intf, group, version, protocol)
-	assert.NotNil(t, err)
+	assert.Nil(t, err)
 	intf = "MyService"
 	err = mapping.Map(intf, group, version, protocol)
 	assert.Nil(t, err)
diff --git a/metadata/mapping/memory/service_name_mapping.go b/metadata/mapping/memory/service_name_mapping.go
new file mode 100644
index 0000000000000000000000000000000000000000..0965d52d91e047215abd9b7d14523ecaa833f0ed
--- /dev/null
+++ b/metadata/mapping/memory/service_name_mapping.go
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package memory
+
+import (
+	"sync"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+)
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/mapping"
+)
+
+func init() {
+	extension.SetGlobalServiceNameMapping(GetNameMappingInstance)
+}
+
+type InMemoryServiceNameMapping struct{}
+
+func (i *InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
+	return nil
+}
+
+func (i *InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+	return gxset.NewSet(config.GetApplicationConfig().Name), nil
+}
+
+var serviceNameMappingInstance *InMemoryServiceNameMapping
+var serviceNameMappingOnce sync.Once
+
+func GetNameMappingInstance() mapping.ServiceNameMapping {
+	serviceNameMappingOnce.Do(func() {
+		serviceNameMappingInstance = &InMemoryServiceNameMapping{}
+	})
+	return serviceNameMappingInstance
+}
diff --git a/metadata/service_name_mapping.go b/metadata/mapping/service_name_mapping.go
similarity index 98%
rename from metadata/service_name_mapping.go
rename to metadata/mapping/service_name_mapping.go
index c14e8ce2e7c40d1573897dfd6ba64c16e18acac7..6caed9f0b48c1bb9c2f0f1026eb642f69bb31113 100644
--- a/metadata/service_name_mapping.go
+++ b/metadata/mapping/service_name_mapping.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package metadata
+package mapping
 
 import (
 	gxset "github.com/dubbogo/gost/container/set"
diff --git a/metadata/report/consul/report.go b/metadata/report/consul/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..eb2bdc25ecec596a8f89abb80856b8d6e7be70a4
--- /dev/null
+++ b/metadata/report/consul/report.go
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package consul
+
+import (
+	consul "github.com/hashicorp/consul/api"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+)
+
+var (
+	emptyStrSlice = make([]string, 0)
+)
+
+func init() {
+	mf := &consulMetadataReportFactory{}
+	extension.SetMetadataReportFactory("consul", func() factory.MetadataReportFactory {
+		return mf
+	})
+}
+
+// consulMetadataReport is the implementation of
+// MetadataReport based on consul.
+type consulMetadataReport struct {
+	client *consul.Client
+}
+
+// StoreProviderMetadata stores the metadata.
+func (m *consulMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error {
+	kv := &consul.KVPair{Key: providerIdentifier.GetIdentifierKey(), Value: []byte(serviceDefinitions)}
+	_, err := m.client.KV().Put(kv, nil)
+	return err
+}
+
+// StoreConsumerMetadata stores the metadata.
+func (m *consulMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error {
+	kv := &consul.KVPair{Key: consumerMetadataIdentifier.GetIdentifierKey(), Value: []byte(serviceParameterString)}
+	_, err := m.client.KV().Put(kv, nil)
+	return err
+}
+
+// SaveServiceMetadata saves the metadata.
+func (m *consulMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	kv := &consul.KVPair{Key: metadataIdentifier.GetIdentifierKey(), Value: []byte(url.String())}
+	_, err := m.client.KV().Put(kv, nil)
+	return err
+}
+
+// RemoveServiceMetadata removes the metadata.
+func (m *consulMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error {
+	k := metadataIdentifier.GetIdentifierKey()
+	_, err := m.client.KV().Delete(k, nil)
+	return err
+}
+
+// GetExportedURLs gets the urls.
+func (m *consulMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) {
+	k := metadataIdentifier.GetIdentifierKey()
+	kv, _, err := m.client.KV().Get(k, nil)
+	if err != nil || kv == nil {
+		return emptyStrSlice, err
+	}
+	return []string{string(kv.Value)}, nil
+}
+
+// SaveSubscribedData saves the urls.
+func (m *consulMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error {
+	kv := &consul.KVPair{Key: subscriberMetadataIdentifier.GetIdentifierKey(), Value: []byte(urls)}
+	_, err := m.client.KV().Put(kv, nil)
+	return err
+}
+
+// GetSubscribedURLs gets the urls.
+func (m *consulMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	k := subscriberMetadataIdentifier.GetIdentifierKey()
+	kv, _, err := m.client.KV().Get(k, nil)
+	if err != nil || kv == nil {
+		return emptyStrSlice, err
+	}
+	return []string{string(kv.Value)}, nil
+}
+
+// GetServiceDefinition gets the service definition.
+func (m *consulMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) {
+	k := metadataIdentifier.GetIdentifierKey()
+	kv, _, err := m.client.KV().Get(k, nil)
+	if err != nil || kv == nil {
+		return "", err
+	}
+	return string(kv.Value), nil
+}
+
+type consulMetadataReportFactory struct {
+}
+
+// nolint
+func (mf *consulMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport {
+	config := &consul.Config{Address: url.Location}
+	client, err := consul.NewClient(config)
+	if err != nil {
+		panic(err)
+	}
+	return &consulMetadataReport{client: client}
+}
diff --git a/metadata/report/consul/report_test.go b/metadata/report/consul/report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..34ee29de945f2b9ac6978a55008048e62f4c6812
--- /dev/null
+++ b/metadata/report/consul/report_test.go
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package consul
+
+import (
+	"encoding/json"
+	"net/url"
+	"strconv"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/remoting/consul"
+)
+
+func newProviderRegistryUrl(host string, port int) *common.URL {
+	return common.NewURLWithOptions(
+		common.WithIp(host),
+		common.WithPort(strconv.Itoa(port)),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)),
+	)
+}
+
+func newBaseMetadataIdentifier(side string) *identifier.BaseMetadataIdentifier {
+	return &identifier.BaseMetadataIdentifier{
+		ServiceInterface: "org.apache.HelloWorld",
+		Version:          "1.0.0",
+		Group:            "group",
+		Side:             side,
+	}
+}
+
+func newMetadataIdentifier(side string) *identifier.MetadataIdentifier {
+	return &identifier.MetadataIdentifier{
+		Application:            "application",
+		BaseMetadataIdentifier: *newBaseMetadataIdentifier(side),
+	}
+}
+
+func newServiceMetadataIdentifier(side string) *identifier.ServiceMetadataIdentifier {
+	return &identifier.ServiceMetadataIdentifier{
+		Revision:               "1.0",
+		Protocol:               "dubbo",
+		BaseMetadataIdentifier: *newBaseMetadataIdentifier(side),
+	}
+}
+
+func newSubscribeMetadataIdentifier(side string) *identifier.SubscriberMetadataIdentifier {
+	return &identifier.SubscriberMetadataIdentifier{
+		Revision:           "1.0",
+		MetadataIdentifier: *newMetadataIdentifier(side),
+	}
+}
+
+type consulMetadataReportTestSuite struct {
+	t *testing.T
+	m report.MetadataReport
+}
+
+func newConsulMetadataReportTestSuite(t *testing.T, m report.MetadataReport) *consulMetadataReportTestSuite {
+	return &consulMetadataReportTestSuite{t: t, m: m}
+}
+
+func (suite *consulMetadataReportTestSuite) testStoreProviderMetadata() {
+	providerMi := newMetadataIdentifier("provider")
+	providerMeta := "provider"
+	err := suite.m.StoreProviderMetadata(providerMi, providerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testStoreConsumerMetadata() {
+	consumerMi := newMetadataIdentifier("consumer")
+	consumerMeta := "consumer"
+	err := suite.m.StoreProviderMetadata(consumerMi, consumerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	err := suite.m.SaveServiceMetadata(serviceMi, url)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testRemoveServiceMetadata() {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	err := suite.m.RemoveServiceMetadata(serviceMi)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testGetExportedURLs() {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	urls, err := suite.m.GetExportedURLs(serviceMi)
+	assert.Equal(suite.t, 1, len(urls))
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testSaveSubscribedData(url common.URL) {
+	subscribeMi := newSubscribeMetadataIdentifier("provider")
+	urls := []string{url.String()}
+	bytes, _ := json.Marshal(urls)
+	err := suite.m.SaveSubscribedData(subscribeMi, string(bytes))
+	assert.Nil(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testGetSubscribedURLs() {
+	subscribeMi := newSubscribeMetadataIdentifier("provider")
+	urls, err := suite.m.GetSubscribedURLs(subscribeMi)
+	assert.Equal(suite.t, 1, len(urls))
+	assert.NoError(suite.t, err)
+}
+
+func (suite *consulMetadataReportTestSuite) testGetServiceDefinition() {
+	providerMi := newMetadataIdentifier("provider")
+	providerMeta, err := suite.m.GetServiceDefinition(providerMi)
+	assert.Equal(suite.t, "provider", providerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func test1(t *testing.T) {
+	consulAgent := consul.NewConsulAgent(t, 8500)
+	defer consulAgent.Close()
+
+	url := newProviderRegistryUrl("localhost", 8500)
+	mf := extension.GetMetadataReportFactory("consul")
+	m := mf.CreateMetadataReport(url)
+
+	suite := newConsulMetadataReportTestSuite(t, m)
+	suite.testStoreProviderMetadata()
+	suite.testStoreConsumerMetadata()
+	suite.testSaveServiceMetadata(*url)
+	suite.testGetExportedURLs()
+	suite.testRemoveServiceMetadata()
+	suite.testSaveSubscribedData(*url)
+	suite.testGetSubscribedURLs()
+	suite.testGetServiceDefinition()
+}
+
+func TestConsulMetadataReport(t *testing.T) {
+	t.Run("test1", test1)
+}
diff --git a/metadata/report/delegate/delegate_report.go b/metadata/report/delegate/delegate_report.go
new file mode 100644
index 0000000000000000000000000000000000000000..cdd29ab2e483647ed90f37032e10d174f7583e39
--- /dev/null
+++ b/metadata/report/delegate/delegate_report.go
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package delegate
+
+import (
+	"encoding/json"
+	"runtime/debug"
+	"sync"
+	"time"
+)
+
+import (
+	"github.com/go-co-op/gocron"
+	perrors "github.com/pkg/errors"
+	"go.uber.org/atomic"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config/instance"
+	"github.com/apache/dubbo-go/metadata/definition"
+	"github.com/apache/dubbo-go/metadata/identifier"
+)
+
+const (
+	// defaultMetadataReportRetryTimes is defined for max  times to retry
+	defaultMetadataReportRetryTimes int64 = 100
+	// defaultMetadataReportRetryPeriod is defined for cycle interval to retry, the unit is second
+	defaultMetadataReportRetryPeriod int64 = 3
+	// defaultMetadataReportRetryPeriod is defined for cycle report or not
+	defaultMetadataReportCycleReport bool = true
+)
+
+// metadataReportRetry is a scheduler for retrying task
+type metadataReportRetry struct {
+	retryPeriod  int64
+	retryLimit   int64
+	scheduler    *gocron.Scheduler
+	job          *gocron.Job
+	retryCounter *atomic.Int64
+	// if no failed report, wait how many times to run retry task.
+	retryTimesIfNonFail int64
+}
+
+// newMetadataReportRetry will create a scheduler for retry task
+func newMetadataReportRetry(retryPeriod int64, retryLimit int64, retryFunc func() bool) (*metadataReportRetry, error) {
+	s1 := gocron.NewScheduler(time.UTC)
+
+	mrr := &metadataReportRetry{
+		retryPeriod:         retryPeriod,
+		retryLimit:          retryLimit,
+		scheduler:           s1,
+		retryCounter:        atomic.NewInt64(0),
+		retryTimesIfNonFail: 600,
+	}
+
+	newJob, err := mrr.scheduler.Every(uint64(mrr.retryPeriod)).Seconds().Do(
+		func() {
+			mrr.retryCounter.Inc()
+			logger.Infof("start to retry task for metadata report. retry times: %v", mrr.retryCounter.Load())
+			if mrr.retryCounter.Load() > mrr.retryLimit {
+				mrr.scheduler.Clear()
+			} else if retryFunc() && mrr.retryCounter.Load() > mrr.retryTimesIfNonFail {
+				mrr.scheduler.Clear() // may not interrupt the running job
+			}
+		})
+
+	mrr.job = newJob
+	return mrr, err
+}
+
+// startRetryTask will make scheduler with retry task run
+func (mrr *metadataReportRetry) startRetryTask() {
+	mrr.scheduler.StartAt(time.Now().Add(500 * time.Millisecond))
+	mrr.scheduler.Start()
+}
+
+// MetadataReport is a absolute delegate for MetadataReport
+type MetadataReport struct {
+	reportUrl           common.URL
+	syncReport          bool
+	metadataReportRetry *metadataReportRetry
+
+	failedReports     map[*identifier.MetadataIdentifier]interface{}
+	failedReportsLock sync.RWMutex
+
+	// allMetadataReports store all the metdadata reports records in memory
+	allMetadataReports     map[*identifier.MetadataIdentifier]interface{}
+	allMetadataReportsLock sync.RWMutex
+}
+
+// NewMetadataReport will create a MetadataReport with initiation
+func NewMetadataReport() (*MetadataReport, error) {
+	url := instance.GetMetadataReportUrl()
+	bmr := &MetadataReport{
+		reportUrl:          url,
+		syncReport:         url.GetParamBool(constant.SYNC_REPORT_KEY, false),
+		failedReports:      make(map[*identifier.MetadataIdentifier]interface{}, 4),
+		allMetadataReports: make(map[*identifier.MetadataIdentifier]interface{}, 4),
+	}
+
+	mrr, err := newMetadataReportRetry(
+		url.GetParamInt(constant.RETRY_PERIOD_KEY, defaultMetadataReportRetryPeriod),
+		url.GetParamInt(constant.RETRY_TIMES_KEY, defaultMetadataReportRetryTimes),
+		bmr.retry,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	bmr.metadataReportRetry = mrr
+	if url.GetParamBool(constant.CYCLE_REPORT_KEY, defaultMetadataReportCycleReport) {
+		scheduler := gocron.NewScheduler(time.UTC)
+		_, err := scheduler.Every(1).Day().Do(
+			func() {
+				logger.Infof("start to publish all metadata in metadata report %s.", url.String())
+				bmr.allMetadataReportsLock.RLock()
+				bmr.doHandlerMetadataCollection(bmr.allMetadataReports)
+				bmr.allMetadataReportsLock.RUnlock()
+
+			})
+		if err != nil {
+			return nil, err
+		}
+		scheduler.StartAt(time.Now().Add(500 * time.Millisecond))
+		scheduler.Start()
+	}
+	return bmr, nil
+}
+
+// retry will do metadata failed reports collection by call metadata report sdk
+func (mr *MetadataReport) retry() bool {
+	mr.failedReportsLock.RLock()
+	defer mr.failedReportsLock.RUnlock()
+	return mr.doHandlerMetadataCollection(mr.failedReports)
+}
+
+// StoreProviderMetadata will delegate to call remote metadata's sdk to store provider service definition
+func (mr *MetadataReport) StoreProviderMetadata(identifier *identifier.MetadataIdentifier, definer definition.ServiceDefiner) {
+	if mr.syncReport {
+		mr.storeMetadataTask(common.PROVIDER, identifier, definer)
+	}
+	go mr.storeMetadataTask(common.PROVIDER, identifier, definer)
+}
+
+// storeMetadataTask will delegate to call remote metadata's sdk to store
+func (mr *MetadataReport) storeMetadataTask(role int, identifier *identifier.MetadataIdentifier, definer interface{}) {
+	logger.Infof("store provider metadata. Identifier :%v ; definition: %v .", identifier, definer)
+	mr.allMetadataReportsLock.Lock()
+	mr.allMetadataReports[identifier] = definer
+	mr.allMetadataReportsLock.Unlock()
+
+	mr.failedReportsLock.Lock()
+	delete(mr.failedReports, identifier)
+	mr.failedReportsLock.Unlock()
+	// data is store the json marshaled definition
+	var (
+		data []byte
+		err  error
+	)
+
+	defer func() {
+		if r := recover(); r != nil {
+			mr.failedReportsLock.Lock()
+			mr.failedReports[identifier] = definer
+			mr.failedReportsLock.Unlock()
+			mr.metadataReportRetry.startRetryTask()
+			logger.Errorf("Failed to put provider metadata %v in %v, cause: %v\n%s\n",
+				identifier, string(data), r, string(debug.Stack()))
+		}
+	}()
+
+	data, err = json.Marshal(definer)
+	if err != nil {
+		logger.Errorf("storeProviderMetadataTask error in stage json.Marshal, msg is %+v", err)
+		panic(err)
+	}
+	report := instance.GetMetadataReportInstance()
+	if role == common.PROVIDER {
+		err = report.StoreProviderMetadata(identifier, string(data))
+	} else if role == common.CONSUMER {
+		err = report.StoreConsumerMetadata(identifier, string(data))
+	}
+
+	if err != nil {
+		logger.Errorf("storeProviderMetadataTask error in stage call  metadata report to StoreProviderMetadata, msg is %+v", err)
+		panic(err)
+	}
+}
+
+// StoreConsumerMetadata will delegate to call remote metadata's sdk to store consumer side service definition
+func (mr *MetadataReport) StoreConsumerMetadata(identifier *identifier.MetadataIdentifier, definer map[string]string) {
+	if mr.syncReport {
+		mr.storeMetadataTask(common.CONSUMER, identifier, definer)
+	}
+	go mr.storeMetadataTask(common.CONSUMER, identifier, definer)
+}
+
+// SaveServiceMetadata will delegate to call remote metadata's sdk to save service metadata
+func (mr *MetadataReport) SaveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	report := instance.GetMetadataReportInstance()
+	if mr.syncReport {
+		return report.SaveServiceMetadata(identifier, url)
+	}
+	go report.SaveServiceMetadata(identifier, url)
+	return nil
+}
+
+// RemoveServiceMetadata will delegate to call remote metadata's sdk to remove service metadata
+func (mr *MetadataReport) RemoveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier) error {
+	report := instance.GetMetadataReportInstance()
+	if mr.syncReport {
+		return report.RemoveServiceMetadata(identifier)
+	}
+	go report.RemoveServiceMetadata(identifier)
+	return nil
+}
+
+// GetExportedURLs will delegate to call remote metadata's sdk to get exported urls
+func (mr *MetadataReport) GetExportedURLs(identifier *identifier.ServiceMetadataIdentifier) ([]string, error) {
+	report := instance.GetMetadataReportInstance()
+	return report.GetExportedURLs(identifier)
+}
+
+// SaveSubscribedData will delegate to call remote metadata's sdk to save subscribed data
+func (mr *MetadataReport) SaveSubscribedData(identifier *identifier.SubscriberMetadataIdentifier, urls []common.URL) error {
+	urlStrList := make([]string, 0, len(urls))
+	for _, url := range urls {
+		urlStrList = append(urlStrList, url.String())
+	}
+	bytes, err := json.Marshal(urlStrList)
+	if err != nil {
+		return perrors.WithMessage(err, "Could not convert the array to json")
+	}
+
+	report := instance.GetMetadataReportInstance()
+	if mr.syncReport {
+		return report.SaveSubscribedData(identifier, string(bytes))
+	}
+	go report.SaveSubscribedData(identifier, string(bytes))
+	return nil
+}
+
+// GetSubscribedURLs will delegate to call remote metadata's sdk to get subscribed urls
+func (MetadataReport) GetSubscribedURLs(identifier *identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	report := instance.GetMetadataReportInstance()
+	return report.GetSubscribedURLs(identifier)
+}
+
+// GetServiceDefinition will delegate to call remote metadata's sdk to get service definitions
+func (MetadataReport) GetServiceDefinition(identifier *identifier.MetadataIdentifier) (string, error) {
+	report := instance.GetMetadataReportInstance()
+	return report.GetServiceDefinition(identifier)
+}
+
+// doHandlerMetadataCollection will store metadata to metadata support with given metadataMap
+func (mr *MetadataReport) doHandlerMetadataCollection(metadataMap map[*identifier.MetadataIdentifier]interface{}) bool {
+	if len(metadataMap) == 0 {
+		return true
+	}
+	for e := range metadataMap {
+		if common.RoleType(common.PROVIDER).Role() == e.Side {
+			mr.StoreProviderMetadata(e, metadataMap[e].(*definition.ServiceDefinition))
+		} else if common.RoleType(common.CONSUMER).Role() == e.Side {
+			mr.StoreConsumerMetadata(e, metadataMap[e].(map[string]string))
+		}
+	}
+	return false
+}
diff --git a/metadata/report/delegate/delegate_report_test.go b/metadata/report/delegate/delegate_report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3dfca577ba06598b90c553048777951c8823b256
--- /dev/null
+++ b/metadata/report/delegate/delegate_report_test.go
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package delegate
+
+import (
+	"fmt"
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/atomic"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config/instance"
+	"github.com/apache/dubbo-go/metadata/definition"
+	"github.com/apache/dubbo-go/metadata/identifier"
+)
+
+func TestMetadataReport_MetadataReportRetry(t *testing.T) {
+	counter := atomic.NewInt64(1)
+
+	retry, err := newMetadataReportRetry(1, 10, func() bool {
+		counter.Add(1)
+		return true
+	})
+	assert.NoError(t, err)
+	retry.startRetryTask()
+	itsTime := time.After(2500 * time.Millisecond)
+	select {
+	case <-itsTime:
+		retry.scheduler.Clear()
+		assert.Equal(t, counter.Load(), int64(3))
+		logger.Info("over")
+	}
+}
+
+func TestMetadataReport_MetadataReportRetryWithLimit(t *testing.T) {
+	counter := atomic.NewInt64(1)
+
+	retry, err := newMetadataReportRetry(1, 1, func() bool {
+		counter.Add(1)
+		return true
+	})
+	assert.NoError(t, err)
+	retry.startRetryTask()
+	itsTime := time.After(2500 * time.Millisecond)
+	select {
+	case <-itsTime:
+		retry.scheduler.Clear()
+		assert.Equal(t, counter.Load(), int64(2))
+		logger.Info("over")
+	}
+}
+
+func mockNewMetadataReport(t *testing.T) *MetadataReport {
+	syncReportKey := "false"
+	retryPeriodKey := "3"
+	retryTimesKey := "100"
+	cycleReportKey := "true"
+
+	url, err := common.NewURL(fmt.Sprintf(
+		"test://127.0.0.1:20000/?"+constant.SYNC_REPORT_KEY+"=%v&"+constant.RETRY_PERIOD_KEY+"=%v&"+
+			constant.RETRY_TIMES_KEY+"=%v&"+constant.CYCLE_REPORT_KEY+"=%v",
+		syncReportKey, retryPeriodKey, retryTimesKey, cycleReportKey))
+	assert.NoError(t, err)
+	instance.SetMetadataReportUrl(url)
+	mtr, err := NewMetadataReport()
+	assert.NoError(t, err)
+	assert.NotNil(t, mtr)
+	return mtr
+}
+
+func TestMetadataReport_StoreProviderMetadata(t *testing.T) {
+	mtr := mockNewMetadataReport(t)
+	var metadataId = &identifier.MetadataIdentifier{
+		Application: "app",
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: "com.ikurento.user.UserProvider",
+			Version:          "0.0.1",
+			Group:            "group1",
+			Side:             "provider",
+		},
+	}
+
+	mtr.StoreProviderMetadata(metadataId, getMockDefinition(metadataId, t))
+}
+
+func getMockDefinition(id *identifier.MetadataIdentifier, t *testing.T) *definition.ServiceDefinition {
+	protocol := "dubbo"
+	beanName := "UserProvider"
+	url, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, id.ServiceInterface, id.Group, id.Version, beanName))
+	assert.NoError(t, err)
+	_, err = common.ServiceMap.Register(id.ServiceInterface, protocol, &definition.UserProvider{})
+	assert.NoError(t, err)
+	service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service()))
+	return definition.BuildServiceDefinition(*service, url)
+}
diff --git a/metadata/report/etcd/report.go b/metadata/report/etcd/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..097835c986962850d137255ec807b417de1484ad
--- /dev/null
+++ b/metadata/report/etcd/report.go
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package etcd
+
+import (
+	"strings"
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+	"github.com/apache/dubbo-go/remoting/etcdv3"
+)
+
+const DEFAULT_ROOT = "dubbo"
+
+func init() {
+	extension.SetMetadataReportFactory(constant.ETCDV3_KEY, func() factory.MetadataReportFactory {
+		return &etcdMetadataReportFactory{}
+	})
+}
+
+// etcdMetadataReport is the implementation of MetadataReport based etcd
+type etcdMetadataReport struct {
+	client *etcdv3.Client
+	root   string
+}
+
+// StoreProviderMetadata will store the metadata
+// metadata including the basic info of the server, provider info, and other user custom info
+func (e *etcdMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error {
+	key := e.getNodeKey(providerIdentifier)
+	return e.client.Create(key, serviceDefinitions)
+}
+
+// StoreConsumerMetadata will store the metadata
+// metadata including the basic info of the server, consumer info, and other user custom info
+func (e *etcdMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error {
+	key := e.getNodeKey(consumerMetadataIdentifier)
+	return e.client.Create(key, serviceParameterString)
+}
+
+// SaveServiceMetadata will store the metadata
+// metadata including the basic info of the server, service info, and other user custom info
+func (e *etcdMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	key := e.getNodeKey(metadataIdentifier)
+	return e.client.Create(key, url.String())
+}
+
+// RemoveServiceMetadata will remove the service metadata
+func (e *etcdMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error {
+	return e.client.Delete(e.getNodeKey(metadataIdentifier))
+}
+
+// GetExportedURLs will look up the exported urls.
+// if not found, an empty list will be returned.
+func (e *etcdMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) {
+	content, err := e.client.Get(e.getNodeKey(metadataIdentifier))
+	if err != nil {
+		logger.Errorf("etcdMetadataReport GetExportedURLs err:{%v}", err.Error())
+		return []string{}, err
+	}
+	if content == "" {
+		return []string{}, nil
+	}
+	return []string{content}, nil
+}
+
+// SaveSubscribedData will convert the urlList to json array and then store it
+func (e *etcdMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error {
+	key := e.getNodeKey(subscriberMetadataIdentifier)
+	return e.client.Create(key, urls)
+}
+
+// GetSubscribedURLs will lookup the url
+// if not found, an empty list will be returned
+func (e *etcdMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	content, err := e.client.Get(e.getNodeKey(subscriberMetadataIdentifier))
+	if err != nil {
+		logger.Errorf("etcdMetadataReport GetSubscribedURLs err:{%v}", err.Error())
+		return nil, err
+	}
+	return []string{content}, nil
+}
+
+// GetServiceDefinition will lookup the service definition
+func (e *etcdMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) {
+	key := e.getNodeKey(metadataIdentifier)
+	content, err := e.client.Get(key)
+	if err != nil {
+		logger.Errorf("etcdMetadataReport GetServiceDefinition err:{%v}", err.Error())
+		return "", err
+	}
+	return content, nil
+}
+
+type etcdMetadataReportFactory struct{}
+
+// CreateMetadataReport get the MetadataReport instance of etcd
+func (e *etcdMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport {
+	timeout, _ := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
+	addresses := strings.Split(url.Location, ",")
+	client, err := etcdv3.NewClient(etcdv3.MetadataETCDV3Client, addresses, timeout, 1)
+	if err != nil {
+		logger.Errorf("Could not create etcd metadata report. URL: %s,error:{%v}", url.String(), err)
+		return nil
+	}
+	group := url.GetParam(constant.GROUP_KEY, DEFAULT_ROOT)
+	group = constant.PATH_SEPARATOR + strings.TrimPrefix(group, constant.PATH_SEPARATOR)
+	return &etcdMetadataReport{client: client, root: group}
+}
+
+func (e *etcdMetadataReport) getNodeKey(MetadataIdentifier identifier.IMetadataIdentifier) string {
+	var rootDir string
+	if e.root == constant.PATH_SEPARATOR {
+		rootDir = e.root
+	} else {
+		rootDir = e.root + constant.PATH_SEPARATOR
+	}
+	return rootDir + MetadataIdentifier.GetFilePathKey()
+}
diff --git a/metadata/report/etcd/report_test.go b/metadata/report/etcd/report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5dd8780b48a741a8eb8735b059bc3617a100f63d
--- /dev/null
+++ b/metadata/report/etcd/report_test.go
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package etcd
+
+import (
+	"encoding/json"
+	"net/url"
+	"strconv"
+	"testing"
+)
+
+import (
+	"github.com/coreos/etcd/embed"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/metadata/identifier"
+)
+
+const defaultEtcdV3WorkDir = "/tmp/default-dubbo-go-registry.etcd"
+
+func initEtcd(t *testing.T) *embed.Etcd {
+	DefaultListenPeerURLs := "http://localhost:2380"
+	DefaultListenClientURLs := "http://localhost:2379"
+	lpurl, _ := url.Parse(DefaultListenPeerURLs)
+	lcurl, _ := url.Parse(DefaultListenClientURLs)
+	cfg := embed.NewConfig()
+	cfg.LPUrls = []url.URL{*lpurl}
+	cfg.LCUrls = []url.URL{*lcurl}
+	cfg.Dir = defaultEtcdV3WorkDir
+	e, err := embed.StartEtcd(cfg)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return e
+}
+
+func TestEtcdMetadataReportFactory_CreateMetadataReport(t *testing.T) {
+	e := initEtcd(t)
+	url, err := common.NewURL("registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	if err != nil {
+		t.Fatal(err)
+	}
+	metadataReportFactory := &etcdMetadataReportFactory{}
+	metadataReport := metadataReportFactory.CreateMetadataReport(&url)
+	assert.NotNil(t, metadataReport)
+	e.Close()
+}
+
+func TestEtcdMetadataReport_CRUD(t *testing.T) {
+	e := initEtcd(t)
+	url, err := common.NewURL("registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	if err != nil {
+		t.Fatal(err)
+	}
+	metadataReportFactory := &etcdMetadataReportFactory{}
+	metadataReport := metadataReportFactory.CreateMetadataReport(&url)
+	assert.NotNil(t, metadataReport)
+
+	err = metadataReport.StoreConsumerMetadata(newMetadataIdentifier("consumer"), "consumer metadata")
+	assert.Nil(t, err)
+
+	err = metadataReport.StoreProviderMetadata(newMetadataIdentifier("provider"), "provider metadata")
+	assert.Nil(t, err)
+
+	serviceMi := newServiceMetadataIdentifier()
+	serviceUrl, _ := common.NewURL("registry://localhost:8848", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	metadataReport.SaveServiceMetadata(serviceMi, serviceUrl)
+	assert.Nil(t, err)
+
+	subMi := newSubscribeMetadataIdentifier()
+	urlList := make([]string, 0, 1)
+	urlList = append(urlList, serviceUrl.String())
+	urls, _ := json.Marshal(urlList)
+	err = metadataReport.SaveSubscribedData(subMi, string(urls))
+	assert.Nil(t, err)
+
+	err = metadataReport.RemoveServiceMetadata(serviceMi)
+	assert.Nil(t, err)
+
+	e.Close()
+}
+
+func newSubscribeMetadataIdentifier() *identifier.SubscriberMetadataIdentifier {
+	return &identifier.SubscriberMetadataIdentifier{
+		Revision:           "subscribe",
+		MetadataIdentifier: *newMetadataIdentifier("provider"),
+	}
+
+}
+
+func newServiceMetadataIdentifier() *identifier.ServiceMetadataIdentifier {
+	return &identifier.ServiceMetadataIdentifier{
+		Protocol: "nacos",
+		Revision: "a",
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: "com.test.MyTest",
+			Version:          "1.0.0",
+			Group:            "test_group",
+			Side:             "service",
+		},
+	}
+}
+
+func newMetadataIdentifier(side string) *identifier.MetadataIdentifier {
+	return &identifier.MetadataIdentifier{
+		Application: "test",
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: "com.test.MyTest",
+			Version:          "1.0.0",
+			Group:            "test_group",
+			Side:             side,
+		},
+	}
+}
diff --git a/metadata/report_factory.go b/metadata/report/factory/report_factory.go
similarity index 79%
rename from metadata/report_factory.go
rename to metadata/report/factory/report_factory.go
index 19b1004eee57073acec13c7f114179c47c73f145..9f00007cefbd5737c9c53d69924eba1d556c0023 100644
--- a/metadata/report_factory.go
+++ b/metadata/report/factory/report_factory.go
@@ -15,16 +15,17 @@
  * limitations under the License.
  */
 
-package metadata
+package factory
 
 import (
 	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/metadata/report"
 )
 
-var (
-	MetadataReportInstance MetadataReport
-)
-
+// MetadataReportFactory interface will create metadata report
 type MetadataReportFactory interface {
-	CreateMetadataReport(*common.URL) MetadataReport
+	CreateMetadataReport(*common.URL) report.MetadataReport
+}
+
+type BaseMetadataReportFactory struct {
 }
diff --git a/metadata/report/nacos/report.go b/metadata/report/nacos/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..d69913bd8fbb04da2d50770c1196917cb1efdaa5
--- /dev/null
+++ b/metadata/report/nacos/report.go
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nacos
+
+import (
+	"net/url"
+)
+
+import (
+	"github.com/nacos-group/nacos-sdk-go/clients/config_client"
+	"github.com/nacos-group/nacos-sdk-go/vo"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+	"github.com/apache/dubbo-go/remoting/nacos"
+)
+
+func init() {
+	mf := &nacosMetadataReportFactory{}
+	extension.SetMetadataReportFactory("nacos", func() factory.MetadataReportFactory {
+		return mf
+	})
+}
+
+// nacosMetadataReport is the implementation
+// of MetadataReport based on nacos.
+type nacosMetadataReport struct {
+	client config_client.IConfigClient
+}
+
+// StoreProviderMetadata stores the metadata.
+func (n *nacosMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error {
+	return n.storeMetadata(vo.ConfigParam{
+		DataId:  providerIdentifier.GetIdentifierKey(),
+		Group:   providerIdentifier.Group,
+		Content: serviceDefinitions,
+	})
+}
+
+// StoreConsumerMetadata stores the metadata.
+func (n *nacosMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error {
+	return n.storeMetadata(vo.ConfigParam{
+		DataId:  consumerMetadataIdentifier.GetIdentifierKey(),
+		Group:   consumerMetadataIdentifier.Group,
+		Content: serviceParameterString,
+	})
+}
+
+// SaveServiceMetadata saves the metadata.
+func (n *nacosMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	return n.storeMetadata(vo.ConfigParam{
+		DataId:  metadataIdentifier.GetIdentifierKey(),
+		Group:   metadataIdentifier.Group,
+		Content: url.String(),
+	})
+}
+
+// RemoveServiceMetadata removes the metadata.
+func (n *nacosMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error {
+	return n.deleteMetadata(vo.ConfigParam{
+		DataId: metadataIdentifier.GetIdentifierKey(),
+		Group:  metadataIdentifier.Group,
+	})
+}
+
+// GetExportedURLs gets the urls.
+func (n *nacosMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) {
+	return n.getConfigAsArray(vo.ConfigParam{
+		DataId: metadataIdentifier.GetIdentifierKey(),
+		Group:  metadataIdentifier.Group,
+	})
+}
+
+// SaveSubscribedData saves the urls.
+func (n *nacosMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error {
+	return n.storeMetadata(vo.ConfigParam{
+		DataId:  subscriberMetadataIdentifier.GetIdentifierKey(),
+		Group:   subscriberMetadataIdentifier.Group,
+		Content: urls,
+	})
+}
+
+// GetSubscribedURLs gets the urls.
+func (n *nacosMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	return n.getConfigAsArray(vo.ConfigParam{
+		DataId: subscriberMetadataIdentifier.GetIdentifierKey(),
+		Group:  subscriberMetadataIdentifier.Group,
+	})
+}
+
+// GetServiceDefinition gets the service definition.
+func (n *nacosMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) {
+	return n.getConfig(vo.ConfigParam{
+		DataId: metadataIdentifier.GetIdentifierKey(),
+		Group:  metadataIdentifier.Group,
+	})
+}
+
+// storeMetadata will publish the metadata to Nacos
+// if failed or error is not nil, error will be returned
+func (n *nacosMetadataReport) storeMetadata(param vo.ConfigParam) error {
+	res, err := n.client.PublishConfig(param)
+	if err != nil {
+		return perrors.WithMessage(err, "Could not publish the metadata")
+	}
+	if !res {
+		return perrors.New("Publish the metadata failed.")
+	}
+	return nil
+}
+
+// deleteMetadata will delete the metadata
+func (n *nacosMetadataReport) deleteMetadata(param vo.ConfigParam) error {
+	res, err := n.client.DeleteConfig(param)
+	if err != nil {
+		return perrors.WithMessage(err, "Could not delete the metadata")
+	}
+	if !res {
+		return perrors.New("Deleting the metadata failed.")
+	}
+	return nil
+}
+
+// getConfigAsArray will read the config and then convert it as an one-element array
+// error or config not found, an empty list will be returned.
+func (n *nacosMetadataReport) getConfigAsArray(param vo.ConfigParam) ([]string, error) {
+	res := make([]string, 0, 1)
+
+	cfg, err := n.getConfig(param)
+	if err != nil || len(cfg) == 0 {
+		return res, err
+	}
+
+	decodeCfg, err := url.QueryUnescape(cfg)
+	if err != nil {
+		logger.Errorf("The config is invalid: %s", cfg)
+		return res, err
+	}
+
+	res = append(res, decodeCfg)
+	return res, nil
+}
+
+// getConfig will read the config
+func (n *nacosMetadataReport) getConfig(param vo.ConfigParam) (string, error) {
+	cfg, err := n.client.GetConfig(param)
+	if err != nil {
+		logger.Errorf("Finding the configuration failed: %v", param)
+		return "", err
+	}
+	return cfg, nil
+}
+
+type nacosMetadataReportFactory struct {
+}
+
+// nolint
+func (n *nacosMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport {
+	client, err := nacos.NewNacosConfigClient(url)
+	if err != nil {
+		logger.Errorf("Could not create nacos metadata report. URL: %s", url.String())
+		return nil
+	}
+	return &nacosMetadataReport{client: client}
+}
diff --git a/metadata/report/nacos/report_test.go b/metadata/report/nacos/report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..be01eb22f7e95966c3bf816fdf648629b64380a3
--- /dev/null
+++ b/metadata/report/nacos/report_test.go
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nacos
+
+import (
+	"encoding/json"
+	"strconv"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+)
+
+func TestNacosMetadataReport_CRUD(t *testing.T) {
+	rpt := newTestReport()
+	assert.NotNil(t, rpt)
+
+	providerMi := newMetadataIdentifier("server")
+	providerMeta := "provider"
+	err := rpt.StoreProviderMetadata(providerMi, providerMeta)
+	assert.Nil(t, err)
+
+	consumerMi := newMetadataIdentifier("client")
+	consumerMeta := "consumer"
+	err = rpt.StoreConsumerMetadata(consumerMi, consumerMeta)
+	assert.Nil(t, err)
+
+	serviceMi := newServiceMetadataIdentifier()
+	serviceUrl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	err = rpt.SaveServiceMetadata(serviceMi, serviceUrl)
+	assert.Nil(t, err)
+
+	exportedUrls, err := rpt.GetExportedURLs(serviceMi)
+	assert.Equal(t, 1, len(exportedUrls))
+	assert.Nil(t, err)
+
+	subMi := newSubscribeMetadataIdentifier()
+	urls := []string{serviceUrl.String()}
+	bytes, _ := json.Marshal(urls)
+	err = rpt.SaveSubscribedData(subMi, string(bytes))
+	assert.Nil(t, err)
+
+	subscribeUrl, err := rpt.GetSubscribedURLs(subMi)
+	assert.Equal(t, 1, len(subscribeUrl))
+	assert.Nil(t, err)
+
+	err = rpt.RemoveServiceMetadata(serviceMi)
+	assert.Nil(t, err)
+}
+
+func newSubscribeMetadataIdentifier() *identifier.SubscriberMetadataIdentifier {
+	return &identifier.SubscriberMetadataIdentifier{
+		Revision:           "subscribe",
+		MetadataIdentifier: *newMetadataIdentifier("provider"),
+	}
+}
+
+func newServiceMetadataIdentifier() *identifier.ServiceMetadataIdentifier {
+	return &identifier.ServiceMetadataIdentifier{
+		Protocol: "nacos",
+		Revision: "a",
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: "com.test.MyTest",
+			Version:          "1.0.0",
+			Group:            "test_group",
+			Side:             "service",
+		},
+	}
+}
+
+func newMetadataIdentifier(side string) *identifier.MetadataIdentifier {
+	return &identifier.MetadataIdentifier{
+		Application: "test",
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: "com.test.MyTest",
+			Version:          "1.0.0",
+			Group:            "test_group",
+			Side:             side,
+		},
+	}
+}
+
+func TestNacosMetadataReportFactory_CreateMetadataReport(t *testing.T) {
+	res := newTestReport()
+	assert.NotNil(t, res)
+}
+
+func newTestReport() report.MetadataReport {
+	regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	res := extension.GetMetadataReportFactory("nacos").CreateMetadataReport(&regurl)
+	return res
+}
diff --git a/metadata/report/report.go b/metadata/report/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..62a9055e843297bd0d69ad94cb09ece64efda85f
--- /dev/null
+++ b/metadata/report/report.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package report
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/metadata/identifier"
+)
+
+// MetadataReport is an interface of
+// remote metadata report.
+type MetadataReport interface {
+	// StoreProviderMetadata stores the metadata.
+	// Metadata includes the basic info of the server,
+	// provider info, and other user custom info.
+	StoreProviderMetadata(*identifier.MetadataIdentifier, string) error
+
+	// StoreConsumerMetadata stores the metadata.
+	// Metadata includes the basic info of the server,
+	// consumer info, and other user custom info.
+	StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error
+
+	// SaveServiceMetadata saves the metadata.
+	// Metadata includes the basic info of the server,
+	// service info, and other user custom info.
+	SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error
+
+	// RemoveServiceMetadata removes the metadata.
+	RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error
+
+	// GetExportedURLs gets the urls.
+	// If not found, an empty list will be returned.
+	GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error)
+
+	// SaveSubscribedData saves the urls.
+	// If not found, an empty str will be returned.
+	SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error
+
+	// GetSubscribedURLs gets the urls.
+	// If not found, an empty list will be returned.
+	GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error)
+
+	// GetServiceDefinition gets the service definition.
+	GetServiceDefinition(*identifier.MetadataIdentifier) (string, error)
+}
diff --git a/metadata/report/zookeeper/report.go b/metadata/report/zookeeper/report.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f46bb023054ec27ca0dbff2c249dc0135af07cd
--- /dev/null
+++ b/metadata/report/zookeeper/report.go
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package zookeeper
+
+import (
+	"strings"
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+	"github.com/apache/dubbo-go/remoting/zookeeper"
+)
+
+var (
+	emptyStrSlice = make([]string, 0)
+)
+
+func init() {
+	mf := &zookeeperMetadataReportFactory{}
+	extension.SetMetadataReportFactory("zookeeper", func() factory.MetadataReportFactory {
+		return mf
+	})
+}
+
+// zookeeperMetadataReport is the implementation of
+// MetadataReport based on zookeeper.
+type zookeeperMetadataReport struct {
+	client  *zookeeper.ZookeeperClient
+	rootDir string
+}
+
+// StoreProviderMetadata stores the metadata.
+func (m *zookeeperMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error {
+	k := m.rootDir + providerIdentifier.GetFilePathKey()
+	return m.client.CreateWithValue(k, []byte(serviceDefinitions))
+}
+
+// StoreConsumerMetadata stores the metadata.
+func (m *zookeeperMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error {
+	k := m.rootDir + consumerMetadataIdentifier.GetFilePathKey()
+	return m.client.CreateWithValue(k, []byte(serviceParameterString))
+}
+
+// SaveServiceMetadata saves the metadata.
+func (m *zookeeperMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	k := m.rootDir + metadataIdentifier.GetFilePathKey()
+	return m.client.CreateWithValue(k, []byte(url.String()))
+}
+
+// RemoveServiceMetadata removes the metadata.
+func (m *zookeeperMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error {
+	k := m.rootDir + metadataIdentifier.GetFilePathKey()
+	return m.client.Delete(k)
+}
+
+// GetExportedURLs gets the urls.
+func (m *zookeeperMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) {
+	k := m.rootDir + metadataIdentifier.GetFilePathKey()
+	v, _, err := m.client.GetContent(k)
+	if err != nil || len(v) == 0 {
+		return emptyStrSlice, err
+	}
+	return []string{string(v)}, nil
+}
+
+// SaveSubscribedData saves the urls.
+func (m *zookeeperMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error {
+	k := m.rootDir + subscriberMetadataIdentifier.GetFilePathKey()
+	return m.client.CreateWithValue(k, []byte(urls))
+}
+
+// GetSubscribedURLs gets the urls.
+func (m *zookeeperMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	k := m.rootDir + subscriberMetadataIdentifier.GetFilePathKey()
+	v, _, err := m.client.GetContent(k)
+	if err != nil || len(v) == 0 {
+		return emptyStrSlice, err
+	}
+	return []string{string(v)}, nil
+}
+
+// GetServiceDefinition gets the service definition.
+func (m *zookeeperMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) {
+	k := m.rootDir + metadataIdentifier.GetFilePathKey()
+	v, _, err := m.client.GetContent(k)
+	return string(v), err
+}
+
+type zookeeperMetadataReportFactory struct {
+}
+
+// nolint
+func (mf *zookeeperMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport {
+	client, err := zookeeper.NewZookeeperClient(
+		"zookeeperMetadataReport",
+		strings.Split(url.Location, ","),
+		15*time.Second,
+	)
+	if err != nil {
+		panic(err)
+	}
+
+	rootDir := url.GetParam(constant.GROUP_KEY, "dubbo")
+	if !strings.HasPrefix(rootDir, constant.PATH_SEPARATOR) {
+		rootDir = constant.PATH_SEPARATOR + rootDir
+	}
+	if rootDir != constant.PATH_SEPARATOR {
+		rootDir = rootDir + constant.PATH_SEPARATOR
+	}
+
+	return &zookeeperMetadataReport{client: client, rootDir: rootDir}
+}
diff --git a/metadata/report/zookeeper/report_test.go b/metadata/report/zookeeper/report_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1e46e2e8d019c0415699ee409833b392a85b504
--- /dev/null
+++ b/metadata/report/zookeeper/report_test.go
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package zookeeper
+
+import (
+	"encoding/json"
+	"net/url"
+	"strconv"
+	"testing"
+)
+
+import (
+	"github.com/dubbogo/go-zookeeper/zk"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+)
+
+func newProviderRegistryUrl(host string, port int) *common.URL {
+	return common.NewURLWithOptions(
+		common.WithIp(host),
+		common.WithPort(strconv.Itoa(port)),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)),
+	)
+}
+
+func newBaseMetadataIdentifier(side string) *identifier.BaseMetadataIdentifier {
+	return &identifier.BaseMetadataIdentifier{
+		ServiceInterface: "org.apache.HelloWorld",
+		Version:          "1.0.0",
+		Group:            "group",
+		Side:             side,
+	}
+}
+
+func newMetadataIdentifier(side string) *identifier.MetadataIdentifier {
+	return &identifier.MetadataIdentifier{
+		Application:            "application",
+		BaseMetadataIdentifier: *newBaseMetadataIdentifier(side),
+	}
+}
+
+func newServiceMetadataIdentifier(side string) *identifier.ServiceMetadataIdentifier {
+	return &identifier.ServiceMetadataIdentifier{
+		Revision:               "1.0",
+		Protocol:               "dubbo",
+		BaseMetadataIdentifier: *newBaseMetadataIdentifier(side),
+	}
+}
+
+func newSubscribeMetadataIdentifier(side string) *identifier.SubscriberMetadataIdentifier {
+	return &identifier.SubscriberMetadataIdentifier{
+		Revision:           "1.0",
+		MetadataIdentifier: *newMetadataIdentifier(side),
+	}
+}
+
+type zookeeperMetadataReportTestSuite struct {
+	t *testing.T
+	m report.MetadataReport
+}
+
+func newZookeeperMetadataReportTestSuite(t *testing.T, m report.MetadataReport) *zookeeperMetadataReportTestSuite {
+	return &zookeeperMetadataReportTestSuite{t: t, m: m}
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testStoreProviderMetadata() {
+	providerMi := newMetadataIdentifier("provider")
+	providerMeta := "provider"
+	err := suite.m.StoreProviderMetadata(providerMi, providerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testStoreConsumerMetadata() {
+	consumerMi := newMetadataIdentifier("consumer")
+	consumerMeta := "consumer"
+	err := suite.m.StoreProviderMetadata(consumerMi, consumerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	err := suite.m.SaveServiceMetadata(serviceMi, url)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testRemoveServiceMetadata() {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	err := suite.m.RemoveServiceMetadata(serviceMi)
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testGetExportedURLs() {
+	serviceMi := newServiceMetadataIdentifier("provider")
+	urls, err := suite.m.GetExportedURLs(serviceMi)
+	assert.Equal(suite.t, 1, len(urls))
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testSaveSubscribedData(url common.URL) {
+	subscribeMi := newSubscribeMetadataIdentifier("provider")
+	urls := []string{url.String()}
+	bytes, _ := json.Marshal(urls)
+	err := suite.m.SaveSubscribedData(subscribeMi, string(bytes))
+	assert.Nil(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testGetSubscribedURLs() {
+	subscribeMi := newSubscribeMetadataIdentifier("provider")
+	urls, err := suite.m.GetSubscribedURLs(subscribeMi)
+	assert.Equal(suite.t, 1, len(urls))
+	assert.NoError(suite.t, err)
+}
+
+func (suite *zookeeperMetadataReportTestSuite) testGetServiceDefinition() {
+	providerMi := newMetadataIdentifier("provider")
+	providerMeta, err := suite.m.GetServiceDefinition(providerMi)
+	assert.Equal(suite.t, "provider", providerMeta)
+	assert.NoError(suite.t, err)
+}
+
+func test1(t *testing.T) {
+	testCluster, err := zk.StartTestCluster(1, nil, nil)
+	assert.NoError(t, err)
+	defer testCluster.Stop()
+
+	url := newProviderRegistryUrl("127.0.0.1", testCluster.Servers[0].Port)
+	mf := extension.GetMetadataReportFactory("zookeeper")
+	m := mf.CreateMetadataReport(url)
+
+	suite := newZookeeperMetadataReportTestSuite(t, m)
+	suite.testStoreProviderMetadata()
+	suite.testStoreConsumerMetadata()
+	suite.testSaveServiceMetadata(*url)
+	suite.testGetExportedURLs()
+	suite.testRemoveServiceMetadata()
+	suite.testSaveSubscribedData(*url)
+	suite.testGetSubscribedURLs()
+	suite.testGetServiceDefinition()
+}
+
+func TestZookeeperMetadataReport(t *testing.T) {
+	t.Run("test1", test1)
+}
diff --git a/metadata/service.go b/metadata/service.go
deleted file mode 100644
index 89df68fb313b1abe63082c0c220b0114c11fca19..0000000000000000000000000000000000000000
--- a/metadata/service.go
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package metadata
-
-import (
-	"github.com/apache/dubbo-go/common"
-	gxset "github.com/dubbogo/gost/container/set"
-)
-
-// Metadata service is a built-in service around the metadata of Dubbo services,
-// whose interface is provided by Dubbo Framework and exported automatically before subscription after other services exporting,
-// which may be used for Dubbo subscribers and admin.
-type MetadataService interface {
-	ServiceName() string
-	ExportURL(url *common.URL) bool
-	UnexportURL(url *common.URL) bool
-	RefreshMetadata(exportedRevision string, subscribedRevision string) bool
-	SubscribeURL(url *common.URL) bool
-	UnsubscribeURL(url *common.URL) bool
-	PublishServiceDefinition(url *common.URL)
-
-	GetExportedURLs(serviceInterface string, group string, version string, protocol string) gxset.HashSet
-	GetServiceDefinition(interfaceName string, version string, group string) string
-	GetServiceDefinitionByServiceKey(serviceKey string) string
-}
diff --git a/metadata/service/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8b4b0c0174cb0e5a8753b814f89ed4d332e2fbe
--- /dev/null
+++ b/metadata/service/exporter/configurable/exporter.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configurable
+
+import (
+	"context"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/metadata/service/exporter"
+)
+
+// MetadataServiceExporter is the ConfigurableMetadataServiceExporter which implement MetadataServiceExporter interface
+type MetadataServiceExporter struct {
+	ServiceConfig   *config.ServiceConfig
+	lock            sync.RWMutex
+	metadataService service.MetadataService
+}
+
+// NewMetadataServiceExporter will return a service_exporter.MetadataServiceExporter with the specified  metadata service
+func NewMetadataServiceExporter(metadataService service.MetadataService) exporter.MetadataServiceExporter {
+	return &MetadataServiceExporter{
+		metadataService: metadataService,
+	}
+}
+
+// Export will export the metadataService
+func (exporter *MetadataServiceExporter) Export() error {
+	if !exporter.IsExported() {
+
+		serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background())
+		serviceConfig.Protocol = constant.DEFAULT_PROTOCOL
+		serviceConfig.Protocols = map[string]*config.ProtocolConfig{
+			constant.DEFAULT_PROTOCOL: generateMetadataProtocol(),
+		}
+		serviceConfig.InterfaceName = constant.METADATA_SERVICE_NAME
+		// identify this is a golang server
+		serviceConfig.Params = map[string]string{}
+		serviceConfig.Group = config.GetApplicationConfig().Name
+		// now the error will always be nil
+		serviceConfig.Version, _ = exporter.metadataService.Version()
+
+		var err error
+		func() {
+			exporter.lock.Lock()
+			defer exporter.lock.Unlock()
+			exporter.ServiceConfig = serviceConfig
+			exporter.ServiceConfig.Implement(exporter.metadataService)
+			err = exporter.ServiceConfig.Export()
+		}()
+
+		logger.Infof("The MetadataService exports urls : %v ", exporter.ServiceConfig.GetExportedUrls())
+		return err
+	}
+	logger.Warnf("The MetadataService has been exported : %v ", exporter.ServiceConfig.GetExportedUrls())
+	return nil
+}
+
+// Unexport will unexport the metadataService
+func (exporter *MetadataServiceExporter) Unexport() {
+	if exporter.IsExported() {
+		exporter.ServiceConfig.Unexport()
+	}
+}
+
+// GetExportedURLs will return the urls that export use.
+// Notice!The exported url is not same as url in registry , for example it lack the ip.
+func (exporter *MetadataServiceExporter) GetExportedURLs() []*common.URL {
+	return exporter.ServiceConfig.GetExportedUrls()
+}
+
+// isExported will return is metadataServiceExporter exported or not
+func (exporter *MetadataServiceExporter) IsExported() bool {
+	exporter.lock.RLock()
+	defer exporter.lock.RUnlock()
+	return exporter.ServiceConfig != nil && exporter.ServiceConfig.IsExport()
+}
+
+// generateMetadataProtocol will return a default ProtocolConfig
+func generateMetadataProtocol() *config.ProtocolConfig {
+	return &config.ProtocolConfig{
+		Name: constant.DEFAULT_PROTOCOL,
+		Port: "20000",
+	}
+}
diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4689c6660b7da78609501c5e98f0dd309e4bce7f
--- /dev/null
+++ b/metadata/service/exporter/configurable/exporter_test.go
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configurable
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
+	"github.com/apache/dubbo-go/config"
+	_ "github.com/apache/dubbo-go/filter/filter_impl"
+	"github.com/apache/dubbo-go/metadata/service/inmemory"
+	"github.com/apache/dubbo-go/protocol/dubbo"
+	_ "github.com/apache/dubbo-go/protocol/dubbo"
+)
+
+func TestConfigurableExporter(t *testing.T) {
+	dubbo.SetServerConfig(dubbo.ServerConfig{
+		SessionNumber:  700,
+		SessionTimeout: "20s",
+		GettySessionParam: dubbo.GettySessionParam{
+			CompressEncoding: false,
+			TcpNoDelay:       true,
+			TcpKeepAlive:     true,
+			KeepAlivePeriod:  "120s",
+			TcpRBufSize:      262144,
+			TcpWBufSize:      65536,
+			PkgWQSize:        512,
+			TcpReadTimeout:   "1s",
+			TcpWriteTimeout:  "5s",
+			WaitTimeout:      "1s",
+			MaxMsgLen:        10240000000,
+			SessionName:      "server",
+		}})
+	mockInitProviderWithSingleRegistry()
+	metadataService, _ := inmemory.NewMetadataService()
+	exported := NewMetadataServiceExporter(metadataService)
+	assert.Equal(t, false, exported.IsExported())
+	assert.NoError(t, exported.Export())
+	assert.Equal(t, true, exported.IsExported())
+	assert.Regexp(t, "dubbo://:20000/MetadataService*", exported.GetExportedURLs()[0].String())
+	exported.Unexport()
+	assert.Equal(t, false, exported.IsExported())
+}
+
+// mockInitProviderWithSingleRegistry will init a mocked providerConfig
+func mockInitProviderWithSingleRegistry() {
+	providerConfig := &config.ProviderConfig{
+
+		BaseConfig: config.BaseConfig{
+			ApplicationConfig: &config.ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "1.0.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
+		},
+
+		Registry: &config.RegistryConfig{
+			Address:  "mock://127.0.0.1:2181",
+			Username: "user1",
+			Password: "pwd1",
+		},
+		Registries: map[string]*config.RegistryConfig{},
+
+		Services: map[string]*config.ServiceConfig{
+			"MockService": {
+				InterfaceName: "com.MockService",
+				Protocol:      "mock",
+				Cluster:       "failover",
+				Loadbalance:   "random",
+				Retries:       "3",
+				Group:         "huadong_idc",
+				Version:       "1.0.0",
+				Methods: []*config.MethodConfig{
+					{
+						Name:        "GetUser",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+					{
+						Name:        "GetUser1",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+				},
+			},
+		},
+		Protocols: map[string]*config.ProtocolConfig{
+			"mock": {
+				Name: "mock",
+				Ip:   "127.0.0.1",
+				Port: "20000",
+			},
+		},
+	}
+	providerConfig.Services["MockService"].InitExported()
+	config.SetProviderConfig(*providerConfig)
+}
diff --git a/metadata/exporter.go b/metadata/service/exporter/exporter.go
similarity index 81%
rename from metadata/exporter.go
rename to metadata/service/exporter/exporter.go
index 5d47f8bd808ec802ba73c7db73d22c78c675d12a..cfdef3a0e79d29ce31717c0fc3c575e9e4ba1759 100644
--- a/metadata/exporter.go
+++ b/metadata/service/exporter/exporter.go
@@ -15,15 +15,16 @@
  * limitations under the License.
  */
 
-package metadata
+package exporter
 
 import (
 	"github.com/apache/dubbo-go/common"
 )
 
-type MetadataExporter interface {
-	Export() MetadataExporter
-	Unexport() MetadataExporter
+// MetadataServiceExporter will export & unexport the metadata service,  get exported url, and return is exported or not
+type MetadataServiceExporter interface {
+	Export() error
+	Unexport()
 	GetExportedURLs() []*common.URL
 	IsExported() bool
 }
diff --git a/metadata/service/inmemory/metadata_service_proxy_factory.go b/metadata/service/inmemory/metadata_service_proxy_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..1f8eeaa55f4a0240746508fee2ff088e3a653ca5
--- /dev/null
+++ b/metadata/service/inmemory/metadata_service_proxy_factory.go
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package inmemory
+
+import (
+	"encoding/json"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func init() {
+	factory := service.NewBaseMetadataServiceProxyFactory(createProxy)
+	extension.SetMetadataServiceProxyFactory(local, func() service.MetadataServiceProxyFactory {
+		return factory
+	})
+}
+
+// createProxy creates an instance of MetadataServiceProxy
+// we read the metadata from ins.Metadata()
+// and then create an Invoker instance
+// also we will mark this proxy as golang's proxy
+func createProxy(ins registry.ServiceInstance) service.MetadataService {
+	urls := buildStandardMetadataServiceURL(ins)
+	if len(urls) == 0 {
+		logger.Errorf("metadata service urls not found, %v", ins)
+		return nil
+	}
+
+	u := urls[0]
+	p := extension.GetProtocol(u.Protocol)
+	invoker := p.Refer(*u)
+	return &MetadataServiceProxy{
+		invkr: invoker,
+	}
+}
+
+// buildStandardMetadataServiceURL will use standard format to build the metadata service url.
+func buildStandardMetadataServiceURL(ins registry.ServiceInstance) []*common.URL {
+	ps := getMetadataServiceUrlParams(ins)
+	res := make([]*common.URL, 0, len(ps))
+	sn := ins.GetServiceName()
+	host := ins.GetHost()
+	for protocol, params := range ps {
+
+		convertedParams := make(map[string][]string, len(params))
+		for k, v := range params {
+			convertedParams[k] = []string{v}
+		}
+
+		u := common.NewURLWithOptions(common.WithIp(host),
+			common.WithPath(constant.METADATA_SERVICE_NAME),
+			common.WithProtocol(protocol),
+			common.WithPort(params[constant.PORT_KEY]),
+			common.WithParams(convertedParams),
+			common.WithParamsValue(constant.GROUP_KEY, sn))
+		res = append(res, u)
+	}
+	return res
+}
+
+// getMetadataServiceUrlParams this will convert the metadata service url parameters to map structure
+// it looks like:
+// {"dubbo":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}
+func getMetadataServiceUrlParams(ins registry.ServiceInstance) map[string]map[string]string {
+	ps := ins.GetMetadata()
+	res := make(map[string]map[string]string, 2)
+	if str, ok := ps[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME]; ok && len(str) > 0 {
+
+		err := json.Unmarshal([]byte(str), &res)
+		if err != nil {
+			logger.Errorf("could not parse the metadata service url parameters to map", err)
+		}
+	}
+	return res
+}
diff --git a/metadata/service/inmemory/metadata_service_proxy_factory_test.go b/metadata/service/inmemory/metadata_service_proxy_factory_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..96020e1eb762442f946ccf8b368d6ebe9429d05e
--- /dev/null
+++ b/metadata/service/inmemory/metadata_service_proxy_factory_test.go
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package inmemory
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestMetadataService_GetMetadataServiceUrlParams(t *testing.T) {
+	str := `{"dubbo":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`
+	tmp := make(map[string]map[string]string)
+	err := json.Unmarshal([]byte(str), &tmp)
+	assert.Nil(t, err)
+}
+
+func TestCreateProxy(t *testing.T) {
+	extension.SetProtocol("mock", func() protocol.Protocol {
+		return &mockProtocol{}
+	})
+	ins := &registry.DefaultServiceInstance{
+		Id:          "test-id",
+		ServiceName: "com.dubbo",
+		Host:        "localhost",
+		Port:        8080,
+		Enable:      true,
+		Healthy:     true,
+	}
+
+	pxy := createProxy(ins)
+	assert.Nil(t, pxy)
+
+	ins.Metadata = map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`}
+	pxy = createProxy(ins)
+	assert.NotNil(t, pxy)
+}
+
+type mockProtocol struct {
+}
+
+func (m mockProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
+	panic("implement me")
+}
+
+func (m mockProtocol) Refer(url common.URL) protocol.Invoker {
+	return &mockInvoker{}
+}
+
+func (m mockProtocol) Destroy() {
+	panic("implement me")
+}
+
+type mockInvoker struct {
+}
+
+func (m *mockInvoker) GetUrl() common.URL {
+	panic("implement me")
+}
+
+func (m *mockInvoker) IsAvailable() bool {
+	panic("implement me")
+}
+
+func (m *mockInvoker) Destroy() {
+	panic("implement me")
+}
+
+func (m *mockInvoker) Invoke(context.Context, protocol.Invocation) protocol.Result {
+	return &protocol.RPCResult{
+		Rest: &[]interface{}{"dubbo://localhost"},
+	}
+}
diff --git a/metadata/service/inmemory/service.go b/metadata/service/inmemory/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..8269e691f1794fd9ac4b6091c157539e39ad7072
--- /dev/null
+++ b/metadata/service/inmemory/service.go
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package inmemory
+
+import (
+	"sort"
+	"sync"
+)
+
+import (
+	cm "github.com/Workiva/go-datastructures/common"
+	"github.com/Workiva/go-datastructures/slice/skip"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/definition"
+	"github.com/apache/dubbo-go/metadata/service"
+)
+
+// version will be used by Version func
+const (
+	version = "1.0.0"
+	local   = "local"
+)
+
+func init() {
+	extension.SetMetadataService(local, NewMetadataService)
+}
+
+// MetadataService is store and query the metadata info in memory when each service registry
+type MetadataService struct {
+	service.BaseMetadataService
+	exportedServiceURLs   *sync.Map
+	subscribedServiceURLs *sync.Map
+	serviceDefinitions    *sync.Map
+	lock                  *sync.RWMutex
+}
+
+var (
+	metadataServiceInstance *MetadataService
+	metadataServiceInitOnce sync.Once
+)
+
+// NewMetadataService: initiate a metadata service
+// it should be singleton
+func NewMetadataService() (service.MetadataService, error) {
+	metadataServiceInitOnce.Do(func() {
+		metadataServiceInstance = &MetadataService{
+			BaseMetadataService:   service.NewBaseMetadataService(config.GetApplicationConfig().Name),
+			exportedServiceURLs:   &sync.Map{},
+			subscribedServiceURLs: &sync.Map{},
+			serviceDefinitions:    &sync.Map{},
+			lock:                  &sync.RWMutex{},
+		}
+	})
+	return metadataServiceInstance, nil
+}
+
+// Comparator is defined as Comparator for skip list to compare the URL
+type Comparator common.URL
+
+// Compare is defined as Comparator for skip list to compare the URL
+func (c Comparator) Compare(comp cm.Comparator) int {
+	a := common.URL(c).String()
+	b := common.URL(comp.(Comparator)).String()
+	switch {
+	case a > b:
+		return 1
+	case a < b:
+		return -1
+	default:
+		return 0
+	}
+}
+
+// addURL will add URL in memory
+func (mts *MetadataService) addURL(targetMap *sync.Map, url *common.URL) bool {
+	var (
+		urlSet interface{}
+		loaded bool
+	)
+	logger.Debug(url.ServiceKey())
+	if urlSet, loaded = targetMap.LoadOrStore(url.ServiceKey(), skip.New(uint64(0))); loaded {
+		mts.lock.RLock()
+		wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url))
+		if len(wantedUrl) > 0 && wantedUrl[0] != nil {
+			mts.lock.RUnlock()
+			return false
+		}
+		mts.lock.RUnlock()
+	}
+	mts.lock.Lock()
+	// double chk
+	wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url))
+	if len(wantedUrl) > 0 && wantedUrl[0] != nil {
+		mts.lock.Unlock()
+		return false
+	}
+	urlSet.(*skip.SkipList).Insert(Comparator(*url))
+	mts.lock.Unlock()
+	return true
+}
+
+// removeURL is used to remove specified url
+func (mts *MetadataService) removeURL(targetMap *sync.Map, url *common.URL) {
+	if value, loaded := targetMap.Load(url.ServiceKey()); loaded {
+		mts.lock.Lock()
+		value.(*skip.SkipList).Delete(Comparator(*url))
+		mts.lock.Unlock()
+		mts.lock.RLock()
+		defer mts.lock.RUnlock()
+		if value.(*skip.SkipList).Len() == 0 {
+			targetMap.Delete(url.ServiceKey())
+		}
+	}
+}
+
+// getAllService can return all the exportedUrlString except for metadataService
+func (mts *MetadataService) getAllService(services *sync.Map) []common.URL {
+	// using skip list to dedup and sorting
+	res := make([]common.URL, 0)
+	services.Range(func(key, value interface{}) bool {
+		urls := value.(*skip.SkipList)
+		for i := uint64(0); i < urls.Len(); i++ {
+			url := common.URL(urls.ByPosition(i).(Comparator))
+			if url.GetParam(constant.INTERFACE_KEY, url.Path) != constant.METADATA_SERVICE_NAME {
+				res = append(res, url)
+			}
+		}
+		return true
+	})
+	sort.Sort(common.URLSlice(res))
+	return res
+}
+
+// getSpecifiedService can return specified service url by serviceKey
+func (mts *MetadataService) getSpecifiedService(services *sync.Map, serviceKey string, protocol string) []common.URL {
+	res := make([]common.URL, 0)
+	serviceList, loaded := services.Load(serviceKey)
+	if loaded {
+		urls := serviceList.(*skip.SkipList)
+		for i := uint64(0); i < urls.Len(); i++ {
+			url := common.URL(urls.ByPosition(i).(Comparator))
+			if len(protocol) == 0 || protocol == constant.ANY_VALUE || url.Protocol == protocol || url.GetParam(constant.PROTOCOL_KEY, "") == protocol {
+				res = append(res, url)
+			}
+		}
+		sort.Stable(common.URLSlice(res))
+	}
+	return res
+}
+
+// ExportURL can store the in memory
+func (mts *MetadataService) ExportURL(url common.URL) (bool, error) {
+	return mts.addURL(mts.exportedServiceURLs, &url), nil
+}
+
+// UnexportURL can remove the url store in memory
+func (mts *MetadataService) UnexportURL(url common.URL) error {
+	mts.removeURL(mts.exportedServiceURLs, &url)
+	return nil
+}
+
+// SubscribeURL can store the in memory
+func (mts *MetadataService) SubscribeURL(url common.URL) (bool, error) {
+	return mts.addURL(mts.subscribedServiceURLs, &url), nil
+}
+
+// UnsubscribeURL can remove the url store in memory
+func (mts *MetadataService) UnsubscribeURL(url common.URL) error {
+	mts.removeURL(mts.subscribedServiceURLs, &url)
+	return nil
+}
+
+// PublishServiceDefinition: publish url's service metadata info, and write into memory
+func (mts *MetadataService) PublishServiceDefinition(url common.URL) error {
+	interfaceName := url.GetParam(constant.INTERFACE_KEY, "")
+	isGeneric := url.GetParamBool(constant.GENERIC_KEY, false)
+	if len(interfaceName) > 0 && !isGeneric {
+		service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service()))
+		sd := definition.BuildServiceDefinition(*service, url)
+		data, err := sd.ToBytes()
+		if err != nil {
+			logger.Errorf("publishProvider getServiceDescriptor error. providerUrl:%v , error:%v ", url, err)
+			return nil
+		}
+		mts.serviceDefinitions.Store(url.ServiceKey(), string(data))
+		return nil
+	}
+	logger.Errorf("publishProvider interfaceName is empty . providerUrl:%v ", url)
+	return nil
+}
+
+// GetExportedURLs get all exported urls
+func (mts *MetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+	if serviceInterface == constant.ANY_VALUE {
+		return service.ConvertURLArrToIntfArr(mts.getAllService(mts.exportedServiceURLs)), nil
+	} else {
+		serviceKey := definition.ServiceDescriperBuild(serviceInterface, group, version)
+		return service.ConvertURLArrToIntfArr(mts.getSpecifiedService(mts.exportedServiceURLs, serviceKey, protocol)), nil
+	}
+}
+
+// GetSubscribedURLs get all subscribedUrl
+func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) {
+	return mts.getAllService(mts.subscribedServiceURLs), nil
+}
+
+// GetServiceDefinition can get service definition by interfaceName, group and version
+func (mts *MetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	serviceKey := definition.ServiceDescriperBuild(interfaceName, group, version)
+	v, _ := mts.serviceDefinitions.Load(serviceKey)
+	return v.(string), nil
+}
+
+// GetServiceDefinition can get service definition by serviceKey
+func (mts *MetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	v, _ := mts.serviceDefinitions.Load(serviceKey)
+	return v.(string), nil
+}
+
+// RefreshMetadata will always return true because it will be implement by remote service
+func (mts *MetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	return true, nil
+}
+
+// Version will return the version of metadata service
+func (mts *MetadataService) Version() (string, error) {
+	return version, nil
+}
diff --git a/metadata/service/inmemory/service_proxy.go b/metadata/service/inmemory/service_proxy.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e01439f042a2046559188ec9df6924da0236cb1
--- /dev/null
+++ b/metadata/service/inmemory/service_proxy.go
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package inmemory
+
+import (
+	"context"
+	"reflect"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+)
+
+// actually it's RPC stub
+// this will only be used by client-side
+// if the metadata service is "local" metadata service in server side,
+// which means that metadata service is RPC service too.
+// so in client-side, if we want to get the metadata information,
+// we must call metadata service
+// this is the stub, or proxy
+// for now, only GetExportedURLs need to be implemented
+type MetadataServiceProxy struct {
+	invkr        protocol.Invoker
+	golangServer bool
+}
+
+func (m *MetadataServiceProxy) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+
+	siV := reflect.ValueOf(serviceInterface)
+	gV := reflect.ValueOf(group)
+	vV := reflect.ValueOf(version)
+	pV := reflect.ValueOf(protocol)
+
+	const methodName = "getExportedURLs"
+
+	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(methodName),
+		invocation.WithArguments([]interface{}{siV.Interface(), gV.Interface(), vV.Interface(), pV.Interface()}),
+		invocation.WithReply(reflect.ValueOf(&[]interface{}{}).Interface()),
+		invocation.WithAttachments(map[string]string{constant.ASYNC_KEY: "false"}),
+		invocation.WithParameterValues([]reflect.Value{siV, gV, vV, pV}))
+
+	res := m.invkr.Invoke(context.Background(), inv)
+	if res.Error() != nil {
+		logger.Errorf("could not get the metadata service from remote provider: %v", res.Error())
+		return []interface{}{}, nil
+	}
+
+	urlStrs := res.Result().(*[]interface{})
+
+	ret := make([]interface{}, 0, len(*urlStrs))
+
+	for _, s := range *urlStrs {
+		ret = append(ret, s)
+	}
+	return ret, nil
+}
+
+func (m *MetadataServiceProxy) MethodMapper() map[string]string {
+	return map[string]string{}
+}
+
+func (m *MetadataServiceProxy) Reference() string {
+	logger.Error("you should never invoke this implementation")
+	return constant.METADATA_SERVICE_NAME
+}
+
+func (m *MetadataServiceProxy) ServiceName() (string, error) {
+	logger.Error("you should never invoke this implementation")
+	return "", nil
+}
+
+func (m *MetadataServiceProxy) ExportURL(url common.URL) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return false, nil
+}
+
+func (m *MetadataServiceProxy) UnexportURL(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *MetadataServiceProxy) SubscribeURL(url common.URL) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return false, nil
+}
+
+func (m *MetadataServiceProxy) UnsubscribeURL(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *MetadataServiceProxy) PublishServiceDefinition(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *MetadataServiceProxy) GetSubscribedURLs() ([]common.URL, error) {
+	logger.Error("you should never invoke this implementation")
+	return []common.URL{}, nil
+}
+
+func (m *MetadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	logger.Error("you should never invoke this implementation")
+	return "", nil
+}
+
+func (m *MetadataServiceProxy) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	logger.Error("you should never invoke this implementation")
+	return "", nil
+}
+
+func (m *MetadataServiceProxy) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return false, nil
+}
+
+func (m *MetadataServiceProxy) Version() (string, error) {
+	logger.Error("you should never invoke this implementation")
+	return "", nil
+}
diff --git a/metadata/service/inmemory/service_proxy_test.go b/metadata/service/inmemory/service_proxy_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d75517e418133ffbf3804ec96f061dda09b9e5e
--- /dev/null
+++ b/metadata/service/inmemory/service_proxy_test.go
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package inmemory
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestMetadataServiceProxy_GetExportedURLs(t *testing.T) {
+
+	pxy := createPxy()
+	assert.NotNil(t, pxy)
+	res, err := pxy.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	assert.Nil(t, err)
+	assert.Len(t, res, 1)
+
+}
+
+// TestNewMetadataService: those methods are not implemented
+// when we implement them, adding UT
+func TestNewMetadataService(t *testing.T) {
+	pxy := createPxy()
+	pxy.ServiceName()
+	pxy.PublishServiceDefinition(common.URL{})
+	pxy.GetServiceDefinition(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	pxy.Version()
+	pxy.GetSubscribedURLs()
+	pxy.UnsubscribeURL(common.URL{})
+	pxy.GetServiceDefinitionByServiceKey("any")
+	pxy.ExportURL(common.URL{})
+	pxy.SubscribeURL(common.URL{})
+	pxy.MethodMapper()
+	pxy.UnexportURL(common.URL{})
+	pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE)
+
+}
+
+func createPxy() service.MetadataService {
+	extension.SetProtocol("mock", func() protocol.Protocol {
+		return &mockProtocol{}
+	})
+
+	ins := &registry.DefaultServiceInstance{
+		Id:          "test-id",
+		ServiceName: "com.dubbo",
+		Host:        "localhost",
+		Port:        8080,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`},
+	}
+
+	return extension.GetMetadataServiceProxyFactory(local).GetProxy(ins)
+}
diff --git a/metadata/service/inmemory/service_test.go b/metadata/service/inmemory/service_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..048c286fdf28fba6a15a86164df0789d421f0797
--- /dev/null
+++ b/metadata/service/inmemory/service_test.go
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package inmemory
+
+import (
+	"fmt"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/metadata/definition"
+)
+
+func TestMetadataService(t *testing.T) {
+	mts, _ := NewMetadataService()
+	serviceName := "com.ikurento.user.UserProvider"
+	group := "group1"
+	version := "0.0.1"
+	protocol := "dubbo"
+	beanName := "UserProvider"
+
+	u2, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider2?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, serviceName, group, version, beanName))
+	assert.NoError(t, err)
+	mts.ExportURL(u2)
+
+	u3, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider3?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, serviceName, group, version, beanName))
+	assert.NoError(t, err)
+	mts.ExportURL(u3)
+
+	u, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, serviceName, group, version, beanName))
+	assert.NoError(t, err)
+	mts.ExportURL(u)
+	list, _ := mts.GetExportedURLs(serviceName, group, version, protocol)
+	assert.Equal(t, 3, len(list))
+	mts.SubscribeURL(u)
+
+	mts.SubscribeURL(u)
+	list2, _ := mts.GetSubscribedURLs()
+	assert.Equal(t, 1, len(list2))
+
+	mts.UnexportURL(u)
+	list3, _ := mts.GetExportedURLs(serviceName, group, version, protocol)
+	assert.Equal(t, 2, len(list3))
+
+	mts.UnsubscribeURL(u)
+	list4, _ := mts.GetSubscribedURLs()
+	assert.Equal(t, 0, len(list4))
+
+	userProvider := &definition.UserProvider{}
+	common.ServiceMap.Register(serviceName, protocol, userProvider)
+	mts.PublishServiceDefinition(u)
+	expected := "{\"CanonicalName\":\"com.ikurento.user.UserProvider\",\"CodeSource\":\"\"," +
+		"\"Methods\":[{\"Name\":\"GetUser\",\"ParameterTypes\":[\"slice\"],\"ReturnType\":\"ptr\"," +
+		"\"Parameters\":null}],\"Types\":null}"
+	def1, _ := mts.GetServiceDefinition(serviceName, group, version)
+	assert.Equal(t, expected, def1)
+	serviceKey := definition.ServiceDescriperBuild(serviceName, group, version)
+	def2, _ := mts.GetServiceDefinitionByServiceKey(serviceKey)
+	assert.Equal(t, expected, def2)
+}
diff --git a/metadata/service/remote/metadata_service_proxy_factory.go b/metadata/service/remote/metadata_service_proxy_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1a8594282c581913d97586630f3e5e74305642d
--- /dev/null
+++ b/metadata/service/remote/metadata_service_proxy_factory.go
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package remote
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/service"
+)
+
+func init() {
+	factory := service.NewBaseMetadataServiceProxyFactory(newMetadataServiceProxy)
+	extension.SetMetadataServiceProxyFactory(remote, func() service.MetadataServiceProxyFactory {
+		return factory
+	})
+}
diff --git a/metadata/service/remote/service.go b/metadata/service/remote/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..ae83a69bef0af1614352c99c1e512a63770a0eff
--- /dev/null
+++ b/metadata/service/remote/service.go
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package remote
+
+import (
+	"sync"
+)
+
+import (
+	"go.uber.org/atomic"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/definition"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report/delegate"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/metadata/service/inmemory"
+)
+
+// version will be used by Version func
+const (
+	version = "1.0.0"
+	remote  = "remote"
+)
+
+func init() {
+	extension.SetMetadataService(remote, newMetadataService)
+}
+
+// MetadataService is a implement of metadata service which will delegate the remote metadata report
+// This is singleton
+type MetadataService struct {
+	service.BaseMetadataService
+	inMemoryMetadataService *inmemory.MetadataService
+	exportedRevision        atomic.String
+	subscribedRevision      atomic.String
+	delegateReport          *delegate.MetadataReport
+}
+
+var (
+	metadataServiceOnce     sync.Once
+	metadataServiceInstance *MetadataService
+)
+
+// newMetadataService will create a new remote MetadataService instance
+func newMetadataService() (service.MetadataService, error) {
+	var err error
+	metadataServiceOnce.Do(func() {
+		var mr *delegate.MetadataReport
+		mr, err = delegate.NewMetadataReport()
+		if err != nil {
+			return
+		}
+		// it will never return error
+		inms, _ := inmemory.NewMetadataService()
+		metadataServiceInstance = &MetadataService{
+			BaseMetadataService:     service.NewBaseMetadataService(config.GetApplicationConfig().Name),
+			inMemoryMetadataService: inms.(*inmemory.MetadataService),
+			delegateReport:          mr,
+		}
+	})
+	return metadataServiceInstance, err
+}
+
+// setInMemoryMetadataService will replace the in memory metadata service by the specific param
+func (mts *MetadataService) setInMemoryMetadataService(metadata *inmemory.MetadataService) {
+	mts.inMemoryMetadataService = metadata
+}
+
+// ExportURL will be implemented by in memory service
+func (mts *MetadataService) ExportURL(url common.URL) (bool, error) {
+	return mts.inMemoryMetadataService.ExportURL(url)
+}
+
+// UnexportURL remove @url's metadata
+func (mts *MetadataService) UnexportURL(url common.URL) error {
+	smi := identifier.NewServiceMetadataIdentifier(url)
+	smi.Revision = mts.exportedRevision.Load()
+	return mts.delegateReport.RemoveServiceMetadata(smi)
+}
+
+// SubscribeURL will be implemented by in memory service
+func (mts *MetadataService) SubscribeURL(url common.URL) (bool, error) {
+	return mts.inMemoryMetadataService.SubscribeURL(url)
+}
+
+// UnsubscribeURL will be implemented by in memory service
+func (mts *MetadataService) UnsubscribeURL(url common.URL) error {
+	return mts.UnsubscribeURL(url)
+}
+
+// PublishServiceDefinition will call remote metadata's StoreProviderMetadata to store url info and service definition
+func (mts *MetadataService) PublishServiceDefinition(url common.URL) error {
+	interfaceName := url.GetParam(constant.INTERFACE_KEY, "")
+	isGeneric := url.GetParamBool(constant.GENERIC_KEY, false)
+	if len(interfaceName) > 0 && !isGeneric {
+		sv := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service()))
+		sd := definition.BuildServiceDefinition(*sv, url)
+		id := &identifier.MetadataIdentifier{
+			BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+				ServiceInterface: interfaceName,
+				Version:          url.GetParam(constant.VERSION_KEY, ""),
+				// Group:            url.GetParam(constant.GROUP_KEY, constant.SERVICE_DISCOVERY_DEFAULT_GROUP),
+				Group: url.GetParam(constant.GROUP_KEY, constant.DUBBO),
+				Side:  url.GetParam(constant.SIDE_KEY, "provider"),
+			},
+		}
+		mts.delegateReport.StoreProviderMetadata(id, sd)
+		return nil
+	}
+	logger.Errorf("publishProvider interfaceName is empty . providerUrl:%v ", url)
+	return nil
+}
+
+// GetExportedURLs will be implemented by in memory service
+func (mts *MetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+	return mts.inMemoryMetadataService.GetExportedURLs(serviceInterface, group, version, protocol)
+}
+
+// GetSubscribedURLs will be implemented by in memory service
+func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) {
+	return mts.inMemoryMetadataService.GetSubscribedURLs()
+}
+
+// GetServiceDefinition will be implemented by in memory service
+func (mts *MetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	return mts.inMemoryMetadataService.GetServiceDefinition(interfaceName, group, version)
+}
+
+// GetServiceDefinitionByServiceKey will be implemented by in memory service
+func (mts *MetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	return mts.inMemoryMetadataService.GetServiceDefinitionByServiceKey(serviceKey)
+}
+
+// RefreshMetadata will refresh the exported & subscribed metadata to remote metadata report from the inmemory metadata service
+func (mts *MetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	if len(exportedRevision) != 0 && exportedRevision != mts.exportedRevision.Load() {
+		mts.exportedRevision.Store(exportedRevision)
+		urls, err := mts.inMemoryMetadataService.GetExportedURLs(constant.ANY_VALUE, "", "", "")
+		if err != nil {
+			logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err)
+			return false, err
+		}
+		logger.Infof("urls length = %v", len(urls))
+		for _, ui := range urls {
+
+			u, err := common.NewURL(ui.(string))
+			if err != nil {
+				logger.Errorf("this is not valid url string: %s ", ui.(string))
+				continue
+			}
+			id := identifier.NewServiceMetadataIdentifier(u)
+			id.Revision = mts.exportedRevision.Load()
+			if err := mts.delegateReport.SaveServiceMetadata(id, u); err != nil {
+				logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err)
+				return false, err
+			}
+		}
+	}
+
+	if len(subscribedRevision) != 0 && subscribedRevision != mts.subscribedRevision.Load() {
+		mts.subscribedRevision.Store(subscribedRevision)
+		urls, err := mts.inMemoryMetadataService.GetSubscribedURLs()
+		if err != nil {
+			logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %v+", err)
+			return false, err
+		}
+		if urls != nil && len(urls) > 0 {
+			id := &identifier.SubscriberMetadataIdentifier{
+				MetadataIdentifier: identifier.MetadataIdentifier{
+					Application: config.GetApplicationConfig().Name,
+				},
+				Revision: subscribedRevision,
+			}
+			if err := mts.delegateReport.SaveSubscribedData(id, urls); err != nil {
+				logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err)
+				return false, err
+			}
+		}
+	}
+	return true, nil
+}
+
+// Version will return the remote service version
+func (MetadataService) Version() (string, error) {
+	return version, nil
+}
diff --git a/metadata/service/remote/service_proxy.go b/metadata/service/remote/service_proxy.go
new file mode 100644
index 0000000000000000000000000000000000000000..eaf7a02f4a0f3a8280835940bd8da720a0bde9f5
--- /dev/null
+++ b/metadata/service/remote/service_proxy.go
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package remote
+
+import (
+	"strings"
+)
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config/instance"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+type metadataServiceProxy struct {
+	serviceName string
+	revision    string
+	report      report.MetadataReport
+}
+
+func (m *metadataServiceProxy) Reference() string {
+	return constant.METADATA_SERVICE_NAME
+}
+
+func (m *metadataServiceProxy) ServiceName() (string, error) {
+	return m.serviceName, nil
+}
+
+func (m *metadataServiceProxy) ExportURL(url common.URL) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return true, nil
+}
+
+func (m *metadataServiceProxy) UnexportURL(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *metadataServiceProxy) SubscribeURL(url common.URL) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return true, nil
+}
+
+func (m *metadataServiceProxy) UnsubscribeURL(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *metadataServiceProxy) PublishServiceDefinition(url common.URL) error {
+	logger.Error("you should never invoke this implementation")
+	return nil
+}
+
+func (m *metadataServiceProxy) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+	urls, err := m.report.GetExportedURLs(&identifier.ServiceMetadataIdentifier{
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: serviceInterface,
+			Version:          version,
+			Group:            group,
+			Side:             constant.PROVIDER_PROTOCOL,
+		},
+		Revision: m.revision,
+		Protocol: protocol,
+	})
+
+	if err != nil {
+		return []interface{}{}, nil
+	}
+	res := make([]common.URL, 0, len(urls))
+	for _, s := range urls {
+		u, err := common.NewURL(s)
+		if err != nil {
+			logger.Errorf("could not parse the url string to URL structure", err)
+			continue
+		}
+		res = append(res, u)
+	}
+	return service.ConvertURLArrToIntfArr(res), nil
+}
+
+func (m *metadataServiceProxy) MethodMapper() map[string]string {
+	return map[string]string{}
+}
+
+func (m *metadataServiceProxy) GetSubscribedURLs() ([]common.URL, error) {
+	logger.Error("you should never invoke this implementation")
+	return []common.URL{}, nil
+}
+
+func (m *metadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	return m.report.GetServiceDefinition(&identifier.MetadataIdentifier{
+		BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{
+			ServiceInterface: interfaceName,
+			Group:            group,
+			Version:          version,
+			Side:             constant.PROVIDER_PROTOCOL,
+		},
+		Application: m.serviceName,
+	})
+}
+
+func (m *metadataServiceProxy) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	params := parse(serviceKey)
+	return m.GetServiceDefinition(params[0], params[1], params[2])
+}
+
+func (m *metadataServiceProxy) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	logger.Error("you should never invoke this implementation")
+	return true, nil
+}
+
+func (m metadataServiceProxy) Version() (string, error) {
+	return version, nil
+}
+
+func newMetadataServiceProxy(ins registry.ServiceInstance) service.MetadataService {
+	revision := ins.GetMetadata()[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME]
+	if len(revision) == 0 {
+		revision = constant.DEFAULT_REVIESION
+	}
+
+	return &metadataServiceProxy{
+		serviceName: ins.GetServiceName(),
+		revision:    revision,
+		report:      instance.GetMetadataReportInstance(),
+	}
+}
+
+func parse(key string) []string {
+	arr := make([]string, 3, 3)
+	tmp := strings.SplitN(key, "/", 2)
+	if len(tmp) > 1 {
+		arr[0] = tmp[0]
+		key = tmp[1]
+	}
+	tmp = strings.SplitN(key, "/", 2)
+	if len(tmp) > 1 {
+		arr[2] = tmp[1]
+		key = tmp[0]
+	}
+	arr[1] = key
+	return arr
+}
diff --git a/metadata/service/remote/service_proxy_test.go b/metadata/service/remote/service_proxy_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c284bb22123e731f3b8905f70508856bc767ace6
--- /dev/null
+++ b/metadata/service/remote/service_proxy_test.go
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package remote
+
+import (
+	"testing"
+)
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/config/instance"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestMetadataServiceProxy_GetExportedURLs(t *testing.T) {
+	pxy := createProxy()
+	res, err := pxy.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	assert.Nil(t, err)
+	assert.Len(t, res, 2)
+}
+
+func TestMetadataServiceProxy_GetServiceDefinition(t *testing.T) {
+	pxy := createProxy()
+	res, err := pxy.GetServiceDefinition(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	assert.Nil(t, err)
+	assert.Equal(t, "definition", res)
+}
+
+// TestMetadataServiceProxy test those unimportant method
+// in fact, we don't use them
+func TestMetadataServiceProxy(t *testing.T) {
+	pxy := createProxy()
+	pxy.ServiceName()
+	pxy.PublishServiceDefinition(common.URL{})
+	pxy.Version()
+	pxy.GetSubscribedURLs()
+	pxy.UnsubscribeURL(common.URL{})
+	pxy.GetServiceDefinitionByServiceKey("any")
+	pxy.ExportURL(common.URL{})
+	pxy.SubscribeURL(common.URL{})
+	pxy.MethodMapper()
+	pxy.UnexportURL(common.URL{})
+	pxy.Reference()
+	pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE)
+}
+
+func createProxy() service.MetadataService {
+
+	prepareTest()
+
+	ins := &registry.DefaultServiceInstance{
+		Id:          "test-id",
+		ServiceName: "com.dubbo",
+		Host:        "localhost",
+		Port:        8080,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`},
+	}
+	return newMetadataServiceProxy(ins)
+}
+
+func prepareTest() {
+	extension.SetMetadataReportFactory("mock", func() factory.MetadataReportFactory {
+		return &mockMetadataReportFactory{}
+	})
+	u, _ := common.NewURL("mock://localhost")
+	instance.GetMetadataReportInstance(&u)
+}
+
+type mockMetadataReportFactory struct {
+}
+
+func (m *mockMetadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport {
+	return &mockMetadataReport{}
+}
+
+type mockMetadataReport struct {
+}
+
+func (m mockMetadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error {
+	return nil
+}
+
+func (m mockMetadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) {
+	return []string{"mock://localhost1", "mock://localhost2"}, nil
+}
+
+func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error {
+	return nil
+}
+
+func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	panic("implement me")
+}
+
+func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) {
+	return "definition", nil
+}
diff --git a/metadata/service/remote/service_test.go b/metadata/service/remote/service_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..734f0989ab06ef17caeef241cd067c678fb8b2ad
--- /dev/null
+++ b/metadata/service/remote/service_test.go
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package remote
+
+import (
+	"fmt"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config/instance"
+	"github.com/apache/dubbo-go/metadata/definition"
+	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/metadata/report"
+	"github.com/apache/dubbo-go/metadata/report/factory"
+	"github.com/apache/dubbo-go/metadata/service/inmemory"
+)
+
+var (
+	serviceMetadata    = make(map[*identifier.ServiceMetadataIdentifier]common.URL, 4)
+	subscribedMetadata = make(map[*identifier.SubscriberMetadataIdentifier]string, 4)
+)
+
+func getMetadataReportFactory() factory.MetadataReportFactory {
+	return &metadataReportFactory{}
+}
+
+type metadataReportFactory struct {
+}
+
+func (mrf *metadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport {
+	return &metadataReport{}
+}
+
+type metadataReport struct {
+}
+
+func (metadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error {
+	return nil
+}
+
+func (metadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error {
+	return nil
+}
+
+func (mr *metadataReport) SaveServiceMetadata(id *identifier.ServiceMetadataIdentifier, url common.URL) error {
+	logger.Infof("SaveServiceMetadata , url is %v", url)
+	serviceMetadata[id] = url
+	return nil
+}
+
+func (metadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error {
+	return nil
+}
+
+func (metadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) {
+	return nil, nil
+}
+
+func (mr *metadataReport) SaveSubscribedData(id *identifier.SubscriberMetadataIdentifier, urls string) error {
+	logger.Infof("SaveSubscribedData, , url is %v", urls)
+	subscribedMetadata[id] = urls
+	return nil
+}
+
+func (metadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) {
+	return nil, nil
+}
+
+func (metadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) {
+	return "", nil
+}
+
+func TestMetadataService(t *testing.T) {
+	extension.SetMetadataReportFactory("mock", getMetadataReportFactory)
+	u, err := common.NewURL(fmt.Sprintf("mock://127.0.0.1:20000/?sync.report=true"))
+	assert.NoError(t, err)
+	instance.GetMetadataReportInstance(&u)
+	mts, err := newMetadataService()
+	assert.NoError(t, err)
+	mts.(*MetadataService).setInMemoryMetadataService(mockInmemoryProc(t))
+	_, _ = mts.RefreshMetadata("0.0.1", "0.0.1")
+}
+
+func mockInmemoryProc(t *testing.T) *inmemory.MetadataService {
+	mts, _ := inmemory.NewMetadataService()
+	serviceName := "com.ikurento.user.UserProvider"
+	group := "group1"
+	version := "0.0.1"
+	protocol := "dubbo"
+	beanName := "UserProvider"
+	userProvider := &definition.UserProvider{}
+
+	u, err := common.NewURL(fmt.Sprintf(
+		"%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+
+			"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+			"environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+
+			"owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245&group=%v&version=%v&bean.name=%v",
+		protocol, serviceName, group, version, beanName))
+	assert.NoError(t, err)
+
+	_, err = mts.ExportURL(u)
+	assert.NoError(t, err)
+	_, err = mts.SubscribeURL(u)
+	assert.NoError(t, err)
+
+	_, err = common.ServiceMap.Register(serviceName, protocol, userProvider)
+	assert.NoError(t, err)
+	err = mts.PublishServiceDefinition(u)
+	assert.NoError(t, err)
+
+	expected := "{\"CanonicalName\":\"com.ikurento.user.UserProvider\",\"CodeSource\":\"\"," +
+		"\"Methods\":[{\"Name\":\"GetUser\",\"ParameterTypes\":[\"slice\"],\"ReturnType\":\"ptr\"," +
+		"\"Parameters\":null}],\"Types\":null}"
+	def1, _ := mts.GetServiceDefinition(serviceName, group, version)
+	assert.Equal(t, expected, def1)
+	serviceKey := definition.ServiceDescriperBuild(serviceName, group, version)
+	def2, _ := mts.GetServiceDefinitionByServiceKey(serviceKey)
+	assert.Equal(t, expected, def2)
+	return mts.(*inmemory.MetadataService)
+}
diff --git a/metadata/service/service.go b/metadata/service/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..f6509d0a72eb26e488dfb4fdeef5f4bbfd6b1bea
--- /dev/null
+++ b/metadata/service/service.go
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package service
+
+import (
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/registry"
+)
+
+// MetadataService is used to define meta data related behaviors
+// usually the implementation should be singleton
+type MetadataService interface {
+	common.RPCService
+	// ServiceName will get the service's name in meta service , which is application name
+	ServiceName() (string, error)
+	// ExportURL will store the exported url in metadata
+	ExportURL(url common.URL) (bool, error)
+	// UnexportURL will delete the exported url in metadata
+	UnexportURL(url common.URL) error
+	// SubscribeURL will store the subscribed url in metadata
+	SubscribeURL(url common.URL) (bool, error)
+	// UnsubscribeURL will delete the subscribed url in metadata
+	UnsubscribeURL(url common.URL) error
+	// PublishServiceDefinition will generate the target url's code info
+	PublishServiceDefinition(url common.URL) error
+	// GetExportedURLs will get the target exported url in metadata
+	// the url should be unique
+	// due to dubbo-go only support return array []interface{} in RPCService, so we should declare the return type as []interface{}
+	// actually, it's []String
+	GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error)
+
+	MethodMapper() map[string]string
+
+	// GetExportedURLs will get the target subscribed url in metadata
+	// the url should be unique
+	GetSubscribedURLs() ([]common.URL, error)
+	// GetServiceDefinition will get the target service info store in metadata
+	GetServiceDefinition(interfaceName string, group string, version string) (string, error)
+	// GetServiceDefinition will get the target service info store in metadata by service key
+	GetServiceDefinitionByServiceKey(serviceKey string) (string, error)
+	// RefreshMetadata will refresh the metadata
+	RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error)
+	// Version will return the metadata service version
+	Version() (string, error)
+}
+
+// BaseMetadataService is used for the event logic for struct who will implement interface MetadataService
+type BaseMetadataService struct {
+	serviceName string
+}
+
+func NewBaseMetadataService(serviceName string) BaseMetadataService {
+	return BaseMetadataService{
+		serviceName: serviceName,
+	}
+}
+
+func (mts *BaseMetadataService) MethodMapper() map[string]string {
+	return map[string]string{
+		"GetExportedURLs": "getExportedURLs",
+	}
+}
+
+// ServiceName can get the service's name in meta service , which is application name
+func (mts *BaseMetadataService) ServiceName() (string, error) {
+	return mts.serviceName, nil
+}
+
+// Version will return the version of metadata service
+func (mts *BaseMetadataService) Reference() string {
+	return constant.SIMPLE_METADATA_SERVICE_NAME
+}
+
+type MetadataServiceProxyFactory interface {
+	GetProxy(ins registry.ServiceInstance) MetadataService
+}
+
+type MetadataServiceProxyCreator func(ins registry.ServiceInstance) MetadataService
+
+type BaseMetadataServiceProxyFactory struct {
+	proxies sync.Map
+	creator MetadataServiceProxyCreator
+}
+
+func NewBaseMetadataServiceProxyFactory(creator MetadataServiceProxyCreator) *BaseMetadataServiceProxyFactory {
+	return &BaseMetadataServiceProxyFactory{
+		creator: creator,
+	}
+}
+
+func (b *BaseMetadataServiceProxyFactory) GetProxy(ins registry.ServiceInstance) MetadataService {
+	key := ins.GetServiceName() + "##" + getExportedServicesRevision(ins)
+	if proxy, ok := b.proxies.Load(key); ok {
+		return proxy.(MetadataService)
+	}
+	v, _ := b.proxies.LoadOrStore(key, b.creator(ins))
+	return v.(MetadataService)
+}
+
+func getExportedServicesRevision(serviceInstance registry.ServiceInstance) string {
+	metaData := serviceInstance.GetMetadata()
+	return metaData[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME]
+}
+
+func ConvertURLArrToIntfArr(urls []common.URL) []interface{} {
+	if len(urls) == 0 {
+		return []interface{}{}
+	}
+
+	res := make([]interface{}, 0, len(urls))
+	for _, u := range urls {
+		res = append(res, u.String())
+	}
+	return res
+}
diff --git a/registry/base_registry.go b/registry/base_registry.go
index 3e1bddf233310871182544b6415c10c8df27e622..ad1a3b61741e003625612ad58409eb8615271a84 100644
--- a/registry/base_registry.go
+++ b/registry/base_registry.go
@@ -56,6 +56,8 @@ func init() {
 	localIP, _ = gxnet.GetLocalIP()
 }
 
+type createPathFunc func(dubboPath string) error
+
 /*
  * -----------------------------------NOTICE---------------------------------------------
  * If there is no special case, you'd better inherit BaseRegistry and implement the
@@ -74,8 +76,12 @@ type FacadeBasedRegistry interface {
 	CreatePath(string) error
 	// DoRegister actually do the register job
 	DoRegister(string, string) error
+	// DoUnregister do the unregister job
+	DoUnregister(string, string) error
 	// DoSubscribe actually subscribe the URL
 	DoSubscribe(conf *common.URL) (Listener, error)
+	// DoUnsubscribe does unsubscribe the URL
+	DoUnsubscribe(conf *common.URL) (Listener, error)
 	// CloseAndNilClient close the client and then reset the client in registry to nil
 	// you should notice that this method will be invoked inside a lock.
 	// So you should implement this method as light weighted as you can.
@@ -94,7 +100,7 @@ type BaseRegistry struct {
 	birth    int64          // time of file birth, seconds since Epoch; 0 if unknown
 	wg       sync.WaitGroup // wg+done for zk restart
 	done     chan struct{}
-	cltLock  sync.Mutex            //ctl lock is a lock for services map
+	cltLock  sync.RWMutex          //ctl lock is a lock for services map
 	services map[string]common.URL // service name + protocol -> service config, for store the service registered
 }
 
@@ -154,6 +160,43 @@ func (r *BaseRegistry) Register(conf common.URL) error {
 	return nil
 }
 
+// UnRegister implement interface registry to unregister
+func (r *BaseRegistry) UnRegister(conf common.URL) error {
+	var (
+		ok     bool
+		err    error
+		oldURL common.URL
+	)
+
+	func() {
+		r.cltLock.Lock()
+		defer r.cltLock.Unlock()
+		oldURL, ok = r.services[conf.Key()]
+
+		if !ok {
+			err = perrors.Errorf("Path{%s} has not registered", conf.Key())
+		}
+
+		delete(r.services, conf.Key())
+	}()
+
+	if err != nil {
+		return err
+	}
+
+	err = r.unregister(conf)
+	if err != nil {
+		func() {
+			r.cltLock.Lock()
+			defer r.cltLock.Unlock()
+			r.services[conf.Key()] = oldURL
+		}()
+		return perrors.WithMessagef(err, "register(conf:%+v)", conf)
+	}
+
+	return nil
+}
+
 // service is for getting service path stored in url
 func (r *BaseRegistry) service(c common.URL) string {
 	return url.QueryEscape(c.Service())
@@ -189,6 +232,18 @@ func (r *BaseRegistry) RestartCallBack() bool {
 
 // register for register url to registry, include init params
 func (r *BaseRegistry) register(c common.URL) error {
+	return r.processURL(c, r.facadeBasedRegistry.DoRegister, r.createPath)
+}
+
+// unregister for unregister url to registry, include init params
+func (r *BaseRegistry) unregister(c common.URL) error {
+	return r.processURL(c, r.facadeBasedRegistry.DoUnregister, nil)
+}
+
+func (r *BaseRegistry) processURL(c common.URL, f func(string, string) error, cpf createPathFunc) error {
+	if f == nil {
+		panic(" Must provide a `function(string, string) error` to process URL. ")
+	}
 	var (
 		err error
 		//revision   string
@@ -213,15 +268,15 @@ func (r *BaseRegistry) register(c common.URL) error {
 	switch role {
 
 	case common.PROVIDER:
-		dubboPath, rawURL, err = r.providerRegistry(c, params)
+		dubboPath, rawURL, err = r.providerRegistry(c, params, cpf)
 	case common.CONSUMER:
-		dubboPath, rawURL, err = r.consumerRegistry(c, params)
+		dubboPath, rawURL, err = r.consumerRegistry(c, params, cpf)
 	default:
 		return perrors.Errorf("@c{%v} type is not referencer or provider", c)
 	}
 	encodedURL = url.QueryEscape(rawURL)
 	dubboPath = strings.ReplaceAll(dubboPath, "$", "%24")
-	err = r.facadeBasedRegistry.DoRegister(dubboPath, encodedURL)
+	err = f(dubboPath, encodedURL)
 
 	if err != nil {
 		return perrors.WithMessagef(err, "register Node(path:%s, url:%s)", dubboPath, rawURL)
@@ -229,8 +284,15 @@ func (r *BaseRegistry) register(c common.URL) error {
 	return nil
 }
 
+// createPath will create dubbo path in register
+func (r *BaseRegistry) createPath(dubboPath string) error {
+	r.cltLock.Lock()
+	defer r.cltLock.Unlock()
+	return r.facadeBasedRegistry.CreatePath(dubboPath)
+}
+
 // providerRegistry for provider role do
-func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string, string, error) {
+func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) {
 	var (
 		dubboPath string
 		rawURL    string
@@ -240,11 +302,9 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string
 		return "", "", perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods)
 	}
 	dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER])
-	func() {
-		r.cltLock.Lock()
-		defer r.cltLock.Unlock()
-		err = r.facadeBasedRegistry.CreatePath(dubboPath)
-	}()
+	if f != nil {
+		err = f(dubboPath)
+	}
 	if err != nil {
 		logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%#v}", dubboPath, perrors.WithStack(err))
 		return "", "", perrors.WithMessagef(err, "facadeBasedRegistry.CreatePath(path:%s)", dubboPath)
@@ -274,7 +334,7 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string
 }
 
 // consumerRegistry for consumer role do
-func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string, string, error) {
+func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) {
 	var (
 		dubboPath string
 		rawURL    string
@@ -282,23 +342,18 @@ func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string
 	)
 	dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.CONSUMER])
 
-	func() {
-		r.cltLock.Lock()
-		defer r.cltLock.Unlock()
-		err = r.facadeBasedRegistry.CreatePath(dubboPath)
-
-	}()
+	if f != nil {
+		err = f(dubboPath)
+	}
 	if err != nil {
 		logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err))
 		return "", "", perrors.WithStack(err)
 	}
 	dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER])
 
-	func() {
-		r.cltLock.Lock()
-		defer r.cltLock.Unlock()
-		err = r.facadeBasedRegistry.CreatePath(dubboPath)
-	}()
+	if f != nil {
+		err = f(dubboPath)
+	}
 
 	if err != nil {
 		logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err))
@@ -323,20 +378,20 @@ func sleepWait(n int) {
 }
 
 // Subscribe :subscribe from registry, event will notify by notifyListener
-func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) {
+func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error {
 	n := 0
 	for {
 		n++
 		if !r.IsAvailable() {
 			logger.Warnf("event listener game over.")
-			return
+			return perrors.New("BaseRegistry is not available.")
 		}
 
 		listener, err := r.facadeBasedRegistry.DoSubscribe(url)
 		if err != nil {
 			if !r.IsAvailable() {
 				logger.Warnf("event listener game over.")
-				return
+				return err
 			}
 			logger.Warnf("getListener() = err:%v", perrors.WithStack(err))
 			time.Sleep(time.Duration(RegistryConnDelay) * time.Second)
@@ -358,6 +413,37 @@ func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener)
 	}
 }
 
+// UnSubscribe URL
+func (r *BaseRegistry) UnSubscribe(url *common.URL, notifyListener NotifyListener) error {
+	if !r.IsAvailable() {
+		logger.Warnf("event listener game over.")
+		return perrors.New("BaseRegistry is not available.")
+	}
+
+	listener, err := r.facadeBasedRegistry.DoUnsubscribe(url)
+	if err != nil {
+		if !r.IsAvailable() {
+			logger.Warnf("event listener game over.")
+			return perrors.New("BaseRegistry is not available.")
+		}
+		logger.Warnf("getListener() = err:%v", perrors.WithStack(err))
+		return perrors.WithStack(err)
+	}
+
+	for {
+		if serviceEvent, err := listener.Next(); err != nil {
+			logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err))
+			listener.Close()
+			break
+		} else {
+			logger.Infof("update begin, service event: %v", serviceEvent.String())
+			notifyListener.Notify(serviceEvent)
+		}
+
+	}
+	return nil
+}
+
 // closeRegisters close and remove registry client and reset services map
 func (r *BaseRegistry) closeRegisters() {
 	logger.Infof("begin to close provider client")
diff --git a/registry/consul/registry.go b/registry/consul/registry.go
index 4ef87394687aecc8804b2cebedd58fc0e72e8e6e..c425c5ec20d36be02c00499340f13b13c9aa2655 100644
--- a/registry/consul/registry.go
+++ b/registry/consul/registry.go
@@ -73,7 +73,8 @@ func newConsulRegistry(url *common.URL) (registry.Registry, error) {
 	return r, nil
 }
 
-// Register service to consul registry center
+// Register register @url
+// it delegate the job to register() method
 func (r *consulRegistry) Register(url common.URL) error {
 	var err error
 
@@ -87,6 +88,7 @@ func (r *consulRegistry) Register(url common.URL) error {
 	return nil
 }
 
+// register actually register the @url
 func (r *consulRegistry) register(url common.URL) error {
 	service, err := buildService(url)
 	if err != nil {
@@ -95,8 +97,9 @@ func (r *consulRegistry) register(url common.URL) error {
 	return r.client.Agent().ServiceRegister(service)
 }
 
-// Unregister service from consul registry center
-func (r *consulRegistry) Unregister(url common.URL) error {
+// UnRegister unregister the @url
+// it delegate the job to unregister() method
+func (r *consulRegistry) UnRegister(url common.URL) error {
 	var err error
 
 	role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, ""))
@@ -109,18 +112,27 @@ func (r *consulRegistry) Unregister(url common.URL) error {
 	return nil
 }
 
+// unregister actually unregister the @url
 func (r *consulRegistry) unregister(url common.URL) error {
 	return r.client.Agent().ServiceDeregister(buildId(url))
 }
 
-// Subscribe service from consul registry center
-func (r *consulRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) {
+// Subscribe subscribe the @url with the @notifyListener
+func (r *consulRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) error {
 	role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, ""))
 	if role == common.CONSUMER {
 		r.subscribe(url, notifyListener)
 	}
+	return nil
+}
+
+// UnSubscribe is not supported yet
+func (r *consulRegistry) UnSubscribe(url *common.URL, notifyListener registry.NotifyListener) error {
+	return perrors.New("UnSubscribe not support in consulRegistry")
 }
 
+// subscribe actually subscribe the @url
+// it loops forever until success
 func (r *consulRegistry) subscribe(url *common.URL, notifyListener registry.NotifyListener) {
 	for {
 		if !r.IsAvailable() {
diff --git a/registry/consul/registry_test.go b/registry/consul/registry_test.go
index bb6842cd8fb67dd2cc70b1a7530fbb94f618a9b0..94718f5ab657c198882f065a50e5d5a2c9d4bc6f 100644
--- a/registry/consul/registry_test.go
+++ b/registry/consul/registry_test.go
@@ -44,7 +44,7 @@ func (suite *consulRegistryTestSuite) testRegister() {
 
 func (suite *consulRegistryTestSuite) testUnregister() {
 	consulProviderRegistry, _ := suite.providerRegistry.(*consulRegistry)
-	err := consulProviderRegistry.Unregister(suite.providerUrl)
+	err := consulProviderRegistry.UnRegister(suite.providerUrl)
 	assert.NoError(suite.t, err)
 }
 
diff --git a/registry/consul/utils_test.go b/registry/consul/utils_test.go
index d66600b773ee78b43ac3da4edf8849d0019c744d..327dd95f7181907f6635c7fe89ef726bdcef5204 100644
--- a/registry/consul/utils_test.go
+++ b/registry/consul/utils_test.go
@@ -19,24 +19,19 @@ package consul
 
 import (
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/url"
-	"os"
 	"strconv"
 	"sync"
 	"testing"
 )
 
-import (
-	"github.com/hashicorp/consul/agent"
-)
-
 import (
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/registry"
 	"github.com/apache/dubbo-go/remoting"
+	"github.com/apache/dubbo-go/remoting/consul"
 )
 
 var (
@@ -51,71 +46,39 @@ var (
 )
 
 func newProviderRegistryUrl(host string, port int) *common.URL {
-	url1 := common.NewURLWithOptions(
+	return common.NewURLWithOptions(
 		common.WithIp(host),
 		common.WithPort(strconv.Itoa(port)),
 		common.WithParams(url.Values{}),
 		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)),
 	)
-	return url1
 }
 
 func newConsumerRegistryUrl(host string, port int) *common.URL {
-	url1 := common.NewURLWithOptions(
+	return common.NewURLWithOptions(
 		common.WithIp(host),
 		common.WithPort(strconv.Itoa(port)),
 		common.WithParams(url.Values{}),
 		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)),
 	)
-	return url1
 }
 
 func newProviderUrl(host string, port int, service string, protocol string) common.URL {
-	url1 := common.NewURLWithOptions(
+	return *common.NewURLWithOptions(
 		common.WithIp(host),
 		common.WithPort(strconv.Itoa(port)),
 		common.WithPath(service),
 		common.WithProtocol(protocol),
 	)
-	return *url1
 }
 
 func newConsumerUrl(host string, port int, service string, protocol string) common.URL {
-	url1 := common.NewURLWithOptions(
+	return *common.NewURLWithOptions(
 		common.WithIp(host),
 		common.WithPort(strconv.Itoa(port)),
 		common.WithPath(service),
 		common.WithProtocol(protocol),
 	)
-	return *url1
-}
-
-type testConsulAgent struct {
-	dataDir   string
-	testAgent *agent.TestAgent
-}
-
-func newConsulAgent(t *testing.T, port int) *testConsulAgent {
-	dataDir, _ := ioutil.TempDir("./", "agent")
-	hcl := `
-		ports { 
-			http = ` + strconv.Itoa(port) + `
-		}
-		data_dir = "` + dataDir + `"
-	`
-	testAgent := &agent.TestAgent{Name: t.Name(), DataDir: dataDir, HCL: hcl}
-	testAgent.Start(t)
-
-	consulAgent := &testConsulAgent{
-		dataDir:   dataDir,
-		testAgent: testAgent,
-	}
-	return consulAgent
-}
-
-func (consulAgent *testConsulAgent) close() {
-	consulAgent.testAgent.Shutdown()
-	os.RemoveAll(consulAgent.dataDir)
 }
 
 type testServer struct {
@@ -184,8 +147,8 @@ func (suite *consulRegistryTestSuite) close() {
 
 // register -> subscribe -> unregister
 func test1(t *testing.T) {
-	consulAgent := newConsulAgent(t, registryPort)
-	defer consulAgent.close()
+	consulAgent := consul.NewConsulAgent(t, registryPort)
+	defer consulAgent.Close()
 
 	server := newServer(providerHost, providerPort)
 	defer server.close()
@@ -204,8 +167,8 @@ func test1(t *testing.T) {
 
 // subscribe -> register
 func test2(t *testing.T) {
-	consulAgent := newConsulAgent(t, registryPort)
-	defer consulAgent.close()
+	consulAgent := consul.NewConsulAgent(t, registryPort)
+	defer consulAgent.Close()
 
 	server := newServer(providerHost, providerPort)
 	defer server.close()
diff --git a/registry/directory/directory.go b/registry/directory/directory.go
index e845db01f1b8f76897f2beeaee45a84537c96d83..2fbf9410f76c473362964c9ef148e3c581d3d045 100644
--- a/registry/directory/directory.go
+++ b/registry/directory/directory.go
@@ -54,7 +54,7 @@ type RegistryDirectory struct {
 	listenerLock                   sync.Mutex
 	serviceType                    string
 	registry                       registry.Registry
-	cacheInvokersMap               *sync.Map //use sync.map
+	cacheInvokersMap               *sync.Map // use sync.map
 	cacheOriginUrl                 *common.URL
 	configurators                  []config_center.Configurator
 	consumerConfigurationListener  *consumerConfigurationListener
@@ -108,15 +108,15 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) {
 		url        *common.URL
 		oldInvoker protocol.Invoker = nil
 	)
-	//judge is override or others
+	// judge is override or others
 	if res != nil {
 		url = &res.Service
-		//1.for override url in 2.6.x
+		// 1.for override url in 2.6.x
 		if url.Protocol == constant.OVERRIDE_PROTOCOL ||
 			url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.CONFIGURATORS_CATEGORY {
 			dir.configurators = append(dir.configurators, extension.GetDefaultConfigurator(url))
 			url = nil
-		} else if url.Protocol == constant.ROUTER_PROTOCOL || //2.for router
+		} else if url.Protocol == constant.ROUTER_PROTOCOL || // 2.for router
 			url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY {
 			url = nil
 
@@ -126,7 +126,7 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) {
 			logger.Infof("selector add service url{%s}", res.Service)
 
 			var urls []*common.URL
-			for _, v := range directory.GetRouterURLSet().Values() {
+			for _, v := range config.GetRouterURLSet().Values() {
 				urls = append(urls, v.(*common.URL))
 			}
 
@@ -177,7 +177,7 @@ func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker {
 	}
 	groupInvokersList := make([]protocol.Invoker, 0, len(groupInvokersMap))
 	if len(groupInvokersMap) == 1 {
-		//len is 1 it means no group setting ,so do not need cluster again
+		// len is 1 it means no group setting ,so do not need cluster again
 		for _, invokers := range groupInvokersMap {
 			groupInvokersList = invokers
 		}
@@ -221,7 +221,7 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
 		logger.Error("URL is nil ,pls check if service url is subscribe successfully!")
 		return nil
 	}
-	//check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol
+	// check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol
 	if url.Protocol == referenceUrl.Protocol || referenceUrl.Protocol == "" {
 		newUrl := common.MergeUrl(url, referenceUrl)
 		dir.overrideUrl(newUrl)
@@ -271,7 +271,7 @@ func (dir *RegistryDirectory) IsAvailable() bool {
 
 // Destroy method
 func (dir *RegistryDirectory) Destroy() {
-	//TODO:unregister & unsubscribe
+	// TODO:unregister & unsubscribe
 	dir.BaseDirectory.Destroy(func() {
 		invokers := dir.cacheInvokers
 		dir.cacheInvokers = []protocol.Invoker{}
diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go
index ac3f7124c12788959f529193b871652085fe6303..f2b2f8edd2d46950d2e74733b1d869e0de282ec0 100644
--- a/registry/directory/directory_test.go
+++ b/registry/directory/directory_test.go
@@ -45,7 +45,11 @@ import (
 )
 
 func init() {
-	config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}})
+	config.SetConsumerConfig(config.ConsumerConfig{
+		BaseConfig: config.BaseConfig{
+			ApplicationConfig: &config.ApplicationConfig{Name: "test-application"},
+		},
+	})
 }
 
 func TestSubscribe(t *testing.T) {
@@ -60,7 +64,7 @@ func TestSubscribe(t *testing.T) {
 //	registryDirectory, mockRegistry := normalRegistryDir()
 //	time.Sleep(1e9)
 //	assert.Len(t, registryDirectory.cacheInvokers, 3)
-//	mockRegistry.MockEvent(&registry.ServiceEvent{Action: remoting.EventTypeDel, Service: *common.NewURLWithOptions(common.WithPath("TEST0"), common.WithProtocol("dubbo"))})
+//	mockRegistry.MockEvent(&registry.ServiceEvent{Action: remoting.EventTypeDel, Service: *event.NewURLWithOptions(event.WithPath("TEST0"), event.WithProtocol("dubbo"))})
 //	time.Sleep(1e9)
 //	assert.Len(t, registryDirectory.cacheInvokers, 2)
 //}
diff --git a/registry/etcdv3/registry.go b/registry/etcdv3/registry.go
index f3df78177bda2b068d0ad88156b593ab3d48c5d7..2fec8eaad25e716fc5ed5ee33775d8898cb212e2 100644
--- a/registry/etcdv3/registry.go
+++ b/registry/etcdv3/registry.go
@@ -116,6 +116,11 @@ func (r *etcdV3Registry) DoRegister(root string, node string) error {
 	return r.client.Create(path.Join(root, node), "")
 }
 
+// nolint
+func (r *etcdV3Registry) DoUnregister(root string, node string) error {
+	return perrors.New("DoUnregister is not support in etcdV3Registry")
+}
+
 // CloseAndNilClient closes listeners and clear client
 func (r *etcdV3Registry) CloseAndNilClient() {
 	r.client.Close()
@@ -174,3 +179,7 @@ func (r *etcdV3Registry) DoSubscribe(svc *common.URL) (registry.Listener, error)
 
 	return configListener, nil
 }
+
+func (r *etcdV3Registry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) {
+	return nil, perrors.New("DoUnsubscribe is not support in etcdV3Registry")
+}
diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..10396049fb6bb7a5a5935ce21639dc5a78a56b0b
--- /dev/null
+++ b/registry/etcdv3/service_discovery.go
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package etcdv3
+
+import (
+	"fmt"
+	"sync"
+	"time"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	"github.com/hashicorp/vault/helper/jsonutil"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/remoting"
+	"github.com/apache/dubbo-go/remoting/etcdv3"
+)
+
+const (
+	ROOT = "/services"
+)
+
+var (
+	initLock sync.Mutex
+)
+
+func init() {
+	extension.SetServiceDiscovery(constant.ETCDV3_KEY, newEtcdV3ServiceDiscovery)
+}
+
+// new etcd service discovery struct
+type etcdV3ServiceDiscovery struct {
+	// descriptor is a short string about the basic information of this instance
+	descriptor string
+	// client is current Etcdv3 client
+	client *etcdv3.Client
+	// serviceInstance is current serviceInstance
+	serviceInstance *registry.ServiceInstance
+	// services is when register or update will add service name
+	services *gxset.HashSet
+	// child listener
+	childListenerMap map[string]*etcdv3.EventListener
+}
+
+// basic information of this instance
+func (e *etcdV3ServiceDiscovery) String() string {
+	return e.descriptor
+}
+
+// Destory service discovery
+func (e *etcdV3ServiceDiscovery) Destroy() error {
+	if e.client != nil {
+		e.client.Close()
+	}
+	return nil
+}
+
+// Register will register an instance of ServiceInstance to registry
+func (e *etcdV3ServiceDiscovery) Register(instance registry.ServiceInstance) error {
+
+	e.serviceInstance = &instance
+
+	path := toPath(instance)
+
+	if nil != e.client {
+		ins, err := jsonutil.EncodeJSON(instance)
+		if err == nil {
+			err = e.client.RegisterTemp(path, string(ins))
+			if err != nil {
+				logger.Errorf("cannot register the instance: %s", string(ins), err)
+			} else {
+				e.services.Add(instance.GetServiceName())
+			}
+		}
+	}
+
+	return nil
+}
+
+// Update will update the data of the instance in registry
+func (e *etcdV3ServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	path := toPath(instance)
+
+	if nil != e.client {
+		ins, err := jsonutil.EncodeJSON(instance)
+		if nil == err {
+			e.client.RegisterTemp(path, string(ins))
+			e.services.Add(instance.GetServiceName())
+		}
+	}
+
+	return nil
+}
+
+// Unregister will unregister this instance from registry
+func (e *etcdV3ServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	path := toPath(instance)
+
+	if nil != e.client {
+		err := e.client.Delete(path)
+		e.services.Remove(instance.GetServiceName())
+		e.serviceInstance = nil
+		return err
+	}
+
+	return nil
+}
+
+// ----------------- discovery -------------------
+// GetDefaultPageSize will return the default page size
+func (e *etcdV3ServiceDiscovery) GetDefaultPageSize() int {
+	return registry.DefaultPageSize
+}
+
+// GetServices will return the all service names.
+func (e *etcdV3ServiceDiscovery) GetServices() *gxset.HashSet {
+	return e.services
+}
+
+// GetInstances will return all service instances with serviceName
+func (e *etcdV3ServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+
+	if nil != e.client {
+		// get keys and values
+		_, vList, err := e.client.GetChildrenKVList(toParentPath(serviceName))
+		if nil == err {
+			serviceInstances := make([]registry.ServiceInstance, 0, len(vList))
+			for _, v := range vList {
+				instance := &registry.DefaultServiceInstance{}
+				err = jsonutil.DecodeJSON([]byte(v), &instance)
+				if nil == err {
+					serviceInstances = append(serviceInstances, instance)
+				}
+			}
+			return serviceInstances
+		}
+		logger.Infof("could not getChildrenKVList the err is:%v", err)
+	}
+
+	return make([]registry.ServiceInstance, 0, 0)
+}
+
+// GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName
+// the page will start at offset
+func (e *etcdV3ServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+
+	all := e.GetInstances(serviceName)
+
+	res := make([]interface{}, 0, pageSize)
+
+	for i := offset; i < len(all) && i < offset+pageSize; i++ {
+		res = append(res, all[i])
+	}
+
+	return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// GetHealthyInstancesByPage will return a page containing instances of ServiceInstance.
+// The param healthy indices that the instance should be healthy or not.
+// The page will start at offset
+func (e *etcdV3ServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	all := e.GetInstances(serviceName)
+	res := make([]interface{}, 0, pageSize)
+
+	var (
+		i     = offset
+		count = 0
+	)
+	for i < len(all) && count < pageSize {
+		ins := all[i]
+		if ins.IsHealthy() == healthy {
+			res = append(res, all[i])
+			count++
+		}
+		i++
+	}
+	return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// Batch get all instances by the specified service names
+func (e *etcdV3ServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+	res := make(map[string]gxpage.Pager, len(serviceNames))
+	for _, name := range serviceNames {
+		res[name] = e.GetInstancesByPage(name, offset, requestedSize)
+	}
+	return res
+}
+
+// ----------------- event ----------------------
+// AddListener adds a new ServiceInstancesChangedListener
+// see addServiceInstancesChangedListener in Java
+func (e *etcdV3ServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	return e.registerSreviceWatcher(listener.ServiceName)
+}
+
+// DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName
+func (e *etcdV3ServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+	return e.DispatchEventForInstances(serviceName, e.GetInstances(serviceName))
+}
+
+// DispatchEventForInstances dispatches the ServiceInstancesChangedEvent to target instances
+func (e *etcdV3ServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	return e.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances))
+}
+
+// DispatchEvent dispatches the event
+func (e *etcdV3ServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	extension.GetGlobalDispatcher().Dispatch(event)
+	return nil
+}
+
+// Convert instance to dubbo path
+func toPath(instance registry.ServiceInstance) string {
+	if instance == nil {
+		return ""
+	}
+	// like: /services/servicename1/host(127.0.0.1)/8080
+	return fmt.Sprintf("%s%d", ROOT+constant.PATH_SEPARATOR+instance.GetServiceName()+constant.PATH_SEPARATOR+instance.GetHost()+constant.KEY_SEPARATOR, instance.GetPort())
+}
+
+// to dubbo service path
+func toParentPath(serviceName string) string {
+	return ROOT + constant.PATH_SEPARATOR + serviceName
+}
+
+// register service watcher
+func (e *etcdV3ServiceDiscovery) registerSreviceWatcher(serviceName string) error {
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	path := toParentPath(serviceName)
+
+	listener, found := e.childListenerMap[serviceName]
+
+	if !found {
+		listener = etcdv3.NewEventListener(e.client)
+		e.childListenerMap[serviceName] = listener
+	}
+
+	listener.ListenServiceEvent(path, e)
+
+	return nil
+}
+
+// when child data change should DispatchEventByServiceName
+func (e *etcdV3ServiceDiscovery) DataChange(eventType remoting.Event) bool {
+
+	if eventType.Action == remoting.EventTypeUpdate {
+		instance := &registry.DefaultServiceInstance{}
+		err := jsonutil.DecodeJSON([]byte(eventType.Content), &instance)
+		if err != nil {
+			instance.ServiceName = ""
+		}
+
+		if err := e.DispatchEventByServiceName(instance.ServiceName); err != nil {
+			return false
+		}
+	}
+
+	return true
+}
+
+// netEcdv3ServiceDiscovery
+func newEtcdV3ServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || len(sdc.RemoteRef) == 0 {
+		return nil, perrors.New("could not init the etcd service instance because the config is invalid")
+	}
+
+	remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef)
+	if !ok {
+		return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef)
+	}
+
+	// init etcdv3 client
+	timeout, err := time.ParseDuration(remoteConfig.TimeoutStr)
+	if err != nil {
+		logger.Errorf("timeout config %v is invalid,err is %v", remoteConfig.TimeoutStr, err.Error())
+		return nil, perrors.WithMessagef(err, "new etcd service discovery(address:%v)", remoteConfig.Address)
+	}
+
+	logger.Infof("etcd address is: %v,timeout is:%s", remoteConfig.Address, timeout.String())
+
+	client := etcdv3.NewServiceDiscoveryClient(
+		etcdv3.WithName(etcdv3.RegistryETCDV3Client),
+		etcdv3.WithTimeout(timeout),
+		etcdv3.WithEndpoints(remoteConfig.Address),
+	)
+
+	descriptor := fmt.Sprintf("etcd-service-discovery[%s]", remoteConfig.Address)
+
+	return &etcdV3ServiceDiscovery{descriptor, client, nil, gxset.NewSet(), make(map[string]*etcdv3.EventListener, 0)}, nil
+}
diff --git a/registry/etcdv3/service_discovery_test.go b/registry/etcdv3/service_discovery_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d8e3f1a2864150cc1f1e8996bc7c53e115dbef45
--- /dev/null
+++ b/registry/etcdv3/service_discovery_test.go
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package etcdv3
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var testName = "test"
+
+func setUp() {
+	config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{
+		Protocol:  "etcdv3",
+		RemoteRef: testName,
+	}
+
+	config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{
+		Address:    "localhost:2379",
+		TimeoutStr: "10s",
+	}
+}
+
+func Test_newEtcdV3ServiceDiscovery(t *testing.T) {
+	name := constant.ETCDV3_KEY
+	_, err := newEtcdV3ServiceDiscovery(name)
+
+	// warn: log configure file name is nil
+	assert.NotNil(t, err)
+
+	sdc := &config.ServiceDiscoveryConfig{
+		Protocol:  "etcdv3",
+		RemoteRef: "mock",
+	}
+	config.GetBaseConfig().ServiceDiscoveries[name] = sdc
+
+	_, err = newEtcdV3ServiceDiscovery(name)
+
+	// RemoteConfig not found
+	assert.NotNil(t, err)
+
+	config.GetBaseConfig().Remotes["mock"] = &config.RemoteConfig{
+		Address:    "localhost:2379",
+		TimeoutStr: "10s",
+	}
+
+	res, err := newEtcdV3ServiceDiscovery(name)
+	assert.Nil(t, err)
+	assert.NotNil(t, res)
+}
+
+func TestEtcdV3ServiceDiscovery_GetDefaultPageSize(t *testing.T) {
+	setUp()
+	serviceDiscovry := &etcdV3ServiceDiscovery{}
+	assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize())
+}
diff --git a/registry/event.go b/registry/event.go
index 5fe6df6a379e1de8662917fb76c6d16fa9a17f37..39fb00c740ef2e70e2cd6768fa4a4bb3226f832c 100644
--- a/registry/event.go
+++ b/registry/event.go
@@ -25,6 +25,7 @@ import (
 
 import (
 	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/observer"
 	"github.com/apache/dubbo-go/remoting"
 )
 
@@ -47,47 +48,9 @@ func (e ServiceEvent) String() string {
 	return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}}", e.Action, e.Service)
 }
 
-// Event is align with Event interface in Java.
-// it's the top abstraction
-// Align with 2.7.5
-type Event interface {
-	fmt.Stringer
-	GetSource() interface{}
-	GetTimestamp() time.Time
-}
-
-// baseEvent is the base implementation of Event
-// You should never use it directly
-type baseEvent struct {
-	source    interface{}
-	timestamp time.Time
-}
-
-// GetSource return the source
-func (b *baseEvent) GetSource() interface{} {
-	return b.source
-}
-
-// GetTimestamp return the timestamp when the event is created
-func (b *baseEvent) GetTimestamp() time.Time {
-	return b.timestamp
-}
-
-// String return a human readable string representing this event
-func (b *baseEvent) String() string {
-	return fmt.Sprintf("baseEvent[source = %#v]", b.source)
-}
-
-func newBaseEvent(source interface{}) *baseEvent {
-	return &baseEvent{
-		source:    source,
-		timestamp: time.Now(),
-	}
-}
-
 // ServiceInstancesChangedEvent represents service instances make some changing
 type ServiceInstancesChangedEvent struct {
-	baseEvent
+	observer.BaseEvent
 	ServiceName string
 	Instances   []ServiceInstance
 }
@@ -100,9 +63,9 @@ func (s *ServiceInstancesChangedEvent) String() string {
 // NewServiceInstancesChangedEvent will create the ServiceInstanceChangedEvent instance
 func NewServiceInstancesChangedEvent(serviceName string, instances []ServiceInstance) *ServiceInstancesChangedEvent {
 	return &ServiceInstancesChangedEvent{
-		baseEvent: baseEvent{
-			source:    serviceName,
-			timestamp: time.Now(),
+		BaseEvent: observer.BaseEvent{
+			Source:    serviceName,
+			Timestamp: time.Now(),
 		},
 		ServiceName: serviceName,
 		Instances:   instances,
diff --git a/registry/event/customizable_service_instance_listener.go b/registry/event/customizable_service_instance_listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..07e84c1454df91d2038beb08abddbc46274623c9
--- /dev/null
+++ b/registry/event/customizable_service_instance_listener.go
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"reflect"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+func init() {
+	extension.AddEventListener(GetCustomizableServiceInstanceListener)
+}
+
+// customizableServiceInstanceListener is singleton
+type customizableServiceInstanceListener struct {
+}
+
+// GetPriority return priority 9999,
+// 9999 is big enough to make sure it will be last invoked
+func (c *customizableServiceInstanceListener) GetPriority() int {
+	return 9999
+}
+
+// OnEvent if the event is ServiceInstancePreRegisteredEvent
+// it will iterate all ServiceInstanceCustomizer instances
+// or it will do nothing
+func (c *customizableServiceInstanceListener) OnEvent(e observer.Event) error {
+	if preRegEvent, ok := e.(*ServiceInstancePreRegisteredEvent); ok {
+		for _, cus := range extension.GetCustomizers() {
+			cus.Customize(preRegEvent.serviceInstance)
+		}
+	}
+	return nil
+}
+
+// GetEventType will return ServiceInstancePreRegisteredEvent
+func (c *customizableServiceInstanceListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(&ServiceInstancePreRegisteredEvent{})
+}
+
+var (
+	customizableServiceInstanceListenerInstance *customizableServiceInstanceListener
+	customizableServiceInstanceListenerOnce     sync.Once
+)
+
+// GetCustomizableServiceInstanceListener returns an instance
+// if the instance was not initialized, we create one
+func GetCustomizableServiceInstanceListener() observer.EventListener {
+	customizableServiceInstanceListenerOnce.Do(func() {
+		customizableServiceInstanceListenerInstance = &customizableServiceInstanceListener{}
+	})
+	return customizableServiceInstanceListenerInstance
+}
diff --git a/registry/event/customizable_service_instance_listener_test.go b/registry/event/customizable_service_instance_listener_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c81ece498b4864c3ea7f586d90052f3022627fc
--- /dev/null
+++ b/registry/event/customizable_service_instance_listener_test.go
@@ -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 event
+
+import (
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestGetCustomizableServiceInstanceListener(t *testing.T) {
+
+	prepareMetadataServiceForTest()
+
+	cus := GetCustomizableServiceInstanceListener()
+
+	assert.Equal(t, 9999, cus.GetPriority())
+
+	extension.AddCustomizers(&mockCustomizer{})
+
+	err := cus.OnEvent(&mockEvent{})
+	assert.Nil(t, err)
+	err = cus.OnEvent(NewServiceInstancePreRegisteredEvent("hello", createInstance()))
+	assert.Nil(t, err)
+
+	tp := cus.GetEventType()
+	assert.NotNil(t, tp)
+}
+
+type mockEvent struct {
+}
+
+func (m *mockEvent) String() string {
+	panic("implement me")
+}
+
+func (m *mockEvent) GetSource() interface{} {
+	panic("implement me")
+}
+
+func (m *mockEvent) GetTimestamp() time.Time {
+	panic("implement me")
+}
+
+type mockCustomizer struct {
+}
+
+func (m *mockCustomizer) GetPriority() int {
+	return 0
+}
+
+func (m *mockCustomizer) Customize(instance registry.ServiceInstance) {
+}
diff --git a/registry/event/event_publishing_service_deiscovery_test.go b/registry/event/event_publishing_service_deiscovery_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..54752c03c0de598226270b27c8d7d0f3621d07d1
--- /dev/null
+++ b/registry/event/event_publishing_service_deiscovery_test.go
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"reflect"
+	"testing"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/suite"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	dispatcher2 "github.com/apache/dubbo-go/common/observer/dispatcher"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/mapping"
+	_ "github.com/apache/dubbo-go/metadata/service/inmemory"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestEventPublishingServiceDiscovery_DispatchEvent(t *testing.T) {
+
+	// extension.SetMetadataService("local", inmemory.NewMetadataService)
+
+	config.GetApplicationConfig().MetadataType = "local"
+
+	extension.SetGlobalServiceNameMapping(func() mapping.ServiceNameMapping {
+		return &mockServiceNameMapping{}
+	})
+
+	dc := NewEventPublishingServiceDiscovery(&ServiceDiscoveryA{})
+	tsd := &TestServiceDiscoveryDestroyingEventListener{
+		BaseListener: observer.NewBaseListener(),
+	}
+	tsd.SetT(t)
+	tsi := &TestServiceInstancePreRegisteredEventListener{}
+	tsi.SetT(t)
+	extension.AddEventListener(func() observer.EventListener {
+		return tsd
+	})
+	extension.AddEventListener(func() observer.EventListener {
+		return tsi
+	})
+	extension.SetEventDispatcher("direct", dispatcher2.NewDirectEventDispatcher)
+	extension.SetAndInitGlobalDispatcher("direct")
+	err := dc.Destroy()
+	assert.Nil(t, err)
+	si := &registry.DefaultServiceInstance{Id: "testServiceInstance"}
+	err = dc.Register(si)
+	assert.Nil(t, err)
+
+}
+
+type TestServiceDiscoveryDestroyingEventListener struct {
+	suite.Suite
+	observer.BaseListener
+}
+
+func (tel *TestServiceDiscoveryDestroyingEventListener) OnEvent(e observer.Event) error {
+	e1, ok := e.(*ServiceDiscoveryDestroyingEvent)
+	assert.Equal(tel.T(), ok, true)
+	assert.Equal(tel.T(), "testServiceDiscovery", e1.GetOriginal().String())
+	assert.Equal(tel.T(), "testServiceDiscovery", e1.GetServiceDiscovery().String())
+	return nil
+}
+
+func (tel *TestServiceDiscoveryDestroyingEventListener) GetPriority() int {
+	return -1
+}
+
+func (tel *TestServiceDiscoveryDestroyingEventListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(ServiceDiscoveryDestroyingEvent{})
+}
+
+type TestServiceInstancePreRegisteredEventListener struct {
+	suite.Suite
+	observer.BaseListener
+}
+
+func (tel *TestServiceInstancePreRegisteredEventListener) OnEvent(e observer.Event) error {
+	e1, ok := e.(*ServiceInstancePreRegisteredEvent)
+	assert.Equal(tel.T(), ok, true)
+	assert.Equal(tel.T(), "testServiceInstance", e1.getServiceInstance().GetId())
+	return nil
+}
+
+func (tel *TestServiceInstancePreRegisteredEventListener) GetPriority() int {
+	return -1
+}
+
+func (tel *TestServiceInstancePreRegisteredEventListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(ServiceInstancePreRegisteredEvent{})
+}
+
+type ServiceDiscoveryA struct {
+}
+
+// String return mockServiceDiscovery
+func (msd *ServiceDiscoveryA) String() string {
+	return "testServiceDiscovery"
+}
+
+// Destroy do nothing
+func (msd *ServiceDiscoveryA) Destroy() error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) Register(instance registry.ServiceInstance) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) Update(instance registry.ServiceInstance) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) Unregister(instance registry.ServiceInstance) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) GetDefaultPageSize() int {
+	return 1
+}
+
+func (msd *ServiceDiscoveryA) GetServices() *gxset.HashSet {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) GetInstances(serviceName string) []registry.ServiceInstance {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) DispatchEventByServiceName(serviceName string) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	return nil
+}
+
+func (msd *ServiceDiscoveryA) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	return nil
+}
+
+type mockServiceNameMapping struct {
+}
+
+func (m *mockServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
+	return nil
+}
+
+func (m *mockServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+	return gxset.NewSet("dubbo"), nil
+}
diff --git a/registry/event/event_publishing_service_discovery.go b/registry/event/event_publishing_service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ee2f4a44946065cdf7489abc391df41f251d810
--- /dev/null
+++ b/registry/event/event_publishing_service_discovery.go
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+// EventPublishingServiceDiscovery will enhance Service Discovery
+// Publish some event about service discovery
+type EventPublishingServiceDiscovery struct {
+	serviceDiscovery registry.ServiceDiscovery
+}
+
+// NewEventPublishingServiceDiscovery is a constructor
+func NewEventPublishingServiceDiscovery(serviceDiscovery registry.ServiceDiscovery) *EventPublishingServiceDiscovery {
+	return &EventPublishingServiceDiscovery{
+		serviceDiscovery: serviceDiscovery,
+	}
+}
+
+// String returns serviceDiscovery.String()
+func (epsd *EventPublishingServiceDiscovery) String() string {
+	return epsd.serviceDiscovery.String()
+}
+
+// Destroy delegate function
+func (epsd *EventPublishingServiceDiscovery) Destroy() error {
+	f := func() error {
+		return epsd.serviceDiscovery.Destroy()
+	}
+	return epsd.executeWithEvents(NewServiceDiscoveryDestroyingEvent(epsd, epsd.serviceDiscovery),
+		f, NewServiceDiscoveryDestroyedEvent(epsd, epsd.serviceDiscovery))
+}
+
+// Register delegate function
+func (epsd *EventPublishingServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	f := func() error {
+		return epsd.serviceDiscovery.Register(instance)
+	}
+	return epsd.executeWithEvents(NewServiceInstancePreRegisteredEvent(epsd.serviceDiscovery, instance),
+		f, NewServiceInstanceRegisteredEvent(epsd.serviceDiscovery, instance))
+
+}
+
+// Update returns the result of serviceDiscovery.Update
+func (epsd *EventPublishingServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	f := func() error {
+		return epsd.serviceDiscovery.Update(instance)
+	}
+	return epsd.executeWithEvents(nil, f, nil)
+}
+
+// Unregister unregister the instance and drop ServiceInstancePreUnregisteredEvent and ServiceInstanceUnregisteredEvent
+func (epsd *EventPublishingServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	f := func() error {
+		return epsd.serviceDiscovery.Unregister(instance)
+	}
+	return epsd.executeWithEvents(NewServiceInstancePreUnregisteredEvent(epsd.serviceDiscovery, instance),
+		f, NewServiceInstanceUnregisteredEvent(epsd.serviceDiscovery, instance))
+}
+
+// GetDefaultPageSize returns the result of serviceDiscovery.GetDefaultPageSize
+func (epsd *EventPublishingServiceDiscovery) GetDefaultPageSize() int {
+	return epsd.serviceDiscovery.GetDefaultPageSize()
+}
+
+// GetServices returns the result of serviceDiscovery.GetServices
+func (epsd *EventPublishingServiceDiscovery) GetServices() *gxset.HashSet {
+	return epsd.serviceDiscovery.GetServices()
+}
+
+// GetInstances returns the result of serviceDiscovery.GetInstances
+func (epsd *EventPublishingServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+	return epsd.serviceDiscovery.GetInstances(serviceName)
+}
+
+// GetInstancesByPage returns the result of serviceDiscovery.GetInstancesByPage
+func (epsd *EventPublishingServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+	return epsd.serviceDiscovery.GetInstancesByPage(serviceName, offset, pageSize)
+}
+
+// GetHealthyInstancesByPage returns the result of serviceDiscovery.GetHealthyInstancesByPage
+func (epsd *EventPublishingServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	return epsd.serviceDiscovery.GetHealthyInstancesByPage(serviceName, offset, pageSize, healthy)
+}
+
+// GetRequestInstances returns result from serviceDiscovery.GetRequestInstances
+func (epsd *EventPublishingServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+	return epsd.serviceDiscovery.GetRequestInstances(serviceNames, offset, requestedSize)
+}
+
+// AddListener add event listener
+func (epsd *EventPublishingServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	extension.GetGlobalDispatcher().AddEventListener(listener)
+	return epsd.serviceDiscovery.AddListener(listener)
+}
+
+// DispatchEventByServiceName pass serviceName to serviceDiscovery
+func (epsd *EventPublishingServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+	return epsd.serviceDiscovery.DispatchEventByServiceName(serviceName)
+}
+
+// DispatchEventForInstances pass params to serviceDiscovery
+func (epsd *EventPublishingServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	return epsd.serviceDiscovery.DispatchEventForInstances(serviceName, instances)
+}
+
+// DispatchEvent pass the event to serviceDiscovery
+func (epsd *EventPublishingServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	return epsd.serviceDiscovery.DispatchEvent(event)
+}
+
+// executeWithEvents dispatch before event and after event if return error will dispatch exception event
+func (epsd *EventPublishingServiceDiscovery) executeWithEvents(beforeEvent observer.Event, f func() error, afterEvent observer.Event) error {
+	globalDispatcher := extension.GetGlobalDispatcher()
+	if beforeEvent != nil {
+		globalDispatcher.Dispatch(beforeEvent)
+	}
+	if err := f(); err != nil {
+		globalDispatcher.Dispatch(NewServiceDiscoveryExceptionEvent(epsd, epsd.serviceDiscovery, err))
+		return err
+	}
+	if afterEvent != nil {
+		globalDispatcher.Dispatch(afterEvent)
+	}
+	return nil
+}
+
+// getMetadataService returns metadata service instance
+func getMetadataService() (service.MetadataService, error) {
+	return extension.GetMetadataService(config.GetApplicationConfig().MetadataType)
+}
diff --git a/registry/event/log_event_listener.go b/registry/event/log_event_listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..0781a6d6db303ba3a1eb99b6b4c6d0743f9066b3
--- /dev/null
+++ b/registry/event/log_event_listener.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"reflect"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+func init() {
+	extension.AddEventListener(GetLogEventListener)
+}
+
+// logEventListener is singleton
+type logEventListener struct {
+}
+
+func (l *logEventListener) GetPriority() int {
+	return 0
+}
+
+func (l *logEventListener) OnEvent(e observer.Event) error {
+	logger.Info("Event happen: " + e.String())
+	return nil
+}
+
+func (l *logEventListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(&observer.BaseEvent{})
+}
+
+var logEventListenerInstance *logEventListener
+var logEventListenerOnce sync.Once
+
+func GetLogEventListener() observer.EventListener {
+	logEventListenerOnce.Do(func() {
+		logEventListenerInstance = &logEventListener{}
+	})
+	return logEventListenerInstance
+}
diff --git a/registry/event/log_event_listener_test.go b/registry/event/log_event_listener_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3136564687f74e4c5cebd13d135e097234b21284
--- /dev/null
+++ b/registry/event/log_event_listener_test.go
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLogEventListener(t *testing.T) {
+	l := &logEventListener{}
+	assert.Equal(t, 0, l.GetPriority())
+	assert.Nil(t, l.OnEvent(&ServiceDiscoveryDestroyedEvent{}))
+}
diff --git a/registry/event/metadata_service_url_params_customizer.go b/registry/event/metadata_service_url_params_customizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..6d8f99b327363c9a2d636079ef1f74e78d4e0184
--- /dev/null
+++ b/registry/event/metadata_service_url_params_customizer.go
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"encoding/json"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func init() {
+	exceptKeys := gxset.NewSet(
+		// remove APPLICATION_KEY because service name must be present
+		constant.APPLICATION_KEY,
+		// remove GROUP_KEY, always uses service name.
+		constant.GROUP_KEY,
+		// remove TIMESTAMP_KEY because it's nonsense
+		constant.TIMESTAMP_KEY)
+	extension.AddCustomizers(&metadataServiceURLParamsMetadataCustomizer{exceptKeys: exceptKeys})
+
+}
+
+type metadataServiceURLParamsMetadataCustomizer struct {
+	exceptKeys *gxset.HashSet
+}
+
+// GetPriority will return 0 so that it will be invoked in front of user defining Customizer
+func (m *metadataServiceURLParamsMetadataCustomizer) GetPriority() int {
+	return 0
+}
+
+func (m *metadataServiceURLParamsMetadataCustomizer) Customize(instance registry.ServiceInstance) {
+	ms, err := getMetadataService()
+	if err != nil {
+		logger.Errorf("could not find the metadata service", err)
+		return
+	}
+	serviceName := constant.METADATA_SERVICE_NAME
+	// error always is nil
+	version, _ := ms.Version()
+	group := instance.GetServiceName()
+	urls, err := ms.GetExportedURLs(serviceName, group, version, constant.ANY_VALUE)
+	if err != nil || len(urls) == 0 {
+		logger.Info("could not find the exported urls", err)
+		return
+	}
+	ps := m.convertToParams(urls)
+	str, err := json.Marshal(ps)
+	if err != nil {
+		logger.Errorf("could not transfer the map to json", err)
+		return
+	}
+	instance.GetMetadata()[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME] = string(str)
+}
+
+func (m *metadataServiceURLParamsMetadataCustomizer) convertToParams(urls []interface{}) map[string]map[string]string {
+
+	// usually there will be only one protocol
+	res := make(map[string]map[string]string, 1)
+	// those keys are useless
+
+	for _, ui := range urls {
+		u, err := common.NewURL(ui.(string))
+		if err != nil {
+			logger.Errorf("could not parse the string to url: %s", ui.(string), err)
+			continue
+		}
+		p := make(map[string]string, len(u.GetParams()))
+		for k, v := range u.GetParams() {
+			// we will ignore that
+			if m.exceptKeys.Contains(k) || len(v) == 0 || len(v[0]) == 0 {
+				continue
+			}
+			p[k] = v[0]
+		}
+		p[constant.PORT_KEY] = u.Port
+		res[u.Protocol] = p
+	}
+	return res
+}
diff --git a/registry/event/metadata_service_url_params_customizer_test.go b/registry/event/metadata_service_url_params_customizer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..98ae2df883f590f4c3e4b379bb5a0fcbe46d946c
--- /dev/null
+++ b/registry/event/metadata_service_url_params_customizer_test.go
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"testing"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func prepareMetadataServiceForTest() {
+	config.GetApplicationConfig().MetadataType = "mock"
+	extension.SetMetadataService("mock", func() (service.MetadataService, error) {
+		return &mockMetadataService{
+			urls: []interface{}{"mock://localhost:8080?a=b"},
+		}, nil
+	})
+}
+
+func TestMetadataServiceURLParamsMetadataCustomizer(t *testing.T) {
+
+	prepareMetadataServiceForTest()
+
+	msup := &metadataServiceURLParamsMetadataCustomizer{exceptKeys: gxset.NewSet()}
+	assert.Equal(t, 0, msup.GetPriority())
+
+	msup.Customize(createInstance())
+}
+
+func createInstance() registry.ServiceInstance {
+	ins := &registry.DefaultServiceInstance{}
+	return ins
+}
+
+type mockMetadataService struct {
+	urls []interface{}
+}
+
+func (m *mockMetadataService) Reference() string {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) ServiceName() (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) UnexportURL(url common.URL) error {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) UnsubscribeURL(url common.URL) error {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+	return m.urls, nil
+}
+
+func (m *mockMetadataService) MethodMapper() map[string]string {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) {
+	res := make([]common.URL, 0, len(m.urls))
+	for _, ui := range m.urls {
+		u, _ := common.NewURL(ui.(string))
+		res = append(res, u)
+	}
+	return res, nil
+}
+
+func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) Version() (string, error) {
+	return "1.0.0", nil
+}
diff --git a/registry/event/protocol_ports_metadata_customizer.go b/registry/event/protocol_ports_metadata_customizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..a58471c2bd5a2e1b7b4211e02f605763b2e72c9c
--- /dev/null
+++ b/registry/event/protocol_ports_metadata_customizer.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"encoding/json"
+	"strconv"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func init() {
+	extension.AddCustomizers(&ProtocolPortsMetadataCustomizer{})
+}
+
+// ProtocolPortsMetadataCustomizer will update the endpoints
+type ProtocolPortsMetadataCustomizer struct {
+}
+
+// GetPriority will return 0, which means it will be invoked at the beginning
+func (p *ProtocolPortsMetadataCustomizer) GetPriority() int {
+	return 0
+}
+
+// Customize put the the string like [{"protocol": "dubbo", "port": 123}] into instance's metadata
+func (p *ProtocolPortsMetadataCustomizer) Customize(instance registry.ServiceInstance) {
+	metadataService, err := getMetadataService()
+	if err != nil {
+		logger.Errorf("Could not init the MetadataService", err)
+		return
+	}
+
+	// 4 is enough... we don't have many protocol
+	protocolMap := make(map[string]int, 4)
+
+	list, err := metadataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	if err != nil || len(list) == 0 {
+		logger.Debugf("Could not find exported urls", err)
+		return
+	}
+
+	for _, ui := range list {
+		u, err := common.NewURL(ui.(string))
+		if err != nil || len(u.Protocol) == 0 {
+			logger.Errorf("the url string is invalid: %s", ui.(string), err)
+			continue
+		}
+
+		port, err := strconv.Atoi(u.Port)
+		if err != nil {
+			logger.Errorf("Could not customize the metadata of port. ", err)
+		}
+		protocolMap[u.Protocol] = port
+	}
+
+	instance.GetMetadata()[constant.SERVICE_INSTANCE_ENDPOINTS] = endpointsStr(protocolMap)
+}
+
+// endpointsStr convert the map to json like [{"protocol": "dubbo", "port": 123}]
+func endpointsStr(protocolMap map[string]int) string {
+	if len(protocolMap) == 0 {
+		return ""
+	}
+
+	endpoints := make([]endpoint, 0, len(protocolMap))
+	for k, v := range protocolMap {
+		endpoints = append(endpoints, endpoint{
+			Port:     v,
+			Protocol: k,
+		})
+	}
+
+	str, err := json.Marshal(endpoints)
+	if err != nil {
+		logger.Errorf("could not convert the endpoints to json", err)
+		return ""
+	}
+	return string(str)
+}
+
+// nolint
+type endpoint struct {
+	Port     int    `json:"port, omitempty"`
+	Protocol string `json:"protocol, omitempty"`
+}
diff --git a/registry/event/service_config_exported_event.go b/registry/event/service_config_exported_event.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ec027da3178f7aba066cdb1d684798e611953ea
--- /dev/null
+++ b/registry/event/service_config_exported_event.go
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/config"
+)
+
+// ServiceConfigExportedEvent represents an service was exported
+type ServiceConfigExportedEvent struct {
+	observer.BaseEvent
+	ServiceConfig *config.ServiceConfig
+}
+
+// NewServiceConfigExportedEvent create an instance
+func NewServiceConfigExportedEvent(serviceConfig *config.ServiceConfig) *ServiceConfigExportedEvent {
+	return &ServiceConfigExportedEvent{
+		BaseEvent: observer.BaseEvent{
+			Source:    serviceConfig,
+			Timestamp: time.Now(),
+		},
+		ServiceConfig: serviceConfig,
+	}
+}
diff --git a/registry/event/service_discovery_event.go b/registry/event/service_discovery_event.go
new file mode 100644
index 0000000000000000000000000000000000000000..13afa1a6aa63a8ad0721692d7e969d3af882b8f5
--- /dev/null
+++ b/registry/event/service_discovery_event.go
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/registry"
+)
+
+// ServiceDiscoveryEvent means that something happens to service discovery instance
+type ServiceDiscoveryEvent struct {
+	observer.BaseEvent
+	original registry.ServiceDiscovery
+}
+
+// NewServiceDiscoveryEvent returns an instance
+func NewServiceDiscoveryEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryEvent {
+	return &ServiceDiscoveryEvent{
+		BaseEvent: *observer.NewBaseEvent(discovery),
+		original:  original,
+	}
+}
+
+// GetServiceDiscovery returns the event source
+func (sde *ServiceDiscoveryEvent) GetServiceDiscovery() registry.ServiceDiscovery {
+	return sde.GetSource().(registry.ServiceDiscovery)
+}
+
+// GetOriginal actually I think we can remove this method.
+func (sde *ServiceDiscoveryEvent) GetOriginal() registry.ServiceDiscovery {
+	return sde.original
+}
+
+// ServiceDiscoveryDestroyingEvent
+// this event will be dispatched before service discovery be destroyed
+type ServiceDiscoveryDestroyingEvent struct {
+	ServiceDiscoveryEvent
+}
+
+// ServiceDiscoveryExceptionEvent
+// this event will be dispatched when the error occur in service discovery
+type ServiceDiscoveryExceptionEvent struct {
+	ServiceDiscoveryEvent
+	err error
+}
+
+// ServiceDiscoveryInitializedEvent
+// this event will be dispatched after service discovery initialize
+type ServiceDiscoveryInitializedEvent struct {
+	ServiceDiscoveryEvent
+}
+
+// ServiceDiscoveryInitializingEvent
+// this event will be dispatched before service discovery initialize
+type ServiceDiscoveryInitializingEvent struct {
+	ServiceDiscoveryEvent
+}
+
+// ServiceDiscoveryDestroyedEvent
+// this event will be dispatched after service discovery be destroyed
+type ServiceDiscoveryDestroyedEvent struct {
+	ServiceDiscoveryEvent
+}
+
+// NewServiceDiscoveryDestroyingEvent create a ServiceDiscoveryDestroyingEvent
+func NewServiceDiscoveryDestroyingEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryDestroyingEvent {
+	return &ServiceDiscoveryDestroyingEvent{*NewServiceDiscoveryEvent(discovery, original)}
+}
+
+// NewServiceDiscoveryExceptionEvent create a ServiceDiscoveryExceptionEvent
+func NewServiceDiscoveryExceptionEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery, err error) *ServiceDiscoveryExceptionEvent {
+	return &ServiceDiscoveryExceptionEvent{*NewServiceDiscoveryEvent(discovery, original), err}
+}
+
+// NewServiceDiscoveryInitializedEvent create a ServiceDiscoveryInitializedEvent
+func NewServiceDiscoveryInitializedEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryInitializedEvent {
+	return &ServiceDiscoveryInitializedEvent{*NewServiceDiscoveryEvent(discovery, original)}
+}
+
+// NewServiceDiscoveryInitializingEvent create a ServiceDiscoveryInitializingEvent
+func NewServiceDiscoveryInitializingEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryInitializingEvent {
+	return &ServiceDiscoveryInitializingEvent{*NewServiceDiscoveryEvent(discovery, original)}
+}
+
+// NewServiceDiscoveryDestroyedEvent create a ServiceDiscoveryDestroyedEvent
+func NewServiceDiscoveryDestroyedEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryDestroyedEvent {
+	return &ServiceDiscoveryDestroyedEvent{*NewServiceDiscoveryEvent(discovery, original)}
+}
diff --git a/registry/event/service_instance_event.go b/registry/event/service_instance_event.go
new file mode 100644
index 0000000000000000000000000000000000000000..d4f23c299a844f4aab25e7d656a2cb99692861d7
--- /dev/null
+++ b/registry/event/service_instance_event.go
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/registry"
+)
+
+// ServiceInstanceEvent means something happen to this ServiceInstance
+// like register this service instance
+type ServiceInstanceEvent struct {
+	observer.BaseEvent
+	serviceInstance registry.ServiceInstance
+}
+
+// NewServiceInstanceEvent create a ServiceInstanceEvent
+func NewServiceInstanceEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceEvent {
+	return &ServiceInstanceEvent{
+		BaseEvent:       *observer.NewBaseEvent(source),
+		serviceInstance: instance,
+	}
+}
+
+// getServiceInstance return the service instance
+func (sie *ServiceInstanceEvent) getServiceInstance() registry.ServiceInstance {
+	return sie.serviceInstance
+}
+
+// ServiceInstancePreRegisteredEvent
+// this event will be dispatched before service instance be registered
+type ServiceInstancePreRegisteredEvent struct {
+	ServiceInstanceEvent
+}
+
+// ServiceInstancePreUnregisteredEvent
+// this event will be dispatched before service instance be unregistered
+type ServiceInstancePreUnregisteredEvent struct {
+	ServiceInstanceEvent
+}
+
+// ServiceInstanceRegisteredEvent
+// this event will be dispatched after service instance be registered
+type ServiceInstanceRegisteredEvent struct {
+	ServiceInstanceEvent
+}
+
+// ServiceInstanceRegisteredEvent
+// this event will be dispatched after service instance be unregistered
+type ServiceInstanceUnregisteredEvent struct {
+	ServiceInstanceEvent
+}
+
+// NewServiceInstancePreRegisteredEvent create a ServiceInstancePreRegisteredEvent
+func NewServiceInstancePreRegisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstancePreRegisteredEvent {
+	return &ServiceInstancePreRegisteredEvent{*NewServiceInstanceEvent(source, instance)}
+}
+
+// NewServiceInstancePreUnregisteredEvent create a ServiceInstancePreUnregisteredEvent
+func NewServiceInstancePreUnregisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstancePreUnregisteredEvent {
+	return &ServiceInstancePreUnregisteredEvent{*NewServiceInstanceEvent(source, instance)}
+}
+
+// NewServiceInstanceRegisteredEvent create a ServiceInstanceRegisteredEvent
+func NewServiceInstanceRegisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceRegisteredEvent {
+	return &ServiceInstanceRegisteredEvent{*NewServiceInstanceEvent(source, instance)}
+}
+
+// NewServiceInstanceUnregisteredEvent create a ServiceInstanceUnregisteredEvent
+func NewServiceInstanceUnregisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceUnregisteredEvent {
+	return &ServiceInstanceUnregisteredEvent{*NewServiceInstanceEvent(source, instance)}
+}
diff --git a/registry/event/service_name_mapping_listener.go b/registry/event/service_name_mapping_listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..a4ac8b28db5a3778cf39eef98886e1825521aa44
--- /dev/null
+++ b/registry/event/service_name_mapping_listener.go
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"reflect"
+	"sync"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/metadata/mapping"
+)
+
+func init() {
+	extension.AddEventListener(GetServiceNameMappingListener)
+}
+
+// serviceNameMappingListener listen to service name mapping event
+// usually it means that we exported some service
+// it's a singleton
+type serviceNameMappingListener struct {
+	nameMapping mapping.ServiceNameMapping
+}
+
+// GetPriority return 3, which ensure that this listener will be invoked after log listener
+func (s *serviceNameMappingListener) GetPriority() int {
+	return 3
+}
+
+// OnEvent only handle ServiceConfigExportedEvent
+func (s *serviceNameMappingListener) OnEvent(e observer.Event) error {
+	if ex, ok := e.(*ServiceConfigExportedEvent); ok {
+		sc := ex.ServiceConfig
+		urls := sc.GetExportedUrls()
+
+		for _, u := range urls {
+			err := s.nameMapping.Map(u.GetParam(constant.INTERFACE_KEY, ""),
+				u.GetParam(constant.GROUP_KEY, ""),
+				u.GetParam(constant.Version, ""),
+				u.Protocol)
+			if err != nil {
+				return perrors.WithMessage(err, "could not map the service: "+u.String())
+			}
+		}
+	}
+	return nil
+}
+
+// GetEventType return ServiceConfigExportedEvent
+func (s *serviceNameMappingListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(&ServiceConfigExportedEvent{})
+}
+
+var (
+	serviceNameMappingListenerInstance *serviceNameMappingListener
+	serviceNameMappingListenerOnce     sync.Once
+)
+
+// GetServiceNameMappingListener returns an instance
+func GetServiceNameMappingListener() observer.EventListener {
+	serviceNameMappingListenerOnce.Do(func() {
+		serviceNameMappingListenerInstance = &serviceNameMappingListener{
+			nameMapping: extension.GetGlobalServiceNameMapping(),
+		}
+	})
+	return serviceNameMappingListenerInstance
+}
diff --git a/registry/event/service_revision_customizer.go b/registry/event/service_revision_customizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd21e8f4c7a71cedfe1de7e9c836e7cee278182e
--- /dev/null
+++ b/registry/event/service_revision_customizer.go
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package event
+
+import (
+	"fmt"
+	"hash/crc32"
+	"sort"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+const defaultRevision = "N/A"
+
+func init() {
+	extension.AddCustomizers(&exportedServicesRevisionMetadataCustomizer{})
+	extension.AddCustomizers(&subscribedServicesRevisionMetadataCustomizer{})
+}
+
+type exportedServicesRevisionMetadataCustomizer struct {
+}
+
+// GetPriority will return 1 so that it will be invoked in front of user defining Customizer
+func (e *exportedServicesRevisionMetadataCustomizer) GetPriority() int {
+	return 1
+}
+
+// Customize calculate the revision for exported urls and then put it into instance metadata
+func (e *exportedServicesRevisionMetadataCustomizer) Customize(instance registry.ServiceInstance) {
+	ms, err := getMetadataService()
+	if err != nil {
+		logger.Errorf("could not get metadata service", err)
+		return
+	}
+
+	urls, err := ms.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+
+	if err != nil {
+		logger.Errorf("could not find the exported url", err)
+	}
+
+	revision := resolveRevision(urls)
+	if len(revision) == 0 {
+		revision = defaultRevision
+	}
+	instance.GetMetadata()[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] = revision
+}
+
+type subscribedServicesRevisionMetadataCustomizer struct {
+}
+
+// GetPriority will return 2 so that it will be invoked in front of user defining Customizer
+func (e *subscribedServicesRevisionMetadataCustomizer) GetPriority() int {
+	return 2
+}
+
+// Customize calculate the revision for subscribed urls and then put it into instance metadata
+func (e *subscribedServicesRevisionMetadataCustomizer) Customize(instance registry.ServiceInstance) {
+	ms, err := getMetadataService()
+	if err != nil {
+		logger.Errorf("could not get metadata service", err)
+		return
+	}
+
+	urls, err := ms.GetSubscribedURLs()
+
+	if err != nil {
+		logger.Errorf("could not find the subscribed url", err)
+	}
+
+	revision := resolveRevision(service.ConvertURLArrToIntfArr(urls))
+	if len(revision) == 0 {
+		revision = defaultRevision
+	}
+	instance.GetMetadata()[constant.SUBSCRIBED_SERVICES_REVISION_PROPERTY_NAME] = revision
+}
+
+// resolveRevision is different from Dubbo because golang doesn't support overload
+// so that we could use interface + method name as identifier and ignore the method params
+// per my understanding, it's enough because Dubbo actually ignore the url params.
+// please refer org.apache.dubbo.common.URL#toParameterString(java.lang.String...)
+func resolveRevision(urls []interface{}) string {
+	if len(urls) == 0 {
+		return ""
+	}
+	candidates := make([]string, 0, len(urls))
+
+	for _, ui := range urls {
+		u, err := common.NewURL(ui.(string))
+		if err != nil {
+			logger.Errorf("could not parse the string to URL structure")
+			continue
+		}
+		sk := u.GetParam(constant.INTERFACE_KEY, "")
+
+		if len(u.Methods) == 0 {
+			candidates = append(candidates, sk)
+		} else {
+			for _, m := range u.Methods {
+				// methods are part of candidates
+				candidates = append(candidates, sk+constant.KEY_SEPARATOR+m)
+			}
+		}
+
+		// append url params if we need it
+	}
+	sort.Sort(sort.StringSlice(candidates))
+
+	// it's nearly impossible to be overflow
+	res := uint64(0)
+	for _, c := range candidates {
+		res += uint64(crc32.ChecksumIEEE([]byte(c)))
+	}
+	return fmt.Sprint(res)
+}
diff --git a/registry/event_listener.go b/registry/event_listener.go
index 1805f2833c96bd08c4cf9c92337d7d221e8829e9..9e9ec2d5d4bcb8d1af90fff73db1c6708427f7f7 100644
--- a/registry/event_listener.go
+++ b/registry/event_listener.go
@@ -18,26 +18,39 @@
 package registry
 
 import (
-	gxsort "github.com/dubbogo/gost/sort"
+	"reflect"
 )
 
-// EventListener is an new interface used to align with dubbo 2.7.5
-// It contains the Prioritized means that the listener has its priority
-type EventListener interface {
-	gxsort.Prioritizer
-	// OnEvent handle this event
-	OnEvent(e Event) error
+import (
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+// The Service Discovery Changed  Event Listener
+type ServiceInstancesChangedListener struct {
+	ServiceName   string
+	ChangedNotify observer.ChangedNotify
 }
 
-// ConditionalEventListener only handle the event which it can handle
-type ConditionalEventListener interface {
-	EventListener
-	// Accept will make the decision whether it should handle this event
-	Accept(e Event) bool
+// OnEvent on ServiceInstancesChangedEvent the service instances change event
+func (lstn *ServiceInstancesChangedListener) OnEvent(e observer.Event) error {
+	lstn.ChangedNotify.Notify(e)
+	return nil
 }
 
-// ServiceInstancesChangedListener is used when the Service Discovery Changed
-// TODO (implement ConditionalEventListener)
-type ServiceInstancesChangedListener struct {
-	ServiceName string
+// Accept return true if the name is the same
+func (lstn *ServiceInstancesChangedListener) Accept(e observer.Event) bool {
+	if ce, ok := e.(*ServiceInstancesChangedEvent); ok {
+		return ce.ServiceName == lstn.ServiceName
+	}
+	return false
+}
+
+// GetPriority returns -1, it will be the first invoked listener
+func (lstn *ServiceInstancesChangedListener) GetPriority() int {
+	return -1
+}
+
+// GetEventType returns ServiceInstancesChangedEvent
+func (lstn *ServiceInstancesChangedListener) GetEventType() reflect.Type {
+	return reflect.TypeOf(&ServiceInstancesChangedEvent{})
 }
diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go
index f06d80124b7a627954ca3f4de0ae189e424708fd..7c5162670d85f661fb8460cc69537ac9b7a12a23 100644
--- a/registry/kubernetes/registry.go
+++ b/registry/kubernetes/registry.go
@@ -114,6 +114,10 @@ func (r *kubernetesRegistry) DoRegister(root string, node string) error {
 	return r.client.Create(path.Join(root, node), "")
 }
 
+func (r *kubernetesRegistry) DoUnregister(root string, node string) error {
+	return perrors.New("DoUnregister is not support in kubernetesRegistry")
+}
+
 // DoSubscribe actually subscribe the provider URL
 func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, error) {
 
@@ -147,6 +151,11 @@ func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, er
 	return configListener, nil
 }
 
+// nolint
+func (r *kubernetesRegistry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) {
+	return nil, perrors.New("DoUnsubscribe is not support in kubernetesRegistry")
+}
+
 // InitListeners init listeners of kubernetes registry center
 func (r *kubernetesRegistry) InitListeners() {
 	r.listener = kubernetes.NewEventListener(r.client)
diff --git a/registry/mock_registry.go b/registry/mock_registry.go
index 2b83d5ab8892f673e1123cd01fa74e48e3d2dc22..10561d0f49e995c94c93fa0463fc0b0421ff6e20 100644
--- a/registry/mock_registry.go
+++ b/registry/mock_registry.go
@@ -51,6 +51,11 @@ func (*MockRegistry) Register(url common.URL) error {
 	return nil
 }
 
+// nolint
+func (r *MockRegistry) UnRegister(conf common.URL) error {
+	return nil
+}
+
 // nolint
 func (r *MockRegistry) Destroy() {
 	if r.destroyed.CAS(false, true) {
@@ -72,7 +77,7 @@ func (r *MockRegistry) subscribe(*common.URL) (Listener, error) {
 }
 
 // nolint
-func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) {
+func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error {
 	go func() {
 		for {
 			if !r.IsAvailable() {
@@ -104,6 +109,12 @@ func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener)
 			}
 		}
 	}()
+	return nil
+}
+
+// UnSubscribe :
+func (r *MockRegistry) UnSubscribe(url *common.URL, notifyListener NotifyListener) error {
+	return nil
 }
 
 type listener struct {
diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go
index 3eeb7680abb3da98f5ed08f1aea57d490b2caf85..51d3e2f56abac8e4ab8b966870f1ff5bb79c4171 100644
--- a/registry/nacos/registry.go
+++ b/registry/nacos/registry.go
@@ -19,6 +19,7 @@ package nacos
 
 import (
 	"bytes"
+	"net"
 	"strconv"
 	"strings"
 	"time"
@@ -26,6 +27,9 @@ import (
 
 import (
 	gxnet "github.com/dubbogo/gost/net"
+	"github.com/nacos-group/nacos-sdk-go/clients"
+	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
+	nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant"
 	"github.com/nacos-group/nacos-sdk-go/vo"
 	perrors "github.com/pkg/errors"
 )
@@ -43,7 +47,7 @@ var (
 )
 
 const (
-	//RegistryConnDelay registry connection delay
+	// RegistryConnDelay registry connection delay
 	RegistryConnDelay = 3
 )
 
@@ -53,18 +57,8 @@ func init() {
 }
 
 type nacosRegistry struct {
-	nacosBaseRegistry
-}
-
-// newNacosRegistry will create an instance
-func newNacosRegistry(url *common.URL) (registry.Registry, error) {
-	base, err := newBaseRegistry(url)
-	if err != nil {
-		return nil, perrors.WithStack(err)
-	}
-	return &nacosRegistry{
-		base,
-	}, nil
+	*common.URL
+	namingClient naming_client.INamingClient
 }
 
 func getCategory(url common.URL) string {
@@ -137,23 +131,28 @@ func (nr *nacosRegistry) Register(url common.URL) error {
 	return nil
 }
 
+// UnRegister
+func (nr *nacosRegistry) UnRegister(conf common.URL) error {
+	return perrors.New("UnRegister is not support in nacosRegistry")
+}
+
 func (nr *nacosRegistry) subscribe(conf *common.URL) (registry.Listener, error) {
 	return NewNacosListener(*conf, nr.namingClient)
 }
 
 // subscribe from registry
-func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) {
+func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) error {
 	for {
 		if !nr.IsAvailable() {
 			logger.Warnf("event listener game over.")
-			return
+			return perrors.New("nacosRegistry is not available.")
 		}
 
 		listener, err := nr.subscribe(url)
 		if err != nil {
 			if !nr.IsAvailable() {
 				logger.Warnf("event listener game over.")
-				return
+				return err
 			}
 			logger.Warnf("getListener() = err:%v", perrors.WithStack(err))
 			time.Sleep(time.Duration(RegistryConnDelay) * time.Second)
@@ -165,7 +164,7 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti
 			if err != nil {
 				logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err))
 				listener.Close()
-				return
+				return err
 			}
 
 			logger.Infof("update begin, service event: %v", serviceEvent.String())
@@ -173,6 +172,12 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti
 		}
 
 	}
+	return nil
+}
+
+// UnSubscribe :
+func (nr *nacosRegistry) UnSubscribe(url *common.URL, notifyListener registry.NotifyListener) error {
+	return perrors.New("UnSubscribe not support in nacosRegistry")
 }
 
 // GetUrl gets its registration URL
@@ -182,6 +187,7 @@ func (nr *nacosRegistry) GetUrl() common.URL {
 
 // IsAvailable determines nacos registry center whether it is available
 func (nr *nacosRegistry) IsAvailable() bool {
+	// TODO
 	return true
 }
 
@@ -189,3 +195,62 @@ func (nr *nacosRegistry) IsAvailable() bool {
 func (nr *nacosRegistry) Destroy() {
 	return
 }
+
+// newNacosRegistry will create new instance
+func newNacosRegistry(url *common.URL) (registry.Registry, error) {
+	nacosConfig, err := getNacosConfig(url)
+	if err != nil {
+		return &nacosRegistry{}, err
+	}
+	client, err := clients.CreateNamingClient(nacosConfig)
+	if err != nil {
+		return &nacosRegistry{}, err
+	}
+	registry := &nacosRegistry{
+		URL:          url,
+		namingClient: client,
+	}
+	return registry, nil
+}
+
+// getNacosConfig will return the nacos config
+// TODO support RemoteRef
+func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
+	if url == nil {
+		return nil, perrors.New("url is empty!")
+	}
+	if len(url.Location) == 0 {
+		return nil, perrors.New("url.location is empty!")
+	}
+	configMap := make(map[string]interface{}, 2)
+
+	addresses := strings.Split(url.Location, ",")
+	serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses))
+	for _, addr := range addresses {
+		ip, portStr, err := net.SplitHostPort(addr)
+		if err != nil {
+			return nil, perrors.WithMessagef(err, "split [%s] ", addr)
+		}
+		port, _ := strconv.Atoi(portStr)
+		serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{
+			IpAddr: ip,
+			Port:   uint64(port),
+		})
+	}
+	configMap["serverConfigs"] = serverConfigs
+
+	var clientConfig nacosConstant.ClientConfig
+	timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
+	if err != nil {
+		return nil, err
+	}
+	clientConfig.TimeoutMs = uint64(timeout.Seconds() * 1000)
+	clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs
+	clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "")
+	clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "")
+	clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "")
+	clientConfig.NotLoadCacheAtStart = true
+	configMap["clientConfig"] = clientConfig
+
+	return configMap, nil
+}
diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go
index 2611a8dc58d2a45e578d90aa6a5d1aeb7e7f4f63..63d92d70fd5e1a00f0ce1ca95b1926fb9c36c84b 100644
--- a/registry/nacos/service_discovery.go
+++ b/registry/nacos/service_discovery.go
@@ -17,26 +17,32 @@
 
 package nacos
 
+import (
+	"fmt"
+	"sync"
+)
+
 import (
 	"github.com/dubbogo/gost/container/set"
 	"github.com/dubbogo/gost/page"
+	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
 	"github.com/nacos-group/nacos-sdk-go/model"
 	"github.com/nacos-group/nacos-sdk-go/vo"
 	perrors "github.com/pkg/errors"
 )
 
 import (
-	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
 	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/remoting/nacos"
 )
 
 const (
-	defaultGroup    = "DEFAULT_GROUP"
-	idKey           = "id"
-	defaultPageSize = 100
+	defaultGroup = constant.SERVICE_DISCOVERY_DEFAULT_GROUP
+	idKey        = "id"
 )
 
 // init will put the service discovery into extension
@@ -48,8 +54,12 @@ func init() {
 // There is a problem, the go client for nacos does not support the id field.
 // we will use the metadata to store the id of ServiceInstance
 type nacosServiceDiscovery struct {
-	nacosBaseRegistry
 	group string
+	// descriptor is a short string about the basic information of this instance
+	descriptor string
+
+	// namingClient is the Nacos' client
+	namingClient naming_client.INamingClient
 }
 
 // Destroy will close the service discovery.
@@ -93,7 +103,7 @@ func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) er
 
 // GetDefaultPageSize will return the constant registry.DefaultPageSize
 func (n *nacosServiceDiscovery) GetDefaultPageSize() int {
-	return defaultPageSize
+	return registry.DefaultPageSize
 }
 
 // GetServices will return the all services
@@ -238,7 +248,7 @@ func (n *nacosServiceDiscovery) DispatchEventForInstances(serviceName string, in
 
 // DispatchEvent will dispatch the event
 func (n *nacosServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
-	// TODO(waiting for event dispatcher, another task)
+	extension.GetGlobalDispatcher().Dispatch(event)
 	return nil
 }
 
@@ -255,10 +265,12 @@ func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInst
 		Ip:          instance.GetHost(),
 		Port:        uint64(instance.GetPort()),
 		Metadata:    metadata,
-		Enable:      instance.IsEnable(),
-		Healthy:     instance.IsHealthy(),
-		GroupName:   n.group,
-		Ephemeral:   true,
+		// We must specify the weight since Java nacos client will ignore the instance whose weight is 0
+		Weight:    1,
+		Enable:    instance.IsEnable(),
+		Healthy:   instance.IsHealthy(),
+		GroupName: n.group,
+		Ephemeral: true,
 	}
 }
 
@@ -272,15 +284,58 @@ func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceIn
 	}
 }
 
-// toDeregisterInstance will create new service discovery instance
-func newNacosServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) {
+func (n *nacosServiceDiscovery) String() string {
+	return n.descriptor
+}
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// newNacosServiceDiscovery will create new service discovery instance
+// use double-check pattern to reduce race condition
+func newNacosServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || len(sdc.RemoteRef) == 0 {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
 
-	base, err := newBaseRegistry(url)
+	remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef)
+	if !ok {
+		return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef)
+	}
+	group := sdc.Group
+	if len(group) == 0 {
+		group = defaultGroup
+	}
+
+	client, err := nacos.NewNacosClient(remoteConfig)
 	if err != nil {
-		return nil, perrors.WithStack(err)
+		return nil, perrors.WithMessage(err, "create nacos client failed.")
 	}
+
+	descriptor := fmt.Sprintf("nacos-service-discovery[%s]", remoteConfig.Address)
+
 	return &nacosServiceDiscovery{
-		nacosBaseRegistry: base,
-		group:             url.GetParam(constant.NACOS_GROUP, defaultGroup),
+		group:        group,
+		namingClient: client,
+		descriptor:   descriptor,
 	}, nil
 }
diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go
index 04431a614b40288b2a21f75d69c4be313bd7721f..720c44a6f98e4693bb2395a538b2f5e679196647 100644
--- a/registry/nacos/service_discovery_test.go
+++ b/registry/nacos/service_discovery_test.go
@@ -18,7 +18,6 @@
 package nacos
 
 import (
-	"strconv"
 	"testing"
 )
 
@@ -27,22 +26,65 @@ import (
 )
 
 import (
-	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/common/observer/dispatcher"
+	"github.com/apache/dubbo-go/config"
 	"github.com/apache/dubbo-go/registry"
 )
 
+var (
+	testName = "test"
+)
+
+func Test_newNacosServiceDiscovery(t *testing.T) {
+	name := "nacos1"
+	_, err := newNacosServiceDiscovery(name)
+
+	// the ServiceDiscoveryConfig not found
+	assert.NotNil(t, err)
+
+	sdc := &config.ServiceDiscoveryConfig{
+		Protocol:  "nacos",
+		RemoteRef: "mock",
+	}
+	config.GetBaseConfig().ServiceDiscoveries[name] = sdc
+
+	_, err = newNacosServiceDiscovery(name)
+
+	// RemoteConfig not found
+	assert.NotNil(t, err)
+
+	config.GetBaseConfig().Remotes["mock"] = &config.RemoteConfig{
+		Address:    "console.nacos.io:80",
+		TimeoutStr: "10s",
+	}
+
+	res, err := newNacosServiceDiscovery(name)
+	assert.Nil(t, err)
+	assert.NotNil(t, res)
+
+}
+
 func TestNacosServiceDiscovery_Destroy(t *testing.T) {
-	serviceDiscovry, err := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
+	prepareData()
+	serviceDiscovery, err := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
 	assert.Nil(t, err)
-	assert.NotNil(t, serviceDiscovry)
-	err = serviceDiscovry.Destroy()
+	assert.NotNil(t, serviceDiscovery)
+	err = serviceDiscovery.Destroy()
 	assert.Nil(t, err)
-	assert.Nil(t, serviceDiscovry.(*nacosServiceDiscovery).namingClient)
+	assert.Nil(t, serviceDiscovery.(*nacosServiceDiscovery).namingClient)
 }
 
 func TestNacosServiceDiscovery_CRUD(t *testing.T) {
+	prepareData()
+	extension.SetEventDispatcher("mock", func() observer.EventDispatcher {
+		return &dispatcher.MockEventDispatcher{}
+	})
+
+	extension.SetAndInitGlobalDispatcher("mock")
+
 	serviceName := "service-name"
 	id := "id"
 	host := "host"
@@ -59,7 +101,7 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) {
 
 	// clean data
 
-	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
+	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
 
 	// clean data for local test
 	serviceDiscovry.Unregister(&registry.DefaultServiceInstance{
@@ -112,11 +154,19 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) {
 }
 
 func TestNacosServiceDiscovery_GetDefaultPageSize(t *testing.T) {
-	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl())
-	assert.Equal(t, defaultPageSize, serviceDiscovry.GetDefaultPageSize())
+	prepareData()
+	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
+	assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize())
 }
 
-func mockUrl() *common.URL {
-	regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
-	return &regurl
+func prepareData() {
+	config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{
+		Protocol:  "nacos",
+		RemoteRef: testName,
+	}
+
+	config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{
+		Address:    "console.nacos.io:80",
+		TimeoutStr: "10s",
+	}
 }
diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go
index 15fd3cacfacad36309e0ad4deb3c7c7441e47e26..2d6e0248fddb88bfa5ce19546fb7aed703b0fd3c 100644
--- a/registry/protocol/protocol_test.go
+++ b/registry/protocol/protocol_test.go
@@ -42,7 +42,9 @@ import (
 )
 
 func init() {
-	config.SetProviderConfig(config.ProviderConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}})
+	config.SetProviderConfig(config.ProviderConfig{BaseConfig: config.BaseConfig{
+		ApplicationConfig: &config.ApplicationConfig{Name: "test-application"},
+	}})
 }
 
 func referNormal(t *testing.T, regProtocol *registryProtocol) {
@@ -66,8 +68,9 @@ func referNormal(t *testing.T, regProtocol *registryProtocol) {
 
 func TestRefer(t *testing.T) {
 	config.SetConsumerConfig(
-		config.ConsumerConfig{
-			ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}})
+		config.ConsumerConfig{BaseConfig: config.BaseConfig{
+			ApplicationConfig: &config.ApplicationConfig{Name: "test-application"},
+		}})
 	regProtocol := newRegistryProtocol()
 	referNormal(t, regProtocol)
 }
diff --git a/registry/registry.go b/registry/registry.go
index 5b37aa684ca90d1f18898b9f62f27d86a2c0fba3..bb09ead7ef2af6707345086f8695b35286d76a10 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -34,15 +34,28 @@ type Registry interface {
 	//And it is also used for service consumer calling , register services cared about ,for dubbo's admin monitoring.
 	Register(url common.URL) error
 
+	// UnRegister is required to support the contract:
+	// 1. If it is the persistent stored data of dynamic=false, the registration data can not be found, then the IllegalStateException is thrown, otherwise it is ignored.
+	// 2. Unregister according to the full url match.
+	// url Registration information , is not allowed to be empty, e.g: dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
+	UnRegister(url common.URL) error
+
 	//When creating new registry extension,pls select one of the following modes.
 	//Will remove in dubbogo version v1.1.0
 	//mode1 : return Listener with Next function which can return subscribe service event from registry
 	//Deprecated!
-	//subscribe(common.URL) (Listener, error)
+	//subscribe(event.URL) (Listener, error)
 
 	//Will relace mode1 in dubbogo version v1.1.0
 	//mode2 : callback mode, subscribe with notify(notify listener).
-	Subscribe(*common.URL, NotifyListener)
+	Subscribe(*common.URL, NotifyListener) error
+
+	// UnSubscribe is required to support the contract:
+	// 1. If don't subscribe, ignore it directly.
+	// 2. Unsubscribe by full URL match.
+	// url      Subscription condition, not allowed to be empty, e.g. consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
+	// listener A listener of the change event, not allowed to be empty
+	UnSubscribe(*common.URL, NotifyListener) error
 }
 
 // nolint
diff --git a/registry/service_discovery.go b/registry/service_discovery.go
index 1d5a3593e392083d2115222e131974b941a391c3..cb7a3c0182ff88995ab9dd6c920523225c3cb36c 100644
--- a/registry/service_discovery.go
+++ b/registry/service_discovery.go
@@ -26,6 +26,8 @@ import (
 	gxpage "github.com/dubbogo/gost/page"
 )
 
+const DefaultPageSize = 100
+
 // ServiceDiscovery is the common operations of Service Discovery
 type ServiceDiscovery interface {
 	fmt.Stringer
diff --git a/registry/service_instance.go b/registry/service_instance.go
index 247c8567659d1d512a6685ddb0404fecd9968bcd..dbb458284d48aa350f2d5d3408b187b437ac81cd 100644
--- a/registry/service_instance.go
+++ b/registry/service_instance.go
@@ -17,6 +17,10 @@
 
 package registry
 
+import (
+	gxsort "github.com/dubbogo/gost/sort"
+)
+
 // ServiceInstance is the model class of an instance of a service, which is used for service registration and discovery.
 type ServiceInstance interface {
 
@@ -84,7 +88,19 @@ func (d *DefaultServiceInstance) IsHealthy() bool {
 	return d.Healthy
 }
 
-// GetMetadata will return the metadata
+// GetMetadata will return the metadata, it will never return nil
 func (d *DefaultServiceInstance) GetMetadata() map[string]string {
+	if d.Metadata == nil {
+		d.Metadata = make(map[string]string, 0)
+	}
 	return d.Metadata
 }
+
+// ServiceInstanceCustomizer is an extension point which allow user using custom logic to modify instance
+// Be careful of priority. Usually you should use number between [100, 9000]
+// other number will be thought as system reserve number
+type ServiceInstanceCustomizer interface {
+	gxsort.Prioritizer
+
+	Customize(instance ServiceInstance)
+}
diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector.go b/registry/servicediscovery/instance/random/random_service_instance_selector.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f8f30dc8e9e91f9c75f8ff0611c98bb2f0c7b85
--- /dev/null
+++ b/registry/servicediscovery/instance/random/random_service_instance_selector.go
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package random
+
+import (
+	"math/rand"
+	"time"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/registry/servicediscovery/instance"
+)
+
+func init() {
+	extension.SetServiceInstanceSelector("random", NewRandomServiceInstanceSelector)
+}
+
+//the ServiceInstanceSelector implementation based on Random algorithm
+type RandomServiceInstanceSelector struct {
+}
+
+func NewRandomServiceInstanceSelector() instance.ServiceInstanceSelector {
+	return &RandomServiceInstanceSelector{}
+}
+
+func (r *RandomServiceInstanceSelector) Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance {
+	if len(serviceInstances) == 0 {
+		return nil
+	}
+	if len(serviceInstances) == 1 {
+		return serviceInstances[0]
+	}
+	rand.Seed(time.Now().UnixNano())
+	index := rand.Intn(len(serviceInstances))
+	return serviceInstances[index]
+
+}
diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector_test.go b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cddeb42c904131cdc6a62e5142de850410a3ec5a
--- /dev/null
+++ b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package random
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestRandomServiceInstanceSelector_Select(t *testing.T) {
+	selector := NewRandomServiceInstanceSelector()
+	serviceInstances := []registry.ServiceInstance{
+		&registry.DefaultServiceInstance{
+			Id:          "1",
+			ServiceName: "test1",
+			Host:        "127.0.0.1:80",
+			Port:        0,
+			Enable:      false,
+			Healthy:     false,
+			Metadata:    nil,
+		},
+		&registry.DefaultServiceInstance{
+			Id:          "2",
+			ServiceName: "test2",
+			Host:        "127.0.0.1:80",
+			Port:        0,
+			Enable:      false,
+			Healthy:     false,
+			Metadata:    nil,
+		},
+	}
+	assert.NotNil(t, selector.Select(common.URL{}, serviceInstances))
+}
diff --git a/metadata/report.go b/registry/servicediscovery/instance/service_instance_selector.go
similarity index 54%
rename from metadata/report.go
rename to registry/servicediscovery/instance/service_instance_selector.go
index f2380f50cd0eb15182c137f02e5f78b4ba8e4fd2..82fb3458be2838e9a5780e95be71aa89039b664f 100644
--- a/metadata/report.go
+++ b/registry/servicediscovery/instance/service_instance_selector.go
@@ -15,21 +15,14 @@
  * limitations under the License.
  */
 
-package metadata
+package instance
 
 import (
 	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/metadata/definition"
-	"github.com/apache/dubbo-go/metadata/identifier"
+	"github.com/apache/dubbo-go/registry"
 )
 
-type MetadataReport interface {
-	StoreProviderMetadata(*identifier.MetadataIdentifier, *definition.ServiceDefinition)
-	StoreConsumerMetadata(*identifier.MetadataIdentifier, map[string]string)
-	SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL)
-	RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier)
-	GetExportedURLs(*identifier.ServiceMetadataIdentifier) []string
-	SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []*common.URL)
-	GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string
-	GetServiceDefinition(*identifier.MetadataIdentifier)
+type ServiceInstanceSelector interface {
+	//Select an instance of ServiceInstance by the specified ServiceInstance service instances
+	Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance
 }
diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..061d832b0328a5e1754c7804bf40cf83ac216a8b
--- /dev/null
+++ b/registry/servicediscovery/service_discovery_registry.go
@@ -0,0 +1,713 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package servicediscovery
+
+import (
+	"bytes"
+	"encoding/json"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+import (
+	cm "github.com/Workiva/go-datastructures/common"
+	gxset "github.com/dubbogo/gost/container/set"
+	gxnet "github.com/dubbogo/gost/net"
+	perrors "github.com/pkg/errors"
+	"go.uber.org/atomic"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/mapping"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/metadata/service/exporter/configurable"
+	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/registry/event"
+	"github.com/apache/dubbo-go/registry/servicediscovery/synthesizer"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+const (
+	protocolName = "service-discovery"
+)
+
+func init() {
+	extension.SetRegistry(protocolName, newServiceDiscoveryRegistry)
+}
+
+// serviceDiscoveryRegistry is the implementation of application-level registry.
+// It's completely different from other registry implementations
+// This implementation is based on ServiceDiscovery abstraction and ServiceNameMapping
+// In order to keep compatible with interface-level registry,
+// this implementation is
+type serviceDiscoveryRegistry struct {
+	lock                             sync.RWMutex
+	url                              *common.URL
+	serviceDiscovery                 registry.ServiceDiscovery
+	subscribedServices               *gxset.HashSet
+	serviceNameMapping               mapping.ServiceNameMapping
+	metaDataService                  service.MetadataService
+	registeredListeners              *gxset.HashSet
+	subscribedURLsSynthesizers       []synthesizer.SubscribedURLsSynthesizer
+	serviceRevisionExportedURLsCache map[string]map[string][]common.URL
+}
+
+func newServiceDiscoveryRegistry(url *common.URL) (registry.Registry, error) {
+
+	tryInitMetadataService()
+
+	serviceDiscovery, err := creatServiceDiscovery(url)
+	if err != nil {
+		return nil, err
+	}
+	subscribedServices := parseServices(url.GetParam(constant.SUBSCRIBED_SERVICE_NAMES_KEY, ""))
+	subscribedURLsSynthesizers := synthesizer.GetAllSynthesizer()
+	serviceNameMapping := extension.GetGlobalServiceNameMapping()
+	metaDataService, err := extension.GetMetadataService(config.GetApplicationConfig().MetadataType)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "could not init metadata service")
+	}
+	return &serviceDiscoveryRegistry{
+		url:                              url,
+		serviceDiscovery:                 serviceDiscovery,
+		subscribedServices:               subscribedServices,
+		subscribedURLsSynthesizers:       subscribedURLsSynthesizers,
+		registeredListeners:              gxset.NewSet(),
+		serviceRevisionExportedURLsCache: make(map[string]map[string][]common.URL, 8),
+		serviceNameMapping:               serviceNameMapping,
+		metaDataService:                  metaDataService,
+	}, nil
+}
+
+func (s *serviceDiscoveryRegistry) UnRegister(url common.URL) error {
+	if !shouldRegister(url) {
+		return nil
+	}
+	return s.metaDataService.UnexportURL(url)
+}
+
+func (s *serviceDiscoveryRegistry) UnSubscribe(url *common.URL, listener registry.NotifyListener) error {
+	if !shouldSubscribe(*url) {
+		return nil
+	}
+	return s.metaDataService.UnsubscribeURL(*url)
+}
+
+func creatServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) {
+	sdcName := url.GetParam(constant.SERVICE_DISCOVERY_KEY, "")
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(sdcName)
+	if !ok {
+		return nil, perrors.Errorf("The service discovery with name: %s is not found", sdcName)
+	}
+	originServiceDiscovery, err := extension.GetServiceDiscovery(sdc.Protocol, sdcName)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "Create service discovery fialed")
+	}
+	return event.NewEventPublishingServiceDiscovery(originServiceDiscovery), nil
+}
+
+func parseServices(literalServices string) *gxset.HashSet {
+	set := gxset.NewSet()
+	if len(literalServices) == 0 {
+		return set
+	}
+	var splitServices = strings.Split(literalServices, ",")
+	for _, s := range splitServices {
+		if len(s) != 0 {
+			set.Add(s)
+		}
+	}
+	return set
+}
+
+func (s *serviceDiscoveryRegistry) GetServiceDiscovery() registry.ServiceDiscovery {
+	return s.serviceDiscovery
+}
+
+func (s *serviceDiscoveryRegistry) GetUrl() common.URL {
+	return *s.url
+}
+
+func (s *serviceDiscoveryRegistry) IsAvailable() bool {
+	// TODO(whether available depends on metadata service and service discovery)
+	return true
+}
+
+func (s *serviceDiscoveryRegistry) Destroy() {
+	err := s.serviceDiscovery.Destroy()
+	if err != nil {
+		logger.Errorf("destroy serviceDiscovery catch error:%s", err.Error())
+	}
+}
+
+func (s *serviceDiscoveryRegistry) Register(url common.URL) error {
+	if !shouldRegister(url) {
+		return nil
+	}
+	ok, err := s.metaDataService.ExportURL(url)
+
+	if err != nil {
+		logger.Errorf("The URL[%s] registry catch error:%s!", url.String(), err.Error())
+		return err
+	}
+	if !ok {
+		logger.Warnf("The URL[%s] has been registry!", url.String())
+	}
+
+	// we try to register this instance. Dubbo do this in org.apache.dubbo.config.bootstrap.DubboBootstrap
+	// But we don't want to design a similar bootstrap class.
+	ins, err := createInstance(url)
+	if err != nil {
+		return perrors.WithMessage(err, "could not create servcie instance, please check your service url")
+	}
+
+	err = s.serviceDiscovery.Register(ins)
+	if err != nil {
+		return perrors.WithMessage(err, "register the service failed")
+	}
+
+	err = s.metaDataService.PublishServiceDefinition(url)
+	if err != nil {
+		return perrors.WithMessage(err, "publish the service definition failed. ")
+	}
+	return s.serviceNameMapping.Map(url.GetParam(constant.INTERFACE_KEY, ""),
+		url.GetParam(constant.GROUP_KEY, ""),
+		url.GetParam(constant.Version, ""),
+		url.Protocol)
+}
+
+func createInstance(url common.URL) (registry.ServiceInstance, error) {
+	appConfig := config.GetApplicationConfig()
+	port, err := strconv.ParseInt(url.Port, 10, 32)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "invalid port: "+url.Port)
+	}
+
+	host := url.Ip
+	if len(host) == 0 {
+		host, err = gxnet.GetLocalIP()
+		if err != nil {
+			return nil, perrors.WithMessage(err, "could not get the local Ip")
+		}
+	}
+
+	// usually we will add more metadata
+	metadata := make(map[string]string, 8)
+	metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType
+
+	return &registry.DefaultServiceInstance{
+		ServiceName: appConfig.Name,
+		Host:        host,
+		Port:        int(port),
+		Id:          host + constant.KEY_SEPARATOR + url.Port,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    metadata,
+	}, nil
+}
+
+func shouldRegister(url common.URL) bool {
+	side := url.GetParam(constant.SIDE_KEY, "")
+	if side == constant.PROVIDER_PROTOCOL {
+		return true
+	}
+	logger.Debugf("The URL should not be register.", url.String())
+	return false
+}
+
+func (s *serviceDiscoveryRegistry) Subscribe(url *common.URL, notify registry.NotifyListener) error {
+	if !shouldSubscribe(*url) {
+		return nil
+	}
+	_, err := s.metaDataService.SubscribeURL(*url)
+	if err != nil {
+		return perrors.WithMessage(err, "subscribe url error: "+url.String())
+	}
+	services := s.getServices(*url)
+	if services.Empty() {
+		return perrors.Errorf("Should has at least one way to know which services this interface belongs to, "+
+			"subscription url:%s", url.String())
+	}
+	for _, srv := range services.Values() {
+		serviceName := srv.(string)
+		serviceInstances := s.serviceDiscovery.GetInstances(serviceName)
+		s.subscribe(url, notify, serviceName, serviceInstances)
+		listener := &registry.ServiceInstancesChangedListener{
+			ServiceName: serviceName,
+			ChangedNotify: &InstanceChangeNotify{
+				notify:                   notify,
+				serviceDiscoveryRegistry: s,
+			},
+		}
+		s.registerServiceInstancesChangedListener(*url, listener)
+	}
+	return nil
+}
+
+func (s *serviceDiscoveryRegistry) registerServiceInstancesChangedListener(url common.URL, listener *registry.ServiceInstancesChangedListener) {
+	listenerId := listener.ServiceName + ":" + getUrlKey(url)
+	if !s.subscribedServices.Contains(listenerId) {
+		err := s.serviceDiscovery.AddListener(listener)
+		if err != nil {
+			logger.Errorf("add listener[%s] catch error,url:%s err:%s", listenerId, url.String(), err.Error())
+		}
+	}
+
+}
+
+func getUrlKey(url common.URL) string {
+	var bf bytes.Buffer
+	if len(url.Protocol) != 0 {
+		bf.WriteString(url.Protocol)
+		bf.WriteString("://")
+	}
+	if len(url.Location) != 0 {
+		bf.WriteString(url.Location)
+		bf.WriteString(":")
+		bf.WriteString(url.Port)
+	}
+	if len(url.Path) != 0 {
+		bf.WriteString("/")
+		bf.WriteString(url.Path)
+	}
+	bf.WriteString("?")
+	appendParam(bf, constant.VERSION_KEY, url)
+	appendParam(bf, constant.GROUP_KEY, url)
+	appendParam(bf, constant.NACOS_PROTOCOL_KEY, url)
+	return bf.String()
+}
+
+func appendParam(buffer bytes.Buffer, paramKey string, url common.URL) {
+	buffer.WriteString(paramKey)
+	buffer.WriteString("=")
+	buffer.WriteString(url.GetParam(paramKey, ""))
+}
+
+func (s *serviceDiscoveryRegistry) subscribe(url *common.URL, notify registry.NotifyListener,
+	serviceName string, serviceInstances []registry.ServiceInstance) {
+	if len(serviceInstances) == 0 {
+		logger.Warnf("here is no instance in service[name : %s]", serviceName)
+		return
+	}
+	var subscribedURLs []common.URL
+	subscribedURLs = append(subscribedURLs, s.getExportedUrls(*url, serviceInstances)...)
+	if len(subscribedURLs) == 0 {
+		subscribedURLs = s.synthesizeSubscribedURLs(url, serviceInstances)
+	}
+	for _, url := range subscribedURLs {
+		notify.Notify(&registry.ServiceEvent{
+			Action:  remoting.EventTypeAdd,
+			Service: url,
+		})
+	}
+
+}
+
+func (s *serviceDiscoveryRegistry) synthesizeSubscribedURLs(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL {
+	var urls []common.URL
+	for _, syn := range s.subscribedURLsSynthesizers {
+		if syn.Support(subscribedURL) {
+			urls = append(urls, syn.Synthesize(subscribedURL, serviceInstances)...)
+		}
+	}
+	return urls
+}
+
+func shouldSubscribe(url common.URL) bool {
+	return !shouldRegister(url)
+}
+
+func (s *serviceDiscoveryRegistry) getServices(url common.URL) *gxset.HashSet {
+	services := gxset.NewSet()
+	serviceNames := url.GetParam(constant.PROVIDER_BY, "")
+	if len(serviceNames) > 0 {
+		services = parseServices(serviceNames)
+	}
+	if services.Empty() {
+		services = s.findMappedServices(url)
+		if services.Empty() {
+			return s.subscribedServices
+		}
+	}
+	return services
+}
+
+func (s *serviceDiscoveryRegistry) findMappedServices(url common.URL) *gxset.HashSet {
+	serviceInterface := url.GetParam(constant.INTERFACE_KEY, url.Path)
+	group := url.GetParam(constant.GROUP_KEY, "")
+	version := url.GetParam(constant.VERSION_KEY, "")
+	protocol := url.Protocol
+	serviceNames, err := s.serviceNameMapping.Get(serviceInterface, group, version, protocol)
+	if err != nil {
+		logger.Errorf("get serviceInterface:[%s] group:[%s] version:[%s] protocol:[%s] from "+
+			"serviceNameMap error:%s", err.Error())
+		return gxset.NewSet()
+	}
+	return serviceNames
+}
+
+func (s *serviceDiscoveryRegistry) getExportedUrls(subscribedURL common.URL, serviceInstances []registry.ServiceInstance) []common.URL {
+	var filterInstances []registry.ServiceInstance
+	for _, s := range serviceInstances {
+		if !s.IsEnable() || !s.IsHealthy() {
+			continue
+		}
+		metaData := s.GetMetadata()
+		_, ok1 := metaData[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME]
+		_, ok2 := metaData[constant.METADATA_SERVICE_URLS_PROPERTY_NAME]
+		if !ok1 && !ok2 {
+			continue
+		}
+		filterInstances = append(filterInstances, s)
+	}
+	if len(filterInstances) == 0 {
+		return []common.URL{}
+	}
+	s.prepareServiceRevisionExportedURLs(filterInstances)
+	subscribedURLs := s.cloneExportedURLs(subscribedURL, filterInstances)
+	return subscribedURLs
+}
+
+// comparator is defined as Comparator for skip list to compare the URL
+type comparator common.URL
+
+// Compare is defined as Comparator for skip list to compare the URL
+func (c comparator) Compare(comp cm.Comparator) int {
+	a := common.URL(c).String()
+	b := common.URL(comp.(comparator)).String()
+	switch {
+	case a > b:
+		return 1
+	case a < b:
+		return -1
+	default:
+		return 0
+	}
+}
+
+func (s *serviceDiscoveryRegistry) getExportedUrlsByInst(serviceInstance registry.ServiceInstance) []common.URL {
+	var urls []common.URL
+	metadataStorageType := getExportedStoreType(serviceInstance)
+	proxyFactory := extension.GetMetadataServiceProxyFactory(metadataStorageType)
+	if proxyFactory == nil {
+		return urls
+	}
+	metadataService := proxyFactory.GetProxy(serviceInstance)
+	if metadataService == nil {
+		return urls
+	}
+	result, err := metadataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE)
+	if err != nil {
+		logger.Errorf("get exported urls catch error:%s,instance:%+v", err.Error(), serviceInstance)
+		return urls
+	}
+
+	ret := make([]common.URL, 0, len(result))
+	for _, ui := range result {
+
+		u, err := common.NewURL(ui.(string))
+
+		if err != nil {
+			logger.Errorf("could not parse the url string to URL structure: %s", ui.(string), err)
+			continue
+		}
+		ret = append(ret, u)
+	}
+	return ret
+}
+
+func (s *serviceDiscoveryRegistry) prepareServiceRevisionExportedURLs(serviceInstances []registry.ServiceInstance) {
+	s.lock.Lock()
+	// 1. expunge stale
+	s.expungeStaleRevisionExportedURLs(serviceInstances)
+	// 2. Initialize
+	s.initRevisionExportedURLs(serviceInstances)
+	s.lock.Unlock()
+}
+
+func (s *serviceDiscoveryRegistry) expungeStaleRevisionExportedURLs(serviceInstances []registry.ServiceInstance) {
+	serviceName := serviceInstances[0].GetServiceName()
+	revisionExportedURLsMap, exist := s.serviceRevisionExportedURLsCache[serviceName]
+	if !exist {
+		return
+	}
+	existRevision := gxset.NewSet()
+	for k := range revisionExportedURLsMap {
+		existRevision.Add(k)
+	}
+	currentRevision := gxset.NewSet()
+	for _, s := range serviceInstances {
+		rv := getExportedServicesRevision(s)
+		if len(rv) != 0 {
+			currentRevision.Add(rv)
+		}
+	}
+	// staleRevisions = existedRevisions(copy) - currentRevisions
+	staleRevision := gxset.NewSet(existRevision.Values()...)
+	staleRevision.Remove(currentRevision.Values()...)
+	// remove exported URLs if staled
+	for _, s := range staleRevision.Values() {
+		delete(revisionExportedURLsMap, s.(string))
+	}
+}
+
+func (s *serviceDiscoveryRegistry) initRevisionExportedURLs(serviceInstances []registry.ServiceInstance) {
+	// initialize the revision exported URLs that the selected service instance exported
+	s.initSelectedRevisionExportedURLs(serviceInstances)
+	// initialize the revision exported URLs that other service instances exported
+	for _, serviceInstance := range serviceInstances {
+		s.initRevisionExportedURLsByInst(serviceInstance)
+	}
+}
+
+func (s *serviceDiscoveryRegistry) initSelectedRevisionExportedURLs(serviceInstances []registry.ServiceInstance) {
+	for range serviceInstances {
+		selectServiceInstance := s.selectServiceInstance(serviceInstances)
+		revisionExportedURLs := s.initRevisionExportedURLsByInst(selectServiceInstance)
+		if len(revisionExportedURLs) > 0 {
+			// If the result is valid,break
+			break
+		}
+	}
+}
+
+func (s *serviceDiscoveryRegistry) selectServiceInstance(serviceInstances []registry.ServiceInstance) registry.ServiceInstance {
+	size := len(serviceInstances)
+	if size == 0 {
+		return nil
+	}
+	if size == 1 {
+		return serviceInstances[0]
+	}
+	selectorName := s.url.GetParam(constant.SERVICE_INSTANCE_SELECTOR, "random")
+	selector, err := extension.GetServiceInstanceSelector(selectorName)
+	if err != nil {
+		logger.Errorf("get service instance selector cathe error:%s", err.Error())
+		return nil
+	}
+	return selector.Select(*s.url, serviceInstances)
+}
+
+func (s *serviceDiscoveryRegistry) initRevisionExportedURLsByInst(serviceInstance registry.ServiceInstance) []common.URL {
+	if serviceInstance == nil {
+		return []common.URL{}
+	}
+	serviceName := serviceInstance.GetServiceName()
+	revision := getExportedServicesRevision(serviceInstance)
+	revisionExportedURLsMap := s.serviceRevisionExportedURLsCache[serviceName]
+	if revisionExportedURLsMap == nil {
+		revisionExportedURLsMap = make(map[string][]common.URL, 4)
+		s.serviceRevisionExportedURLsCache[serviceName] = revisionExportedURLsMap
+	}
+	revisionExportedURLs := revisionExportedURLsMap[revision]
+	firstGet := false
+	if revisionExportedURLs == nil || len(revisionExportedURLs) == 0 {
+		if len(revisionExportedURLsMap) > 0 {
+			// The case is that current ServiceInstance with the different revision
+			logger.Warnf("The ServiceInstance[id: %s, host : %s , port : %s] has different revision : %s"+
+				", please make sure the service [name : %s] is changing or not.", serviceInstance.GetId(),
+				serviceInstance.GetHost(), serviceInstance.GetPort(), revision, serviceInstance.GetServiceName())
+		} else {
+			firstGet = true
+		}
+		revisionExportedURLs = s.getExportedUrlsByInst(serviceInstance)
+		if revisionExportedURLs != nil {
+			revisionExportedURLsMap[revision] = revisionExportedURLs
+			logger.Debugf("Get the exported URLs[size : %s, first : %s] from the target service "+
+				"instance [id: %s , service : %s , host : %s , port : %s , revision : %s]",
+				len(revisionExportedURLs), firstGet, serviceInstance.GetId(), serviceInstance.GetServiceName(),
+				serviceInstance.GetHost(), serviceInstance.GetPort(), revision)
+		}
+	} else {
+		// Else, The cache is hit
+		logger.Debugf("Get the exported URLs[size : %s] from cache, the instance"+
+			"[id: %s , service : %s , host : %s , port : %s , revision : %s]", len(revisionExportedURLs), firstGet,
+			serviceInstance.GetId(), serviceInstance.GetServiceName(), serviceInstance.GetHost(),
+			serviceInstance.GetPort(), revision)
+	}
+	return revisionExportedURLs
+}
+
+func getExportedServicesRevision(serviceInstance registry.ServiceInstance) string {
+	metaData := serviceInstance.GetMetadata()
+	return metaData[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME]
+}
+
+func getExportedStoreType(serviceInstance registry.ServiceInstance) string {
+	metaData := serviceInstance.GetMetadata()
+	result, ok := metaData[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME]
+	if !ok {
+		return constant.DEFAULT_METADATA_STORAGE_TYPE
+	}
+	return result
+}
+
+func (s *serviceDiscoveryRegistry) cloneExportedURLs(url common.URL, serviceInsances []registry.ServiceInstance) []common.URL {
+	if len(serviceInsances) == 0 {
+		return []common.URL{}
+	}
+	var clonedExportedURLs []common.URL
+	removeParamSet := gxset.NewSet()
+	removeParamSet.Add(constant.PID_KEY)
+	removeParamSet.Add(constant.TIMESTAMP_KEY)
+	for _, serviceInstance := range serviceInsances {
+		templateExportURLs := s.getTemplateExportedURLs(url, serviceInstance)
+		host := serviceInstance.GetHost()
+		for _, u := range templateExportURLs {
+			port := strconv.Itoa(getProtocolPort(serviceInstance, u.Protocol))
+			if u.Location != host || u.Port != port {
+				u.Port = port                  // reset port
+				u.Location = host + ":" + port // reset host
+			}
+
+			cloneUrl := u.CloneExceptParams(removeParamSet)
+			clonedExportedURLs = append(clonedExportedURLs, *cloneUrl)
+		}
+	}
+	return clonedExportedURLs
+
+}
+
+type endpoint struct {
+	Port     int    `json:"port, omitempty"`
+	Protocol string `json:"protocol, omitempty"`
+}
+
+func getProtocolPort(serviceInstance registry.ServiceInstance, protocol string) int {
+	md := serviceInstance.GetMetadata()
+	rawEndpoints := md[constant.SERVICE_INSTANCE_ENDPOINTS]
+	if len(rawEndpoints) == 0 {
+		return -1
+	}
+	var endpoints []endpoint
+	err := json.Unmarshal([]byte(rawEndpoints), &endpoints)
+	if err != nil {
+		logger.Errorf("json umarshal rawEndpoints[%s] catch error:%s", rawEndpoints, err.Error())
+		return -1
+	}
+	for _, e := range endpoints {
+		if e.Protocol == protocol {
+			return e.Port
+		}
+	}
+	return -1
+}
+func (s *serviceDiscoveryRegistry) getTemplateExportedURLs(url common.URL, serviceInstance registry.ServiceInstance) []common.URL {
+	exportedURLs := s.getRevisionExportedURLs(serviceInstance)
+	if len(exportedURLs) == 0 {
+		return []common.URL{}
+	}
+	return filterSubscribedURLs(url, exportedURLs)
+}
+
+func (s *serviceDiscoveryRegistry) getRevisionExportedURLs(serviceInstance registry.ServiceInstance) []common.URL {
+	if serviceInstance == nil {
+		return []common.URL{}
+	}
+	serviceName := serviceInstance.GetServiceName()
+	revision := getExportedServicesRevision(serviceInstance)
+	s.lock.RLock()
+	revisionExportedURLsMap, exist := s.serviceRevisionExportedURLsCache[serviceName]
+	if !exist {
+		return []common.URL{}
+	}
+	exportedURLs, exist := revisionExportedURLsMap[revision]
+	if !exist {
+		return []common.URL{}
+	}
+	s.lock.RUnlock()
+	// Get a copy from source in order to prevent the caller trying to change the cached data
+	cloneExportedURLs := make([]common.URL, len(exportedURLs))
+	copy(cloneExportedURLs, exportedURLs)
+	return cloneExportedURLs
+}
+
+func filterSubscribedURLs(subscribedURL common.URL, exportedURLs []common.URL) []common.URL {
+	var filterExportedURLs []common.URL
+	for _, url := range exportedURLs {
+		if url.GetParam(constant.INTERFACE_KEY, url.Path) != subscribedURL.GetParam(constant.INTERFACE_KEY, url.Path) {
+			break
+		}
+		if url.GetParam(constant.VERSION_KEY, "") != subscribedURL.GetParam(constant.VERSION_KEY, "") {
+			break
+		}
+		if url.GetParam(constant.GROUP_KEY, "") != subscribedURL.GetParam(constant.GROUP_KEY, "") {
+			break
+		}
+		if len(subscribedURL.Protocol) != 0 {
+			if subscribedURL.Protocol != url.Protocol {
+				break
+			}
+		}
+		filterExportedURLs = append(filterExportedURLs, url)
+	}
+	return filterExportedURLs
+}
+
+type InstanceChangeNotify struct {
+	notify                   registry.NotifyListener
+	serviceDiscoveryRegistry *serviceDiscoveryRegistry
+}
+
+func (icn *InstanceChangeNotify) Notify(event observer.Event) {
+
+	if se, ok := event.(*registry.ServiceInstancesChangedEvent); ok {
+		sdr := icn.serviceDiscoveryRegistry
+		sdr.subscribe(sdr.url, icn.notify, se.ServiceName, se.Instances)
+	}
+}
+
+var (
+	exporting = &atomic.Bool{}
+)
+
+// tryInitMetadataService will try to initialize metadata service
+// TODO (move to somewhere)
+func tryInitMetadataService() {
+
+	ms, err := extension.GetMetadataService(config.GetApplicationConfig().MetadataType)
+	if err != nil {
+		logger.Errorf("could not init metadata service", err)
+	}
+
+	if !config.IsProvider() || exporting.Load() {
+		return
+	}
+
+	// In theory, we can use sync.Once
+	// But sync.Once is not reentrant.
+	// Now the invocation chain is createRegistry -> tryInitMetadataService -> metadataServiceExporter.export
+	// -> createRegistry -> initMetadataService...
+	// So using sync.Once will result in dead lock
+	exporting.Store(true)
+
+	expt := configurable.NewMetadataServiceExporter(ms)
+
+	err = expt.Export()
+	if err != nil {
+		logger.Errorf("could not export the metadata service", err)
+	}
+	extension.GetGlobalDispatcher().Dispatch(event.NewServiceConfigExportedEvent(expt.(*configurable.MetadataServiceExporter).ServiceConfig))
+}
diff --git a/registry/servicediscovery/service_discovery_registry_test.go b/registry/servicediscovery/service_discovery_registry_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..53eb86507e635be32eb362519922f7042f945519
--- /dev/null
+++ b/registry/servicediscovery/service_discovery_registry_test.go
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package servicediscovery
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/dubbogo/gost/container/set"
+	"github.com/dubbogo/gost/page"
+	"github.com/stretchr/testify/assert"
+)
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/mapping"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	serviceInterface = "org.apache.dubbo.metadata.MetadataService"
+	group            = "dubbo-provider"
+	version          = "1.0.0"
+)
+
+func TestServiceDiscoveryRegistry_Register(t *testing.T) {
+	config.GetApplicationConfig().MetadataType = "mock"
+	extension.SetMetadataService("mock", func() (service service.MetadataService, err error) {
+		service = &mockMetadataService{}
+		return
+	})
+
+	extension.SetServiceDiscovery("mock", func(name string) (discovery registry.ServiceDiscovery, err error) {
+		return &mockServiceDiscovery{}, nil
+	})
+
+	extension.SetGlobalServiceNameMapping(func() mapping.ServiceNameMapping {
+		return &mockServiceNameMapping{}
+	})
+
+	extension.SetEventDispatcher("mock", func() observer.EventDispatcher {
+		return &mockEventDispatcher{}
+	})
+	extension.SetAndInitGlobalDispatcher("mock")
+
+	config.GetBaseConfig().ServiceDiscoveries["mock"] = &config.ServiceDiscoveryConfig{
+		Protocol: "mock",
+	}
+	registryURL, _ := common.NewURL("service-discovery://localhost:12345",
+		common.WithParamsValue("service_discovery", "mock"),
+		common.WithParamsValue("subscribed-services", "a, b , c,d,e ,"))
+	url, _ := common.NewURL("dubbo://192.168.0.102:20880/" + serviceInterface +
+		"?&application=" + group +
+		"&interface=" + serviceInterface +
+		"&group=" + group +
+		"&version=" + version +
+		"&service_discovery=mock" +
+		"&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs" +
+		"&side=provider")
+	registry, err := newServiceDiscoveryRegistry(&registryURL)
+	assert.Nil(t, err)
+	assert.NotNil(t, registry)
+	registry.Register(url)
+}
+
+type mockEventDispatcher struct {
+}
+
+func (m *mockEventDispatcher) AddEventListener(listener observer.EventListener) {
+
+}
+
+func (m *mockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) {
+
+}
+
+func (m *mockEventDispatcher) RemoveEventListener(listener observer.EventListener) {
+	panic("implement me")
+}
+
+func (m *mockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) {
+	panic("implement me")
+}
+
+func (m *mockEventDispatcher) GetAllEventListeners() []observer.EventListener {
+	return []observer.EventListener{}
+}
+
+func (m *mockEventDispatcher) RemoveAllEventListeners() {
+	panic("implement me")
+}
+
+func (m *mockEventDispatcher) Dispatch(event observer.Event) {
+}
+
+type mockServiceNameMapping struct {
+}
+
+func (m *mockServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
+	return nil
+}
+
+func (m *mockServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+	panic("implement me")
+}
+
+type mockServiceDiscovery struct {
+}
+
+func (m *mockServiceDiscovery) String() string {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) Destroy() error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	return nil
+}
+
+func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetDefaultPageSize() int {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetServices() *gxset.HashSet {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	panic("implement me")
+}
+
+func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	panic("implement me")
+}
+
+type mockMetadataService struct {
+}
+
+func (m *mockMetadataService) Reference() string {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) ServiceName() (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) {
+	return true, nil
+}
+
+func (m *mockMetadataService) UnexportURL(url common.URL) error {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) UnsubscribeURL(url common.URL) error {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error {
+	return nil
+}
+
+func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) MethodMapper() map[string]string {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) {
+	panic("implement me")
+}
+
+func (m *mockMetadataService) Version() (string, error) {
+	panic("implement me")
+}
diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..086a26de58f8472e35e07a8a174fdee86afa82f2
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rest
+
+import (
+	"net/url"
+	"strings"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/registry/servicediscovery/synthesizer"
+)
+
+func init() {
+	synthesizer.AddSynthesizer(NewRestSubscribedURLsSynthesizer())
+}
+
+//SubscribedURLsSynthesizer implementation for rest protocol
+type RestSubscribedURLsSynthesizer struct {
+}
+
+func (r RestSubscribedURLsSynthesizer) Support(subscribedURL *common.URL) bool {
+	if "rest" == subscribedURL.Protocol {
+		return true
+	}
+	return false
+}
+
+func (r RestSubscribedURLsSynthesizer) Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL {
+	urls := make([]common.URL, len(serviceInstances), len(serviceInstances))
+	for i, s := range serviceInstances {
+		splitHost := strings.Split(s.GetHost(), ":")
+		u := common.NewURLWithOptions(common.WithProtocol(subscribedURL.Protocol), common.WithIp(splitHost[0]),
+			common.WithPort(splitHost[1]), common.WithPath(subscribedURL.GetParam(constant.INTERFACE_KEY, subscribedURL.Path)),
+			common.WithParams(url.Values{}),
+			common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL),
+			common.WithParamsValue(constant.APPLICATION_KEY, s.GetServiceName()),
+			common.WithParamsValue(constant.REGISTRY_KEY, "true"),
+		)
+		urls[i] = *u
+	}
+	return urls
+}
+
+func NewRestSubscribedURLsSynthesizer() RestSubscribedURLsSynthesizer {
+	return RestSubscribedURLsSynthesizer{}
+}
diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b52cc2323d6f9ae1bca8cfd1a4c5217af5e25f12
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rest
+
+import (
+	"net/url"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/registry"
+)
+
+func TestRestSubscribedURLsSynthesizer_Synthesize(t *testing.T) {
+	syn := RestSubscribedURLsSynthesizer{}
+	subUrl, _ := common.NewURL("rest://127.0.0.1:20000/org.apache.dubbo-go.mockService")
+	instances := []registry.ServiceInstance{
+		&registry.DefaultServiceInstance{
+			Id:          "test1",
+			ServiceName: "test1",
+			Host:        "127.0.0.1:80",
+			Port:        80,
+			Enable:      false,
+			Healthy:     false,
+			Metadata:    nil,
+		},
+		&registry.DefaultServiceInstance{
+			Id:          "test2",
+			ServiceName: "test2",
+			Host:        "127.0.0.2:8081",
+			Port:        8081,
+			Enable:      false,
+			Healthy:     false,
+			Metadata:    nil,
+		},
+	}
+
+	var expectUrls []common.URL
+	u1 := common.NewURLWithOptions(common.WithProtocol("rest"), common.WithIp("127.0.0.1"),
+		common.WithPort("80"), common.WithPath("org.apache.dubbo-go.mockService"),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL),
+		common.WithParamsValue(constant.APPLICATION_KEY, "test1"),
+		common.WithParamsValue(constant.REGISTRY_KEY, "true"))
+	u2 := common.NewURLWithOptions(common.WithProtocol("rest"), common.WithIp("127.0.0.2"),
+		common.WithPort("8081"), common.WithPath("org.apache.dubbo-go.mockService"),
+		common.WithParams(url.Values{}),
+		common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL),
+		common.WithParamsValue(constant.APPLICATION_KEY, "test2"),
+		common.WithParamsValue(constant.REGISTRY_KEY, "true"))
+	expectUrls = append(expectUrls, *u1, *u2)
+	result := syn.Synthesize(&subUrl, instances)
+	assert.Equal(t, expectUrls, result)
+}
diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..949a5822c237de413b59d35efe94f807975795cf
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package synthesizer
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/registry"
+)
+
+type SubscribedURLsSynthesizer interface {
+	// Supports the synthesis of the subscribed url or not
+	Support(subscribedURL *common.URL) bool
+	// synthesize the subscribed url
+	Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL
+}
diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..c9b1449bef1a8fba0afb8cda163d740e34ac1157
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package synthesizer
+
+import (
+	"sync"
+)
+
+var (
+	synthesizers     []SubscribedURLsSynthesizer
+	synthesizerMutex sync.RWMutex
+)
+
+// nolint
+func AddSynthesizer(synthesizer SubscribedURLsSynthesizer) {
+	synthesizerMutex.Lock()
+	defer synthesizerMutex.Unlock()
+	synthesizers = append(synthesizers, synthesizer)
+}
+
+// nolint
+func GetAllSynthesizer() []SubscribedURLsSynthesizer {
+	synthesizerMutex.RLock()
+	defer synthesizerMutex.RUnlock()
+	return synthesizers
+}
diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go
index 816e39bef5e8544de746cc950c599bf48eb14148..ec82fa0309118fba4b5c21772d4dfd356f3b0c5c 100644
--- a/registry/zookeeper/listener.go
+++ b/registry/zookeeper/listener.go
@@ -37,7 +37,7 @@ import (
 
 // RegistryDataListener contains all URL information subscribed by zookeeper registry
 type RegistryDataListener struct {
-	subscribed map[*common.URL]config_center.ConfigurationListener
+	subscribed map[string]config_center.ConfigurationListener
 	mutex      sync.Mutex
 	closed     bool
 }
@@ -45,7 +45,7 @@ type RegistryDataListener struct {
 // NewRegistryDataListener constructs a new RegistryDataListener
 func NewRegistryDataListener() *RegistryDataListener {
 	return &RegistryDataListener{
-		subscribed: make(map[*common.URL]config_center.ConfigurationListener)}
+		subscribed: make(map[string]config_center.ConfigurationListener)}
 }
 
 // SubscribeURL is used to set a watch listener for url
@@ -53,7 +53,17 @@ func (l *RegistryDataListener) SubscribeURL(url *common.URL, listener config_cen
 	if l.closed {
 		return
 	}
-	l.subscribed[url] = listener
+	l.subscribed[url.ServiceKey()] = listener
+}
+
+// UnSubscribeURL is used to set a watch listener for url
+func (l *RegistryDataListener) UnSubscribeURL(url *common.URL) config_center.ConfigurationListener {
+	if l.closed {
+		return nil
+	}
+	listener := l.subscribed[url.ServiceKey()]
+	delete(l.subscribed, url.ServiceKey())
+	return listener
 }
 
 // DataChange accepts all events sent from the zookeeper server and trigger the corresponding listener for processing
@@ -75,8 +85,8 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
 	if l.closed {
 		return false
 	}
-	for url, listener := range l.subscribed {
-		if serviceURL.URLEqual(*url) {
+	for serviceKey, listener := range l.subscribed {
+		if serviceURL.ServiceKey() == serviceKey {
 			listener.Process(
 				&config_center.ConfigChangeEvent{
 					Key:        eventType.Path,
@@ -101,17 +111,25 @@ func (l *RegistryDataListener) Close() {
 
 // RegistryConfigurationListener represent the processor of zookeeper watcher
 type RegistryConfigurationListener struct {
-	client    *zk.ZookeeperClient
-	registry  *zkRegistry
-	events    chan *config_center.ConfigChangeEvent
-	isClosed  bool
-	close     chan struct{}
-	closeOnce sync.Once
+	client       *zk.ZookeeperClient
+	registry     *zkRegistry
+	events       chan *config_center.ConfigChangeEvent
+	isClosed     bool
+	close        chan struct{}
+	closeOnce    sync.Once
+	subscribeURL *common.URL
 }
 
 // NewRegistryConfigurationListener for listening the event of zk.
-func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry) *RegistryConfigurationListener {
-	return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32), isClosed: false, close: make(chan struct{}, 1)}
+func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry, conf *common.URL) *RegistryConfigurationListener {
+	reg.WaitGroup().Add(1)
+	return &RegistryConfigurationListener{
+		client:       client,
+		registry:     reg,
+		events:       make(chan *config_center.ConfigChangeEvent, 32),
+		isClosed:     false,
+		close:        make(chan struct{}, 1),
+		subscribeURL: conf}
 }
 
 // Process submit the ConfigChangeEvent to the event chan to notify all observer
diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go
index 04316bd9d88bbf84d2d0afdddedb78d37707856c..8f2ac1023b8ad34938b9996b480e3bbc4adbaaea 100644
--- a/registry/zookeeper/registry.go
+++ b/registry/zookeeper/registry.go
@@ -20,6 +20,7 @@ package zookeeper
 import (
 	"fmt"
 	"net/url"
+	"path"
 	"sync"
 	"time"
 )
@@ -129,13 +130,17 @@ func (r *zkRegistry) InitListeners() {
 		recoverd := r.dataListener.subscribed
 		if recoverd != nil && len(recoverd) > 0 {
 			// recover all subscribed url
-			for conf, oldListener := range recoverd {
-				if regConfigListener, ok := oldListener.(*RegistryConfigurationListener); ok {
+			for _, oldListener := range recoverd {
+				var (
+					regConfigListener *RegistryConfigurationListener
+					ok                bool
+				)
+
+				if regConfigListener, ok = oldListener.(*RegistryConfigurationListener); ok {
 					regConfigListener.Close()
 				}
-				newDataListener.SubscribeURL(conf, NewRegistryConfigurationListener(r.client, r))
-				r.WaitGroup().Add(1)
-				go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), newDataListener)
+				newDataListener.SubscribeURL(regConfigListener.subscribeURL, NewRegistryConfigurationListener(r.client, r, regConfigListener.subscribeURL))
+				go r.listener.ListenServiceEvent(regConfigListener.subscribeURL, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(regConfigListener.subscribeURL.Service())), newDataListener)
 
 			}
 		}
@@ -153,11 +158,24 @@ func (r *zkRegistry) DoRegister(root string, node string) error {
 	return r.registerTempZookeeperNode(root, node)
 }
 
+func (r *zkRegistry) DoUnregister(root string, node string) error {
+	r.cltLock.Lock()
+	defer r.cltLock.Unlock()
+	if !r.ZkClient().ZkConnValid() {
+		return perrors.Errorf("zk client is not valid.")
+	}
+	return r.ZkClient().Delete(path.Join(root, node))
+}
+
 // DoSubscribe actually subscribes the provider URL
 func (r *zkRegistry) DoSubscribe(conf *common.URL) (registry.Listener, error) {
 	return r.getListener(conf)
 }
 
+func (r *zkRegistry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) {
+	return r.getCloseListener(conf)
+}
+
 // CloseAndNilClient closes listeners and clear client
 func (r *zkRegistry) CloseAndNilClient() {
 	r.client.Close()
@@ -227,9 +245,9 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen
 	dataListener := r.dataListener
 	dataListener.mutex.Lock()
 	defer dataListener.mutex.Unlock()
-	if r.dataListener.subscribed[conf] != nil {
+	if r.dataListener.subscribed[conf.ServiceKey()] != nil {
 
-		zkListener, _ := r.dataListener.subscribed[conf].(*RegistryConfigurationListener)
+		zkListener, _ := r.dataListener.subscribed[conf.ServiceKey()].(*RegistryConfigurationListener)
 		if zkListener != nil {
 			r.listenerLock.Lock()
 			defer r.listenerLock.Unlock()
@@ -241,7 +259,7 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen
 		}
 	}
 
-	zkListener = NewRegistryConfigurationListener(r.client, r)
+	zkListener = NewRegistryConfigurationListener(r.client, r, conf)
 	if r.listener == nil {
 		r.cltLock.Lock()
 		client := r.client
@@ -260,8 +278,42 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen
 
 	//Interested register to dataconfig.
 	r.dataListener.SubscribeURL(conf, zkListener)
-	r.WaitGroup().Add(1)
+
 	go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), r.dataListener)
 
 	return zkListener, nil
 }
+
+func (r *zkRegistry) getCloseListener(conf *common.URL) (*RegistryConfigurationListener, error) {
+
+	var zkListener *RegistryConfigurationListener
+	r.dataListener.mutex.Lock()
+	configurationListener := r.dataListener.subscribed[conf.ServiceKey()]
+	if configurationListener != nil {
+
+		zkListener, _ := configurationListener.(*RegistryConfigurationListener)
+		if zkListener != nil {
+			if zkListener.isClosed {
+				return nil, perrors.New("configListener already been closed")
+			}
+		}
+	}
+
+	zkListener = r.dataListener.UnSubscribeURL(conf).(*RegistryConfigurationListener)
+	r.dataListener.mutex.Unlock()
+
+	if r.listener == nil {
+		return nil, perrors.New("listener is null can not close.")
+	}
+
+	//Interested register to dataconfig.
+	r.listenerLock.Lock()
+	listener := r.listener
+	r.listener = nil
+	r.listenerLock.Unlock()
+
+	r.dataListener.Close()
+	listener.Close()
+
+	return zkListener, nil
+}
diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go
index 688deccfbec67771c4071f6307802a16e4e0fc8b..d915fc2ce10359f0dd1970daf019746ce066f511 100644
--- a/registry/zookeeper/registry_test.go
+++ b/registry/zookeeper/registry_test.go
@@ -45,6 +45,31 @@ func Test_Register(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func Test_UnRegister(t *testing.T) {
+	// register
+	regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
+
+	ts, reg, _ := newMockZkRegistry(&regurl)
+	defer ts.Stop()
+	err := reg.Register(url)
+	children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers")
+	assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock%26.*.serviceid%3Dsoa.mock", children)
+	assert.NoError(t, err)
+
+	err = reg.UnRegister(url)
+	children, err = reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers")
+	assert.Equal(t, 0, len(children))
+	assert.Error(t, err)
+	assert.True(t, reg.IsAvailable())
+
+	err = reg.Register(url)
+	children, _ = reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers")
+	assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock%26.*.serviceid%3Dsoa.mock", children)
+	assert.NoError(t, err)
+
+}
+
 func Test_Subscribe(t *testing.T) {
 	regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
 	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
@@ -74,6 +99,39 @@ func Test_Subscribe(t *testing.T) {
 	defer ts.Stop()
 }
 
+func Test_UnSubscribe(t *testing.T) {
+	regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
+	ts, reg, _ := newMockZkRegistry(&regurl)
+
+	//provider register
+	err := reg.Register(url)
+	assert.NoError(t, err)
+
+	if err != nil {
+		return
+	}
+
+	//consumer register
+	regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
+	_, reg2, _ := newMockZkRegistry(&regurl, zookeeper.WithTestCluster(ts))
+
+	reg2.Register(url)
+	listener, _ := reg2.DoSubscribe(&url)
+
+	serviceEvent, _ := listener.Next()
+	assert.NoError(t, err)
+	if err != nil {
+		return
+	}
+	assert.Regexp(t, ".*ServiceEvent{Action{add}.*", serviceEvent.String())
+
+	reg2.UnSubscribe(&url, nil)
+	assert.Nil(t, reg2.listener)
+
+	defer ts.Stop()
+}
+
 func Test_ConsumerDestory(t *testing.T) {
 	regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)))
 	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
diff --git a/registry/zookeeper/service_discovery.go b/registry/zookeeper/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ad83ef90947afc0a5ca75af5009e8b55b4f6627
--- /dev/null
+++ b/registry/zookeeper/service_discovery.go
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package zookeeper
+
+import (
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+import (
+	"github.com/dubbogo/gost/container/set"
+	"github.com/dubbogo/gost/page"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/registry"
+	"github.com/apache/dubbo-go/remoting"
+	"github.com/apache/dubbo-go/remoting/zookeeper"
+	"github.com/apache/dubbo-go/remoting/zookeeper/curator_discovery"
+)
+
+const (
+	// RegistryZkClient zk client name
+	ServiceDiscoveryZkClient = "zk service discovery"
+)
+
+var (
+	// 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition
+	instanceMap = make(map[string]registry.ServiceDiscovery, 16)
+	initLock    sync.Mutex
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.ZOOKEEPER_KEY, newZookeeperServiceDiscovery)
+}
+
+type zookeeperServiceDiscovery struct {
+	client      *zookeeper.ZookeeperClient
+	csd         *curator_discovery.ServiceDiscovery
+	listener    *zookeeper.ZkEventListener
+	url         *common.URL
+	wg          sync.WaitGroup
+	cltLock     sync.Mutex
+	listenLock  sync.Mutex
+	done        chan struct{}
+	rootPath    string
+	listenNames []string
+}
+
+// newZookeeperServiceDiscovery the constructor of newZookeeperServiceDiscovery
+func newZookeeperServiceDiscovery(name string) (registry.ServiceDiscovery, error) {
+	instance, ok := instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// double check
+	instance, ok = instanceMap[name]
+	if ok {
+		return instance, nil
+	}
+
+	sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name)
+	if !ok || len(sdc.RemoteRef) == 0 {
+		return nil, perrors.New("could not init the instance because the config is invalid")
+	}
+	remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef)
+	if !ok {
+		return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef)
+	}
+	rootPath := remoteConfig.GetParam("rootPath", "/services")
+	url := common.NewURLWithOptions(
+		common.WithParams(make(url.Values)),
+		common.WithPassword(remoteConfig.Password),
+		common.WithUsername(remoteConfig.Username),
+		common.WithParamsValue(constant.REGISTRY_TIMEOUT_KEY, remoteConfig.TimeoutStr))
+	url.Location = remoteConfig.Address
+	zksd := &zookeeperServiceDiscovery{
+		url:      url,
+		rootPath: rootPath,
+	}
+	err := zookeeper.ValidateZookeeperClient(zksd, zookeeper.WithZkName(ServiceDiscoveryZkClient))
+	if err != nil {
+		return nil, err
+	}
+	go zookeeper.HandleClientRestart(zksd)
+	zksd.csd = curator_discovery.NewServiceDiscovery(zksd.client, rootPath)
+	return zksd, nil
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) ZkClient() *zookeeper.ZookeeperClient {
+	return zksd.client
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) SetZkClient(client *zookeeper.ZookeeperClient) {
+	zksd.client = client
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) ZkClientLock() *sync.Mutex {
+	return &zksd.cltLock
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) WaitGroup() *sync.WaitGroup {
+	return &zksd.wg
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) Done() chan struct{} {
+	return zksd.done
+}
+
+// RestartCallBack when zookeeper connection reconnect this function will be invoked.
+// try to re-register service, and listen services
+func (zksd *zookeeperServiceDiscovery) RestartCallBack() bool {
+	zksd.csd.ReRegisterServices()
+	zksd.listenLock.Lock()
+	defer zksd.listenLock.Unlock()
+	for _, name := range zksd.listenNames {
+		zksd.csd.ListenServiceEvent(name, zksd)
+	}
+	return true
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) GetUrl() common.URL {
+	return *zksd.url
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) String() string {
+	return fmt.Sprintf("zookeeper-service-discovery[%s]", zksd.url)
+}
+
+// Close client be closed
+func (zksd *zookeeperServiceDiscovery) Destroy() error {
+	zksd.client.Close()
+	return nil
+}
+
+// Register will register service in zookeeper, instance convert to curator's service instance
+// which define in curator-x-discovery.
+func (zksd *zookeeperServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	cris := zksd.toCuratorInstance(instance)
+	return zksd.csd.RegisterService(cris)
+}
+
+// Register will update service in zookeeper, instance convert to curator's service instance
+// which define in curator-x-discovery, please refer to https://github.com/apache/curator.
+func (zksd *zookeeperServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	cris := zksd.toCuratorInstance(instance)
+	return zksd.csd.UpdateService(cris)
+}
+
+// Unregister will unregister the instance in zookeeper
+func (zksd *zookeeperServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	cris := zksd.toCuratorInstance(instance)
+	return zksd.csd.UnregisterService(cris)
+}
+
+// GetDefaultPageSize will return the constant registry.DefaultPageSize
+func (zksd *zookeeperServiceDiscovery) GetDefaultPageSize() int {
+	return registry.DefaultPageSize
+}
+
+// GetServices will return the all services in zookeeper
+func (zksd *zookeeperServiceDiscovery) GetServices() *gxset.HashSet {
+	services, err := zksd.csd.QueryForNames()
+	res := gxset.NewSet()
+	if err != nil {
+		logger.Errorf("[zkServiceDiscovery] Could not query the services: %v", err)
+		return res
+	}
+	for _, service := range services {
+		res.Add(service)
+	}
+	return res
+}
+
+// GetInstances will return the instances in a service
+func (zksd *zookeeperServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+	criss, err := zksd.csd.QueryForInstances(serviceName)
+	if err != nil {
+		logger.Errorf("[zkServiceDiscovery] Could not query the instances for service{%s}, error = err{%v} ",
+			serviceName, err)
+		return make([]registry.ServiceInstance, 0, 0)
+	}
+	iss := make([]registry.ServiceInstance, 0, len(criss))
+	for _, cris := range criss {
+		iss = append(iss, zksd.toZookeeperInstance(cris))
+	}
+	return iss
+}
+
+// GetInstancesByPage will return the instances
+func (zksd *zookeeperServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+	all := zksd.GetInstances(serviceName)
+	res := make([]interface{}, 0, pageSize)
+	// could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
+	for i := offset; i < len(all) && i < offset+pageSize; i++ {
+		res = append(res, all[i])
+	}
+	return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// GetHealthyInstancesByPage will return the instance
+// In zookeeper, all service instance's is healthy.
+// However, the healthy parameter in this method maybe false. So we can not use that API.
+// Thus, we must query all instances and then do filter
+func (zksd *zookeeperServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	all := zksd.GetInstances(serviceName)
+	res := make([]interface{}, 0, pageSize)
+	// could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance
+	var (
+		i     = offset
+		count = 0
+	)
+	for i < len(all) && count < pageSize {
+		ins := all[i]
+		if ins.IsHealthy() == healthy {
+			res = append(res, all[i])
+			count++
+		}
+		i++
+	}
+	return gxpage.New(offset, pageSize, res, len(all))
+}
+
+// GetRequestInstances will return the instances
+func (zksd *zookeeperServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager {
+	res := make(map[string]gxpage.Pager, len(serviceNames))
+	for _, name := range serviceNames {
+		res[name] = zksd.GetInstancesByPage(name, offset, requestedSize)
+	}
+	return res
+}
+
+// AddListener ListenServiceEvent will add a data listener in service
+func (zksd *zookeeperServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	zksd.listenLock.Lock()
+	defer zksd.listenLock.Unlock()
+	zksd.listenNames = append(zksd.listenNames, listener.ServiceName)
+	zksd.csd.ListenServiceEvent(listener.ServiceName, zksd)
+	return nil
+}
+
+func (zksd *zookeeperServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+	return zksd.DispatchEventForInstances(serviceName, zksd.GetInstances(serviceName))
+}
+
+// DispatchEventForInstances dispatch ServiceInstancesChangedEvent
+func (zksd *zookeeperServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	return zksd.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances))
+}
+
+// nolint
+func (zksd *zookeeperServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	extension.GetGlobalDispatcher().Dispatch(event)
+	return nil
+}
+
+// DataChange implement DataListener's DataChange function
+// to resolve event to do DispatchEventByServiceName
+func (zksd *zookeeperServiceDiscovery) DataChange(eventType remoting.Event) bool {
+	path := strings.TrimPrefix(eventType.Path, zksd.rootPath)
+	path = strings.TrimPrefix(path, constant.PATH_SEPARATOR)
+	// get service name in zk path
+	serviceName := strings.Split(path, constant.PATH_SEPARATOR)[0]
+	err := zksd.DispatchEventByServiceName(serviceName)
+	if err != nil {
+		logger.Errorf("[zkServiceDiscovery] DispatchEventByServiceName{%s} error = err{%v}", serviceName, err)
+		return false
+	}
+	return true
+}
+
+// toCuratorInstance convert to curator's service instance
+func (zksd *zookeeperServiceDiscovery) toCuratorInstance(instance registry.ServiceInstance) *curator_discovery.ServiceInstance {
+	id := instance.GetHost() + ":" + strconv.Itoa(instance.GetPort())
+	pl := make(map[string]interface{}, 8)
+	pl["id"] = id
+	pl["name"] = instance.GetServiceName()
+	pl["metadata"] = instance.GetMetadata()
+	cuis := &curator_discovery.ServiceInstance{
+		Name:                instance.GetServiceName(),
+		Id:                  id,
+		Address:             instance.GetHost(),
+		Port:                instance.GetPort(),
+		Payload:             pl,
+		RegistrationTimeUTC: 0,
+	}
+	return cuis
+}
+
+// toZookeeperInstance convert to registry's service instance
+func (zksd *zookeeperServiceDiscovery) toZookeeperInstance(cris *curator_discovery.ServiceInstance) registry.ServiceInstance {
+	pl, ok := cris.Payload.(map[string]interface{})
+	if !ok {
+		logger.Errorf("[zkServiceDiscovery] toZookeeperInstance{%s} payload is not map[string]interface{}", cris.Id)
+		return nil
+	}
+	mdi, ok := pl["metadata"].(map[string]interface{})
+	if !ok {
+		logger.Errorf("[zkServiceDiscovery] toZookeeperInstance{%s} metadata is not map[string]interface{}", cris.Id)
+		return nil
+	}
+	md := make(map[string]string, len(mdi))
+	for k, v := range mdi {
+		md[k] = fmt.Sprint(v)
+	}
+	return &registry.DefaultServiceInstance{
+		Id:          cris.Id,
+		ServiceName: cris.Name,
+		Host:        cris.Address,
+		Port:        cris.Port,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    md,
+	}
+}
diff --git a/registry/zookeeper/service_discovery_test.go b/registry/zookeeper/service_discovery_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ea3c7ddd48adc0adc4162d8306d28283575f694a
--- /dev/null
+++ b/registry/zookeeper/service_discovery_test.go
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package zookeeper
+
+import (
+	"strconv"
+	"sync"
+	"testing"
+)
+
+import (
+	"github.com/dubbogo/go-zookeeper/zk"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/observer"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/registry"
+)
+
+var testName = "test"
+
+func prepareData(t *testing.T) *zk.TestCluster {
+	ts, err := zk.StartTestCluster(1, nil, nil)
+	assert.NoError(t, err)
+	assert.NotNil(t, ts.Servers[0])
+	address := "127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)
+
+	config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{
+		Protocol:  "zookeeper",
+		RemoteRef: "test",
+	}
+
+	config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{
+		Address:    address,
+		TimeoutStr: "10s",
+	}
+	return ts
+}
+
+func TestNewZookeeperServiceDiscovery(t *testing.T) {
+	name := "zookeeper1"
+	_, err := newZookeeperServiceDiscovery(name)
+
+	// the ServiceDiscoveryConfig not found
+	assert.NotNil(t, err)
+
+	sdc := &config.ServiceDiscoveryConfig{
+		Protocol:  "zookeeper",
+		RemoteRef: "mock",
+	}
+	config.GetBaseConfig().ServiceDiscoveries[name] = sdc
+	_, err = newZookeeperServiceDiscovery(name)
+
+	// RemoteConfig not found
+	assert.NotNil(t, err)
+}
+
+func TestCURDZookeeperServiceDiscovery(t *testing.T) {
+	ts := prepareData(t)
+	defer ts.Stop()
+	sd, err := newZookeeperServiceDiscovery(testName)
+	assert.Nil(t, err)
+	defer sd.Destroy()
+	md := make(map[string]string)
+	md["t1"] = "test1"
+	err = sd.Register(&registry.DefaultServiceInstance{
+		Id:          "testId",
+		ServiceName: testName,
+		Host:        "127.0.0.1",
+		Port:        2233,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    md,
+	})
+	assert.Nil(t, err)
+
+	testsPager := sd.GetHealthyInstancesByPage(testName, 0, 1, true)
+	assert.Equal(t, 1, testsPager.GetDataSize())
+	assert.Equal(t, 1, testsPager.GetTotalPages())
+	test := testsPager.GetData()[0].(registry.ServiceInstance)
+	assert.Equal(t, "127.0.0.1:2233", test.GetId())
+	assert.Equal(t, "test1", test.GetMetadata()["t1"])
+
+	md["t1"] = "test12"
+	err = sd.Update(&registry.DefaultServiceInstance{
+		Id:          "testId",
+		ServiceName: testName,
+		Host:        "127.0.0.1",
+		Port:        2233,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    md,
+	})
+	assert.Nil(t, err)
+
+	testsPager = sd.GetInstancesByPage(testName, 0, 1)
+	assert.Equal(t, 1, testsPager.GetDataSize())
+	test = testsPager.GetData()[0].(registry.ServiceInstance)
+	assert.Equal(t, "test12", test.GetMetadata()["t1"])
+
+	testsMap := sd.GetRequestInstances([]string{testName}, 0, 1)
+	assert.Equal(t, 1, len(testsMap))
+	assert.Equal(t, 1, testsMap[testName].GetDataSize())
+	test = testsMap[testName].GetData()[0].(registry.ServiceInstance)
+	assert.Equal(t, "test12", test.GetMetadata()["t1"])
+
+	names := sd.GetServices()
+	assert.Equal(t, 1, names.Size())
+	assert.Equal(t, testName, names.Values()[0])
+
+	err = sd.Unregister(&registry.DefaultServiceInstance{
+		Id:          "testId",
+		ServiceName: testName,
+		Host:        "127.0.0.1",
+		Port:        2233,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    nil,
+	})
+	assert.Nil(t, err)
+}
+
+func TestAddListenerZookeeperServiceDiscovery(t *testing.T) {
+	ts := prepareData(t)
+	defer ts.Stop()
+	sd, err := newZookeeperServiceDiscovery(testName)
+	assert.Nil(t, err)
+	defer sd.Destroy()
+
+	err = sd.Register(&registry.DefaultServiceInstance{
+		Id:          "testId",
+		ServiceName: testName,
+		Host:        "127.0.0.1",
+		Port:        2233,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    nil,
+	})
+	assert.Nil(t, err)
+
+	assert.Nil(t, err)
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+	tn := &testNotify{
+		wg: wg,
+		t:  t,
+	}
+	sicl := &registry.ServiceInstancesChangedListener{
+		ServiceName:   testName,
+		ChangedNotify: tn,
+	}
+	extension.SetAndInitGlobalDispatcher("direct")
+	extension.GetGlobalDispatcher().AddEventListener(sicl)
+	err = sd.AddListener(sicl)
+	assert.Nil(t, err)
+
+	err = sd.Update(&registry.DefaultServiceInstance{
+		Id:          "testId",
+		ServiceName: testName,
+		Host:        "127.0.0.1",
+		Port:        2233,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    nil,
+	})
+	tn.wg.Wait()
+}
+
+type testNotify struct {
+	wg *sync.WaitGroup
+	t  *testing.T
+}
+
+func (tn *testNotify) Notify(e observer.Event) {
+	ice := e.(*registry.ServiceInstancesChangedEvent)
+	assert.Equal(tn.t, 1, len(ice.Instances))
+	assert.Equal(tn.t, "127.0.0.1:2233", ice.Instances[0].GetId())
+	tn.wg.Done()
+}
diff --git a/remoting/consul/test_agent.go b/remoting/consul/test_agent.go
new file mode 100644
index 0000000000000000000000000000000000000000..1744da7bd9992ae3cd376b22e9ea3a135dce2b16
--- /dev/null
+++ b/remoting/consul/test_agent.go
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package consul
+
+import (
+	"io/ioutil"
+	"os"
+	"strconv"
+	"testing"
+)
+
+import (
+	"github.com/hashicorp/consul/agent"
+)
+
+// Consul agent, used for test, simulates
+// an embedded consul server.
+type ConsulAgent struct {
+	dataDir   string
+	testAgent *agent.TestAgent
+}
+
+func NewConsulAgent(t *testing.T, port int) *ConsulAgent {
+	dataDir, _ := ioutil.TempDir("./", "agent")
+	hcl := `
+		ports { 
+			http = ` + strconv.Itoa(port) + `
+		}
+		data_dir = "` + dataDir + `"
+	`
+	testAgent := &agent.TestAgent{Name: t.Name(), DataDir: dataDir, HCL: hcl}
+	testAgent.Start(t)
+
+	consulAgent := &ConsulAgent{
+		dataDir:   dataDir,
+		testAgent: testAgent,
+	}
+	return consulAgent
+}
+
+func (consulAgent *ConsulAgent) Close() error {
+	var err error
+
+	err = consulAgent.testAgent.Shutdown()
+	if err != nil {
+		return err
+	}
+	return os.RemoveAll(consulAgent.dataDir)
+}
diff --git a/remoting/consul/test_agent_test.go b/remoting/consul/test_agent_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8cf0ac6cd80e517ab7bc1b52cc7774a708082d5e
--- /dev/null
+++ b/remoting/consul/test_agent_test.go
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package consul
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewConsulAgent(t *testing.T) {
+	consulAgent := NewConsulAgent(t, 8500)
+	err := consulAgent.Close()
+	assert.NoError(t, err)
+}
diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go
index b337c79584cc5058e89bd582b007e72fb10da7ee..7632a7cd042d36db5e02280a14224b83e8736e6c 100644
--- a/remoting/etcdv3/client.go
+++ b/remoting/etcdv3/client.go
@@ -19,7 +19,6 @@ package etcdv3
 
 import (
 	"context"
-	"path"
 	"sync"
 	"time"
 )
@@ -42,6 +41,8 @@ const (
 	MaxFailTimes = 15
 	// RegistryETCDV3Client client name
 	RegistryETCDV3Client = "etcd registry"
+	// metadataETCDV3Client client name
+	MetadataETCDV3Client = "etcd metadata"
 )
 
 var (
@@ -106,7 +107,7 @@ func ValidateClient(container clientFacade, opts ...Option) error {
 
 	// new Client
 	if container.Client() == nil {
-		newClient, err := newClient(options.name, options.endpoints, options.timeout, options.heartbeat)
+		newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat)
 		if err != nil {
 			logger.Warnf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}",
 				options.name, options.endpoints, options.timeout, err)
@@ -118,7 +119,7 @@ func ValidateClient(container clientFacade, opts ...Option) error {
 	// Client lose connection with etcd server
 	if container.Client().rawClient == nil {
 
-		newClient, err := newClient(options.name, options.endpoints, options.timeout, options.heartbeat)
+		newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat)
 		if err != nil {
 			logger.Warnf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}",
 				options.name, options.endpoints, options.timeout, err)
@@ -130,6 +131,26 @@ func ValidateClient(container clientFacade, opts ...Option) error {
 	return nil
 }
 
+//  nolint
+func NewServiceDiscoveryClient(opts ...Option) *Client {
+	options := &Options{
+		heartbeat: 1, // default heartbeat
+	}
+
+	for _, opt := range opts {
+		opt(options)
+	}
+
+	newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat)
+	if err != nil {
+		logger.Errorf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}",
+			options.name, options.endpoints, options.timeout, err)
+		return nil
+	}
+
+	return newClient
+}
+
 // Client represents etcd client Configuration
 type Client struct {
 	lock sync.RWMutex
@@ -148,7 +169,8 @@ type Client struct {
 	Wait sync.WaitGroup
 }
 
-func newClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) {
+// nolint
+func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	rawClient, err := clientv3.New(clientv3.Config{
@@ -285,6 +307,28 @@ func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error {
 	return nil
 }
 
+// if k not exist will put k/v in etcd
+// if k is already exist in etcd, replace it
+func (c *Client) update(k string, v string, opts ...clientv3.OpOption) error {
+
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+
+	if c.rawClient == nil {
+		return ErrNilETCDV3Client
+	}
+
+	_, err := c.rawClient.Txn(c.ctx).
+		If(clientv3.Compare(clientv3.Version(k), "!=", -1)).
+		Then(clientv3.OpPut(k, v, opts...)).
+		Commit()
+	if err != nil {
+		return err
+
+	}
+	return nil
+}
+
 func (c *Client) delete(k string) error {
 
 	c.lock.RLock()
@@ -455,6 +499,15 @@ func (c *Client) Create(k string, v string) error {
 	return nil
 }
 
+// Update key value ...
+func (c *Client) Update(k, v string) error {
+	err := c.update(k, v)
+	if err != nil {
+		return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v)
+	}
+	return nil
+}
+
 // nolint
 func (c *Client) Delete(k string) error {
 
@@ -467,16 +520,14 @@ func (c *Client) Delete(k string) error {
 }
 
 // RegisterTemp registers a temporary node
-func (c *Client) RegisterTemp(basePath string, node string) (string, error) {
+func (c *Client) RegisterTemp(k, v string) error {
 
-	completeKey := path.Join(basePath, node)
-
-	err := c.keepAliveKV(completeKey, "")
+	err := c.keepAliveKV(k, v)
 	if err != nil {
-		return "", perrors.WithMessagef(err, "keepalive kv (key %s)", completeKey)
+		return perrors.WithMessagef(err, "keepalive kv (key %s)", k)
 	}
 
-	return completeKey, nil
+	return nil
 }
 
 // GetChildrenKVList gets children kv list by @k
diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go
index 9a5ef60993598cf93c469989d68c991657bd5fb8..3de266f42ffbc69a1e2ba4662a9a9fff1d831cd4 100644
--- a/remoting/etcdv3/client_test.go
+++ b/remoting/etcdv3/client_test.go
@@ -120,7 +120,7 @@ func (suite *ClientTestSuite) TearDownSuite() {
 }
 
 func (suite *ClientTestSuite) setUpClient() *Client {
-	c, err := newClient(suite.etcdConfig.name,
+	c, err := NewClient(suite.etcdConfig.name,
 		suite.etcdConfig.endpoints,
 		suite.etcdConfig.timeout,
 		suite.etcdConfig.heartbeat)
@@ -384,7 +384,7 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() {
 		assert.Contains(t, events, eDelete)
 	}()
 
-	_, err := c.RegisterTemp("scott", "wang")
+	err := c.RegisterTemp("scott/wang", "test")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/remoting/etcdv3/listener.go b/remoting/etcdv3/listener.go
index 00b5b19b36d3baa8871efdd3d53e80f05d7aeac1..4f80a89dfb713036a5d4d812bc7a2d5551f42284 100644
--- a/remoting/etcdv3/listener.go
+++ b/remoting/etcdv3/listener.go
@@ -49,7 +49,7 @@ func NewEventListener(client *Client) *EventListener {
 	}
 }
 
-// ListenServiceNodeEvent Listen on a spec key
+// listenServiceNodeEvent Listen on a spec key
 // this method will return true when spec key deleted,
 // this method will return false when deep layer connection lose
 func (l *EventListener) ListenServiceNodeEvent(key string, listener ...remoting.DataListener) bool {
@@ -178,9 +178,9 @@ func timeSecondDuration(sec int) time.Duration {
 }
 
 // ListenServiceEvent is invoked by etcdv3 ConsumerRegistry::Registe/ etcdv3 ConsumerRegistry::get/etcdv3 ConsumerRegistry::getListener
-// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent
+// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent
 //                            |
-//                            --------> ListenServiceNodeEvent
+//                            --------> listenServiceNodeEvent
 func (l *EventListener) ListenServiceEvent(key string, listener remoting.DataListener) {
 
 	l.keyMapLock.Lock()
diff --git a/registry/nacos/base_registry.go b/remoting/nacos/builder.go
similarity index 64%
rename from registry/nacos/base_registry.go
rename to remoting/nacos/builder.go
index 51c1eb8ad350231d1d2901f8acef3dbde204a741..8a247e267daa02d748c9a5e47ced698f617dfe9a 100644
--- a/registry/nacos/base_registry.go
+++ b/remoting/nacos/builder.go
@@ -26,6 +26,7 @@ import (
 
 import (
 	"github.com/nacos-group/nacos-sdk-go/clients"
+	"github.com/nacos-group/nacos-sdk-go/clients/config_client"
 	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
 	nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant"
 	perrors "github.com/pkg/errors"
@@ -34,30 +35,16 @@ import (
 import (
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/config"
 )
 
-// baseRegistry is the parent of both interface-level registry
-// and service discovery(related to application-level registry)
-type nacosBaseRegistry struct {
-	*common.URL
-	namingClient naming_client.INamingClient
-}
-
-// newBaseRegistry will create new instance
-func newBaseRegistry(url *common.URL) (nacosBaseRegistry, error) {
+// NewNacosConfigClient read the config from url and build an instance
+func NewNacosConfigClient(url *common.URL) (config_client.IConfigClient, error) {
 	nacosConfig, err := getNacosConfig(url)
 	if err != nil {
-		return nacosBaseRegistry{}, err
-	}
-	client, err := clients.CreateNamingClient(nacosConfig)
-	if err != nil {
-		return nacosBaseRegistry{}, err
-	}
-	registry := nacosBaseRegistry{
-		URL:          url,
-		namingClient: client,
+		return nil, err
 	}
-	return registry, nil
+	return clients.CreateConfigClient(nacosConfig)
 }
 
 // getNacosConfig will return the nacos config
@@ -104,3 +91,40 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
 
 	return configMap, nil
 }
+
+// NewNacosClient creates an instance with the config
+func NewNacosClient(rc *config.RemoteConfig) (naming_client.INamingClient, error) {
+	if len(rc.Address) == 0 {
+		return nil, perrors.New("nacos address is empty!")
+	}
+	configMap := make(map[string]interface{}, 2)
+
+	addresses := strings.Split(rc.Address, ",")
+	serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses))
+	for _, addr := range addresses {
+		ip, portStr, err := net.SplitHostPort(addr)
+		if err != nil {
+			return nil, perrors.WithMessagef(err, "split [%s] ", addr)
+		}
+		port, _ := strconv.Atoi(portStr)
+		serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{
+			IpAddr: ip,
+			Port:   uint64(port),
+		})
+	}
+	configMap["serverConfigs"] = serverConfigs
+
+	var clientConfig nacosConstant.ClientConfig
+	timeout := rc.Timeout()
+	clientConfig.TimeoutMs = uint64(timeout.Nanoseconds() / constant.MsToNanoRate)
+	clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs
+	clientConfig.CacheDir = rc.GetParam(constant.NACOS_CACHE_DIR_KEY, "")
+	clientConfig.LogDir = rc.GetParam(constant.NACOS_LOG_DIR_KEY, "")
+	clientConfig.Endpoint = rc.Address
+	clientConfig.Username = rc.Username
+	clientConfig.Password = rc.Password
+	clientConfig.NotLoadCacheAtStart = true
+	configMap["clientConfig"] = clientConfig
+
+	return clients.CreateNamingClient(configMap)
+}
diff --git a/metadata/namemapping/memory/service_name_mapping.go b/remoting/nacos/builder_test.go
similarity index 64%
rename from metadata/namemapping/memory/service_name_mapping.go
rename to remoting/nacos/builder_test.go
index 8a891491bdb97808b77422092a1043c1c0ffafbf..61d13ef26f9f1d17173bbeb11468f9babdade2f5 100644
--- a/metadata/namemapping/memory/service_name_mapping.go
+++ b/remoting/nacos/builder_test.go
@@ -15,22 +15,35 @@
  * limitations under the License.
  */
 
-package memory
+package nacos
 
 import (
-	gxset "github.com/dubbogo/gost/container/set"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
 )
 
 import (
 	"github.com/apache/dubbo-go/config"
 )
 
-type InMemoryServiceNameMapping struct{}
+func TestNewNacosClient(t *testing.T) {
+	rc := &config.RemoteConfig{}
+	client, err := NewNacosClient(rc)
 
-func (i InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error {
-	return nil
-}
+	// address is nil
+	assert.NotNil(t, err)
+
+	rc.Address = "console.nacos.io:80:123"
+	client, err = NewNacosClient(rc)
+	// invalid address
+	assert.NotNil(t, err)
 
-func (i InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
-	return gxset.NewSet(config.GetApplicationConfig().Name), nil
+	rc.Address = "console.nacos.io:80"
+	rc.TimeoutStr = "10s"
+	client, err = NewNacosClient(rc)
+	assert.NotNil(t, client)
+	assert.Nil(t, err)
 }
diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go
index 226ec24085ddd0cfaea0c0d6a0ba6c91a9840ad0..4ca34a6aeccf7b588a96edb44b4d5913a3e0fd8e 100644
--- a/remoting/zookeeper/client.go
+++ b/remoting/zookeeper/client.go
@@ -42,21 +42,23 @@ const (
 )
 
 var (
-	errNilZkClientConn = perrors.New("zookeeperclient{conn} is nil")
+	errNilZkClientConn = perrors.New("zookeeper client{conn} is nil")
 	errNilChildren     = perrors.Errorf("has none children")
 	errNilNode         = perrors.Errorf("node does not exist")
 )
 
 // ZookeeperClient represents zookeeper client Configuration
 type ZookeeperClient struct {
-	name          string
-	ZkAddrs       []string
-	sync.RWMutex  // for conn
-	Conn          *zk.Conn
-	Timeout       time.Duration
-	exit          chan struct{}
-	Wait          sync.WaitGroup
-	eventRegistry map[string][]*chan struct{}
+	name         string
+	ZkAddrs      []string
+	sync.RWMutex // for conn
+	Conn         *zk.Conn
+	Timeout      time.Duration
+	exit         chan struct{}
+	Wait         sync.WaitGroup
+
+	eventRegistry     map[string][]*chan struct{}
+	eventRegistryLock sync.RWMutex
 }
 
 // nolint
@@ -108,14 +110,15 @@ func WithZkName(name string) Option {
 }
 
 // ValidateZookeeperClient validates client and sets options
-func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
-	var err error
+func ValidateZookeeperClient(container ZkClientFacade, opts ...Option) error {
+	var (
+		err error
+	)
 	options := &Options{}
 	for _, opt := range opts {
 		opt(options)
 	}
 	connected := false
-	err = nil
 
 	lock := container.ZkClientLock()
 	url := container.GetUrl()
@@ -124,16 +127,15 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
 	defer lock.Unlock()
 
 	if container.ZkClient() == nil {
-		//in dubbo ,every registry only connect one node ,so this is []string{r.Address}
-		var timeout time.Duration
-		timeout, err = time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
+		// in dubbo, every registry only connect one node, so this is []string{r.Address}
+		timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
 		if err != nil {
 			logger.Errorf("timeout config %v is invalid ,err is %v",
 				url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error())
 			return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location)
 		}
 		zkAddresses := strings.Split(url.Location, ",")
-		newClient, err := newZookeeperClient(options.zkName, zkAddresses, timeout)
+		newClient, err := NewZookeeperClient(options.zkName, zkAddresses, timeout)
 		if err != nil {
 			logger.Warnf("newZookeeperClient(name{%s}, zk address{%v}, timeout{%d}) = error{%v}",
 				options.zkName, url.Location, timeout.String(), err)
@@ -154,14 +156,15 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
 	}
 
 	if connected {
-		logger.Info("Connect to zookeeper successfully, name{%s}, zk address{%v}", options.zkName, url.Location)
-		container.WaitGroup().Add(1) //zk client start successful, then registry wg +1
+		logger.Infof("Connect to zookeeper successfully, name{%s}, zk address{%v}", options.zkName, url.Location)
+		container.WaitGroup().Add(1) // zk client start successful, then registry wg +1
 	}
 
 	return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.PrimitiveURL)
 }
 
-func newZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*ZookeeperClient, error) {
+// nolint
+func NewZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*ZookeeperClient, error) {
 	var (
 		err   error
 		event <-chan zk.Event
@@ -211,14 +214,14 @@ func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option)
 		eventRegistry: make(map[string][]*chan struct{}),
 	}
 
-	opions := &Options{}
+	options := &Options{}
 	for _, opt := range opts {
-		opt(opions)
+		opt(options)
 	}
 
 	// connect to zookeeper
-	if opions.ts != nil {
-		ts = opions.ts
+	if options.ts != nil {
+		ts = options.ts
 	} else {
 		ts, err = zk.StartTestCluster(1, nil, nil)
 		if err != nil {
@@ -246,13 +249,12 @@ func (z *ZookeeperClient) HandleZkEvent(session <-chan zk.Event) {
 		logger.Infof("zk{path:%v, name:%s} connection goroutine game over.", z.ZkAddrs, z.name)
 	}()
 
-LOOP:
 	for {
 		select {
 		case <-z.exit:
-			break LOOP
+			return
 		case event = <-session:
-			logger.Warnf("client{%s} get a zookeeper event{type:%s, server:%s, path:%s, state:%d-%s, err:%v}",
+			logger.Infof("client{%s} get a zookeeper event{type:%s, server:%s, path:%s, state:%d-%s, err:%v}",
 				z.name, event.Type, event.Server, event.Path, event.State, StateToString(event.State), event.Err)
 			switch (int)(event.State) {
 			case (int)(zk.StateDisconnected):
@@ -265,11 +267,10 @@ LOOP:
 				if conn != nil {
 					conn.Close()
 				}
-
-				break LOOP
+				return
 			case (int)(zk.EventNodeDataChanged), (int)(zk.EventNodeChildrenChanged):
 				logger.Infof("zkClient{%s} get zk node changed event{path:%s}", z.name, event.Path)
-				z.Lock()
+				z.eventRegistryLock.RLock()
 				for p, a := range z.eventRegistry {
 					if strings.HasPrefix(p, event.Path) {
 						logger.Infof("send event{state:zk.EventNodeDataChange, Path:%s} notify event to path{%s} related listener",
@@ -279,16 +280,18 @@ LOOP:
 						}
 					}
 				}
-				z.Unlock()
+				z.eventRegistryLock.RUnlock()
 			case (int)(zk.StateConnecting), (int)(zk.StateConnected), (int)(zk.StateHasSession):
 				if state == (int)(zk.StateHasSession) {
 					continue
 				}
+				z.eventRegistryLock.RLock()
 				if a, ok := z.eventRegistry[event.Path]; ok && 0 < len(a) {
 					for _, e := range a {
 						*e <- struct{}{}
 					}
 				}
+				z.eventRegistryLock.RUnlock()
 			}
 			state = (int)(event.State)
 		}
@@ -301,13 +304,12 @@ func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) {
 		return
 	}
 
-	z.Lock()
+	z.eventRegistryLock.Lock()
+	defer z.eventRegistryLock.Unlock()
 	a := z.eventRegistry[zkPath]
 	a = append(a, event)
-
 	z.eventRegistry[zkPath] = a
 	logger.Debugf("zkClient{%s} register event{path:%s, ptr:%p}", z.name, zkPath, event)
-	z.Unlock()
 }
 
 // UnregisterEvent unregisters zookeeper events
@@ -315,16 +317,16 @@ func (z *ZookeeperClient) UnregisterEvent(zkPath string, event *chan struct{}) {
 	if zkPath == "" {
 		return
 	}
-	z.Lock()
-	defer z.Unlock()
+
+	z.eventRegistryLock.Lock()
+	defer z.eventRegistryLock.Unlock()
 	infoList, ok := z.eventRegistry[zkPath]
 	if !ok {
 		return
 	}
 	for i, e := range infoList {
 		if e == event {
-			arr := infoList
-			infoList = append(arr[:i], arr[i+1:]...)
+			infoList = append(infoList[:i], infoList[i+1:]...)
 			logger.Infof("zkClient{%s} unregister event{path:%s, event:%p}", z.name, zkPath, event)
 		}
 	}
@@ -362,11 +364,11 @@ func (z *ZookeeperClient) ZkConnValid() bool {
 	}
 
 	valid := true
-	z.Lock()
+	z.RLock()
 	if z.Conn == nil {
 		valid = false
 	}
-	z.Unlock()
+	z.RUnlock()
 
 	return valid
 }
@@ -384,11 +386,11 @@ func (z *ZookeeperClient) Close() {
 	z.Conn = nil
 	z.Unlock()
 	if conn != nil {
-		logger.Warnf("zkClient Conn{name:%s, zk addr:%s} exit now.", z.name, conn.SessionID())
+		logger.Infof("zkClient Conn{name:%s, zk addr:%d} exit now.", z.name, conn.SessionID())
 		conn.Close()
 	}
 
-	logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs)
+	logger.Infof("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs)
 }
 
 // Create will create the node recursively, which means that if the parent node is absent,
@@ -407,21 +409,21 @@ func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error {
 	)
 
 	logger.Debugf("zookeeperClient.Create(basePath{%s})", basePath)
+	conn := z.getConn()
+	err = errNilZkClientConn
+	if conn == nil {
+		return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
+	}
+
 	for _, str := range strings.Split(basePath, "/")[1:] {
 		tmpPath = path.Join(tmpPath, "/", str)
-		err = errNilZkClientConn
-		z.Lock()
-		conn := z.Conn
-		z.Unlock()
-		if conn != nil {
-			_, err = conn.Create(tmpPath, value, 0, zk.WorldACL(zk.PermAll))
-		}
+		_, err = conn.Create(tmpPath, value, 0, zk.WorldACL(zk.PermAll))
 
 		if err != nil {
 			if err == zk.ErrNodeExists {
-				logger.Debugf("zk.create(\"%s\") exists\n", tmpPath)
+				logger.Debugf("zk.create(\"%s\") exists", tmpPath)
 			} else {
-				logger.Errorf("zk.create(\"%s\") error(%v)\n", tmpPath, perrors.WithStack(err))
+				logger.Errorf("zk.create(\"%s\") error(%v)", tmpPath, perrors.WithStack(err))
 				return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
 			}
 		}
@@ -430,16 +432,52 @@ func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error {
 	return nil
 }
 
-// nolint
-func (z *ZookeeperClient) Delete(basePath string) error {
+// CreateTempWithValue will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first,and set value in last child path
+// If the path exist, it will update data
+func (z *ZookeeperClient) CreateTempWithValue(basePath string, value []byte) error {
 	var (
-		err error
+		err     error
+		tmpPath string
 	)
 
+	logger.Debugf("zookeeperClient.Create(basePath{%s})", basePath)
+	conn := z.getConn()
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	if conn == nil {
+		return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
+	}
+
+	pathSlice := strings.Split(basePath, "/")[1:]
+	length := len(pathSlice)
+	for i, str := range pathSlice {
+		tmpPath = path.Join(tmpPath, "/", str)
+		// last child need be ephemeral
+		if i == length-1 {
+			_, err = conn.Create(tmpPath, value, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
+			if err == zk.ErrNodeExists {
+				return err
+			}
+		} else {
+			_, err = conn.Create(tmpPath, []byte{}, 0, zk.WorldACL(zk.PermAll))
+		}
+		if err != nil {
+			if err == zk.ErrNodeExists {
+				logger.Debugf("zk.create(\"%s\") exists", tmpPath)
+			} else {
+				logger.Errorf("zk.create(\"%s\") error(%v)", tmpPath, perrors.WithStack(err))
+				return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
+			}
+		}
+	}
+
+	return nil
+}
+
+// nolint
+func (z *ZookeeperClient) Delete(basePath string) error {
+	err := errNilZkClientConn
+	conn := z.getConn()
 	if conn != nil {
 		err = conn.Delete(basePath, -1)
 	}
@@ -451,26 +489,22 @@ func (z *ZookeeperClient) Delete(basePath string) error {
 func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, error) {
 	var (
 		err     error
-		data    []byte
 		zkPath  string
 		tmpPath string
 	)
 
 	err = errNilZkClientConn
-	data = []byte("")
 	zkPath = path.Join(basePath) + "/" + node
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
-		tmpPath, err = conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
+		tmpPath, err = conn.Create(zkPath, []byte(""), zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
 	}
 
 	if err != nil {
-		logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err))
+		logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)", zkPath, perrors.WithStack(err))
 		return zkPath, perrors.WithStack(err)
 	}
-	logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath)
+	logger.Debugf("zkClient{%s} create a temp zookeeper node:%s", z.name, tmpPath)
 
 	return tmpPath, nil
 }
@@ -483,9 +517,7 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string,
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		tmpPath, err = conn.Create(
 			path.Join(basePath)+"/",
@@ -497,11 +529,11 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string,
 
 	logger.Debugf("zookeeperClient.RegisterTempSeq(basePath{%s}) = tempPath{%s}", basePath, tmpPath)
 	if err != nil && err != zk.ErrNodeExists {
-		logger.Errorf("zkClient{%s} conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)\n",
+		logger.Errorf("zkClient{%s} conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)",
 			z.name, basePath, string(data), err)
 		return "", perrors.WithStack(err)
 	}
-	logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath)
+	logger.Debugf("zkClient{%s} create a temp zookeeper node:%s", z.name, tmpPath)
 
 	return tmpPath, nil
 }
@@ -516,9 +548,7 @@ func (z *ZookeeperClient) GetChildrenW(path string) ([]string, <-chan zk.Event,
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		children, stat, watcher, err = conn.ChildrenW(path)
 	}
@@ -552,9 +582,7 @@ func (z *ZookeeperClient) GetChildren(path string) ([]string, error) {
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		children, stat, err = conn.Children(path)
 	}
@@ -585,9 +613,7 @@ func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) {
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		exist, _, watcher, err = conn.ExistsW(zkPath)
 	}
@@ -608,3 +634,15 @@ func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) {
 func (z *ZookeeperClient) GetContent(zkPath string) ([]byte, *zk.Stat, error) {
 	return z.Conn.Get(zkPath)
 }
+
+// nolint
+func (z *ZookeeperClient) SetContent(zkPath string, content []byte, version int32) (*zk.Stat, error) {
+	return z.Conn.Set(zkPath, content, version)
+}
+
+// getConn gets zookeeper connection safely
+func (z *ZookeeperClient) getConn() *zk.Conn {
+	z.RLock()
+	defer z.RUnlock()
+	return z.Conn
+}
diff --git a/remoting/zookeeper/client_test.go b/remoting/zookeeper/client_test.go
index 0f6899568ad4744dc58022c41e22db6f901ad5de..34741700ca2a9d86ee5321b0b19ed64b2b1a25a8 100644
--- a/remoting/zookeeper/client_test.go
+++ b/remoting/zookeeper/client_test.go
@@ -18,7 +18,6 @@
 package zookeeper
 
 import (
-	"fmt"
 	"testing"
 	"time"
 )
@@ -28,6 +27,10 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+import (
+	"github.com/apache/dubbo-go/common/logger"
+)
+
 func verifyEventStateOrder(t *testing.T, c <-chan zk.Event, expectedStates []zk.State, source string) {
 	for _, state := range expectedStates {
 		for {
@@ -35,7 +38,7 @@ func verifyEventStateOrder(t *testing.T, c <-chan zk.Event, expectedStates []zk.
 			if !ok {
 				t.Fatalf("unexpected channel close for %s", source)
 			}
-			fmt.Println(event)
+			logger.Debug(event)
 			if event.Type != zk.EventSession {
 				continue
 			}
@@ -87,9 +90,10 @@ func Test_newMockZookeeperClient(t *testing.T) {
 }
 
 func TestCreate(t *testing.T) {
-	ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second)
+	ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
 	defer ts.Stop()
-	err := z.Create("test1/test2/test3/test4")
+	err = z.Create("test1/test2/test3/test4")
 	assert.NoError(t, err)
 
 	states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession}
@@ -97,21 +101,24 @@ func TestCreate(t *testing.T) {
 }
 
 func TestCreateDelete(t *testing.T) {
-	ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second)
+	ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
 	defer ts.Stop()
 
 	states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession}
 	verifyEventStateOrder(t, event, states, "event channel")
-	err := z.Create("/test1/test2/test3/test4")
+	err = z.Create("/test1/test2/test3/test4")
+	assert.NoError(t, err)
+	err = z.Delete("/test1/test2/test3/test4")
 	assert.NoError(t, err)
-	err2 := z.Delete("/test1/test2/test3/test4")
-	assert.NoError(t, err2)
+	// verifyEventOrder(t, event, []zk.EventType{zk.EventNodeCreated}, "event channel")
 }
 
 func TestRegisterTemp(t *testing.T) {
-	ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second)
+	ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
 	defer ts.Stop()
-	err := z.Create("/test1/test2/test3")
+	err = z.Create("/test1/test2/test3")
 	assert.NoError(t, err)
 
 	tmpath, err := z.RegisterTemp("/test1/test2/test3", "test4")
@@ -122,9 +129,10 @@ func TestRegisterTemp(t *testing.T) {
 }
 
 func TestRegisterTempSeq(t *testing.T) {
-	ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second)
+	ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
 	defer ts.Stop()
-	err := z.Create("/test1/test2/test3")
+	err = z.Create("/test1/test2/test3")
 	assert.NoError(t, err)
 	tmpath, err := z.RegisterTempSeq("/test1/test2/test3", []byte("test"))
 	assert.NoError(t, err)
diff --git a/remoting/zookeeper/curator_discovery/service_discovery.go b/remoting/zookeeper/curator_discovery/service_discovery.go
new file mode 100644
index 0000000000000000000000000000000000000000..9566b5494389325520b4eb6a8eb170e0b305bb47
--- /dev/null
+++ b/remoting/zookeeper/curator_discovery/service_discovery.go
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package curator_discovery
+
+import (
+	"encoding/json"
+	"path"
+	"strings"
+	"sync"
+)
+
+import (
+	"github.com/dubbogo/go-zookeeper/zk"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/remoting"
+	"github.com/apache/dubbo-go/remoting/zookeeper"
+)
+
+// Entry contain a service instance
+type Entry struct {
+	sync.Mutex
+	instance *ServiceInstance
+}
+
+// ServiceInstance which define in curator-x-discovery, please refer to
+// https://github.com/apache/curator/blob/master/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceDiscovery.java
+// It's not exactly the same as curator-x-discovery's service discovery
+type ServiceDiscovery struct {
+	client   *zookeeper.ZookeeperClient
+	mutex    *sync.Mutex
+	basePath string
+	services *sync.Map
+	listener *zookeeper.ZkEventListener
+}
+
+// NewServiceDiscovery the constructor of service discovery
+func NewServiceDiscovery(client *zookeeper.ZookeeperClient, basePath string) *ServiceDiscovery {
+	return &ServiceDiscovery{
+		client:   client,
+		mutex:    &sync.Mutex{},
+		basePath: basePath,
+		services: &sync.Map{},
+		listener: zookeeper.NewZkEventListener(client),
+	}
+}
+
+// registerService register service to zookeeper
+func (sd *ServiceDiscovery) registerService(instance *ServiceInstance) error {
+	path := sd.pathForInstance(instance.Name, instance.Id)
+	data, err := json.Marshal(instance)
+	if err != nil {
+		return err
+	}
+	err = sd.client.CreateTempWithValue(path, data)
+	if err == zk.ErrNodeExists {
+		_, state, _ := sd.client.GetContent(path)
+		if state != nil {
+			_, err = sd.client.SetContent(path, data, state.Version+1)
+			if err != nil {
+				logger.Debugf("Try to update the node data failed. In most cases, it's not a problem. ")
+			}
+		}
+		return nil
+	}
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// RegisterService register service to zookeeper, and ensure cache is consistent with zookeeper
+func (sd *ServiceDiscovery) RegisterService(instance *ServiceInstance) error {
+	value, loaded := sd.services.LoadOrStore(instance.Id, &Entry{})
+	entry, ok := value.(*Entry)
+	if !ok {
+		return perrors.New("[ServiceDiscovery] services value not entry")
+	}
+	entry.Lock()
+	defer entry.Unlock()
+	entry.instance = instance
+	err := sd.registerService(instance)
+	if err != nil {
+		return err
+	}
+	if !loaded {
+		sd.ListenServiceInstanceEvent(instance.Name, instance.Id, sd)
+	}
+	return nil
+}
+
+// UpdateService update service in zookeeper, and ensure cache is consistent with zookeeper
+func (sd *ServiceDiscovery) UpdateService(instance *ServiceInstance) error {
+	value, ok := sd.services.Load(instance.Id)
+	if !ok {
+		return perrors.Errorf("[ServiceDiscovery] Service{%s} not registered", instance.Id)
+	}
+	entry, ok := value.(*Entry)
+	if !ok {
+		return perrors.New("[ServiceDiscovery] services value not entry")
+	}
+	data, err := json.Marshal(instance)
+
+	if err != nil {
+		return err
+	}
+
+	entry.Lock()
+	defer entry.Unlock()
+	entry.instance = instance
+	path := sd.pathForInstance(instance.Name, instance.Id)
+
+	_, err = sd.client.SetContent(path, data, -1)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// updateInternalService update service in cache
+func (sd *ServiceDiscovery) updateInternalService(name, id string) {
+	value, ok := sd.services.Load(id)
+	if !ok {
+		return
+	}
+	entry, ok := value.(*Entry)
+	if !ok {
+		return
+	}
+	entry.Lock()
+	defer entry.Unlock()
+	instance, err := sd.QueryForInstance(name, id)
+	if err != nil {
+		logger.Infof("[zkServiceDiscovery] UpdateInternalService{%s} error = err{%v}", id, err)
+		return
+	}
+	entry.instance = instance
+	return
+}
+
+// UnregisterService un-register service in zookeeper and delete service in cache
+func (sd *ServiceDiscovery) UnregisterService(instance *ServiceInstance) error {
+	_, ok := sd.services.Load(instance.Id)
+	if !ok {
+		return nil
+	}
+	sd.services.Delete(instance.Id)
+	return sd.unregisterService(instance)
+}
+
+// unregisterService un-register service in zookeeper
+func (sd *ServiceDiscovery) unregisterService(instance *ServiceInstance) error {
+	path := sd.pathForInstance(instance.Name, instance.Id)
+	return sd.client.Delete(path)
+}
+
+// ReRegisterServices re-register all cache services to zookeeper
+func (sd *ServiceDiscovery) ReRegisterServices() {
+	sd.services.Range(func(key, value interface{}) bool {
+		entry, ok := value.(*Entry)
+		if !ok {
+			return true
+		}
+		entry.Lock()
+		defer entry.Unlock()
+		instance := entry.instance
+		err := sd.registerService(instance)
+		if err != nil {
+			logger.Errorf("[zkServiceDiscovery] registerService{%s} error = err{%v}", instance.Id, perrors.WithStack(err))
+			return true
+		}
+		sd.ListenServiceInstanceEvent(instance.Name, instance.Id, sd)
+		return true
+	})
+}
+
+// QueryForInstances query instances in zookeeper by name
+func (sd *ServiceDiscovery) QueryForInstances(name string) ([]*ServiceInstance, error) {
+	ids, err := sd.client.GetChildren(sd.pathForName(name))
+	if err != nil {
+		return nil, err
+	}
+	var (
+		instance  *ServiceInstance
+		instances []*ServiceInstance
+	)
+	for _, id := range ids {
+		instance, err = sd.QueryForInstance(name, id)
+		if err != nil {
+			return nil, err
+		}
+		instances = append(instances, instance)
+	}
+	return instances, nil
+}
+
+// QueryForInstance query instances in zookeeper by name and id
+func (sd *ServiceDiscovery) QueryForInstance(name string, id string) (*ServiceInstance, error) {
+	path := sd.pathForInstance(name, id)
+	data, _, err := sd.client.GetContent(path)
+	if err != nil {
+		return nil, err
+	}
+	instance := &ServiceInstance{}
+	err = json.Unmarshal(data, instance)
+	if err != nil {
+		return nil, err
+	}
+	return instance, nil
+}
+
+// QueryForInstance query all service name in zookeeper
+func (sd *ServiceDiscovery) QueryForNames() ([]string, error) {
+	return sd.client.GetChildren(sd.basePath)
+}
+
+// ListenServiceEvent add a listener in a service
+func (sd *ServiceDiscovery) ListenServiceEvent(name string, listener remoting.DataListener) {
+	sd.listener.ListenServiceEvent(nil, sd.pathForName(name), listener)
+}
+
+// ListenServiceEvent add a listener in a instance
+func (sd *ServiceDiscovery) ListenServiceInstanceEvent(name, id string, listener remoting.DataListener) {
+	sd.listener.ListenServiceNodeEvent(sd.pathForInstance(name, id), listener)
+}
+
+// DataChange implement DataListener's DataChange function
+func (sd *ServiceDiscovery) DataChange(eventType remoting.Event) bool {
+	path := eventType.Path
+	name, id, err := sd.getNameAndId(path)
+	if err != nil {
+		logger.Errorf("[ServiceDiscovery] data change error = {%v}", err)
+		return true
+	}
+	sd.updateInternalService(name, id)
+	return true
+}
+
+// getNameAndId get service name and instance id by path
+func (sd *ServiceDiscovery) getNameAndId(path string) (string, string, error) {
+	path = strings.TrimPrefix(path, sd.basePath)
+	path = strings.TrimPrefix(path, constant.PATH_SEPARATOR)
+	pathSlice := strings.Split(path, constant.PATH_SEPARATOR)
+	if len(pathSlice) < 2 {
+		return "", "", perrors.Errorf("[ServiceDiscovery] path{%s} dont contain name and id", path)
+	}
+	name := pathSlice[0]
+	id := pathSlice[1]
+	return name, id, nil
+}
+
+// nolint
+func (sd *ServiceDiscovery) pathForInstance(name, id string) string {
+	return path.Join(sd.basePath, name, id)
+}
+
+// nolint
+func (sd *ServiceDiscovery) pathForName(name string) string {
+	return path.Join(sd.basePath, name)
+}
diff --git a/remoting/zookeeper/curator_discovery/service_instance.go b/remoting/zookeeper/curator_discovery/service_instance.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8d2bc723e0e0dd90ffdaa6ccd7c9908d65ac9a0
--- /dev/null
+++ b/remoting/zookeeper/curator_discovery/service_instance.go
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package curator_discovery
+
+// ServiceInstance which define in curator-x-discovery, please refer to
+// https://github.com/apache/curator/blob/master/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java
+type ServiceInstance struct {
+	Name                string
+	Id                  string
+	Address             string
+	Port                int
+	Payload             interface{}
+	RegistrationTimeUTC int64
+}
diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go
index f9b9332504f445724a54b94356771e4ad49b62f0..d5d9e6e74858e3ec520aedee5b8ba059baf928d8 100644
--- a/remoting/zookeeper/facade.go
+++ b/remoting/zookeeper/facade.go
@@ -30,18 +30,18 @@ import (
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-type zkClientFacade interface {
+type ZkClientFacade interface {
 	ZkClient() *ZookeeperClient
 	SetZkClient(*ZookeeperClient)
 	ZkClientLock() *sync.Mutex
-	WaitGroup() *sync.WaitGroup //for wait group control, zk client listener & zk client container
-	Done() chan struct{}        //for zk client control
+	WaitGroup() *sync.WaitGroup // for wait group control, zk client listener & zk client container
+	Done() chan struct{}        // for zk client control
 	RestartCallBack() bool
-	common.Node
+	GetUrl() common.URL
 }
 
 // HandleClientRestart keeps the connection between client and server
-func HandleClientRestart(r zkClientFacade) {
+func HandleClientRestart(r ZkClientFacade) {
 	var (
 		err error
 
diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go
index 01d46da6cc1abae90210a323d32ac84bad80249b..1cd8f064bb15a2ac48b0d62154309b27c55ab946 100644
--- a/remoting/zookeeper/facade_test.go
+++ b/remoting/zookeeper/facade_test.go
@@ -38,7 +38,7 @@ type mockFacade struct {
 	done    chan struct{}
 }
 
-func newMockFacade(client *ZookeeperClient, url *common.URL) zkClientFacade {
+func newMockFacade(client *ZookeeperClient, url *common.URL) ZkClientFacade {
 	mock := &mockFacade{
 		client: client,
 		URL:    url,
diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go
index 948e0d73a579c82601eb759ea89953d1748bf55b..9a4874db24696d90e4fcc7d9d987f5888f1be599 100644
--- a/remoting/zookeeper/listener.go
+++ b/remoting/zookeeper/listener.go
@@ -58,8 +58,20 @@ func (l *ZkEventListener) SetClient(client *ZookeeperClient) {
 	l.client = client
 }
 
+// ListenServiceNodeEvent listen a path node event
+func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener remoting.DataListener) {
+	// listen l service node
+	l.wg.Add(1)
+	go func(zkPath string, listener remoting.DataListener) {
+		if l.listenServiceNodeEvent(zkPath, listener) {
+			listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel})
+		}
+		logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath)
+	}(zkPath, listener)
+}
+
 // nolint
-func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool {
+func (l *ZkEventListener) listenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool {
 	defer l.wg.Done()
 	var zkEvent zk.Event
 	for {
@@ -154,7 +166,7 @@ func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, li
 		l.wg.Add(1)
 		go func(node string, zkPath string, listener remoting.DataListener) {
 			logger.Infof("delete zkNode{%s}", node)
-			if l.ListenServiceNodeEvent(node, listener) {
+			if l.listenServiceNodeEvent(node, listener) {
 				logger.Infof("delete content{%s}", node)
 				listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel})
 			}
@@ -240,10 +252,10 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen
 				}
 			}
 
-			//listen l service node
+			// listen l service node
 			dubboPath := path.Join(zkPath, c)
 
-			//Save the path to avoid listen repeatedly
+			// Save the path to avoid listen repeatedly
 			l.pathMapLock.Lock()
 			_, ok := l.pathMap[dubboPath]
 			l.pathMapLock.Unlock()
@@ -255,7 +267,7 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen
 			l.pathMapLock.Lock()
 			l.pathMap[dubboPath] = struct{}{}
 			l.pathMapLock.Unlock()
-			//When Zk disconnected, the Conn will be set to nil, so here need check the value of Conn
+			// When Zk disconnected, the Conn will be set to nil, so here need check the value of Conn
 			l.client.RLock()
 			if l.client.Conn == nil {
 				l.client.RUnlock()
@@ -273,14 +285,14 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen
 			logger.Infof("listen dubbo service key{%s}", dubboPath)
 			l.wg.Add(1)
 			go func(zkPath string, listener remoting.DataListener) {
-				if l.ListenServiceNodeEvent(zkPath) {
+				if l.listenServiceNodeEvent(zkPath) {
 					listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel})
 				}
 				logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath)
 			}(dubboPath, listener)
 
-			//listen sub path recursive
-			//if zkPath is end of "providers/ & consumers/" we do not listen children dir
+			// listen sub path recursive
+			// if zkPath is end of "providers/ & consumers/" we do not listen children dir
 			if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) == -1 &&
 				strings.LastIndex(zkPath, constant.CONSUMER_CATEGORY) == -1 {
 				l.wg.Add(1)
@@ -310,9 +322,9 @@ func timeSecondDuration(sec int) time.Duration {
 }
 
 // ListenServiceEvent is invoked by ZkConsumerRegistry::Register/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener
-// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent
+// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent
 //                            |
-//                            --------> ListenServiceNodeEvent
+//                            --------> listenServiceNodeEvent
 func (l *ZkEventListener) ListenServiceEvent(conf *common.URL, zkPath string, listener remoting.DataListener) {
 	logger.Infof("listen dubbo path{%s}", zkPath)
 	l.wg.Add(1)
@@ -326,7 +338,8 @@ func (l *ZkEventListener) valid() bool {
 	return l.client.ZkConnValid()
 }
 
-// nolint
+// Close will let client listen exit
 func (l *ZkEventListener) Close() {
+	close(l.client.exit)
 	l.wg.Wait()
 }