diff --git a/.gitignore b/.gitignore
index 568e9f24541dd6f02dd8670436fd48db481b7f21..8158b497e3620a15fc5a52e91020e1fcebd5be41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,15 +20,19 @@ 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*
+metadata/report/consul/agent*
+remoting/consul/agent*
 config_center/apollo/mockDubbog.properties.json
+
+# vim stuff
+*~
+.*.sw?
diff --git a/CHANGE.md b/CHANGE.md
index 00b074d284971d779c84792951262879098fc18b..4a75ad3519fce9e8fe52fabc516d4a66c6f7b135 100644
--- a/CHANGE.md
+++ b/CHANGE.md
@@ -1,6 +1,56 @@
 # 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)
+- [Ftr: using different labels btw provider and consumer, k8s service discovery across namespaces](https://github.com/apache/dubbo-go/pull/577 )
+
+### 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/NOTICE b/NOTICE
index d7aa899d1cef0fba67826bebd0d587e9cc17ba5d..1120c200c997fe6befbe3f78d95e9bdb8a05a487 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,4 +1,4 @@
-Apache Dubbo Go
+Apache Dubbo-go
 Copyright 2018-2020 The Apache Software Foundation
 
 This product includes software developed at
diff --git a/README.md b/README.md
index 3f8394536f944518f8d969289147272c32f169da..2f40eaa3a65e9e64c751ba77b981bdade081c321 100644
--- a/README.md
+++ b/README.md
@@ -169,12 +169,40 @@ If you are willing to do some code contributions and document contributions to [
 
 Benchmark project [dubbo-go-benchmark](https://github.com/dubbogo/dubbo-go-benchmark).
 
-About dubbo-go benchmarking report, please refer to [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc).
+About dubbo-go benchmarking report, please refer to [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-jsonrpc).
 
 ## [User List](https://github.com/apache/dubbo-go/issues/2)
 
 If you are using [apache/dubbo-go](github.com/apache/dubbo-go) and think that it helps you or want do some contributions to it, please add your company to to [the user list](https://github.com/apache/dubbo-go/issues/2) to let us know your needs.
 
-![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)
-![Excellent Health Technology Group](https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png)
-![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png)
+
+<div>
+<table>
+  <tbody>
+  <tr></tr>
+    <tr>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://pic.c-ctrip.com/common/c_logo2013.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://mosn.io/images/community/tuya.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="https://github.com/mosn" target="_blank">
+          <img width="222px"  src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png">
+        </a>
+      </td>
+    </tr>
+    <tr></tr>
+  </tbody>
+</table>
+</div>
diff --git a/README_CN.md b/README_CN.md
index 582c5cf04cba08d4167c87b40fd0e86a3aa2ceb0..94630da6c3b2f5e4c0c7e0fe39da4a3b4e780ba4 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -168,12 +168,39 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic
 
 鎬ц兘娴嬭瘯椤圭洰鏄� [dubbo-go-benchmark](https://github.com/dubbogo/dubbo-go-benchmark)銆�
 
-鍏充簬 dubbo-go 鎬ц兘娴嬭瘯鎶ュ憡锛岃闃呰 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc)銆�
+鍏充簬 dubbo-go 鎬ц兘娴嬭瘯鎶ュ憡锛岃闃呰 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-jsonrpc)銆�
 
 ## [User List](https://github.com/apache/dubbo-go/issues/2)
 
 鑻ヤ綘姝e湪浣跨敤 [apache/dubbo-go](github.com/apache/dubbo-go) 涓旇涓哄叾鏈夌敤鎴栬€呭悜瀵瑰叾鍋氭敼杩涳紝璇峰繚鍒楄吹鍙镐俊鎭簬 [鐢ㄦ埛鍒楄〃](https://github.com/apache/dubbo-go/issues/2)锛屼互渚挎垜浠煡鏅撲箣銆�
 
-![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)
-![Excellent Health Technology Group](https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png)
-![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png)
+<div>
+<table>
+  <tbody>
+  <tr></tr>
+    <tr>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://pic.c-ctrip.com/common/c_logo2013.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="" target="_blank">
+          <img width="222px"  src="https://mosn.io/images/community/tuya.png">
+        </a>
+      </td>
+      <td align="center"  valign="middle">
+        <a href="https://github.com/mosn" target="_blank">
+          <img width="222px"  src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png">
+        </a>
+      </td>
+    </tr>
+    <tr></tr>
+  </tbody>
+</table>
+</div>
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/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go
index b70a97fad2de1b267ac1c6a5f0672ff445fadcc3..ebd5767e4c320f10c8911cf9ac3f2c81deaafb0e 100644
--- a/cluster/cluster_impl/available_cluster.go
+++ b/cluster/cluster_impl/available_cluster.go
@@ -38,6 +38,7 @@ func NewAvailableCluster() cluster.Cluster {
 	return &availableCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluser *availableCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return NewAvailableClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go
index 695ffcddbbce5a1c65f806b4561670d726588aaa..8121e5c0eab16b92b323fbc0e6e944231d1ed1b9 100644
--- a/cluster/cluster_impl/base_cluster_invoker_test.go
+++ b/cluster/cluster_impl/base_cluster_invoker_test.go
@@ -33,10 +33,15 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
+const (
+	baseClusterInvokerMethodName = "getUser"
+	baseClusterInvokerFormat     = "dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider"
+)
+
 func TestStickyNormal(t *testing.T) {
 	invokers := []protocol.Invoker{}
 	for i := 0; i < 10; i++ {
-		url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i))
+		url, _ := common.NewURL(fmt.Sprintf(baseClusterInvokerFormat, i))
 		url.SetParam("sticky", "true")
 		invokers = append(invokers, NewMockInvoker(url, 1))
 	}
@@ -45,7 +50,7 @@ func TestStickyNormal(t *testing.T) {
 	invoked := []protocol.Invoker{}
 
 	tmpRandomBalance := loadbalance.NewRandomLoadBalance()
-	tmpInvocation := invocation.NewRPCInvocation("getUser", nil, nil)
+	tmpInvocation := invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil)
 	result := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked)
 	result1 := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked)
 	assert.Equal(t, result, result1)
@@ -54,7 +59,7 @@ func TestStickyNormal(t *testing.T) {
 func TestStickyNormalWhenError(t *testing.T) {
 	invokers := []protocol.Invoker{}
 	for i := 0; i < 10; i++ {
-		url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i))
+		url, _ := common.NewURL(fmt.Sprintf(baseClusterInvokerFormat, i))
 		url.SetParam("sticky", "true")
 		invokers = append(invokers, NewMockInvoker(url, 1))
 	}
@@ -62,8 +67,8 @@ func TestStickyNormalWhenError(t *testing.T) {
 	base.availablecheck = true
 
 	invoked := []protocol.Invoker{}
-	result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked)
+	result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil), invokers, invoked)
 	invoked = append(invoked, result)
-	result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked)
+	result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil), invokers, invoked)
 	assert.NotEqual(t, result, result1)
 }
diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go
index ba454af6a8553f31b72b1d30ef5f44ec7a8278d2..ea3dee921888baa8f997cd02e92ee582f09a37d5 100644
--- a/cluster/cluster_impl/broadcast_cluster.go
+++ b/cluster/cluster_impl/broadcast_cluster.go
@@ -39,6 +39,7 @@ func NewBroadcastCluster() cluster.Cluster {
 	return &broadcastCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *broadcastCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newBroadcastClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/broadcast_cluster_invoker.go b/cluster/cluster_impl/broadcast_cluster_invoker.go
index 3a97d3d9b499c011ac90cb88b6692565388411a7..b117dbb246bec6fed6ced4fb4abcdeb4a4b7cee5 100644
--- a/cluster/cluster_impl/broadcast_cluster_invoker.go
+++ b/cluster/cluster_impl/broadcast_cluster_invoker.go
@@ -36,6 +36,7 @@ func newBroadcastClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	}
 }
 
+// nolint
 func (invoker *broadcastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	invokers := invoker.directory.List(invocation)
 	err := invoker.checkInvokers(invokers, invocation)
diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go
index 432e33122c2ee599bc848ca9ab1842084da5ef68..278ac5432e7db5d8379e4aba81065ff2ab704ff5 100644
--- a/cluster/cluster_impl/failback_cluster.go
+++ b/cluster/cluster_impl/failback_cluster.go
@@ -39,6 +39,7 @@ func NewFailbackCluster() cluster.Cluster {
 	return &failbackCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *failbackCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newFailbackClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go
index 46b0ff634e56c45223a5aeb5566b9b1401518960..62f48045ec6edbc19d6603509fa1ae8c2d4ce9ee 100644
--- a/cluster/cluster_impl/failback_cluster_invoker.go
+++ b/cluster/cluster_impl/failback_cluster_invoker.go
@@ -72,6 +72,19 @@ func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	return invoker
 }
 
+func (invoker *failbackClusterInvoker) tryTimerTaskProc(ctx context.Context, retryTask *retryTimerTask) {
+	invoked := make([]protocol.Invoker, 0)
+	invoked = append(invoked, retryTask.lastInvoker)
+
+	retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked)
+	var result protocol.Result
+	result = retryInvoker.Invoke(ctx, retryTask.invocation)
+	if result.Error() != nil {
+		retryTask.lastInvoker = retryInvoker
+		invoker.checkRetry(retryTask, result.Error())
+	}
+}
+
 func (invoker *failbackClusterInvoker) process(ctx context.Context) {
 	invoker.ticker = time.NewTicker(time.Second * 1)
 	for range invoker.ticker.C {
@@ -91,25 +104,11 @@ func (invoker *failbackClusterInvoker) process(ctx context.Context) {
 			}
 
 			// ignore return. the get must success.
-			_, err = invoker.taskList.Get(1)
-			if err != nil {
+			if _, err = invoker.taskList.Get(1); err != nil {
 				logger.Warnf("get task found err: %v\n", err)
 				break
 			}
-
-			go func(retryTask *retryTimerTask) {
-				invoked := make([]protocol.Invoker, 0)
-				invoked = append(invoked, retryTask.lastInvoker)
-
-				retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked)
-				var result protocol.Result
-				result = retryInvoker.Invoke(ctx, retryTask.invocation)
-				if result.Error() != nil {
-					retryTask.lastInvoker = retryInvoker
-					invoker.checkRetry(retryTask, result.Error())
-				}
-			}(retryTask)
-
+			go invoker.tryTimerTaskProc(ctx, retryTask)
 		}
 	}
 }
@@ -127,31 +126,29 @@ func (invoker *failbackClusterInvoker) checkRetry(retryTask *retryTimerTask, err
 	}
 }
 
+// nolint
 func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	invokers := invoker.directory.List(invocation)
-	err := invoker.checkInvokers(invokers, invocation)
-	if err != nil {
+	if err := invoker.checkInvokers(invokers, invocation); err != nil {
 		logger.Errorf("Failed to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n",
 			invocation.MethodName(), invoker.GetUrl().Service(), err)
 		return &protocol.RPCResult{}
 	}
-	url := invokers[0].GetUrl()
-	methodName := invocation.MethodName()
+
 	//Get the service loadbalance config
+	url := invokers[0].GetUrl()
 	lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE)
-
 	//Get the service method loadbalance config if have
+	methodName := invocation.MethodName()
 	if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); v != "" {
 		lb = v
 	}
-	loadbalance := extension.GetLoadbalance(lb)
 
+	loadBalance := extension.GetLoadbalance(lb)
 	invoked := make([]protocol.Invoker, 0, len(invokers))
-	var result protocol.Result
-
-	ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked)
+	ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked)
 	//DO INVOKE
-	result = ivk.Invoke(ctx, invocation)
+	result := ivk.Invoke(ctx, invocation)
 	if result.Error() != nil {
 		invoker.once.Do(func() {
 			invoker.taskList = queue.New(invoker.failbackTasks)
@@ -164,7 +161,7 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr
 			return &protocol.RPCResult{}
 		}
 
-		timerTask := newRetryTimerTask(loadbalance, invocation, invokers, ivk)
+		timerTask := newRetryTimerTask(loadBalance, invocation, invokers, ivk)
 		invoker.taskList.Put(timerTask)
 
 		logger.Errorf("Failback to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n",
@@ -172,7 +169,6 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr
 		// ignore
 		return &protocol.RPCResult{}
 	}
-
 	return result
 }
 
diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go
index ac9ec6b821c1d0333c73fae56169d5bc8256ec5b..a5ea7a05855e58b9db02f41fcfba83b52d35d8b0 100644
--- a/cluster/cluster_impl/failfast_cluster.go
+++ b/cluster/cluster_impl/failfast_cluster.go
@@ -39,6 +39,7 @@ func NewFailFastCluster() cluster.Cluster {
 	return &failfastCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *failfastCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newFailFastClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/failfast_cluster_invoker.go b/cluster/cluster_impl/failfast_cluster_invoker.go
index 3b4dc9b721720948cf635f57191d1e6bce023a1e..d71ef5f5a1dfed2d29f6ae4f29fb08d34aae9c5d 100644
--- a/cluster/cluster_impl/failfast_cluster_invoker.go
+++ b/cluster/cluster_impl/failfast_cluster_invoker.go
@@ -35,6 +35,7 @@ func newFailFastClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	}
 }
 
+// nolint
 func (invoker *failfastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	invokers := invoker.directory.List(invocation)
 	err := invoker.checkInvokers(invokers, invocation)
diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go
index d30a743e034dafabad87381cdaa356e7603b74d1..254cc097e3e7f0cc70dfcff01212562bc6febe97 100644
--- a/cluster/cluster_impl/failover_cluster.go
+++ b/cluster/cluster_impl/failover_cluster.go
@@ -40,6 +40,7 @@ func NewFailoverCluster() cluster.Cluster {
 	return &failoverCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newFailoverClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go
index 66adabd1043d6e5d770704774dda22ba9e6faebe..4260a9324dd360ac24f80e425d8542a423c8815e 100644
--- a/cluster/cluster_impl/failover_cluster_invoker.go
+++ b/cluster/cluster_impl/failover_cluster_invoker.go
@@ -19,6 +19,7 @@ package cluster_impl
 
 import (
 	"context"
+	"fmt"
 	"strconv"
 )
 
@@ -44,6 +45,7 @@ func newFailoverClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	}
 }
 
+// nolint
 func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	var (
 		result    protocol.Result
@@ -91,8 +93,10 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr
 	invokerSvc := invoker.GetUrl().Service()
 	invokerUrl := invoker.directory.GetUrl()
 	return &protocol.RPCResult{
-		Err: perrors.Errorf("Failed to invoke the method %v in the service %v. Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. Last error is %v.",
-			methodName, invokerSvc, retries, providers, len(providers), len(invokers), invokerUrl, ip, constant.Version, result.Error().Error(),
+		Err: perrors.Wrap(result.Error(), fmt.Sprintf("Failed to invoke the method %v in the service %v. "+
+			"Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. "+
+			"Last error is %+v.", methodName, invokerSvc, retries, providers, len(providers), len(invokers),
+			invokerUrl, ip, constant.Version, result.Error().Error()),
 		)}
 }
 
diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go
index e05b79202cd202334db1c19421e3163ee28bac26..d3ac2c8a5ffd7fce647649c53fe343ba93999636 100644
--- a/cluster/cluster_impl/failover_cluster_test.go
+++ b/cluster/cluster_impl/failover_cluster_test.go
@@ -43,6 +43,7 @@ import (
 // mock invoker
 // ///////////////////////////
 
+// nolint
 type MockInvoker struct {
 	url       common.URL
 	available bool
@@ -51,6 +52,7 @@ type MockInvoker struct {
 	successCount int
 }
 
+// nolint
 func NewMockInvoker(url common.URL, successCount int) *MockInvoker {
 	return &MockInvoker{
 		url:          url,
@@ -60,23 +62,28 @@ func NewMockInvoker(url common.URL, successCount int) *MockInvoker {
 	}
 }
 
+// nolint
 func (bi *MockInvoker) GetUrl() common.URL {
 	return bi.url
 }
 
+// nolint
 func (bi *MockInvoker) IsAvailable() bool {
 	return bi.available
 }
 
+// nolint
 func (bi *MockInvoker) IsDestroyed() bool {
 	return bi.destroyed
 }
 
+// nolint
 type rest struct {
 	tried   int
 	success bool
 }
 
+// nolint
 func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) protocol.Result {
 	count++
 	var (
@@ -93,14 +100,17 @@ func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation)
 	return result
 }
 
+// nolint
 func (bi *MockInvoker) Destroy() {
 	logger.Infof("Destroy invoker: %v", bi.GetUrl().String())
 	bi.destroyed = true
 	bi.available = false
 }
 
+// nolint
 var count int
 
+// nolint
 func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failoverCluster := NewFailoverCluster()
@@ -119,6 +129,7 @@ func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocat
 	return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 }
 
+// nolint
 func TestFailoverInvokeSuccess(t *testing.T) {
 	urlParams := url.Values{}
 	result := normalInvoke(3, urlParams)
@@ -126,6 +137,7 @@ func TestFailoverInvokeSuccess(t *testing.T) {
 	count = 0
 }
 
+// nolint
 func TestFailoverInvokeFail(t *testing.T) {
 	urlParams := url.Values{}
 	result := normalInvoke(4, urlParams)
@@ -133,6 +145,7 @@ func TestFailoverInvokeFail(t *testing.T) {
 	count = 0
 }
 
+// nolint
 func TestFailoverInvoke1(t *testing.T) {
 	urlParams := url.Values{}
 	urlParams.Set(constant.RETRIES_KEY, "3")
@@ -141,6 +154,7 @@ func TestFailoverInvoke1(t *testing.T) {
 	count = 0
 }
 
+// nolint
 func TestFailoverInvoke2(t *testing.T) {
 	urlParams := url.Values{}
 	urlParams.Set(constant.RETRIES_KEY, "2")
@@ -152,6 +166,7 @@ func TestFailoverInvoke2(t *testing.T) {
 	count = 0
 }
 
+// nolint
 func TestFailoverDestroy(t *testing.T) {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failoverCluster := NewFailoverCluster()
diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go
index f708b7fb9108bdd17fec5dc68dc1e4249c8199d4..d9465c034cb9e76d2f360b395f17b23d9234c532 100644
--- a/cluster/cluster_impl/failsafe_cluster.go
+++ b/cluster/cluster_impl/failsafe_cluster.go
@@ -39,6 +39,7 @@ func NewFailsafeCluster() cluster.Cluster {
 	return &failsafeCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *failsafeCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newFailsafeClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/failsafe_cluster_invoker.go b/cluster/cluster_impl/failsafe_cluster_invoker.go
index 4d8fe27719eb71fa287fe4142d8e92ca17acfba4..27c59fff18d4a93fc693d7edbca467e0ffef549a 100644
--- a/cluster/cluster_impl/failsafe_cluster_invoker.go
+++ b/cluster/cluster_impl/failsafe_cluster_invoker.go
@@ -45,6 +45,7 @@ func newFailsafeClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	}
 }
 
+// nolint
 func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	invokers := invoker.directory.List(invocation)
 
diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go
index 0e6cd26882788a1f897d0d4dc8e0d4eb0a9d4218..8c9911327527e66864200630f5a52dd5c7b9be53 100644
--- a/cluster/cluster_impl/forking_cluster.go
+++ b/cluster/cluster_impl/forking_cluster.go
@@ -39,6 +39,7 @@ func NewForkingCluster() cluster.Cluster {
 	return &forkingCluster{}
 }
 
+// Join returns a baseClusterInvoker instance
 func (cluster *forkingCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newForkingClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go
index a5a3f2ec6605dfb843fab09dff0a53000bbc3298..168444881653ca38ef61a9bc8e50f2d4bc3e624c 100644
--- a/cluster/cluster_impl/forking_cluster_invoker.go
+++ b/cluster/cluster_impl/forking_cluster_invoker.go
@@ -44,7 +44,7 @@ func newForkingClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	}
 }
 
-// Invoke ...
+// nolint
 func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	if err := invoker.checkWhetherDestroyed(); err != nil {
 		return &protocol.RPCResult{Err: err}
diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go
index d887cfb45b9c92c859b1396046c1c1c73d46b295..a7fa5d4f02087d4ec8ee0e011c09b11805f79a3f 100644
--- a/cluster/cluster_impl/mock_cluster.go
+++ b/cluster/cluster_impl/mock_cluster.go
@@ -33,6 +33,7 @@ func NewMockCluster() cluster.Cluster {
 	return &mockCluster{}
 }
 
+// nolint
 func (cluster *mockCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return protocol.NewBaseInvoker(directory.GetUrl())
 }
diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go
index fcefa52862a39eece98dca8660e62d9ca144e955..f4c08973713e6b3736b4ca1ed0d6e5275cf61d01 100644
--- a/cluster/cluster_impl/registry_aware_cluster.go
+++ b/cluster/cluster_impl/registry_aware_cluster.go
@@ -34,6 +34,7 @@ func NewRegistryAwareCluster() cluster.Cluster {
 	return &registryAwareCluster{}
 }
 
+// nolint
 func (cluster *registryAwareCluster) Join(directory cluster.Directory) protocol.Invoker {
 	return newRegistryAwareClusterInvoker(directory)
 }
diff --git a/cluster/cluster_impl/registry_aware_cluster_invoker.go b/cluster/cluster_impl/registry_aware_cluster_invoker.go
index cded5bf16432e6b0c590e15b81c28369889a5f88..7840da5218090a995298e718a4bbce9d7f5480b4 100644
--- a/cluster/cluster_impl/registry_aware_cluster_invoker.go
+++ b/cluster/cluster_impl/registry_aware_cluster_invoker.go
@@ -36,6 +36,7 @@ func newRegistryAwareClusterInvoker(directory cluster.Directory) protocol.Invoke
 	}
 }
 
+// nolint
 func (invoker *registryAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	invokers := invoker.directory.List(invocation)
 	//First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'default' key.
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/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go
index 84fbb268c7a8ec32f007a734e2d6da56ef3c6d25..85eb96417c5c1f1946962a87f6b889c2ed09c26c 100644
--- a/cluster/loadbalance/consistent_hash.go
+++ b/cluster/loadbalance/consistent_hash.go
@@ -38,9 +38,9 @@ import (
 )
 
 const (
-	// ConsistentHash ...
+	// ConsistentHash consistent hash
 	ConsistentHash = "consistenthash"
-	// HashNodes ...
+	// HashNodes hash nodes
 	HashNodes = "hash.nodes"
 	// HashArguments  key of hash arguments  in url
 	HashArguments = "hash.arguments"
@@ -157,6 +157,7 @@ func (c *ConsistentHashSelector) selectForKey(hash uint32) protocol.Invoker {
 	return c.virtualInvokers[c.keys[idx]]
 }
 
+// nolint
 func (c *ConsistentHashSelector) hash(digest [16]byte, i int) uint32 {
 	return uint32((digest[3+i*4]&0xFF)<<24) | uint32((digest[2+i*4]&0xFF)<<16) |
 		uint32((digest[1+i*4]&0xFF)<<8) | uint32(digest[i*4]&0xFF)&0xFFFFFFF
diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go
index a44293172c6e2c96bd098a19306f69260b713689..9f22d39dc46243dddda89151e07dbea39ab933fb 100644
--- a/cluster/loadbalance/consistent_hash_test.go
+++ b/cluster/loadbalance/consistent_hash_test.go
@@ -18,6 +18,7 @@
 package loadbalance
 
 import (
+	"fmt"
 	"testing"
 )
 
@@ -32,6 +33,19 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
+const (
+	ip       = "192.168.1.0"
+	port8080 = 8080
+	port8082 = 8082
+
+	url8080Short = "dubbo://192.168.1.0:8080"
+	url8081Short = "dubbo://192.168.1.0:8081"
+	url20000     = "dubbo://192.168.1.0:20000/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1"
+	url8080      = "dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1"
+	url8081      = "dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1"
+	url8082      = "dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1"
+)
+
 func TestConsistentHashSelectorSuite(t *testing.T) {
 	suite.Run(t, new(consistentHashSelectorSuite))
 }
@@ -43,8 +57,7 @@ type consistentHashSelectorSuite struct {
 
 func (s *consistentHashSelectorSuite) SetupTest() {
 	var invokers []protocol.Invoker
-	url, _ := common.NewURL(
-		"dubbo://192.168.1.0:20000/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1")
+	url, _ := common.NewURL(url20000)
 	invokers = append(invokers, protocol.NewBaseInvoker(url))
 	s.selector = newConsistentHashSelector(invokers, "echo", 999944)
 }
@@ -55,14 +68,14 @@ func (s *consistentHashSelectorSuite) TestToKey() {
 }
 
 func (s *consistentHashSelectorSuite) TestSelectForKey() {
-	url1, _ := common.NewURL("dubbo://192.168.1.0:8080")
-	url2, _ := common.NewURL("dubbo://192.168.1.0:8081")
+	url1, _ := common.NewURL(url8080Short)
+	url2, _ := common.NewURL(url8081Short)
 	s.selector.virtualInvokers = make(map[uint32]protocol.Invoker)
 	s.selector.virtualInvokers[99874] = protocol.NewBaseInvoker(url1)
 	s.selector.virtualInvokers[9999945] = protocol.NewBaseInvoker(url2)
 	s.selector.keys = []uint32{99874, 9999945}
 	result := s.selector.selectForKey(9999944)
-	s.Equal(result.GetUrl().String(), "dubbo://192.168.1.0:8081?")
+	s.Equal(result.GetUrl().String(), url8081Short+"?")
 }
 
 func TestConsistentHashLoadBalanceSuite(t *testing.T) {
@@ -83,11 +96,11 @@ type consistentHashLoadBalanceSuite struct {
 
 func (s *consistentHashLoadBalanceSuite) SetupTest() {
 	var err error
-	s.url1, err = common.NewURL("dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1")
+	s.url1, err = common.NewURL(url8080)
 	s.NoError(err)
-	s.url2, err = common.NewURL("dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1")
+	s.url2, err = common.NewURL(url8081)
 	s.NoError(err)
-	s.url3, err = common.NewURL("dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1")
+	s.url3, err = common.NewURL(url8082)
 	s.NoError(err)
 
 	s.invoker1 = protocol.NewBaseInvoker(s.url1)
@@ -101,9 +114,9 @@ func (s *consistentHashLoadBalanceSuite) SetupTest() {
 func (s *consistentHashLoadBalanceSuite) TestSelect() {
 	args := []interface{}{"name", "password", "age"}
 	invoker := s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil))
-	s.Equal(invoker.GetUrl().Location, "192.168.1.0:8080")
+	s.Equal(invoker.GetUrl().Location, fmt.Sprintf("%s:%d", ip, port8080))
 
 	args = []interface{}{"ok", "abc"}
 	invoker = s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil))
-	s.Equal(invoker.GetUrl().Location, "192.168.1.0:8082")
+	s.Equal(invoker.GetUrl().Location, fmt.Sprintf("%s:%d", ip, port8082))
 }
diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go
index 37ad91c3ed6b44370820a989b7af8ccaa82c48a2..87767359a94c29c9aa83ae1fa5b042d7bac2548b 100644
--- a/cluster/loadbalance/least_active.go
+++ b/cluster/loadbalance/least_active.go
@@ -46,6 +46,7 @@ func NewLeastActiveLoadBalance() cluster.LoadBalance {
 	return &leastActiveLoadBalance{}
 }
 
+// Select gets invoker based on least active load balancing strategy
 func (lb *leastActiveLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker {
 	count := len(invokers)
 	if count == 0 {
diff --git a/cluster/loadbalance/random_test.go b/cluster/loadbalance/random_test.go
index 88392de52c93579dd4def3da2d60b415b601b21e..b94d7da43d5bd42e6798fca750c8616830a8df8f 100644
--- a/cluster/loadbalance/random_test.go
+++ b/cluster/loadbalance/random_test.go
@@ -36,18 +36,24 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
+const (
+	tmpUrl       = "dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"
+	tmpUrlFormat = "dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider"
+	tmpIp        = "192.168.1.100"
+)
+
 func TestRandomlbSelect(t *testing.T) {
 	randomlb := NewRandomLoadBalance()
 
 	invokers := []protocol.Invoker{}
 
-	url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", 0))
+	url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, 0))
 	invokers = append(invokers, protocol.NewBaseInvoker(url))
 	i := randomlb.Select(invokers, &invocation.RPCInvocation{})
 	assert.True(t, i.GetUrl().URLEqual(url))
 
 	for i := 1; i < 10; i++ {
-		url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i))
+		url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i))
 		invokers = append(invokers, protocol.NewBaseInvoker(url))
 	}
 	randomlb.Select(invokers, &invocation.RPCInvocation{})
@@ -58,13 +64,13 @@ func TestRandomlbSelectWeight(t *testing.T) {
 
 	invokers := []protocol.Invoker{}
 	for i := 0; i < 10; i++ {
-		url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i))
+		url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i))
 		invokers = append(invokers, protocol.NewBaseInvoker(url))
 	}
 
 	urlParams := url.Values{}
 	urlParams.Set("methods.test."+constant.WEIGHT_KEY, "10000000000000")
-	urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams))
+	urll, _ := common.NewURL(tmpUrl, common.WithParams(urlParams))
 	invokers = append(invokers, protocol.NewBaseInvoker(urll))
 	ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test"))
 
@@ -72,7 +78,7 @@ func TestRandomlbSelectWeight(t *testing.T) {
 	var selected float64
 	for i := 0; i < 10000; i++ {
 		s := randomlb.Select(invokers, ivc)
-		if s.GetUrl().Ip == "192.168.1.100" {
+		if s.GetUrl().Ip == tmpIp {
 			selected++
 		}
 		selectedInvoker = append(selectedInvoker, s)
@@ -89,13 +95,13 @@ func TestRandomlbSelectWarmup(t *testing.T) {
 
 	invokers := []protocol.Invoker{}
 	for i := 0; i < 10; i++ {
-		url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i))
+		url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i))
 		invokers = append(invokers, protocol.NewBaseInvoker(url))
 	}
 
 	urlParams := url.Values{}
 	urlParams.Set(constant.REMOTE_TIMESTAMP_KEY, strconv.FormatInt(time.Now().Add(time.Minute*(-9)).Unix(), 10))
-	urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams))
+	urll, _ := common.NewURL(tmpUrl, common.WithParams(urlParams))
 	invokers = append(invokers, protocol.NewBaseInvoker(urll))
 	ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test"))
 
@@ -103,7 +109,7 @@ func TestRandomlbSelectWarmup(t *testing.T) {
 	var selected float64
 	for i := 0; i < 10000; i++ {
 		s := randomlb.Select(invokers, ivc)
-		if s.GetUrl().Ip == "192.168.1.100" {
+		if s.GetUrl().Ip == tmpIp {
 			selected++
 		}
 		selectedInvoker = append(selectedInvoker, s)
diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go
index c44b239dbcbcc744f47ca3c97128f92567e32a78..51a76da99818063b6f13de1c007ccbc3709e1701 100644
--- a/cluster/loadbalance/round_robin.go
+++ b/cluster/loadbalance/round_robin.go
@@ -31,12 +31,12 @@ import (
 )
 
 const (
-	// RoundRobin ...
+	// RoundRobin load balancing way
 	RoundRobin = "roundrobin"
 
-	// COMPLETE ...
+	// nolint
 	COMPLETE = 0
-	// UPDATING ...
+	// nolint
 	UPDATING = 1
 )
 
@@ -59,6 +59,7 @@ func NewRoundRobinLoadBalance() cluster.LoadBalance {
 	return &roundRobinLoadBalance{}
 }
 
+// Select gets invoker based on round robin load balancing strategy
 func (lb *roundRobinLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker {
 	count := len(invokers)
 	if count == 0 {
diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go
index b4e4e1c0bd6ec02fdc19ab0fa2de0f4de80457fd..c1f723525f5307e7732f0ea1ecc27eca7ba09c8d 100644
--- a/cluster/router/chain/chain_test.go
+++ b/cluster/router/chain/chain_test.go
@@ -20,7 +20,6 @@ package chain
 import (
 	"encoding/base64"
 	"fmt"
-	"strconv"
 	"testing"
 	"time"
 )
@@ -43,9 +42,22 @@ import (
 )
 
 const (
-	path     = "/dubbo/config/dubbo/test-condition.condition-router"
-	zkPrefix = "zookeeper://127.0.0.1:"
-	anyUrl   = "condition://0.0.0.0/com.foo.BarService"
+	localIP    = "127.0.0.1"
+	test1234IP = "1.2.3.4"
+	test1111IP = "1.1.1.1"
+	test0000IP = "0.0.0.0"
+	port20000  = 20000
+
+	path             = "/dubbo/config/dubbo/test-condition.condition-router"
+	zkFormat         = "zookeeper://%s:%d"
+	consumerFormat   = "consumer://%s/com.foo.BarService"
+	dubboForamt      = "dubbo://%s:%d/com.foo.BarService"
+	anyUrlFormat     = "condition://%s/com.foo.BarService"
+	zk               = "zookeeper"
+	applicationKey   = "test-condition"
+	applicationField = "application"
+	forceField       = "force"
+	forceValue       = "true"
 )
 
 func TestNewRouterChain(t *testing.T) {
@@ -66,14 +78,14 @@ conditions:
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
 	assert.Nil(t, err)
 	assert.NotNil(t, configuration)
 
-	chain, err := NewRouterChain(getRouteUrl("test-condition"))
+	chain, err := NewRouterChain(getRouteUrl(applicationKey))
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(chain.routers))
 	appRouter := chain.routers[0].(*condition.AppRouter)
@@ -116,15 +128,15 @@ conditions:
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
-	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	chain, err := NewRouterChain(getConditionRouteUrl(applicationKey))
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(chain.routers))
 
-	url := getConditionRouteUrl("test-condition")
+	url := getConditionRouteUrl(applicationKey)
 	assert.NotNil(t, url)
 	factory := extension.GetRouterFactory(url.Protocol)
 	r, err := factory.NewPriorityRouter(url)
@@ -142,22 +154,22 @@ func TestRouterChainRoute(t *testing.T) {
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
-	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	chain, err := NewRouterChain(getConditionRouteUrl(applicationKey))
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(chain.routers))
 
-	url := getConditionRouteUrl("test-condition")
+	url := getConditionRouteUrl(applicationKey)
 	assert.NotNil(t, url)
 
 	invokers := []protocol.Invoker{}
-	dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService"))
+	dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000))
 	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
 
-	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP))
 	inv := &invocation.RPCInvocation{}
 	finalInvokers := chain.Route(invokers, &targetURL, inv)
 
@@ -182,46 +194,46 @@ conditions:
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
-	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	chain, err := NewRouterChain(getConditionRouteUrl(applicationKey))
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(chain.routers))
 
 	invokers := []protocol.Invoker{}
-	dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService"))
+	dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000))
 	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
 
-	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP))
 	inv := &invocation.RPCInvocation{}
 	finalInvokers := chain.Route(invokers, &targetURL, inv)
 
 	assert.Equal(t, 0, len(finalInvokers))
 }
 
-func TestRouterChain_Route_NoRoute(t *testing.T) {
+func TestRouterChainRouteNoRoute(t *testing.T) {
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
-	chain, err := NewRouterChain(getConditionNoRouteUrl("test-condition"))
+	chain, err := NewRouterChain(getConditionNoRouteUrl(applicationKey))
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(chain.routers))
 
-	url := getConditionRouteUrl("test-condition")
+	url := getConditionRouteUrl(applicationKey)
 	assert.NotNil(t, url)
 
 	invokers := []protocol.Invoker{}
-	dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService"))
+	dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000))
 	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
 
-	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP))
 	inv := &invocation.RPCInvocation{}
 	finalInvokers := chain.Route(invokers, &targetURL, inv)
 
@@ -229,26 +241,26 @@ func TestRouterChain_Route_NoRoute(t *testing.T) {
 }
 
 func getConditionNoRouteUrl(applicationKey string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
-	url.AddParam("application", applicationKey)
-	url.AddParam("force", "true")
+	url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP))
+	url.AddParam(applicationField, applicationKey)
+	url.AddParam(forceField, forceValue)
 	rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host != 1.2.3.4"))
 	url.AddParam(constant.RULE_KEY, rule)
 	return &url
 }
 
 func getConditionRouteUrl(applicationKey string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
-	url.AddParam("application", applicationKey)
-	url.AddParam("force", "true")
+	url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP))
+	url.AddParam(applicationField, applicationKey)
+	url.AddParam(forceField, forceValue)
 	rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host = 1.2.3.4"))
 	url.AddParam(constant.RULE_KEY, rule)
 	return &url
 }
 
 func getRouteUrl(applicationKey string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
-	url.AddParam("application", applicationKey)
-	url.AddParam("force", "true")
+	url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP))
+	url.AddParam(applicationField, applicationKey)
+	url.AddParam(forceField, forceValue)
 	return &url
 }
diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go
index f37a483e8468bc57d3ce1e73172ccf9a05bc29f0..8b38f2dd6136b4d31f46e7214c0ad1359537b198 100644
--- a/cluster/router/condition/app_router_test.go
+++ b/cluster/router/condition/app_router_test.go
@@ -18,7 +18,7 @@
 package condition
 
 import (
-	"strconv"
+	"fmt"
 	"testing"
 	"time"
 )
@@ -31,6 +31,7 @@ import (
 import (
 	"github.com/apache/dubbo-go/common"
 	"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/config_center"
 	"github.com/apache/dubbo-go/remoting"
@@ -38,7 +39,15 @@ import (
 )
 
 const (
-	path = "/dubbo/config/dubbo/test-condition.condition-router"
+	routerPath    = "/dubbo/config/dubbo/test-condition.condition-router"
+	routerLocalIP = "127.0.0.1"
+	routerZk      = "zookeeper"
+	routerKey     = "test-condition"
+)
+
+var (
+	zkFormat        = "zookeeper://%s:%d"
+	conditionFormat = "condition://%s/com.foo.BarService"
 )
 
 func TestNewAppRouter(t *testing.T) {
@@ -51,22 +60,22 @@ conditions:
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create(path)
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set(path, []byte(testYML), 0)
+	_, err = z.Conn.Set(routerPath, []byte(testYML), 0)
 	assert.NoError(t, err)
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
 	assert.Nil(t, err)
 	assert.NotNil(t, configuration)
 
-	appRouteURL := getAppRouteURL("test-condition")
+	appRouteURL := getAppRouteURL(routerKey)
 	appRouter, err := NewAppRouter(appRouteURL)
 	assert.Nil(t, err)
 	assert.NotNil(t, appRouter)
@@ -97,22 +106,22 @@ conditions:
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create(path)
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set(path, []byte(testYML), 0)
+	_, err = z.Conn.Set(routerPath, []byte(testYML), 0)
 	assert.NoError(t, err)
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
 	assert.Nil(t, err)
 	assert.NotNil(t, configuration)
 
-	appRouteURL := getAppRouteURL("test-condition")
+	appRouteURL := getAppRouteURL(routerKey)
 	appRouter, err := NewAppRouter(appRouteURL)
 	assert.Nil(t, err)
 	assert.NotNil(t, appRouter)
@@ -134,22 +143,22 @@ conditions:
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create(path)
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set(path, []byte(testYML), 0)
+	_, err = z.Conn.Set(routerPath, []byte(testYML), 0)
 	assert.NoError(t, err)
 	defer ts.Stop()
 	defer z.Close()
 
-	zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port))
-	configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl)
+	zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port))
+	configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(configuration)
 
 	assert.Nil(t, err)
 	assert.NotNil(t, configuration)
 
-	appRouteURL := getAppRouteURL("test-condition")
+	appRouteURL := getAppRouteURL(routerKey)
 	appRouter, err := NewAppRouter(appRouteURL)
 	assert.Nil(t, err)
 	assert.NotNil(t, appRouter)
@@ -175,7 +184,7 @@ conditions:
 }
 
 func getAppRouteURL(applicationKey string) *common.URL {
-	url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService")
+	url, _ := common.NewURL(fmt.Sprintf(conditionFormat, constant.ANYHOST_VALUE))
 	url.AddParam("application", applicationKey)
 	url.AddParam("force", "true")
 	return &url
diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go
index d080a46cc736c71f22e9e3d7f8b236b4b5307362..0f61b39fc71af3aaeffc731974a0fa997503693e 100644
--- a/cluster/router/condition/factory_test.go
+++ b/cluster/router/condition/factory_test.go
@@ -33,12 +33,21 @@ import (
 
 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"
 )
 
-const anyUrl = "condition://0.0.0.0/com.foo.BarService"
+const (
+	factory1111Ip               = "1.1.1.1"
+	factoryUrlFormat            = "condition://%s/com.foo.BarService"
+	factoryDubboFormat          = "dubbo://%s:20880/com.foo.BarService"
+	factoryConsumerMethodFormat = "consumer://%s/com.foo.BarService?methods=getFoo"
+	factory333URL               = "dubbo://10.20.3.3:20880/com.foo.BarService"
+	factoryConsumerFormat       = "consumer://%s/com.foo.BarService"
+	factoryHostIp1234Format     = "host = %s =>  host = 1.2.3.4"
+)
 
 type MockInvoker struct {
 	url          common.URL
@@ -61,21 +70,21 @@ func (bi *MockInvoker) GetUrl() common.URL {
 }
 
 func getRouteUrl(rule string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
+	url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE))
 	url.AddParam("rule", rule)
 	url.AddParam("force", "true")
 	return &url
 }
 
 func getRouteUrlWithForce(rule, force string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
+	url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE))
 	url.AddParam("rule", rule)
 	url.AddParam("force", force)
 	return &url
 }
 
 func getRouteUrlWithNoForce(rule string) *common.URL {
-	url, _ := common.NewURL(anyUrl)
+	url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE))
 	url.AddParam("rule", rule)
 	return &url
 }
@@ -118,11 +127,11 @@ func (bi *MockInvoker) Destroy() {
 	bi.available = false
 }
 
-func TestRouteMatchWhen(t *testing.T) {
+func TestRoute_matchWhen(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4"))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
-	cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService")
+	cUrl, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, factory1111Ip))
 	matchWhen := router.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen)
 	rule1 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4"))
@@ -151,12 +160,12 @@ func TestRouteMatchWhen(t *testing.T) {
 	assert.Equal(t, true, matchWhen6)
 }
 
-func TestRouteMatchFilter(t *testing.T) {
+func TestRoute_matchFilter(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
 	t.Logf("The local ip is %s", localIP)
 	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invokers := []protocol.Invoker{NewMockInvoker(url1, 1), NewMockInvoker(url2, 2), NewMockInvoker(url3, 3)}
 	rule1 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.3"))
 	rule2 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.* & host != 10.20.3.3"))
@@ -170,7 +179,7 @@ func TestRouteMatchFilter(t *testing.T) {
 	router4, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule4))
 	router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5))
 	router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6))
-	cUrl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	cUrl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{})
 	fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{})
 	fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{})
@@ -186,22 +195,22 @@ func TestRouteMatchFilter(t *testing.T) {
 
 }
 
-func TestRouteMethodRoute(t *testing.T) {
+func TestRoute_methodRoute(t *testing.T) {
 	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{}))
 	rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4"))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo")
 	matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv)
 	assert.Equal(t, true, matchWhen)
-	url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo")
+	url1, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip))
 	matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv)
 	assert.Equal(t, true, matchWhen)
-	url2, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo")
+	url2, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip))
 	rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4"))
 	router2, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule2))
 	matchWhen = router2.(*ConditionRouter).MatchWhen(&url2, inv)
 	assert.Equal(t, false, matchWhen)
-	url3, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo")
+	url3, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip))
 	rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4"))
 	router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3))
 	matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv)
@@ -209,31 +218,31 @@ func TestRouteMethodRoute(t *testing.T) {
 
 }
 
-func TestRouteReturnFalse(t *testing.T) {
+func TestRoute_ReturnFalse(t *testing.T) {
 	url, _ := common.NewURL("")
 	localIP, _ := gxnet.GetLocalIP()
 	invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false"))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
 }
 
-func TestRouteReturnEmpty(t *testing.T) {
+func TestRoute_ReturnEmpty(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
 	url, _ := common.NewURL("")
 	invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => "))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
 }
 
-func TestRouteReturnAll(t *testing.T) {
+func TestRoute_ReturnAll(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
 	urlString := "dubbo://" + localIP + "/com.foo.BarService"
 	dubboURL, _ := common.NewURL(urlString)
@@ -243,24 +252,24 @@ func TestRouteReturnAll(t *testing.T) {
 	invokers := []protocol.Invoker{mockInvoker1, mockInvoker2, mockInvoker3}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, invokers, fileredInvokers)
 }
 
-func TestRouteHostFilter(t *testing.T) {
+func TestRoute_HostFilter(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
@@ -268,18 +277,18 @@ func TestRouteHostFilter(t *testing.T) {
 	assert.Equal(t, invoker3, fileredInvokers[1])
 }
 
-func TestRouteEmptyHostFilter(t *testing.T) {
+func TestRoute_Empty_HostFilter(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
@@ -287,18 +296,18 @@ func TestRouteEmptyHostFilter(t *testing.T) {
 	assert.Equal(t, invoker3, fileredInvokers[1])
 }
 
-func TestRouteFalseHostFilter(t *testing.T) {
+func TestRoute_False_HostFilter(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
@@ -306,18 +315,18 @@ func TestRouteFalseHostFilter(t *testing.T) {
 	assert.Equal(t, invoker3, fileredInvokers[1])
 }
 
-func TestRoutePlaceholder(t *testing.T) {
+func TestRoute_Placeholder(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host"))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
@@ -325,35 +334,35 @@ func TestRoutePlaceholder(t *testing.T) {
 	assert.Equal(t, invoker3, fileredInvokers[1])
 }
 
-func TestRouteNoForce(t *testing.T) {
+func TestRoute_NoForce(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
-	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4"))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP)))
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, invokers, fileredInvokers)
 }
 
-func TestRouteForce(t *testing.T) {
+func TestRoute_Force(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService")
-	url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
-	url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP))
+	url1, _ := common.NewURL(factory333URL)
+	url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
+	url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP))
 	invoker1 := NewMockInvoker(url1, 1)
 	invoker2 := NewMockInvoker(url2, 2)
 	invoker3 := NewMockInvoker(url3, 3)
 	invokers := []protocol.Invoker{invoker1, invoker2, invoker3}
 	inv := &invocation.RPCInvocation{}
-	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4"))
-	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP)))
+	curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP))
 	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true"))
 	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go
index b2c876690043d18a1a9e746fee13f06c77a0de03..eabdf1c263446140b359b3e791238b020cecb50c 100644
--- a/cluster/router/condition/file.go
+++ b/cluster/router/condition/file.go
@@ -77,10 +77,7 @@ func (f *FileConditionRouter) URL() common.URL {
 }
 
 func parseCondition(conditions []string) string {
-	var (
-		when string
-		then string
-	)
+	var when, then string
 	for _, condition := range conditions {
 		condition = strings.Trim(condition, " ")
 		if strings.Contains(condition, "=>") {
@@ -101,10 +98,7 @@ func parseCondition(conditions []string) string {
 					then = provider
 				}
 			}
-
 		}
-
 	}
-
 	return strings.Join([]string{when, then}, " => ")
 }
diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go
index 0267a3c7a462acb43f84ccb4701247147699804a..40a251573f5e73d40032972313565d98b288b1b1 100644
--- a/cluster/router/condition/router.go
+++ b/cluster/router/condition/router.go
@@ -181,9 +181,7 @@ func parseRule(rule string) (map[string]MatchPair, error) {
 		return condition, nil
 	}
 
-	var (
-		pair MatchPair
-	)
+	var pair MatchPair
 	values := gxset.NewSet()
 	matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1)
 	for _, groups := range matches {
diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go
index 8a95d9a7e8dffdc3f30f94c76274a729837fc133..5d35ae8e486e3f7b29b2a68a3864ef806a1053c7 100644
--- a/cluster/router/healthcheck/default_health_check_test.go
+++ b/cluster/router/healthcheck/default_health_check_test.go
@@ -18,6 +18,7 @@
 package healthcheck
 
 import (
+	"fmt"
 	"math"
 	"testing"
 )
@@ -32,10 +33,16 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func TestDefaultHealthChecker_IsHealthy(t *testing.T) {
+const (
+	healthCheckDubbo1010IP    = "192.168.10.10"
+	healthCheckDubbo1011IP    = "192.168.10.11"
+	healthCheckMethodTest     = "test"
+	healthCheckDubboUrlFormat = "dubbo://%s:20000/com.ikurento.user.UserProvider"
+)
 
+func TestDefaultHealthCheckerIsHealthy(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	invoker := NewMockInvoker(url)
 	healthy := hc.IsHealthy(invoker)
@@ -45,7 +52,7 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) {
 	url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "100")
 	// fake the outgoing request
 	for i := 0; i < 11; i++ {
-		request(url, "test", 0, true, false)
+		request(url, healthCheckMethodTest, 0, true, false)
 	}
 	hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	healthy = hc.IsHealthy(invoker)
@@ -54,7 +61,7 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) {
 
 	// successive failed count is more than constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, go to unhealthy
 	for i := 0; i < 11; i++ {
-		request(url, "test", 0, false, false)
+		request(url, healthCheckMethodTest, 0, false, false)
 	}
 	url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10")
 	url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000")
@@ -63,18 +70,18 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) {
 	assert.False(t, hc.IsHealthy(invoker))
 
 	// reset successive failed count and go to healthy
-	request(url, "test", 0, false, true)
+	request(url, healthCheckMethodTest, 0, false, true)
 	healthy = hc.IsHealthy(invoker)
 	assert.True(t, hc.IsHealthy(invoker))
 }
 
-func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) {
+func TestDefaultHealthCheckerGetCircuitBreakerSleepWindowTime(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	// Increase the number of failed requests
 	for i := 0; i < 100; i++ {
-		request(url, "test", 1, false, false)
+		request(url, healthCheckMethodTest, 1, false, false)
 	}
 	sleepWindowTime := defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url))
 	assert.True(t, sleepWindowTime == constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS)
@@ -84,48 +91,48 @@ func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) {
 	sleepWindowTime = NewDefaultHealthChecker(&url).(*DefaultHealthChecker).getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url))
 	assert.True(t, sleepWindowTime == 0)
 
-	url1, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider")
+	url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP))
 	sleepWindowTime = defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url1))
 	assert.True(t, sleepWindowTime == 0)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
 	sleepWindowTime = defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url1))
 	assert.True(t, sleepWindowTime > 0 && sleepWindowTime < constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS)
 }
 
-func TestDefaultHealthChecker_getCircuitBreakerTimeout(t *testing.T) {
+func TestDefaultHealthCheckerGetCircuitBreakerTimeout(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	timeout := defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url))
 	assert.True(t, timeout == 0)
-	url1, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider")
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
-	request(url1, "test", 1, false, false)
+	url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP))
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
+	request(url1, healthCheckMethodTest, 1, false, false)
 	timeout = defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url1))
 	// timeout must after the current time
 	assert.True(t, timeout > protocol.CurrentTimeMillis())
 
 }
 
-func TestDefaultHealthChecker_isCircuitBreakerTripped(t *testing.T) {
+func TestDefaultHealthCheckerIsCircuitBreakerTripped(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	status := protocol.GetURLStatus(url)
 	tripped := defaultHc.isCircuitBreakerTripped(status)
 	assert.False(t, tripped)
 	// Increase the number of failed requests
 	for i := 0; i < 100; i++ {
-		request(url, "test", 1, false, false)
+		request(url, healthCheckMethodTest, 1, false, false)
 	}
 	tripped = defaultHc.isCircuitBreakerTripped(protocol.GetURLStatus(url))
 	assert.True(t, tripped)
@@ -134,13 +141,13 @@ func TestDefaultHealthChecker_isCircuitBreakerTripped(t *testing.T) {
 
 func TestNewDefaultHealthChecker(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
 	assert.NotNil(t, defaultHc)
 	assert.Equal(t, defaultHc.outStandingRequestConutLimit, int32(math.MaxInt32))
 	assert.Equal(t, defaultHc.requestSuccessiveFailureThreshold, int32(constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF))
 
-	url1, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	url1.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10")
 	url1.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10")
 	nondefaultHc := NewDefaultHealthChecker(&url1).(*DefaultHealthChecker)
diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go
index c3a26a93896e185f0dea3732ca5afcf7687ad5ea..e80fd4c4d38dbb1c0f2b14ba1e22971249bc54b6 100644
--- a/cluster/router/healthcheck/factory_test.go
+++ b/cluster/router/healthcheck/factory_test.go
@@ -31,34 +31,43 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// nolint
 type MockInvoker struct {
 	url common.URL
 }
 
+// nolint
 func NewMockInvoker(url common.URL) *MockInvoker {
 	return &MockInvoker{
 		url: url,
 	}
 }
 
+// nolint
 func (bi *MockInvoker) GetUrl() common.URL {
 	return bi.url
 }
+
+// nolint
 func (bi *MockInvoker) IsAvailable() bool {
 	return true
 }
 
+// nolint
 func (bi *MockInvoker) IsDestroyed() bool {
 	return true
 }
 
+// nolint
 func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result {
 	return nil
 }
 
+// nolint
 func (bi *MockInvoker) Destroy() {
 }
 
+// nolint
 func TestHealthCheckRouteFactory(t *testing.T) {
 	factory := newHealthCheckRouteFactory()
 	assert.NotNil(t, factory)
diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go
index 7bfffea705bfedade9d1d13ac7e9c380651335dd..d5862fb884114bac0ea2ec9ee8926baac57d5ba6 100644
--- a/cluster/router/healthcheck/health_check_route_test.go
+++ b/cluster/router/healthcheck/health_check_route_test.go
@@ -18,6 +18,7 @@
 package healthcheck
 
 import (
+	"fmt"
 	"math"
 	"testing"
 	"time"
@@ -34,13 +35,22 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestHealthCheckRouter_Route(t *testing.T) {
+const (
+	healthCheckRoute1010IP         = "192.168.10.10"
+	healthCheckRoute1011IP         = "192.168.10.11"
+	healthCheckRoute1012IP         = "192.168.10.12"
+	healthCheckRouteMethodNameTest = "test"
+	healthCheck1001URL             = "dubbo://192.168.10.1/com.ikurento.user.UserProvider"
+	healthCheckRouteUrlFormat      = "dubbo://%s:20000/com.ikurento.user.UserProvider"
+)
+
+func TestHealthCheckRouterRoute(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	consumerURL, _ := common.NewURL("dubbo://192.168.10.1/com.ikurento.user.UserProvider")
+	consumerURL, _ := common.NewURL(healthCheck1001URL)
 	consumerURL.SetParam(HEALTH_ROUTE_ENABLED_KEY, "true")
-	url1, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
-	url2, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider")
-	url3, _ := common.NewURL("dubbo://192.168.10.12:20000/com.ikurento.user.UserProvider")
+	url1, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1010IP))
+	url2, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1011IP))
+	url3, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1012IP))
 	hcr, _ := NewHealthCheckRouter(&consumerURL)
 
 	var invokers []protocol.Invoker
@@ -48,21 +58,21 @@ func TestHealthCheckRouter_Route(t *testing.T) {
 	invoker2 := NewMockInvoker(url2)
 	invoker3 := NewMockInvoker(url3)
 	invokers = append(invokers, invoker1, invoker2, invoker3)
-	inv := invocation.NewRPCInvocation("test", nil, nil)
+	inv := invocation.NewRPCInvocation(healthCheckRouteMethodNameTest, nil, nil)
 	res := hcr.Route(invokers, &consumerURL, inv)
 	// now all invokers are healthy
 	assert.True(t, len(res) == len(invokers))
 
 	for i := 0; i < 10; i++ {
-		request(url1, "test", 0, false, false)
+		request(url1, healthCheckRouteMethodNameTest, 0, false, false)
 	}
 	res = hcr.Route(invokers, &consumerURL, inv)
 	// invokers1  is unhealthy now
 	assert.True(t, len(res) == 2 && !contains(res, invoker1))
 
 	for i := 0; i < 10; i++ {
-		request(url1, "test", 0, false, false)
-		request(url2, "test", 0, false, false)
+		request(url1, healthCheckRouteMethodNameTest, 0, false, false)
+		request(url2, healthCheckRouteMethodNameTest, 0, false, false)
 	}
 
 	res = hcr.Route(invokers, &consumerURL, inv)
@@ -70,9 +80,9 @@ func TestHealthCheckRouter_Route(t *testing.T) {
 	assert.True(t, len(res) == 1 && !contains(res, invoker1) && !contains(res, invoker2))
 
 	for i := 0; i < 10; i++ {
-		request(url1, "test", 0, false, false)
-		request(url2, "test", 0, false, false)
-		request(url3, "test", 0, false, false)
+		request(url1, healthCheckRouteMethodNameTest, 0, false, false)
+		request(url2, healthCheckRouteMethodNameTest, 0, false, false)
+		request(url3, healthCheckRouteMethodNameTest, 0, false, false)
 	}
 
 	res = hcr.Route(invokers, &consumerURL, inv)
@@ -80,12 +90,12 @@ func TestHealthCheckRouter_Route(t *testing.T) {
 	assert.True(t, len(res) == 3)
 
 	// reset the invoker1 successive failed count, so invoker1 go to healthy
-	request(url1, "test", 0, false, true)
+	request(url1, healthCheckRouteMethodNameTest, 0, false, true)
 	res = hcr.Route(invokers, &consumerURL, inv)
 	assert.True(t, contains(res, invoker1))
 
 	for i := 0; i < 6; i++ {
-		request(url1, "test", 0, false, false)
+		request(url1, healthCheckRouteMethodNameTest, 0, false, false)
 	}
 	// now all invokers are unhealthy, so downgraded to all again
 	res = hcr.Route(invokers, &consumerURL, inv)
@@ -108,7 +118,7 @@ func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool {
 
 func TestNewHealthCheckRouter(t *testing.T) {
 	defer protocol.CleanAllStatus()
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP))
 	hcr, _ := NewHealthCheckRouter(&url)
 	h := hcr.(*HealthCheckRouter)
 	assert.Nil(t, h.checker)
diff --git a/cluster/router/tag/factory_test.go b/cluster/router/tag/factory_test.go
index f11f2944115e0c158b353c7256a850df469b71c5..ee195820c123e1fc67a2c27cd12aaa544650b615 100644
--- a/cluster/router/tag/factory_test.go
+++ b/cluster/router/tag/factory_test.go
@@ -18,6 +18,7 @@
 package tag
 
 import (
+	"fmt"
 	"testing"
 )
 
@@ -29,8 +30,13 @@ import (
 	"github.com/apache/dubbo-go/common"
 )
 
-func TestTagRouterFactory_NewRouter(t *testing.T) {
-	u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true")
+const (
+	factoryLocalIP = "127.0.0.1"
+	factoryFormat  = "dubbo://%s:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true"
+)
+
+func TestTagRouterFactoryNewRouter(t *testing.T) {
+	u1, err := common.NewURL(fmt.Sprintf(factoryFormat, factoryLocalIP))
 	assert.Nil(t, err)
 	factory := NewTagRouterFactory()
 	tagRouter, e := factory.NewPriorityRouter(&u1)
diff --git a/cluster/router/tag/file_test.go b/cluster/router/tag/file_test.go
index 94fcf9e0e0fabed2445417d14b711f91b65b9e5e..513ba0c0b6c622d6a52fad35a24824121eb71b76 100644
--- a/cluster/router/tag/file_test.go
+++ b/cluster/router/tag/file_test.go
@@ -29,18 +29,21 @@ import (
 	"github.com/apache/dubbo-go/common/constant"
 )
 
+const (
+	fileTestTag = `priority: 100
+force: true`
+)
+
 func TestNewFileTagRouter(t *testing.T) {
-	router, e := NewFileTagRouter([]byte(`priority: 100
-force: true`))
+	router, e := NewFileTagRouter([]byte(fileTestTag))
 	assert.Nil(t, e)
 	assert.NotNil(t, router)
 	assert.Equal(t, 100, router.routerRule.Priority)
 	assert.Equal(t, true, router.routerRule.Force)
 }
 
-func TestFileTagRouter_URL(t *testing.T) {
-	router, e := NewFileTagRouter([]byte(`priority: 100
-force: true`))
+func TestFileTagRouterURL(t *testing.T) {
+	router, e := NewFileTagRouter([]byte(fileTestTag))
 	assert.Nil(t, e)
 	assert.NotNil(t, router)
 	url := router.URL()
@@ -52,9 +55,8 @@ force: true`))
 
 }
 
-func TestFileTagRouter_Priority(t *testing.T) {
-	router, e := NewFileTagRouter([]byte(`priority: 100
-force: true`))
+func TestFileTagRouterPriority(t *testing.T) {
+	router, e := NewFileTagRouter([]byte(fileTestTag))
 	assert.Nil(t, e)
 	assert.NotNil(t, router)
 	priority := router.Priority()
diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go
index 87da418943e90c63a905f35260ada7880d6f51b9..e1376fd96a88246e7ad0e26d2e3c34693c88a77c 100644
--- a/cluster/router/tag/tag_router.go
+++ b/cluster/router/tag/tag_router.go
@@ -31,12 +31,14 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// tagRouter defines url, enable and the priority
 type tagRouter struct {
 	url      *common.URL
 	enabled  bool
 	priority int64
 }
 
+// NewTagRouter returns a tagRouter instance if url is not nil
 func NewTagRouter(url *common.URL) (*tagRouter, error) {
 	if url == nil {
 		return nil, perrors.Errorf("Illegal route URL!")
@@ -48,10 +50,12 @@ func NewTagRouter(url *common.URL) (*tagRouter, error) {
 	}, nil
 }
 
+// nolint
 func (c *tagRouter) isEnabled() bool {
 	return c.enabled
 }
 
+// Route gets a list of invoker
 func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
 	if !c.isEnabled() {
 		return invokers
@@ -62,14 +66,17 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati
 	return filterUsingStaticTag(invokers, url, invocation)
 }
 
+// URL gets the url of tagRouter
 func (c *tagRouter) URL() common.URL {
 	return *c.url
 }
 
+// Priority gets the priority of tagRouter
 func (c *tagRouter) Priority() int64 {
 	return c.priority
 }
 
+// filterUsingStaticTag gets a list of invoker using static tag
 func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
 	if tag, ok := invocation.Attachments()[constant.Tagkey]; ok {
 		result := make([]protocol.Invoker, 0, 8)
@@ -86,6 +93,7 @@ func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocati
 	return invokers
 }
 
+// isForceUseTag returns whether force use tag
 func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool {
 	if b, e := strconv.ParseBool(invocation.AttachmentsByKey(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))); e == nil {
 		return b
diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go
index 280b56c8ccb69eb5d32dae2369bdc862adb8e6fd..000b3ec6724d85590c86456a009d5194c4e71e03 100644
--- a/cluster/router/tag/tag_router_test.go
+++ b/cluster/router/tag/tag_router_test.go
@@ -32,6 +32,21 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
+const (
+	tagRouterTestHangZhouUrl     = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou"
+	tagRouterTestShangHaiUrl     = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai"
+	tagRouterTestBeijingUrl      = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing"
+	tagRouterTestUserConsumer    = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true"
+	tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true"
+
+	tagRouterTestDubboTag      = "dubbo.tag"
+	tagRouterTestDubboForceTag = "dubbo.force.tag"
+	tagRouterTestHangZhou      = "hangzhou"
+	tagRouterTestGuangZhou     = "guangzhou"
+	tagRouterTestFalse         = "false"
+	tagRouterTestTrue          = "true"
+)
+
 // MockInvoker is only mock the Invoker to support test tagRouter
 type MockInvoker struct {
 	url          common.URL
@@ -73,8 +88,8 @@ func (bi *MockInvoker) Destroy() {
 	bi.available = false
 }
 
-func TestTagRouter_Priority(t *testing.T) {
-	u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true")
+func TestTagRouterPriority(t *testing.T) {
+	u1, err := common.NewURL(tagRouterTestUserConsumerTag)
 	assert.Nil(t, err)
 	tagRouter, e := NewTagRouter(&u1)
 	assert.Nil(t, e)
@@ -82,15 +97,15 @@ func TestTagRouter_Priority(t *testing.T) {
 	assert.Equal(t, int64(0), p)
 }
 
-func TestTagRouter_Route_force(t *testing.T) {
-	u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true")
+func TestTagRouterRouteForce(t *testing.T) {
+	u1, e1 := common.NewURL(tagRouterTestUserConsumerTag)
 	assert.Nil(t, e1)
 	tagRouter, e := NewTagRouter(&u1)
 	assert.Nil(t, e)
 
-	u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou")
-	u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai")
-	u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing")
+	u2, e2 := common.NewURL(tagRouterTestHangZhouUrl)
+	u3, e3 := common.NewURL(tagRouterTestShangHaiUrl)
+	u4, e4 := common.NewURL(tagRouterTestBeijingUrl)
 	assert.Nil(t, e2)
 	assert.Nil(t, e3)
 	assert.Nil(t, e4)
@@ -100,29 +115,29 @@ func TestTagRouter_Route_force(t *testing.T) {
 	var invokers []protocol.Invoker
 	invokers = append(invokers, inv2, inv3, inv4)
 	inv := &invocation.RPCInvocation{}
-	inv.SetAttachments("dubbo.tag", "hangzhou")
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou)
 	invRst1 := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 1, len(invRst1))
-	assert.Equal(t, "hangzhou", invRst1[0].GetUrl().GetParam("dubbo.tag", ""))
+	assert.Equal(t, tagRouterTestHangZhou, invRst1[0].GetUrl().GetParam(tagRouterTestDubboTag, ""))
 
-	inv.SetAttachments("dubbo.tag", "guangzhou")
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
 	invRst2 := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 0, len(invRst2))
-	inv.SetAttachments("dubbo.force.tag", "false")
-	inv.SetAttachments("dubbo.tag", "guangzhou")
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse)
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
 	invRst3 := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 3, len(invRst3))
 }
 
-func TestTagRouter_Route_noForce(t *testing.T) {
-	u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true")
+func TestTagRouterRouteNoForce(t *testing.T) {
+	u1, e1 := common.NewURL(tagRouterTestUserConsumer)
 	assert.Nil(t, e1)
 	tagRouter, e := NewTagRouter(&u1)
 	assert.Nil(t, e)
 
-	u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou")
-	u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai")
-	u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing")
+	u2, e2 := common.NewURL(tagRouterTestHangZhouUrl)
+	u3, e3 := common.NewURL(tagRouterTestShangHaiUrl)
+	u4, e4 := common.NewURL(tagRouterTestShangHaiUrl)
 	assert.Nil(t, e2)
 	assert.Nil(t, e3)
 	assert.Nil(t, e4)
@@ -132,16 +147,16 @@ func TestTagRouter_Route_noForce(t *testing.T) {
 	var invokers []protocol.Invoker
 	invokers = append(invokers, inv2, inv3, inv4)
 	inv := &invocation.RPCInvocation{}
-	inv.SetAttachments("dubbo.tag", "hangzhou")
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou)
 	invRst := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 1, len(invRst))
-	assert.Equal(t, "hangzhou", invRst[0].GetUrl().GetParam("dubbo.tag", ""))
+	assert.Equal(t, tagRouterTestHangZhou, invRst[0].GetUrl().GetParam(tagRouterTestDubboTag, ""))
 
-	inv.SetAttachments("dubbo.tag", "guangzhou")
-	inv.SetAttachments("dubbo.force.tag", "true")
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestTrue)
 	invRst1 := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 0, len(invRst1))
-	inv.SetAttachments("dubbo.force.tag", "false")
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse)
 	invRst2 := tagRouter.Route(invokers, &u1, inv)
 	assert.Equal(t, 3, len(invRst2))
 }
diff --git a/common/constant/default.go b/common/constant/default.go
index dd756917cee3f8ee99eb8f9fdb52d9dd2821d307..0680c4192d085237f2e1a460e668b14b26a9b32e 100644
--- a/common/constant/default.go
+++ b/common/constant/default.go
@@ -77,3 +77,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/env.go b/common/constant/env.go
index 5376323328f431083a47395c9e2ebbab5b37f307..f8402b9cb8649f9471d8ae6f0d26314053496bb7 100644
--- a/common/constant/env.go
+++ b/common/constant/env.go
@@ -17,6 +17,7 @@
 
 package constant
 
+// nolint
 const (
 	// CONF_CONSUMER_FILE_PATH ...
 	CONF_CONSUMER_FILE_PATH = "CONF_CONSUMER_FILE_PATH"
diff --git a/common/constant/key.go b/common/constant/key.go
index 06b37cf709bc4940f6763b72e877d3aab44b2109..c3fe0e79230d3cdf7f41186ac77ca4581d416331 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -43,6 +43,10 @@ const (
 	DEFAULT_REMOTING_TIMEOUT = 3000
 	RELEASE_KEY              = "release"
 	ANYHOST_KEY              = "anyhost"
+	PORT_KEY                 = "port"
+	PROTOCOL_KEY             = "protocol"
+	PATH_SEPARATOR           = "/"
+	DUBBO_KEY                = "dubbo"
 )
 
 const (
@@ -78,6 +82,11 @@ const (
 	PROVIDER_SHUTDOWN_FILTER               = "pshutdown"
 	CONSUMER_SHUTDOWN_FILTER               = "cshutdown"
 	SERIALIZATION_KEY                      = "serialization"
+	PID_KEY                                = "pid"
+	SYNC_REPORT_KEY                        = "sync.report"
+	RETRY_PERIOD_KEY                       = "retry.period"
+	RETRY_TIMES_KEY                        = "retry.times"
+	CYCLE_REPORT_KEY                       = "cycle.report"
 )
 
 const (
@@ -151,6 +160,17 @@ const (
 	NACOS_CATEGORY_KEY           = "category"
 	NACOS_PROTOCOL_KEY           = "protocol"
 	NACOS_PATH_KEY               = "path"
+	NACOS_NAMESPACE_ID           = "namespaceId"
+	NACOS_PASSWORD               = "password"
+	NACOS_USERNAME               = "username"
+)
+
+const (
+	ZOOKEEPER_KEY = "zookeeper"
+)
+
+const (
+	ETCDV3_KEY = "etcdv3"
 )
 
 const (
@@ -228,7 +248,6 @@ const (
 	KEY_SEPARATOR      = ":"
 	DEFAULT_PATH_TAG   = "metadata"
 	KEY_REVISON_PREFIX = "revision"
-	PATH_SEPARATOR     = "/"
 
 	// metadata service
 	METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService"
@@ -257,7 +276,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/proxy/proxy.go b/common/proxy/proxy.go
index abcf87cd9d297769bf8aff6fa07d6a4659091eb6..ce0f4d1d3f4dc8b93467aaeede40ea03b53c6e66 100644
--- a/common/proxy/proxy.go
+++ b/common/proxy/proxy.go
@@ -23,6 +23,11 @@ import (
 	"sync"
 )
 
+import (
+	"github.com/apache/dubbo-go-hessian2/java_exception"
+	perrors "github.com/pkg/errors"
+)
+
 import (
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
@@ -154,7 +159,18 @@ func (p *Proxy) Implement(v common.RPCService) {
 			}
 
 			err = result.Error()
-			logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
+			if err != nil {
+				// the cause reason
+				err = perrors.Cause(err)
+				// if some error happened, it should be log some info in the seperate file.
+				if throwabler, ok := err.(java_exception.Throwabler); ok {
+					logger.Warnf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace())
+				} else {
+					logger.Warnf("result err: %v", err)
+				}
+			} else {
+				logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
+			}
 			if len(outs) == 1 {
 				return []reflect.Value{reflect.ValueOf(&err).Elem()}
 			}
diff --git a/common/rpc_service.go b/common/rpc_service.go
index 05ca3721d98124ccbdebedb47b5ae4edd8b84add..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
@@ -272,7 +277,7 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro
 		}
 	}
 	delete(svcs, serviceId)
-	if len(sm.serviceMap) == 0 {
+	if len(sm.serviceMap[protocol]) == 0 {
 		delete(sm.serviceMap, protocol)
 	}
 
@@ -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 5a3e69f266ab509881b6fdafb21926732f68aa94..7c2c39c60feccccdc428e1e427a6261c743bd92b 100644
--- a/common/url.go
+++ b/common/url.go
@@ -38,6 +38,7 @@ import (
 
 import (
 	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
 )
 
 // ///////////////////////////////
@@ -180,7 +181,12 @@ func WithToken(token string) option {
 		if len(token) > 0 {
 			value := token
 			if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" {
-				value = uuid.NewV4().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)
 		}
@@ -681,3 +687,22 @@ func ParamsUnescapeEncode(params url.Values) string {
 	}
 	return buf.String()
 }
+
+// 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 cc02da3debd77b1693e26ee8b522330f3c2f23c9..2b9b91c3b34167321e6f30e2427d20e71ecbf76b 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 cf490019b2a48b98ffb6fab1036bfd6218fe7343..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 {
@@ -124,20 +144,20 @@ func getKeyPrefix(val reflect.Value) []string {
 	var (
 		prefix string
 	)
-
+	configPrefixMethod := "Prefix"
 	if val.CanAddr() {
-		prefix = val.Addr().MethodByName("Prefix").Call(nil)[0].String()
+		prefix = val.Addr().MethodByName(configPrefixMethod).Call(nil)[0].String()
 	} else {
-		prefix = val.MethodByName("Prefix").Call(nil)[0].String()
+		prefix = val.MethodByName(configPrefixMethod).Call(nil)[0].String()
 	}
-	var retPrefixs []string
+	var retPrefixes []string
 
 	for _, pfx := range strings.Split(prefix, "|") {
 
-		retPrefixs = append(retPrefixs, pfx)
+		retPrefixes = append(retPrefixes, pfx)
 
 	}
-	return retPrefixs
+	return retPrefixes
 
 }
 
@@ -164,13 +184,13 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 						idStr string
 					)
 
-					prefixs := getKeyPrefix(val)
+					prefixes := getKeyPrefix(val)
 
 					if id.Kind() == reflect.String {
 						idStr = id.Interface().(string)
 					}
 
-					for _, pfx := range prefixs {
+					for _, pfx := range prefixes {
 
 						if len(pfx) > 0 {
 							if len(idStr) > 0 {
@@ -190,18 +210,20 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 
 					}
 					if ok {
+						errMsg := func(structName string, fieldName string, errorDetails error) {
+							logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+								structName, fieldName, errorDetails)
+						}
 						switch f.Kind() {
 						case reflect.Int64:
 							x, err := strconv.Atoi(value)
 							if err != nil {
-								logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
-									val.Type().Name(), val.Type().Field(i).Name, err)
+								errMsg(val.Type().Name(), val.Type().Field(i).Name, err)
 							} else {
 								if !f.OverflowInt(int64(x)) {
 									f.SetInt(int64(x))
 								} else {
-									logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
-										val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is  overflow", int64(x)))
+									errMsg(val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is  overflow", int64(x)))
 								}
 							}
 						case reflect.String:
@@ -209,21 +231,18 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 						case reflect.Bool:
 							x, err := strconv.ParseBool(value)
 							if err != nil {
-								logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
-									val.Type().Name(), val.Type().Field(i).Name, err)
+								errMsg(val.Type().Name(), val.Type().Field(i).Name, err)
 							}
 							f.SetBool(x)
 						case reflect.Float64:
 							x, err := strconv.ParseFloat(value, 64)
 							if err != nil {
-								logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
-									val.Type().Name(), val.Type().Field(i).Name, err)
+								errMsg(val.Type().Name(), val.Type().Field(i).Name, err)
 							} else {
 								if !f.OverflowFloat(x) {
 									f.SetFloat(x)
 								} else {
-									logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
-										val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is  overflow", x))
+									errMsg(val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is  overflow", x))
 								}
 							}
 						default:
@@ -264,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, "|") {
@@ -279,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)
@@ -314,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())
 
@@ -328,39 +347,40 @@ func (c *BaseConfig) SetFatherConfig(fatherConfig interface{}) {
 }
 
 func initializeStruct(t reflect.Type, v reflect.Value) {
-	if v.Kind() == reflect.Struct {
-		for i := 0; i < v.NumField(); i++ {
-			f := v.Field(i)
-			ft := t.Field(i)
-
-			if ft.Tag.Get("property") != "" {
-				switch ft.Type.Kind() {
-				case reflect.Map:
-					if f.IsNil() {
-						f.Set(reflect.MakeMap(ft.Type))
-					}
-				case reflect.Slice:
-					if f.IsNil() {
-						f.Set(reflect.MakeSlice(ft.Type, 0, 0))
-					}
-				case reflect.Chan:
-					if f.IsNil() {
-						f.Set(reflect.MakeChan(ft.Type, 0))
-					}
-				case reflect.Struct:
-					if f.IsNil() {
-						initializeStruct(ft.Type, f)
-					}
-				case reflect.Ptr:
-					if f.IsNil() {
-						fv := reflect.New(ft.Type.Elem())
-						initializeStruct(ft.Type.Elem(), fv.Elem())
-						f.Set(fv)
-					}
-				default:
-				}
-			}
+	if v.Kind() != reflect.Struct {
+		return
+	}
+	for i := 0; i < v.NumField(); i++ {
+		f := v.Field(i)
+		ft := t.Field(i)
 
+		if ft.Tag.Get("property") == "" {
+			continue
+		}
+		switch ft.Type.Kind() {
+		case reflect.Map:
+			if f.IsNil() {
+				f.Set(reflect.MakeMap(ft.Type))
+			}
+		case reflect.Slice:
+			if f.IsNil() {
+				f.Set(reflect.MakeSlice(ft.Type, 0, 0))
+			}
+		case reflect.Chan:
+			if f.IsNil() {
+				f.Set(reflect.MakeChan(ft.Type, 0))
+			}
+		case reflect.Struct:
+			if f.IsNil() {
+				initializeStruct(ft.Type, f)
+			}
+		case reflect.Ptr:
+			if f.IsNil() {
+				fv := reflect.New(ft.Type.Elem())
+				initializeStruct(ft.Type.Elem(), fv.Elem())
+				f.Set(fv)
+			}
+		default:
 		}
 	}
 }
diff --git a/config/base_config_test.go b/config/base_config_test.go
index bc422d018946017a2c50dccefe54357d786a7589..6db6a8dcb84de3fdefe94cce87338b9efe28246c 100644
--- a/config/base_config_test.go
+++ b/config/base_config_test.go
@@ -33,89 +33,95 @@ import (
 	_ "github.com/apache/dubbo-go/config_center/apollo"
 )
 
-func Test_refresh(t *testing.T) {
+func getMockMap() map[string]string {
+	baseMockMap := map[string]string{
+		"dubbo.registries.shanghai_reg1.protocol":             "mock100",
+		"dubbo.reference.com.MockService.MockService.retries": "10",
+		"dubbo.com.MockService.MockService.GetUser.retries":   "10",
+		"dubbo.consumer.check":                                "false",
+		"dubbo.application.name":                              "dubbo",
+	}
+	return baseMockMap
+}
+
+var baseAppConfig = &ApplicationConfig{
+	Organization: "dubbo_org",
+	Name:         "dubbo",
+	Module:       "module",
+	Version:      "2.6.0",
+	Owner:        "dubbo",
+	Environment:  "test",
+}
+
+var baseRegistries = map[string]*RegistryConfig{
+	"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",
+	},
+}
+
+var baseMockRef = map[string]*ReferenceConfig{
+	"MockService": {
+		InterfaceName: "com.MockService",
+		Protocol:      "mock",
+		Cluster:       "failover",
+		Loadbalance:   "random",
+		Retries:       "3",
+		Group:         "huadong_idc",
+		Version:       "1.0.0",
+		Methods: []*MethodConfig{
+			{
+				InterfaceId:   "MockService",
+				InterfaceName: "com.MockService",
+				Name:          "GetUser",
+				Retries:       "2",
+				LoadBalance:   "random",
+			},
+			{
+				InterfaceId:   "MockService",
+				InterfaceName: "com.MockService",
+				Name:          "GetUser1",
+				Retries:       "2",
+				LoadBalance:   "random",
+			},
+		},
+	},
+}
+
+func TestRefresh(t *testing.T) {
 	c := &BaseConfig{}
-	mockMap := map[string]string{}
-	mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100"
-	mockMap["dubbo.reference.com.MockService.MockService.retries"] = "10"
-	mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10"
-	mockMap["dubbo.consumer.check"] = "false"
-	mockMap["dubbo.application.name"] = "dubbo"
+	mockMap := getMockMap()
 	mockMap["dubbo.shutdown.timeout"] = "12s"
 
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 
 	father := &ConsumerConfig{
 		Check: &[]bool{true}[0],
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registries: map[string]*RegistryConfig{
-			//"shanghai_reg1": {
-			//	id:         "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",
-			},
-		},
-		References: map[string]*ReferenceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Cluster:       "failover",
-				Loadbalance:   "random",
-				Retries:       "3",
-				Group:         "huadong_idc",
-				Version:       "1.0.0",
-				Methods: []*MethodConfig{
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser1",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-				},
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 		ShutdownConfig: &ShutdownConfig{
 			Timeout:              "12s",
 			StepTimeout:          "2s",
@@ -135,90 +141,21 @@ func Test_refresh(t *testing.T) {
 	assert.Equal(t, "dubbo", father.ApplicationConfig.Name)
 }
 
-func Test_appExternal_refresh(t *testing.T) {
+func TestAppExternalRefresh(t *testing.T) {
 	c := &BaseConfig{}
-	mockMap := map[string]string{}
-	mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100"
-	mockMap["dubbo.reference.com.MockService.MockService.retries"] = "10"
+	mockMap := getMockMap()
 	mockMap["dubbo.reference.com.MockService.retries"] = "5"
-	mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10"
-	mockMap["dubbo.consumer.check"] = "false"
-	mockMap["dubbo.application.name"] = "dubbo"
 
 	config.GetEnvInstance().UpdateAppExternalConfigMap(mockMap)
 	mockMap["dubbo.consumer.check"] = "true"
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 	father := &ConsumerConfig{
 		Check: &[]bool{true}[0],
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registries: map[string]*RegistryConfig{
-			//"shanghai_reg1": {
-			//	id:         "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",
-			},
-		},
-		References: map[string]*ReferenceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Cluster:       "failover",
-				Loadbalance:   "random",
-				Retries:       "3",
-				Group:         "huadong_idc",
-				Version:       "1.0.0",
-				Methods: []*MethodConfig{
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser1",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-				},
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -231,89 +168,22 @@ func Test_appExternal_refresh(t *testing.T) {
 	assert.Equal(t, "dubbo", father.ApplicationConfig.Name)
 }
 
-func Test_appExternalWithoutId_refresh(t *testing.T) {
+func TestAppExternalWithoutIDRefresh(t *testing.T) {
 	c := &BaseConfig{}
-	mockMap := map[string]string{}
-	mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100"
+	mockMap := getMockMap()
+	delete(mockMap, "dubbo.reference.com.MockService.MockService.retries")
 	mockMap["dubbo.reference.com.MockService.retries"] = "10"
-	mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10"
-	mockMap["dubbo.consumer.check"] = "false"
-	mockMap["dubbo.application.name"] = "dubbo"
 
 	config.GetEnvInstance().UpdateAppExternalConfigMap(mockMap)
 	mockMap["dubbo.consumer.check"] = "true"
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 	father := &ConsumerConfig{
 		Check: &[]bool{true}[0],
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registries: map[string]*RegistryConfig{
-			//"shanghai_reg1": {
-			//	id:         "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",
-			},
-		},
-		References: map[string]*ReferenceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Cluster:       "failover",
-				Loadbalance:   "random",
-				Retries:       "3",
-				Group:         "huadong_idc",
-				Version:       "1.0.0",
-				Methods: []*MethodConfig{
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser",
-						Retries:       "3",
-						Loadbalance:   "random",
-					},
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser1",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-				},
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -326,7 +196,7 @@ func Test_appExternalWithoutId_refresh(t *testing.T) {
 	assert.Equal(t, "dubbo", father.ApplicationConfig.Name)
 }
 
-func Test_refresh_singleRegistry(t *testing.T) {
+func TestRefreshSingleRegistry(t *testing.T) {
 	c := &BaseConfig{}
 	mockMap := map[string]string{}
 	mockMap["dubbo.registry.address"] = "mock100://127.0.0.1:2181"
@@ -339,42 +209,12 @@ func Test_refresh_singleRegistry(t *testing.T) {
 
 	father := &ConsumerConfig{
 		Check: &[]bool{true}[0],
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
+		},
 		Registries: map[string]*RegistryConfig{},
 		Registry:   &RegistryConfig{},
-		References: map[string]*ReferenceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Cluster:       "failover",
-				Loadbalance:   "random",
-				Retries:       "3",
-				Group:         "huadong_idc",
-				Version:       "1.0.0",
-				Methods: []*MethodConfig{
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-					{
-						InterfaceId:   "MockService",
-						InterfaceName: "com.MockService",
-						Name:          "GetUser1",
-						Retries:       "2",
-						Loadbalance:   "random",
-					},
-				},
-			},
-		},
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -387,14 +227,11 @@ func Test_refresh_singleRegistry(t *testing.T) {
 	assert.Equal(t, "dubbo", father.ApplicationConfig.Name)
 }
 
-func Test_refreshProvider(t *testing.T) {
+func TestRefreshProvider(t *testing.T) {
 	c := &BaseConfig{}
-	mockMap := map[string]string{}
-	mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100"
+	mockMap := getMockMap()
+	delete(mockMap, "dubbo.reference.com.MockService.MockService.retries")
 	mockMap["dubbo.service.com.MockService.MockService.retries"] = "10"
-	mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10"
-	mockMap["dubbo.consumer.check"] = "false"
-	mockMap["dubbo.application.name"] = "dubbo"
 	mockMap["dubbo.protocols.jsonrpc1.name"] = "jsonrpc"
 	mockMap["dubbo.protocols.jsonrpc1.ip"] = "127.0.0.1"
 	mockMap["dubbo.protocols.jsonrpc1.port"] = "20001"
@@ -402,48 +239,10 @@ func Test_refreshProvider(t *testing.T) {
 	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
 
 	father := &ProviderConfig{
-		ApplicationConfig: &ApplicationConfig{
-			Organization: "dubbo_org",
-			Name:         "dubbo",
-			Module:       "module",
-			Version:      "2.6.0",
-			Owner:        "dubbo",
-			Environment:  "test"},
-		Registries: map[string]*RegistryConfig{
-			//"shanghai_reg1": {
-			//	id:         "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: baseAppConfig,
 		},
+		Registries: baseRegistries,
 		Services: map[string]*ServiceConfig{
 			"MockService": {
 				InterfaceName: "com.MockService",
@@ -459,13 +258,14 @@ func Test_refreshProvider(t *testing.T) {
 						InterfaceName: "com.MockService",
 						Name:          "GetUser",
 						Retries:       "2",
-						Loadbalance:   "random",
+						LoadBalance:   "random",
 					},
-					{InterfaceId: "MockService",
+					{
+						InterfaceId:   "MockService",
 						InterfaceName: "com.MockService",
 						Name:          "GetUser1",
 						Retries:       "2",
-						Loadbalance:   "random",
+						LoadBalance:   "random",
 					},
 				},
 			},
@@ -482,7 +282,7 @@ func Test_refreshProvider(t *testing.T) {
 	assert.Equal(t, "20001", father.Protocols["jsonrpc1"].Port)
 }
 
-func Test_startConfigCenter(t *testing.T) {
+func TestStartConfigCenter(t *testing.T) {
 	extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory {
 		return &config_center.MockDynamicConfigurationFactory{}
 	})
@@ -499,7 +299,7 @@ func Test_startConfigCenter(t *testing.T) {
 	assert.Equal(t, "ikurento.com", v)
 }
 
-func Test_initializeStruct(t *testing.T) {
+func TestInitializeStruct(t *testing.T) {
 	testConsumerConfig := &ConsumerConfig{}
 	tp := reflect.TypeOf(ConsumerConfig{})
 	v := reflect.New(tp)
@@ -509,7 +309,7 @@ func Test_initializeStruct(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 c84817eaea9d2ba825c96bdb7b2945018b523114..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
 	}
 }
 
@@ -89,7 +104,7 @@ func loadConsumerConfig() {
 	// init other consumer config
 	conConfigType := consumerConfig.ConfigType
 	for key, value := range extension.GetDefaultConfigReader() {
-		if conConfigType == nil {
+		if conConfigType != nil {
 			if v, ok := conConfigType[key]; ok {
 				value = v
 			}
@@ -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 6368fcbd2c7bc675231e7b7835750f26743708af..01d2ca812a278ee8fb80feb584673e2ebe470a01 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 (
@@ -36,10 +37,13 @@ import (
 	"github.com/apache/dubbo-go/config_center"
 )
 
+const mockConsumerConfigPath = "./testdata/consumer_config.yml"
+const mockProviderConfigPath = "./testdata/provider_config.yml"
+
 func TestConfigLoader(t *testing.T) {
-	conPath, err := filepath.Abs("./testdata/consumer_config.yml")
+	conPath, err := filepath.Abs(mockConsumerConfigPath)
 	assert.NoError(t, err)
-	proPath, err := filepath.Abs("./testdata/provider_config.yml")
+	proPath, err := filepath.Abs(mockProviderConfigPath)
 	assert.NoError(t, err)
 
 	assert.Nil(t, consumerConfig)
@@ -90,7 +94,7 @@ func TestLoad(t *testing.T) {
 
 func TestLoadWithSingleReg(t *testing.T) {
 	doInitConsumerWithSingleRegistry()
-	doInitProviderWithSingleRegistry()
+	mockInitProviderWithSingleRegistry()
 
 	ms := &MockService{}
 	SetConsumerService(ms)
@@ -152,7 +156,7 @@ func TestConfigLoaderWithConfigCenter(t *testing.T) {
 
 	conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml")
 	assert.NoError(t, err)
-	proPath, err := filepath.Abs("./testdata/provider_config.yml")
+	proPath, err := filepath.Abs(mockProviderConfigPath)
 	assert.NoError(t, err)
 
 	assert.Nil(t, consumerConfig)
@@ -205,7 +209,7 @@ func TestConfigLoaderWithConfigCenterSingleRegistry(t *testing.T) {
 
 	conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml")
 	assert.NoError(t, err)
-	proPath, err := filepath.Abs("./testdata/provider_config.yml")
+	proPath, err := filepath.Abs(mockProviderConfigPath)
 	assert.NoError(t, err)
 
 	assert.Nil(t, consumerConfig)
@@ -233,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/config_utils.go b/config/config_utils.go
index 6bc574a546ebad548aaa15ce7dc9bcf68b95c3a1..5759ff34f6e5673c4a6de8af3b4e1a34a2e7af4c 100644
--- a/config/config_utils.go
+++ b/config/config_utils.go
@@ -18,6 +18,7 @@
 package config
 
 import (
+	"fmt"
 	"regexp"
 	"strings"
 )
@@ -30,50 +31,35 @@ func mergeValue(str1, str2, def string) string {
 	if str1 == "" && str2 == "" {
 		return def
 	}
-	str := "," + strings.Trim(str1, ",")
-	if str1 == "" {
-		str = "," + strings.Trim(str2, ",")
-	} else if str2 != "" {
-		str = str + "," + strings.Trim(str2, ",")
-	}
+	s1 := strings.Split(str1, ",")
+	s2 := strings.Split(str2, ",")
+	str := "," + strings.Join(append(s1, s2...), ",")
 	defKey := strings.Contains(str, ","+constant.DEFAULT_KEY)
 	if !defKey {
 		str = "," + constant.DEFAULT_KEY + str
 	}
 	str = strings.TrimPrefix(strings.Replace(str, ","+constant.DEFAULT_KEY, ","+def, -1), ",")
+	return removeMinus(strings.Split(str, ","))
+}
 
-	strArr := strings.Split(str, ",")
-	strMap := make(map[string][]int)
-	for k, v := range strArr {
-		add := true
+func removeMinus(strArr []string) string {
+	if len(strArr) == 0 {
+		return ""
+	}
+	var normalStr string
+	var minusStrArr []string
+	for _, v := range strArr {
 		if strings.HasPrefix(v, "-") {
-			v = v[1:]
-			add = false
-		}
-		if _, ok := strMap[v]; !ok {
-			if add {
-				strMap[v] = []int{1, k}
-			}
+			minusStrArr = append(minusStrArr, v[1:])
 		} else {
-			if add {
-				strMap[v][0] += 1
-				strMap[v] = append(strMap[v], k)
-			} else {
-				strMap[v][0] -= 1
-				strMap[v] = strMap[v][:len(strMap[v])-1]
-			}
+			normalStr += fmt.Sprintf(",%s", v)
 		}
 	}
-	strArr = make([]string, len(strArr))
-	for key, value := range strMap {
-		if value[0] == 0 {
-			continue
-		}
-		for i := 1; i < len(value); i++ {
-			strArr[value[i]] = key
-		}
+	normalStr = strings.Trim(normalStr, ",")
+	for _, v := range minusStrArr {
+		normalStr = strings.Replace(normalStr, v, "", 1)
 	}
 	reg := regexp.MustCompile("[,]+")
-	str = reg.ReplaceAllString(strings.Join(strArr, ","), ",")
-	return strings.Trim(str, ",")
+	normalStr = reg.ReplaceAllString(strings.Trim(normalStr, ","), ",")
+	return normalStr
 }
diff --git a/config/config_utils_test.go b/config/config_utils_test.go
index 5170b90c83a0f31bbbe1b5de5bca9b8dc5869ac6..81fc3a3721d5915ec3652e03f80ec203e170d1fa 100644
--- a/config/config_utils_test.go
+++ b/config/config_utils_test.go
@@ -41,3 +41,23 @@ func TestMergeValue(t *testing.T) {
 	str = mergeValue("", "default,-b,e,f", "a,b")
 	assert.Equal(t, "a,e,f", str)
 }
+
+func TestRemoveMinus(t *testing.T) {
+	strList := removeMinus([]string{})
+	assert.Equal(t, strList, "")
+
+	strList = removeMinus([]string{"a", "b", "c", "d", "-a"})
+	assert.Equal(t, strList, "b,c,d")
+
+	strList = removeMinus([]string{"a", "b", "c", "d", "-a", "-b"})
+	assert.Equal(t, strList, "c,d")
+
+	strList = removeMinus([]string{"a", "b", "c", "-c", "-a", "-b"})
+	assert.Equal(t, strList, "")
+
+	strList = removeMinus([]string{"b", "a", "-c", "c"})
+	assert.Equal(t, strList, "b,a")
+
+	strList = removeMinus([]string{"c", "b", "a", "d", "c", "-c", "-a", "e", "f"})
+	assert.Equal(t, strList, "b,d,c,e,f")
+}
diff --git a/config/consumer_config.go b/config/consumer_config.go
index 1453628ab5e24a3607183c349d44e9440868ab60..48f29f0e70028a7c057ee3831b45afa72446f3d0 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 `default:"{}" yaml:"registries" json:"registries" 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/graceful_shutdown_config_test.go b/config/graceful_shutdown_config_test.go
index 583ed70b838a8271a47e180ee3c6eb32cbb46984..80eb5317386f4e9d966f7a9f07635a810727d77c 100644
--- a/config/graceful_shutdown_config_test.go
+++ b/config/graceful_shutdown_config_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestShutdownConfig_GetTimeout(t *testing.T) {
+func TestShutdownConfigGetTimeout(t *testing.T) {
 	config := ShutdownConfig{}
 	assert.False(t, config.RejectRequest)
 	assert.False(t, config.RequestsFinished)
diff --git a/config/graceful_shutdown_signal_darwin.go b/config/graceful_shutdown_signal_darwin.go
index 6f1fa982a30125096c553e65c13bae1a413ea141..1a557dd3ed32cfe571216f61a5a6f8fe064bb254 100644
--- a/config/graceful_shutdown_signal_darwin.go
+++ b/config/graceful_shutdown_signal_darwin.go
@@ -26,7 +26,7 @@ var (
 	// ShutdownSignals receives shutdown signals to process
 	ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP,
 		syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,
-		syscall.SIGABRT, syscall.SIGSYS}
+		syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM}
 
 	// DumpHeapShutdownSignals receives shutdown signals to process
 	DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL,
diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go
index 6f1fa982a30125096c553e65c13bae1a413ea141..1a557dd3ed32cfe571216f61a5a6f8fe064bb254 100644
--- a/config/graceful_shutdown_signal_linux.go
+++ b/config/graceful_shutdown_signal_linux.go
@@ -26,7 +26,7 @@ var (
 	// ShutdownSignals receives shutdown signals to process
 	ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP,
 		syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,
-		syscall.SIGABRT, syscall.SIGSYS}
+		syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM}
 
 	// DumpHeapShutdownSignals receives shutdown signals to process
 	DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL,
diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go
index 3136e5ae15081f026e8a6e602a5174e1d396abf7..89edd27b184e3fddda5e794f686b86397c7019bb 100644
--- a/config/graceful_shutdown_signal_windows.go
+++ b/config/graceful_shutdown_signal_windows.go
@@ -26,7 +26,7 @@ var (
 	// ShutdownSignals receives shutdown signals to process
 	ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL,
 		syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP,
-		syscall.SIGABRT}
+		syscall.SIGABRT, syscall.SIGTERM}
 
 	// DumpHeapShutdownSignals receives shutdown signals to process
 	DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT}
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 635feecc2d433366534566d184e058eb54a881ed..1c585ee79d58826b227df52574b3403639856306 100644
--- a/config/metadata_report_config_test.go
+++ b/config/metadata_report_config_test.go
@@ -24,12 +24,15 @@ import (
 )
 
 func TestMetadataReportConfig_ToUrl(t *testing.T) {
-	metadataReportConfig := MetadataReportConfig{
-		Protocol:   "mock",
+	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/method_config.go b/config/method_config.go
index e64773eb135b2f9ec55377bded815147e2e192af..b64306fd6aa865d219506ea2722067619b00fea7 100644
--- a/config/method_config.go
+++ b/config/method_config.go
@@ -25,13 +25,13 @@ import (
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-// MethodConfig ...
+// MethodConfig defines method config
 type MethodConfig struct {
 	InterfaceId                 string
 	InterfaceName               string
 	Name                        string `yaml:"name"  json:"name,omitempty" property:"name"`
 	Retries                     string `yaml:"retries"  json:"retries,omitempty" property:"retries"`
-	Loadbalance                 string `yaml:"loadbalance"  json:"loadbalance,omitempty" property:"loadbalance"`
+	LoadBalance                 string `yaml:"loadbalance"  json:"loadbalance,omitempty" property:"loadbalance"`
 	Weight                      int64  `yaml:"weight"  json:"weight,omitempty" property:"weight"`
 	TpsLimitInterval            string `yaml:"tps.limit.interval" json:"tps.limit.interval,omitempty" property:"tps.limit.interval"`
 	TpsLimitRate                string `yaml:"tps.limit.rate" json:"tps.limit.rate,omitempty" property:"tps.limit.rate"`
diff --git a/config/provider_config.go b/config/provider_config.go
index 97d037ede008b5fc8cab2febb2177a4e8c3dbd1b..7cd3c1e98bfb8c35abb2b414b782ec709d0a8d0d 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 `default:"{}" yaml:"registries" json:"registries" 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..e9a895d57a90d9fd2d5d08dcd8706e5b2d058174 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())
@@ -216,7 +220,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values {
 	urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter))
 
 	for _, v := range c.Methods {
-		urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.Loadbalance)
+		urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.LoadBalance)
 		urlMap.Set("methods."+v.Name+"."+constant.RETRIES_KEY, v.Retries)
 		urlMap.Set("methods."+v.Name+"."+constant.STICKY_KEY, strconv.FormatBool(v.Sticky))
 		if len(v.RequestTimeout) != 0 {
diff --git a/config/reference_config_test.go b/config/reference_config_test.go
index 7a65e55f09c997cb49b83f1f185faf9338cf0f5a..45cdb2dfaca32f918bc067cf489a3c6fc4820dbc 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",
@@ -99,12 +103,12 @@ func doInitConsumer() {
 					{
 						Name:        "GetUser",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 					},
 					{
 						Name:        "GetUser1",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 						Sticky:      true,
 					},
 				},
@@ -123,6 +127,7 @@ func (m *MockProvider) Reference() string {
 }
 
 func (m *MockProvider) CallBack(res common.CallbackResponse) {
+	// CallBack is a mock function. to implement the interface
 }
 
 func doInitConsumerAsync() {
@@ -135,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{
@@ -165,12 +174,12 @@ func doInitConsumerWithSingleRegistry() {
 					{
 						Name:        "GetUser",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 					},
 					{
 						Name:        "GetUser1",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 					},
 				},
 			},
@@ -178,7 +187,7 @@ func doInitConsumerWithSingleRegistry() {
 	}
 }
 
-func Test_ReferMultireg(t *testing.T) {
+func TestReferMultireg(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("registry", GetProtocol)
 	extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster)
@@ -191,7 +200,7 @@ func Test_ReferMultireg(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_Refer(t *testing.T) {
+func TestRefer(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("registry", GetProtocol)
 	extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster)
@@ -205,7 +214,7 @@ func Test_Refer(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_ReferAsync(t *testing.T) {
+func TestReferAsync(t *testing.T) {
 	doInitConsumerAsync()
 	extension.SetProtocol("registry", GetProtocol)
 	extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster)
@@ -220,7 +229,7 @@ func Test_ReferAsync(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_ReferP2P(t *testing.T) {
+func TestReferP2P(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("dubbo", GetProtocol)
 	m := consumerConfig.References["MockService"]
@@ -234,7 +243,7 @@ func Test_ReferP2P(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_ReferMultiP2P(t *testing.T) {
+func TestReferMultiP2P(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("dubbo", GetProtocol)
 	m := consumerConfig.References["MockService"]
@@ -248,7 +257,7 @@ func Test_ReferMultiP2P(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_ReferMultiP2PWithReg(t *testing.T) {
+func TestReferMultiP2PWithReg(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("dubbo", GetProtocol)
 	extension.SetProtocol("registry", GetProtocol)
@@ -263,7 +272,7 @@ func Test_ReferMultiP2PWithReg(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_Implement(t *testing.T) {
+func TestImplement(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("registry", GetProtocol)
 	extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster)
@@ -276,7 +285,7 @@ func Test_Implement(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_Forking(t *testing.T) {
+func TestForking(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("dubbo", GetProtocol)
 	extension.SetProtocol("registry", GetProtocol)
@@ -293,7 +302,7 @@ func Test_Forking(t *testing.T) {
 	consumerConfig = nil
 }
 
-func Test_Sticky(t *testing.T) {
+func TestSticky(t *testing.T) {
 	doInitConsumer()
 	extension.SetProtocol("dubbo", GetProtocol)
 	extension.SetProtocol("registry", GetProtocol)
@@ -332,4 +341,6 @@ func (*mockRegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter
 	return protocol.NewBaseExporter("test", invoker, &sync.Map{})
 }
 
-func (*mockRegistryProtocol) Destroy() {}
+func (*mockRegistryProtocol) Destroy() {
+	// Destroy is a mock function
+}
diff --git a/config/registry_config_test.go b/config/registry_config_test.go
index 6c2fed605d6c50b483f7ad2900e5a483b3986e1b..6e5dedc34ff5489fc190841bce73cd015eb78132 100644
--- a/config/registry_config_test.go
+++ b/config/registry_config_test.go
@@ -29,7 +29,7 @@ import (
 	"github.com/apache/dubbo-go/common"
 )
 
-func Test_loadRegistries(t *testing.T) {
+func TestLoadRegistries(t *testing.T) {
 	target := "shanghai1"
 	regs := map[string]*RegistryConfig{
 
@@ -47,7 +47,7 @@ func Test_loadRegistries(t *testing.T) {
 	assert.Equal(t, "127.0.0.2:2181,128.0.0.1:2181", urls[0].Location)
 }
 
-func Test_loadRegistries1(t *testing.T) {
+func TestLoadRegistries1(t *testing.T) {
 	target := "shanghai1"
 	regs := map[string]*RegistryConfig{
 
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..16a2bec918d0f9a2de2174324e78ca21e853dabf 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..72e51c1c82562b03736fd0afef79b78d83d6f4f3 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"
 )
 
@@ -58,10 +57,10 @@ func TestRouterInit(t *testing.T) {
 	errPro := RouterInit(errorTestYML)
 	assert.Error(t, errPro)
 
-	assert.Equal(t, 0, directory.GetRouterURLSet().Size())
+	assert.Equal(t, 0, routerURLSet.Size())
 
 	errPro = RouterInit(testYML)
 	assert.NoError(t, errPro)
 
-	assert.Equal(t, 1, directory.GetRouterURLSet().Size())
+	assert.Equal(t, 1, routerURLSet.Size())
 }
diff --git a/config/service_config.go b/config/service_config.go
index a49af18c21e4b51fd9214f5f49e9380ec7716690..d77d4bc1bb057145258e73af5404ea69d730a31c 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -74,14 +74,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 + "."
 }
@@ -95,6 +99,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
 }
 
@@ -108,6 +114,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()
@@ -126,7 +142,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List {
 	return ports
 }
 
-// Export ...
+// Export exports the service
 func (c *ServiceConfig) Export() error {
 	// TODO: config center start here
 
@@ -143,7 +159,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
@@ -179,6 +195,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
@@ -191,23 +210,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
 }
@@ -262,7 +305,7 @@ func (c *ServiceConfig) getUrlMap() url.Values {
 
 	for _, v := range c.Methods {
 		prefix := "methods." + v.Name + "."
-		urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.Loadbalance)
+		urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.LoadBalance)
 		urlMap.Set(prefix+constant.RETRIES_KEY, v.Retries)
 		urlMap.Set(prefix+constant.WEIGHT_KEY, strconv.FormatInt(v.Weight, 10))
 
@@ -277,3 +320,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 e39d32b7f7976cbdc668153bcd901e40dd635556..0f7e404f6e336b8ad254e4007825ecbfcaf9d78b 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": {
@@ -87,16 +56,17 @@ func doInitProvider() {
 					{
 						Name:        "GetUser",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 						Weight:      200,
 					},
 					{
 						Name:        "GetUser1",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 						Weight:      200,
 					},
 				},
+				exported: new(atomic.Bool),
 			},
 			"MockServiceNoRightProtocol": {
 				InterfaceName: "com.MockService",
@@ -111,68 +81,55 @@ func doInitProvider() {
 					{
 						Name:        "GetUser",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 						Weight:      200,
 					},
 					{
 						Name:        "GetUser1",
 						Retries:     "2",
-						Loadbalance: "random",
+						LoadBalance: "random",
 						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",
@@ -183,19 +140,20 @@ func doInitProviderWithSingleRegistry() {
 	}
 }
 
-func Test_Export(t *testing.T) {
+func TestExport(t *testing.T) {
 	doInitProvider()
 	extension.SetProtocol("registry", GetProtocol)
 
 	for i := range providerConfig.Services {
 		service := providerConfig.Services[i]
 		service.Implement(&MockService{})
+		service.Protocols = providerConfig.Protocols
 		service.Export()
 	}
 	providerConfig = nil
 }
 
-func Test_getRandomPort(t *testing.T) {
+func TestgetRandomPort(t *testing.T) {
 	protocolConfigs := make([]*ProtocolConfig, 0, 3)
 
 	ip, err := gxnet.GetLocalIP()
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/apollo/factory.go b/config_center/apollo/factory.go
index f975ce13d8e5832f03051c42f92157532347d283..c52d942c4f481c64d0c465cafbd6f7f8c3bb1346 100644
--- a/config_center/apollo/factory.go
+++ b/config_center/apollo/factory.go
@@ -34,6 +34,7 @@ func createDynamicConfigurationFactory() config_center.DynamicConfigurationFacto
 
 type apolloConfigurationFactory struct{}
 
+// GetDynamicConfiguration gets the dynamic configuration
 func (f *apolloConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) {
 	dynamicConfiguration, err := newApolloConfiguration(url)
 	if err != nil {
diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go
index 1cf65ed22ba0a1f765af66191ed19a04f81b0fe6..48a35093d20c445f6b6ff391f6a2e0e5b737e8f2 100644
--- a/config_center/apollo/listener.go
+++ b/config_center/apollo/listener.go
@@ -36,7 +36,7 @@ func NewApolloListener() *apolloListener {
 	}
 }
 
-// OnChange ...
+// OnChange process each listener
 func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) {
 	for key, change := range changeEvent.Changes {
 		for listener := range a.listeners {
diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go
index de208946f1715878c2afc62fdd41df93f74797c7..8fe0a251239f7bfc6a3f70c3834da1b3af8484ba 100644
--- a/config_center/mock_dynamic_config.go
+++ b/config_center/mock_dynamic_config.go
@@ -33,7 +33,7 @@ import (
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// MockDynamicConfigurationFactory ...
+// MockDynamicConfigurationFactory defines content
 type MockDynamicConfigurationFactory struct {
 	Content string
 }
@@ -96,7 +96,7 @@ func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.Ha
 	return gxset.NewSet(c.content), nil
 }
 
-// MockDynamicConfiguration ...
+// MockDynamicConfiguration uses to parse content and defines listener
 type MockDynamicConfiguration struct {
 	parser   parser.ConfigurationParser
 	content  string
@@ -149,7 +149,7 @@ func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string,
 	return c.GetProperties(key, opts...)
 }
 
-// MockServiceConfigEvent ...
+// MockServiceConfigEvent returns ConfiguratorConfig
 func (c *MockDynamicConfiguration) MockServiceConfigEvent() {
 	config := &parser.ConfiguratorConfig{
 		ConfigVersion: "2.7.1",
@@ -171,7 +171,7 @@ func (c *MockDynamicConfiguration) MockServiceConfigEvent() {
 	c.listener[key].Process(&ConfigChangeEvent{Key: key, Value: string(value), ConfigType: remoting.EventTypeAdd})
 }
 
-// MockApplicationConfigEvent ...
+// MockApplicationConfigEvent returns ConfiguratorConfig
 func (c *MockDynamicConfiguration) MockApplicationConfigEvent() {
 	config := &parser.ConfiguratorConfig{
 		ConfigVersion: "2.7.1",
diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go
index 3b432819f43327888ade3da5303e445d6a2ef0fe..6fe5c4d7df28a7693c732543140ed74f959dc77e 100644
--- a/config_center/nacos/client.go
+++ b/config_center/nacos/client.go
@@ -33,6 +33,7 @@ import (
 )
 
 import (
+	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/logger"
 )
@@ -89,20 +90,17 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error {
 	}
 
 	url := container.GetUrl()
-	logDir = url.GetParam(constant.CONFIG_LOG_DIR_KEY, logDir)
-
+	timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT))
+	if err != nil {
+		logger.Errorf("invalid timeout config %+v,got err %+v",
+			url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err)
+		return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location)
+	}
+	nacosAddresses := strings.Split(url.Location, ",")
 	if container.NacosClient() == nil {
-		//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, "newNacosClient(address:%+v)", url.Location)
-		}
-		nacosAddresses := strings.Split(url.Location, ",")
-		newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout)
+		newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout, url)
 		if err != nil {
-			logger.Warnf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}",
+			logger.Errorf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}",
 				os.nacosName, url.Location, timeout.String(), err)
 			return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location)
 		}
@@ -110,41 +108,19 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error {
 	}
 
 	if container.NacosClient().Client() == nil {
-		svrConfList := []nacosconst.ServerConfig{}
-		for _, nacosAddr := range container.NacosClient().NacosAddrs {
-			split := strings.Split(nacosAddr, ":")
-			port, err := strconv.ParseUint(split[1], 10, 64)
-			if err != nil {
-				logger.Warnf("nacos addr port parse error ,error message is %v", err)
-				continue
-			}
-			svrconf := nacosconst.ServerConfig{
-				IpAddr: split[0],
-				Port:   port,
-			}
-			svrConfList = append(svrConfList, svrconf)
-		}
-
-		client, err := clients.CreateConfigClient(map[string]interface{}{
-			"serverConfigs": svrConfList,
-			"clientConfig": nacosconst.ClientConfig{
-				TimeoutMs:           uint64(int32(container.NacosClient().Timeout / time.Millisecond)),
-				ListenInterval:      10000,
-				NotLoadCacheAtStart: true,
-				LogDir:              logDir,
-			},
-		})
-
-		container.NacosClient().SetClient(&client)
+		configClient, err := initNacosConfigClient(nacosAddresses, timeout, url)
 		if err != nil {
-			logger.Errorf("nacos create config client error:%v", err)
+			logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v",
+				nacosAddresses, timeout.String(), url, err)
+			return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location)
 		}
+		container.NacosClient().SetClient(&configClient)
 	}
 
 	return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL)
 }
 
-func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*NacosClient, error) {
+func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url common.URL) (*NacosClient, error) {
 	var (
 		err error
 		n   *NacosClient
@@ -160,12 +136,24 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N
 		},
 	}
 
-	svrConfList := make([]nacosconst.ServerConfig, 0, len(n.NacosAddrs))
-	for _, nacosAddr := range n.NacosAddrs {
+	configClient, err := initNacosConfigClient(nacosAddrs, timeout, url)
+	if err != nil {
+		logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v",
+			nacosAddrs, timeout.String(), url, err)
+		return n, perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location)
+	}
+	n.SetClient(&configClient)
+
+	return n, nil
+}
+
+func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url common.URL) (config_client.IConfigClient, error) {
+	svrConfList := []nacosconst.ServerConfig{}
+	for _, nacosAddr := range nacosAddrs {
 		split := strings.Split(nacosAddr, ":")
 		port, err := strconv.ParseUint(split[1], 10, 64)
 		if err != nil {
-			logger.Warnf("convert port , source:%s , error:%v ", split[1], err)
+			logger.Errorf("strconv.ParseUint(nacos addr port:%+v) = error %+v", split[1], err)
 			continue
 		}
 		svrconf := nacosconst.ServerConfig{
@@ -174,21 +162,21 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N
 		}
 		svrConfList = append(svrConfList, svrconf)
 	}
-	client, err := clients.CreateConfigClient(map[string]interface{}{
+
+	return clients.CreateConfigClient(map[string]interface{}{
 		"serverConfigs": svrConfList,
 		"clientConfig": nacosconst.ClientConfig{
-			TimeoutMs:           uint64(timeout / time.Millisecond),
-			ListenInterval:      20000,
+			TimeoutMs:           uint64(int32(timeout / time.Millisecond)),
+			ListenInterval:      uint64(int32(timeout / time.Millisecond)),
 			NotLoadCacheAtStart: true,
-			LogDir:              logDir,
+			LogDir:              url.GetParam(constant.NACOS_LOG_DIR_KEY, logDir),
+			CacheDir:            url.GetParam(constant.NACOS_CACHE_DIR_KEY, ""),
+			Endpoint:            url.GetParam(constant.NACOS_ENDPOINT, ""),
+			Username:            url.GetParam(constant.NACOS_USERNAME, ""),
+			Password:            url.GetParam(constant.NACOS_PASSWORD, ""),
+			NamespaceId:         url.GetParam(constant.NACOS_NAMESPACE_ID, ""),
 		},
 	})
-	n.SetClient(&client)
-	if err != nil {
-		return nil, perrors.WithMessagef(err, "nacos clients.CreateConfigClient(nacosAddrs:%+v)", nacosAddrs)
-	}
-
-	return n, nil
 }
 
 // Done Get nacos client exit signal
@@ -233,5 +221,4 @@ func (n *NacosClient) Close() {
 
 	n.stop()
 	n.SetClient(nil)
-	logger.Warnf("nacosClient{name:%s, nacos addr:%s} exit now.", n.name, n.NacosAddrs)
 }
diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go
index 2e7f4645149b8cd70510713d49ced9ba31d66bb6..01319f362b956a0a15be8d5d4dde2a8b2be57c89 100644
--- a/config_center/nacos/client_test.go
+++ b/config_center/nacos/client_test.go
@@ -56,7 +56,7 @@ func TestNewNacosClient(t *testing.T) {
 
 func TestSetNacosClient(t *testing.T) {
 	server := mockCommonNacosServer()
-	nacosURL := server.Listener.Addr().String()
+	nacosURL := "registry://" + server.Listener.Addr().String()
 	registryUrl, _ := common.NewURL(nacosURL)
 	c := &nacosDynamicConfiguration{
 		url:  &registryUrl,
@@ -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/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go
index 6fbdc27d4339150bfec624f7dc5ea0f6a608d7a7..f794221f9cf3c689d52b32a57ab51f7908f17297 100644
--- a/config_center/parser/configuration_parser.go
+++ b/config_center/parser/configuration_parser.go
@@ -35,13 +35,13 @@ import (
 )
 
 const (
-	// ScopeApplication ...
+	// ScopeApplication : scope application
 	ScopeApplication = "application"
-	// GeneralType ...
+	// GeneralType defines the general type
 	GeneralType = "general"
 )
 
-// ConfigurationParser ...
+// ConfigurationParser interface
 type ConfigurationParser interface {
 	Parse(string) (map[string]string, error)
 	ParseToUrls(content string) ([]*common.URL, error)
@@ -50,7 +50,7 @@ type ConfigurationParser interface {
 // DefaultConfigurationParser for supporting properties file in config center
 type DefaultConfigurationParser struct{}
 
-// ConfiguratorConfig ...
+// ConfiguratorConfig defines configurator config
 type ConfiguratorConfig struct {
 	ConfigVersion string       `yaml:"configVersion"`
 	Scope         string       `yaml:"scope"`
@@ -59,7 +59,7 @@ type ConfiguratorConfig struct {
 	Configs       []ConfigItem `yaml:"configs"`
 }
 
-// ConfigItem ...
+// ConfigItem defines config item
 type ConfigItem struct {
 	Type              string            `yaml:"type"`
 	Enabled           bool              `yaml:"enabled"`
@@ -81,7 +81,7 @@ func (parser *DefaultConfigurationParser) Parse(content string) (map[string]stri
 	return pps.Map(), nil
 }
 
-// ParseToUrls ...
+// ParseToUrls is used to parse content to urls
 func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common.URL, error) {
 	config := ConfiguratorConfig{}
 	if err := yaml.Unmarshal([]byte(content), &config); err != nil {
@@ -110,6 +110,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common
 	return allUrls, nil
 }
 
+// serviceItemToUrls is used to transfer item and config to urls
 func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) {
 	var addresses = item.Addresses
 	if len(addresses) == 0 {
@@ -156,6 +157,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR
 	return urls, nil
 }
 
+// nolint
 func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) {
 	var addresses = item.Addresses
 	if len(addresses) == 0 {
@@ -196,6 +198,7 @@ func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, e
 	return urls, nil
 }
 
+// getServiceString returns service string
 func getServiceString(service string) (string, error) {
 	if len(service) == 0 {
 		return "", perrors.New("service field in configuration is null.")
@@ -219,6 +222,7 @@ func getServiceString(service string) (string, error) {
 	return serviceStr, nil
 }
 
+// nolint
 func getParamString(item ConfigItem) (string, error) {
 	var retStr string
 	retStr = retStr + "category="
@@ -241,6 +245,7 @@ func getParamString(item ConfigItem) (string, error) {
 	return retStr, nil
 }
 
+// getEnabledString returns enabled string
 func getEnabledString(item ConfigItem, config ConfiguratorConfig) string {
 	retStr := "&enabled="
 	if len(item.Type) == 0 || item.Type == GeneralType {
diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go
index 747c4be352add3f549eaf02e83da6442a8a84c6a..bc6eb6d6eec4413515228a712cd11810e11f08cc 100644
--- a/config_center/zookeeper/listener.go
+++ b/config_center/zookeeper/listener.go
@@ -27,7 +27,7 @@ import (
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// CacheListener ...
+// CacheListener defines keyListeners and rootPath
 type CacheListener struct {
 	keyListeners sync.Map
 	rootPath     string
diff --git a/filter/filter.go b/filter/filter.go
index d20ca72c345c6812f4bce6df5dbaf683429a9874..804bf3b9df8040c6377648f150c25d421b29b93d 100644
--- a/filter/filter.go
+++ b/filter/filter.go
@@ -27,7 +27,7 @@ import (
 // Filter interface defines the functions of a filter
 // Extension - Filter
 type Filter interface {
-	// Invoke is the core function of a filter, it determins the process of the filter
+	// Invoke is the core function of a filter, it determines the process of the filter
 	Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result
 	// OnResponse updates the results from Invoke and then returns the modified results.
 	OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result
diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go
index 49cdc2287c28ae0cbbd0fcab3700536595bb0f5e..621012c24c0ad12b3bda397148a3ed9c29d080ed 100644
--- a/filter/filter_impl/access_log_filter.go
+++ b/filter/filter_impl/access_log_filter.go
@@ -36,20 +36,20 @@ import (
 const (
 	//used in URL.
 
-	// FileDateFormat ...
+	// nolint
 	FileDateFormat = "2006-01-02"
-	// MessageDateLayout ...
+	// nolint
 	MessageDateLayout = "2006-01-02 15:04:05"
-	// LogMaxBuffer ...
+	// nolint
 	LogMaxBuffer = 5000
-	// LogFileMode ...
+	// nolint
 	LogFileMode = 0600
 
 	// those fields are the data collected by this filter
 
-	// Types ...
+	// nolint
 	Types = "types"
-	// Arguments ...
+	// nolint
 	Arguments = "arguments"
 )
 
diff --git a/filter/filter_impl/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go
index f0de24d2a89f35876a32763eeb75495e8919ecd9..55c328cc30ae892c603fcc65034e48d2a52403d2 100644
--- a/filter/filter_impl/access_log_filter_test.go
+++ b/filter/filter_impl/access_log_filter_test.go
@@ -53,7 +53,7 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) {
 	assert.Nil(t, result.Error())
 }
 
-func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) {
+func TestAccessLogFilterInvokeDefaultConfig(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	url, _ := common.NewURL(
@@ -74,7 +74,7 @@ func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) {
 	assert.Nil(t, result.Error())
 }
 
-func TestAccessLogFilter_OnResponse(t *testing.T) {
+func TestAccessLogFilterOnResponse(t *testing.T) {
 	result := &protocol.RPCResult{}
 	accessLogFilter := GetAccessLogFilter()
 	response := accessLogFilter.OnResponse(nil, result, nil, nil)
diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go
index d5a51d50d93bd9769001964fbb0ae7905eb24980..6b72830e6a1a523b775b9294863ab18f8fe518a2 100644
--- a/filter/filter_impl/active_filter_test.go
+++ b/filter/filter_impl/active_filter_test.go
@@ -36,7 +36,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/mock"
 )
 
-func TestActiveFilter_Invoke(t *testing.T) {
+func TestActiveFilterInvoke(t *testing.T) {
 	invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0))
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	filter := ActiveFilter{}
@@ -50,7 +50,7 @@ func TestActiveFilter_Invoke(t *testing.T) {
 
 }
 
-func TestActiveFilter_OnResponse(t *testing.T) {
+func TestActiveFilterOnResponse(t *testing.T) {
 	c := protocol.CurrentTimeMillis()
 	elapsed := 100
 	invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{
diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go
index 7da5ec7029ea698b1bf1a14ad36123fbec3aacf7..24ba6e47acbffd28eedfffcd0dbaf9717e222946 100644
--- a/filter/filter_impl/echo_filter.go
+++ b/filter/filter_impl/echo_filter.go
@@ -65,7 +65,7 @@ func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ pr
 	return result
 }
 
-// GetFilter ...
+// GetFilter gets the Filter
 func GetFilter() filter.Filter {
 	return &EchoFilter{}
 }
diff --git a/filter/filter_impl/echo_filter_test.go b/filter/filter_impl/echo_filter_test.go
index fc09bdce696c6be3c9e11d0ac864b187d1d85cde..b821a1a30cb8af7e957fac45beecd46067627f91 100644
--- a/filter/filter_impl/echo_filter_test.go
+++ b/filter/filter_impl/echo_filter_test.go
@@ -32,7 +32,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestEchoFilter_Invoke(t *testing.T) {
+func TestEchoFilterInvoke(t *testing.T) {
 	filter := GetFilter()
 	result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil))
 	assert.Equal(t, "OK", result.Result())
diff --git a/filter/filter_impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go
index bfc5096ca089867f6e6234089e387d3f9b48a3aa..5fc309cfb49fbeef5e933a84c1b80654087da701 100644
--- a/filter/filter_impl/execute_limit_filter.go
+++ b/filter/filter_impl/execute_limit_filter.go
@@ -74,7 +74,7 @@ type ExecuteLimitFilter struct {
 	executeState *concurrent.Map
 }
 
-// ExecuteState ...
+// ExecuteState defines the concurrent count
 type ExecuteState struct {
 	concurrentCount int64
 }
diff --git a/filter/filter_impl/execute_limit_filter_test.go b/filter/filter_impl/execute_limit_filter_test.go
index ae8641f2db0b98b59f9939cfc85f3ad096b1bc7f..d36d6edef1ec52c24a9ccd64233b4620b4f10bc7 100644
--- a/filter/filter_impl/execute_limit_filter_test.go
+++ b/filter/filter_impl/execute_limit_filter_test.go
@@ -34,7 +34,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestExecuteLimitFilter_Invoke_Ignored(t *testing.T) {
+func TestExecuteLimitFilterInvokeIgnored(t *testing.T) {
 	methodName := "hello"
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
 
@@ -49,7 +49,7 @@ func TestExecuteLimitFilter_Invoke_Ignored(t *testing.T) {
 	assert.Nil(t, result.Error())
 }
 
-func TestExecuteLimitFilter_Invoke_Configure_Error(t *testing.T) {
+func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) {
 	methodName := "hello1"
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
 
@@ -66,7 +66,7 @@ func TestExecuteLimitFilter_Invoke_Configure_Error(t *testing.T) {
 	assert.Nil(t, result.Error())
 }
 
-func TestExecuteLimitFilter_Invoke(t *testing.T) {
+func TestExecuteLimitFilterInvoke(t *testing.T) {
 	methodName := "hello1"
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
 
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go
index 3f4d714e6b0cbdf48f5e1afce3222a18857041f9..d385054ed98518177aea5e574e034cb35c72e398 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_filter.go
@@ -47,7 +47,7 @@ func init() {
 
 //  when do a generic invoke, struct need to be map
 
-// GenericFilter ...
+// nolint
 type GenericFilter struct{}
 
 // Invoke turns the parameters to map for generic method
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go
index b08229199898a30657682d47c32689dc084f5bf4..e40733209b2e1db972ab576dea54f206a1e888c0 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_filter_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func Test_struct2MapAll(t *testing.T) {
+func TestStruct2MapAll(t *testing.T) {
 	var testData struct {
 		AaAa string `m:"aaAa"`
 		BaBa string
@@ -64,7 +64,7 @@ type testStruct struct {
 	} `m:"xxYy"`
 }
 
-func Test_struct2MapAll_Slice(t *testing.T) {
+func TestStruct2MapAllSlice(t *testing.T) {
 	var testData struct {
 		AaAa string `m:"aaAa"`
 		BaBa string
@@ -89,7 +89,7 @@ func Test_struct2MapAll_Slice(t *testing.T) {
 	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
 }
 
-func Test_struct2MapAll_Map(t *testing.T) {
+func TestStruct2MapAllMap(t *testing.T) {
 	var testData struct {
 		AaAa string
 		Baba map[string]interface{}
diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go
index 6272df6b39b0c18a77721f3a8c9e92618133aa6c..3711e68cce67dacfec074a5e44c080a629bce305 100644
--- a/filter/filter_impl/generic_service_filter.go
+++ b/filter/filter_impl/generic_service_filter.go
@@ -40,9 +40,9 @@ import (
 )
 
 const (
-	// GENERIC_SERVICE ...
+	// GENERIC_SERVICE defines the filter name
 	GENERIC_SERVICE = "generic_service"
-	// GENERIC_SERIALIZATION_DEFAULT ...
+	// nolint
 	GENERIC_SERIALIZATION_DEFAULT = "true"
 )
 
@@ -50,10 +50,10 @@ func init() {
 	extension.SetFilter(GENERIC_SERVICE, GetGenericServiceFilter)
 }
 
-// GenericServiceFilter ...
+// nolint
 type GenericServiceFilter struct{}
 
-// Invoke ...
+// Invoke is used to call service method by invocation
 func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	logger.Infof("invoking generic service filter.")
 	logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments()))
@@ -115,7 +115,7 @@ func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Inv
 	return invoker.Invoke(ctx, newInvocation)
 }
 
-// OnResponse ...
+// nolint
 func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil {
 		v := reflect.ValueOf(result.Result())
@@ -127,7 +127,7 @@ func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.
 	return result
 }
 
-// GetGenericServiceFilter ...
+// nolint
 func GetGenericServiceFilter() filter.Filter {
 	return &GenericServiceFilter{}
 }
diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go
index 2a911659f068b53836b87af5caf5773d8ac5f119..67819717cf07383373f905ed9420a42cc0bfddb9 100644
--- a/filter/filter_impl/generic_service_filter_test.go
+++ b/filter/filter_impl/generic_service_filter_test.go
@@ -51,7 +51,7 @@ func (c *TestStruct) JavaClassName() string {
 
 type TestService struct{}
 
-// MethodOne ...
+// nolint
 func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct,
 	test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) {
 	if test1 == nil {
@@ -72,12 +72,12 @@ func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []T
 	return &TestStruct{}, nil
 }
 
-// Reference ...
+// nolint
 func (*TestService) Reference() string {
 	return "com.test.Path"
 }
 
-func TestGenericServiceFilter_Invoke(t *testing.T) {
+func TestGenericServiceFilterInvoke(t *testing.T) {
 	hessian.RegisterPOJO(&TestStruct{})
 	methodName := "$invoke"
 	m := make(map[string]interface{})
@@ -105,7 +105,7 @@ func TestGenericServiceFilter_Invoke(t *testing.T) {
 	assert.Nil(t, result.Error())
 }
 
-func TestGenericServiceFilter_ResponseTestStruct(t *testing.T) {
+func TestGenericServiceFilterResponseTestStruct(t *testing.T) {
 	ts := &TestStruct{
 		AaAa: "aaa",
 		BaBa: "bbb",
@@ -130,7 +130,7 @@ func TestGenericServiceFilter_ResponseTestStruct(t *testing.T) {
 	assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.Map)
 }
 
-func TestGenericServiceFilter_ResponseString(t *testing.T) {
+func TestGenericServiceFilterResponseString(t *testing.T) {
 	str := "111"
 	result := &protocol.RPCResult{
 		Rest: str,
diff --git a/filter/filter_impl/graceful_shutdown_filter_test.go b/filter/filter_impl/graceful_shutdown_filter_test.go
index 4c670933e3dcec29ad9ae7bfef250b4236ae7c54..87ac2eac616a20617b7a5e68254a1f47ecb8ac17 100644
--- a/filter/filter_impl/graceful_shutdown_filter_test.go
+++ b/filter/filter_impl/graceful_shutdown_filter_test.go
@@ -38,7 +38,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestGenericFilter_Invoke(t *testing.T) {
+func TestGenericFilterInvoke(t *testing.T) {
 	invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]string, 0))
 
 	invokeUrl := common.NewURLWithOptions(
diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go
index 711ef71c44192c5a1d76783a3b3d4cbd0b97632c..e2275149f1229c87ed4ae3f89dc9ae32d9fe6461 100644
--- a/filter/filter_impl/hystrix_filter.go
+++ b/filter/filter_impl/hystrix_filter.go
@@ -37,11 +37,11 @@ import (
 )
 
 const (
-	// HYSTRIX_CONSUMER ...
+	// nolint
 	HYSTRIX_CONSUMER = "hystrix_consumer"
-	// HYSTRIX_PROVIDER ...
+	// nolint
 	HYSTRIX_PROVIDER = "hystrix_provider"
-	// HYSTRIX ...
+	// nolint
 	HYSTRIX = "hystrix"
 )
 
@@ -85,14 +85,14 @@ func NewHystrixFilterError(err error, failByHystrix bool) error {
 	}
 }
 
-// HystrixFilter ...
+// nolint
 type HystrixFilter struct {
 	COrP     bool //true for consumer
 	res      map[string][]*regexp.Regexp
 	ifNewMap sync.Map
 }
 
-// Invoke is an implentation of filter, provides Hystrix pattern latency and fault tolerance
+// Invoke is an implementation of filter, provides Hystrix pattern latency and fault tolerance
 func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	cmdName := fmt.Sprintf("%s&method=%s", invoker.GetUrl().Key(), invocation.MethodName())
 
@@ -256,7 +256,7 @@ func initHystrixConfigProvider() error {
 //	return initHystrixConfig()
 //}
 
-// CommandConfigWithError ...
+// nolint
 type CommandConfigWithError struct {
 	Timeout                int      `yaml:"timeout"`
 	MaxConcurrentRequests  int      `yaml:"max_concurrent_requests"`
@@ -274,14 +274,14 @@ type CommandConfigWithError struct {
 //- ErrorPercentThreshold: it causes circuits to open once the rolling measure of errors exceeds this percent of requests
 //See hystrix doc
 
-// HystrixFilterConfig ...
+// nolint
 type HystrixFilterConfig struct {
 	Configs  map[string]*CommandConfigWithError
 	Default  string
 	Services map[string]ServiceHystrixConfig
 }
 
-// ServiceHystrixConfig ...
+// nolint
 type ServiceHystrixConfig struct {
 	ServiceConfig string `yaml:"service_config"`
 	Methods       map[string]string
diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go
index 71fc097c8bf4752e0cb2b451b0da7e16480b0701..eebbae55565aa7072846d368403605002df0c61b 100644
--- a/filter/filter_impl/hystrix_filter_test.go
+++ b/filter/filter_impl/hystrix_filter_test.go
@@ -90,7 +90,7 @@ func TestGetHystrixFilter(t *testing.T) {
 	assert.NotNil(t, filterGot)
 }
 
-func TestGetConfig_1(t *testing.T) {
+func TestGetConfig1(t *testing.T) {
 	mockInitHystrixConfig()
 	configGot := getConfig("com.ikurento.user.UserProvider", "GetUser", true)
 	assert.NotNil(t, configGot)
@@ -101,7 +101,7 @@ func TestGetConfig_1(t *testing.T) {
 	assert.Equal(t, 5, configGot.RequestVolumeThreshold)
 }
 
-func TestGetConfig_2(t *testing.T) {
+func TestGetConfig2(t *testing.T) {
 	mockInitHystrixConfig()
 	configGot := getConfig("com.ikurento.user.UserProvider", "GetUser0", true)
 	assert.NotNil(t, configGot)
@@ -112,7 +112,7 @@ func TestGetConfig_2(t *testing.T) {
 	assert.Equal(t, 15, configGot.RequestVolumeThreshold)
 }
 
-func TestGetConfig_3(t *testing.T) {
+func TestGetConfig3(t *testing.T) {
 	mockInitHystrixConfig()
 	//This should use default
 	configGot := getConfig("Mock.Service", "GetMock", true)
@@ -145,7 +145,7 @@ func (iv *testMockFailInvoker) Invoke(_ context.Context, _ protocol.Invocation)
 	}
 }
 
-func TestHystrixFilter_Invoke_Success(t *testing.T) {
+func TestHystrixFilterInvokeSuccess(t *testing.T) {
 	hf := &HystrixFilter{}
 	result := hf.Invoke(context.Background(), &testMockSuccessInvoker{}, &invocation.RPCInvocation{})
 	assert.NotNil(t, result)
@@ -153,14 +153,14 @@ func TestHystrixFilter_Invoke_Success(t *testing.T) {
 	assert.NotNil(t, result.Result())
 }
 
-func TestHystrixFilter_Invoke_Fail(t *testing.T) {
+func TestHystrixFilterInvokeFail(t *testing.T) {
 	hf := &HystrixFilter{}
 	result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{})
 	assert.NotNil(t, result)
 	assert.Error(t, result.Error())
 }
 
-func TestHystricFilter_Invoke_CircuitBreak(t *testing.T) {
+func TestHystricFilterInvokeCircuitBreak(t *testing.T) {
 	mockInitHystrixConfig()
 	hystrix.Flush()
 	hf := &HystrixFilter{COrP: true}
@@ -183,7 +183,7 @@ func TestHystricFilter_Invoke_CircuitBreak(t *testing.T) {
 
 }
 
-func TestHystricFilter_Invoke_CircuitBreak_Omit_Exception(t *testing.T) {
+func TestHystricFilterInvokeCircuitBreakOmitException(t *testing.T) {
 	mockInitHystrixConfig()
 	hystrix.Flush()
 	reg, _ := regexp.Compile(".*exception.*")
diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go
index 709404a2af4f4df0dbf625dbbbd673e34975c0db..881106f4bc5a7890569be347122da5144e440c8b 100644
--- a/filter/filter_impl/metrics_filter_test.go
+++ b/filter/filter_impl/metrics_filter_test.go
@@ -38,7 +38,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestMetricsFilter_Invoke(t *testing.T) {
+func TestMetricsFilterInvoke(t *testing.T) {
 
 	// prepare the mock reporter
 	config.GetMetricConfig().Reporters = []string{"mock"}
diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go
new file mode 100644
index 0000000000000000000000000000000000000000..7722d2954f905ece4a1b48628c31c06debf45614
--- /dev/null
+++ b/filter/filter_impl/seata_filter.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 filter_impl
+
+import (
+	"context"
+	"strings"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/filter"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+const (
+	SEATA     = "seata"
+	SEATA_XID = "SEATA_XID"
+)
+
+func init() {
+	extension.SetFilter(SEATA, getSeataFilter)
+}
+
+// SeataFilter when use seata-golang, use this filter to transfer xid
+type SeataFilter struct{}
+
+// When use Seata, transfer xid by attachments
+// Invoke Get Xid by attachment key `SEATA_XID`
+func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
+	logger.Infof("invoking seata filter.")
+	xid := invocation.AttachmentsByKey(SEATA_XID, "")
+	if strings.TrimSpace(xid) != "" {
+		logger.Debugf("Method: %v,Xid: %v", invocation.MethodName(), xid)
+		return invoker.Invoke(context.WithValue(ctx, SEATA_XID, xid), invocation)
+	}
+	return invoker.Invoke(ctx, invocation)
+}
+
+// OnResponse dummy process, returns the result directly
+func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
+	return result
+}
+
+// getSeataFilter create SeataFilter instance
+func getSeataFilter() filter.Filter {
+	return &SeataFilter{}
+}
diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c39897f7aa3a044490e9eece8f352a9db9fc74b
--- /dev/null
+++ b/filter/filter_impl/seata_filter_test.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 filter_impl
+
+import (
+	"context"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+)
+
+type testMockSeataInvoker struct {
+	protocol.BaseInvoker
+}
+
+func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocation) protocol.Result {
+	val := ctx.Value(SEATA_XID)
+	if val != nil {
+		xid, ok := val.(string)
+		if ok {
+			return &protocol.RPCResult{Rest: xid}
+		}
+	}
+	return &protocol.RPCResult{}
+}
+
+func TestSeataFilter_Invoke(t *testing.T) {
+	filter := getSeataFilter()
+	result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{
+		SEATA_XID: "10.30.21.227:8091:2000047792",
+	}))
+	assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result())
+}
diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go
index 23742c66e94d9ecfc09d004441a54aad86ef049e..fe4e38747ed10a40950c87747220339d0d566781 100644
--- a/filter/filter_impl/token_filter.go
+++ b/filter/filter_impl/token_filter.go
@@ -34,7 +34,7 @@ import (
 )
 
 const (
-	// TOKEN ...
+	// nolint
 	TOKEN = "token"
 )
 
@@ -66,7 +66,7 @@ func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, i
 	return result
 }
 
-// GetTokenFilter ...
+// nolint
 func GetTokenFilter() filter.Filter {
 	return &TokenFilter{}
 }
diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go
index b8b297e67267640a1c294541afdd4e062bfebb25..c2f69bd03941b1404585dc5842c56eb2bf3c918f 100644
--- a/filter/filter_impl/token_filter_test.go
+++ b/filter/filter_impl/token_filter_test.go
@@ -34,7 +34,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestTokenFilter_Invoke(t *testing.T) {
+func TestTokenFilterInvoke(t *testing.T) {
 	filter := GetTokenFilter()
 
 	url := common.NewURLWithOptions(
@@ -50,7 +50,7 @@ func TestTokenFilter_Invoke(t *testing.T) {
 	assert.Nil(t, result.Result())
 }
 
-func TestTokenFilter_InvokeEmptyToken(t *testing.T) {
+func TestTokenFilterInvokeEmptyToken(t *testing.T) {
 	filter := GetTokenFilter()
 
 	testUrl := common.URL{}
@@ -61,7 +61,7 @@ func TestTokenFilter_InvokeEmptyToken(t *testing.T) {
 	assert.Nil(t, result.Result())
 }
 
-func TestTokenFilter_InvokeEmptyAttach(t *testing.T) {
+func TestTokenFilterInvokeEmptyAttach(t *testing.T) {
 	filter := GetTokenFilter()
 
 	testUrl := common.NewURLWithOptions(
@@ -72,7 +72,7 @@ func TestTokenFilter_InvokeEmptyAttach(t *testing.T) {
 	assert.NotNil(t, result.Error())
 }
 
-func TestTokenFilter_InvokeNotEqual(t *testing.T) {
+func TestTokenFilterInvokeNotEqual(t *testing.T) {
 	filter := GetTokenFilter()
 
 	testUrl := common.NewURLWithOptions(
diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go
index 7419a4576122d4db334969b0711666b5b2816e60..d495e035de521612cc9b85ec9e817ae3355a818b 100644
--- a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go
+++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go
@@ -29,7 +29,7 @@ import (
 )
 
 const (
-	// FixedWindowKey ...
+	// FixedWindowKey defines tps limit algorithm
 	FixedWindowKey = "fixedWindow"
 )
 
diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go
index 5eaf2f707dcc9dd6cf325988242623dd5161c1a8..83a2f9b54d2a7b3d603538201a822fa0428523f5 100644
--- a/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go
+++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) {
+func TestFixedWindowTpsLimitStrategyImplIsAllowable(t *testing.T) {
 	creator := &fixedWindowStrategyCreator{}
 	strategy := creator.Create(2, 60000)
 	assert.True(t, strategy.IsAllowable())
diff --git a/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go
index 57342d1c443993c49c6124f0ef28dae5ebb203e8..ca5d70ae143eb0d08ba1f3407a7ea10d9c113643 100644
--- a/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go
+++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestSlidingWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) {
+func TestSlidingWindowTpsLimitStrategyImplIsAllowable(t *testing.T) {
 	creator := &slidingWindowStrategyCreator{}
 	strategy := creator.Create(2, 60000)
 	assert.True(t, strategy.IsAllowable())
diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go
index c228c7349ce6ad305051e0ba9b26dddee8c12c71..be76466092b48022330a6fb35ba99bbf0d03134e 100644
--- a/filter/filter_impl/tps/tps_limit_strategy_mock.go
+++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go
@@ -23,6 +23,9 @@ package tps
 
 import (
 	gomock "github.com/golang/mock/gomock"
+)
+
+import (
 	reflect "reflect"
 )
 
diff --git a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go
index 90cd15201cd71aafcc50a1dfb801ece7a5dee26a..80d31f8ff053d4d86b5f565e7018294b049f2801 100644
--- a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go
+++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestThreadSafeFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) {
+func TestThreadSafeFixedWindowTpsLimitStrategyImplIsAllowable(t *testing.T) {
 	creator := &threadSafeFixedWindowStrategyCreator{}
 	strategy := creator.Create(2, 60000)
 	assert.True(t, strategy.IsAllowable())
diff --git a/filter/filter_impl/tps/tps_limiter_method_service_test.go b/filter/filter_impl/tps/tps_limiter_method_service_test.go
index 441224a3e35147b85c3553871dcaa1fefd09db04..edae99ec2d3157ad7f0d81c95a2fb181410475fa 100644
--- a/filter/filter_impl/tps/tps_limiter_method_service_test.go
+++ b/filter/filter_impl/tps/tps_limiter_method_service_test.go
@@ -34,7 +34,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestMethodServiceTpsLimiterImpl_IsAllowable_Only_Service_Level(t *testing.T) {
+func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) {
 	methodName := "hello"
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
 
@@ -61,7 +61,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Only_Service_Level(t *testing.T
 	assert.True(t, result)
 }
 
-func TestMethodServiceTpsLimiterImpl_IsAllowable_No_Config(t *testing.T) {
+func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) {
 	methodName := "hello1"
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
 	// ctrl := gomock.NewController(t)
@@ -77,7 +77,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_No_Config(t *testing.T) {
 	assert.True(t, result)
 }
 
-func TestMethodServiceTpsLimiterImpl_IsAllowable_Method_Level_Override(t *testing.T) {
+func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) {
 	methodName := "hello2"
 	methodConfigPrefix := "methods." + methodName + "."
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
@@ -110,7 +110,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Method_Level_Override(t *testin
 	assert.True(t, result)
 }
 
-func TestMethodServiceTpsLimiterImpl_IsAllowable_Both_Method_And_Service(t *testing.T) {
+func TestMethodServiceTpsLimiterImplIsAllowableBothMethodAndService(t *testing.T) {
 	methodName := "hello3"
 	methodConfigPrefix := "methods." + methodName + "."
 	invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0))
diff --git a/filter/filter_impl/tps_limit_filter_test.go b/filter/filter_impl/tps_limit_filter_test.go
index cc423ae1e5f3589dd60b0c8655f1123c290f0ffc..274e4e6de61b94079e9ad3b2f7a5bcd79a276cc6 100644
--- a/filter/filter_impl/tps_limit_filter_test.go
+++ b/filter/filter_impl/tps_limit_filter_test.go
@@ -39,7 +39,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) {
+func TestTpsLimitFilterInvokeWithNoTpsLimiter(t *testing.T) {
 	tpsFilter := GetTpsLimitFilter()
 	invokeUrl := common.NewURLWithOptions(
 		common.WithParams(url.Values{}),
@@ -55,7 +55,7 @@ func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) {
 
 }
 
-func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) {
+func TestGenericFilterInvokeWithDefaultTpsLimiter(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	mockLimiter := tps.NewMockTpsLimiter(ctrl)
@@ -78,7 +78,7 @@ func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) {
 	assert.Nil(t, result.Result())
 }
 
-func TestGenericFilter_Invoke_With_Default_TpsLimiter_Not_Allow(t *testing.T) {
+func TestGenericFilterInvokeWithDefaultTpsLimiterNotAllow(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	mockLimiter := tps.NewMockTpsLimiter(ctrl)
diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go
index a51692dddcc3400032650f4953eb1e28fb047709..57f4095d49be784d7688d2acf17c1ea0225d0000 100644
--- a/filter/filter_impl/tracing_filter_test.go
+++ b/filter/filter_impl/tracing_filter_test.go
@@ -26,17 +26,14 @@ import (
 	"github.com/opentracing/opentracing-go"
 )
 
-import (
-	"github.com/apache/dubbo-go/common/constant"
-)
-
 import (
 	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/protocol"
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestTracingFilter_Invoke(t *testing.T) {
+func TestTracingFilterInvoke(t *testing.T) {
 	url, _ := common.NewURL(
 		"dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" +
 			"&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." +
diff --git a/go.mod b/go.mod
index 7e4b8d9a4a000e6ee6b138fd57a4df653d416b2f..44f73d342e059cce6b11c87efc3549a46a074178 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,7 @@ 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/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect
-	github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279
-	github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
+	github.com/apache/dubbo-go-hessian2 v1.6.1
 	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
@@ -13,11 +11,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/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
-	github.com/go-errors/errors v1.0.1 // indirect
+	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
@@ -29,34 +26,29 @@ 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/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
+	github.com/hashicorp/vault v0.10.3
 	github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
-	github.com/jonboulle/clockwork v0.1.0 // indirect
 	github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
 	github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect
-	github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
-	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
-	github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
 	github.com/magiconair/properties v1.8.1
 	github.com/matttproud/golang_protobuf_extensions v1.0.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.0.0-20191128082542-fe1b325b125c
+	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
-	github.com/satori/go.uuid v1.2.0
+	github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
 	github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
 	github.com/soheilhy/cmux v0.1.4 // indirect
 	github.com/stretchr/testify v1.5.1
-	github.com/tebeka/strftime v0.1.3 // indirect
 	github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
-	github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
 	github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
 	github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8
 	go.etcd.io/bbolt v1.3.4 // indirect
 	go.uber.org/atomic v1.6.0
 	go.uber.org/zap v1.15.0
+	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
 	google.golang.org/grpc v1.22.1
 	gopkg.in/yaml.v2 v2.2.2
 	k8s.io/api v0.0.0-20190325185214-7544f9db76f6
diff --git a/go.sum b/go.sum
index f5610cb4b29d664c71c434a21c118de94f7986b1..8c8c4ef132e2c4fe5a9631f74fcabc8cae9767c9 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,11 +35,18 @@ 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 h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0=
 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/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4=
+github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -107,10 +113,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=
@@ -136,6 +145,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=
@@ -151,6 +162,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=
@@ -205,6 +217,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,8 +402,10 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c h1:WoCa3AvgQMVKNs+RIFlWPRgY9QVJwUxJDrGxHs0fcRo=
-github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo=
+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=
@@ -397,10 +413,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=
@@ -454,8 +474,11 @@ 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 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 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=
 github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8=
@@ -508,12 +531,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=
@@ -525,7 +553,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=
@@ -542,6 +569,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=
@@ -550,6 +579,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=
@@ -583,7 +614,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=
@@ -628,7 +658,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锛乀he 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..b85e0ac2ac9378d3d9e2572e45255f37f5f84eab
--- /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 (
+	"github.com/apache/dubbo-go/remoting/getty"
+	"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"
+)
+
+func TestConfigurableExporter(t *testing.T) {
+	getty.SetServerConfig(getty.ServerConfig{
+		SessionNumber:  700,
+		SessionTimeout: "20s",
+		GettySessionParam: getty.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/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go
index 9585461434a899ee3e4a7ca689a77b3a7d1b2b27..3c68b63f7b1678075d505799044d1a9b1a7e1da4 100644
--- a/protocol/dubbo/dubbo_invoker_test.go
+++ b/protocol/dubbo/dubbo_invoker_test.go
@@ -42,7 +42,7 @@ import (
 	"github.com/apache/dubbo-go/remoting/getty"
 )
 
-func TestDubboInvoker_Invoke(t *testing.T) {
+func TestDubboInvokerInvoke(t *testing.T) {
 	proto, url := InitTest(t)
 
 	c := getExchangeClient(url)
diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go
index 07b890f1345eaf994b6d7ba90642a116526355c4..352d980017932f7c2f8f2538bb0c689ebe4e2ee2 100644
--- a/protocol/dubbo/dubbo_protocol_test.go
+++ b/protocol/dubbo/dubbo_protocol_test.go
@@ -82,14 +82,6 @@ func TestDubboProtocol_Export(t *testing.T) {
 		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
 		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
 		"side=provider&timeout=3000&timestamp=1556509797245")
-	assert.NoError(t, err)
-	exporter := proto.Export(protocol.NewBaseInvoker(url))
-
-	// make sure url
-	eq := exporter.GetInvoker().GetUrl().URLEqual(url)
-	assert.True(t, eq)
-
-	// second service: the same path and the different version
 	url2, err := common.NewURL("dubbo://127.0.0.1:20095/com.ikurento.user.UserProvider?anyhost=true&"+
 		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
 		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+
@@ -104,7 +96,7 @@ func TestDubboProtocol_Export(t *testing.T) {
 	// make sure exporterMap after 'Unexport'
 	_, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey())
 	assert.True(t, ok)
-	exporter.Unexport()
+	exporter2.Unexport()
 	_, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey())
 	assert.False(t, ok)
 
diff --git a/protocol/grpc/common_test.go b/protocol/grpc/common_test.go
index 33c2fc617d52795d13d9b4fc02054ef5a79d0934..b732283a9bf9d316c1b9d4356a1b9563bfa1d3ec 100644
--- a/protocol/grpc/common_test.go
+++ b/protocol/grpc/common_test.go
@@ -106,7 +106,7 @@ func dubboGreeterSayHelloHandler(srv interface{}, ctx context.Context,
 		Server:     srv,
 		FullMethod: "/helloworld.Greeter/SayHello",
 	}
-	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+	handler := func(context.Context, interface{}) (interface{}, error) {
 		result := base.GetProxyImpl().Invoke(context.Background(), invo)
 		return result.Result(), result.Error()
 	}
diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go
index 79962b59e29bb0e3aeb58776f6c26abc2e6832de..0dc764854d61576892800180041c53f0a7735c7c 100644
--- a/protocol/grpc/grpc_exporter.go
+++ b/protocol/grpc/grpc_exporter.go
@@ -28,7 +28,7 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// GrpcExporter ...
+// nolint
 type GrpcExporter struct {
 	*protocol.BaseExporter
 }
diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go
index e150d05e6fb39890bd3e355f4042b4ef34db42ed..737e8c40a063b07e56d7c90f7de04670461ce103 100644
--- a/protocol/grpc/grpc_invoker.go
+++ b/protocol/grpc/grpc_invoker.go
@@ -37,14 +37,14 @@ import (
 
 var errNoReply = errors.New("request need @response")
 
-// GrpcInvoker ...
+// nolint
 type GrpcInvoker struct {
 	protocol.BaseInvoker
 	quitOnce sync.Once
 	client   *Client
 }
 
-// NewGrpcInvoker ...
+// NewGrpcInvoker returns a Grpc invoker instance
 func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker {
 	return &GrpcInvoker{
 		BaseInvoker: *protocol.NewBaseInvoker(url),
@@ -52,7 +52,7 @@ func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker {
 	}
 }
 
-// Invoke ...
+// Invoke is used to call service method by invocation
 func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	var (
 		result protocol.RPCResult
@@ -81,17 +81,17 @@ func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio
 	return &result
 }
 
-// IsAvailable ...
+// IsAvailable get available status
 func (gi *GrpcInvoker) IsAvailable() bool {
 	return gi.BaseInvoker.IsAvailable() && gi.client.GetState() != connectivity.Shutdown
 }
 
-// IsDestroyed ...
+// IsDestroyed get destroyed status
 func (gi *GrpcInvoker) IsDestroyed() bool {
 	return gi.BaseInvoker.IsDestroyed() && gi.client.GetState() == connectivity.Shutdown
 }
 
-// Destroy ...
+// Destroy will destroy gRPC's invoker and client, so it is only called once
 func (gi *GrpcInvoker) Destroy() {
 	gi.quitOnce.Do(func() {
 		gi.BaseInvoker.Destroy()
diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go
index 3054ada13340a9c9cc038a63d89c45ced9ec7ac7..d5ebbb4f47a324791a3367a649bd49b06281540f 100644
--- a/protocol/grpc/grpc_invoker_test.go
+++ b/protocol/grpc/grpc_invoker_test.go
@@ -33,11 +33,19 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
+const (
+	mockGrpcCommonUrl = "grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl" +
+		"&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter" +
+		"&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=" +
+		"&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown&registry.role=3&remote.timestamp=1576923717&retries=" +
+		"&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider&timestamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!"
+)
+
 func TestInvoke(t *testing.T) {
 	go internal.InitGrpcServer()
 	defer internal.ShutdownGrpcServer()
 
-	url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown&registry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider&timestamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!")
+	url, err := common.NewURL(mockGrpcCommonUrl)
 	assert.Nil(t, err)
 
 	cli := NewClient(url)
diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go
index d028f8ef4285b0183e6e0b5b32deede59ce5c531..87ce714fc7437eca53509bb368ed7bc774929634 100644
--- a/protocol/grpc/grpc_protocol_test.go
+++ b/protocol/grpc/grpc_protocol_test.go
@@ -32,12 +32,12 @@ import (
 	"github.com/apache/dubbo-go/protocol/grpc/internal"
 )
 
-func TestGrpcProtocol_Export(t *testing.T) {
+func TestGrpcProtocolExport(t *testing.T) {
 	// Export
 	addService()
 
 	proto := GetProtocol()
-	url, err := common.NewURL("grpc://127.0.0.1:40000/GrpcGreeterImpl?accesslog=&app.version=0.0.1&application=BDTService&bean.name=GrpcGreeterImpl&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&registry.role=3&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&timestamp=1576923717&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100")
+	url, err := common.NewURL(mockGrpcCommonUrl)
 	assert.NoError(t, err)
 	exporter := proto.Export(protocol.NewBaseInvoker(url))
 	time.Sleep(time.Second)
@@ -61,13 +61,13 @@ func TestGrpcProtocol_Export(t *testing.T) {
 	assert.False(t, ok)
 }
 
-func TestGrpcProtocol_Refer(t *testing.T) {
+func TestGrpcProtocolRefer(t *testing.T) {
 	go internal.InitGrpcServer()
 	defer internal.ShutdownGrpcServer()
 	time.Sleep(time.Second)
 
 	proto := GetProtocol()
-	url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown&registry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider&timestamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!")
+	url, err := common.NewURL(mockGrpcCommonUrl)
 	assert.NoError(t, err)
 	invoker := proto.Refer(url)
 
diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go
index a84b4a7975a57065f565885b33b5e7660981dfb4..5086364097790faf45a1127f6e14ecff7ffbdc86 100644
--- a/protocol/invocation/rpcinvocation.go
+++ b/protocol/invocation/rpcinvocation.go
@@ -159,6 +159,8 @@ func (r *RPCInvocation) Invoker() protocol.Invoker {
 
 // nolint
 func (r *RPCInvocation) SetInvoker(invoker protocol.Invoker) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
 	r.invoker = invoker
 }
 
diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go
index 5fca66d99399b2974f858cbedb31d9615a303637..2a2ddfeeeb52b865e96ccff69d2b39d8a671ed41 100644
--- a/protocol/jsonrpc/http.go
+++ b/protocol/jsonrpc/http.go
@@ -47,7 +47,7 @@ import (
 // Request
 // ////////////////////////////////////////////
 
-// Request ...
+// Request is HTTP protocol request
 type Request struct {
 	ID          int64
 	group       string
diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go
index 188b036dfca5087f4729afbc64017d27b9aee902..ae97cf5292b0bbf52372b52da4bb70bc14f084ed 100644
--- a/protocol/jsonrpc/http_test.go
+++ b/protocol/jsonrpc/http_test.go
@@ -44,7 +44,15 @@ type (
 	}
 )
 
-func TestHTTPClient_Call(t *testing.T) {
+const (
+	mockJsonCommonUrl = "jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" +
+		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
+		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
+		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
+		"side=provider&timeout=3000&timestamp=1556509797245&bean.name=UserProvider"
+)
+
+func TestHTTPClientCall(t *testing.T) {
 
 	methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "jsonrpc", &UserProvider{})
 	assert.NoError(t, err)
@@ -52,11 +60,7 @@ func TestHTTPClient_Call(t *testing.T) {
 
 	// Export
 	proto := GetProtocol()
-	url, err := common.NewURL("jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" +
-		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
-		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
-		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
-		"side=provider&timeout=3000&timestamp=1556509797245&bean.name=UserProvider")
+	url, err := common.NewURL(mockJsonCommonUrl)
 	assert.NoError(t, err)
 	proto.Export(&proxy_factory.ProxyInvoker{
 		BaseInvoker: *protocol.NewBaseInvoker(url),
diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go
index 389ead9c1a530742c872a238d89b2df5ae3462ca..506c4c953b1b1113b43669171efdeeaeb6fca10d 100644
--- a/protocol/jsonrpc/json.go
+++ b/protocol/jsonrpc/json.go
@@ -124,10 +124,8 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) {
 	if param != nil {
 		switch k := reflect.TypeOf(param).Kind(); k {
 		case reflect.Map:
-			if reflect.TypeOf(param).Key().Kind() == reflect.String {
-				if reflect.ValueOf(param).IsNil() {
-					param = nil
-				}
+			if reflect.TypeOf(param).Key().Kind() == reflect.String && reflect.ValueOf(param).IsNil() {
+				param = nil
 			}
 		case reflect.Slice:
 			if reflect.ValueOf(param).IsNil() {
@@ -137,10 +135,8 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) {
 		case reflect.Ptr:
 			switch ptrK := reflect.TypeOf(param).Elem().Kind(); ptrK {
 			case reflect.Map:
-				if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String {
-					if reflect.ValueOf(param).Elem().IsNil() {
-						param = nil
-					}
+				if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String && reflect.ValueOf(param).Elem().IsNil() {
+					param = nil
 				}
 			case reflect.Slice:
 				if reflect.ValueOf(param).Elem().IsNil() {
@@ -292,7 +288,7 @@ type ServerCodec struct {
 
 var (
 	null = json.RawMessage([]byte("null"))
-	// Version ...
+	// Version is json RPC's version
 	Version = "2.0"
 )
 
diff --git a/protocol/jsonrpc/json_test.go b/protocol/jsonrpc/json_test.go
index ade74246121b5f275c8dbeaa5923228dbab2804f..a3814e9ad5e1df2c3a8d8cc4305e5787b44596ec 100644
--- a/protocol/jsonrpc/json_test.go
+++ b/protocol/jsonrpc/json_test.go
@@ -30,7 +30,7 @@ type TestData struct {
 	Test string
 }
 
-func TestJsonClientCodec_Write(t *testing.T) {
+func TestJsonClientCodecWrite(t *testing.T) {
 	cd := &CodecData{
 		ID:     1,
 		Method: "GetUser",
@@ -46,7 +46,7 @@ func TestJsonClientCodec_Write(t *testing.T) {
 	assert.EqualError(t, err, "unsupported param type: int")
 }
 
-func TestJsonClientCodec_Read(t *testing.T) {
+func TestJsonClientCodecRead(t *testing.T) {
 	codec := newJsonClientCodec()
 	codec.pending[1] = "GetUser"
 	rsp := &TestData{}
@@ -60,7 +60,7 @@ func TestJsonClientCodec_Read(t *testing.T) {
 	assert.EqualError(t, err, "{\"code\":-32000,\"message\":\"error\"}")
 }
 
-func TestServerCodec_Write(t *testing.T) {
+func TestServerCodecWrite(t *testing.T) {
 	codec := newServerCodec()
 	a := json.RawMessage([]byte("1"))
 	codec.req = serverRequest{Version: "1.0", Method: "GetUser", ID: &a}
@@ -73,7 +73,7 @@ func TestServerCodec_Write(t *testing.T) {
 	assert.Equal(t, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"Test\":\"test\"},\"error\":{\"code\":-32000,\"message\":\"error\"}}\n", string(data))
 }
 
-func TestServerCodec_Read(t *testing.T) {
+func TestServerCodecRead(t *testing.T) {
 	codec := newServerCodec()
 	header := map[string]string{}
 	err := codec.ReadHeader(header, []byte("{\"jsonrpc\":\"2.0\",\"method\":\"GetUser\",\"params\":[\"args\",2],\"id\":1}\n"))
diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go
index 0f14ba11e2dec18bbd4d63e87e8c0fb2727f3755..d7124ca07c6ba6d79dc72e7fb6bd98cd4b3a97b2 100644
--- a/protocol/jsonrpc/jsonrpc_invoker_test.go
+++ b/protocol/jsonrpc/jsonrpc_invoker_test.go
@@ -34,7 +34,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestJsonrpcInvoker_Invoke(t *testing.T) {
+func TestJsonrpcInvokerInvoke(t *testing.T) {
 
 	methods, err := common.ServiceMap.Register("UserProvider", "jsonrpc", &UserProvider{})
 	assert.NoError(t, err)
diff --git a/protocol/jsonrpc/jsonrpc_protocol_test.go b/protocol/jsonrpc/jsonrpc_protocol_test.go
index c00bed12fe9fbb4937f21810cee548a25e3b1c05..10a9016913c77551efc54a92149ef377ade399f5 100644
--- a/protocol/jsonrpc/jsonrpc_protocol_test.go
+++ b/protocol/jsonrpc/jsonrpc_protocol_test.go
@@ -34,7 +34,7 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func TestJsonrpcProtocol_Export(t *testing.T) {
+func TestJsonrpcProtocolExport(t *testing.T) {
 	// Export
 	proto := GetProtocol()
 	url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
@@ -65,7 +65,7 @@ func TestJsonrpcProtocol_Export(t *testing.T) {
 	assert.False(t, ok)
 }
 
-func TestJsonrpcProtocol_Refer(t *testing.T) {
+func TestJsonrpcProtocolRefer(t *testing.T) {
 	// Refer
 	proto := GetProtocol()
 	url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go
index 8763c20410915d4e81afd35691be7d9ed490f7a1..2e9ffed06f03d196c91f6679df21538612bac405 100644
--- a/protocol/protocolwrapper/mock_protocol_filter.go
+++ b/protocol/protocolwrapper/mock_protocol_filter.go
@@ -45,5 +45,5 @@ func (pfw *mockProtocolFilter) Refer(url common.URL) protocol.Invoker {
 
 // Destroy will do nothing
 func (pfw *mockProtocolFilter) Destroy() {
-
+	return
 }
diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go
index 6cb343c160323805836aa00a78def97bf5878b54..2d8046087fb5055f3e1fb2f1ff5a04c56660aeb1 100644
--- a/protocol/protocolwrapper/protocol_filter_wrapper.go
+++ b/protocol/protocolwrapper/protocol_filter_wrapper.go
@@ -89,7 +89,7 @@ func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker {
 	return next
 }
 
-// GetProtocol ...
+// nolint
 func GetProtocol() protocol.Protocol {
 	return &ProtocolFilterWrapper{}
 }
@@ -98,30 +98,30 @@ func GetProtocol() protocol.Protocol {
 // filter invoker
 ///////////////////////////
 
-// FilterInvoker ...
+// FilterInvoker defines invoker and filter
 type FilterInvoker struct {
 	next    protocol.Invoker
 	invoker protocol.Invoker
 	filter  filter.Filter
 }
 
-// GetUrl ...
+// GetUrl is used to get url from FilterInvoker
 func (fi *FilterInvoker) GetUrl() common.URL {
 	return fi.invoker.GetUrl()
 }
 
-// IsAvailable ...
+// IsAvailable is used to get available status
 func (fi *FilterInvoker) IsAvailable() bool {
 	return fi.invoker.IsAvailable()
 }
 
-// Invoke ...
+// Invoke is used to call service method by invocation
 func (fi *FilterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	result := fi.filter.Invoke(ctx, fi.next, invocation)
 	return fi.filter.OnResponse(ctx, result, fi.invoker, invocation)
 }
 
-// Destroy ...
+// Destroy will destroy invoker
 func (fi *FilterInvoker) Destroy() {
 	fi.invoker.Destroy()
 }
diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go
index 8491d57462d47d6af72040d41b78dcb30e6da697..b03ea7b9b66d926aff8851f88e1bd7434b254903 100644
--- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go
+++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go
@@ -36,7 +36,7 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func TestProtocolFilterWrapper_Export(t *testing.T) {
+func TestProtocolFilterWrapperExport(t *testing.T) {
 	filtProto := extension.GetProtocol(FILTER)
 	filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{}
 
@@ -48,7 +48,7 @@ func TestProtocolFilterWrapper_Export(t *testing.T) {
 	assert.True(t, ok)
 }
 
-func TestProtocolFilterWrapper_Refer(t *testing.T) {
+func TestProtocolFilterWrapperRefer(t *testing.T) {
 	filtProto := extension.GetProtocol(FILTER)
 	filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{}
 
diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go
index b60f50a5a70cde01a051dbb2a4490cbb792e0116..f301d945045a445b5370252044442080f042d126 100644
--- a/protocol/rest/client/client_impl/resty_client.go
+++ b/protocol/rest/client/client_impl/resty_client.go
@@ -50,7 +50,7 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient {
 	client := resty.New()
 	client.SetTransport(
 		&http.Transport{
-			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+			DialContext: func(_ context.Context, network, addr string) (net.Conn, error) {
 				c, err := net.DialTimeout(network, addr, restOption.ConnectTimeout)
 				if err != nil {
 					return nil, err
diff --git a/protocol/rest/config/reader/rest_config_reader_test.go b/protocol/rest/config/reader/rest_config_reader_test.go
index d2dba40b9b85a6cd7772e0fee619720c79e91eb4..c6f891262ce3ba5ec6645ade9084005d14716789 100644
--- a/protocol/rest/config/reader/rest_config_reader_test.go
+++ b/protocol/rest/config/reader/rest_config_reader_test.go
@@ -31,7 +31,7 @@ import (
 	"github.com/apache/dubbo-go/protocol/rest/config"
 )
 
-func TestRestConfigReader_ReadConsumerConfig(t *testing.T) {
+func TestRestConfigReaderReadConsumerConfig(t *testing.T) {
 	bs, err := yaml.LoadYMLConfig("./testdata/consumer_config.yml")
 	assert.NoError(t, err)
 	configReader := NewRestConfigReader()
@@ -40,7 +40,7 @@ func TestRestConfigReader_ReadConsumerConfig(t *testing.T) {
 	assert.NotEmpty(t, config.GetRestConsumerServiceConfigMap())
 }
 
-func TestRestConfigReader_ReadProviderConfig(t *testing.T) {
+func TestRestConfigReaderReadProviderConfig(t *testing.T) {
 	bs, err := yaml.LoadYMLConfig("./testdata/provider_config.yml")
 	assert.NoError(t, err)
 	configReader := NewRestConfigReader()
diff --git a/protocol/rest/config/rest_config.go b/protocol/rest/config/rest_config.go
index 168ec8ce525fc7fd5d4a30d4f11ba7bf42d1c921..4732dd8e4eae3ba874fdd8ed95380e6ace3ab66d 100644
--- a/protocol/rest/config/rest_config.go
+++ b/protocol/rest/config/rest_config.go
@@ -26,7 +26,7 @@ var (
 	restProviderServiceConfigMap map[string]*RestServiceConfig
 )
 
-// RestConsumerConfig ...
+// nolint
 type RestConsumerConfig struct {
 	Client                string                        `default:"resty" yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"`
 	Produces              string                        `default:"application/json" yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
@@ -34,7 +34,7 @@ type RestConsumerConfig struct {
 	RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"references" json:"references,omitempty" property:"references"`
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the RestConsumerConfig by @unmarshal function
 func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -46,7 +46,7 @@ func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er
 	return nil
 }
 
-// RestProviderConfig ...
+// nolint
 type RestProviderConfig struct {
 	Server                string                        `default:"go-restful" yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"`
 	Produces              string                        `default:"*/*" yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
@@ -54,7 +54,7 @@ type RestProviderConfig struct {
 	RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"services" json:"services,omitempty" property:"services"`
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the RestProviderConfig by @unmarshal function
 func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -66,7 +66,7 @@ func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) er
 	return nil
 }
 
-// RestServiceConfig ...
+// nolint
 type RestServiceConfig struct {
 	InterfaceName        string              `required:"true"  yaml:"interface"  json:"interface,omitempty" property:"interface"`
 	Url                  string              `yaml:"url"  json:"url,omitempty" property:"url"`
@@ -80,7 +80,7 @@ type RestServiceConfig struct {
 	RestMethodConfigsMap map[string]*RestMethodConfig
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the RestServiceConfig by @unmarshal function
 func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -92,7 +92,7 @@ func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
 	return nil
 }
 
-// RestMethodConfig ...
+// nolint
 type RestMethodConfig struct {
 	InterfaceName  string
 	MethodName     string `required:"true" yaml:"name"  json:"name,omitempty" property:"name"`
@@ -110,7 +110,7 @@ type RestMethodConfig struct {
 	HeadersMap     map[int]string
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the RestMethodConfig by @unmarshal function
 func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -122,32 +122,32 @@ func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro
 	return nil
 }
 
-// GetRestConsumerServiceConfig ...
+// nolint
 func GetRestConsumerServiceConfig(path string) *RestServiceConfig {
 	return restConsumerServiceConfigMap[path]
 }
 
-// GetRestProviderServiceConfig ...
+// nolint
 func GetRestProviderServiceConfig(path string) *RestServiceConfig {
 	return restProviderServiceConfigMap[path]
 }
 
-// SetRestConsumerServiceConfigMap ...
+// nolint
 func SetRestConsumerServiceConfigMap(configMap map[string]*RestServiceConfig) {
 	restConsumerServiceConfigMap = configMap
 }
 
-// SetRestProviderServiceConfigMap ...
+// nolint
 func SetRestProviderServiceConfigMap(configMap map[string]*RestServiceConfig) {
 	restProviderServiceConfigMap = configMap
 }
 
-// GetRestConsumerServiceConfigMap ...
+// nolint
 func GetRestConsumerServiceConfigMap() map[string]*RestServiceConfig {
 	return restConsumerServiceConfigMap
 }
 
-// GetRestProviderServiceConfigMap ...
+// nolint
 func GetRestProviderServiceConfigMap() map[string]*RestServiceConfig {
 	return restProviderServiceConfigMap
 }
diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go
index 1ee208615ea07e3f2850920492ab9e9821e7ffef..e39558caeae9811817cc26a1717c1b8e3729234c 100644
--- a/protocol/rest/rest_exporter.go
+++ b/protocol/rest/rest_exporter.go
@@ -28,16 +28,19 @@ import (
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// nolint
 type RestExporter struct {
 	protocol.BaseExporter
 }
 
+// NewRestExporter returns a RestExporter
 func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *RestExporter {
 	return &RestExporter{
 		BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap),
 	}
 }
 
+// Unexport unexport the RestExporter
 func (re *RestExporter) Unexport() {
 	serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
 	interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go
index 121d1217efd3baea0f961b67e243e9a0450aefc2..691beeda4085f316075fe55b9328bc86d6328187 100644
--- a/protocol/rest/rest_invoker.go
+++ b/protocol/rest/rest_invoker.go
@@ -35,12 +35,14 @@ import (
 	"github.com/apache/dubbo-go/protocol/rest/config"
 )
 
+// nolint
 type RestInvoker struct {
 	protocol.BaseInvoker
 	client              client.RestClient
 	restMethodConfigMap map[string]*config.RestMethodConfig
 }
 
+// NewRestInvoker returns a RestInvoker
 func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker {
 	return &RestInvoker{
 		BaseInvoker:         *protocol.NewBaseInvoker(url),
@@ -49,6 +51,7 @@ func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig
 	}
 }
 
+// Invoke is used to call service method by invocation
 func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	inv := invocation.(*invocation_impl.RPCInvocation)
 	methodConfig := ri.restMethodConfigMap[inv.MethodName()]
@@ -95,6 +98,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio
 	return &result
 }
 
+// restStringMapTransform is used to transform rest map
 func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) {
 	resMap := make(map[string]string, len(paramsMap))
 	for k, v := range paramsMap {
@@ -106,6 +110,7 @@ func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[s
 	return resMap, nil
 }
 
+// nolint
 func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) {
 	header := http.Header{}
 	headersMap := methodConfig.HeadersMap
diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go
index 2ea260c58d03c27a691e48b953ce6d64f75040a2..9df97a211e9d90daa3206b94926ceeac42df4606 100644
--- a/protocol/rest/rest_invoker_test.go
+++ b/protocol/rest/rest_invoker_test.go
@@ -39,7 +39,15 @@ import (
 	"github.com/apache/dubbo-go/protocol/rest/server/server_impl"
 )
 
-func TestRestInvoker_Invoke(t *testing.T) {
+const (
+	mockRestCommonUrl = "rest://127.0.0.1:8877/com.ikurento.user.UserProvider?anyhost=true&" +
+		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
+		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
+		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
+		"side=provider&timeout=3000&timestamp=1556509797245"
+)
+
+func TestRestInvokerInvoke(t *testing.T) {
 	// Refer
 	proto := GetRestProtocol()
 	defer proto.Destroy()
@@ -55,11 +63,7 @@ func TestRestInvoker_Invoke(t *testing.T) {
 		chain.ProcessFilter(request, response)
 	})
 
-	url, err := common.NewURL("rest://127.0.0.1:8877/com.ikurento.user.UserProvider?anyhost=true&" +
-		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
-		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
-		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
-		"side=provider&timeout=3000&timestamp=1556509797245")
+	url, err := common.NewURL(mockRestCommonUrl)
 	assert.NoError(t, err)
 	_, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{})
 	assert.NoError(t, err)
diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go
index e15eeb39d72212eb9f1d0235313eba231d3b0a36..0cd26c240a7eb1b83a04ca2f57c332bbda994967 100644
--- a/protocol/rest/rest_protocol.go
+++ b/protocol/rest/rest_protocol.go
@@ -44,10 +44,12 @@ var (
 
 const REST = "rest"
 
+// nolint
 func init() {
 	extension.SetProtocol(REST, GetRestProtocol)
 }
 
+// nolint
 type RestProtocol struct {
 	protocol.BaseProtocol
 	serverLock sync.Mutex
@@ -56,6 +58,7 @@ type RestProtocol struct {
 	clientMap  map[client.RestOptions]client.RestClient
 }
 
+// NewRestProtocol returns a RestProtocol
 func NewRestProtocol() *RestProtocol {
 	return &RestProtocol{
 		BaseProtocol: protocol.NewBaseProtocol(),
@@ -64,6 +67,7 @@ func NewRestProtocol() *RestProtocol {
 	}
 }
 
+// Export export rest service
 func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	url := invoker.GetUrl()
 	serviceKey := url.ServiceKey()
@@ -81,6 +85,7 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	return exporter
 }
 
+// Refer create rest service reference
 func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker {
 	// create rest_invoker
 	var requestTimeout = config.GetConsumerConfig().RequestTimeout
@@ -101,6 +106,7 @@ func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker {
 	return invoker
 }
 
+// nolint
 func (rp *RestProtocol) getServer(url common.URL, serverType string) server.RestServer {
 	restServer, ok := rp.serverMap[url.Location]
 	if ok {
@@ -122,6 +128,7 @@ func (rp *RestProtocol) getServer(url common.URL, serverType string) server.Rest
 	return restServer
 }
 
+// nolint
 func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType string) client.RestClient {
 	restClient, ok := rp.clientMap[restOptions]
 	if ok {
@@ -138,6 +145,7 @@ func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType str
 	return restClient
 }
 
+// Destroy destroy rest service
 func (rp *RestProtocol) Destroy() {
 	// destroy rest_server
 	rp.BaseProtocol.Destroy()
@@ -150,6 +158,7 @@ func (rp *RestProtocol) Destroy() {
 	}
 }
 
+// GetRestProtocol get a rest protocol
 func GetRestProtocol() protocol.Protocol {
 	if restProtocol == nil {
 		restProtocol = NewRestProtocol()
diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go
index 9117148777ca868cb7d2672236e800c836d3de84..9ff4e7df7fe41d7fd028bf476cc2192cf7cefcca 100644
--- a/protocol/rest/rest_protocol_test.go
+++ b/protocol/rest/rest_protocol_test.go
@@ -38,14 +38,10 @@ import (
 	rest_config "github.com/apache/dubbo-go/protocol/rest/config"
 )
 
-func TestRestProtocol_Refer(t *testing.T) {
+func TestRestProtocolRefer(t *testing.T) {
 	// Refer
 	proto := GetRestProtocol()
-	url, err := common.NewURL("rest://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
-		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
-		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
-		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
-		"side=provider&timeout=3000&timestamp=1556509797245")
+	url, err := common.NewURL(mockRestCommonUrl)
 	assert.NoError(t, err)
 	con := config.ConsumerConfig{
 		ConnectTimeout: 5 * time.Second,
@@ -71,14 +67,10 @@ func TestRestProtocol_Refer(t *testing.T) {
 	assert.Equal(t, 0, invokersLen)
 }
 
-func TestRestProtocol_Export(t *testing.T) {
+func TestRestProtocolExport(t *testing.T) {
 	// Export
 	proto := GetRestProtocol()
-	url, err := common.NewURL("rest://127.0.0.1:8888/com.ikurento.user.UserProvider?anyhost=true&" +
-		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
-		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
-		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
-		"side=provider&timeout=3000&timestamp=1556509797245")
+	url, err := common.NewURL(mockRestCommonUrl)
 	assert.NoError(t, err)
 	_, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{})
 	assert.NoError(t, err)
diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go
index 0eeca6c1bba1f471117eb687a92d0458b9d5901d..60becfb34135470b0e69972c25a743f44efe19d5 100644
--- a/protocol/rpc_status.go
+++ b/protocol/rpc_status.go
@@ -170,12 +170,12 @@ func CurrentTimeMillis() int64 {
 
 // Destroy is used to clean all status
 func CleanAllStatus() {
-	delete1 := func(key interface{}, value interface{}) bool {
+	delete1 := func(key, _ interface{}) bool {
 		methodStatistics.Delete(key)
 		return true
 	}
 	methodStatistics.Range(delete1)
-	delete2 := func(key interface{}, value interface{}) bool {
+	delete2 := func(key, _ interface{}) bool {
 		serviceStatistic.Delete(key)
 		return true
 	}
diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go
index 611b7cba6e453cdf00624b4414d468278fd62cb1..cc12753cf25007ab6d1010836b203aee22d78ca9 100644
--- a/protocol/rpc_status_test.go
+++ b/protocol/rpc_status_test.go
@@ -30,10 +30,14 @@ import (
 	"github.com/apache/dubbo-go/common"
 )
 
+const (
+	mockCommonDubboUrl = "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider"
+)
+
 func TestBeginCount(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	BeginCount(url, "test")
 	urlStatus := GetURLStatus(url)
 	methodStatus := GetMethodStatus(url, "test")
@@ -47,7 +51,7 @@ func TestBeginCount(t *testing.T) {
 func TestEndCount(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	EndCount(url, "test", 100, true)
 	urlStatus := GetURLStatus(url)
 	methodStatus := GetMethodStatus(url, "test")
@@ -60,7 +64,7 @@ func TestEndCount(t *testing.T) {
 func TestGetMethodStatus(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	status := GetMethodStatus(url, "test")
 	assert.NotNil(t, status)
 	assert.Equal(t, int32(0), status.total)
@@ -69,25 +73,25 @@ func TestGetMethodStatus(t *testing.T) {
 func TestGetUrlStatus(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	status := GetURLStatus(url)
 	assert.NotNil(t, status)
 	assert.Equal(t, int32(0), status.total)
 }
 
-func Test_beginCount0(t *testing.T) {
+func TestbeginCount0(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	status := GetURLStatus(url)
 	beginCount0(status)
 	assert.Equal(t, int32(1), status.active)
 }
 
-func Test_All(t *testing.T) {
+func TestAll(t *testing.T) {
 	defer CleanAllStatus()
 
-	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	url, _ := common.NewURL(mockCommonDubboUrl)
 	request(url, "test", 100, false, true)
 	urlStatus := GetURLStatus(url)
 	methodStatus := GetMethodStatus(url, "test")
diff --git a/registry/base_configuration_listener.go b/registry/base_configuration_listener.go
index 7b28d7ee1b30a03b8211c4c1efa5824c3b61453a..3b36510306680486ba9d269472450df8867b61b1 100644
--- a/registry/base_configuration_listener.go
+++ b/registry/base_configuration_listener.go
@@ -100,7 +100,12 @@ func ToConfigurators(urls []*common.URL, f func(url *common.URL) config_center.C
 			configurators = []config_center.Configurator{}
 			break
 		}
-		//TODO:anyhost_key judage
+
+		override := url.GetParams()
+		delete(override, constant.ANYHOST_KEY)
+		if len(override) == 0 {
+			continue
+		}
 		configurators = append(configurators, f(url))
 	}
 	return configurators
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..4b069c2e82c614872bfe986fc911fc672f0efad1 100644
--- a/registry/nacos/service_discovery_test.go
+++ b/registry/nacos/service_discovery_test.go
@@ -18,8 +18,10 @@
 package nacos
 
 import (
+	"math/rand"
 	"strconv"
 	"testing"
+	"time"
 )
 
 import (
@@ -27,23 +29,66 @@ 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) {
-	serviceName := "service-name"
+	prepareData()
+	extension.SetEventDispatcher("mock", func() observer.EventDispatcher {
+		return &dispatcher.MockEventDispatcher{}
+	})
+
+	extension.SetAndInitGlobalDispatcher("mock")
+	rand.Seed(time.Now().Unix())
+	serviceName := "service-name" + strconv.Itoa(rand.Intn(10000))
 	id := "id"
 	host := "host"
 	port := 123
@@ -59,7 +104,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{
@@ -71,7 +116,9 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) {
 
 	err := serviceDiscovry.Register(instance)
 	assert.Nil(t, err)
-
+	//sometimes nacos may be failed to push update of instance,
+	//so it need 10s to pull, we sleep 10 second to make sure instance has been update
+	time.Sleep(11 * time.Second)
 	page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true)
 	assert.NotNil(t, page)
 
@@ -112,11 +159,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..415ca35fbad2fa335d687dc7a7718fa3a4b2b487
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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"
+)
+
+// SubscribedURLsSynthesizer is used to synthesize the subscribed url
+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 c5b2f33c6107e82aa172c818c0d8aca1483248c6..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,18 +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 {
+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)}
+	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 1e7bd08adef5ac9920413fd198b726f49c11ecd4..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,12 +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))
-				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)
 
 			}
 		}
@@ -152,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()
@@ -226,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()
@@ -240,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
@@ -264,3 +283,37 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen
 
 	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 e37b6383df55f1c7e7b64be62fc2eb22d1034616..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)
@@ -154,7 +154,7 @@ func (suite *ClientTestSuite) TestClientValid() {
 	c := suite.client
 	t := suite.T()
 
-	if c.Valid() != true {
+	if !c.Valid() {
 		t.Fatal("client is not valid")
 	}
 	c.Close()
@@ -174,7 +174,7 @@ func (suite *ClientTestSuite) TestClientDone() {
 
 	c.Wait.Wait()
 
-	if c.Valid() == true {
+	if c.Valid() {
 		suite.T().Fatal("client should be invalid then")
 	}
 }
@@ -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/facade.go b/remoting/etcdv3/facade.go
index 3f5999fdf3c5a0791d780e8f5521ef3ea51e9372..2edbb6650890d6d655d8356e1c0a2979a022f0a8 100644
--- a/remoting/etcdv3/facade.go
+++ b/remoting/etcdv3/facade.go
@@ -85,10 +85,8 @@ LOOP:
 				)
 				logger.Infof("ETCDV3ProviderRegistry.validateETCDV3Client(etcd Addr{%s}) = error{%#v}",
 					endpoint, perrors.WithStack(err))
-				if err == nil {
-					if r.RestartCallBack() {
-						break
-					}
+				if err == nil && r.RestartCallBack() {
+					break
 				}
 				failTimes++
 				if MaxFailTimes <= failTimes {
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/remoting/kubernetes/client_test.go b/remoting/kubernetes/client_test.go
index e116c48b1aa85b9b2331045dfbc42891aee1f2e1..d6c5a2e88057459c0a87cd9a607e9c10970b07b2 100644
--- a/remoting/kubernetes/client_test.go
+++ b/remoting/kubernetes/client_test.go
@@ -59,6 +59,9 @@ var tests = []struct {
 // test dataset prefix
 const prefix = "name"
 
+var (
+	watcherStopLog = "the watcherSet watcher was stopped"
+)
 var clientPodListJsonData = `{
     "apiVersion": "v1",
     "items": [
@@ -258,12 +261,12 @@ func TestClientValid(t *testing.T) {
 	client := getTestClient(t)
 	defer client.Close()
 
-	if client.Valid() != true {
+	if !client.Valid() {
 		t.Fatal("client is not valid")
 	}
 
 	client.Close()
-	if client.Valid() != false {
+	if client.Valid() {
 		t.Fatal("client is valid")
 	}
 }
@@ -278,7 +281,7 @@ func TestClientDone(t *testing.T) {
 
 	<-client.Done()
 
-	if client.Valid() == true {
+	if client.Valid() {
 		t.Fatal("client should be invalid")
 	}
 }
@@ -331,7 +334,7 @@ func TestClientGetChildrenKVList(t *testing.T) {
 					return
 				}
 			case <-done:
-				t.Log("the watcherSet watcher was stopped")
+				t.Log(watcherStopLog)
 				return
 			}
 		}
@@ -399,7 +402,7 @@ func TestClientWatchPrefix(t *testing.T) {
 			case e := <-wc:
 				t.Logf("got event %v k %s v %s", e.EventType, e.Key, e.Value)
 			case <-done:
-				t.Log("the watcherSet watcher was stopped")
+				t.Log(watcherStopLog)
 				return
 			}
 		}
@@ -441,7 +444,7 @@ func TestClientWatch(t *testing.T) {
 			case e := <-wc:
 				t.Logf("got event %v k %s v %s", e.EventType, e.Key, e.Value)
 			case <-done:
-				t.Log("the watcherSet watcher was stopped")
+				t.Log(watcherStopLog)
 				return
 			}
 		}
diff --git a/remoting/kubernetes/facade_test.go b/remoting/kubernetes/facade_test.go
index 4323c0ec565d64f445f15c3e5c265451b2b87ce7..65c5d715a38bd0862245255e0276ff5e959de3a3 100644
--- a/remoting/kubernetes/facade_test.go
+++ b/remoting/kubernetes/facade_test.go
@@ -48,6 +48,7 @@ func (r *mockFacade) GetUrl() common.URL {
 }
 
 func (r *mockFacade) Destroy() {
+	// TODO implementation me
 }
 
 func (r *mockFacade) RestartCallBack() bool {
diff --git a/remoting/listener.go b/remoting/listener.go
index 6cbb883181ff8ec1c9124f8d8cc3d7ec0920abd9..eb27c71dfd64a1063927663a9817f8f23b85dd20 100644
--- a/remoting/listener.go
+++ b/remoting/listener.go
@@ -48,6 +48,7 @@ var serviceEventTypeStrings = [...]string{
 	"update",
 }
 
+// nolint
 func (t EventType) String() string {
 	return serviceEventTypeStrings[t]
 }
@@ -63,6 +64,7 @@ type Event struct {
 	Content string
 }
 
+// nolint
 func (e Event) String() string {
 	return fmt.Sprintf("Event{Action{%s}, Content{%s}}", e.Action, e.Content)
 }
diff --git a/registry/nacos/base_registry.go b/remoting/nacos/builder.go
similarity index 58%
rename from registry/nacos/base_registry.go
rename to remoting/nacos/builder.go
index 63f4999675470853d0f48d1a22b709efdc1c9d26..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
@@ -95,8 +82,49 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
 	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.NamespaceId = url.GetParam(constant.NACOS_NAMESPACE_ID, "")
+	clientConfig.Username = url.GetParam(constant.NACOS_USERNAME, "")
+	clientConfig.Password = url.GetParam(constant.NACOS_PASSWORD, "")
+	clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACE_ID, "")
 	clientConfig.NotLoadCacheAtStart = true
 	configMap["clientConfig"] = clientConfig
 
 	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 a8ad41846eaf933f0410bbb1ae44aef893c79d92..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.Mutex    // 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锛宎nd 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 10de42523e731d0780ff7132f4655850409135aa..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
 
@@ -78,10 +78,8 @@ LOOP:
 				err = ValidateZookeeperClient(r, WithZkName(zkName))
 				logger.Infof("ZkProviderRegistry.validateZookeeperClient(zkAddr{%s}) = error{%#v}",
 					zkAddress, perrors.WithStack(err))
-				if err == nil {
-					if r.RestartCallBack() {
-						break
-					}
+				if err == nil && r.RestartCallBack() {
+					break
 				}
 				failTimes++
 				if MaxFailTimes <= failTimes {
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 2c7f1f84d701d1f1b3994bf84402146de4200c06..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 {
@@ -77,13 +89,21 @@ func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remo
 			case zk.EventNodeDataChanged:
 				logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDataChanged}", zkPath)
 				if len(listener) > 0 {
-					content, _, _ := l.client.Conn.Get(zkEvent.Path)
+					content, _, err := l.client.Conn.Get(zkEvent.Path)
+					if err != nil {
+						logger.Warnf("zk.Conn.Get{key:%s} = error{%v}", zkPath, err)
+						return false
+					}
 					listener[0].DataChange(remoting.Event{Path: zkEvent.Path, Action: remoting.EventTypeUpdate, Content: string(content)})
 				}
 			case zk.EventNodeCreated:
 				logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeCreated}", zkPath)
 				if len(listener) > 0 {
-					content, _, _ := l.client.Conn.Get(zkEvent.Path)
+					content, _, err := l.client.Conn.Get(zkEvent.Path)
+					if err != nil {
+						logger.Warnf("zk.Conn.Get{key:%s} = error{%v}", zkPath, err)
+						return false
+					}
 					listener[0].DataChange(remoting.Event{Path: zkEvent.Path, Action: remoting.EventTypeAdd, Content: string(content)})
 				}
 			case zk.EventNotWatching:
@@ -146,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})
 			}
@@ -227,15 +247,15 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen
 			// Only need to compare Path when subscribing to provider
 			if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) != -1 {
 				provider, _ := common.NewURL(c)
-				if provider.Path != conf.Path {
+				if provider.ServiceKey() != conf.ServiceKey() {
 					continue
 				}
 			}
 
-			//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()
@@ -247,8 +267,14 @@ 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
+			l.client.RLock()
+			if l.client.Conn == nil {
+				l.client.RUnlock()
+				break
+			}
 			content, _, err := l.client.Conn.Get(dubboPath)
+			l.client.RUnlock()
 			if err != nil {
 				logger.Errorf("Get new node path {%v} 's content error,message is  {%v}", dubboPath, perrors.WithStack(err))
 			}
@@ -259,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)
@@ -296,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)
@@ -312,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()
 }
diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go
index ba7d6ba81b6af97dc5ad3788e8399d08cbe5b2bb..37ef1b4b967d2f6708a4a099875ae90f273ae483 100644
--- a/remoting/zookeeper/listener_test.go
+++ b/remoting/zookeeper/listener_test.go
@@ -32,6 +32,10 @@ import (
 	"github.com/apache/dubbo-go/remoting"
 )
 
+var (
+	dubboPropertiesPath = "/dubbo/dubbo.properties"
+)
+
 func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Event) {
 	ts, client, event, err := NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
@@ -58,10 +62,10 @@ func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Even
 	dubbo.service.com.ikurento.user.UserProvider.cluster=failover
 `
 
-	err = client.Create("/dubbo/dubbo.properties")
+	err = client.Create(dubboPropertiesPath)
 	assert.NoError(t, err)
 
-	_, err = client.Conn.Set("/dubbo/dubbo.properties", []byte(data), 0)
+	_, err = client.Conn.Set(dubboPropertiesPath, []byte(data), 0)
 	assert.NoError(t, err)
 
 	return ts, client, event
@@ -99,7 +103,7 @@ func TestListener(t *testing.T) {
 	dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait}
 	listener.ListenServiceEvent(nil, "/dubbo", dataListener)
 	time.Sleep(1 * time.Second)
-	_, err := client.Conn.Set("/dubbo/dubbo.properties", []byte(changedData), 1)
+	_, err := client.Conn.Set(dubboPropertiesPath, []byte(changedData), 1)
 	assert.NoError(t, err)
 	wait.Wait()
 	assert.Equal(t, changedData, dataListener.eventList[1].Content)