Merge pull request #658 from apache/develop

Develop to master
diff --git a/.gitignore b/.gitignore
index 5a0a4f8..fabff68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,16 +20,14 @@
 
 # go mod, go test
 vendor/
-coverage.txt
-
 logs/
 .vscode/
-coverage.txt
 
 # unit test
 remoting/zookeeper/zookeeper-4unittest/
 config_center/zookeeper/zookeeper-4unittest/
 registry/zookeeper/zookeeper-4unittest/
+metadata/report/zookeeper/zookeeper-4unittest/
 registry/consul/agent*
 config_center/apollo/mockDubbog.properties.json
 
diff --git a/.travis.yml b/.travis.yml
index ba30c9d..566c88e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,35 @@
-language: go

+dist: trusty

+sudo: required

 

+# define the dependence env

+language: go

 os:

   - linux

-

 go:

   - "1.13"

-

+services:

+  - docker

 env:

   - GO111MODULE=on

-

 install: true

 

+# define ci-stage

 script:

+  # license-check

+  - echo 'start license check'

   - go fmt ./... && [[ -z `git status -s` ]]

+  - sh before_validate_license.sh

+  - chmod u+x /tmp/tools/license/license-header-checker

+  - /tmp/tools/license/license-header-checker -v -a -r -i vendor  /tmp/tools/license/license.txt . go  && [[ -z `git status -s` ]]

+  # unit-test

+  - echo 'start unit-test'

   - chmod u+x before_ut.sh && ./before_ut.sh

   - go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic

+  # integrate-test

+  - chmod +x integrate_test.sh && ./integrate_test.sh

 

 after_success:

   - bash <(curl -s https://codecov.io/bash)

 

 notifications:

-  webhooks: https://oapi.dingtalk.com/robot/send?access_token=f5d6237f2c79db584e75604f7f88db1ce1673c8c0e98451217b28fde791e1d4f
\ No newline at end of file
+  webhooks: https://oapi.dingtalk.com/robot/send?access_token=f5d6237f2c79db584e75604f7f88db1ce1673c8c0e98451217b28fde791e1d4f

diff --git a/CHANGE.md b/CHANGE.md
index 00b074d..4a75ad3 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/README.md b/README.md
index 16cb1f5..2f40eaa 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@
 
 Both extension module and layered project architecture is according to Apache Dubbo (including protocol layer, registry layer, cluster layer, config layer and so on), the advantage of this arch is as following: you can implement these layered interfaces in your own way, override the default implementation of dubbo-go by calling 'extension.SetXXX' of extension, complete your special needs without modifying the source code. At the same time, you are welcome to contribute implementation of useful extension to the community.
 
-![frame design](https://raw.githubusercontent.com/wiki/dubbo/dubbo-go/dubbo-go%E4%BB%A3%E7%A0%81%E5%88%86%E5%B1%82%E8%AE%BE%E8%AE%A1.png)
+![dubbo go extend](./doc/pic/arch/dubbo-go-ext.png)
 
 If you wanna know more about dubbo-go, please visit this reference [Project Architeture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design)
 
diff --git a/README_CN.md b/README_CN.md
index 53d5f85..94630da 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -29,7 +29,7 @@
 
 基于dubbo的extension模块和分层的代码设计(包括 protocol layer, registry layer, cluster layer, config 等等)。我们的目标是:你可以对这些分层接口进行新的实现,并通过调用 extension 模块的“ extension.SetXXX ”方法来覆盖 dubbo-go [同 go-for-apache-dubbo ]的默认实现,以完成自己的特殊需求而无需修改源代码。同时,欢迎你为社区贡献有用的拓展实现。
 
-![框架设计](https://raw.githubusercontent.com/wiki/dubbo/dubbo-go/dubbo-go%E4%BB%A3%E7%A0%81%E5%88%86%E5%B1%82%E8%AE%BE%E8%AE%A1.png)
+![dubbo go extend](./doc/pic/arch/dubbo-go-ext.png)
 
 关于详细设计请阅读 [code layered design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design)
 
@@ -108,6 +108,7 @@
     * [For dubbo](https://github.com/apache/dubbo-go/pull/344)
     * [For grpc](https://github.com/apache/dubbo-go/pull/397)
 
+
 - 其他功能支持:
     * 启动时检查
     * 服务直连
diff --git a/before_ut.bat b/before_ut.bat
index 5e1c877..5d2b9e4 100644
--- a/before_ut.bat
+++ b/before_ut.bat
@@ -20,7 +20,7 @@
 set zkJar=%zkJarPath%/%zkJarName%
 
 if not exist "%zkJar%" (
-   md %zkJarPath%
+   md "%zkJarPath%"
    curl -L %remoteJarUrl% -o %zkJar%
 )
 
@@ -34,4 +34,7 @@
 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 7ee92e5..210e9e7 100755
--- a/before_ut.sh
+++ b/before_ut.sh
@@ -25,13 +25,16 @@
 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/before_validate_license.sh b/before_validate_license.sh
new file mode 100644
index 0000000..8fa6e38
--- /dev/null
+++ b/before_validate_license.sh
@@ -0,0 +1,26 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+remoteLicenseCheckerPath="https://github.com/dubbogo/resources/raw/master/tools/license"
+remoteLicenseCheckerName="license-header-checker"
+remoteLicenseCheckerURL="${remoteLicenseCheckerPath}/${remoteLicenseCheckerName}"
+remoteLicenseName="license.txt"
+remoteLicenseURL="${remoteLicenseCheckerPath}/${remoteLicenseName}"
+
+licensePath="/tmp/tools/license"
+mkdir -p ${licensePath}
+wget -P "${licensePath}" ${remoteLicenseCheckerURL}
+wget -P "${licensePath}" ${remoteLicenseURL}
diff --git a/cluster/cluster.go b/cluster/cluster.go
index 617ce5e..7a0df4c 100644
--- a/cluster/cluster.go
+++ b/cluster/cluster.go
@@ -21,7 +21,8 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// Cluster ...
+// Cluster
+// Extension - Cluster
 type Cluster interface {
 	Join(Directory) protocol.Invoker
 }
diff --git a/cluster/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go
index e041d91..b70a97f 100644
--- a/cluster/cluster_impl/available_cluster.go
+++ b/cluster/cluster_impl/available_cluster.go
@@ -31,7 +31,9 @@
 	extension.SetCluster(available, NewAvailableCluster)
 }
 
-// NewAvailableCluster ...
+// NewAvailableCluster returns a cluster instance
+//
+// Obtain available service providers
 func NewAvailableCluster() cluster.Cluster {
 	return &availableCluster{}
 }
diff --git a/cluster/cluster_impl/available_cluster_invoker.go b/cluster/cluster_impl/available_cluster_invoker.go
index e69f8b9..39a8923 100644
--- a/cluster/cluster_impl/available_cluster_invoker.go
+++ b/cluster/cluster_impl/available_cluster_invoker.go
@@ -35,7 +35,7 @@
 	baseClusterInvoker
 }
 
-// NewAvailableClusterInvoker ...
+// NewAvailableClusterInvoker returns a cluster invoker instance
 func NewAvailableClusterInvoker(directory cluster.Directory) protocol.Invoker {
 	return &availableClusterInvoker{
 		baseClusterInvoker: newBaseClusterInvoker(directory),
diff --git a/cluster/cluster_impl/available_cluster_invoker_test.go b/cluster/cluster_impl/available_cluster_invoker_test.go
index c2cebd3..0631000 100644
--- a/cluster/cluster_impl/available_cluster_invoker_test.go
+++ b/cluster/cluster_impl/available_cluster_invoker_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"context"
+	"fmt"
 	"strings"
 	"testing"
 )
@@ -32,6 +33,7 @@
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/cluster/loadbalance"
 	"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/protocol/invocation"
@@ -39,10 +41,11 @@
 )
 
 var (
-	availableUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	availableUrl, _ = common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider",
+		constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
-func registerAvailable(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker {
+func registerAvailable(invoker *mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	availableCluster := NewAvailableCluster()
 
@@ -60,7 +63,7 @@
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerAvailable(t, invoker)
+	clusterInvoker := registerAvailable(invoker)
 
 	mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}}
 	invoker.EXPECT().IsAvailable().Return(true)
@@ -76,7 +79,7 @@
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerAvailable(t, invoker)
+	clusterInvoker := registerAvailable(invoker)
 
 	invoker.EXPECT().IsAvailable().Return(false)
 
diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go
index 1279999..bbdfa71 100644
--- a/cluster/cluster_impl/base_cluster_invoker.go
+++ b/cluster/cluster_impl/base_cluster_invoker.go
@@ -87,8 +87,11 @@
 }
 
 func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker {
-
 	var selectedInvoker protocol.Invoker
+	if len(invokers) <= 0 {
+		return selectedInvoker
+	}
+
 	url := invokers[0].GetUrl()
 	sticky := url.GetParamBool(constant.STICKY_KEY, false)
 	//Get the service method sticky config if have
@@ -98,19 +101,17 @@
 		invoker.stickyInvoker = nil
 	}
 
-	if sticky && invoker.stickyInvoker != nil && (invoked == nil || !isInvoked(invoker.stickyInvoker, invoked)) {
-		if invoker.availablecheck && invoker.stickyInvoker.IsAvailable() {
-			return invoker.stickyInvoker
-		}
+	if sticky && invoker.availablecheck &&
+		invoker.stickyInvoker != nil && invoker.stickyInvoker.IsAvailable() &&
+		(invoked == nil || !isInvoked(invoker.stickyInvoker, invoked)) {
+		return invoker.stickyInvoker
 	}
 
 	selectedInvoker = invoker.doSelectInvoker(lb, invocation, invokers, invoked)
-
 	if sticky {
 		invoker.stickyInvoker = selectedInvoker
 	}
 	return selectedInvoker
-
 }
 
 func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker {
diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go
index d074697..8121e5c 100644
--- a/cluster/cluster_impl/base_cluster_invoker_test.go
+++ b/cluster/cluster_impl/base_cluster_invoker_test.go
@@ -33,25 +33,33 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func Test_StickyNormal(t *testing.T) {
+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))
 	}
 	base := &baseClusterInvoker{}
 	base.availablecheck = true
 	invoked := []protocol.Invoker{}
-	result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked)
-	result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked)
+
+	tmpRandomBalance := loadbalance.NewRandomLoadBalance()
+	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)
 }
 
-func Test_StickyNormalWhenError(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))
 	}
@@ -59,8 +67,8 @@
 	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 a1692e9..ba454af 100644
--- a/cluster/cluster_impl/broadcast_cluster.go
+++ b/cluster/cluster_impl/broadcast_cluster.go
@@ -31,7 +31,10 @@
 	extension.SetCluster(broadcast, NewBroadcastCluster)
 }
 
-// NewBroadcastCluster ...
+// NewBroadcastCluster returns a broadcast cluster instance.
+//
+// Calling all providers' broadcast one by one. All errors will be reported.
+// It is usually used to notify all providers to update local resource information such as caches or logs.
 func NewBroadcastCluster() cluster.Cluster {
 	return &broadcastCluster{}
 }
diff --git a/cluster/cluster_impl/broadcast_cluster_invoker_test.go b/cluster/cluster_impl/broadcast_cluster_invoker_test.go
index 9b5733e..08d0002 100644
--- a/cluster/cluster_impl/broadcast_cluster_invoker_test.go
+++ b/cluster/cluster_impl/broadcast_cluster_invoker_test.go
@@ -20,6 +20,7 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"testing"
 )
 
@@ -32,6 +33,7 @@
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/cluster/loadbalance"
 	"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/protocol/invocation"
@@ -39,10 +41,11 @@
 )
 
 var (
-	broadcastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	broadcastUrl, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
-func registerBroadcast(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker {
+func registerBroadcast(mockInvokers ...*mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 
 	invokers := []protocol.Invoker{}
@@ -59,7 +62,7 @@
 	return clusterInvoker
 }
 
-func Test_BroadcastInvokeSuccess(t *testing.T) {
+func TestBroadcastInvokeSuccess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
@@ -72,13 +75,13 @@
 		invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult)
 	}
 
-	clusterInvoker := registerBroadcast(t, invokers...)
+	clusterInvoker := registerBroadcast(invokers...)
 
 	result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 	assert.Equal(t, mockResult, result)
 }
 
-func Test_BroadcastInvokeFailed(t *testing.T) {
+func TestBroadcastInvokeFailed(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
@@ -102,7 +105,7 @@
 		invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult)
 	}
 
-	clusterInvoker := registerBroadcast(t, invokers...)
+	clusterInvoker := registerBroadcast(invokers...)
 
 	result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 	assert.Equal(t, mockFailedResult.Err, result.Error())
diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go
index 7657357..432e331 100644
--- a/cluster/cluster_impl/failback_cluster.go
+++ b/cluster/cluster_impl/failback_cluster.go
@@ -31,7 +31,10 @@
 	extension.SetCluster(failback, NewFailbackCluster)
 }
 
-// NewFailbackCluster ...
+// NewFailbackCluster returns a failback cluster instance
+//
+// Failure automatically restored, failed to record the background request,
+// regular retransmission. Usually used for message notification operations.
 func NewFailbackCluster() cluster.Cluster {
 	return &failbackCluster{}
 }
diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go
index 46b0ff6..af17a93 100644
--- a/cluster/cluster_impl/failback_cluster_invoker.go
+++ b/cluster/cluster_impl/failback_cluster_invoker.go
@@ -72,6 +72,19 @@
 	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 @@
 			}
 
 			// 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)
 		}
 	}
 }
@@ -129,29 +128,26 @@
 
 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
-	lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE)
 
+	//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 +160,7 @@
 			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 +168,6 @@
 		// ignore
 		return &protocol.RPCResult{}
 	}
-
 	return result
 }
 
diff --git a/cluster/cluster_impl/failback_cluster_test.go b/cluster/cluster_impl/failback_cluster_test.go
index 69418bc..0edb81d 100644
--- a/cluster/cluster_impl/failback_cluster_test.go
+++ b/cluster/cluster_impl/failback_cluster_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"context"
+	"fmt"
 	"sync"
 	"testing"
 	"time"
@@ -34,6 +35,7 @@
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/cluster/loadbalance"
 	"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/protocol/invocation"
@@ -41,11 +43,12 @@
 )
 
 var (
-	failbackUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	failbackUrl, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
 // registerFailback register failbackCluster to cluster extension.
-func registerFailback(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker {
+func registerFailback(invoker *mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failbackCluster := NewFailbackCluster()
 
@@ -60,12 +63,12 @@
 }
 
 // success firstly, failback should return origin invoke result.
-func Test_FailbackSuceess(t *testing.T) {
+func TestFailbackSuceess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker)
+	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
 
 	invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes()
 
@@ -77,12 +80,12 @@
 }
 
 // failed firstly, success later after one retry.
-func Test_FailbackRetryOneSuccess(t *testing.T) {
+func TestFailbackRetryOneSuccess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker)
+	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
 
 	invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes()
 
@@ -95,7 +98,7 @@
 	wg.Add(1)
 	now := time.Now()
 	mockSuccResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}}
-	invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result {
+	invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result {
 		delta := time.Since(now).Nanoseconds() / int64(time.Second)
 		assert.True(t, delta >= 5)
 		wg.Done()
@@ -120,12 +123,12 @@
 }
 
 // failed firstly, and failed again after ech retry time.
-func Test_FailbackRetryFailed(t *testing.T) {
+func TestFailbackRetryFailed(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker)
+	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
 
 	invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes()
 
@@ -141,7 +144,7 @@
 	// add retry call that eventually failed.
 	for i := 0; i < retries; i++ {
 		j := i + 1
-		invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result {
+		invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result {
 			delta := time.Since(now).Nanoseconds() / int64(time.Second)
 			assert.True(t, delta >= int64(5*j))
 			wg.Done()
@@ -166,12 +169,12 @@
 }
 
 // add 10 tasks but all failed firstly, and failed again with one retry.
-func Test_FailbackRetryFailed10Times(t *testing.T) {
+func TestFailbackRetryFailed10Times(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker)
+	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
 	clusterInvoker.maxRetries = 10
 
 	invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes()
@@ -184,7 +187,7 @@
 	var wg sync.WaitGroup
 	wg.Add(10)
 	now := time.Now()
-	invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result {
+	invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result {
 		delta := time.Since(now).Nanoseconds() / int64(time.Second)
 		assert.True(t, delta >= 5)
 		wg.Done()
@@ -208,12 +211,12 @@
 	assert.Equal(t, int64(0), clusterInvoker.taskList.Len())
 }
 
-func Test_FailbackOutOfLimit(t *testing.T) {
+func TestFailbackOutOfLimit(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker)
+	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
 	clusterInvoker.failbackTasks = 1
 
 	invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes()
diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go
index 1e85485..ac9ec6b 100644
--- a/cluster/cluster_impl/failfast_cluster.go
+++ b/cluster/cluster_impl/failfast_cluster.go
@@ -31,7 +31,10 @@
 	extension.SetCluster(failfast, NewFailFastCluster)
 }
 
-// NewFailFastCluster ...
+// NewFailFastCluster returns a failfast cluster instance.
+//
+// Fast failure, only made a call, failure immediately error. Usually used for non-idempotent write operations,
+// such as adding records.
 func NewFailFastCluster() cluster.Cluster {
 	return &failfastCluster{}
 }
diff --git a/cluster/cluster_impl/failfast_cluster_test.go b/cluster/cluster_impl/failfast_cluster_test.go
index c5ab7cd..77e8e9c 100644
--- a/cluster/cluster_impl/failfast_cluster_test.go
+++ b/cluster/cluster_impl/failfast_cluster_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"context"
+	"fmt"
 	"testing"
 )
 
@@ -32,6 +33,7 @@
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/cluster/loadbalance"
 	"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/protocol/invocation"
@@ -39,11 +41,12 @@
 )
 
 var (
-	failfastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	failfastUrl, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
 // registerFailfast register failfastCluster to cluster extension.
-func registerFailfast(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker {
+func registerFailfast(invoker *mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failfastCluster := NewFailFastCluster()
 
@@ -57,12 +60,12 @@
 	return clusterInvoker
 }
 
-func Test_FailfastInvokeSuccess(t *testing.T) {
+func TestFailfastInvokeSuccess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailfast(t, invoker)
+	clusterInvoker := registerFailfast(invoker)
 
 	invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes()
 
@@ -77,12 +80,12 @@
 	assert.Equal(t, 0, res.tried)
 }
 
-func Test_FailfastInvokeFail(t *testing.T) {
+func TestFailfastInvokeFail(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailfast(t, invoker)
+	clusterInvoker := registerFailfast(invoker)
 
 	invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes()
 
diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go
index b16be3b..d30a743 100644
--- a/cluster/cluster_impl/failover_cluster.go
+++ b/cluster/cluster_impl/failover_cluster.go
@@ -31,7 +31,11 @@
 	extension.SetCluster(name, NewFailoverCluster)
 }
 
-// NewFailoverCluster ...
+// NewFailoverCluster returns a failover cluster instance
+//
+// Failure automatically switch, when there is a failure,
+// retry the other server (default). Usually used for read operations,
+// but retries can result in longer delays.
 func NewFailoverCluster() cluster.Cluster {
 	return &failoverCluster{}
 }
diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go
index 6178a05..66adabd 100644
--- a/cluster/cluster_impl/failover_cluster_invoker.go
+++ b/cluster/cluster_impl/failover_cluster_invoker.go
@@ -45,52 +45,35 @@
 }
 
 func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
+	var (
+		result    protocol.Result
+		invoked   []protocol.Invoker
+		providers []string
+	)
 
 	invokers := invoker.directory.List(invocation)
-	err := invoker.checkInvokers(invokers, invocation)
-
-	if err != nil {
+	if err := invoker.checkInvokers(invokers, invocation); err != nil {
 		return &protocol.RPCResult{Err: err}
 	}
 
-	loadbalance := getLoadBalance(invokers[0], invocation)
-
 	methodName := invocation.MethodName()
-	url := invokers[0].GetUrl()
+	retries := getRetries(invokers, methodName)
+	loadBalance := getLoadBalance(invokers[0], invocation)
 
-	//get reties
-	retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES)
-
-	//Get the service method loadbalance config if have
-	if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 {
-		retriesConfig = v
-	}
-	retries, err := strconv.Atoi(retriesConfig)
-	if err != nil || retries < 0 {
-		logger.Error("Your retries config is invalid,pls do a check. And will use the default retries configuration instead.")
-		retries = constant.DEFAULT_RETRIES_INT
-	}
-	invoked := []protocol.Invoker{}
-	providers := []string{}
-	var result protocol.Result
-	if retries > len(invokers) {
-		retries = len(invokers)
-	}
 	for i := 0; i <= retries; i++ {
 		//Reselect before retry to avoid a change of candidate `invokers`.
 		//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
 		if i > 0 {
-			err := invoker.checkWhetherDestroyed()
-			if err != nil {
+			if err := invoker.checkWhetherDestroyed(); err != nil {
 				return &protocol.RPCResult{Err: err}
 			}
+
 			invokers = invoker.directory.List(invocation)
-			err = invoker.checkInvokers(invokers, invocation)
-			if err != nil {
+			if err := invoker.checkInvokers(invokers, invocation); err != nil {
 				return &protocol.RPCResult{Err: err}
 			}
 		}
-		ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked)
+		ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked)
 		if ivk == nil {
 			continue
 		}
@@ -100,13 +83,40 @@
 		if result.Error() != nil {
 			providers = append(providers, ivk.GetUrl().Key())
 			continue
-		} else {
-			return result
 		}
+		return result
 	}
+
 	ip, _ := gxnet.GetLocalIP()
-	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, invoker.GetUrl().Service(), retries, providers, len(providers), len(invokers), invoker.directory.GetUrl(), ip, constant.Version, result.Error().Error(),
-	)}
+	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(),
+		)}
+}
+
+func getRetries(invokers []protocol.Invoker, methodName string) int {
+	if len(invokers) <= 0 {
+		return constant.DEFAULT_RETRIES_INT
+	}
+
+	url := invokers[0].GetUrl()
+	//get reties
+	retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES)
+	//Get the service method loadbalance config if have
+	if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 {
+		retriesConfig = v
+	}
+
+	retries, err := strconv.Atoi(retriesConfig)
+	if err != nil || retries < 0 {
+		logger.Error("Your retries config is invalid,pls do a check. And will use the default retries configuration instead.")
+		retries = constant.DEFAULT_RETRIES_INT
+	}
+
+	if retries > len(invokers) {
+		retries = len(invokers)
+	}
+	return retries
 }
diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go
index 1be2106..e05b792 100644
--- a/cluster/cluster_impl/failover_cluster_test.go
+++ b/cluster/cluster_impl/failover_cluster_test.go
@@ -101,14 +101,14 @@
 
 var count int
 
-func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result {
+func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failoverCluster := NewFailoverCluster()
 
 	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), common.WithParams(urlParam))
-		invokers = append(invokers, NewMockInvoker(url, successCount))
+		newUrl, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i), common.WithParams(urlParam))
+		invokers = append(invokers, NewMockInvoker(newUrl, successCount))
 	}
 
 	staticDir := directory.NewStaticDirectory(invokers)
@@ -119,40 +119,40 @@
 	return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 }
 
-func Test_FailoverInvokeSuccess(t *testing.T) {
+func TestFailoverInvokeSuccess(t *testing.T) {
 	urlParams := url.Values{}
-	result := normalInvoke(t, 3, urlParams)
+	result := normalInvoke(3, urlParams)
 	assert.NoError(t, result.Error())
 	count = 0
 }
 
-func Test_FailoverInvokeFail(t *testing.T) {
+func TestFailoverInvokeFail(t *testing.T) {
 	urlParams := url.Values{}
-	result := normalInvoke(t, 4, urlParams)
+	result := normalInvoke(4, urlParams)
 	assert.Errorf(t, result.Error(), "error")
 	count = 0
 }
 
-func Test_FailoverInvoke1(t *testing.T) {
+func TestFailoverInvoke1(t *testing.T) {
 	urlParams := url.Values{}
 	urlParams.Set(constant.RETRIES_KEY, "3")
-	result := normalInvoke(t, 4, urlParams)
+	result := normalInvoke(4, urlParams)
 	assert.NoError(t, result.Error())
 	count = 0
 }
 
-func Test_FailoverInvoke2(t *testing.T) {
+func TestFailoverInvoke2(t *testing.T) {
 	urlParams := url.Values{}
 	urlParams.Set(constant.RETRIES_KEY, "2")
 	urlParams.Set("methods.test."+constant.RETRIES_KEY, "3")
 
 	ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test"))
-	result := normalInvoke(t, 4, urlParams, ivc)
+	result := normalInvoke(4, urlParams, ivc)
 	assert.NoError(t, result.Error())
 	count = 0
 }
 
-func Test_FailoverDestroy(t *testing.T) {
+func TestFailoverDestroy(t *testing.T) {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failoverCluster := NewFailoverCluster()
 
@@ -170,5 +170,4 @@
 	count = 0
 	clusterInvoker.Destroy()
 	assert.Equal(t, false, clusterInvoker.IsAvailable())
-
 }
diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go
index 177d24a..f708b7f 100644
--- a/cluster/cluster_impl/failsafe_cluster.go
+++ b/cluster/cluster_impl/failsafe_cluster.go
@@ -31,7 +31,10 @@
 	extension.SetCluster(failsafe, NewFailsafeCluster)
 }
 
-// NewFailsafeCluster ...
+// NewFailsafeCluster returns a failsafe cluster instance.
+//
+// Failure of security, anomalies, directly ignored. Usually it is
+// used to write audit logs and other operations.
 func NewFailsafeCluster() cluster.Cluster {
 	return &failsafeCluster{}
 }
diff --git a/cluster/cluster_impl/failsafe_cluster_test.go b/cluster/cluster_impl/failsafe_cluster_test.go
index 0bfeb57..d9a716e 100644
--- a/cluster/cluster_impl/failsafe_cluster_test.go
+++ b/cluster/cluster_impl/failsafe_cluster_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"context"
+	"fmt"
 	"testing"
 )
 
@@ -32,6 +33,7 @@
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/cluster/loadbalance"
 	"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/protocol/invocation"
@@ -39,11 +41,12 @@
 )
 
 var (
-	failsafeUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	failsafeUrl, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
 // registerFailsafe register failsafeCluster to cluster extension.
-func registerFailsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker {
+func registerFailsafe(invoker *mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
 	failsafeCluster := NewFailsafeCluster()
 
@@ -57,12 +60,12 @@
 	return clusterInvoker
 }
 
-func Test_FailSafeInvokeSuccess(t *testing.T) {
+func TestFailSafeInvokeSuccess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailsafe(t, invoker)
+	clusterInvoker := registerFailsafe(invoker)
 
 	invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes()
 
@@ -76,12 +79,12 @@
 	assert.True(t, res.success)
 }
 
-func Test_FailSafeInvokeFail(t *testing.T) {
+func TestFailSafeInvokeFail(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	invoker := mock.NewMockInvoker(ctrl)
-	clusterInvoker := registerFailsafe(t, invoker)
+	clusterInvoker := registerFailsafe(invoker)
 
 	invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes()
 
diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go
index a6f7a7b..0e6cd26 100644
--- a/cluster/cluster_impl/forking_cluster.go
+++ b/cluster/cluster_impl/forking_cluster.go
@@ -31,7 +31,10 @@
 	extension.SetCluster(forking, NewForkingCluster)
 }
 
-// NewForkingCluster ...
+// NewForkingCluster returns a forking cluster instance.
+//
+// Multiple servers are invoked in parallel, returning as soon as one succeeds.
+// Usually it is used for real-time demanding read operations while wasting more service resources.
 func NewForkingCluster() cluster.Cluster {
 	return &forkingCluster{}
 }
diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go
index 7325694..a5a3f2e 100644
--- a/cluster/cluster_impl/forking_cluster_invoker.go
+++ b/cluster/cluster_impl/forking_cluster_invoker.go
@@ -46,14 +46,12 @@
 
 // Invoke ...
 func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
-	err := invoker.checkWhetherDestroyed()
-	if err != nil {
+	if err := invoker.checkWhetherDestroyed(); err != nil {
 		return &protocol.RPCResult{Err: err}
 	}
 
 	invokers := invoker.directory.List(invocation)
-	err = invoker.checkInvokers(invokers, invocation)
-	if err != nil {
+	if err := invoker.checkInvokers(invokers, invocation); err != nil {
 		return &protocol.RPCResult{Err: err}
 	}
 
@@ -63,11 +61,9 @@
 	if forks < 0 || forks > len(invokers) {
 		selected = invokers
 	} else {
-		selected = make([]protocol.Invoker, 0)
-		loadbalance := getLoadBalance(invokers[0], invocation)
+		loadBalance := getLoadBalance(invokers[0], invocation)
 		for i := 0; i < forks; i++ {
-			ivk := invoker.doSelect(loadbalance, invocation, invokers, selected)
-			if ivk != nil {
+			if ivk := invoker.doSelect(loadBalance, invocation, invokers, selected); ivk != nil {
 				selected = append(selected, ivk)
 			}
 		}
@@ -77,8 +73,7 @@
 	for _, ivk := range selected {
 		go func(k protocol.Invoker) {
 			result := k.Invoke(ctx, invocation)
-			err := resultQ.Put(result)
-			if err != nil {
+			if err := resultQ.Put(result); err != nil {
 				logger.Errorf("resultQ put failed with exception: %v.\n", err)
 			}
 		}(ivk)
@@ -99,6 +94,5 @@
 	if !ok {
 		return &protocol.RPCResult{Err: fmt.Errorf("failed to forking invoke provider %v, but not legal resp", selected)}
 	}
-
 	return result
 }
diff --git a/cluster/cluster_impl/forking_cluster_test.go b/cluster/cluster_impl/forking_cluster_test.go
index 526b137..a2fa136 100644
--- a/cluster/cluster_impl/forking_cluster_test.go
+++ b/cluster/cluster_impl/forking_cluster_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"context"
+	"fmt"
 	"strconv"
 	"sync"
 	"testing"
@@ -42,10 +43,11 @@
 )
 
 var (
-	forkingUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	forkingUrl, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 )
 
-func registerForking(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker {
+func registerForking(mockInvokers ...*mock.MockInvoker) protocol.Invoker {
 	extension.SetLoadbalance(loadbalance.RoundRobin, loadbalance.NewRoundRobinLoadBalance)
 
 	invokers := []protocol.Invoker{}
@@ -62,7 +64,7 @@
 	return clusterInvoker
 }
 
-func Test_ForkingInvokeSuccess(t *testing.T) {
+func TestForkingInvokeSuccess(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
@@ -79,20 +81,20 @@
 		invokers = append(invokers, invoker)
 		invoker.EXPECT().IsAvailable().Return(true).AnyTimes()
 		invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
-			func(invocation protocol.Invocation) protocol.Result {
+			func(protocol.Invocation) protocol.Result {
 				wg.Done()
 				return mockResult
 			})
 	}
 
-	clusterInvoker := registerForking(t, invokers...)
+	clusterInvoker := registerForking(invokers...)
 
 	result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 	assert.Equal(t, mockResult, result)
 	wg.Wait()
 }
 
-func Test_ForkingInvokeTimeout(t *testing.T) {
+func TestForkingInvokeTimeout(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
@@ -108,14 +110,14 @@
 		invokers = append(invokers, invoker)
 		invoker.EXPECT().IsAvailable().Return(true).AnyTimes()
 		invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
-			func(invocation protocol.Invocation) protocol.Result {
+			func(protocol.Invocation) protocol.Result {
 				time.Sleep(2 * time.Second)
 				wg.Done()
 				return mockResult
 			})
 	}
 
-	clusterInvoker := registerForking(t, invokers...)
+	clusterInvoker := registerForking(invokers...)
 
 	result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 	assert.NotNil(t, result)
@@ -123,7 +125,7 @@
 	wg.Wait()
 }
 
-func Test_ForkingInvokeHalfTimeout(t *testing.T) {
+func TestForkingInvokeHalfTimeout(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
@@ -140,13 +142,13 @@
 		invoker.EXPECT().IsAvailable().Return(true).AnyTimes()
 		if i == 1 {
 			invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
-				func(invocation protocol.Invocation) protocol.Result {
+				func(protocol.Invocation) protocol.Result {
 					wg.Done()
 					return mockResult
 				})
 		} else {
 			invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(
-				func(invocation protocol.Invocation) protocol.Result {
+				func(protocol.Invocation) protocol.Result {
 					time.Sleep(2 * time.Second)
 					wg.Done()
 					return mockResult
@@ -154,7 +156,7 @@
 		}
 	}
 
-	clusterInvoker := registerForking(t, invokers...)
+	clusterInvoker := registerForking(invokers...)
 
 	result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 	assert.Equal(t, mockResult, result)
diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go
index 943c2ad..d887cfb 100644
--- a/cluster/cluster_impl/mock_cluster.go
+++ b/cluster/cluster_impl/mock_cluster.go
@@ -24,7 +24,11 @@
 
 type mockCluster struct{}
 
-// NewMockCluster ...
+// NewMockCluster returns a mock cluster instance.
+//
+// Mock cluster is usually used for service degradation, such as an authentication service.
+// When the service provider is completely hung up, the client does not throw an exception,
+// return an authorization failure through the Mock data instead.
 func NewMockCluster() cluster.Cluster {
 	return &mockCluster{}
 }
diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go
index 079b688..fcefa52 100644
--- a/cluster/cluster_impl/registry_aware_cluster.go
+++ b/cluster/cluster_impl/registry_aware_cluster.go
@@ -29,7 +29,7 @@
 	extension.SetCluster("registryAware", NewRegistryAwareCluster)
 }
 
-// NewRegistryAwareCluster ...
+// NewRegistryAwareCluster returns a registry aware cluster instance
 func NewRegistryAwareCluster() cluster.Cluster {
 	return &registryAwareCluster{}
 }
diff --git a/cluster/cluster_impl/registry_aware_cluster_test.go b/cluster/cluster_impl/registry_aware_cluster_test.go
index 3d0dcc0..74584b4 100644
--- a/cluster/cluster_impl/registry_aware_cluster_test.go
+++ b/cluster/cluster_impl/registry_aware_cluster_test.go
@@ -33,7 +33,7 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func Test_RegAwareInvokeSuccess(t *testing.T) {
+func TestRegAwareInvokeSuccess(t *testing.T) {
 
 	regAwareCluster := NewRegistryAwareCluster()
 
diff --git a/cluster/directory.go b/cluster/directory.go
index 5a03b3a..37f0c32 100644
--- a/cluster/directory.go
+++ b/cluster/directory.go
@@ -23,7 +23,7 @@
 )
 
 // Directory
-//Extension - Directory
+// Extension - Directory
 type Directory interface {
 	common.Node
 	List(invocation protocol.Invocation) []protocol.Invoker
diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go
index 75d9ef2..8a7e0c0 100644
--- a/cluster/directory/base_directory.go
+++ b/cluster/directory/base_directory.go
@@ -22,7 +22,6 @@
 )
 
 import (
-	"github.com/dubbogo/gost/container/set"
 	"go.uber.org/atomic"
 )
 
@@ -35,8 +34,6 @@
 	"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
@@ -83,16 +80,16 @@
 		return
 	}
 
-	routers := make([]router.Router, 0, len(urls))
+	routers := make([]router.PriorityRouter, 0, len(urls))
 
 	for _, url := range urls {
 		routerKey := url.GetParam(constant.ROUTER_KEY, "")
 
 		if len(routerKey) > 0 {
 			factory := extension.GetRouterFactory(url.Protocol)
-			r, err := factory.NewRouter(url)
+			r, err := factory.NewPriorityRouter(url)
 			if err != nil {
-				logger.Errorf("Create router fail. router key: %s, error: %v", routerKey, url.Service(), err)
+				logger.Errorf("Create router fail. router key: %s, url:%s, error: %+v", routerKey, url.Service(), err)
 				return
 			}
 			routers = append(routers, r)
@@ -120,14 +117,3 @@
 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/directory/base_directory_test.go b/cluster/directory/base_directory_test.go
index d599395..8b60163 100644
--- a/cluster/directory/base_directory_test.go
+++ b/cluster/directory/base_directory_test.go
@@ -34,19 +34,20 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
+var (
+	url, _ = common.NewURL(
+		fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
+	anyUrl, _ = common.NewURL(fmt.Sprintf("condition://%s/com.foo.BarService", constant.ANYHOST_VALUE))
+)
+
 func TestNewBaseDirectory(t *testing.T) {
-	url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider"))
 	directory := NewBaseDirectory(&url)
-
 	assert.NotNil(t, directory)
-
 	assert.Equal(t, url, directory.GetUrl())
 	assert.Equal(t, &url, directory.GetDirectoryUrl())
-
 }
 
 func TestBuildRouterChain(t *testing.T) {
-	url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider"))
 	directory := NewBaseDirectory(&url)
 
 	assert.NotNil(t, directory)
@@ -63,9 +64,8 @@
 }
 
 func getRouteUrl(rule string) *common.URL {
-	url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService")
-	url.AddParam("rule", rule)
-	url.AddParam("force", "true")
-	url.AddParam(constant.ROUTER_KEY, "router")
+	anyUrl.AddParam("rule", rule)
+	anyUrl.AddParam("force", "true")
+	anyUrl.AddParam(constant.ROUTER_KEY, "router")
 	return &url
 }
diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go
index 9f600fe..87f5135 100644
--- a/cluster/directory/static_directory.go
+++ b/cluster/directory/static_directory.go
@@ -61,7 +61,7 @@
 // List List invokers
 func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
 	l := len(dir.invokers)
-	invokers := make([]protocol.Invoker, l, l)
+	invokers := make([]protocol.Invoker, l)
 	copy(invokers, dir.invokers)
 	routerChain := dir.RouterChain()
 
diff --git a/cluster/directory/static_directory_test.go b/cluster/directory/static_directory_test.go
index c50c9a4..8e75a2c 100644
--- a/cluster/directory/static_directory_test.go
+++ b/cluster/directory/static_directory_test.go
@@ -32,7 +32,7 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func Test_StaticDirList(t *testing.T) {
+func TestStaticDirList(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))
@@ -45,7 +45,7 @@
 	assert.Len(t, list, 10)
 }
 
-func Test_StaticDirDestroy(t *testing.T) {
+func TestStaticDirDestroy(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))
diff --git a/cluster/loadbalance.go b/cluster/loadbalance.go
index fb3641a..a5b344a 100644
--- a/cluster/loadbalance.go
+++ b/cluster/loadbalance.go
@@ -22,7 +22,7 @@
 )
 
 // LoadBalance
-//Extension - LoadBalance
+// Extension - LoadBalance
 type LoadBalance interface {
 	Select([]protocol.Invoker, protocol.Invocation) protocol.Invoker
 }
diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go
index 957c110..84fbb26 100644
--- a/cluster/loadbalance/consistent_hash.go
+++ b/cluster/loadbalance/consistent_hash.go
@@ -27,7 +27,9 @@
 	"strconv"
 	"strings"
 )
-
+import (
+	gxsort "github.com/dubbogo/gost/sort"
+)
 import (
 	"github.com/apache/dubbo-go/cluster"
 	"github.com/apache/dubbo-go/common/constant"
@@ -40,7 +42,7 @@
 	ConsistentHash = "consistenthash"
 	// HashNodes ...
 	HashNodes = "hash.nodes"
-	// HashArguments ...
+	// HashArguments  key of hash arguments  in url
 	HashArguments = "hash.arguments"
 )
 
@@ -53,16 +55,18 @@
 	extension.SetLoadbalance(ConsistentHash, NewConsistentHashLoadBalance)
 }
 
-// ConsistentHashLoadBalance ...
+// ConsistentHashLoadBalance implementation of load balancing: using consistent hashing
 type ConsistentHashLoadBalance struct {
 }
 
-// NewConsistentHashLoadBalance ...
+// NewConsistentHashLoadBalance creates NewConsistentHashLoadBalance
+//
+// The same parameters of the request is always sent to the same provider.
 func NewConsistentHashLoadBalance() cluster.LoadBalance {
 	return &ConsistentHashLoadBalance{}
 }
 
-// Select ...
+// Select gets invoker based on load balancing strategy
 func (lb *ConsistentHashLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker {
 	methodName := invocation.MethodName()
 	key := invokers[0].GetUrl().ServiceKey() + "." + methodName
@@ -85,27 +89,12 @@
 	return selector.Select(invocation)
 }
 
-// Uint32Slice ...
-type Uint32Slice []uint32
-
-func (s Uint32Slice) Len() int {
-	return len(s)
-}
-
-func (s Uint32Slice) Less(i, j int) bool {
-	return s[i] < s[j]
-}
-
-func (s Uint32Slice) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-
-// ConsistentHashSelector ...
+// ConsistentHashSelector implementation of Selector:get invoker based on load balancing strategy
 type ConsistentHashSelector struct {
 	hashCode        uint32
 	replicaNum      int
 	virtualInvokers map[uint32]protocol.Invoker
-	keys            Uint32Slice
+	keys            gxsort.Uint32Slice
 	argumentIndex   []int
 }
 
@@ -141,7 +130,7 @@
 	return selector
 }
 
-// Select ...
+// Select gets invoker based on load balancing strategy
 func (c *ConsistentHashSelector) Select(invocation protocol.Invocation) protocol.Invoker {
 	key := c.toKey(invocation.Arguments())
 	digest := md5.Sum([]byte(key))
diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go
index a442931..9f22d39 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 @@
 	"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 @@
 
 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) 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 @@
 
 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) 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 e7c41aa..37ad91c 100644
--- a/cluster/loadbalance/least_active.go
+++ b/cluster/loadbalance/least_active.go
@@ -28,7 +28,7 @@
 )
 
 const (
-	// LeastActive ...
+	// LeastActive is used to set the load balance extension
 	LeastActive = "leastactive"
 )
 
@@ -39,7 +39,9 @@
 type leastActiveLoadBalance struct {
 }
 
-// NewLeastActiveLoadBalance ...
+// NewLeastActiveLoadBalance returns a least active load balance.
+//
+// A random mechanism based on actives, actives means the number of a consumer's requests have been sent to provider but not yet got response.
 func NewLeastActiveLoadBalance() cluster.LoadBalance {
 	return &leastActiveLoadBalance{}
 }
diff --git a/cluster/loadbalance/least_active_test.go b/cluster/loadbalance/least_active_test.go
index 54e57e9..34be17a 100644
--- a/cluster/loadbalance/least_active_test.go
+++ b/cluster/loadbalance/least_active_test.go
@@ -28,6 +28,7 @@
 
 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"
 )
@@ -37,7 +38,7 @@
 
 	var invokers []protocol.Invoker
 
-	url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService")
+	url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/org.apache.demo.HelloService", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 	invokers = append(invokers, protocol.NewBaseInvoker(url))
 	i := loadBalance.Select(invokers, &invocation.RPCInvocation{})
 	assert.True(t, i.GetUrl().URLEqual(url))
diff --git a/cluster/loadbalance/random.go b/cluster/loadbalance/random.go
index 56f1363..cdde1b4 100644
--- a/cluster/loadbalance/random.go
+++ b/cluster/loadbalance/random.go
@@ -38,7 +38,9 @@
 type randomLoadBalance struct {
 }
 
-// NewRandomLoadBalance ...
+// NewRandomLoadBalance returns a random load balance instance.
+//
+// Set random probabilities by weight, and the request will be sent to provider randomly.
 func NewRandomLoadBalance() cluster.LoadBalance {
 	return &randomLoadBalance{}
 }
diff --git a/cluster/loadbalance/random_test.go b/cluster/loadbalance/random_test.go
index ff876f4..b94d7da 100644
--- a/cluster/loadbalance/random_test.go
+++ b/cluster/loadbalance/random_test.go
@@ -36,35 +36,41 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func Test_RandomlbSelect(t *testing.T) {
+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{})
 }
 
-func Test_RandomlbSelectWeight(t *testing.T) {
+func TestRandomlbSelectWeight(t *testing.T) {
 	randomlb := NewRandomLoadBalance()
 
 	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 @@
 	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)
@@ -84,18 +90,18 @@
 	})
 }
 
-func Test_RandomlbSelectWarmup(t *testing.T) {
+func TestRandomlbSelectWarmup(t *testing.T) {
 	randomlb := NewRandomLoadBalance()
 
 	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 @@
 	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 4d03999..c44b239 100644
--- a/cluster/loadbalance/round_robin.go
+++ b/cluster/loadbalance/round_robin.go
@@ -52,7 +52,9 @@
 
 type roundRobinLoadBalance struct{}
 
-// NewRoundRobinLoadBalance ...
+// NewRoundRobinLoadBalance returns a round robin load balance
+//
+// Use the weight's common advisory to determine round robin ratio
 func NewRoundRobinLoadBalance() cluster.LoadBalance {
 	return &roundRobinLoadBalance{}
 }
diff --git a/cluster/loadbalance/round_robin_test.go b/cluster/loadbalance/round_robin_test.go
index 1517f2a..5354bae 100644
--- a/cluster/loadbalance/round_robin_test.go
+++ b/cluster/loadbalance/round_robin_test.go
@@ -29,6 +29,7 @@
 
 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"
 )
@@ -38,7 +39,8 @@
 
 	var invokers []protocol.Invoker
 
-	url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService")
+	url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/org.apache.demo.HelloService",
+		constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
 	invokers = append(invokers, protocol.NewBaseInvoker(url))
 	i := loadBalance.Select(invokers, &invocation.RPCInvocation{})
 	assert.True(t, i.GetUrl().URLEqual(url))
diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go
index 9f36ad9..b6c0138 100644
--- a/cluster/loadbalance/util.go
+++ b/cluster/loadbalance/util.go
@@ -26,7 +26,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// GetWeight ...
+// GetWeight gets weight for load balance strategy
 func GetWeight(invoker protocol.Invoker, invocation protocol.Invocation) int64 {
 	url := invoker.GetUrl()
 	weight := url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT)
diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go
index d48a837..97d20ac 100644
--- a/cluster/router/chain/chain.go
+++ b/cluster/router/chain/chain.go
@@ -40,19 +40,21 @@
 	// Full list of addresses from registry, classified by method name.
 	invokers []protocol.Invoker
 	// Containing all routers, reconstruct every time 'route://' urls change.
-	routers []router.Router
+	routers []router.PriorityRouter
 	// Fixed router instances: ConfigConditionRouter, TagRouter, e.g., the rule for each instance may change but the
 	// instance will never delete or recreate.
-	builtinRouters []router.Router
+	builtinRouters []router.PriorityRouter
 
 	mutex sync.RWMutex
+
+	url common.URL
 }
 
 // Route Loop routers in RouterChain and call Route method to determine the target invokers list.
 func (c *RouterChain) Route(invoker []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
 	finalInvokers := invoker
 	l := len(c.routers)
-	rs := make([]router.Router, l, int(math.Ceil(float64(l)*1.2)))
+	rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2)))
 	c.mutex.RLock()
 	copy(rs, c.routers)
 	c.mutex.RUnlock()
@@ -67,8 +69,8 @@
 // New a array add builtinRouters which is not sorted in RouterChain and routers
 // Sort the array
 // Replace router array in RouterChain
-func (c *RouterChain) AddRouters(routers []router.Router) {
-	newRouters := make([]router.Router, 0, len(c.builtinRouters)+len(routers))
+func (c *RouterChain) AddRouters(routers []router.PriorityRouter) {
+	newRouters := make([]router.PriorityRouter, 0, len(c.builtinRouters)+len(routers))
 	newRouters = append(newRouters, c.builtinRouters...)
 	newRouters = append(newRouters, routers...)
 	sortRouter(newRouters)
@@ -77,6 +79,11 @@
 	c.routers = newRouters
 }
 
+// URL Return URL in RouterChain
+func (c *RouterChain) URL() common.URL {
+	return c.url
+}
+
 // NewRouterChain Use url to init router chain
 // Loop routerFactories and call NewRouter method
 func NewRouterChain(url *common.URL) (*RouterChain, error) {
@@ -84,9 +91,9 @@
 	if len(routerFactories) == 0 {
 		return nil, perrors.Errorf("No routerFactory exits , create one please")
 	}
-	routers := make([]router.Router, 0, len(routerFactories))
+	routers := make([]router.PriorityRouter, 0, len(routerFactories))
 	for key, routerFactory := range routerFactories {
-		r, err := routerFactory().NewRouter(url)
+		r, err := routerFactory().NewPriorityRouter(url)
 		if r == nil || err != nil {
 			logger.Errorf("router chain build router fail! routerFactories key:%s  error:%s", key, err.Error())
 			continue
@@ -94,7 +101,7 @@
 		routers = append(routers, r)
 	}
 
-	newRouters := make([]router.Router, len(routers))
+	newRouters := make([]router.PriorityRouter, len(routers))
 	copy(newRouters, routers)
 
 	sortRouter(newRouters)
@@ -103,17 +110,20 @@
 		builtinRouters: routers,
 		routers:        newRouters,
 	}
+	if url != nil {
+		chain.url = *url
+	}
 
 	return chain, nil
 }
 
 // sortRouter Sort router instance by priority with stable algorithm
-func sortRouter(routers []router.Router) {
+func sortRouter(routers []router.PriorityRouter) {
 	sort.Stable(byPriority(routers))
 }
 
 // byPriority Sort by priority
-type byPriority []router.Router
+type byPriority []router.PriorityRouter
 
 func (a byPriority) Len() int           { return len(a) }
 func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go
index 0cb47c4..c1f7235 100644
--- a/cluster/router/chain/chain_test.go
+++ b/cluster/router/chain/chain_test.go
@@ -20,7 +20,6 @@
 import (
 	"encoding/base64"
 	"fmt"
-	"strconv"
 	"testing"
 	"time"
 )
@@ -42,10 +41,29 @@
 	"github.com/apache/dubbo-go/remoting/zookeeper"
 )
 
+const (
+	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) {
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(path)
 	assert.NoError(t, err)
 
 	testyml := `enabled: true
@@ -55,19 +73,19 @@
   - => host != 172.22.3.91
 `
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0)
+	_, err = z.Conn.Set(path, []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, 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)
@@ -92,10 +110,10 @@
 	assert.NotNil(t, chain)
 }
 
-func TestRouterChain_AddRouters(t *testing.T) {
+func TestRouterChainAddRouters(t *testing.T) {
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(path)
 	assert.NoError(t, err)
 
 	testyml := `enabled: true
@@ -105,63 +123,63 @@
   - => host != 172.22.3.91
 `
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0)
+	_, err = z.Conn.Set(path, []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, 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.NewRouter(url)
+	r, err := factory.NewPriorityRouter(url)
 	assert.Nil(t, err)
 	assert.NotNil(t, r)
 
-	routers := make([]router.Router, 0)
+	routers := make([]router.PriorityRouter, 0)
 	routers = append(routers, r)
 	chain.AddRouters(routers)
 	assert.Equal(t, 3, len(chain.routers))
 }
 
-func TestRouterChain_Route(t *testing.T) {
+func TestRouterChainRoute(t *testing.T) {
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	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, 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)
 
 	assert.Equal(t, 1, len(finalInvokers))
 }
 
-func TestRouterChain_Route_AppRouter(t *testing.T) {
+func TestRouterChainRouteAppRouter(t *testing.T) {
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(path)
 	assert.NoError(t, err)
 
 	testyml := `enabled: true
@@ -171,51 +189,51 @@
   - => host = 1.1.1.1 => host != 1.2.3.4
 `
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0)
+	_, err = z.Conn.Set(path, []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, 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("zookeeper://127.0.0.1:" + 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)
 
@@ -223,26 +241,26 @@
 }
 
 func getConditionNoRouteUrl(applicationKey string) *common.URL {
-	url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService")
-	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("condition://0.0.0.0/com.foo.BarService")
-	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("condition://0.0.0.0/com.foo.BarService")
-	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/chan.go b/cluster/router/chan.go
new file mode 100644
index 0000000..6904e17
--- /dev/null
+++ b/cluster/router/chan.go
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package router
+
+// Chain
+type Chain interface {
+	router
+	// AddRouters Add routers
+	AddRouters([]PriorityRouter)
+}
diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go
index bd817af..8b38f2d 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,12 +31,25 @@
 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"
 	"github.com/apache/dubbo-go/remoting/zookeeper"
 )
 
+const (
+	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) {
 
 	testYML := `enabled: true
@@ -47,22 +60,22 @@
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
@@ -93,27 +106,27 @@
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
 
-	rule, err := Parse(testYML)
+	rule, err := getRule(testYML)
 	assert.Nil(t, err)
 	appRouter.generateConditions(rule)
 
@@ -130,22 +143,22 @@
 `
 	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
 	assert.NoError(t, err)
-	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	err = z.Create(routerPath)
 	assert.NoError(t, err)
 
-	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
@@ -171,7 +184,7 @@
 }
 
 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.go b/cluster/router/condition/factory.go
index 66512a1..f8d3e13 100644
--- a/cluster/router/condition/factory.go
+++ b/cluster/router/condition/factory.go
@@ -32,28 +32,28 @@
 // ConditionRouterFactory Condition router factory
 type ConditionRouterFactory struct{}
 
-func newConditionRouterFactory() router.RouterFactory {
+func newConditionRouterFactory() router.PriorityRouterFactory {
 	return &ConditionRouterFactory{}
 }
 
-// NewRouter Create ConditionRouterFactory by URL
-func (c *ConditionRouterFactory) NewRouter(url *common.URL) (router.Router, error) {
+// NewPriorityRouter creates ConditionRouterFactory by URL
+func (c *ConditionRouterFactory) NewPriorityRouter(url *common.URL) (router.PriorityRouter, error) {
 	return NewConditionRouter(url)
 }
 
 // NewRouter Create FileRouterFactory by Content
-func (c *ConditionRouterFactory) NewFileRouter(content []byte) (router.Router, error) {
+func (c *ConditionRouterFactory) NewFileRouter(content []byte) (router.PriorityRouter, error) {
 	return NewFileConditionRouter(content)
 }
 
 // AppRouterFactory Application router factory
 type AppRouterFactory struct{}
 
-func newAppRouterFactory() router.RouterFactory {
+func newAppRouterFactory() router.PriorityRouterFactory {
 	return &AppRouterFactory{}
 }
 
-// NewRouter Create AppRouterFactory by URL
-func (c *AppRouterFactory) NewRouter(url *common.URL) (router.Router, error) {
+// NewPriorityRouter creates AppRouterFactory by URL
+func (c *AppRouterFactory) NewPriorityRouter(url *common.URL) (router.PriorityRouter, error) {
 	return NewAppRouter(url)
 }
diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go
index 99cec34..0f61b39 100644
--- a/cluster/router/condition/factory_test.go
+++ b/cluster/router/condition/factory_test.go
@@ -33,11 +33,22 @@
 
 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 (
+	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
 	available    bool
@@ -59,21 +70,21 @@
 }
 
 func getRouteUrl(rule string) *common.URL {
-	url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService")
+	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("condition://0.0.0.0/com.foo.BarService")
+	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("condition://0.0.0.0/com.foo.BarService")
+	url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE))
 	url.AddParam("rule", rule)
 	return &url
 }
@@ -119,32 +130,32 @@
 func TestRoute_matchWhen(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4"))
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
-	cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService")
+	router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule))
+	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"))
-	router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1))
+	router1, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule1))
 	matchWhen1 := router1.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen1)
 	rule2 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 & host !=1.1.1.1 => host = 1.2.3.4"))
-	router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2))
+	router2, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule2))
 	matchWhen2 := router2.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, false, matchWhen2)
 	rule3 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.4 & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4"))
-	router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3))
+	router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3))
 	matchWhen3 := router3.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen3)
 	rule4 := 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"))
-	router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4))
+	router4, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule4))
 	matchWhen4 := router4.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen4)
 	rule5 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.1 => host = 1.2.3.4"))
-	router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5))
+	router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5))
 	matchWhen5 := router5.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, false, matchWhen5)
 	rule6 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4"))
-	router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6))
+	router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6))
 	matchWhen6 := router6.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen6)
 }
@@ -153,8 +164,8 @@
 	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"))
@@ -162,13 +173,13 @@
 	rule4 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.2,10.20.3.3,10.20.3.4"))
 	rule5 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host != 10.20.3.3"))
 	rule6 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " serialization = fastjson"))
-	router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1))
-	router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2))
-	router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3))
-	router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4))
-	router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5))
-	router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6))
-	cUrl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
+	router1, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule1))
+	router2, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule2))
+	router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3))
+	router4, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule4))
+	router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5))
+	router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6))
+	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{})
@@ -187,21 +198,21 @@
 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().NewRouter(getRouteUrl(rule))
+	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().NewRouter(getRouteUrl(rule2))
+	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().NewRouter(getRouteUrl(rule3))
+	router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3))
 	matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv)
 	assert.Equal(t, true, matchWhen)
 
@@ -213,8 +224,8 @@
 	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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 }
@@ -225,8 +236,8 @@
 	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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 }
@@ -241,25 +252,25 @@
 	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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 	assert.Equal(t, invoker2, fileredInvokers[0])
@@ -268,17 +279,17 @@
 
 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 	assert.Equal(t, invoker2, fileredInvokers[0])
@@ -287,17 +298,17 @@
 
 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 	assert.Equal(t, invoker2, fileredInvokers[0])
@@ -306,17 +317,17 @@
 
 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	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))
 	assert.Equal(t, invoker2, fileredInvokers[0])
@@ -325,34 +336,34 @@
 
 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithNoForce(rule))
+	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 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")
-	router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithForce(rule, "true"))
+	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 efeec53..eabdf1c 100644
--- a/cluster/router/condition/file.go
+++ b/cluster/router/condition/file.go
@@ -44,7 +44,7 @@
 // NewFileConditionRouter Create file condition router instance with content ( from config file)
 func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) {
 	fileRouter := &FileConditionRouter{}
-	rule, err := Parse(string(content))
+	rule, err := getRule(string(content))
 	if err != nil {
 		return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err))
 	}
@@ -77,10 +77,7 @@
 }
 
 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 @@
 					then = provider
 				}
 			}
-
 		}
-
 	}
-
 	return strings.Join([]string{when, then}, " => ")
 }
diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go
index ba2fbb0..4ccc19e 100644
--- a/cluster/router/condition/listenable_router.go
+++ b/cluster/router/condition/listenable_router.go
@@ -102,7 +102,7 @@
 		return
 	}
 
-	routerRule, err := Parse(content)
+	routerRule, err := getRule(content)
 	if err != nil {
 		logger.Errorf("Parse condition router rule fail,error:[%s] ", err)
 		return
diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go
index c5d4644..40a2515 100644
--- a/cluster/router/condition/router.go
+++ b/cluster/router/condition/router.go
@@ -27,7 +27,6 @@
 )
 
 import (
-	matcher "github.com/apache/dubbo-go/cluster/router/match"
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/logger"
@@ -182,9 +181,7 @@
 		return condition, nil
 	}
 
-	var (
-		pair MatchPair
-	)
+	var pair MatchPair
 	values := gxset.NewSet()
 	matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1)
 	for _, groups := range matches {
@@ -301,7 +298,7 @@
 	if !pair.Matches.Empty() && pair.Mismatches.Empty() {
 
 		for match := range pair.Matches.Items {
-			if matcher.IsMatchGlobalPattern(match.(string), value, param) {
+			if isMatchGlobalPattern(match.(string), value, param) {
 				return true
 			}
 		}
@@ -310,7 +307,7 @@
 	if !pair.Mismatches.Empty() && pair.Matches.Empty() {
 
 		for mismatch := range pair.Mismatches.Items {
-			if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) {
+			if isMatchGlobalPattern(mismatch.(string), value, param) {
 				return false
 			}
 		}
@@ -319,12 +316,12 @@
 	if !pair.Mismatches.Empty() && !pair.Matches.Empty() {
 		//when both mismatches and matches contain the same value, then using mismatches first
 		for mismatch := range pair.Mismatches.Items {
-			if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) {
+			if isMatchGlobalPattern(mismatch.(string), value, param) {
 				return false
 			}
 		}
 		for match := range pair.Matches.Items {
-			if matcher.IsMatchGlobalPattern(match.(string), value, param) {
+			if isMatchGlobalPattern(match.(string), value, param) {
 				return true
 			}
 		}
diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go
index 1374cf9..ce397d6 100644
--- a/cluster/router/condition/router_rule.go
+++ b/cluster/router/condition/router_rule.go
@@ -18,11 +18,17 @@
 package condition
 
 import (
-	"gopkg.in/yaml.v2"
+	"strings"
+)
+
+import (
+	gxstrings "github.com/dubbogo/gost/strings"
 )
 
 import (
 	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/yaml"
 )
 
 // RouterRule RouterRule config read from config file or config center
@@ -44,9 +50,9 @@
  *     =>
  *     1.1.1.1
  */
-func Parse(rawRule string) (*RouterRule, error) {
+func getRule(rawRule string) (*RouterRule, error) {
 	r := &RouterRule{}
-	err := yaml.Unmarshal([]byte(rawRule), r)
+	err := yaml.UnmarshalYML([]byte(rawRule), r)
 	if err != nil {
 		return r, err
 	}
@@ -57,3 +63,11 @@
 
 	return r, nil
 }
+
+// isMatchGlobalPattern Match value to param content by pattern
+func isMatchGlobalPattern(pattern string, value string, param *common.URL) bool {
+	if param != nil && strings.HasPrefix(pattern, "$") {
+		pattern = param.GetRawParam(pattern[1:])
+	}
+	return gxstrings.IsMatchPattern(pattern, value)
+}
diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go
index 5acc728..675acae 100644
--- a/cluster/router/condition/router_rule_test.go
+++ b/cluster/router/condition/router_rule_test.go
@@ -20,11 +20,16 @@
 import (
 	"testing"
 )
+
 import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestParse(t *testing.T) {
+import (
+	"github.com/apache/dubbo-go/common"
+)
+
+func TestGetRule(t *testing.T) {
 	testyml := `
 scope: application
 runtime: true
@@ -36,7 +41,7 @@
     ip=127.0.0.1
     =>
     1.1.1.1`
-	rule, e := Parse(testyml)
+	rule, e := getRule(testyml)
 
 	assert.Nil(t, e)
 	assert.NotNil(t, rule)
@@ -50,3 +55,8 @@
 	assert.Equal(t, false, rule.Dynamic)
 	assert.Equal(t, "", rule.Key)
 }
+
+func TestIsMatchGlobPattern(t *testing.T) {
+	url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e")
+	assert.Equal(t, true, isMatchGlobalPattern("$key", "value", &url))
+}
diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go
index 74aa394..5d35ae8 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,12 +33,18 @@
 	"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, 1)
+	invoker := NewMockInvoker(url)
 	healthy := hc.IsHealthy(invoker)
 	assert.True(t, healthy)
 
@@ -45,7 +52,7 @@
 	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 @@
 
 	// 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 @@
 	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 @@
 	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 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.go b/cluster/router/healthcheck/factory.go
index 32d84d1..40c9dd7 100644
--- a/cluster/router/healthcheck/factory.go
+++ b/cluster/router/healthcheck/factory.go
@@ -33,11 +33,11 @@
 }
 
 // newHealthCheckRouteFactory construct a new HealthCheckRouteFactory
-func newHealthCheckRouteFactory() router.RouterFactory {
+func newHealthCheckRouteFactory() router.PriorityRouterFactory {
 	return &HealthCheckRouteFactory{}
 }
 
-// NewRouter construct a new NewHealthCheckRouter via url
-func (f *HealthCheckRouteFactory) NewRouter(url *common.URL) (router.Router, error) {
+// NewPriorityRouter construct a new NewHealthCheckRouter via url
+func (f *HealthCheckRouteFactory) NewPriorityRouter(url *common.URL) (router.PriorityRouter, error) {
 	return NewHealthCheckRouter(url)
 }
diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go
index a9d94da..c3a26a9 100644
--- a/cluster/router/healthcheck/factory_test.go
+++ b/cluster/router/healthcheck/factory_test.go
@@ -35,7 +35,7 @@
 	url common.URL
 }
 
-func NewMockInvoker(url common.URL, successCount int) *MockInvoker {
+func NewMockInvoker(url common.URL) *MockInvoker {
 	return &MockInvoker{
 		url: url,
 	}
diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go
index 1ddc9cc..ee42e47 100644
--- a/cluster/router/healthcheck/health_check_route.go
+++ b/cluster/router/healthcheck/health_check_route.go
@@ -38,7 +38,7 @@
 }
 
 // NewHealthCheckRouter construct an HealthCheckRouter via url
-func NewHealthCheckRouter(url *common.URL) (router.Router, error) {
+func NewHealthCheckRouter(url *common.URL) (router.PriorityRouter, error) {
 	r := &HealthCheckRouter{
 		url:     url,
 		enabled: url.GetParamBool(HEALTH_ROUTE_ENABLED_KEY, false),
diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go
index 759ef93..d5862fb 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,35 +35,44 @@
 	"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
-	invoker1 := NewMockInvoker(url1, 1)
-	invoker2 := NewMockInvoker(url2, 1)
-	invoker3 := NewMockInvoker(url3, 1)
+	invoker1 := NewMockInvoker(url1)
+	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 @@
 	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 @@
 	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 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/match/match_utils.go b/cluster/router/match/match_utils.go
deleted file mode 100644
index 28fe715..0000000
--- a/cluster/router/match/match_utils.go
+++ /dev/null
@@ -1,63 +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 match
-
-import (
-	"strings"
-)
-
-import (
-	"github.com/apache/dubbo-go/common"
-)
-
-// IsMatchGlobalPattern Match value to param content by pattern
-func IsMatchGlobalPattern(pattern string, value string, param *common.URL) bool {
-	if param != nil && strings.HasPrefix(pattern, "$") {
-		pattern = param.GetRawParam(pattern[1:])
-	}
-	return isMatchInternalPattern(pattern, value)
-}
-
-func isMatchInternalPattern(pattern string, value string) bool {
-	if "*" == pattern {
-		return true
-	}
-	if len(pattern) == 0 && len(value) == 0 {
-		return true
-	}
-	if len(pattern) == 0 || len(value) == 0 {
-		return false
-	}
-	i := strings.LastIndex(pattern, "*")
-	switch i {
-	case -1:
-		// doesn't find "*"
-		return value == pattern
-	case len(pattern) - 1:
-		// "*" is at the end
-		return strings.HasPrefix(value, pattern[0:i])
-	case 0:
-		// "*" is at the beginning
-		return strings.HasSuffix(value, pattern[i+1:])
-	default:
-		// "*" is in the middle
-		prefix := pattern[0:1]
-		suffix := pattern[i+1:]
-		return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix)
-	}
-}
diff --git a/cluster/router/match/match_utils_test.go b/cluster/router/match/match_utils_test.go
deleted file mode 100644
index f16480f..0000000
--- a/cluster/router/match/match_utils_test.go
+++ /dev/null
@@ -1,46 +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 match
-
-import (
-	"testing"
-)
-
-import (
-	"github.com/stretchr/testify/assert"
-)
-
-import (
-	"github.com/apache/dubbo-go/common"
-)
-
-func TestIsMatchInternalPattern(t *testing.T) {
-	assert.Equal(t, true, isMatchInternalPattern("*", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("", ""))
-	assert.Equal(t, false, isMatchInternalPattern("", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("value", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("v*", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("*ue", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("*e", "value"))
-	assert.Equal(t, true, isMatchInternalPattern("v*e", "value"))
-}
-
-func TestIsMatchGlobPattern(t *testing.T) {
-	url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e")
-	assert.Equal(t, true, IsMatchGlobalPattern("$key", "value", &url))
-}
diff --git a/cluster/router/router.go b/cluster/router/router.go
index a28002a..1d1f79d 100644
--- a/cluster/router/router.go
+++ b/cluster/router/router.go
@@ -23,34 +23,30 @@
 )
 
 // Extension - Router
-
-// RouterFactory Router create factory
-type RouterFactory interface {
-	// NewRouter Create router instance with URL
-	NewRouter(*common.URL) (Router, error)
+// PriorityRouterFactory creates creates priority router with url
+type PriorityRouterFactory interface {
+	// NewPriorityRouter creates router instance with URL
+	NewPriorityRouter(*common.URL) (PriorityRouter, error)
 }
 
-// RouterFactory Router create factory use for parse config file
-type FIleRouterFactory interface {
+// FilePriorityRouterFactory creates priority router with parse config file
+type FilePriorityRouterFactory interface {
 	// NewFileRouters Create file router with config file
-	NewFileRouter([]byte) (Router, error)
+	NewFileRouter([]byte) (PriorityRouter, error)
 }
 
 // Router
-type Router interface {
+type router interface {
 	// Route Determine the target invokers list.
 	Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker
-	// Priority Return Priority in router
-	// 0 to ^int(0) is better
-	Priority() int64
 	// URL Return URL in router
 	URL() common.URL
 }
 
-// Chain
-type Chain interface {
-	// Route Determine the target invokers list with chain.
-	Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker
-	// AddRouters Add routers
-	AddRouters([]Router)
+// Router
+type PriorityRouter interface {
+	router
+	// Priority Return Priority in router
+	// 0 to ^int(0) is better
+	Priority() int64
 }
diff --git a/cluster/router/tag/factory.go b/cluster/router/tag/factory.go
new file mode 100644
index 0000000..a5d989c
--- /dev/null
+++ b/cluster/router/tag/factory.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 tag
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+)
+
+func init() {
+	extension.SetRouterFactory(constant.TagRouterName, NewTagRouterFactory)
+}
+
+type tagRouterFactory struct{}
+
+// NewTagRouterFactory create a tagRouterFactory
+func NewTagRouterFactory() router.PriorityRouterFactory {
+	return &tagRouterFactory{}
+}
+
+// NewPriorityRouter create a tagRouter by tagRouterFactory with a url
+// The url contains router configuration information
+func (c *tagRouterFactory) NewPriorityRouter(url *common.URL) (router.PriorityRouter, error) {
+	return NewTagRouter(url)
+}
+
+// NewFileRouter create a tagRouter by profile content
+func (c *tagRouterFactory) NewFileRouter(content []byte) (router.PriorityRouter, error) {
+	return NewFileTagRouter(content)
+}
diff --git a/cluster/router/tag/factory_test.go b/cluster/router/tag/factory_test.go
new file mode 100644
index 0000000..ee19582
--- /dev/null
+++ b/cluster/router/tag/factory_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 tag
+
+import (
+	"fmt"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+)
+
+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)
+	assert.Nil(t, e)
+	assert.NotNil(t, tagRouter)
+}
diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go
new file mode 100644
index 0000000..8144c83
--- /dev/null
+++ b/cluster/router/tag/file.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 tag
+
+import (
+	"net/url"
+	"strconv"
+	"sync"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+// FileTagRouter Use for parse config file of Tag router
+type FileTagRouter struct {
+	parseOnce  sync.Once
+	router     *tagRouter
+	routerRule *RouterRule
+	url        *common.URL
+	force      bool
+}
+
+// NewFileTagRouter Create file tag router instance with content ( from config file)
+func NewFileTagRouter(content []byte) (*FileTagRouter, error) {
+	fileRouter := &FileTagRouter{}
+	rule, err := getRule(string(content))
+	if err != nil {
+		return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err))
+	}
+	fileRouter.routerRule = rule
+	url := fileRouter.URL()
+	fileRouter.router, err = NewTagRouter(&url)
+	return fileRouter, err
+}
+
+// URL Return URL in file tag router n
+func (f *FileTagRouter) URL() common.URL {
+	f.parseOnce.Do(func() {
+		routerRule := f.routerRule
+		f.url = common.NewURLWithOptions(
+			common.WithProtocol(constant.TAG_ROUTE_PROTOCOL),
+			common.WithParams(url.Values{}),
+			common.WithParamsValue(constant.ForceUseTag, strconv.FormatBool(routerRule.Force)),
+			common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)),
+			common.WithParamsValue(constant.ROUTER_KEY, constant.TAG_ROUTE_PROTOCOL))
+	})
+	return *f.url
+}
+
+// Priority Return Priority in listenable router
+func (f *FileTagRouter) Priority() int64 {
+	return f.router.priority
+}
+
+func (f *FileTagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if len(invokers) == 0 {
+		return invokers
+	}
+	return f.Route(invokers, url, invocation)
+}
diff --git a/cluster/router/tag/file_test.go b/cluster/router/tag/file_test.go
new file mode 100644
index 0000000..513ba0c
--- /dev/null
+++ b/cluster/router/tag/file_test.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 tag
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+const (
+	fileTestTag = `priority: 100
+force: true`
+)
+
+func TestNewFileTagRouter(t *testing.T) {
+	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 TestFileTagRouterURL(t *testing.T) {
+	router, e := NewFileTagRouter([]byte(fileTestTag))
+	assert.Nil(t, e)
+	assert.NotNil(t, router)
+	url := router.URL()
+	assert.NotNil(t, url)
+	force := url.GetParam(constant.ForceUseTag, "false")
+	priority := url.GetParam(constant.RouterPriority, "0")
+	assert.Equal(t, "true", force)
+	assert.Equal(t, "100", priority)
+
+}
+
+func TestFileTagRouterPriority(t *testing.T) {
+	router, e := NewFileTagRouter([]byte(fileTestTag))
+	assert.Nil(t, e)
+	assert.NotNil(t, router)
+	priority := router.Priority()
+	assert.Equal(t, int64(100), priority)
+}
diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go
new file mode 100644
index 0000000..926446d
--- /dev/null
+++ b/cluster/router/tag/router_rule.go
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tag
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/common/yaml"
+)
+
+// RouterRule RouterRule config read from config file or config center
+type RouterRule struct {
+	router.BaseRouterRule `yaml:",inline""`
+}
+
+func getRule(rawRule string) (*RouterRule, error) {
+	r := &RouterRule{}
+	err := yaml.UnmarshalYML([]byte(rawRule), r)
+	if err != nil {
+		return r, err
+	}
+	r.RawRule = rawRule
+	return r, nil
+}
diff --git a/cluster/router/tag/router_rule_test.go b/cluster/router/tag/router_rule_test.go
new file mode 100644
index 0000000..2df6519
--- /dev/null
+++ b/cluster/router/tag/router_rule_test.go
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tag
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetRule(t *testing.T) {
+	yml := `
+scope: application
+runtime: true
+force: true
+`
+	rule, e := getRule(yml)
+	assert.Nil(t, e)
+	assert.NotNil(t, rule)
+	assert.Equal(t, true, rule.Force)
+	assert.Equal(t, true, rule.Runtime)
+	assert.Equal(t, "application", rule.Scope)
+}
diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go
new file mode 100644
index 0000000..87da418
--- /dev/null
+++ b/cluster/router/tag/tag_router.go
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tag
+
+import (
+	"strconv"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+type tagRouter struct {
+	url      *common.URL
+	enabled  bool
+	priority int64
+}
+
+func NewTagRouter(url *common.URL) (*tagRouter, error) {
+	if url == nil {
+		return nil, perrors.Errorf("Illegal route URL!")
+	}
+	return &tagRouter{
+		url:      url,
+		enabled:  url.GetParamBool(constant.RouterEnabled, true),
+		priority: url.GetParamInt(constant.RouterPriority, 0),
+	}, nil
+}
+
+func (c *tagRouter) isEnabled() bool {
+	return c.enabled
+}
+
+func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if !c.isEnabled() {
+		return invokers
+	}
+	if len(invokers) == 0 {
+		return invokers
+	}
+	return filterUsingStaticTag(invokers, url, invocation)
+}
+
+func (c *tagRouter) URL() common.URL {
+	return *c.url
+}
+
+func (c *tagRouter) Priority() int64 {
+	return c.priority
+}
+
+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)
+		for _, v := range invokers {
+			if v.GetUrl().GetParam(constant.Tagkey, "") == tag {
+				result = append(result, v)
+			}
+		}
+		if len(result) == 0 && !isForceUseTag(url, invocation) {
+			return invokers
+		}
+		return result
+	}
+	return invokers
+}
+
+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
+	}
+	return false
+}
diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go
new file mode 100644
index 0000000..000b3ec
--- /dev/null
+++ b/cluster/router/tag/tag_router_test.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 tag
+
+import (
+	"context"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/protocol"
+	"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
+	available    bool
+	destroyed    bool
+	successCount int
+}
+
+func NewMockInvoker(url common.URL) *MockInvoker {
+	return &MockInvoker{
+		url:          url,
+		available:    true,
+		destroyed:    false,
+		successCount: 0,
+	}
+}
+
+func (bi *MockInvoker) GetUrl() common.URL {
+	return bi.url
+}
+
+func (bi *MockInvoker) IsAvailable() bool {
+	return bi.available
+}
+
+func (bi *MockInvoker) IsDestroyed() bool {
+	return bi.destroyed
+}
+
+func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result {
+	bi.successCount++
+
+	result := &protocol.RPCResult{Err: nil}
+	return result
+}
+
+func (bi *MockInvoker) Destroy() {
+	bi.destroyed = true
+	bi.available = false
+}
+
+func TestTagRouterPriority(t *testing.T) {
+	u1, err := common.NewURL(tagRouterTestUserConsumerTag)
+	assert.Nil(t, err)
+	tagRouter, e := NewTagRouter(&u1)
+	assert.Nil(t, e)
+	p := tagRouter.Priority()
+	assert.Equal(t, int64(0), p)
+}
+
+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(tagRouterTestHangZhouUrl)
+	u3, e3 := common.NewURL(tagRouterTestShangHaiUrl)
+	u4, e4 := common.NewURL(tagRouterTestBeijingUrl)
+	assert.Nil(t, e2)
+	assert.Nil(t, e3)
+	assert.Nil(t, e4)
+	inv2 := NewMockInvoker(u2)
+	inv3 := NewMockInvoker(u3)
+	inv4 := NewMockInvoker(u4)
+	var invokers []protocol.Invoker
+	invokers = append(invokers, inv2, inv3, inv4)
+	inv := &invocation.RPCInvocation{}
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou)
+	invRst1 := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 1, len(invRst1))
+	assert.Equal(t, tagRouterTestHangZhou, invRst1[0].GetUrl().GetParam(tagRouterTestDubboTag, ""))
+
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
+	invRst2 := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 0, len(invRst2))
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse)
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
+	invRst3 := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 3, len(invRst3))
+}
+
+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(tagRouterTestHangZhouUrl)
+	u3, e3 := common.NewURL(tagRouterTestShangHaiUrl)
+	u4, e4 := common.NewURL(tagRouterTestShangHaiUrl)
+	assert.Nil(t, e2)
+	assert.Nil(t, e3)
+	assert.Nil(t, e4)
+	inv2 := NewMockInvoker(u2)
+	inv3 := NewMockInvoker(u3)
+	inv4 := NewMockInvoker(u4)
+	var invokers []protocol.Invoker
+	invokers = append(invokers, inv2, inv3, inv4)
+	inv := &invocation.RPCInvocation{}
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou)
+	invRst := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 1, len(invRst))
+	assert.Equal(t, tagRouterTestHangZhou, invRst[0].GetUrl().GetParam(tagRouterTestDubboTag, ""))
+
+	inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou)
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestTrue)
+	invRst1 := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 0, len(invRst1))
+	inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse)
+	invRst2 := tagRouter.Route(invokers, &u1, inv)
+	assert.Equal(t, 3, len(invRst2))
+}
diff --git a/common/config/environment.go b/common/config/environment.go
index 071af31..44cdd1f 100644
--- a/common/config/environment.go
+++ b/common/config/environment.go
@@ -46,7 +46,7 @@
 	once     sync.Once
 )
 
-// GetEnvInstance ...
+// GetEnvInstance gets env instance by singleton
 func GetEnvInstance() *Environment {
 	once.Do(func() {
 		instance = &Environment{configCenterFirst: true}
@@ -54,7 +54,7 @@
 	return instance
 }
 
-// NewEnvInstance ...
+// NewEnvInstance creates Environment instance
 func NewEnvInstance() {
 	instance = &Environment{configCenterFirst: true}
 }
@@ -67,21 +67,22 @@
 //	return env.configCenterFirst
 //}
 
-// UpdateExternalConfigMap ...
+// UpdateExternalConfigMap updates env externalConfigMap field
 func (env *Environment) UpdateExternalConfigMap(externalMap map[string]string) {
 	for k, v := range externalMap {
 		env.externalConfigMap.Store(k, v)
 	}
 }
 
-// UpdateAppExternalConfigMap ...
+// UpdateAppExternalConfigMap updates env appExternalConfigMap field
 func (env *Environment) UpdateAppExternalConfigMap(externalMap map[string]string) {
 	for k, v := range externalMap {
 		env.appExternalConfigMap.Store(k, v)
 	}
 }
 
-// Configuration ...
+// Configuration puts externalConfigMap and appExternalConfigMap into list
+// List represents a doubly linked list.
 func (env *Environment) Configuration() *list.List {
 	cfgList := list.New()
 	// The sequence would be: SystemConfiguration -> ExternalConfiguration -> AppExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
@@ -90,17 +91,17 @@
 	return cfgList
 }
 
-// SetDynamicConfiguration ...
+// SetDynamicConfiguration sets value for dynamicConfiguration
 func (env *Environment) SetDynamicConfiguration(dc config_center.DynamicConfiguration) {
 	env.dynamicConfiguration = dc
 }
 
-// GetDynamicConfiguration ...
+// GetDynamicConfiguration gets dynamicConfiguration
 func (env *Environment) GetDynamicConfiguration() config_center.DynamicConfiguration {
 	return env.dynamicConfiguration
 }
 
-// InmemoryConfiguration ...
+// InmemoryConfiguration stores config in memory
 type InmemoryConfiguration struct {
 	store *sync.Map
 }
@@ -109,7 +110,7 @@
 	return &InmemoryConfiguration{store: p}
 }
 
-// GetProperty ...
+// GetProperty gets value from InmemoryConfiguration instance by @key
 func (conf *InmemoryConfiguration) GetProperty(key string) (bool, string) {
 	if conf.store == nil {
 		return false, ""
@@ -123,14 +124,14 @@
 	return false, ""
 }
 
-// GetSubProperty ...
+// GetSubProperty gets sub property from InmemoryConfiguration instance by @subkey
 func (conf *InmemoryConfiguration) GetSubProperty(subKey string) map[string]struct{} {
 	if conf.store == nil {
 		return nil
 	}
 
 	properties := make(map[string]struct{})
-	conf.store.Range(func(key, value interface{}) bool {
+	conf.store.Range(func(key, _ interface{}) bool {
 		if idx := strings.Index(key.(string), subKey); idx >= 0 {
 			after := key.(string)[idx+len(subKey):]
 			if i := strings.Index(after, "."); i >= 0 {
diff --git a/common/config/environment_test.go b/common/config/environment_test.go
index 2d84dc4..183071b 100644
--- a/common/config/environment_test.go
+++ b/common/config/environment_test.go
@@ -29,14 +29,14 @@
 	assert.NotNil(t, instance)
 }
 
-func TestEnvironment_UpdateExternalConfigMap(t *testing.T) {
+func TestEnvironmentUpdateExternalConfigMap(t *testing.T) {
 	GetEnvInstance().UpdateExternalConfigMap(map[string]string{"1": "2"})
 	v, ok := GetEnvInstance().externalConfigMap.Load("1")
 	assert.True(t, ok)
 	assert.Equal(t, "2", v)
 }
 
-func TestEnvironment_ConfigurationAndGetProperty(t *testing.T) {
+func TestEnvironmentConfigurationAndGetProperty(t *testing.T) {
 	GetEnvInstance().UpdateExternalConfigMap(map[string]string{"1": "2"})
 	list := GetEnvInstance().Configuration()
 	ok, v := list.Back().Value.(*InmemoryConfiguration).GetProperty("1")
@@ -44,7 +44,7 @@
 	assert.Equal(t, "2", v)
 }
 
-func TestInmemoryConfiguration_GetSubProperty(t *testing.T) {
+func TestInmemoryConfigurationGetSubProperty(t *testing.T) {
 	GetEnvInstance().UpdateExternalConfigMap(map[string]string{"123": "2"})
 	list := GetEnvInstance().Configuration()
 	m := list.Front().Value.(*InmemoryConfiguration).GetSubProperty("1")
diff --git a/common/constant/default.go b/common/constant/default.go
index c5aa5ac..c1c404e 100644
--- a/common/constant/default.go
+++ b/common/constant/default.go
@@ -43,6 +43,7 @@
 	DEFAULT_FAILBACK_TASKS     = 100
 	DEFAULT_REST_CLIENT        = "resty"
 	DEFAULT_REST_SERVER        = "go-restful"
+	DEFAULT_PORT               = 20000
 )
 
 const (
@@ -57,8 +58,8 @@
 
 const (
 	ANY_VALUE           = "*"
-	ANYHOST_KEY         = "anyhost"
 	ANYHOST_VALUE       = "0.0.0.0"
+	LOCAL_HOST_VALUE    = "192.168.1.1"
 	REMOVE_VALUE_PREFIX = "-"
 )
 
@@ -75,3 +76,12 @@
 const (
 	COMMA_SPLIT_PATTERN = "\\s*[,]+\\s*"
 )
+
+const (
+	SIMPLE_METADATA_SERVICE_NAME = "MetadataService"
+	DEFAULT_REVIESION            = "N/A"
+)
+
+const (
+	SERVICE_DISCOVERY_DEFAULT_GROUP = "DEFAULT_GROUP"
+)
diff --git a/common/constant/key.go b/common/constant/key.go
index 07335be..cd23dd0 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -22,10 +22,12 @@
 )
 
 const (
+	PORT_KEY               = "port"
 	GROUP_KEY              = "group"
 	VERSION_KEY            = "version"
 	INTERFACE_KEY          = "interface"
 	PATH_KEY               = "path"
+	PROTOCOL_KEY           = "protocol"
 	SERVICE_KEY            = "service"
 	METHODS_KEY            = "methods"
 	TIMEOUT_KEY            = "timeout"
@@ -41,6 +43,9 @@
 	LOCAL_ADDR             = "local-addr"
 	REMOTE_ADDR            = "remote-addr"
 	PATH_SEPARATOR         = "/"
+	DUBBO_KEY              = "dubbo"
+	RELEASE_KEY            = "release"
+	ANYHOST_KEY            = "anyhost"
 )
 
 const (
@@ -75,6 +80,11 @@
 	EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler"
 	PROVIDER_SHUTDOWN_FILTER               = "pshutdown"
 	CONSUMER_SHUTDOWN_FILTER               = "cshutdown"
+	PID_KEY                                = "pid"
+	SYNC_REPORT_KEY                        = "sync.report"
+	RETRY_PERIOD_KEY                       = "retry.period"
+	RETRY_TIMES_KEY                        = "retry.times"
+	CYCLE_REPORT_KEY                       = "cycle.report"
 )
 
 const (
@@ -105,6 +115,7 @@
 	ROUTERS_CATEGORY         = "routers"
 	ROUTE_PROTOCOL           = "route"
 	CONDITION_ROUTE_PROTOCOL = "condition"
+	TAG_ROUTE_PROTOCOL       = "tag"
 	PROVIDERS_CATEGORY       = "providers"
 	ROUTER_KEY               = "router"
 )
@@ -116,6 +127,7 @@
 	CONFIG_CLUSTER_KEY    = "config.cluster"
 	CONFIG_CHECK_KEY      = "config.check"
 	CONFIG_TIMEOUT_KET    = "config.timeout"
+	CONFIG_LOG_DIR_KEY    = "config.logDir"
 	CONFIG_VERSION_KEY    = "configVersion"
 	COMPATIBLE_CONFIG_KEY = "compatible_config"
 )
@@ -128,6 +140,7 @@
 	ProviderConfigPrefix       = "dubbo.provider."
 	ConsumerConfigPrefix       = "dubbo.consumer."
 	ShutdownConfigPrefix       = "dubbo.shutdown."
+	MetadataReportPrefix       = "dubbo.metadata-report."
 	RouterConfigPrefix         = "dubbo.router."
 )
 
@@ -145,6 +158,17 @@
 	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 (
@@ -161,7 +185,8 @@
 	ListenableRouterName = "listenable"
 	// HealthCheckRouterName Specify the name of HealthCheckRouter
 	HealthCheckRouterName = "health_check"
-
+	// TagRouterName Specify the name of TagRouter
+	TagRouterName = "tag"
 	// ConditionRouterRuleSuffix Specify condition router suffix
 	ConditionRouterRuleSuffix = ".condition-router"
 
@@ -171,6 +196,13 @@
 	RouterEnabled = "enabled"
 	// Priority Priority key in router module
 	RouterPriority = "priority"
+
+	// ForceUseTag is the tag in attachment
+	ForceUseTag = "dubbo.force.tag"
+	Tagkey      = "dubbo.tag"
+
+	// Attachment key in context in invoker
+	AttachmentKey = "attachment"
 )
 
 const (
@@ -201,9 +233,22 @@
 	// consumer
 	CONSUMER = "consumer"
 	// key of access key id
-	ACCESS_KEY_ID_KEY = "accessKeyId"
+	ACCESS_KEY_ID_KEY = ".accessKeyId"
 	// key of secret access key
-	SECRET_ACCESS_KEY_KEY = "secretAccessKey"
+	SECRET_ACCESS_KEY_KEY = ".secretAccessKey"
+)
+
+// metadata report
+
+const (
+	METACONFIG_REMOTE  = "remote"
+	METACONFIG_LOCAL   = "local"
+	KEY_SEPARATOR      = ":"
+	DEFAULT_PATH_TAG   = "metadata"
+	KEY_REVISON_PREFIX = "revision"
+
+	// metadata service
+	METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService"
 )
 
 // HealthCheck Router
@@ -227,3 +272,21 @@
 	// The default time window of circuit-tripped  in millisecond if not specfied
 	MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000
 )
+
+// service discovery
+const (
+	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/auth.go b/common/extension/auth.go
index d790004..4fca0a8 100644
--- a/common/extension/auth.go
+++ b/common/extension/auth.go
@@ -26,13 +26,13 @@
 	accesskeyStorages = make(map[string]func() filter.AccessKeyStorage)
 )
 
-// SetAuthenticator put the fcn into map with name
+// SetAuthenticator puts the @fcn into map with name
 func SetAuthenticator(name string, fcn func() filter.Authenticator) {
 	authenticators[name] = fcn
 }
 
-// GetAuthenticator find the Authenticator with name
-// if not found, it will panic
+// GetAuthenticator finds the Authenticator with @name
+// Panic if not found
 func GetAuthenticator(name string) filter.Authenticator {
 	if authenticators[name] == nil {
 		panic("authenticator for " + name + " is not existing, make sure you have import the package.")
@@ -40,13 +40,13 @@
 	return authenticators[name]()
 }
 
-// SetAccesskeyStorages will set the fcn into map with this name
+// SetAccesskeyStorages will set the @fcn into map with this name
 func SetAccesskeyStorages(name string, fcn func() filter.AccessKeyStorage) {
 	accesskeyStorages[name] = fcn
 }
 
-// GetAccesskeyStorages find the storage with the name.
-// If not found, it will panic.
+// GetAccesskeyStorages finds the storage with the @name.
+// Panic if not found
 func GetAccesskeyStorages(name string) filter.AccessKeyStorage {
 	if accesskeyStorages[name] == nil {
 		panic("accesskeyStorages for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/cluster.go b/common/extension/cluster.go
index b2d81f6..8be27a1 100644
--- a/common/extension/cluster.go
+++ b/common/extension/cluster.go
@@ -25,12 +25,13 @@
 	clusters = make(map[string]func() cluster.Cluster)
 )
 
-// SetCluster ...
+// SetCluster sets the cluster fault-tolerant mode with @name
+// For example: available/failfast/broadcast/failfast/failsafe/...
 func SetCluster(name string, fcn func() cluster.Cluster) {
 	clusters[name] = fcn
 }
 
-// GetCluster ...
+// GetCluster finds the cluster fault-tolerant mode with @name
 func GetCluster(name string) cluster.Cluster {
 	if clusters[name] == nil {
 		panic("cluster for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/config_center.go b/common/extension/config_center.go
index 03d27db..5a2c52f 100644
--- a/common/extension/config_center.go
+++ b/common/extension/config_center.go
@@ -26,12 +26,12 @@
 	configCenters = make(map[string]func(config *common.URL) (config_center.DynamicConfiguration, error))
 )
 
-// SetConfigCenter ...
-func SetConfigCenter(name string, v func(config *common.URL) (config_center.DynamicConfiguration, error)) {
+// SetConfigCenter sets the DynamicConfiguration with @name
+func SetConfigCenter(name string, v func(*common.URL) (config_center.DynamicConfiguration, error)) {
 	configCenters[name] = v
 }
 
-// GetConfigCenter ...
+// GetConfigCenter finds the DynamicConfiguration with @name
 func GetConfigCenter(name string, config *common.URL) (config_center.DynamicConfiguration, error) {
 	if configCenters[name] == nil {
 		panic("config center for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/config_center_factory.go b/common/extension/config_center_factory.go
index 85913fd..dff8975 100644
--- a/common/extension/config_center_factory.go
+++ b/common/extension/config_center_factory.go
@@ -25,12 +25,12 @@
 	configCenterFactories = make(map[string]func() config_center.DynamicConfigurationFactory)
 )
 
-// SetConfigCenterFactory ...
+// SetConfigCenterFactory sets the DynamicConfigurationFactory with @name
 func SetConfigCenterFactory(name string, v func() config_center.DynamicConfigurationFactory) {
 	configCenterFactories[name] = v
 }
 
-// GetConfigCenterFactory ...
+// GetConfigCenterFactory finds the DynamicConfigurationFactory with @name
 func GetConfigCenterFactory(name string) config_center.DynamicConfigurationFactory {
 	if configCenterFactories[name] == nil {
 		panic("config center for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/config_reader.go b/common/extension/config_reader.go
index aced5b0..5e13d86 100644
--- a/common/extension/config_reader.go
+++ b/common/extension/config_reader.go
@@ -26,12 +26,12 @@
 	defaults      = make(map[string]string)
 )
 
-// SetConfigReaders set a creator of config reader.
+// SetConfigReaders sets a creator of config reader with @name
 func SetConfigReaders(name string, v func() interfaces.ConfigReader) {
 	configReaders[name] = v
 }
 
-// GetConfigReaders get a config reader by name.
+// GetConfigReaders gets a config reader with @name
 func GetConfigReaders(name string) interfaces.ConfigReader {
 	if configReaders[name] == nil {
 		panic("config reader for " + name + " is not existing, make sure you have imported the package.")
@@ -39,12 +39,12 @@
 	return configReaders[name]()
 }
 
-// SetDefaultConfigReader set {name} to default config reader for {module}
+// SetDefaultConfigReader sets @name for @module in default config reader
 func SetDefaultConfigReader(module, name string) {
 	defaults[module] = name
 }
 
-// GetDefaultConfigReader
+// GetDefaultConfigReader gets default config reader
 func GetDefaultConfigReader() map[string]string {
 	return defaults
 }
diff --git a/common/extension/configurator.go b/common/extension/configurator.go
index de98f8a..dc2bea7 100644
--- a/common/extension/configurator.go
+++ b/common/extension/configurator.go
@@ -23,7 +23,7 @@
 )
 
 const (
-	// DefaultKey ...
+	// DefaultKey for default Configurator
 	DefaultKey = "default"
 )
 
@@ -33,12 +33,12 @@
 	configurator = make(map[string]getConfiguratorFunc)
 )
 
-// SetConfigurator ...
+// SetConfigurator sets the getConfiguratorFunc with @name
 func SetConfigurator(name string, v getConfiguratorFunc) {
 	configurator[name] = v
 }
 
-// GetConfigurator ...
+// GetConfigurator finds the Configurator with @name
 func GetConfigurator(name string, url *common.URL) config_center.Configurator {
 	if configurator[name] == nil {
 		panic("configurator for " + name + " is not existing, make sure you have import the package.")
@@ -47,12 +47,12 @@
 
 }
 
-// SetDefaultConfigurator ...
+// SetDefaultConfigurator sets the default Configurator
 func SetDefaultConfigurator(v getConfiguratorFunc) {
 	configurator[DefaultKey] = v
 }
 
-// GetDefaultConfigurator ...
+// GetDefaultConfigurator gets default configurator
 func GetDefaultConfigurator(url *common.URL) config_center.Configurator {
 	if configurator[DefaultKey] == nil {
 		panic("configurator for default is not existing, make sure you have import the package.")
@@ -61,7 +61,7 @@
 
 }
 
-// GetDefaultConfiguratorFunc ...
+// GetDefaultConfiguratorFunc gets default configurator function
 func GetDefaultConfiguratorFunc() getConfiguratorFunc {
 	if configurator[DefaultKey] == nil {
 		panic("configurator for default is not existing, make sure you have import the package.")
diff --git a/common/extension/event_dispatcher.go b/common/extension/event_dispatcher.go
new file mode 100644
index 0000000..f0503e0
--- /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 0000000..472360c
--- /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/filter.go b/common/extension/filter.go
index deea2d9..96059c4 100644
--- a/common/extension/filter.go
+++ b/common/extension/filter.go
@@ -26,12 +26,13 @@
 	rejectedExecutionHandler = make(map[string]func() filter.RejectedExecutionHandler)
 )
 
-// SetFilter ...
+// SetFilter sets the filter extension with @name
+// For example: hystrix/metrics/token/tracing/limit/...
 func SetFilter(name string, v func() filter.Filter) {
 	filters[name] = v
 }
 
-// GetFilter ...
+// GetFilter finds the filter extension with @name
 func GetFilter(name string) filter.Filter {
 	if filters[name] == nil {
 		panic("filter for " + name + " is not existing, make sure you have imported the package.")
@@ -39,12 +40,12 @@
 	return filters[name]()
 }
 
-// SetRejectedExecutionHandler ...
+// SetRejectedExecutionHandler sets the RejectedExecutionHandler with @name
 func SetRejectedExecutionHandler(name string, creator func() filter.RejectedExecutionHandler) {
 	rejectedExecutionHandler[name] = creator
 }
 
-// GetRejectedExecutionHandler ...
+// GetRejectedExecutionHandler finds the RejectedExecutionHandler with @name
 func GetRejectedExecutionHandler(name string) filter.RejectedExecutionHandler {
 	creator, ok := rejectedExecutionHandler[name]
 	if !ok {
diff --git a/common/extension/graceful_shutdown.go b/common/extension/graceful_shutdown.go
index 3abd75c..cb55419 100644
--- a/common/extension/graceful_shutdown.go
+++ b/common/extension/graceful_shutdown.go
@@ -49,7 +49,7 @@
 	customShutdownCallbacks.PushBack(callback)
 }
 
-// GetAllCustomShutdownCallbacks ...
+// GetAllCustomShutdownCallbacks gets all custom shutdown callbacks
 func GetAllCustomShutdownCallbacks() *list.List {
 	return customShutdownCallbacks
 }
diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go
index 365c5d0..8def727 100644
--- a/common/extension/health_checker.go
+++ b/common/extension/health_checker.go
@@ -26,12 +26,12 @@
 	healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker)
 )
 
-// SethealthChecker set the HealthChecker with name
-func SethealthChecker(name string, fcn func(url *common.URL) router.HealthChecker) {
+// SethealthChecker sets the HealthChecker with @name
+func SethealthChecker(name string, fcn func(_ *common.URL) router.HealthChecker) {
 	healthCheckers[name] = fcn
 }
 
-// GetHealthChecker get the HealthChecker with name
+// GetHealthChecker gets the HealthChecker with @name
 func GetHealthChecker(name string, url *common.URL) router.HealthChecker {
 	if healthCheckers[name] == nil {
 		panic("healthCheckers for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/health_checker_test.go b/common/extension/health_checker_test.go
index ec934e6..4e83a6f 100644
--- a/common/extension/health_checker_test.go
+++ b/common/extension/health_checker_test.go
@@ -44,6 +44,6 @@
 	return true
 }
 
-func newMockhealthCheck(url *common.URL) router.HealthChecker {
+func newMockhealthCheck(_ *common.URL) router.HealthChecker {
 	return &mockHealthChecker{}
 }
diff --git a/common/extension/loadbalance.go b/common/extension/loadbalance.go
index 0d557a4..aa19141 100644
--- a/common/extension/loadbalance.go
+++ b/common/extension/loadbalance.go
@@ -25,12 +25,13 @@
 	loadbalances = make(map[string]func() cluster.LoadBalance)
 )
 
-// SetLoadbalance ...
+// SetLoadbalance sets the loadbalance extension with @name
+// For example: random/round_robin/consistent_hash/least_active/...
 func SetLoadbalance(name string, fcn func() cluster.LoadBalance) {
 	loadbalances[name] = fcn
 }
 
-// GetLoadbalance ...
+// GetLoadbalance finds the loadbalance extension with @name
 func GetLoadbalance(name string) cluster.LoadBalance {
 	if loadbalances[name] == nil {
 		panic("loadbalance for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/metadata_report_factory.go b/common/extension/metadata_report_factory.go
new file mode 100644
index 0000000..593318d
--- /dev/null
+++ b/common/extension/metadata_report_factory.go
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package extension
+
+import (
+	"github.com/apache/dubbo-go/metadata/report/factory"
+)
+
+var (
+	metaDataReportFactories = make(map[string]func() factory.MetadataReportFactory, 8)
+)
+
+// SetMetadataReportFactory sets the MetadataReportFactory with @name
+func SetMetadataReportFactory(name string, v func() factory.MetadataReportFactory) {
+	metaDataReportFactories[name] = v
+}
+
+// GetMetadataReportFactory finds the MetadataReportFactory with @name
+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.")
+	}
+	return metaDataReportFactories[name]()
+}
diff --git a/common/extension/metadata_service.go b/common/extension/metadata_service.go
new file mode 100644
index 0000000..1823273
--- /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 0000000..2b88d37
--- /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/metrics.go b/common/extension/metrics.go
index 42fca7a..60cf6ba 100644
--- a/common/extension/metrics.go
+++ b/common/extension/metrics.go
@@ -27,12 +27,12 @@
 	metricReporterMap = make(map[string]func() metrics.Reporter, 4)
 )
 
-// SetMetricReporter set a reporter with the name
+// SetMetricReporter sets a reporter with the @name
 func SetMetricReporter(name string, reporterFunc func() metrics.Reporter) {
 	metricReporterMap[name] = reporterFunc
 }
 
-// GetMetricReporter find the reporter with name.
+// GetMetricReporter finds the reporter with @name.
 // if not found, it will panic.
 // we should know that this method usually is called when system starts, so we should panic
 func GetMetricReporter(name string) metrics.Reporter {
diff --git a/common/extension/metrics_test.go b/common/extension/metrics_test.go
index 6a8a3fe..2aaae75 100644
--- a/common/extension/metrics_test.go
+++ b/common/extension/metrics_test.go
@@ -45,5 +45,6 @@
 type mockReporter struct {
 }
 
+// Report method for feature expansion
 func (m mockReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) {
 }
diff --git a/common/extension/protocol.go b/common/extension/protocol.go
index 009687a..c89dd08 100644
--- a/common/extension/protocol.go
+++ b/common/extension/protocol.go
@@ -25,12 +25,12 @@
 	protocols = make(map[string]func() protocol.Protocol)
 )
 
-// SetProtocol ...
+// SetProtocol sets the protocol extension with @name
 func SetProtocol(name string, v func() protocol.Protocol) {
 	protocols[name] = v
 }
 
-// GetProtocol ...
+// GetProtocol finds the protocol extension with @name
 func GetProtocol(name string) protocol.Protocol {
 	if protocols[name] == nil {
 		panic("protocol for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/proxy_factory.go b/common/extension/proxy_factory.go
index 19826bb..1e326d8 100644
--- a/common/extension/proxy_factory.go
+++ b/common/extension/proxy_factory.go
@@ -25,12 +25,12 @@
 	proxyFactories = make(map[string]func(...proxy.Option) proxy.ProxyFactory)
 )
 
-// SetProxyFactory ...
+// SetProxyFactory sets the ProxyFactory extension with @name
 func SetProxyFactory(name string, f func(...proxy.Option) proxy.ProxyFactory) {
 	proxyFactories[name] = f
 }
 
-// GetProxyFactory ...
+// GetProxyFactory finds the ProxyFactory extension with @name
 func GetProxyFactory(name string) proxy.ProxyFactory {
 	if name == "" {
 		name = "default"
diff --git a/common/extension/registry.go b/common/extension/registry.go
index 6ba746d..187c8fe 100644
--- a/common/extension/registry.go
+++ b/common/extension/registry.go
@@ -26,15 +26,15 @@
 	registrys = make(map[string]func(config *common.URL) (registry.Registry, error))
 )
 
-// SetRegistry ...
-func SetRegistry(name string, v func(config *common.URL) (registry.Registry, error)) {
+// SetRegistry sets the registry extension with @name
+func SetRegistry(name string, v func(_ *common.URL) (registry.Registry, error)) {
 	registrys[name] = v
 }
 
-// GetRegistry ...
+// GetRegistry finds the registry extension with @name
 func GetRegistry(name string, config *common.URL) (registry.Registry, error) {
 	if registrys[name] == nil {
-		panic("registry for " + name + " is not existing, make sure you have import the package.")
+		panic("registry for " + name + " does not exist. please make sure that you have imported the package `github.com/apache/dubbo-go/registry/" + name + "`.")
 	}
 	return registrys[name](config)
 
diff --git a/common/extension/registry_directory.go b/common/extension/registry_directory.go
new file mode 100644
index 0000000..330fc46
--- /dev/null
+++ b/common/extension/registry_directory.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 extension
+
+import (
+	"github.com/apache/dubbo-go/cluster"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/registry"
+)
+
+type registryDirectory func(url *common.URL, registry registry.Registry) (cluster.Directory, error)
+
+var defaultRegistry registryDirectory
+
+// SetDefaultRegistryDirectory sets the default registryDirectory
+func SetDefaultRegistryDirectory(v registryDirectory) {
+	defaultRegistry = v
+}
+
+// GetDefaultRegistryDirectory finds the registryDirectory with url and registry
+func GetDefaultRegistryDirectory(config *common.URL, registry registry.Registry) (cluster.Directory, error) {
+	if defaultRegistry == nil {
+		panic("registry directory is not existing, make sure you have import the package.")
+	}
+	return defaultRegistry(config, registry)
+}
diff --git a/common/extension/rest_client.go b/common/extension/rest_client.go
index 514d1fd..0c2f4dd 100644
--- a/common/extension/rest_client.go
+++ b/common/extension/rest_client.go
@@ -25,10 +25,12 @@
 	restClients = make(map[string]func(restOptions *client.RestOptions) client.RestClient, 8)
 )
 
-func SetRestClient(name string, fun func(restOptions *client.RestOptions) client.RestClient) {
+// SetRestClient sets the RestClient with @name
+func SetRestClient(name string, fun func(_ *client.RestOptions) client.RestClient) {
 	restClients[name] = fun
 }
 
+// GetNewRestClient finds the RestClient with @name
 func GetNewRestClient(name string, restOptions *client.RestOptions) client.RestClient {
 	if restClients[name] == nil {
 		panic("restClient for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/rest_server.go b/common/extension/rest_server.go
index fa8d435..37a231a 100644
--- a/common/extension/rest_server.go
+++ b/common/extension/rest_server.go
@@ -25,10 +25,12 @@
 	restServers = make(map[string]func() server.RestServer, 8)
 )
 
+// SetRestServer sets the RestServer with @name
 func SetRestServer(name string, fun func() server.RestServer) {
 	restServers[name] = fun
 }
 
+// GetNewRestServer finds the RestServer with @name
 func GetNewRestServer(name string) server.RestServer {
 	if restServers[name] == nil {
 		panic("restServer for " + name + " is not existing, make sure you have import the package.")
diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go
index 70d71df..b61a056 100644
--- a/common/extension/router_factory.go
+++ b/common/extension/router_factory.go
@@ -26,31 +26,31 @@
 )
 
 var (
-	routers               = make(map[string]func() router.RouterFactory)
+	routers               = make(map[string]func() router.PriorityRouterFactory)
 	fileRouterFactoryOnce sync.Once
-	fileRouterFactories   = make(map[string]router.FIleRouterFactory)
+	fileRouterFactories   = make(map[string]router.FilePriorityRouterFactory)
 )
 
-// SetRouterFactory Set create router factory function by name
-func SetRouterFactory(name string, fun func() router.RouterFactory) {
+// SetRouterFactory sets create router factory function with @name
+func SetRouterFactory(name string, fun func() router.PriorityRouterFactory) {
 	routers[name] = fun
 }
 
-// GetRouterFactory Get create router factory function by name
-func GetRouterFactory(name string) router.RouterFactory {
+// GetRouterFactory gets create router factory function by @name
+func GetRouterFactory(name string) router.PriorityRouterFactory {
 	if routers[name] == nil {
 		panic("router_factory for " + name + " is not existing, make sure you have import the package.")
 	}
 	return routers[name]()
 }
 
-// GetRouterFactories Get all create router factory function
-func GetRouterFactories() map[string]func() router.RouterFactory {
+// GetRouterFactories gets all create router factory function
+func GetRouterFactories() map[string]func() router.PriorityRouterFactory {
 	return routers
 }
 
-// GetFileRouterFactories Get all create file router factory instance
-func GetFileRouterFactories() map[string]router.FIleRouterFactory {
+// GetFileRouterFactories gets all create file router factory instance
+func GetFileRouterFactories() map[string]router.FilePriorityRouterFactory {
 	l := len(routers)
 	if l == 0 {
 		return nil
@@ -58,7 +58,7 @@
 	fileRouterFactoryOnce.Do(func() {
 		for k := range routers {
 			factory := GetRouterFactory(k)
-			if fr, ok := factory.(router.FIleRouterFactory); ok {
+			if fr, ok := factory.(router.FilePriorityRouterFactory); ok {
 				fileRouterFactories[k] = fr
 			}
 		}
diff --git a/common/extension/service_discovery.go b/common/extension/service_discovery.go
new file mode 100644
index 0000000..0227920
--- /dev/null
+++ b/common/extension/service_discovery.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 (
+	perrors "github.com/pkg/errors"
+)
+import (
+	"github.com/apache/dubbo-go/registry"
+)
+
+var (
+	discoveryCreatorMap = make(map[string]func(name string) (registry.ServiceDiscovery, error), 4)
+)
+
+// SetServiceDiscovery will store the @creator and @name
+// 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(protocol string, name string) (registry.ServiceDiscovery, error) {
+	creator, ok := discoveryCreatorMap[protocol]
+	if !ok {
+		return nil, perrors.New("Could not find the service discovery with discovery protocol: " + protocol)
+	}
+	return creator(name)
+}
diff --git a/common/extension/service_instance_customizer.go b/common/extension/service_instance_customizer.go
new file mode 100644
index 0000000..3ebb3e4
--- /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 0000000..66d3e76
--- /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 0000000..99fd4c2
--- /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/extension/tps_limit.go b/common/extension/tps_limit.go
index c72c2b0..d25821d 100644
--- a/common/extension/tps_limit.go
+++ b/common/extension/tps_limit.go
@@ -26,12 +26,12 @@
 	tpsLimiter       = make(map[string]func() filter.TpsLimiter)
 )
 
-// SetTpsLimiter ...
+// SetTpsLimiter sets the TpsLimiter with @name
 func SetTpsLimiter(name string, creator func() filter.TpsLimiter) {
 	tpsLimiter[name] = creator
 }
 
-// GetTpsLimiter ...
+// GetTpsLimiter finds the TpsLimiter with @name
 func GetTpsLimiter(name string) filter.TpsLimiter {
 	creator, ok := tpsLimiter[name]
 	if !ok {
@@ -41,12 +41,12 @@
 	return creator()
 }
 
-// SetTpsLimitStrategy ...
+// SetTpsLimitStrategy sets the TpsLimitStrategyCreator with @name
 func SetTpsLimitStrategy(name string, creator filter.TpsLimitStrategyCreator) {
 	tpsLimitStrategy[name] = creator
 }
 
-// GetTpsLimitStrategyCreator ...
+// GetTpsLimitStrategyCreator finds the TpsLimitStrategyCreator with @name
 func GetTpsLimitStrategyCreator(name string) filter.TpsLimitStrategyCreator {
 	creator, ok := tpsLimitStrategy[name]
 	if !ok {
diff --git a/common/logger/logger.go b/common/logger/logger.go
index 016afe6..9bc6a46 100644
--- a/common/logger/logger.go
+++ b/common/logger/logger.go
@@ -40,13 +40,13 @@
 	logger Logger
 )
 
-// DubboLogger ...
+// nolint
 type DubboLogger struct {
 	Logger
 	dynamicLevel zap.AtomicLevel
 }
 
-// Logger ...
+// Logger is the interface for Logger types
 type Logger interface {
 	Info(args ...interface{})
 	Warn(args ...interface{})
@@ -67,7 +67,7 @@
 	}
 }
 
-// InitLog ...
+// InitLog use for init logger by call InitLogger
 func InitLog(logConfFile string) error {
 	if logConfFile == "" {
 		InitLogger(nil)
@@ -96,7 +96,7 @@
 	return nil
 }
 
-// InitLogger ...
+// InitLogger use for init logger by @conf
 func InitLogger(conf *zap.Config) {
 	var zapLoggerConfig zap.Config
 	if conf == nil {
@@ -125,18 +125,18 @@
 	getty.SetLogger(logger)
 }
 
-// SetLogger ...
+// SetLogger sets logger for dubbo and getty
 func SetLogger(log Logger) {
 	logger = log
 	getty.SetLogger(logger)
 }
 
-// GetLogger ...
+// GetLogger gets the logger
 func GetLogger() Logger {
 	return logger
 }
 
-// SetLoggerLevel ...
+// SetLoggerLevel use for set logger level
 func SetLoggerLevel(level string) bool {
 	if l, ok := logger.(OpsLogger); ok {
 		l.SetLoggerLevel(level)
@@ -145,13 +145,13 @@
 	return false
 }
 
-// OpsLogger ...
+// OpsLogger use for the SetLoggerLevel
 type OpsLogger interface {
 	Logger
 	SetLoggerLevel(level string)
 }
 
-// SetLoggerLevel ...
+// SetLoggerLevel use for set logger level
 func (dl *DubboLogger) SetLoggerLevel(level string) {
 	l := new(zapcore.Level)
 	l.Set(level)
diff --git a/common/logger/logging.go b/common/logger/logging.go
index 36d48ee..7a31ece 100644
--- a/common/logger/logging.go
+++ b/common/logger/logging.go
@@ -17,42 +17,42 @@
 
 package logger
 
-// Info ...
+// Info is info level
 func Info(args ...interface{}) {
 	logger.Info(args...)
 }
 
-// Warn ...
+// Warn is warning level
 func Warn(args ...interface{}) {
 	logger.Warn(args...)
 }
 
-// Error ...
+// Error is error level
 func Error(args ...interface{}) {
 	logger.Error(args...)
 }
 
-// Debug ...
+// Debug is debug level
 func Debug(args ...interface{}) {
 	logger.Debug(args...)
 }
 
-// Infof ...
+// Infof is format info level
 func Infof(fmt string, args ...interface{}) {
 	logger.Infof(fmt, args...)
 }
 
-// Warnf ...
+// Warnf is format warning level
 func Warnf(fmt string, args ...interface{}) {
 	logger.Warnf(fmt, args...)
 }
 
-// Errorf ...
+// Errorf is format error level
 func Errorf(fmt string, args ...interface{}) {
 	logger.Errorf(fmt, args...)
 }
 
-// Debugf ...
+// Debugf is format debug level
 func Debugf(fmt string, args ...interface{}) {
 	logger.Debugf(fmt, args...)
 }
diff --git a/common/node.go b/common/node.go
index 979eee3..4febd78 100644
--- a/common/node.go
+++ b/common/node.go
@@ -17,7 +17,7 @@
 
 package common
 
-// Node ...
+// Node use for process dubbo node
 type Node interface {
 	GetUrl() URL
 	IsAvailable() bool
diff --git a/common/observer/dispatcher/direct_event_dispatcher.go b/common/observer/dispatcher/direct_event_dispatcher.go
new file mode 100644
index 0000000..a2d334c
--- /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 0000000..12facbb
--- /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 0000000..45cdaa7
--- /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 0000000..209a50c
--- /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 0000000..17745e6
--- /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 0000000..faa8705
--- /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 0000000..bf6ae72
--- /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 0000000..5a03382
--- /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 68ba3ff..abcf87c 100644
--- a/common/proxy/proxy.go
+++ b/common/proxy/proxy.go
@@ -25,12 +25,13 @@
 
 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"
 	invocation_impl "github.com/apache/dubbo-go/protocol/invocation"
 )
 
-// Proxy struct
+// nolint
 type Proxy struct {
 	rpc         common.RPCService
 	invoke      protocol.Invoker
@@ -44,7 +45,7 @@
 	typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type()
 )
 
-// NewProxy ...
+// NewProxy create service proxy.
 func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[string]string) *Proxy {
 	return &Proxy{
 		invoke:      invoke,
@@ -59,7 +60,6 @@
 // 		type XxxProvider struct {
 //  		Yyy func(ctx context.Context, args []interface{}, rsp *Zzz) error
 // 		}
-
 func (p *Proxy) Implement(v common.RPCService) {
 
 	// check parameters, incoming interface must be a elem's pointer.
@@ -141,7 +141,7 @@
 			}
 
 			// add user setAttachment
-			atm := invCtx.Value("attachment")
+			atm := invCtx.Value(constant.AttachmentKey)
 			if m, ok := atm.(map[string]string); ok {
 				for k, value := range m {
 					inv.SetAttachments(k, value)
@@ -149,6 +149,9 @@
 			}
 
 			result := p.invoke.Invoke(invCtx, inv)
+			if len(result.Attachments()) > 0 {
+				invCtx = context.WithValue(invCtx, constant.AttachmentKey, result.Attachments())
+			}
 
 			err = result.Error()
 			logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
@@ -202,12 +205,12 @@
 
 }
 
-// Get ...
+// Get gets rpc service instance.
 func (p *Proxy) Get() common.RPCService {
 	return p.rpc
 }
 
-// GetCallback ...
+// GetCallback gets callback.
 func (p *Proxy) GetCallback() interface{} {
 	return p.callBack
 }
diff --git a/common/proxy/proxy_factory.go b/common/proxy/proxy_factory.go
index 7b249a3..117428c 100644
--- a/common/proxy/proxy_factory.go
+++ b/common/proxy/proxy_factory.go
@@ -22,12 +22,12 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// ProxyFactory ...
+// ProxyFactory interface.
 type ProxyFactory interface {
 	GetProxy(invoker protocol.Invoker, url *common.URL) *Proxy
 	GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *Proxy
 	GetInvoker(url common.URL) protocol.Invoker
 }
 
-// Option ...
+// Option will define a function of handling ProxyFactory
 type Option func(ProxyFactory)
diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go
index 114cfee..1b8ca22 100644
--- a/common/proxy/proxy_factory/default.go
+++ b/common/proxy/proxy_factory/default.go
@@ -40,7 +40,7 @@
 	extension.SetProxyFactory("default", NewDefaultProxyFactory)
 }
 
-// DefaultProxyFactory ...
+// DefaultProxyFactory is the default proxy factory
 type DefaultProxyFactory struct {
 	//delegate ProxyFactory
 }
@@ -53,17 +53,17 @@
 //	}
 //}
 
-// NewDefaultProxyFactory ...
-func NewDefaultProxyFactory(options ...proxy.Option) proxy.ProxyFactory {
+// NewDefaultProxyFactory returns a proxy factory instance
+func NewDefaultProxyFactory(_ ...proxy.Option) proxy.ProxyFactory {
 	return &DefaultProxyFactory{}
 }
 
-// GetProxy ...
+// GetProxy gets a proxy
 func (factory *DefaultProxyFactory) GetProxy(invoker protocol.Invoker, url *common.URL) *proxy.Proxy {
 	return factory.GetAsyncProxy(invoker, nil, url)
 }
 
-// GetAsyncProxy ...
+// GetAsyncProxy gets a async proxy
 func (factory *DefaultProxyFactory) GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *proxy.Proxy {
 	//create proxy
 	attachments := map[string]string{}
@@ -71,19 +71,19 @@
 	return proxy.NewProxy(invoker, callBack, attachments)
 }
 
-// GetInvoker ...
+// GetInvoker gets a invoker
 func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker {
 	return &ProxyInvoker{
 		BaseInvoker: *protocol.NewBaseInvoker(url),
 	}
 }
 
-// ProxyInvoker ...
+// ProxyInvoker is a invoker struct
 type ProxyInvoker struct {
 	protocol.BaseInvoker
 }
 
-// Invoke ...
+// Invoke is used to call service method by invocation
 func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	result := &protocol.RPCResult{}
 	result.SetAttachments(invocation.Attachments())
@@ -113,6 +113,7 @@
 
 	in := []reflect.Value{svc.Rcvr()}
 	if method.CtxType() != nil {
+		ctx = context.WithValue(ctx, constant.AttachmentKey, invocation.Attachments())
 		in = append(in, method.SuiteContext(ctx))
 	}
 
diff --git a/common/proxy/proxy_factory/default_test.go b/common/proxy/proxy_factory/default_test.go
index 7159b4b..99d5c02 100644
--- a/common/proxy/proxy_factory/default_test.go
+++ b/common/proxy/proxy_factory/default_test.go
@@ -31,7 +31,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func Test_GetProxy(t *testing.T) {
+func TestGetProxy(t *testing.T) {
 	proxyFactory := NewDefaultProxyFactory()
 	url := common.NewURLWithOptions()
 	proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(*url), url)
@@ -45,7 +45,7 @@
 	fmt.Println("CallBack res:", res)
 }
 
-func Test_GetAsyncProxy(t *testing.T) {
+func TestGetAsyncProxy(t *testing.T) {
 	proxyFactory := NewDefaultProxyFactory()
 	url := common.NewURLWithOptions()
 	async := &TestAsync{}
@@ -53,7 +53,7 @@
 	assert.NotNil(t, proxy)
 }
 
-func Test_GetInvoker(t *testing.T) {
+func TestGetInvoker(t *testing.T) {
 	proxyFactory := NewDefaultProxyFactory()
 	url := common.NewURLWithOptions()
 	invoker := proxyFactory.GetInvoker(*url)
diff --git a/common/proxy/proxy_test.go b/common/proxy/proxy_test.go
index 8c1c029..14b2bef 100644
--- a/common/proxy/proxy_test.go
+++ b/common/proxy/proxy_test.go
@@ -53,7 +53,7 @@
 	return "com.test.TestServiceInt"
 }
 
-func TestProxy_Implement(t *testing.T) {
+func TestProxyImplement(t *testing.T) {
 
 	invoker := protocol.NewBaseInvoker(common.URL{})
 	p := NewProxy(invoker, nil, map[string]string{constant.ASYNC_KEY: "false"})
@@ -84,7 +84,7 @@
 		TestService
 		methodOne func(context.Context, interface{}, *struct{}) error
 	}
-	s1 := &S1{TestService: *s, methodOne: func(i context.Context, i2 interface{}, i3 *struct{}) error {
+	s1 := &S1{TestService: *s, methodOne: func(_ context.Context, _ interface{}, _ *struct{}) error {
 		return perrors.New("errors")
 	}}
 	p.Implement(s1)
diff --git a/common/rpc_service.go b/common/rpc_service.go
index b235c32..9ef2b95 100644
--- a/common/rpc_service.go
+++ b/common/rpc_service.go
@@ -35,23 +35,23 @@
 )
 
 // RPCService
-//rpc service interface
+// rpc service interface
 type RPCService interface {
 	// Reference:
 	// rpc service id or reference id
 	Reference() string
 }
 
-//AsyncCallbackService callback interface for async
+// AsyncCallbackService callback interface for async
 type AsyncCallbackService interface {
 	// Callback: callback
 	CallBack(response CallbackResponse)
 }
 
-//CallbackResponse for different protocol
+// CallbackResponse for different protocol
 type CallbackResponse interface{}
 
-//AsyncCallback async callback method
+// AsyncCallback async callback method
 type AsyncCallback func(response CallbackResponse)
 
 // for lowercase func
@@ -59,7 +59,6 @@
 //     return map[string][string]{}
 // }
 const (
-	// METHOD_MAPPER ...
 	METHOD_MAPPER = "MethodMapper"
 )
 
@@ -68,10 +67,10 @@
 	// because Typeof takes an empty interface value. This is annoying.
 	typeOfError = reflect.TypeOf((*error)(nil)).Elem()
 
-	// ServiceMap ...
-	// todo: lowerecas?
+	// ServiceMap store description of service.
 	ServiceMap = &serviceMap{
-		serviceMap: make(map[string]map[string]*Service),
+		serviceMap:   make(map[string]map[string]*Service),
+		interfaceMap: make(map[string][]*Service),
 	}
 )
 
@@ -79,7 +78,7 @@
 // info of method
 //////////////////////////
 
-// MethodType ...
+// MethodType is description of service method.
 type MethodType struct {
 	method    reflect.Method
 	ctxType   reflect.Type   // request context
@@ -87,27 +86,27 @@
 	replyType reflect.Type   // return value, otherwise it is nil
 }
 
-// Method ...
+// Method gets @m.method.
 func (m *MethodType) Method() reflect.Method {
 	return m.method
 }
 
-// CtxType ...
+// CtxType gets @m.ctxType.
 func (m *MethodType) CtxType() reflect.Type {
 	return m.ctxType
 }
 
-// ArgsType ...
+// ArgsType gets @m.argsType.
 func (m *MethodType) ArgsType() []reflect.Type {
 	return m.argsType
 }
 
-// ReplyType ...
+// ReplyType gets @m.replyType.
 func (m *MethodType) ReplyType() reflect.Type {
 	return m.replyType
 }
 
-// SuiteContext ...
+// SuiteContext tranfers @ctx to reflect.Value type or get it from @m.ctxType.
 func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value {
 	if contextv := reflect.ValueOf(ctx); contextv.IsValid() {
 		return contextv
@@ -119,7 +118,7 @@
 // info of service interface
 //////////////////////////
 
-// Service ...
+// Service is description of service
 type Service struct {
 	name     string
 	rcvr     reflect.Value
@@ -127,17 +126,22 @@
 	methods  map[string]*MethodType
 }
 
-// Method ...
+// Method gets @s.methods.
 func (s *Service) Method() map[string]*MethodType {
 	return s.methods
 }
 
-// RcvrType ...
+// 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
 }
 
-// Rcvr ...
+// Rcvr gets @s.rcvr.
 func (s *Service) Rcvr() reflect.Value {
 	return s.rcvr
 }
@@ -147,10 +151,12 @@
 //////////////////////////
 
 type serviceMap struct {
-	mutex      sync.RWMutex                   // protects the serviceMap
-	serviceMap map[string]map[string]*Service // protocol -> service name -> service
+	mutex        sync.RWMutex                   // protects the serviceMap
+	serviceMap   map[string]map[string]*Service // protocol -> service name -> service
+	interfaceMap map[string][]*Service          // interface -> service
 }
 
+// GetService gets a service defination by protocol and name
 func (sm *serviceMap) GetService(protocol, name string) *Service {
 	sm.mutex.RLock()
 	defer sm.mutex.RUnlock()
@@ -163,10 +169,24 @@
 	return nil
 }
 
-func (sm *serviceMap) Register(protocol string, rcvr RPCService) (string, error) {
+// GetInterface gets an interface defination by interface name
+func (sm *serviceMap) GetInterface(interfaceName string) []*Service {
+	sm.mutex.RLock()
+	defer sm.mutex.RUnlock()
+	if s, ok := sm.interfaceMap[interfaceName]; ok {
+		return s
+	}
+	return nil
+}
+
+// Register registers a service by @interfaceName and @protocol
+func (sm *serviceMap) Register(interfaceName, protocol string, rcvr RPCService) (string, error) {
 	if sm.serviceMap[protocol] == nil {
 		sm.serviceMap[protocol] = make(map[string]*Service)
 	}
+	if sm.interfaceMap[interfaceName] == nil {
+		sm.interfaceMap[interfaceName] = make([]*Service, 0, 16)
+	}
 
 	s := new(Service)
 	s.rcvrType = reflect.TypeOf(rcvr)
@@ -201,32 +221,65 @@
 	}
 	sm.mutex.Lock()
 	sm.serviceMap[protocol][s.name] = s
+	sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], s)
 	sm.mutex.Unlock()
 
 	return strings.TrimSuffix(methods, ","), nil
 }
 
-func (sm *serviceMap) UnRegister(protocol, serviceId string) error {
+// UnRegister cancels a service by @interfaceName, @protocol and @serviceId
+func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) error {
 	if protocol == "" || serviceId == "" {
 		return perrors.New("protocol or serviceName is nil")
 	}
-	sm.mutex.RLock()
-	svcs, ok := sm.serviceMap[protocol]
-	if !ok {
-		sm.mutex.RUnlock()
-		return perrors.New("no services for " + protocol)
+
+	var (
+		err   error
+		index = -1
+		svcs  map[string]*Service
+		svrs  []*Service
+		ok    bool
+	)
+
+	f := func() error {
+		sm.mutex.RLock()
+		defer sm.mutex.RUnlock()
+		svcs, ok = sm.serviceMap[protocol]
+		if !ok {
+			return perrors.New("no services for " + protocol)
+		}
+		s, ok := svcs[serviceId]
+		if !ok {
+			return perrors.New("no service for " + serviceId)
+		}
+		svrs, ok = sm.interfaceMap[interfaceName]
+		if !ok {
+			return perrors.New("no service for " + interfaceName)
+		}
+		for i, svr := range svrs {
+			if svr == s {
+				index = i
+			}
+		}
+		return nil
 	}
-	_, ok = svcs[serviceId]
-	if !ok {
-		sm.mutex.RUnlock()
-		return perrors.New("no service for " + serviceId)
+
+	if err = f(); err != nil {
+		return err
 	}
-	sm.mutex.RUnlock()
 
 	sm.mutex.Lock()
 	defer sm.mutex.Unlock()
+	sm.interfaceMap[interfaceName] = make([]*Service, 0, len(svrs))
+	for i, _ := range svrs {
+		if i != index {
+			sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], svrs[i])
+		}
+	}
 	delete(svcs, serviceId)
-	delete(sm.serviceMap, protocol)
+	if len(sm.serviceMap[protocol]) == 0 {
+		delete(sm.serviceMap, protocol)
+	}
 
 	return nil
 }
@@ -289,6 +342,16 @@
 		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/rpc_service_test.go b/common/rpc_service_test.go
index 8c9b9d1..7c4eb42 100644
--- a/common/rpc_service_test.go
+++ b/common/rpc_service_test.go
@@ -27,6 +27,14 @@
 	"github.com/stretchr/testify/assert"
 )
 
+const (
+	referenceTestPath             = "com.test.Path"
+	referenceTestPathDistinct     = "com.test.Path1"
+	testInterfaceName             = "testService"
+	testProtocol                  = "testprotocol"
+	testSuiteMethodExpectedString = "interface {}"
+)
+
 type TestService struct {
 }
 
@@ -40,7 +48,7 @@
 	return nil
 }
 func (s *TestService) Reference() string {
-	return "com.test.Path"
+	return referenceTestPath
 }
 func (s *TestService) MethodMapper() map[string]string {
 	return map[string]string{
@@ -63,64 +71,66 @@
 	return nil
 }
 func (s *testService) Reference() string {
-	return "com.test.Path"
+	return referenceTestPath
 }
 
 type TestService1 struct {
 }
 
 func (s *TestService1) Reference() string {
-	return "com.test.Path1"
+	return referenceTestPathDistinct
 }
 
-func TestServiceMap_Register(t *testing.T) {
+func TestServiceMapRegister(t *testing.T) {
 	// lowercase
 	s0 := &testService{}
 	// methods, err := ServiceMap.Register("testporotocol", s0)
-	_, err := ServiceMap.Register("testporotocol", s0)
+	_, err := ServiceMap.Register(testInterfaceName, "testporotocol", s0)
 	assert.EqualError(t, err, "type testService is not exported")
 
 	// succ
 	s := &TestService{}
-	methods, err := ServiceMap.Register("testporotocol", s)
+	methods, err := ServiceMap.Register(testInterfaceName, "testporotocol", s)
 	assert.NoError(t, err)
 	assert.Equal(t, "MethodOne,MethodThree,methodTwo", methods)
 
 	// repeat
-	_, err = ServiceMap.Register("testporotocol", s)
+	_, err = ServiceMap.Register(testInterfaceName, "testporotocol", s)
 	assert.EqualError(t, err, "service already defined: com.test.Path")
 
 	// no method
 	s1 := &TestService1{}
-	_, err = ServiceMap.Register("testporotocol", s1)
+	_, err = ServiceMap.Register(testInterfaceName, "testporotocol", s1)
 	assert.EqualError(t, err, "type com.test.Path1 has no exported methods of suitable type")
 
 	ServiceMap = &serviceMap{
-		serviceMap: make(map[string]map[string]*Service),
+		serviceMap:   make(map[string]map[string]*Service),
+		interfaceMap: make(map[string][]*Service),
 	}
 }
 
-func TestServiceMap_UnRegister(t *testing.T) {
+func TestServiceMapUnRegister(t *testing.T) {
 	s := &TestService{}
-	_, err := ServiceMap.Register("testprotocol", s)
+	_, err := ServiceMap.Register("TestService", testProtocol, s)
 	assert.NoError(t, err)
-	assert.NotNil(t, ServiceMap.GetService("testprotocol", "com.test.Path"))
+	assert.NotNil(t, ServiceMap.GetService(testProtocol, referenceTestPath))
+	assert.Equal(t, 1, len(ServiceMap.GetInterface("TestService")))
 
-	err = ServiceMap.UnRegister("", "com.test.Path")
+	err = ServiceMap.UnRegister("", "", referenceTestPath)
 	assert.EqualError(t, err, "protocol or serviceName is nil")
 
-	err = ServiceMap.UnRegister("protocol", "com.test.Path")
+	err = ServiceMap.UnRegister("", "protocol", referenceTestPath)
 	assert.EqualError(t, err, "no services for protocol")
 
-	err = ServiceMap.UnRegister("testprotocol", "com.test.Path1")
+	err = ServiceMap.UnRegister("", testProtocol, referenceTestPathDistinct)
 	assert.EqualError(t, err, "no service for com.test.Path1")
 
 	// succ
-	err = ServiceMap.UnRegister("testprotocol", "com.test.Path")
+	err = ServiceMap.UnRegister("TestService", testProtocol, referenceTestPath)
 	assert.NoError(t, err)
 }
 
-func TestMethodType_SuiteContext(t *testing.T) {
+func TestMethodTypeSuiteContext(t *testing.T) {
 	mt := &MethodType{ctxType: reflect.TypeOf(context.TODO())}
 	ctx := context.WithValue(context.Background(), "key", "value")
 	assert.Equal(t, reflect.ValueOf(ctx), mt.SuiteContext(ctx))
@@ -137,9 +147,9 @@
 	method = methodType.Method()
 	assert.Equal(t, "func(*common.TestService, context.Context, interface {}, interface {}, interface {}) error", method.Type.String())
 	at := methodType.ArgsType()
-	assert.Equal(t, "interface {}", at[0].String())
-	assert.Equal(t, "interface {}", at[1].String())
-	assert.Equal(t, "interface {}", at[2].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[0].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[1].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[2].String())
 	ct := methodType.CtxType()
 	assert.Equal(t, "context.Context", ct.String())
 	rt := methodType.ReplyType()
@@ -151,12 +161,12 @@
 	method = methodType.Method()
 	assert.Equal(t, "func(*common.TestService, interface {}, interface {}, interface {}) (interface {}, error)", method.Type.String())
 	at = methodType.ArgsType()
-	assert.Equal(t, "interface {}", at[0].String())
-	assert.Equal(t, "interface {}", at[1].String())
-	assert.Equal(t, "interface {}", at[2].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[0].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[1].String())
+	assert.Equal(t, testSuiteMethodExpectedString, at[2].String())
 	assert.Nil(t, methodType.CtxType())
 	rt = methodType.ReplyType()
-	assert.Equal(t, "interface {}", rt.String())
+	assert.Equal(t, testSuiteMethodExpectedString, rt.String())
 
 	method, ok = reflect.TypeOf(s).MethodByName("MethodThree")
 	assert.True(t, ok)
diff --git a/common/url.go b/common/url.go
index ebb648d..807d0ed 100644
--- a/common/url.go
+++ b/common/url.go
@@ -18,7 +18,6 @@
 package common
 
 import (
-	"bytes"
 	"encoding/base64"
 	"fmt"
 	"math"
@@ -26,7 +25,6 @@
 	"net/url"
 	"strconv"
 	"strings"
-	"sync"
 )
 
 import (
@@ -38,151 +36,162 @@
 
 import (
 	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
 )
 
-/////////////////////////////////
+// ///////////////////////////////
 // dubbo role type
-/////////////////////////////////
+// ///////////////////////////////
 
 // role constant
 const (
-	// CONSUMER ...
+	// CONSUMER is consumer role
 	CONSUMER = iota
-	// CONFIGURATOR ...
+	// CONFIGURATOR is configurator role
 	CONFIGURATOR
-	// ROUTER ...
+	// ROUTER is router role
 	ROUTER
-	// PROVIDER ...
+	// PROVIDER is provider role
 	PROVIDER
+	PROTOCOL = "protocol"
 )
 
 var (
-	// DubboNodes ...
+	// DubboNodes Dubbo service node
 	DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"}
 	// DubboRole Dubbo service role
 	DubboRole = [...]string{"consumer", "", "routers", "provider"}
 )
 
-// RoleType ...
+// nolint
 type RoleType int
 
 func (t RoleType) String() string {
 	return DubboNodes[t]
 }
 
-// Role ...
+// Role returns role by @RoleType
 func (t RoleType) Role() string {
 	return DubboRole[t]
 }
 
 type baseUrl struct {
-	Protocol string
-	Location string // ip+port
-	Ip       string
-	Port     string
-	//url.Values is not safe map, add to avoid concurrent map read and map write error
-	paramsLock   sync.RWMutex
+	Protocol     string
+	Location     string // ip+port
+	Ip           string
+	Port         string
 	params       url.Values
 	PrimitiveURL string
 }
 
-// URL ...
+// URL is not thread-safe.
+// we fail to define this struct to be immutable object.
+// but, those method which will update the URL, including SetParam, SetParams
+// are only allowed to be invoked in creating URL instance
+// Please keep in mind that this struct is immutable after it has been created and initialized.
 type URL struct {
 	baseUrl
 	Path     string // like  /com.ikurento.dubbo.UserProvider3
 	Username string
 	Password string
 	Methods  []string
-	//special for registry
+	// special for registry
 	SubURL *URL
 }
 
+// Option accepts url
+// Option will define a function of handling URL
 type option func(*URL)
 
-// WithUsername ...
+// WithUsername sets username for url
 func WithUsername(username string) option {
 	return func(url *URL) {
 		url.Username = username
 	}
 }
 
-// WithPassword ...
+// WithPassword sets password for url
 func WithPassword(pwd string) option {
 	return func(url *URL) {
 		url.Password = pwd
 	}
 }
 
-// WithMethods ...
+// WithMethods sets methods for url
 func WithMethods(methods []string) option {
 	return func(url *URL) {
 		url.Methods = methods
 	}
 }
 
-// WithParams ...
+// WithParams sets params for url
 func WithParams(params url.Values) option {
 	return func(url *URL) {
 		url.params = params
 	}
 }
 
-// WithParamsValue ...
+// WithParamsValue sets params field for url
 func WithParamsValue(key, val string) option {
 	return func(url *URL) {
 		url.SetParam(key, val)
 	}
 }
 
-// WithProtocol ...
+// WithProtocol sets protocol for url
 func WithProtocol(proto string) option {
 	return func(url *URL) {
 		url.Protocol = proto
 	}
 }
 
-// WithIp ...
+// WithIp sets ip for url
 func WithIp(ip string) option {
 	return func(url *URL) {
 		url.Ip = ip
 	}
 }
 
-// WithPort ...
+// WithPort sets port for url
 func WithPort(port string) option {
 	return func(url *URL) {
 		url.Port = port
 	}
 }
 
-// WithPath ...
+// WithPath sets path for url
 func WithPath(path string) option {
 	return func(url *URL) {
 		url.Path = "/" + strings.TrimPrefix(path, "/")
 	}
 }
 
-// WithLocation ...
+// WithLocation sets location for url
 func WithLocation(location string) option {
 	return func(url *URL) {
 		url.Location = location
 	}
 }
 
-// WithToken ...
+// WithToken sets token for url
 func WithToken(token string) option {
 	return func(url *URL) {
 		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)
 		}
 	}
 }
 
-// NewURLWithOptions ...
+// NewURLWithOptions will create a new url with options
 func NewURLWithOptions(opts ...option) *URL {
 	url := &URL{}
 	for _, opt := range opts {
@@ -195,7 +204,6 @@
 // NewURL will create a new url
 // the urlString should not be empty
 func NewURL(urlString string, opts ...option) (URL, error) {
-
 	var (
 		err          error
 		rawUrlString string
@@ -213,7 +221,7 @@
 		return s, perrors.Errorf("url.QueryUnescape(%s),  error{%v}", urlString, err)
 	}
 
-	//rawUrlString = "//" + rawUrlString
+	// rawUrlString = "//" + rawUrlString
 	if strings.Index(rawUrlString, "//") < 0 {
 		t := URL{baseUrl: baseUrl{}}
 		for _, opt := range opts {
@@ -249,7 +257,7 @@
 	return s, nil
 }
 
-// URLEqual ...
+// URLEqual judge @url and @c is equal or not.
 func (c URL) URLEqual(url URL) bool {
 	c.Ip = ""
 	c.Port = ""
@@ -265,17 +273,19 @@
 	} else if urlGroup == constant.ANY_VALUE {
 		urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1)
 	}
+
+	// 1. protocol, username, password, ip, port, service name, group, version should be equal
 	if cKey != urlKey {
 		return false
 	}
+
+	// 2. if url contains enabled key, should be true, or *
 	if url.GetParam(constant.ENABLED_KEY, "true") != "true" && url.GetParam(constant.ENABLED_KEY, "") != constant.ANY_VALUE {
 		return false
 	}
-	//TODO :may need add interface key any value condition
-	if !isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) {
-		return false
-	}
-	return true
+
+	// TODO :may need add interface key any value condition
+	return isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY))
 }
 
 func isMatchCategory(category1 string, category2 string) bool {
@@ -291,38 +301,35 @@
 }
 
 func (c URL) String() string {
-	var buildString string
+	var buf strings.Builder
 	if len(c.Username) == 0 && len(c.Password) == 0 {
-		buildString = fmt.Sprintf(
+		buf.WriteString(fmt.Sprintf(
 			"%s://%s:%s%s?",
-			c.Protocol, c.Ip, c.Port, c.Path)
+			c.Protocol, c.Ip, c.Port, c.Path))
 	} else {
-		buildString = fmt.Sprintf(
+		buf.WriteString(fmt.Sprintf(
 			"%s://%s:%s@%s:%s%s?",
-			c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)
+			c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path))
 	}
-	c.paramsLock.RLock()
-	buildString += c.params.Encode()
-	c.paramsLock.RUnlock()
-	return buildString
+	buf.WriteString(c.params.Encode())
+	return buf.String()
 }
 
-// Key ...
+// Key gets key
 func (c URL) Key() string {
 	buildString := fmt.Sprintf(
 		"%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s",
 		c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, ""))
 	return buildString
-	//return c.ServiceKey()
 }
 
-// ServiceKey ...
+// ServiceKey gets a unique key of a service.
 func (c URL) ServiceKey() string {
 	intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
 	if intf == "" {
 		return ""
 	}
-	buf := &bytes.Buffer{}
+	var buf strings.Builder
 	group := c.GetParam(constant.GROUP_KEY, "")
 	if group != "" {
 		buf.WriteString(group)
@@ -347,7 +354,7 @@
 	if intf == "" {
 		return ""
 	}
-	buf := &bytes.Buffer{}
+	var buf strings.Builder
 	buf.WriteString(intf)
 	buf.WriteString(":")
 	version := c.GetParam(constant.VERSION_KEY, "")
@@ -362,44 +369,44 @@
 	return buf.String()
 }
 
-// EncodedServiceKey ...
+// EncodedServiceKey encode the service key
 func (c *URL) EncodedServiceKey() string {
 	serviceKey := c.ServiceKey()
 	return strings.Replace(serviceKey, "/", "*", 1)
 }
 
-// Service ...
+// Service gets service
 func (c URL) Service() string {
 	service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
 	if service != "" {
 		return service
 	} else if c.SubURL != nil {
 		service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
-		if service != "" { //if url.path is "" then return suburl's path, special for registry url
+		if service != "" { // if url.path is "" then return suburl's path, special for registry url
 			return service
 		}
 	}
 	return ""
 }
 
-// AddParam ...
+// AddParam will add the key-value pair
+// Not thread-safe
+// think twice before using it.
 func (c *URL) AddParam(key string, value string) {
-	c.paramsLock.Lock()
 	c.params.Add(key, value)
-	c.paramsLock.Unlock()
 }
 
-// SetParam ...
+// SetParam will put the key-value pair into url
+// it's not thread safe.
+// think twice before you want to use this method
+// usually it should only be invoked when you want to initialized an url
 func (c *URL) SetParam(key string, value string) {
-	c.paramsLock.Lock()
 	c.params.Set(key, value)
-	c.paramsLock.Unlock()
 }
 
-// RangeParams ...
+// RangeParams will iterate the params
+// it's not thread-safe
 func (c *URL) RangeParams(f func(key, value string) bool) {
-	c.paramsLock.RLock()
-	defer c.paramsLock.RUnlock()
 	for k, v := range c.params {
 		if !f(k, v[0]) {
 			break
@@ -407,35 +414,31 @@
 	}
 }
 
-// GetParam ...
+// GetParam gets value by key
 func (c URL) GetParam(s string, d string) string {
-	var r string
-	c.paramsLock.RLock()
-	if r = c.params.Get(s); len(r) == 0 {
+	r := c.params.Get(s)
+	if len(r) == 0 {
 		r = d
 	}
-	c.paramsLock.RUnlock()
 	return r
 }
 
-// GetParams ...
+// GetParams gets values
 func (c URL) GetParams() url.Values {
 	return c.params
 }
 
-// GetParamAndDecoded ...
+// GetParamAndDecoded gets values and decode
 func (c URL) GetParamAndDecoded(key string) (string, error) {
-	c.paramsLock.RLock()
-	defer c.paramsLock.RUnlock()
 	ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, ""))
 	value := string(ruleDec)
 	return value, err
 }
 
-// GetRawParam ...
+// GetRawParam gets raw param
 func (c URL) GetRawParam(key string) string {
 	switch key {
-	case "protocol":
+	case PROTOCOL:
 		return c.Protocol
 	case "username":
 		return c.Username
@@ -452,76 +455,61 @@
 	}
 }
 
-// GetParamBool ...
-func (c URL) GetParamBool(s string, d bool) bool {
-
-	var r bool
-	var err error
-	if r, err = strconv.ParseBool(c.GetParam(s, "")); err != nil {
+// GetParamBool judge whether @key exists or not
+func (c URL) GetParamBool(key string, d bool) bool {
+	r, err := strconv.ParseBool(c.GetParam(key, ""))
+	if err != nil {
 		return d
 	}
 	return r
 }
 
-// GetParamInt ...
-func (c URL) GetParamInt(s string, d int64) int64 {
-	var r int
-	var err error
-
-	if r, err = strconv.Atoi(c.GetParam(s, "")); r == 0 || err != nil {
+// GetParamInt gets int value by @key
+func (c URL) GetParamInt(key string, d int64) int64 {
+	r, err := strconv.Atoi(c.GetParam(key, ""))
+	if r == 0 || err != nil {
 		return d
 	}
 	return int64(r)
 }
 
-// GetMethodParamInt ...
+// GetMethodParamInt gets int method param
 func (c URL) GetMethodParamInt(method string, key string, d int64) int64 {
-	var r int
-	var err error
-	c.paramsLock.RLock()
-	defer c.paramsLock.RUnlock()
-	if r, err = strconv.Atoi(c.GetParam("methods."+method+"."+key, "")); r == 0 || err != nil {
+	r, err := strconv.Atoi(c.GetParam("methods."+method+"."+key, ""))
+	if r == 0 || err != nil {
 		return d
 	}
 	return int64(r)
 }
 
-// GetMethodParamInt64 ...
+// GetMethodParamInt64 gets int64 method param
 func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 {
 	r := c.GetMethodParamInt(method, key, math.MinInt64)
 	if r == math.MinInt64 {
 		return c.GetParamInt(key, d)
 	}
-
 	return r
 }
 
-// GetMethodParam ...
+// GetMethodParam gets method param
 func (c URL) GetMethodParam(method string, key string, d string) string {
-	var r string
-	if r = c.GetParam("methods."+method+"."+key, ""); r == "" {
+	r := c.GetParam("methods."+method+"."+key, "")
+	if r == "" {
 		r = d
 	}
 	return r
 }
 
-// GetMethodParamBool ...
+// GetMethodParamBool judge whether @method param exists or not
 func (c URL) GetMethodParamBool(method string, key string, d bool) bool {
 	r := c.GetParamBool("methods."+method+"."+key, d)
 	return r
 }
 
-// RemoveParams ...
-func (c *URL) RemoveParams(set *gxset.HashSet) {
-	c.paramsLock.Lock()
-	defer c.paramsLock.Unlock()
-	for k := range set.Items {
-		s := k.(string)
-		delete(c.params, s)
-	}
-}
-
-// SetParams ...
+// SetParams will put all key-value pair into url.
+// 1. if there already has same key, the value will be override
+// 2. it's not thread safe
+// 3. think twice when you want to invoke this method
 func (c *URL) SetParams(m url.Values) {
 	for k := range m {
 		c.SetParam(k, m.Get(k))
@@ -530,7 +518,6 @@
 
 // ToMap transfer URL to Map
 func (c URL) ToMap() map[string]string {
-
 	paramsMap := make(map[string]string)
 
 	c.RangeParams(func(key, value string) bool {
@@ -539,7 +526,7 @@
 	})
 
 	if c.Protocol != "" {
-		paramsMap["protocol"] = c.Protocol
+		paramsMap[PROTOCOL] = c.Protocol
 	}
 	if c.Username != "" {
 		paramsMap["username"] = c.Username
@@ -558,7 +545,7 @@
 		paramsMap["port"] = port
 	}
 	if c.Protocol != "" {
-		paramsMap["protocol"] = c.Protocol
+		paramsMap[PROTOCOL] = c.Protocol
 	}
 	if c.Path != "" {
 		paramsMap["path"] = c.Path
@@ -571,29 +558,35 @@
 
 // configuration  > reference config >service config
 //  in this function we should merge the reference local url config into the service url from registry.
-//TODO configuration merge, in the future , the configuration center's config should merge too.
+// TODO configuration merge, in the future , the configuration center's config should merge too.
 
-// MergeUrl ...
+// MergeUrl will merge those two url
+// the result is based on serviceUrl, and the key which si only contained in referenceUrl
+// will be added into result.
+// for example, if serviceUrl contains params (a1->v1, b1->v2) and referenceUrl contains params(a2->v3, b1 -> v4)
+// the params of result will be (a1->v1, b1->v2, a2->v3).
+// You should notice that the value of b1 is v2, not v4.
+// due to URL is not thread-safe, so this method is not thread-safe
 func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL {
 	mergedUrl := serviceUrl.Clone()
 
-	//iterator the referenceUrl if serviceUrl not have the key ,merge in
+	// iterator the referenceUrl if serviceUrl not have the key ,merge in
 	referenceUrl.RangeParams(func(key, value string) bool {
 		if v := mergedUrl.GetParam(key, ""); len(v) == 0 {
 			mergedUrl.SetParam(key, value)
 		}
 		return true
 	})
-	//loadBalance,cluster,retries strategy config
+	// loadBalance,cluster,retries strategy config
 	methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY})
 
-	//remote timestamp
+	// remote timestamp
 	if v := serviceUrl.GetParam(constant.TIMESTAMP_KEY, ""); len(v) > 0 {
 		mergedUrl.SetParam(constant.REMOTE_TIMESTAMP_KEY, v)
 		mergedUrl.SetParam(constant.TIMESTAMP_KEY, referenceUrl.GetParam(constant.TIMESTAMP_KEY, ""))
 	}
 
-	//finally execute methodConfigMergeFcn
+	// finally execute methodConfigMergeFcn
 	for _, method := range referenceUrl.Methods {
 		for _, fcn := range methodConfigMergeFcn {
 			fcn("methods." + method)
@@ -603,7 +596,7 @@
 	return mergedUrl
 }
 
-// Clone ...
+// Clone will copy the url
 func (c *URL) Clone() *URL {
 	newUrl := &URL{}
 	copier.Copy(newUrl, c)
@@ -615,8 +608,43 @@
 	return newUrl
 }
 
+func (c *URL) CloneExceptParams(excludeParams *gxset.HashSet) *URL {
+	newUrl := &URL{}
+	copier.Copy(newUrl, c)
+	newUrl.params = url.Values{}
+	c.RangeParams(func(key, value string) bool {
+		if !excludeParams.Contains(key) {
+			newUrl.SetParam(key, value)
+		}
+		return true
+	})
+	return newUrl
+}
+
+// Copy url based on the reserved parameter's keys.
+func (c *URL) CloneWithParams(reserveParams []string) *URL {
+	params := url.Values{}
+	for _, reserveParam := range reserveParams {
+		v := c.GetParam(reserveParam, "")
+		if len(v) != 0 {
+			params.Set(reserveParam, v)
+		}
+	}
+
+	return NewURLWithOptions(
+		WithProtocol(c.Protocol),
+		WithUsername(c.Username),
+		WithPassword(c.Password),
+		WithIp(c.Ip),
+		WithPort(c.Port),
+		WithPath(c.Path),
+		WithMethods(c.Methods),
+		WithParams(params),
+	)
+}
+
 func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) {
-	var methodConfigMergeFcn = []func(method string){}
+	methodConfigMergeFcn := make([]func(method string), 0, len(paramKeys))
 	for _, paramKey := range paramKeys {
 		if v := referenceUrl.GetParam(paramKey, ""); len(v) > 0 {
 			mergedUrl.SetParam(paramKey, v)
@@ -629,3 +657,22 @@
 	}
 	return methodConfigMergeFcn
 }
+
+// URLSlice will be used to sort URL instance
+// Instances will be order by URL.String()
+type URLSlice []URL
+
+// nolint
+func (s URLSlice) Len() int {
+	return len(s)
+}
+
+// nolint
+func (s URLSlice) Less(i, j int) bool {
+	return s[i].String() < s[j].String()
+}
+
+// nolint
+func (s URLSlice) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
diff --git a/common/url_test.go b/common/url_test.go
index 2372de5..6845190 100644
--- a/common/url_test.go
+++ b/common/url_test.go
@@ -31,24 +31,30 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
+const (
+	userName        = "username"
+	password        = "password"
+	loopbackAddress = "127.0.0.1"
+)
+
 func TestNewURLWithOptions(t *testing.T) {
 	methods := []string{"Methodone,methodtwo"}
 	params := url.Values{}
 	params.Set("key", "value")
 	u := NewURLWithOptions(WithPath("com.test.Service"),
-		WithUsername("username"),
-		WithPassword("password"),
+		WithUsername(userName),
+		WithPassword(password),
 		WithProtocol("testprotocol"),
-		WithIp("127.0.0.1"),
+		WithIp(loopbackAddress),
 		WithPort("8080"),
 		WithMethods(methods),
 		WithParams(params),
 		WithParamsValue("key2", "value2"))
 	assert.Equal(t, "/com.test.Service", u.Path)
-	assert.Equal(t, "username", u.Username)
-	assert.Equal(t, "password", u.Password)
+	assert.Equal(t, userName, u.Username)
+	assert.Equal(t, password, u.Password)
 	assert.Equal(t, "testprotocol", u.Protocol)
-	assert.Equal(t, "127.0.0.1", u.Ip)
+	assert.Equal(t, loopbackAddress, u.Ip)
 	assert.Equal(t, "8080", u.Port)
 	assert.Equal(t, methods, u.Methods)
 	assert.Equal(t, params, u.params)
@@ -65,7 +71,7 @@
 	assert.Equal(t, "/com.ikurento.user.UserProvider", u.Path)
 	assert.Equal(t, "127.0.0.1:20000", u.Location)
 	assert.Equal(t, "dubbo", u.Protocol)
-	assert.Equal(t, "127.0.0.1", u.Ip)
+	assert.Equal(t, loopbackAddress, u.Ip)
 	assert.Equal(t, "20000", u.Port)
 	assert.Equal(t, URL{}.Methods, u.Methods)
 	assert.Equal(t, "", u.Username)
@@ -92,7 +98,7 @@
 	assert.Equal(t, "/com.ikurento.user.UserProvider", u.Path)
 	assert.Equal(t, "127.0.0.1:20000", u.Location)
 	assert.Equal(t, "dubbo", u.Protocol)
-	assert.Equal(t, "127.0.0.1", u.Ip)
+	assert.Equal(t, loopbackAddress, u.Ip)
 	assert.Equal(t, "20000", u.Port)
 	assert.Equal(t, URL{}.Methods, u.Methods)
 	assert.Equal(t, "", u.Username)
@@ -108,7 +114,7 @@
 		"ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&timestamp=1556509797245", u.String())
 }
 
-func TestURL_URLEqual(t *testing.T) {
+func TestURLEqual(t *testing.T) {
 	u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0")
 	assert.NoError(t, err)
 	u2, err := NewURL("dubbo://127.0.0.2:20001/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0")
@@ -118,9 +124,36 @@
 	u3, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0")
 	assert.NoError(t, err)
 	assert.False(t, u1.URLEqual(u3))
+
+	// urlGroupAnyValue's group is *
+	urlGroupAnyValue, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0")
+	assert.NoError(t, err)
+	assert.True(t, u3.URLEqual(urlGroupAnyValue))
+
+	// test for enabled
+	urlEnabledEmpty, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=")
+	assert.NoError(t, err)
+	assert.True(t, u3.URLEqual(urlEnabledEmpty))
+
+	urlEnabledFalse, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=1")
+	assert.NoError(t, err)
+	assert.False(t, u3.URLEqual(urlEnabledFalse))
+
+	urlEnabledTrue, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=true")
+	assert.NoError(t, err)
+	assert.True(t, u3.URLEqual(urlEnabledTrue))
+
+	urlEnabledAny, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=*")
+	assert.NoError(t, err)
+	assert.True(t, u3.URLEqual(urlEnabledAny))
+
+	// test for category
+	categoryAny, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=*&category=*")
+	assert.NoError(t, err)
+	assert.True(t, categoryAny.URLEqual(u3))
 }
 
-func TestURL_GetParam(t *testing.T) {
+func TestURLGetParam(t *testing.T) {
 	params := url.Values{}
 	params.Set("key", "value")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -132,7 +165,7 @@
 	assert.Equal(t, "default", v)
 }
 
-func TestURL_GetParamInt(t *testing.T) {
+func TestURLGetParamInt(t *testing.T) {
 	params := url.Values{}
 	params.Set("key", "3")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -144,7 +177,7 @@
 	assert.Equal(t, int64(1), v)
 }
 
-func TestURL_GetParamBool(t *testing.T) {
+func TestURLGetParamBool(t *testing.T) {
 	params := url.Values{}
 	params.Set("force", "true")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -156,7 +189,7 @@
 	assert.Equal(t, false, v)
 }
 
-func TestURL_GetParamAndDecoded(t *testing.T) {
+func TestURLGetParamAndDecoded(t *testing.T) {
 	rule := "host = 2.2.2.2,1.1.1.1,3.3.3.3 & host !=1.1.1.1 => host = 1.2.3.4"
 	params := url.Values{}
 	params.Set("rule", base64.URLEncoding.EncodeToString([]byte(rule)))
@@ -165,20 +198,20 @@
 	assert.Equal(t, rule, v)
 }
 
-func TestURL_GetRawParam(t *testing.T) {
+func TestURLGetRawParam(t *testing.T) {
 	u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
 	u.Username = "test"
 	u.Password = "test"
 	assert.Equal(t, "condition", u.GetRawParam("protocol"))
 	assert.Equal(t, "0.0.0.0", u.GetRawParam("host"))
 	assert.Equal(t, "8080", u.GetRawParam("port"))
-	assert.Equal(t, "test", u.GetRawParam("username"))
-	assert.Equal(t, "test", u.GetRawParam("password"))
+	assert.Equal(t, "test", u.GetRawParam(userName))
+	assert.Equal(t, "test", u.GetRawParam(password))
 	assert.Equal(t, "/com.foo.BarService", u.GetRawParam("path"))
 	assert.Equal(t, "fastjson", u.GetRawParam("serialization"))
 }
 
-func TestURL_ToMap(t *testing.T) {
+func TestURLToMap(t *testing.T) {
 	u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
 	u.Username = "test"
 	u.Password = "test"
@@ -188,13 +221,13 @@
 	assert.Equal(t, "condition", m["protocol"])
 	assert.Equal(t, "0.0.0.0", m["host"])
 	assert.Equal(t, "8080", m["port"])
-	assert.Equal(t, "test", m["username"])
-	assert.Equal(t, "test", m["password"])
+	assert.Equal(t, "test", m[userName])
+	assert.Equal(t, "test", m[password])
 	assert.Equal(t, "/com.foo.BarService", m["path"])
 	assert.Equal(t, "fastjson", m["serialization"])
 }
 
-func TestURL_GetMethodParamInt(t *testing.T) {
+func TestURLGetMethodParamInt(t *testing.T) {
 	params := url.Values{}
 	params.Set("methods.GetValue.timeout", "3")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -206,7 +239,7 @@
 	assert.Equal(t, int64(1), v)
 }
 
-func TestURL_GetMethodParam(t *testing.T) {
+func TestURLGetMethodParam(t *testing.T) {
 	params := url.Values{}
 	params.Set("methods.GetValue.timeout", "3s")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -218,7 +251,7 @@
 	assert.Equal(t, "1s", v)
 }
 
-func TestURL_GetMethodParamBool(t *testing.T) {
+func TestURLGetMethodParamBool(t *testing.T) {
 	params := url.Values{}
 	params.Set("methods.GetValue.async", "true")
 	u := URL{baseUrl: baseUrl{params: params}}
@@ -252,7 +285,7 @@
 	assert.Equal(t, "2", mergedUrl.GetParam(constant.METHOD_KEYS+".testMethod."+constant.RETRIES_KEY, ""))
 }
 
-func TestURL_SetParams(t *testing.T) {
+func TestURLSetParams(t *testing.T) {
 	u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0")
 	assert.NoError(t, err)
 	params := url.Values{}
diff --git a/common/yaml/yaml.go b/common/yaml/yaml.go
index 7c31d71..5edda1b 100644
--- a/common/yaml/yaml.go
+++ b/common/yaml/yaml.go
@@ -40,7 +40,7 @@
 	return ioutil.ReadFile(confProFile)
 }
 
-// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object
+// unmarshalYMLConfig Load yml config byte from file, then unmarshal to object
 func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) {
 	confFileStream, err := LoadYMLConfig(confProFile)
 	if err != nil {
@@ -48,3 +48,7 @@
 	}
 	return confFileStream, yaml.Unmarshal(confFileStream, out)
 }
+
+func UnmarshalYML(data []byte, out interface{}) error {
+	return yaml.Unmarshal(data, out)
+}
diff --git a/common/yaml/yaml_test.go b/common/yaml/yaml_test.go
index 45eee59..5a271a2 100644
--- a/common/yaml/yaml_test.go
+++ b/common/yaml/yaml_test.go
@@ -38,7 +38,7 @@
 	assert.Equal(t, "childStrTest", c.ChildConfig.StrTest)
 }
 
-func TestUnmarshalYMLConfig_Error(t *testing.T) {
+func TestUnmarshalYMLConfigError(t *testing.T) {
 	c := &Config{}
 	_, err := UnmarshalYMLConfig("./testdata/config", c)
 	assert.Error(t, err)
@@ -46,6 +46,18 @@
 	assert.Error(t, err)
 }
 
+func TestUnmarshalYML(t *testing.T) {
+	c := &Config{}
+	b, err := LoadYMLConfig("./testdata/config.yml")
+	assert.NoError(t, err)
+	err = UnmarshalYML(b, c)
+	assert.NoError(t, err)
+	assert.Equal(t, "strTest", c.StrTest)
+	assert.Equal(t, 11, c.IntTest)
+	assert.Equal(t, false, c.BooleanTest)
+	assert.Equal(t, "childStrTest", c.ChildConfig.StrTest)
+}
+
 type Config struct {
 	StrTest     string      `yaml:"strTest" default:"default" json:"strTest,omitempty" property:"strTest"`
 	IntTest     int         `default:"109"  yaml:"intTest" json:"intTest,omitempty" property:"intTest"`
diff --git a/config/application_config.go b/config/application_config.go
index 23ab7d3..ef99664 100644
--- a/config/application_config.go
+++ b/config/application_config.go
@@ -25,32 +25,24 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-// ApplicationConfig ...
+// ApplicationConfig is a configuration for current application, whether the application is a provider or a consumer
 type ApplicationConfig struct {
-	Organization string `yaml:"organization"  json:"organization,omitempty" property:"organization"`
+	Organization string `yaml:"organization" json:"organization,omitempty" property:"organization"`
 	Name         string `yaml:"name" json:"name,omitempty" property:"name"`
 	Module       string `yaml:"module" json:"module,omitempty" property:"module"`
 	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"`
+	// the metadata type. remote or local
+	MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"`
 }
 
-// Prefix ...
+// nolint
 func (*ApplicationConfig) Prefix() string {
 	return constant.DUBBO + ".application."
 }
 
-// Id ...
-func (c *ApplicationConfig) Id() string {
-	return ""
-}
-
-// SetId ...
-func (c *ApplicationConfig) SetId(id string) {
-
-}
-
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ApplicationConfig by @unmarshal function
 func (c *ApplicationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
diff --git a/config/base_config.go b/config/base_config.go
index 93c0ce6..0ba5bc7 100644
--- a/config/base_config.go
+++ b/config/base_config.go
@@ -43,13 +43,33 @@
 // 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,12 +84,11 @@
 	if c.prepareEnvironment() != nil {
 		return perrors.WithMessagef(err, "start config center error!")
 	}
-	//c.fresh()
+	// c.fresh()
 	return err
 }
 
 func (c *BaseConfig) prepareEnvironment() error {
-
 	factory := extension.GetConfigCenterFactory(c.ConfigCenterConfig.Protocol)
 	dynamicConfig, err := factory.GetDynamicConfiguration(c.configCenterUrl)
 	config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig)
@@ -102,14 +121,14 @@
 			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 {
@@ -125,20 +144,20 @@
 	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
 
 }
 
@@ -165,13 +184,13 @@
 						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 {
@@ -191,18 +210,20 @@
 
 					}
 					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:
@@ -210,21 +231,18 @@
 						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:
@@ -265,7 +283,7 @@
 				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, "|") {
@@ -280,7 +298,7 @@
 
 					}
 
-					//iter := f.MapRange()
+					// iter := f.MapRange()
 
 					for _, k := range f.MapKeys() {
 						v := f.MapIndex(k)
@@ -315,7 +333,7 @@
 }
 
 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())
 
@@ -323,45 +341,46 @@
 	setFieldValue(val, reflect.Value{}, config)
 }
 
-// SetFatherConfig ...
+// SetFatherConfig sets father config by @fatherConfig
 func (c *BaseConfig) SetFatherConfig(fatherConfig interface{}) {
 	c.fatherConfig = fatherConfig
 }
 
 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 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") != "" {
-				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 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 d16b242..15b4687 100644
--- a/config/base_config_test.go
+++ b/config/base_config_test.go
@@ -21,9 +21,11 @@
 	"reflect"
 	"testing"
 )
+
 import (
 	"github.com/stretchr/testify/assert"
 )
+
 import (
 	"github.com/apache/dubbo-go/common/config"
 	"github.com/apache/dubbo-go/common/extension"
@@ -31,89 +33,95 @@
 	_ "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",
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
-		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",
-					},
-				},
-			},
-		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 		ShutdownConfig: &ShutdownConfig{
 			Timeout:              "12s",
 			StepTimeout:          "2s",
@@ -133,90 +141,21 @@
 	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",
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
-		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",
-					},
-				},
-			},
-		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -229,89 +168,22 @@
 	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",
-			},
+		BaseConfig: BaseConfig{
+			ApplicationConfig: baseAppConfig,
 		},
-		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",
-					},
-				},
-			},
-		},
+		Registries: baseRegistries,
+		References: baseMockRef,
 	}
 
 	c.SetFatherConfig(father)
@@ -324,7 +196,7 @@
 	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"
@@ -337,42 +209,12 @@
 
 	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)
@@ -385,14 +227,11 @@
 	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"
@@ -400,48 +239,10 @@
 	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,7 +260,8 @@
 						Retries:       "2",
 						Loadbalance:   "random",
 					},
-					{InterfaceId: "MockService",
+					{
+						InterfaceId:   "MockService",
 						InterfaceName: "com.MockService",
 						Name:          "GetUser1",
 						Retries:       "2",
@@ -480,8 +282,7 @@
 	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{}
 	})
@@ -498,22 +299,22 @@
 	assert.Equal(t, "ikurento.com", v)
 }
 
-func Test_initializeStruct(t *testing.T) {
-	consumerConfig := &ConsumerConfig{}
+func TestInitializeStruct(t *testing.T) {
+	testConsumerConfig := &ConsumerConfig{}
 	tp := reflect.TypeOf(ConsumerConfig{})
 	v := reflect.New(tp)
 	initializeStruct(tp, v.Elem())
-	fmt.Println(reflect.ValueOf(consumerConfig).Elem().Type().String())
+	fmt.Println(reflect.ValueOf(testConsumerConfig).Elem().Type().String())
 	fmt.Println(v.Elem().Type().String())
-	reflect.ValueOf(consumerConfig).Elem().Set(v.Elem())
+	reflect.ValueOf(testConsumerConfig).Elem().Set(v.Elem())
 
 	assert.Condition(t, func() (success bool) {
-		return consumerConfig.ApplicationConfig != nil
+		return testConsumerConfig.Registry != nil
 	})
 	assert.Condition(t, func() (success bool) {
-		return consumerConfig.Registries != nil
+		return testConsumerConfig.Registries != nil
 	})
 	assert.Condition(t, func() (success bool) {
-		return consumerConfig.References != nil
+		return testConsumerConfig.References != nil
 	})
 }
diff --git a/config/config_center_config.go b/config/config_center_config.go
index 40b9b65..c9133dc 100644
--- a/config/config_center_config.go
+++ b/config/config_center_config.go
@@ -31,7 +31,13 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-// ConfigCenterConfig ...
+// ConfigCenterConfig is configuration for config center
+//
+// ConfigCenter also introduced concepts of namespace and group to better manage Key-Value pairs by group,
+// those configs are already built-in in many professional third-party configuration centers.
+// In most cases, namespace is used to isolate different tenants, while group is used to divide the key set from one tenant into groups.
+//
+// ConfigCenter has currently supported Zookeeper, Nacos, Etcd, Consul, Apollo
 type ConfigCenterConfig struct {
 	context       context.Context
 	Protocol      string `required:"true"  yaml:"protocol"  json:"protocol,omitempty"`
@@ -40,6 +46,7 @@
 	Group         string `default:"dubbo" yaml:"group" json:"group,omitempty"`
 	Username      string `yaml:"username" json:"username,omitempty"`
 	Password      string `yaml:"password" json:"password,omitempty"`
+	LogDir        string `yaml:"log_dir" json:"log_dir,omitempty"`
 	ConfigFile    string `default:"dubbo.properties" yaml:"config_file"  json:"config_file,omitempty"`
 	Namespace     string `default:"dubbo" yaml:"namespace"  json:"namespace,omitempty"`
 	AppConfigFile string `default:"dubbo.properties" yaml:"app_config_file"  json:"app_config_file,omitempty"`
@@ -48,7 +55,7 @@
 	timeout       time.Duration
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ConfigCenterConfig by @unmarshal function
 func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -60,12 +67,13 @@
 	return nil
 }
 
-// GetUrlMap ...
+// GetUrlMap gets url map from ConfigCenterConfig
 func (c *ConfigCenterConfig) GetUrlMap() url.Values {
 	urlMap := url.Values{}
 	urlMap.Set(constant.CONFIG_NAMESPACE_KEY, c.Namespace)
 	urlMap.Set(constant.CONFIG_GROUP_KEY, c.Group)
 	urlMap.Set(constant.CONFIG_CLUSTER_KEY, c.Cluster)
 	urlMap.Set(constant.CONFIG_APP_ID_KEY, c.AppId)
+	urlMap.Set(constant.CONFIG_LOG_DIR_KEY, c.LogDir)
 	return urlMap
 }
diff --git a/config/config_loader.go b/config/config_loader.go
index c0687d8..d5f8c68 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -21,6 +21,7 @@
 	"fmt"
 	"log"
 	"os"
+	"sync"
 	"time"
 )
 
@@ -33,15 +34,21 @@
 	"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 @@
 	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
 	}
 }
 
@@ -81,128 +96,146 @@
 	}
 }
 
+func loadConsumerConfig() {
+	if consumerConfig == nil {
+		logger.Warnf("consumerConfig is nil!")
+		return
+	}
+	// init other consumer config
+	conConfigType := consumerConfig.ConfigType
+	for key, value := range extension.GetDefaultConfigReader() {
+		if conConfigType != nil {
+			if v, ok := conConfigType[key]; ok {
+				value = v
+			}
+		}
+		if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil {
+			logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value)
+		}
+	}
+
+	checkApplicationName(consumerConfig.ApplicationConfig)
+	if err := configCenterRefreshConsumer(); err != nil {
+		logger.Errorf("[consumer config center refresh] %#v", err)
+	}
+	checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
+	for key, ref := range consumerConfig.References {
+		if ref.Generic {
+			genericService := NewGenericService(key)
+			SetConsumerService(genericService)
+		}
+		rpcService := GetConsumerService(key)
+		if rpcService == nil {
+			logger.Warnf("%s does not exist!", key)
+			continue
+		}
+		ref.id = key
+		ref.Refer(rpcService)
+		ref.Implement(rpcService)
+	}
+
+	// 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
+
+				if refconfig.invoker != nil &&
+					!refconfig.invoker.IsAvailable() {
+					checkok = false
+					count++
+					if count > maxWait {
+						errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version)
+						logger.Error(errMsg)
+						panic(errMsg)
+					}
+					time.Sleep(time.Second * 1)
+					break
+				}
+				if refconfig.invoker == nil {
+					logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName)
+				}
+			}
+		}
+		if checkok {
+			break
+		}
+		checkok = true
+	}
+}
+
+func loadProviderConfig() {
+	if providerConfig == nil {
+		logger.Warnf("providerConfig is nil!")
+		return
+	}
+
+	// init other provider config
+	proConfigType := providerConfig.ConfigType
+	for key, value := range extension.GetDefaultConfigReader() {
+		if proConfigType != nil {
+			if v, ok := proConfigType[key]; ok {
+				value = v
+			}
+		}
+		if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil {
+			logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value)
+		}
+	}
+
+	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 {
+			logger.Warnf("%s does not exist!", key)
+			continue
+		}
+		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))
+		}
+	}
+}
+
+func initRouter() {
+	if confRouterFile != "" {
+		if err := RouterInit(confRouterFile); err != nil {
+			log.Printf("[routerConfig init] %#v", err)
+		}
+	}
+}
+
 // Load Dubbo Init
 func Load() {
 
 	// init router
-	if confRouterFile != "" {
-		if errPro := RouterInit(confRouterFile); errPro != nil {
-			log.Printf("[routerConfig init] %#v", errPro)
-		}
+	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
-	if consumerConfig == nil {
-		logger.Warnf("consumerConfig is nil!")
-	} else {
-		// init other consumer config
-		conConfigType := consumerConfig.ConfigType
-		for key, value := range extension.GetDefaultConfigReader() {
-			if conConfigType == nil {
-				if v, ok := conConfigType[key]; ok {
-					value = v
-				}
-			}
-			if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil {
-				logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value)
-			}
-		}
-
-		metricConfig = consumerConfig.MetricConfig
-		applicationConfig = consumerConfig.ApplicationConfig
-
-		checkApplicationName(consumerConfig.ApplicationConfig)
-		if err := configCenterRefreshConsumer(); err != nil {
-			logger.Errorf("[consumer config center refresh] %#v", err)
-		}
-		checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
-		for key, ref := range consumerConfig.References {
-			if ref.Generic {
-				genericService := NewGenericService(key)
-				SetConsumerService(genericService)
-			}
-			rpcService := GetConsumerService(key)
-			if rpcService == nil {
-				logger.Warnf("%s does not exist!", key)
-				continue
-			}
-			ref.id = key
-			ref.Refer(rpcService)
-			ref.Implement(rpcService)
-		}
-
-		//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
-
-					if refconfig.invoker != nil &&
-						!refconfig.invoker.IsAvailable() {
-						checkok = false
-						count++
-						if count > maxWait {
-							errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version)
-							logger.Error(errMsg)
-							panic(errMsg)
-						}
-						time.Sleep(time.Second * 1)
-						break
-					}
-					if refconfig.invoker == nil {
-						logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName)
-					}
-				}
-			}
-			if checkok {
-				break
-			}
-			checkok = true
-		}
-	}
+	loadConsumerConfig()
 
 	// service config
-	if providerConfig == nil {
-		logger.Warnf("providerConfig is nil!")
-	} else {
-		// init other provider config
-		proConfigType := providerConfig.ConfigType
-		for key, value := range extension.GetDefaultConfigReader() {
-			if proConfigType != nil {
-				if v, ok := proConfigType[key]; ok {
-					value = v
-				}
-			}
-			if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil {
-				logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value)
-			}
-		}
+	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 {
-				logger.Warnf("%s does not exist!", key)
-				continue
-			}
-			svs.id = key
-			svs.Implement(rpcService)
-			if err := svs.Export(); err != nil {
-				panic(fmt.Sprintf("service %s export failed! ", key))
-			}
-		}
-	}
 	// init the shutdown callback
 	GracefulShutdownInit()
 }
@@ -219,39 +252,79 @@
 
 // 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 498f826..2cbf526 100644
--- a/config/config_loader_test.go
+++ b/config/config_loader_test.go
@@ -24,6 +24,7 @@
 
 import (
 	"github.com/stretchr/testify/assert"
+	"go.uber.org/atomic"
 )
 
 import (
@@ -36,10 +37,13 @@
 	"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)
@@ -82,14 +86,15 @@
 
 	conServices = map[string]common.RPCService{}
 	proServices = map[string]common.RPCService{}
-	common.ServiceMap.UnRegister("mock", "MockService")
+	err := common.ServiceMap.UnRegister("com.MockService", "mock", "MockService")
+	assert.Nil(t, err)
 	consumerConfig = nil
 	providerConfig = nil
 }
 
 func TestLoadWithSingleReg(t *testing.T) {
 	doInitConsumerWithSingleRegistry()
-	doInitProviderWithSingleRegistry()
+	mockInitProviderWithSingleRegistry()
 
 	ms := &MockService{}
 	SetConsumerService(ms)
@@ -110,7 +115,7 @@
 
 	conServices = map[string]common.RPCService{}
 	proServices = map[string]common.RPCService{}
-	common.ServiceMap.UnRegister("mock", "MockService")
+	common.ServiceMap.UnRegister("com.MockService", "mock", "MockService")
 	consumerConfig = nil
 	providerConfig = nil
 }
@@ -139,7 +144,7 @@
 
 	conServices = map[string]common.RPCService{}
 	proServices = map[string]common.RPCService{}
-	common.ServiceMap.UnRegister("mock", "MockService")
+	common.ServiceMap.UnRegister("com.MockService", "mock", "MockService")
 	consumerConfig = nil
 	providerConfig = nil
 }
@@ -151,7 +156,7 @@
 
 	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)
@@ -204,7 +209,7 @@
 
 	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)
@@ -232,3 +237,66 @@
 	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 6bc574a..5759ff3 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 @@
 	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 5170b90..81fc3a3 100644
--- a/config/config_utils_test.go
+++ b/config/config_utils_test.go
@@ -41,3 +41,23 @@
 	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 1fa6841..f8b671b 100644
--- a/config/consumer_config.go
+++ b/config/consumer_config.go
@@ -38,23 +38,22 @@
 // consumerConfig
 /////////////////////////
 
-// ConsumerConfig ...
+// ConsumerConfig is Consumer default configuration
 type ConsumerConfig struct {
 	BaseConfig `yaml:",inline"`
 	Filter     string `yaml:"filter" json:"filter,omitempty" property:"filter"`
-	// application
-	ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"`
 	// client
 	Connect_Timeout string `default:"100ms"  yaml:"connect_timeout" json:"connect_timeout,omitempty" property:"connect_timeout"`
 	ConnectTimeout  time.Duration
 
+	Registry   *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
+	Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
+
 	Request_Timeout string `yaml:"request_timeout" default:"5s" json:"request_timeout,omitempty" property:"request_timeout"`
 	RequestTimeout  time.Duration
 	ProxyFactory    string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
 	Check           *bool  `yaml:"check"  json:"check,omitempty" property:"check"`
 
-	Registry       *RegistryConfig             `yaml:"registry" json:"registry,omitempty" property:"registry"`
-	Registries     map[string]*RegistryConfig  `yaml:"registries" json:"registries,omitempty" property:"registries"`
 	References     map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"`
 	ProtocolConf   interface{}                 `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"`
 	FilterConf     interface{}                 `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
@@ -62,7 +61,7 @@
 	ConfigType     map[string]string           `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ConsumerConfig by @unmarshal function
 func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -74,17 +73,17 @@
 	return nil
 }
 
-// Prefix ...
+// nolint
 func (*ConsumerConfig) Prefix() string {
 	return constant.ConsumerConfigPrefix
 }
 
-// SetConsumerConfig ...
+// SetConsumerConfig sets consumerConfig by @c
 func SetConsumerConfig(c ConsumerConfig) {
 	consumerConfig = &c
 }
 
-// ConsumerInit ...
+// ConsumerInit loads config file to init consumer config
 func ConsumerInit(confConFile string) error {
 	if confConFile == "" {
 		return perrors.Errorf("application configure(consumer) file name is nil")
@@ -117,6 +116,7 @@
 			return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout)
 		}
 	}
+
 	logger.Debugf("consumer config{%#v}\n", consumerConfig)
 
 	return nil
@@ -127,7 +127,7 @@
 	var err error
 	if consumerConfig.ConfigCenterConfig != nil {
 		consumerConfig.SetFatherConfig(consumerConfig)
-		if err := consumerConfig.startConfigCenter(); err != nil {
+		if err = consumerConfig.startConfigCenter(); err != nil {
 			return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err))
 		}
 		consumerConfig.fresh()
@@ -142,6 +142,5 @@
 			return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout)
 		}
 	}
-
 	return nil
 }
diff --git a/config/generic_service.go b/config/generic_service.go
index b66e399..a3332af 100644
--- a/config/generic_service.go
+++ b/config/generic_service.go
@@ -19,18 +19,18 @@
 
 import "context"
 
-// GenericService ...
+// GenericService uses for generic invoke for service call
 type GenericService struct {
 	Invoke       func(ctx context.Context, req []interface{}) (interface{}, error) `dubbo:"$invoke"`
 	referenceStr string
 }
 
-// NewGenericService ...
+// NewGenericService returns a GenericService instance
 func NewGenericService(referenceStr string) *GenericService {
 	return &GenericService{referenceStr: referenceStr}
 }
 
-// Reference ...
+// Reference gets referenceStr from GenericService
 func (u *GenericService) Reference() string {
 	return u.referenceStr
 }
diff --git a/config/graceful_shutdown.go b/config/graceful_shutdown.go
index 382f05c..aa102f3 100644
--- a/config/graceful_shutdown.go
+++ b/config/graceful_shutdown.go
@@ -52,7 +52,7 @@
  * We define them by using 'package build' feature https://golang.org/pkg/go/build/
  */
 
-// GracefulShutdownInit ...
+// nolint
 func GracefulShutdownInit() {
 
 	signals := make(chan os.Signal, 1)
@@ -83,7 +83,7 @@
 	}()
 }
 
-// BeforeShutdown ...
+// BeforeShutdown provides processing flow before shutdown
 func BeforeShutdown() {
 
 	destroyAllRegistries()
@@ -126,10 +126,8 @@
 	}
 }
 
-/**
- * destroy the provider's protocol.
- * if the protocol is consumer's protocol too, we will keep it.
- */
+// destroyProviderProtocols destroys the provider's protocol.
+// if the protocol is consumer's protocol too, we will keep it
 func destroyProviderProtocols(consumerProtocols *gxset.HashSet) {
 
 	logger.Info("Graceful shutdown --- Destroy provider's protocols. ")
@@ -215,9 +213,7 @@
 	return timeout
 }
 
-/*
- * we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc.
- */
+// we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc.
 func getConsumerProtocols() *gxset.HashSet {
 	result := gxset.NewSet()
 	if consumerConfig == nil || consumerConfig.References == nil {
diff --git a/config/graceful_shutdown_config.go b/config/graceful_shutdown_config.go
index 6bbabeb..8717516 100644
--- a/config/graceful_shutdown_config.go
+++ b/config/graceful_shutdown_config.go
@@ -31,7 +31,7 @@
 	defaultStepTimeout = 10 * time.Second
 )
 
-// ShutdownConfig ...
+// ShutdownConfig is used as configuration for graceful shutdown
 type ShutdownConfig struct {
 	/*
 	 * Total timeout. Even though we don't release all resources,
@@ -58,12 +58,12 @@
 	RequestsFinished bool
 }
 
-// Prefix ...
+// nolint
 func (config *ShutdownConfig) Prefix() string {
 	return constant.ShutdownConfigPrefix
 }
 
-// GetTimeout ...
+// nolint
 func (config *ShutdownConfig) GetTimeout() time.Duration {
 	result, err := time.ParseDuration(config.Timeout)
 	if err != nil {
@@ -74,7 +74,7 @@
 	return result
 }
 
-// GetStepTimeout ...
+// nolint
 func (config *ShutdownConfig) GetStepTimeout() time.Duration {
 	result, err := time.ParseDuration(config.StepTimeout)
 	if err != nil {
diff --git a/config/graceful_shutdown_config_test.go b/config/graceful_shutdown_config_test.go
index 583ed70..80eb531 100644
--- a/config/graceful_shutdown_config_test.go
+++ b/config/graceful_shutdown_config_test.go
@@ -26,7 +26,7 @@
 	"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 8ad79ff..6f1fa98 100644
--- a/config/graceful_shutdown_signal_darwin.go
+++ b/config/graceful_shutdown_signal_darwin.go
@@ -23,12 +23,12 @@
 )
 
 var (
-	// ShutdownSignals ...
+	// 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}
 
-	// DumpHeapShutdownSignals ...
+	// DumpHeapShutdownSignals receives shutdown signals to process
 	DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL,
 		syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS}
 )
diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go
index 8ad79ff..6f1fa98 100644
--- a/config/graceful_shutdown_signal_linux.go
+++ b/config/graceful_shutdown_signal_linux.go
@@ -23,12 +23,12 @@
 )
 
 var (
-	// ShutdownSignals ...
+	// 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}
 
-	// DumpHeapShutdownSignals ...
+	// DumpHeapShutdownSignals receives shutdown signals to process
 	DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL,
 		syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS}
 )
diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go
index 815a05e..3136e5a 100644
--- a/config/graceful_shutdown_signal_windows.go
+++ b/config/graceful_shutdown_signal_windows.go
@@ -23,11 +23,11 @@
 )
 
 var (
-	// ShutdownSignals ...
+	// 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}
 
-	// DumpHeapShutdownSignals ...
+	// 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
new file mode 100644
index 0000000..8e833dd
--- /dev/null
+++ b/config/instance/metadata_report.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 instance
+
+import (
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/metadata/report"
+)
+
+var (
+	instance  report.MetadataReport
+	reportUrl common.URL
+	once      sync.Once
+)
+
+// 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() {
+		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 0000000..d489af0
--- /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/interfaces/config_reader.go b/config/interfaces/config_reader.go
index 8b79a17..b23f989 100644
--- a/config/interfaces/config_reader.go
+++ b/config/interfaces/config_reader.go
@@ -19,7 +19,7 @@
 
 import "bytes"
 
-// ConfigReader
+// ConfigReader is used to read config from consumer or provider
 type ConfigReader interface {
 	ReadConsumerConfig(reader *bytes.Buffer) error
 	ReadProviderConfig(reader *bytes.Buffer) error
diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go
new file mode 100644
index 0000000..6d319e5
--- /dev/null
+++ b/config/metadata_report_config.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 config
+
+import (
+	"net/url"
+)
+
+import (
+	"github.com/creasty/defaults"
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/config/instance"
+)
+
+// MethodConfig is method level configuration
+type MetadataReportConfig struct {
+	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
+func (c *MetadataReportConfig) Prefix() string {
+	return constant.MetadataReportPrefix
+}
+
+// UnmarshalYAML unmarshal the MetadataReportConfig by @unmarshal function
+func (c *MetadataReportConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	if err := defaults.Set(c); err != nil {
+		return perrors.WithStack(err)
+	}
+	type plain MetadataReportConfig
+	if err := unmarshal((*plain)(c)); err != nil {
+		return perrors.WithStack(err)
+	}
+	return nil
+}
+
+// nolint
+func (c *MetadataReportConfig) ToUrl() (*common.URL, error) {
+	urlMap := make(url.Values)
+
+	if c.Params != nil {
+		for k, v := range c.Params {
+			urlMap.Set(k, v)
+		}
+	}
+
+	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(rc.Username),
+		common.WithPassword(rc.Password),
+		common.WithLocation(rc.Address),
+		common.WithProtocol(c.Protocol),
+	)
+	if err != nil || len(res.Protocol) == 0 {
+		return nil, perrors.New("Invalid MetadataReportConfig.")
+	}
+	res.SetParam("metadata", res.Protocol)
+	return &res, nil
+}
+
+func (c *MetadataReportConfig) IsValid() bool {
+	return len(c.Protocol) != 0
+}
+
+// StartMetadataReport: The entry of metadata report start
+func startMetadataReport(metadataType string, metadataReportConfig *MetadataReportConfig) error {
+	if metadataReportConfig == nil || !metadataReportConfig.IsValid() {
+		return nil
+	}
+
+	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 {
+		instance.GetMetadataReportInstance(url)
+	} else {
+		return perrors.Wrap(err, "Start MetadataReport failed.")
+	}
+
+	return nil
+}
diff --git a/config/metadata_report_config_test.go b/config/metadata_report_config_test.go
new file mode 100644
index 0000000..1c585ee
--- /dev/null
+++ b/config/metadata_report_config_test.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 config
+
+import "testing"
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMetadataReportConfig_ToUrl(t *testing.T) {
+	GetBaseConfig().Remotes["mock"] = &RemoteConfig{
+		Address:    "127.0.0.1:2181",
+		Username:   "test",
+		Password:   "test",
+		TimeoutStr: "3s",
+	}
+	metadataReportConfig := MetadataReportConfig{
+		Protocol:  "mock",
+		RemoteRef: "mock",
+		Params: map[string]string{
+			"k": "v",
+		},
+	}
+	url, error := metadataReportConfig.ToUrl()
+	assert.NoError(t, error)
+	assert.Equal(t, "mock", url.Protocol)
+	assert.Equal(t, "127.0.0.1:2181", url.Location)
+	assert.Equal(t, "127.0.0.1", url.Ip)
+	assert.Equal(t, "2181", url.Port)
+	assert.Equal(t, "test", url.Username)
+	assert.Equal(t, "test", url.Password)
+	assert.Equal(t, "v", url.GetParam("k", ""))
+	assert.Equal(t, "mock", url.GetParam("metadata", ""))
+}
diff --git a/config/method_config.go b/config/method_config.go
index 8f196d9..e64773e 100644
--- a/config/method_config.go
+++ b/config/method_config.go
@@ -42,7 +42,7 @@
 	RequestTimeout              string `yaml:"timeout"  json:"timeout,omitempty" property:"timeout"`
 }
 
-// Prefix ...
+// nolint
 func (c *MethodConfig) Prefix() string {
 	if len(c.InterfaceId) != 0 {
 		return constant.DUBBO + "." + c.InterfaceName + "." + c.InterfaceId + "." + c.Name + "."
@@ -51,7 +51,7 @@
 	return constant.DUBBO + "." + c.InterfaceName + "." + c.Name + "."
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the MethodConfig by @unmarshal function
 func (c *MethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
diff --git a/config/mock_rpcservice.go b/config/mock_rpcservice.go
index 6c43699..1e21b25 100644
--- a/config/mock_rpcservice.go
+++ b/config/mock_rpcservice.go
@@ -21,20 +21,20 @@
 	"context"
 )
 
-// MockService ...
+// MockService mocks the rpc service for test
 type MockService struct{}
 
-// Reference ...
+// Reference mocks the Reference method
 func (*MockService) Reference() string {
 	return "MockService"
 }
 
-// GetUser ...
+// GetUser mocks the GetUser method
 func (*MockService) GetUser(ctx context.Context, itf []interface{}, str *struct{}) error {
 	return nil
 }
 
-// GetUser1 ...
+// GetUser1 mocks the GetUser1 method
 func (*MockService) GetUser1(ctx context.Context, itf []interface{}, str *struct{}) error {
 	return nil
 }
diff --git a/config/protocol_config.go b/config/protocol_config.go
index 33de976..cee5b7a 100644
--- a/config/protocol_config.go
+++ b/config/protocol_config.go
@@ -25,14 +25,14 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-// ProtocolConfig ...
+// ProtocolConfig is protocol configuration
 type ProtocolConfig struct {
 	Name string `required:"true" yaml:"name"  json:"name,omitempty" property:"name"`
 	Ip   string `required:"true" yaml:"ip"  json:"ip,omitempty" property:"ip"`
 	Port string `required:"true" yaml:"port"  json:"port,omitempty" property:"port"`
 }
 
-// Prefix ...
+// nolint
 func (c *ProtocolConfig) Prefix() string {
 	return constant.ProtocolConfigPrefix
 }
diff --git a/config/provider_config.go b/config/provider_config.go
index 14b77ca..9d8a242 100644
--- a/config/provider_config.go
+++ b/config/provider_config.go
@@ -28,7 +28,6 @@
 
 import (
 	"github.com/apache/dubbo-go/common/constant"
-	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/common/yaml"
 )
 
@@ -36,24 +35,23 @@
 // providerConfig
 /////////////////////////
 
-// ProviderConfig ...
+// 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"`
+	BaseConfig     `yaml:",inline"`
+	Filter         string                     `yaml:"filter" json:"filter,omitempty" property:"filter"`
+	ProxyFactory   string                     `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"`
+	Services       map[string]*ServiceConfig  `yaml:"services" json:"services,omitempty" property:"services"`
+	Protocols      map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"`
+	ProtocolConf   interface{}                `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" `
+	FilterConf     interface{}                `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
+	ShutdownConfig *ShutdownConfig            `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
+	ConfigType     map[string]string          `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
 
-	ApplicationConfig *ApplicationConfig         `yaml:"application" json:"application,omitempty" property:"application"`
-	Registry          *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
-	Registries        map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
-	Services          map[string]*ServiceConfig  `yaml:"services" json:"services,omitempty" property:"services"`
-	Protocols         map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"`
-	ProtocolConf      interface{}                `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" `
-	FilterConf        interface{}                `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
-	ShutdownConfig    *ShutdownConfig            `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
-	ConfigType        map[string]string          `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
+	Registry   *RegistryConfig            `yaml:"registry" json:"registry,omitempty" property:"registry"`
+	Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"`
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ProviderConfig by @unmarshal function
 func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -65,17 +63,17 @@
 	return nil
 }
 
-// Prefix ...
+// nolint
 func (*ProviderConfig) Prefix() string {
 	return constant.ProviderConfigPrefix
 }
 
-// SetProviderConfig ...
+// SetProviderConfig sets provider config by @p
 func SetProviderConfig(p ProviderConfig) {
 	providerConfig = &p
 }
 
-// ProviderInit ...
+// ProviderInit loads config file to init provider config
 func ProviderInit(confProFile string) error {
 	if len(confProFile) == 0 {
 		return perrors.Errorf("application configure(provider) file name is nil")
@@ -87,22 +85,20 @@
 	}
 
 	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
 		}
 	}
 
-	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 7ce0013..748b2d4 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -39,7 +39,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// ReferenceConfig ...
+// ReferenceConfig is the configuration of service consumer
 type ReferenceConfig struct {
 	context        context.Context
 	pxy            *proxy.Proxy
@@ -55,6 +55,7 @@
 	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"`
@@ -63,9 +64,10 @@
 	Generic        bool   `yaml:"generic"  json:"generic,omitempty" property:"generic"`
 	Sticky         bool   `yaml:"sticky"   json:"sticky,omitempty" property:"sticky"`
 	RequestTimeout string `yaml:"timeout"  json:"timeout,omitempty" property:"timeout"`
+	ForceTag       bool   `yaml:"force.tag"  json:"force.tag,omitempty" property:"force.tag"`
 }
 
-// Prefix ...
+// nolint
 func (c *ReferenceConfig) Prefix() string {
 	return constant.ReferenceConfigPrefix + c.InterfaceName + "."
 }
@@ -75,7 +77,7 @@
 	return &ReferenceConfig{id: id, context: ctx}
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ReferenceConfig by @unmarshal function
 func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	type rf ReferenceConfig
 	raw := rf{} // Put your defaults here
@@ -99,7 +101,9 @@
 		common.WithParams(c.getUrlMap()),
 		common.WithParamsValue(constant.BEAN_NAME_KEY, c.id),
 	)
-
+	if c.ForceTag {
+		cfgURL.AddParam(constant.ForceUseTag, "true")
+	}
 	if c.Url != "" {
 		// 1. user specified URL, could be peer-to-peer address, or register center's address.
 		urlStrings := gxstrings.RegSplit(c.Url, "\\s*[;]+\\s*")
@@ -141,6 +145,8 @@
 				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))
@@ -165,7 +171,7 @@
 	c.pxy.Implement(v)
 }
 
-// GetRPCService ...
+// GetRPCService gets RPCService from proxy
 func (c *ReferenceConfig) GetRPCService() common.RPCService {
 	return c.pxy.Get()
 }
@@ -185,6 +191,11 @@
 	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())
+
 	if len(c.RequestTimeout) != 0 {
 		urlMap.Set(constant.TIMEOUT_KEY, c.RequestTimeout)
 	}
diff --git a/config/reference_config_test.go b/config/reference_config_test.go
index 7a65e55..3fbf8da 100644
--- a/config/reference_config_test.go
+++ b/config/reference_config_test.go
@@ -38,13 +38,16 @@
 
 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 @@
 				Password:   "pwd1",
 			},
 		},
+
 		References: map[string]*ReferenceConfig{
 			"MockService": {
 				id: "MockProvider",
@@ -123,6 +127,7 @@
 }
 
 func (m *MockProvider) CallBack(res common.CallbackResponse) {
+	// CallBack is a mock function. to implement the interface
 }
 
 func doInitConsumerAsync() {
@@ -135,19 +140,23 @@
 
 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{
@@ -178,7 +187,7 @@
 	}
 }
 
-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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	return protocol.NewBaseExporter("test", invoker, &sync.Map{})
 }
 
-func (*mockRegistryProtocol) Destroy() {}
+func (*mockRegistryProtocol) Destroy() {
+	// Destroy is a mock function
+}
diff --git a/config/registry_config.go b/config/registry_config.go
index 4e4b6e9..ef527c8 100644
--- a/config/registry_config.go
+++ b/config/registry_config.go
@@ -33,20 +33,21 @@
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-// RegistryConfig ...
+// RegistryConfig is the configuration of the registry center
 type RegistryConfig struct {
 	Protocol string `required:"true" yaml:"protocol"  json:"protocol,omitempty" property:"protocol"`
-	//I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig
+	// I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig
 	TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second
 	Group      string `yaml:"group" json:"group,omitempty" property:"group"`
-	//for registry
-	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"`
+	// for registry
+	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"`
+	Simplified bool              `yaml:"simplified" json:"simplified,omitempty"  property:"simplified"`
+	Params     map[string]string `yaml:"params" json:"params,omitempty" property:"params"`
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the RegistryConfig by @unmarshal function
 func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -58,7 +59,7 @@
 	return nil
 }
 
-// Prefix ...
+// nolint
 func (*RegistryConfig) Prefix() string {
 	return constant.RegistryConfigPrefix + "|" + constant.SingleRegistryConfigPrefix
 }
@@ -70,9 +71,11 @@
 	for k, registryConf := range registries {
 		target := false
 
-		// if user not config targetRegistries,default load all
-		// Notice:in func "func Split(s, sep string) []string"  comment : if s does not contain sep and sep is not empty, SplitAfter returns a slice of length 1 whose only element is s.
-		// So we have to add the condition when targetRegistries string is not set (it will be "" when not set)
+		// if user not config targetRegistries, default load all
+		// Notice: in func "func Split(s, sep string) []string" comment:
+		// if s does not contain sep and sep is not empty, SplitAfter returns
+		// a slice of length 1 whose only element is s. So we have to add the
+		// condition when targetRegistries string is not set (it will be "" when not set)
 		if len(trSlice) == 0 || (len(trSlice) == 1 && trSlice[0] == "") {
 			target = true
 		} else {
@@ -86,29 +89,24 @@
 		}
 
 		if target {
-			var (
-				url common.URL
-				err error
-			)
-
 			addresses := strings.Split(registryConf.Address, ",")
 			address := addresses[0]
-			address = traslateRegistryConf(address, registryConf)
-			url, err = common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address,
+			address = translateRegistryConf(address, registryConf)
+			url, err := common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address,
 				common.WithParams(registryConf.getUrlMap(roleType)),
+				common.WithParamsValue("simplified", strconv.FormatBool(registryConf.Simplified)),
 				common.WithUsername(registryConf.Username),
 				common.WithPassword(registryConf.Password),
 				common.WithLocation(registryConf.Address),
 			)
 
 			if err != nil {
-				logger.Errorf("The registry id:%s url is invalid , error: %#v", k, err)
+				logger.Errorf("The registry id: %s url is invalid, error: %#v", k, err)
 				panic(err)
 			} else {
 				urls = append(urls, &url)
 			}
 		}
-
 	}
 
 	return urls
@@ -123,15 +121,14 @@
 	for k, v := range c.Params {
 		urlMap.Set(k, v)
 	}
-
 	return urlMap
 }
 
-func traslateRegistryConf(address string, registryConf *RegistryConfig) string {
+func translateRegistryConf(address string, registryConf *RegistryConfig) string {
 	if strings.Contains(address, "://") {
 		translatedUrl, err := url.Parse(address)
 		if err != nil {
-			logger.Errorf("The registry  url is invalid , error: %#v", err)
+			logger.Errorf("The registry url is invalid, error: %#v", err)
 			panic(err)
 		}
 		address = translatedUrl.Host
diff --git a/config/registry_config_test.go b/config/registry_config_test.go
index 6c2fed6..6e5dedc 100644
--- a/config/registry_config_test.go
+++ b/config/registry_config_test.go
@@ -29,7 +29,7 @@
 	"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 @@
 	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 0000000..5e0330c
--- /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 0000000..82535fd
--- /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/condition_router_config.go b/config/router_config.go
similarity index 80%
rename from config/condition_router_config.go
rename to config/router_config.go
index 87e8351..16a2bec 100644
--- a/config/condition_router_config.go
+++ b/config/router_config.go
@@ -18,31 +18,40 @@
 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"
 )
 
-//RouterInit Load config file to init router config
+var (
+	routerURLSet = gxset.NewSet()
+)
+
+// RouterInit Load config file to init router config
 func RouterInit(confRouterFile string) error {
 	fileRouterFactories := extension.GetFileRouterFactories()
 	bytes, err := yaml.LoadYMLConfig(confRouterFile)
 	if err != nil {
 		return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err))
 	}
+	logger.Warnf("get fileRouterFactories len{%+v})", len(fileRouterFactories))
 	for k, factory := range fileRouterFactories {
 		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 \n", k)
+		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/condition_router_config_test.go b/config/router_config_test.go
similarity index 90%
rename from config/condition_router_config_test.go
rename to config/router_config_test.go
index 2f0a38b..72e51c1 100644
--- a/config/condition_router_config_test.go
+++ b/config/router_config_test.go
@@ -27,7 +27,6 @@
 )
 
 import (
-	"github.com/apache/dubbo-go/cluster/directory"
 	_ "github.com/apache/dubbo-go/cluster/router/condition"
 )
 
@@ -58,10 +57,10 @@
 	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.go b/config/service.go
index b7e7dc2..b746141 100644
--- a/config/service.go
+++ b/config/service.go
@@ -36,17 +36,17 @@
 	proServices[service.Reference()] = service
 }
 
-// GetConsumerService ...
+// GetConsumerService gets ConsumerService by @name
 func GetConsumerService(name string) common.RPCService {
 	return conServices[name]
 }
 
-// GetProviderService ...
+// GetProviderService gets ProviderService by @name
 func GetProviderService(name string) common.RPCService {
 	return proServices[name]
 }
 
-// GetCallback ...
+// GetCallback gets CallbackResponse by @name
 func GetCallback(name string) func(response common.CallbackResponse) {
 	service := GetConsumerService(name)
 	if sv, ok := service.(common.AsyncCallbackService); ok {
diff --git a/config/service_config.go b/config/service_config.go
index 7d97fa4..57fce02 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -18,6 +18,7 @@
 package config
 
 import (
+	"container/list"
 	"context"
 	"fmt"
 	"net/url"
@@ -29,6 +30,7 @@
 
 import (
 	"github.com/creasty/defaults"
+	gxnet "github.com/dubbogo/gost/net"
 	perrors "github.com/pkg/errors"
 	"go.uber.org/atomic"
 )
@@ -42,7 +44,7 @@
 	"github.com/apache/dubbo-go/protocol/protocolwrapper"
 )
 
-// ServiceConfig ...
+// ServiceConfig is the configuration of the service provider
 type ServiceConfig struct {
 	context                     context.Context
 	id                          string
@@ -69,20 +71,25 @@
 	ExecuteLimitRejectedHandler string            `yaml:"execute.limit.rejected.handler" json:"execute.limit.rejected.handler,omitempty" property:"execute.limit.rejected.handler"`
 	Auth                        string            `yaml:"auth" json:"auth,omitempty" property:"auth"`
 	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
 }
 
-// Prefix ...
+// Prefix returns dubbo.service.${interface}.
 func (c *ServiceConfig) Prefix() string {
 	return constant.ServiceConfigPrefix + c.InterfaceName + "."
 }
 
-// UnmarshalYAML ...
+// UnmarshalYAML unmarshals the ServiceConfig by @unmarshal function
 func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := defaults.Set(c); err != nil {
 		return err
@@ -91,6 +98,8 @@
 	if err := unmarshal((*plain)(c)); err != nil {
 		return err
 	}
+	c.exported = atomic.NewBool(false)
+	c.unexported = atomic.NewBool(false)
 	return nil
 }
 
@@ -104,7 +113,35 @@
 	}
 }
 
-// Export ...
+// 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()
+	for _, proto := range protocolConfigs {
+		if len(proto.Port) > 0 {
+			continue
+		}
+
+		tcp, err := gxnet.ListenOnTCPRandomPort(proto.Ip)
+		if err != nil {
+			panic(perrors.New(fmt.Sprintf("Get tcp port error,err is {%v}", err)))
+		}
+		defer tcp.Close()
+		ports.PushBack(strings.Split(tcp.Addr().String(), ":")[1])
+	}
+	return ports
+}
+
+// Export exports the service
 func (c *ServiceConfig) Export() error {
 	// TODO: config center start here
 
@@ -121,29 +158,44 @@
 
 	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
 	}
+
+	ports := getRandomPort(protocolConfigs)
+	nextPort := ports.Front()
 	for _, proto := range protocolConfigs {
 		// registry the service reflect
-		methods, err := common.ServiceMap.Register(proto.Name, c.rpcService)
+		methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService)
 		if err != nil {
-			err := perrors.Errorf("The service %v  export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error())
-			logger.Errorf(err.Error())
-			return err
+			formatErr := perrors.Errorf("The service %v  export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error())
+			logger.Errorf(formatErr.Error())
+			return formatErr
+		}
+
+		port := proto.Port
+
+		if len(proto.Port) == 0 {
+			port = nextPort.Value.(string)
+			nextPort = nextPort.Next()
 		}
 		ivkURL := common.NewURLWithOptions(
 			common.WithPath(c.id),
 			common.WithProtocol(proto.Name),
 			common.WithIp(proto.Ip),
-			common.WithPort(proto.Port),
+			common.WithPort(port),
 			common.WithParams(urlMap),
 			common.WithParamsValue(constant.BEAN_NAME_KEY, c.id),
 			common.WithMethods(strings.Split(methods, ",")),
 			common.WithToken(c.Token),
 		)
+		if len(c.Tag) > 0 {
+			ivkURL.AddParam(constant.Tagkey, c.Tag)
+		}
+
+		var exporter protocol.Exporter
 
 		if len(regUrls) > 0 {
 			for _, regUrl := range regUrls {
@@ -157,23 +209,47 @@
 				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
 }
@@ -193,6 +269,9 @@
 	urlMap.Set(constant.GROUP_KEY, c.Group)
 	urlMap.Set(constant.VERSION_KEY, c.Version)
 	urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))
+	urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version)
+	urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.PROVIDER)).Role())
+
 	// application info
 	urlMap.Set(constant.APPLICATION_KEY, providerConfig.ApplicationConfig.Name)
 	urlMap.Set(constant.ORGANIZATION_KEY, providerConfig.ApplicationConfig.Organization)
@@ -239,3 +318,16 @@
 
 	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 6f32308..e7d5507 100644
--- a/config/service_config_test.go
+++ b/config/service_config_test.go
@@ -22,18 +22,79 @@
 )
 
 import (
+	gxnet "github.com/dubbogo/gost/net"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/atomic"
+)
+
+import (
 	"github.com/apache/dubbo-go/common/extension"
 )
 
 func doInitProvider() {
 	providerConfig = &ProviderConfig{
-		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"},
+		},
+		Services: map[string]*ServiceConfig{
+			"MockService": {
+				InterfaceName: "com.MockService",
+				Protocol:      "mock",
+				Registry:      "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2",
+				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),
+			},
+			"MockServiceNoRightProtocol": {
+				InterfaceName: "com.MockService",
+				Protocol:      "mock1",
+				Registry:      "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2",
+				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),
+			},
+		},
+
 		Registries: map[string]*RegistryConfig{
 			"shanghai_reg1": {
 				Protocol:   "mock",
@@ -68,56 +129,7 @@
 				Password:   "pwd1",
 			},
 		},
-		Services: map[string]*ServiceConfig{
-			"MockService": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock",
-				Registry:      "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2",
-				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,
-					},
-				},
-			},
-			"MockServiceNoRightProtocol": {
-				InterfaceName: "com.MockService",
-				Protocol:      "mock1",
-				Registry:      "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2",
-				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,
-					},
-				},
-			},
-		},
+
 		Protocols: map[string]*ProtocolConfig{
 			"mock": {
 				Name: "mock",
@@ -128,64 +140,47 @@
 	}
 }
 
-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,
-					},
-				},
-			},
-		},
-		Protocols: map[string]*ProtocolConfig{
-			"mock": {
-				Name: "mock",
-				Ip:   "127.0.0.1",
-				Port: "20000",
-			},
-		},
-	}
-}
-
-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 TestgetRandomPort(t *testing.T) {
+	protocolConfigs := make([]*ProtocolConfig, 0, 3)
+
+	ip, err := gxnet.GetLocalIP()
+	protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+		Ip: ip,
+	})
+	protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+		Ip: ip,
+	})
+	protocolConfigs = append(protocolConfigs, &ProtocolConfig{
+		Ip: ip,
+	})
+	assert.NoError(t, err)
+	ports := getRandomPort(protocolConfigs)
+
+	assert.Equal(t, ports.Len(), len(protocolConfigs))
+
+	front := ports.Front()
+	for {
+		if front == nil {
+			break
+		}
+		t.Logf("port:%v", front.Value)
+		front = front.Next()
+	}
+
+	protocolConfigs = make([]*ProtocolConfig, 0, 3)
+	ports = getRandomPort(protocolConfigs)
+	assert.Equal(t, ports.Len(), len(protocolConfigs))
+}
diff --git a/config/service_discovery_config.go b/config/service_discovery_config.go
new file mode 100644
index 0000000..d3dc697
--- /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/impl.go b/config_center/apollo/impl.go
index 3b5d1f4..b049d33 100644
--- a/config_center/apollo/impl.go
+++ b/config_center/apollo/impl.go
@@ -25,7 +25,8 @@
 )
 
 import (
-	"github.com/pkg/errors"
+	gxset "github.com/dubbogo/gost/container/set"
+	perrors "github.com/pkg/errors"
 	"github.com/zouyx/agollo"
 )
 
@@ -119,7 +120,7 @@
 func (c *apolloConfiguration) GetInternalProperty(key string, opts ...cc.Option) (string, error) {
 	config := agollo.GetConfig(c.appConf.NamespaceName)
 	if config == nil {
-		return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key))
+		return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key))
 	}
 	return config.GetStringValue(key, ""), nil
 }
@@ -128,6 +129,16 @@
 	return c.GetInternalProperty(key, opts...)
 }
 
+// PublishConfig will publish the config with the (key, group, value) pair
+func (c *apolloConfiguration) PublishConfig(string, string, string) error {
+	return perrors.New("unsupport operation")
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *apolloConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+	return nil, perrors.New("unsupport operation")
+}
+
 func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (string, error) {
 	/**
 	 * when group is not null, we are getting startup configs(config file) from Config Center, for example:
@@ -135,7 +146,7 @@
 	 */
 	config := agollo.GetConfig(key)
 	if config == nil {
-		return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key))
+		return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key))
 	}
 	return config.GetContent(agollo.Properties), nil
 }
diff --git a/config_center/apollo/impl_test.go b/config_center/apollo/impl_test.go
index a95524b..335fb71 100644
--- a/config_center/apollo/impl_test.go
+++ b/config_center/apollo/impl_test.go
@@ -125,7 +125,7 @@
 	return runMockConfigServer(handlerMap, notifyResponse)
 }
 
-func configResponse(rw http.ResponseWriter, req *http.Request) {
+func configResponse(rw http.ResponseWriter, _ *http.Request) {
 	result := fmt.Sprintf(mockConfigRes)
 	fmt.Fprintf(rw, "%s", result)
 }
@@ -135,7 +135,7 @@
 	fmt.Fprintf(rw, "%s", result)
 }
 
-func serviceConfigResponse(rw http.ResponseWriter, req *http.Request) {
+func serviceConfigResponse(rw http.ResponseWriter, _ *http.Request) {
 	result := fmt.Sprintf(mockServiceConfigRes)
 	fmt.Fprintf(rw, "%s", result)
 }
@@ -164,7 +164,7 @@
 	return ts
 }
 
-func Test_GetConfig(t *testing.T) {
+func TestGetConfig(t *testing.T) {
 	configuration := initMockApollo(t)
 	configs, err := configuration.GetProperties(mockNamespace, config_center.WithGroup("dubbo"))
 	assert.NoError(t, err)
@@ -175,7 +175,7 @@
 	deleteMockJson(t)
 }
 
-func Test_GetConfigItem(t *testing.T) {
+func TestGetConfigItem(t *testing.T) {
 	configuration := initMockApollo(t)
 	configs, err := configuration.GetInternalProperty("application.organization")
 	assert.NoError(t, err)
@@ -238,7 +238,7 @@
 	apollo.RemoveListener(mockNamespace, listener)
 	assert.Equal(t, "", listener.event)
 	listenerCount := 0
-	apollo.listeners.Range(func(key, value interface{}) bool {
+	apollo.listeners.Range(func(_, value interface{}) bool {
 		apolloListener := value.(*apolloListener)
 		for e := range apolloListener.listeners {
 			fmt.Println(e)
diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go
index fb257a4..1cf65ed 100644
--- a/config_center/apollo/listener.go
+++ b/config_center/apollo/listener.go
@@ -29,7 +29,7 @@
 	listeners map[config_center.ConfigurationListener]struct{}
 }
 
-// NewApolloListener ...
+// NewApolloListener creates a new apolloListener
 func NewApolloListener() *apolloListener {
 	return &apolloListener{
 		listeners: make(map[config_center.ConfigurationListener]struct{}, 0),
@@ -49,7 +49,7 @@
 	}
 }
 
-// AddListener ...
+// AddListener adds a listener for apollo
 func (a *apolloListener) AddListener(l config_center.ConfigurationListener) {
 	if _, ok := a.listeners[l]; !ok {
 		a.listeners[l] = struct{}{}
@@ -57,7 +57,7 @@
 	}
 }
 
-// RemoveListener ...
+// RemoveListener removes listeners of apollo
 func (a *apolloListener) RemoveListener(l config_center.ConfigurationListener) {
 	delete(a.listeners, l)
 }
diff --git a/config_center/configuration_listener.go b/config_center/configuration_listener.go
index e70e4f6..97fd9c7 100644
--- a/config_center/configuration_listener.go
+++ b/config_center/configuration_listener.go
@@ -25,12 +25,13 @@
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// ConfigurationListener ...
+// ConfigurationListener for changing listener's event
 type ConfigurationListener interface {
+	// Process the notification event once there's any change happens on the config
 	Process(*ConfigChangeEvent)
 }
 
-// ConfigChangeEvent ...
+// ConfigChangeEvent for changing listener's event
 type ConfigChangeEvent struct {
 	Key        string
 	Value      interface{}
diff --git a/config_center/configurator.go b/config_center/configurator.go
index ffa9034..9db4804 100644
--- a/config_center/configurator.go
+++ b/config_center/configurator.go
@@ -21,7 +21,7 @@
 	"github.com/apache/dubbo-go/common"
 )
 
-// Configurator ...
+// Configurator supports GetUrl and constructor
 type Configurator interface {
 	GetUrl() *common.URL
 	Configure(url *common.URL)
diff --git a/config_center/configurator/mock.go b/config_center/configurator/mock.go
index d294b91..7ec7179 100644
--- a/config_center/configurator/mock.go
+++ b/config_center/configurator/mock.go
@@ -23,7 +23,7 @@
 	"github.com/apache/dubbo-go/config_center"
 )
 
-// NewMockConfigurator ...
+// NewMockConfigurator creates a new mockConfigurator
 func NewMockConfigurator(url *common.URL) config_center.Configurator {
 	return &mockConfigurator{configuratorUrl: url}
 }
@@ -32,12 +32,12 @@
 	configuratorUrl *common.URL
 }
 
-// GetUrl ...
+// GetUrl gets a configuratorUrl
 func (c *mockConfigurator) GetUrl() *common.URL {
 	return c.configuratorUrl
 }
 
-// Configure ...
+// Configure sets up param CLUSTER_KEY and cluster for url
 func (c *mockConfigurator) Configure(url *common.URL) {
 	if cluster := c.GetUrl().GetParam(constant.CLUSTER_KEY, ""); cluster != "" {
 		url.SetParam(constant.CLUSTER_KEY, cluster)
diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go
index 18415be..294a60e 100644
--- a/config_center/configurator/override.go
+++ b/config_center/configurator/override.go
@@ -50,12 +50,12 @@
 }
 
 func (c *overrideConfigurator) Configure(url *common.URL) {
-	//remove configuratorUrl some param that can not be configured
+	// remove configuratorUrl some param that can not be configured
 	if c.configuratorUrl.GetParam(constant.ENABLED_KEY, "true") == "false" || len(c.configuratorUrl.Location) == 0 {
 		return
 	}
 
-	//branch for version 2.7.x
+	// branch for version 2.7.x
 	apiVersion := c.configuratorUrl.GetParam(constant.CONFIG_VERSION_KEY, "")
 	if len(apiVersion) != 0 {
 		currentSide := url.GetParam(constant.SIDE_KEY, "")
@@ -67,48 +67,51 @@
 			c.configureIfMatch(url.Ip, url)
 		}
 	} else {
-		//branch for version 2.6.x and less
+		// branch for version 2.6.x and less
 		c.configureDeprecated(url)
 	}
 }
 
-//translate from java, compatible rules in java
+func (c *overrideConfigurator) configureIfMatchInternal(url *common.URL) {
+	configApp := c.configuratorUrl.GetParam(constant.APPLICATION_KEY, c.configuratorUrl.Username)
+	currentApp := url.GetParam(constant.APPLICATION_KEY, url.Username)
+	if len(configApp) == 0 || constant.ANY_VALUE == configApp || configApp == currentApp {
+		conditionKeys := gxset.NewSet()
+		conditionKeys.Add(constant.CATEGORY_KEY)
+		conditionKeys.Add(constant.CHECK_KEY)
+		conditionKeys.Add(constant.ENABLED_KEY)
+		conditionKeys.Add(constant.GROUP_KEY)
+		conditionKeys.Add(constant.VERSION_KEY)
+		conditionKeys.Add(constant.APPLICATION_KEY)
+		conditionKeys.Add(constant.SIDE_KEY)
+		conditionKeys.Add(constant.CONFIG_VERSION_KEY)
+		conditionKeys.Add(constant.COMPATIBLE_CONFIG_KEY)
+		returnUrl := false
+		c.configuratorUrl.RangeParams(func(k, _ string) bool {
+			value := c.configuratorUrl.GetParam(k, "")
+			if strings.HasPrefix(k, "~") || k == constant.APPLICATION_KEY || k == constant.SIDE_KEY {
+				conditionKeys.Add(k)
+				if len(value) != 0 && value != constant.ANY_VALUE && value != url.GetParam(strings.TrimPrefix(k, "~"), "") {
+					returnUrl = true
+					return false
+				}
+			}
+			return true
+		})
+		if returnUrl {
+			return
+		}
+		configUrl := c.configuratorUrl.CloneExceptParams(conditionKeys)
+		url.SetParams(configUrl.GetParams())
+	}
+}
+
+// configureIfMatch translate from java, compatible rules in java
 func (c *overrideConfigurator) configureIfMatch(host string, url *common.URL) {
 	if constant.ANYHOST_VALUE == c.configuratorUrl.Ip || host == c.configuratorUrl.Ip {
 		providers := c.configuratorUrl.GetParam(constant.OVERRIDE_PROVIDERS_KEY, "")
 		if len(providers) == 0 || strings.Index(providers, url.Location) >= 0 || strings.Index(providers, constant.ANYHOST_VALUE) >= 0 {
-			configApp := c.configuratorUrl.GetParam(constant.APPLICATION_KEY, c.configuratorUrl.Username)
-			currentApp := url.GetParam(constant.APPLICATION_KEY, url.Username)
-			if len(configApp) == 0 || constant.ANY_VALUE == configApp || configApp == currentApp {
-				conditionKeys := gxset.NewSet()
-				conditionKeys.Add(constant.CATEGORY_KEY)
-				conditionKeys.Add(constant.CHECK_KEY)
-				conditionKeys.Add(constant.ENABLED_KEY)
-				conditionKeys.Add(constant.GROUP_KEY)
-				conditionKeys.Add(constant.VERSION_KEY)
-				conditionKeys.Add(constant.APPLICATION_KEY)
-				conditionKeys.Add(constant.SIDE_KEY)
-				conditionKeys.Add(constant.CONFIG_VERSION_KEY)
-				conditionKeys.Add(constant.COMPATIBLE_CONFIG_KEY)
-				returnUrl := false
-				c.configuratorUrl.RangeParams(func(k, v string) bool {
-					value := c.configuratorUrl.GetParam(k, "")
-					if strings.HasPrefix(k, "~") || k == constant.APPLICATION_KEY || k == constant.SIDE_KEY {
-						conditionKeys.Add(k)
-						if len(value) != 0 && value != constant.ANY_VALUE && value != url.GetParam(strings.TrimPrefix(k, "~"), "") {
-							returnUrl = true
-							return false
-						}
-					}
-					return true
-				})
-				if returnUrl {
-					return
-				}
-				configUrl := c.configuratorUrl.Clone()
-				configUrl.RemoveParams(conditionKeys)
-				url.SetParams(configUrl.GetParams())
-			}
+			c.configureIfMatchInternal(url)
 		}
 	}
 }
diff --git a/config_center/configurator/override_test.go b/config_center/configurator/override_test.go
index c0aeb15..8eccb50 100644
--- a/config_center/configurator/override_test.go
+++ b/config_center/configurator/override_test.go
@@ -30,51 +30,58 @@
 	"github.com/apache/dubbo-go/common/extension"
 )
 
-func Test_configureVerison2p6(t *testing.T) {
+const (
+	defaults = "default"
+	override = "override"
+	failfast = "failfast"
+	failover = "failover"
+)
+
+func TestConfigureVerison2p6(t *testing.T) {
 	url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService")
 	assert.NoError(t, err)
-	configurator := extension.GetConfigurator("default", &url)
-	assert.Equal(t, "override", configurator.GetUrl().Protocol)
+	configurator := extension.GetConfigurator(defaults, &url)
+	assert.Equal(t, override, configurator.GetUrl().Protocol)
 
 	providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider&timestamp=1562076628&version=&warmup=100")
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
-	assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
+	assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, ""))
 }
 
-func Test_configureVerisonOverrideAddr(t *testing.T) {
+func TestConfigureVerisonOverrideAddr(t *testing.T) {
 	url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&providerAddresses=127.0.0.2:20001|127.0.0.3:20001")
 	assert.NoError(t, err)
-	configurator := extension.GetConfigurator("default", &url)
-	assert.Equal(t, "override", configurator.GetUrl().Protocol)
+	configurator := extension.GetConfigurator(defaults, &url)
+	assert.Equal(t, override, configurator.GetUrl().Protocol)
 
 	providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider&timestamp=1562076628&version=&warmup=100")
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
-	assert.Equal(t, "failover", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
+	assert.Equal(t, failover, providerUrl.GetParam(constant.CLUSTER_KEY, ""))
 }
 
-func Test_configureVerison2p6WithIp(t *testing.T) {
+func TestConfigureVerison2p6WithIp(t *testing.T) {
 	url, err := common.NewURL("override://127.0.0.1:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService")
 	assert.NoError(t, err)
-	configurator := extension.GetConfigurator("default", &url)
-	assert.Equal(t, "override", configurator.GetUrl().Protocol)
+	configurator := extension.GetConfigurator(defaults, &url)
+	assert.Equal(t, override, configurator.GetUrl().Protocol)
 
 	providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider&timestamp=1562076628&version=&warmup=100")
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
-	assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
+	assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, ""))
 
 }
 
-func Test_configureVerison2p7(t *testing.T) {
+func TestConfigureVerison2p7(t *testing.T) {
 	url, err := common.NewURL("jsonrpc://0.0.0.0:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&configVersion=1.0&side=provider")
 	assert.NoError(t, err)
-	configurator := extension.GetConfigurator("default", &url)
+	configurator := extension.GetConfigurator(defaults, &url)
 
 	providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider&timestamp=1562076628&version=&warmup=100")
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
-	assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
+	assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, ""))
 
 }
diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go
index d6c3b06..540febc 100644
--- a/config_center/dynamic_configuration.go
+++ b/config_center/dynamic_configuration.go
@@ -22,13 +22,17 @@
 )
 
 import (
+	gxset "github.com/dubbogo/gost/container/set"
+)
+
+import (
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/config_center/parser"
 )
 
-//////////////////////////////////////////
+// ////////////////////////////////////////
 // DynamicConfiguration
-//////////////////////////////////////////
+// ////////////////////////////////////////
 const (
 	// DEFAULT_GROUP: default group
 	DEFAULT_GROUP = "dubbo"
@@ -36,20 +40,26 @@
 	DEFAULT_CONFIG_TIMEOUT = "10s"
 )
 
-// DynamicConfiguration ...
+// DynamicConfiguration for modify listener and get properties file
 type DynamicConfiguration interface {
 	Parser() parser.ConfigurationParser
 	SetParser(parser.ConfigurationParser)
 	AddListener(string, ConfigurationListener, ...Option)
 	RemoveListener(string, ConfigurationListener, ...Option)
-	//GetProperties get properties file
+	// GetProperties get properties file
 	GetProperties(string, ...Option) (string, error)
 
-	//GetRule get Router rule properties file
+	// GetRule get Router rule properties file
 	GetRule(string, ...Option) (string, error)
 
-	//GetInternalProperty get value by key in Default properties file(dubbo.properties)
+	// GetInternalProperty get value by key in Default properties file(dubbo.properties)
 	GetInternalProperty(string, ...Option) (string, error)
+
+	// PublishConfig will publish the config with the (key, group, value) pair
+	PublishConfig(string, string, string) error
+
+	// GetConfigKeysByGroup will return all keys with the group
+	GetConfigKeysByGroup(group string) (*gxset.HashSet, error)
 }
 
 // Options ...
@@ -61,21 +71,21 @@
 // Option ...
 type Option func(*Options)
 
-// WithGroup ...
+// WithGroup assigns group to opt.Group
 func WithGroup(group string) Option {
 	return func(opt *Options) {
 		opt.Group = group
 	}
 }
 
-// WithTimeout ...
+// WithTimeout assigns time to opt.Timeout
 func WithTimeout(time time.Duration) Option {
 	return func(opt *Options) {
 		opt.Timeout = time
 	}
 }
 
-//GetRuleKey The format is '{interfaceName}:[version]:[group]'
+// GetRuleKey The format is '{interfaceName}:[version]:[group]'
 func GetRuleKey(url common.URL) string {
 	return url.ColonSeparatedKey()
 }
diff --git a/config_center/dynamic_configuration_factory.go b/config_center/dynamic_configuration_factory.go
index 9f9b132..46faf86 100644
--- a/config_center/dynamic_configuration_factory.go
+++ b/config_center/dynamic_configuration_factory.go
@@ -21,7 +21,7 @@
 	"github.com/apache/dubbo-go/common"
 )
 
-// DynamicConfigurationFactory ...
+// DynamicConfigurationFactory gets the DynamicConfiguration
 type DynamicConfigurationFactory interface {
 	GetDynamicConfiguration(*common.URL) (DynamicConfiguration, error)
 }
diff --git a/config_center/dynamic_configuration_test.go b/config_center/dynamic_configuration_test.go
new file mode 100644
index 0000000..8b5a8d7
--- /dev/null
+++ b/config_center/dynamic_configuration_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 config_center
+
+import (
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+)
+
+func TestWithTimeout(t *testing.T) {
+	fa := WithTimeout(12 * time.Second)
+	opt := &Options{}
+	fa(opt)
+	assert.Equal(t, 12*time.Second, opt.Timeout)
+}
+
+func TestGetRuleKey(t *testing.T) {
+	url, err := common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider?interface=test&group=groupA&version=0")
+	assert.NoError(t, err)
+	assert.Equal(t, "test:0:groupA", GetRuleKey(url))
+}
diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go
index 4d972b6..de20894 100644
--- a/config_center/mock_dynamic_config.go
+++ b/config_center/mock_dynamic_config.go
@@ -22,6 +22,7 @@
 )
 
 import (
+	gxset "github.com/dubbogo/gost/container/set"
 	"gopkg.in/yaml.v2"
 )
 
@@ -37,12 +38,16 @@
 	Content string
 }
 
+const (
+	mockServiceName = "org.apache.dubbo-go.mockService"
+)
+
 var (
 	once                 sync.Once
 	dynamicConfiguration *MockDynamicConfiguration
 )
 
-// GetDynamicConfiguration ...
+// GetDynamicConfiguration returns a DynamicConfiguration
 func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(_ *common.URL) (DynamicConfiguration, error) {
 	var err error
 	once.Do(func() {
@@ -81,6 +86,16 @@
 
 }
 
+// PublishConfig will publish the config with the (key, group, value) pair
+func (c *MockDynamicConfiguration) PublishConfig(string, string, string) error {
+	return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+	return gxset.NewSet(c.content), nil
+}
+
 // MockDynamicConfiguration ...
 type MockDynamicConfiguration struct {
 	parser   parser.ConfigurationParser
@@ -88,16 +103,17 @@
 	listener map[string]ConfigurationListener
 }
 
-// AddListener ...
+// AddListener adds a listener for MockDynamicConfiguration
 func (c *MockDynamicConfiguration) AddListener(key string, listener ConfigurationListener, _ ...Option) {
 	c.listener[key] = listener
 }
 
-// RemoveListener ...
+// RemoveListener removes the listener for MockDynamicConfiguration
 func (c *MockDynamicConfiguration) RemoveListener(_ string, _ ConfigurationListener, _ ...Option) {
+	// mock remove
 }
 
-// GetConfig ...
+// GetConfig returns content of MockDynamicConfiguration
 func (c *MockDynamicConfiguration) GetConfig(_ string, _ ...Option) (string, error) {
 
 	return c.content, nil
@@ -108,17 +124,17 @@
 	return c.GetConfig(key, opts...)
 }
 
-// Parser ...
+// Parser returns a parser of MockDynamicConfiguration
 func (c *MockDynamicConfiguration) Parser() parser.ConfigurationParser {
 	return c.parser
 }
 
-// SetParser ...
+// SetParser sets parser of MockDynamicConfiguration
 func (c *MockDynamicConfiguration) SetParser(p parser.ConfigurationParser) {
 	c.parser = p
 }
 
-// GetProperties ...
+// GetProperties gets content of MockDynamicConfiguration
 func (c *MockDynamicConfiguration) GetProperties(_ string, _ ...Option) (string, error) {
 	return c.content, nil
 }
@@ -128,7 +144,7 @@
 	return c.GetProperties(key, opts...)
 }
 
-// GetRule ...
+// GetRule gets properties of MockDynamicConfiguration
 func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string, error) {
 	return c.GetProperties(key, opts...)
 }
@@ -138,20 +154,20 @@
 	config := &parser.ConfiguratorConfig{
 		ConfigVersion: "2.7.1",
 		Scope:         parser.GeneralType,
-		Key:           "org.apache.dubbo-go.mockService",
+		Key:           mockServiceName,
 		Enabled:       true,
 		Configs: []parser.ConfigItem{
 			{Type: parser.GeneralType,
 				Enabled:    true,
 				Addresses:  []string{"0.0.0.0"},
-				Services:   []string{"org.apache.dubbo-go.mockService"},
+				Services:   []string{mockServiceName},
 				Side:       "provider",
 				Parameters: map[string]string{"cluster": "mock1"},
 			},
 		},
 	}
 	value, _ := yaml.Marshal(config)
-	key := "group*org.apache.dubbo-go.mockService:1.0.0" + constant.CONFIGURATORS_SUFFIX
+	key := "group*" + mockServiceName + ":1.0.0" + constant.CONFIGURATORS_SUFFIX
 	c.listener[key].Process(&ConfigChangeEvent{Key: key, Value: string(value), ConfigType: remoting.EventTypeAdd})
 }
 
@@ -160,13 +176,13 @@
 	config := &parser.ConfiguratorConfig{
 		ConfigVersion: "2.7.1",
 		Scope:         parser.ScopeApplication,
-		Key:           "org.apache.dubbo-go.mockService",
+		Key:           mockServiceName,
 		Enabled:       true,
 		Configs: []parser.ConfigItem{
 			{Type: parser.ScopeApplication,
 				Enabled:    true,
 				Addresses:  []string{"0.0.0.0"},
-				Services:   []string{"org.apache.dubbo-go.mockService"},
+				Services:   []string{mockServiceName},
 				Side:       "provider",
 				Parameters: map[string]string{"cluster": "mock1"},
 			},
diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go
index d3373e2..6fe5c4d 100644
--- a/config_center/nacos/client.go
+++ b/config_center/nacos/client.go
@@ -18,6 +18,7 @@
 package nacos
 
 import (
+	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -32,11 +33,13 @@
 )
 
 import (
+	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-const logDir = "logs/nacos/log"
+// Nacos Log dir, it can be override when creating client by config_center.log_dir
+var logDir = filepath.Join("logs", "nacos", "log")
 
 // NacosClient Nacos client
 type NacosClient struct {
@@ -87,19 +90,17 @@
 	}
 
 	url := container.GetUrl()
-
+	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))
+		newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout, url)
 		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)
-		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)
 		}
@@ -107,41 +108,19 @@
 	}
 
 	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
@@ -157,12 +136,24 @@
 		},
 	}
 
-	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{
@@ -171,21 +162,21 @@
 		}
 		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
@@ -230,5 +221,4 @@
 
 	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 ef63eef..01319f3 100644
--- a/config_center/nacos/client_test.go
+++ b/config_center/nacos/client_test.go
@@ -31,7 +31,7 @@
 	"github.com/apache/dubbo-go/common"
 )
 
-func Test_newNacosClient(t *testing.T) {
+func TestNewNacosClient(t *testing.T) {
 	server := mockCommonNacosServer()
 	nacosURL := strings.ReplaceAll(server.URL, "http", "registry")
 	registryUrl, _ := common.NewURL(nacosURL)
@@ -53,3 +53,62 @@
 	<-c.client.Done()
 	c.Destroy()
 }
+
+func TestSetNacosClient(t *testing.T) {
+	server := mockCommonNacosServer()
+	nacosURL := "registry://" + server.Listener.Addr().String()
+	registryUrl, _ := common.NewURL(nacosURL)
+	c := &nacosDynamicConfiguration{
+		url:  &registryUrl,
+		done: make(chan struct{}),
+	}
+	var client *NacosClient
+	client = &NacosClient{
+		name:       nacosClientName,
+		NacosAddrs: []string{nacosURL},
+		Timeout:    15 * time.Second,
+		exit:       make(chan struct{}),
+		onceClose: func() {
+			close(client.exit)
+		},
+	}
+	c.SetNacosClient(client)
+	err := ValidateNacosClient(c, WithNacosName(nacosClientName))
+	assert.NoError(t, err)
+	c.wg.Add(1)
+	go HandleClientRestart(c)
+	go func() {
+		// c.client.Close() and <-c.client.Done() have order requirements.
+		// If c.client.Close() is called first.It is possible that "go HandleClientRestart(c)"
+		// sets c.client to nil before calling c.client.Done().
+		time.Sleep(time.Second)
+		c.client.Close()
+	}()
+	<-c.client.Done()
+	c.Destroy()
+}
+
+func TestNewNacosClient_connectError(t *testing.T) {
+	nacosURL := "registry://127.0.0.1:8888"
+	registryUrl, err := common.NewURL(nacosURL)
+	assert.NoError(t, err)
+	c := &nacosDynamicConfiguration{
+		url:  &registryUrl,
+		done: make(chan struct{}),
+	}
+	err = ValidateNacosClient(c, WithNacosName(nacosClientName))
+	assert.NoError(t, err)
+	c.wg.Add(1)
+	go HandleClientRestart(c)
+	go func() {
+		// c.client.Close() and <-c.client.Done() have order requirements.
+		// If c.client.Close() is called first.It is possible that "go HandleClientRestart(c)"
+		// sets c.client to nil before calling c.client.Done().
+		time.Sleep(time.Second)
+		c.client.Close()
+	}()
+	<-c.client.Done()
+	// let client do retry
+	time.Sleep(5 * time.Second)
+	c.Destroy()
+}
diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go
index 60ab89b..bbf707b 100644
--- a/config_center/nacos/impl.go
+++ b/config_center/nacos/impl.go
@@ -18,10 +18,12 @@
 package nacos
 
 import (
+	"strings"
 	"sync"
 )
 
 import (
+	gxset "github.com/dubbogo/gost/container/set"
 	"github.com/nacos-group/nacos-sdk-go/vo"
 	perrors "github.com/pkg/errors"
 )
@@ -34,8 +36,16 @@
 	"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
@@ -74,7 +84,7 @@
 	n.removeListener(key, listener)
 }
 
-//nacos distinguishes configuration files based on group and dataId. defalut group = "dubbo" and dataId = key
+// GetProperties nacos distinguishes configuration files based on group and dataId. defalut group = "dubbo" and dataId = key
 func (n *nacosDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) {
 	return n.GetRule(key, opts...)
 }
@@ -84,6 +94,47 @@
 	return n.GetProperties(key, opts...)
 }
 
+// PublishConfig will publish the config with the (key, group, value) pair
+func (n *nacosDynamicConfiguration) PublishConfig(key string, group string, value string) error {
+
+	group = n.resolvedGroup(group)
+
+	ok, err := (*n.client.Client()).PublishConfig(vo.ConfigParam{
+		DataId:  key,
+		Group:   group,
+		Content: value,
+	})
+
+	if err != nil {
+		return perrors.WithStack(err)
+	}
+	if !ok {
+		return perrors.New("publish config to Nocos failed")
+	}
+	return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (n *nacosDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+	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
 func (n *nacosDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
 	tmpOpts := &config_center.Options{}
@@ -92,12 +143,12 @@
 	}
 	content, err := (*n.client.Client()).GetConfig(vo.ConfigParam{
 		DataId: key,
-		Group:  tmpOpts.Group,
+		Group:  n.resolvedGroup(tmpOpts.Group),
 	})
 	if err != nil {
 		return "", perrors.WithStack(err)
 	} else {
-		return string(content), nil
+		return content, nil
 	}
 }
 
@@ -145,6 +196,15 @@
 	n.closeConfigs()
 }
 
+// resolvedGroup will regular the group. Now, it will replace the '/' with '-'.
+// '/' is a special character for nacos
+func (n *nacosDynamicConfiguration) resolvedGroup(group string) string {
+	if len(group) <= 0 {
+		return group
+	}
+	return strings.ReplaceAll(group, "/", "-")
+}
+
 // IsAvailable Get available status
 func (n *nacosDynamicConfiguration) IsAvailable() bool {
 	select {
@@ -155,12 +215,12 @@
 	}
 }
 
-func (r *nacosDynamicConfiguration) closeConfigs() {
-	r.cltLock.Lock()
-	client := r.client
-	r.client = nil
-	r.cltLock.Unlock()
+func (n *nacosDynamicConfiguration) closeConfigs() {
+	n.cltLock.Lock()
+	client := n.client
+	n.client = nil
+	n.cltLock.Unlock()
 	// Close the old client first to close the tmp node
 	client.Close()
-	logger.Infof("begin to close provider nacos client")
+	logger.Infof("begin to close provider n client")
 }
diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go
index b4e6f1d..88d200e 100644
--- a/config_center/nacos/impl_test.go
+++ b/config_center/nacos/impl_test.go
@@ -59,15 +59,10 @@
 }
 
 func mockCommonNacosServer() *httptest.Server {
-	return runMockConfigServer(func(writer http.ResponseWriter, request *http.Request) {
-		data := `
-	dubbo.service.com.ikurento.user.UserProvider.cluster=failback
-	dubbo.service.com.ikurento.user.UserProvider.protocol=myDubbo1
-	dubbo.protocols.myDubbo.port=20000
-	dubbo.protocols.myDubbo.name=dubbo
-`
+	return runMockConfigServer(func(writer http.ResponseWriter, _ *http.Request) {
+		data := "true"
 		fmt.Fprintf(writer, "%s", data)
-	}, func(writer http.ResponseWriter, request *http.Request) {
+	}, func(writer http.ResponseWriter, _ *http.Request) {
 		data := `dubbo.properties%02dubbo%02dubbo.service.com.ikurento.user.UserProvider.cluster=failback`
 		fmt.Fprintf(writer, "%s", data)
 	})
@@ -77,15 +72,16 @@
 	server := mockCommonNacosServer()
 	nacosURL := strings.ReplaceAll(server.URL, "http", "registry")
 	regurl, _ := common.NewURL(nacosURL)
-	nacosConfiguration, err := newNacosDynamicConfiguration(&regurl)
+	factory := &nacosDynamicConfigurationFactory{}
+	nacosConfiguration, err := factory.GetDynamicConfiguration(&regurl)
 	assert.NoError(t, err)
 
 	nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{})
 
-	return nacosConfiguration, err
+	return nacosConfiguration.(*nacosDynamicConfiguration), err
 }
 
-func Test_GetConfig(t *testing.T) {
+func TestGetConfig(t *testing.T) {
 	nacos, err := initNacosData(t)
 	assert.NoError(t, err)
 	configs, err := nacos.GetProperties("dubbo.properties", config_center.WithGroup("dubbo"))
@@ -93,17 +89,53 @@
 	assert.NoError(t, err)
 }
 
-func Test_AddListener(t *testing.T) {
+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)
+	key := "myKey"
+	group := "/custom/a/b"
+	value := "MyValue"
+	err = nacos.PublishConfig(key, group, value)
+	assert.Nil(t, err)
+}
+
+func TestAddListener(t *testing.T) {
 	nacos, err := initNacosData(t)
 	assert.NoError(t, err)
 	listener := &mockDataListener{}
 	time.Sleep(time.Second * 2)
 	nacos.AddListener("dubbo.properties", listener)
-	listener.wg.Add(1)
-	listener.wg.Wait()
 }
 
-func Test_RemoveListener(t *testing.T) {
+func TestRemoveListener(_ *testing.T) {
 	//TODO not supported in current go_nacos_sdk version
 }
 
diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go
index 25c5865..3118a9d 100644
--- a/config_center/nacos/listener.go
+++ b/config_center/nacos/listener.go
@@ -31,32 +31,34 @@
 	"github.com/apache/dubbo-go/remoting"
 )
 
-func callback(listener config_center.ConfigurationListener, namespace, group, dataId, data string) {
+func callback(listener config_center.ConfigurationListener, _, _, dataId, data string) {
 	listener.Process(&config_center.ConfigChangeEvent{Key: dataId, Value: data, ConfigType: remoting.EventTypeUpdate})
 }
 
-func (l *nacosDynamicConfiguration) addListener(key string, listener config_center.ConfigurationListener) {
-	_, loaded := l.keyListeners.Load(key)
+func (n *nacosDynamicConfiguration) addListener(key string, listener config_center.ConfigurationListener) {
+	_, loaded := n.keyListeners.Load(key)
 	if !loaded {
 		_, cancel := context.WithCancel(context.Background())
-		err := (*l.client.Client()).ListenConfig(vo.ConfigParam{
+		err := (*n.client.Client()).ListenConfig(vo.ConfigParam{
 			DataId: key,
 			Group:  "dubbo",
 			OnChange: func(namespace, group, dataId, data string) {
 				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
-		l.keyListeners.Store(key, newListener)
+		n.keyListeners.Store(key, newListener)
 	} else {
 		// TODO check goroutine alive, but this version of go_nacos_sdk is not support.
 		logger.Infof("profile:%s. this profile is already listening", key)
 	}
 }
 
-func (l *nacosDynamicConfiguration) removeListener(key string, listener config_center.ConfigurationListener) {
+func (n *nacosDynamicConfiguration) removeListener(key string, listener config_center.ConfigurationListener) {
 	// TODO: not supported in current go_nacos_sdk version
 	logger.Warn("not supported in current go_nacos_sdk version")
 }
diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go
index f33b4ba..6fbdc27 100644
--- a/config_center/parser/configuration_parser.go
+++ b/config_center/parser/configuration_parser.go
@@ -47,7 +47,7 @@
 	ParseToUrls(content string) ([]*common.URL, error)
 }
 
-// DefaultConfigurationParser for support properties file in config center
+// DefaultConfigurationParser for supporting properties file in config center
 type DefaultConfigurationParser struct{}
 
 // ConfiguratorConfig ...
@@ -71,7 +71,7 @@
 	Side              string            `yaml:"side"`
 }
 
-// Parse ...
+// Parse load content
 func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) {
 	pps, err := properties.LoadString(content)
 	if err != nil {
diff --git a/config_center/parser/configuration_parser_test.go b/config_center/parser/configuration_parser_test.go
index 7a59ea9..be2d45b 100644
--- a/config_center/parser/configuration_parser_test.go
+++ b/config_center/parser/configuration_parser_test.go
@@ -25,10 +25,65 @@
 	"github.com/stretchr/testify/assert"
 )
 
-func TestDefaultConfigurationParser_Parser(t *testing.T) {
+func TestDefaultConfigurationParserParser(t *testing.T) {
 	parser := &DefaultConfigurationParser{}
 	m, err := parser.Parse("dubbo.registry.address=172.0.0.1\ndubbo.registry.name=test")
 	assert.NoError(t, err)
 	assert.Equal(t, 2, len(m))
 	assert.Equal(t, "172.0.0.1", m["dubbo.registry.address"])
 }
+
+func TestDefaultConfigurationParserAppItemToUrls_ParserToUrls(t *testing.T) {
+	parser := &DefaultConfigurationParser{}
+	content := `configVersion: 2.7.1
+scope: application
+key: org.apache.dubbo-go.mockService
+enabled: true
+configs:
+- type: application
+  enabled: true
+  addresses:
+  - 0.0.0.0
+  providerAddresses: []
+  services:
+  - org.apache.dubbo-go.mockService
+  applications: []
+  parameters:
+    cluster: mock1
+  side: provider`
+	urls, err := parser.ParseToUrls(content)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, len(urls))
+	assert.Equal(t, "org.apache.dubbo-go.mockService", urls[0].GetParam("application", ""))
+	assert.Equal(t, "mock1", urls[0].GetParam("cluster", ""))
+	assert.Equal(t, "override", urls[0].Protocol)
+	assert.Equal(t, "0.0.0.0", urls[0].Location)
+}
+
+func TestDefaultConfigurationParserServiceItemToUrls_ParserToUrls(t *testing.T) {
+	parser := &DefaultConfigurationParser{}
+	content := `configVersion: 2.7.1
+scope: notApplication
+key: groupA/test:1
+enabled: true
+configs:
+- type: application
+  enabled: true
+  addresses:
+  - 0.0.0.0
+  providerAddresses: []
+  services:
+  - org.apache.dubbo-go.mockService
+  applications: []
+  parameters:
+    cluster: mock1
+  side: provider`
+	urls, err := parser.ParseToUrls(content)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, len(urls))
+	assert.Equal(t, "groupA", urls[0].GetParam("group", ""))
+	assert.Equal(t, "/test", urls[0].Path)
+	assert.Equal(t, "mock1", urls[0].GetParam("cluster", ""))
+	assert.Equal(t, "override", urls[0].Protocol)
+	assert.Equal(t, "0.0.0.0", urls[0].Location)
+}
diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go
index 404243d..ef579eb 100644
--- a/config_center/zookeeper/impl.go
+++ b/config_center/zookeeper/impl.go
@@ -20,11 +20,10 @@
 import (
 	"strings"
 	"sync"
-	"time"
 )
 
 import (
-	"github.com/dubbogo/go-zookeeper/zk"
+	gxset "github.com/dubbogo/gost/container/set"
 	perrors "github.com/pkg/errors"
 )
 
@@ -39,8 +38,9 @@
 
 const (
 	// ZkClient
-	//zookeeper client name
-	ZkClient = "zk config_center"
+	// zookeeper client name
+	ZkClient      = "zk config_center"
+	pathSeparator = "/"
 )
 
 type zookeeperDynamicConfiguration struct {
@@ -74,37 +74,11 @@
 	c.cacheListener = NewCacheListener(c.rootPath)
 
 	err = c.client.Create(c.rootPath)
-	c.listener.ListenServiceEvent(c.rootPath, c.cacheListener)
+	c.listener.ListenServiceEvent(url, c.rootPath, c.cacheListener)
 	return c, err
 
 }
 
-func newMockZookeeperDynamicConfiguration(url *common.URL, opts ...zookeeper.Option) (*zk.TestCluster, *zookeeperDynamicConfiguration, error) {
-	c := &zookeeperDynamicConfiguration{
-		url:      url,
-		rootPath: "/" + url.GetParam(constant.CONFIG_NAMESPACE_KEY, config_center.DEFAULT_GROUP) + "/config",
-	}
-	var (
-		tc  *zk.TestCluster
-		err error
-	)
-	tc, c.client, _, err = zookeeper.NewMockZookeeperClient("test", 15*time.Second, opts...)
-	if err != nil {
-		logger.Errorf("mock zookeeper client start error ,error message is %v", err)
-		return tc, c, err
-	}
-	c.wg.Add(1)
-	go zookeeper.HandleClientRestart(c)
-
-	c.listener = zookeeper.NewZkEventListener(c.client)
-	c.cacheListener = NewCacheListener(c.rootPath)
-
-	err = c.client.Create(c.rootPath)
-	go c.listener.ListenServiceEvent(c.rootPath, c.cacheListener)
-	return tc, c, err
-
-}
-
 func (c *zookeeperDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, opions ...config_center.Option) {
 	c.cacheListener.AddListener(key, listener)
 }
@@ -143,11 +117,39 @@
 	return string(content), nil
 }
 
-//For zookeeper, getConfig and getConfigs have the same meaning.
+// GetInternalProperty For zookeeper, getConfig and getConfigs have the same meaning.
 func (c *zookeeperDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) {
 	return c.GetProperties(key, opts...)
 }
 
+// PublishConfig will put the value into Zk with specific path
+func (c *zookeeperDynamicConfiguration) PublishConfig(key string, group string, value string) error {
+	path := c.getPath(key, group)
+	err := c.client.CreateWithValue(path, []byte(value))
+	if err != nil {
+		return perrors.WithStack(err)
+	}
+	return nil
+}
+
+// GetConfigKeysByGroup will return all keys with the group
+func (c *zookeeperDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) {
+	path := c.getPath("", group)
+	result, err := c.client.GetChildren(path)
+	if err != nil {
+		return nil, perrors.WithStack(err)
+	}
+
+	if len(result) == 0 {
+		return nil, perrors.New("could not find keys with group: " + group)
+	}
+	set := gxset.NewSet()
+	for _, e := range result {
+		set.Add(e)
+	}
+	return set, nil
+}
+
 func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) {
 	return c.GetProperties(key, opts...)
 }
@@ -214,3 +216,17 @@
 func (c *zookeeperDynamicConfiguration) RestartCallBack() bool {
 	return true
 }
+
+func (c *zookeeperDynamicConfiguration) getPath(key string, group string) string {
+	if len(key) == 0 {
+		return c.buildPath(group)
+	}
+	return c.buildPath(group) + pathSeparator + key
+}
+
+func (c *zookeeperDynamicConfiguration) buildPath(group string) string {
+	if len(group) == 0 {
+		group = config_center.DEFAULT_GROUP
+	}
+	return c.rootPath + pathSeparator + group
+}
diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go
index 22e1519..ecc3527 100644
--- a/config_center/zookeeper/impl_test.go
+++ b/config_center/zookeeper/impl_test.go
@@ -18,27 +18,46 @@
 
 import (
 	"fmt"
+	"path"
+	"strconv"
 	"sync"
 	"testing"
 )
 
 import (
 	"github.com/dubbogo/go-zookeeper/zk"
+	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/constant"
 	"github.com/apache/dubbo-go/config_center"
 	"github.com/apache/dubbo-go/config_center/parser"
 )
 
-func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicConfiguration) {
-	regurl, _ := common.NewURL("registry://127.0.0.1:1111")
-	ts, reg, err := newMockZookeeperDynamicConfiguration(&regurl)
-	reg.SetParser(&parser.DefaultConfigurationParser{})
+const (
+	dubboPropertyFileName = "dubbo.properties"
+)
 
+func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicConfiguration) {
+	ts, err := zk.StartTestCluster(1, nil, nil)
 	assert.NoError(t, err)
+	assert.NotNil(t, ts.Servers[0])
+	urlString := "registry://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)
+	regurl, err := common.NewURL(urlString)
+	assert.NoError(t, err)
+	regurl.AddParam(constant.REGISTRY_TIMEOUT_KEY, "15s")
+	zkFactory := &zookeeperDynamicConfigurationFactory{}
+	reg, err := zkFactory.GetDynamicConfiguration(&regurl)
+	zreg, ok := reg.(*zookeeperDynamicConfiguration)
+	assert.True(t, ok)
+	assert.NoError(t, err)
+	assert.True(t, zreg.IsAvailable())
+	assert.Equal(t, zreg.GetUrl(), regurl)
+	assert.True(t, zreg.RestartCallBack())
+	zreg.SetParser(&parser.DefaultConfigurationParser{})
 
 	data := `
 	dubbo.consumer.request_timeout=5s
@@ -62,37 +81,43 @@
 	dubbo.service.com.ikurento.user.UserProvider.cluster=failover
 `
 	if group != "" {
-		err = reg.client.Create(reg.rootPath + "/dubbo/dubbo.properties")
+		err = zreg.client.Create(path.Join(zreg.rootPath, "dubbo", dubboPropertyFileName))
 		assert.NoError(t, err)
 
-		_, err = reg.client.Conn.Set(reg.rootPath+"/dubbo/dubbo.properties", []byte(data), 0)
+		_, err = zreg.client.Conn.Set(path.Join(zreg.rootPath, "dubbo", dubboPropertyFileName), []byte(data), 0)
 		assert.NoError(t, err)
 	} else {
-		err = reg.client.Create(reg.rootPath + "/dubbo.properties")
+		err = zreg.client.Create(path.Join(zreg.rootPath, dubboPropertyFileName))
 		assert.NoError(t, err)
 
-		_, err = reg.client.Conn.Set(reg.rootPath+"/dubbo.properties", []byte(data), 0)
+		_, err = zreg.client.Conn.Set(path.Join(zreg.rootPath, dubboPropertyFileName), []byte(data), 0)
 		assert.NoError(t, err)
 	}
 
-	return ts, reg
+	return ts, zreg
 }
 
-func Test_GetConfig(t *testing.T) {
+func TestGetConfig(t *testing.T) {
 	ts, reg := initZkData("dubbo", t)
 	defer ts.Stop()
-	configs, err := reg.GetProperties("dubbo.properties", config_center.WithGroup("dubbo"))
+	configs, err := reg.GetProperties(dubboPropertyFileName, config_center.WithGroup("dubbo"))
 	assert.NoError(t, err)
 	m, err := reg.Parser().Parse(configs)
 	assert.NoError(t, err)
 	assert.Equal(t, "5s", m["dubbo.consumer.request_timeout"])
+	configs, err = reg.GetProperties(dubboPropertyFileName)
+	assert.Error(t, err)
+	configs, err = reg.GetInternalProperty(dubboPropertyFileName)
+	assert.Error(t, err)
+	configs, err = reg.GetRule(dubboPropertyFileName)
+	assert.Error(t, err)
 }
 
-func Test_AddListener(t *testing.T) {
+func TestAddListener(t *testing.T) {
 	ts, reg := initZkData("", t)
 	defer ts.Stop()
 	listener := &mockDataListener{}
-	reg.AddListener("dubbo.properties", listener)
+	reg.AddListener(dubboPropertyFileName, listener)
 	listener.wg.Add(1)
 	data := `
 	dubbo.consumer.request_timeout=3s
@@ -115,17 +140,17 @@
 	dubbo.service.com.ikurento.user.UserProvider.warmup=100
 	dubbo.service.com.ikurento.user.UserProvider.cluster=failover
 `
-	_, err := reg.client.Conn.Set(reg.rootPath+"/dubbo.properties", []byte(data), 1)
+	_, err := reg.client.Conn.Set(path.Join(reg.rootPath, dubboPropertyFileName), []byte(data), 1)
 	assert.NoError(t, err)
 	listener.wg.Wait()
-	assert.Equal(t, "dubbo.properties", listener.event)
+	assert.Equal(t, dubboPropertyFileName, listener.event)
 }
 
-func Test_RemoveListener(t *testing.T) {
+func TestRemoveListener(t *testing.T) {
 	ts, reg := initZkData("", t)
 	defer ts.Stop()
 	listener := &mockDataListener{}
-	reg.AddListener("dubbo.properties", listener)
+	reg.AddListener(dubboPropertyFileName, listener)
 	listener.wg.Add(1)
 	data := `
 	dubbo.consumer.request_timeout=3s
@@ -148,14 +173,34 @@
 	dubbo.service.com.ikurento.user.UserProvider.warmup=100
 	dubbo.service.com.ikurento.user.UserProvider.cluster=failover
 `
-	reg.RemoveListener("dubbo.properties", listener)
+	reg.RemoveListener(dubboPropertyFileName, listener)
 	listener.wg.Done()
-	_, err := reg.client.Conn.Set(reg.rootPath+"/dubbo.properties", []byte(data), 1)
+	_, err := reg.client.Conn.Set(path.Join(reg.rootPath, dubboPropertyFileName), []byte(data), 1)
 	assert.NoError(t, err)
 	listener.wg.Wait()
 	assert.Equal(t, "", listener.event)
 }
 
+func TestZookeeperDynamicConfigurationPublishConfig(t *testing.T) {
+	value := "Test Data"
+	customGroup := "Custom Group"
+	key := "myKey"
+	ts, zk := initZkData(config_center.DEFAULT_GROUP, t)
+	defer ts.Stop()
+	err := zk.PublishConfig(key, customGroup, value)
+	assert.Nil(t, err)
+	result, err := zk.GetInternalProperty("myKey", config_center.WithGroup(customGroup))
+	assert.Nil(t, err)
+	assert.Equal(t, value, result)
+
+	var keys *gxset.HashSet
+	keys, err = zk.GetConfigKeysByGroup(customGroup)
+	assert.Nil(t, err)
+	assert.Equal(t, 1, keys.Size())
+	assert.True(t, keys.Contains(key))
+
+}
+
 type mockDataListener struct {
 	wg    sync.WaitGroup
 	event string
diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go
index 122dfaf..747c4be 100644
--- a/config_center/zookeeper/listener.go
+++ b/config_center/zookeeper/listener.go
@@ -33,12 +33,12 @@
 	rootPath     string
 }
 
-// NewCacheListener ...
+// NewCacheListener creates a new CacheListener
 func NewCacheListener(rootPath string) *CacheListener {
 	return &CacheListener{rootPath: rootPath}
 }
 
-// AddListener ...
+// AddListener will add a listener if loaded
 func (l *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) {
 
 	// reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure
@@ -50,7 +50,7 @@
 	}
 }
 
-// RemoveListener ...
+// RemoveListener will delete a listener if loaded
 func (l *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) {
 	listeners, loaded := l.keyListeners.Load(key)
 	if loaded {
@@ -58,7 +58,7 @@
 	}
 }
 
-// DataChange ...
+// DataChange changes all listeners' event
 func (l *CacheListener) DataChange(event remoting.Event) bool {
 	if event.Content == "" {
 		//meanings new node
diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png
index 87726d8..e5f1927 100644
--- a/doc/pic/arch/dubbo-go-arch.png
+++ b/doc/pic/arch/dubbo-go-arch.png
Binary files differ
diff --git a/doc/pic/arch/dubbo-go-ext.png b/doc/pic/arch/dubbo-go-ext.png
new file mode 100644
index 0000000..d065ef4
--- /dev/null
+++ b/doc/pic/arch/dubbo-go-ext.png
Binary files differ
diff --git a/filter/access_key.go b/filter/access_key.go
index 40d4157..4801d64 100644
--- a/filter/access_key.go
+++ b/filter/access_key.go
@@ -22,6 +22,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// AccessKeyPair stores the basic attributes for authentication.
 type AccessKeyPair struct {
 	AccessKey    string `yaml:"accessKey"   json:"accessKey,omitempty" property:"accessKey"`
 	SecretKey    string `yaml:"secretKey"   json:"secretKey,omitempty" property:"secretKey"`
@@ -31,8 +32,7 @@
 	Options      string `yaml:"options"   json:"options,omitempty" property:"options"`
 }
 
-// AccessKeyStorage
-// This SPI Extension support us to store our AccessKeyPair or load AccessKeyPair from other
+// AccessKeyStorage supports us to store our AccessKeyPair or load AccessKeyPair from other
 // storage, such as filesystem.
 type AccessKeyStorage interface {
 	GetAccessKeyPair(protocol.Invocation, *common.URL) *AccessKeyPair
diff --git a/filter/authenticator.go b/filter/authenticator.go
index ac2c860..71f659d 100644
--- a/filter/authenticator.go
+++ b/filter/authenticator.go
@@ -22,14 +22,13 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// Authenticator
+// Authenticator defines how an Authenticator works.
+// Custom Authenticator must be set by calling auth.SetAuthenticator before use.
 type Authenticator interface {
 
-	// Sign
-	// give a sign to request
+	// Sign adds signature to the invocation
 	Sign(protocol.Invocation, *common.URL) error
 
-	// Authenticate
-	// verify the signature of the request is valid or not
+	// Authenticate verifies the signature of the request
 	Authenticate(protocol.Invocation, *common.URL) error
 }
diff --git a/filter/filter.go b/filter/filter.go
index c069510..d20ca72 100644
--- a/filter/filter.go
+++ b/filter/filter.go
@@ -24,9 +24,11 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// Filter
+// 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(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 fbfe756..49cdc22 100644
--- a/filter/filter_impl/access_log_filter.go
+++ b/filter/filter_impl/access_log_filter.go
@@ -71,14 +71,18 @@
  *
  * the value of "accesslog" can be "true" or "default" too.
  * If the value is one of them, the access log will be record in log file which defined in log.yml
+ * AccessLogFilter is designed to be singleton
  */
 type AccessLogFilter struct {
 	logChan chan AccessLogData
 }
 
-// Invoke ...
+// Invoke will check whether user wants to use this filter.
+// If we find the value of key constant.ACCESS_LOG_KEY, we will log the invocation info
 func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	accessLog := invoker.GetUrl().GetParam(constant.ACCESS_LOG_KEY, "")
+
+	// the user do not
 	if len(accessLog) > 0 {
 		accessLogData := AccessLogData{data: ef.buildAccessLogData(invoker, invocation), accessLog: accessLog}
 		ef.logIntoChannel(accessLogData)
@@ -86,7 +90,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// it won't block the invocation
+// logIntoChannel won't block the invocation
 func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) {
 	select {
 	case ef.logChan <- accessLogData:
@@ -97,6 +101,7 @@
 	}
 }
 
+// buildAccessLogData builds the access log data
 func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string {
 	dataMap := make(map[string]string, 16)
 	attachments := invocation.Attachments()
@@ -130,11 +135,12 @@
 	return dataMap
 }
 
-// OnResponse ...
+// OnResponse do nothing
 func (ef *AccessLogFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result {
 	return result
 }
 
+// writeLogToFile actually write the logs into file
 func (ef *AccessLogFilter) writeLogToFile(data AccessLogData) {
 	accessLog := data.accessLog
 	if isDefault(accessLog) {
@@ -156,6 +162,12 @@
 	}
 }
 
+// openLogFile will open the log file with append mode.
+// If the file is not found, it will create the file.
+// Actually, the accessLog is the filename
+// You may find out that, once we want to write access log into log file,
+// we open the file again and again.
+// It needs to be optimized.
 func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) {
 	logFile, err := os.OpenFile(accessLog, os.O_CREATE|os.O_APPEND|os.O_RDWR, LogFileMode)
 	if err != nil {
@@ -169,6 +181,12 @@
 		return nil, err
 	}
 	last := fileInfo.ModTime().Format(FileDateFormat)
+
+	// this is confused.
+	// for example, if the last = '2020-03-04'
+	// and today is '2020-03-05'
+	// we will create one new file to log access data
+	// By this way, we can split the access log based on days.
 	if now != last {
 		err = os.Rename(fileInfo.Name(), fileInfo.Name()+"."+now)
 		if err != nil {
@@ -180,11 +198,12 @@
 	return logFile, err
 }
 
+// isDefault check whether accessLog == true or accessLog == default
 func isDefault(accessLog string) bool {
 	return strings.EqualFold("true", accessLog) || strings.EqualFold("default", accessLog)
 }
 
-// GetAccessLogFilter ...
+// GetAccessLogFilter return the instance of AccessLogFilter
 func GetAccessLogFilter() filter.Filter {
 	accessLogFilter := &AccessLogFilter{logChan: make(chan AccessLogData, LogMaxBuffer)}
 	go func() {
@@ -195,12 +214,13 @@
 	return accessLogFilter
 }
 
-// AccessLogData ...
+// AccessLogData defines the data that will be log into file
 type AccessLogData struct {
 	accessLog string
 	data      map[string]string
 }
 
+// toLogMessage convert the AccessLogData to String
 func (ef *AccessLogData) toLogMessage() string {
 	builder := strings.Builder{}
 	builder.WriteString("[")
diff --git a/filter/filter_impl/active_filter.go b/filter/filter_impl/active_filter.go
index 23f2c8e..795de96 100644
--- a/filter/filter_impl/active_filter.go
+++ b/filter/filter_impl/active_filter.go
@@ -39,11 +39,11 @@
 	extension.SetFilter(active, GetActiveFilter)
 }
 
-// ActiveFilter ...
+// ActiveFilter tracks the requests status
 type ActiveFilter struct {
 }
 
-// Invoke ...
+// Invoke starts to record the requests status
 func (ef *ActiveFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	logger.Infof("invoking active filter. %v,%v", invocation.MethodName(), len(invocation.Arguments()))
 	invocation.(*invocation2.RPCInvocation).SetAttachments(dubboInvokeStartTime, strconv.FormatInt(protocol.CurrentTimeMillis(), 10))
@@ -51,7 +51,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse update the active count base on the request result.
 func (ef *ActiveFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	startTime, err := strconv.ParseInt(invocation.(*invocation2.RPCInvocation).AttachmentsByKey(dubboInvokeStartTime, "0"), 10, 64)
 	if err != nil {
@@ -64,7 +64,7 @@
 	return result
 }
 
-// GetActiveFilter ...
+// GetActiveFilter creates ActiveFilter instance
 func GetActiveFilter() filter.Filter {
 	return &ActiveFilter{}
 }
diff --git a/filter/filter_impl/auth/accesskey_storage.go b/filter/filter_impl/auth/accesskey_storage.go
index 5adb9d9..90d3efb 100644
--- a/filter/filter_impl/auth/accesskey_storage.go
+++ b/filter/filter_impl/auth/accesskey_storage.go
@@ -25,13 +25,11 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// DefaultAccesskeyStorage
-// The default implementation of AccesskeyStorage
+// DefaultAccesskeyStorage is the default implementation of AccesskeyStorage
 type DefaultAccesskeyStorage struct {
 }
 
-// GetAccessKeyPair
-// get AccessKeyPair from url by the key "accessKeyId" and "secretAccessKey"
+// GetAccessKeyPair retrieves AccessKeyPair from url by the key "accessKeyId" and "secretAccessKey"
 func (storage *DefaultAccesskeyStorage) GetAccessKeyPair(invocation protocol.Invocation, url *common.URL) *filter.AccessKeyPair {
 	return &filter.AccessKeyPair{
 		AccessKey: url.GetParam(constant.ACCESS_KEY_ID_KEY, ""),
@@ -43,6 +41,7 @@
 	extension.SetAccesskeyStorages(constant.DEFAULT_ACCESS_KEY_STORAGE, GetDefaultAccesskeyStorage)
 }
 
+// GetDefaultAccesskeyStorage initiates an empty DefaultAccesskeyStorage
 func GetDefaultAccesskeyStorage() filter.AccessKeyStorage {
 	return &DefaultAccesskeyStorage{}
 }
diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go
index 0627447..945cf3e 100644
--- a/filter/filter_impl/auth/consumer_sign.go
+++ b/filter/filter_impl/auth/consumer_sign.go
@@ -29,8 +29,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// ConsumerSignFilter
-// This filter is working for signing the request on consumer side
+// ConsumerSignFilter signs the request on consumer side
 type ConsumerSignFilter struct {
 }
 
@@ -38,6 +37,7 @@
 	extension.SetFilter(constant.CONSUMER_SIGN_FILTER, getConsumerSignFilter)
 }
 
+// Invoke retrieves the configured Authenticator to add signature to invocation
 func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	logger.Infof("invoking ConsumerSign filter.")
 	url := invoker.GetUrl()
@@ -52,6 +52,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
+// OnResponse dummy process, returns the result directly
 func (csf *ConsumerSignFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go
index 2b8d559..5b86fc1 100644
--- a/filter/filter_impl/auth/default_authenticator.go
+++ b/filter/filter_impl/auth/default_authenticator.go
@@ -37,13 +37,11 @@
 	extension.SetAuthenticator(constant.DEFAULT_AUTHENTICATOR, GetDefaultAuthenticator)
 }
 
-// DefaultAuthenticator
-// The default implemetation of Authenticator
+// DefaultAuthenticator is the default implementation of Authenticator
 type DefaultAuthenticator struct {
 }
 
-// Sign
-// add the signature for the invocation
+// Sign adds the signature to the invocation
 func (authenticator *DefaultAuthenticator) Sign(invocation protocol.Invocation, url *common.URL) error {
 	currentTimeMillis := strconv.Itoa(int(time.Now().Unix() * 1000))
 
@@ -84,8 +82,7 @@
 	return signature, nil
 }
 
-// Authenticate
-// This method verifies whether the signature sent by the requester is correct
+// Authenticate verifies whether the signature sent by the requester is correct
 func (authenticator *DefaultAuthenticator) Authenticate(invocation protocol.Invocation, url *common.URL) error {
 	accessKeyId := invocation.AttachmentsByKey(constant.AK_KEY, "")
 
@@ -122,6 +119,7 @@
 	}
 }
 
+// GetDefaultAuthenticator creates an empty DefaultAuthenticator instance
 func GetDefaultAuthenticator() filter.Authenticator {
 	return &DefaultAuthenticator{}
 }
diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go
index 0d5772e..d5f5db3 100644
--- a/filter/filter_impl/auth/provider_auth.go
+++ b/filter/filter_impl/auth/provider_auth.go
@@ -29,8 +29,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// ProviderAuthFilter
-// This filter is used to verify the correctness of the signature on provider side
+// ProviderAuthFilter verifies the correctness of the signature on provider side
 type ProviderAuthFilter struct {
 }
 
@@ -38,6 +37,7 @@
 	extension.SetFilter(constant.PROVIDER_AUTH_FILTER, getProviderAuthFilter)
 }
 
+// Invoke retrieves the configured Authenticator to verify the signature in an invocation
 func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	logger.Infof("invoking providerAuth filter.")
 	url := invoker.GetUrl()
@@ -55,6 +55,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
+// OnResponse dummy process, returns the result directly
 func (paf *ProviderAuthFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go
index 043a549..45170bb 100644
--- a/filter/filter_impl/auth/sign_util.go
+++ b/filter/filter_impl/auth/sign_util.go
@@ -26,12 +26,12 @@
 	"strings"
 )
 
-// Sign
-// get a signature string with given information, such as metadata or parameters
+// Sign gets a signature string with given bytes
 func Sign(metadata, key string) string {
 	return doSign([]byte(metadata), key)
 }
 
+// SignWithParams returns a signature with giving params and metadata.
 func SignWithParams(params []interface{}, metadata, key string) (string, error) {
 	if params == nil || len(params) == 0 {
 		return Sign(metadata, key), nil
@@ -61,6 +61,7 @@
 	return base64.URLEncoding.EncodeToString(signature)
 }
 
+// IsEmpty verify whether the inputted string is empty
 func IsEmpty(s string, allowSpace bool) bool {
 	if len(s) == 0 {
 		return true
diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go
index a12800a..7da5ec7 100644
--- a/filter/filter_impl/echo_filter.go
+++ b/filter/filter_impl/echo_filter.go
@@ -38,13 +38,13 @@
 	extension.SetFilter(ECHO, GetFilter)
 }
 
-// EchoFilter
+// EchoFilter health check
 // RPCService need a Echo method in consumer, if you want to use EchoFilter
 // eg:
 //		Echo func(ctx context.Context, arg interface{}, rsp *Xxx) error
 type EchoFilter struct{}
 
-// Invoke ...
+// Invoke response to the callers with its first argument.
 func (ef *EchoFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	logger.Infof("invoking echo filter.")
 	logger.Debugf("%v,%v", invocation.MethodName(), len(invocation.Arguments()))
@@ -58,7 +58,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker,
 	_ protocol.Invocation) protocol.Result {
 
diff --git a/filter/filter_impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go
index 434c378..bfc5096 100644
--- a/filter/filter_impl/execute_limit_filter.go
+++ b/filter/filter_impl/execute_limit_filter.go
@@ -45,9 +45,8 @@
 	extension.SetFilter(name, GetExecuteLimitFilter)
 }
 
+// ExecuteLimitFilter  will limit the number of in-progress request and it's thread-safe.
 /**
- * ExecuteLimitFilter
- * The filter will limit the number of in-progress request and it's thread-safe.
  * example:
  * "UserProvider":
  *   registry: "hangzhouzk"
@@ -80,7 +79,7 @@
 	concurrentCount int64
 }
 
-// Invoke ...
+// Invoke judges whether the current processing requests over the threshold
 func (ef *ExecuteLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	methodConfigPrefix := "methods." + invocation.MethodName() + "."
 	ivkURL := invoker.GetUrl()
@@ -122,7 +121,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (ef *ExecuteLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result {
 	return result
 }
@@ -138,7 +137,7 @@
 var executeLimitOnce sync.Once
 var executeLimitFilter *ExecuteLimitFilter
 
-// GetExecuteLimitFilter ...
+// GetExecuteLimitFilter returns the singleton ExecuteLimitFilter instance
 func GetExecuteLimitFilter() filter.Filter {
 	executeLimitOnce.Do(func() {
 		executeLimitFilter = &ExecuteLimitFilter{
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go
index 9bc131e..3f4d714 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_filter.go
@@ -50,7 +50,7 @@
 // GenericFilter ...
 type GenericFilter struct{}
 
-// Invoke ...
+// Invoke turns the parameters to map for generic method
 func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
 		oldArguments := invocation.Arguments()
@@ -73,13 +73,13 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker,
 	_ protocol.Invocation) protocol.Result {
 	return result
 }
 
-// GetGenericFilter ...
+// GetGenericFilter returns GenericFilter instance
 func GetGenericFilter() filter.Filter {
 	return &GenericFilter{}
 }
diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go
index 2a331d7..f0bdb7f 100644
--- a/filter/filter_impl/generic_service_filter_test.go
+++ b/filter/filter_impl/generic_service_filter_test.go
@@ -96,7 +96,7 @@
 			hessian.Object("222")},
 	}
 	s := &TestService{}
-	_, _ = common.ServiceMap.Register("testprotocol", s)
+	_, _ = common.ServiceMap.Register("TestService", "testprotocol", s)
 	rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil)
 	filter := GetGenericServiceFilter()
 	url, _ := common.NewURL("testprotocol://127.0.0.1:20000/com.test.Path")
diff --git a/filter/filter_impl/graceful_shutdown_filter.go b/filter/filter_impl/graceful_shutdown_filter.go
index 95e625b..4a4e8ce 100644
--- a/filter/filter_impl/graceful_shutdown_filter.go
+++ b/filter/filter_impl/graceful_shutdown_filter.go
@@ -53,6 +53,7 @@
 	shutdownConfig *config.ShutdownConfig
 }
 
+// Invoke adds the requests count and block the new requests if application is closing
 func (gf *gracefulShutdownFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	if gf.rejectNewRequest() {
 		logger.Info("The application is closing, new request will be rejected.")
@@ -62,6 +63,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
+// OnResponse reduces the number of active processes then return the process result
 func (gf *gracefulShutdownFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	atomic.AddInt32(&gf.activeCount, -1)
 	// although this isn't thread safe, it won't be a problem if the gf.rejectNewRequest() is true.
diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go
index 9fd97b5..711ef71 100644
--- a/filter/filter_impl/hystrix_filter.go
+++ b/filter/filter_impl/hystrix_filter.go
@@ -55,14 +55,14 @@
 
 //The filter in the server end of dubbo-go can't get the invoke result for now,
 //this filter ONLY works in CLIENT end (consumer side) temporarily
-//Only after the callService logic is integrated into the filter chain of server end can this filter be used,
+//Only after the callService logic is integrated into the filter chain of server end then the filter can be used,
 //which will be done soon
 func init() {
 	extension.SetFilter(HYSTRIX_CONSUMER, GetHystrixFilterConsumer)
 	extension.SetFilter(HYSTRIX_PROVIDER, GetHystrixFilterProvider)
 }
 
-// HystrixFilterError ...
+// HystrixFilterError implements error interface
 type HystrixFilterError struct {
 	err           error
 	failByHystrix bool
@@ -72,12 +72,12 @@
 	return hfError.err.Error()
 }
 
-// FailByHystrix ...
+// FailByHystrix returns whether the fails causing by Hystrix
 func (hfError *HystrixFilterError) FailByHystrix() bool {
 	return hfError.failByHystrix
 }
 
-// NewHystrixFilterError ...
+// NewHystrixFilterError return a HystrixFilterError instance
 func NewHystrixFilterError(err error, failByHystrix bool) error {
 	return &HystrixFilterError{
 		err:           err,
@@ -92,7 +92,7 @@
 	ifNewMap sync.Map
 }
 
-// Invoke ...
+// Invoke is an implentation 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())
 
@@ -124,7 +124,7 @@
 	_, _, err := hystrix.GetCircuit(cmdName)
 	configLoadMutex.RUnlock()
 	if err != nil {
-		logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: ", cmdName, err)
+		logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: %+v", cmdName, err)
 		return invoker.Invoke(ctx, invocation)
 	}
 	logger.Infof("[Hystrix Filter]Using hystrix filter: %s", cmdName)
@@ -154,12 +154,12 @@
 	return result
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (hf *HystrixFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
 
-// GetHystrixFilterConsumer ...
+// GetHystrixFilterConsumer returns HystrixFilter instance for consumer
 func GetHystrixFilterConsumer() filter.Filter {
 	//When first called, load the config in
 	consumerConfigOnce.Do(func() {
@@ -170,7 +170,7 @@
 	return &HystrixFilter{COrP: true}
 }
 
-// GetHystrixFilterProvider ...
+// GetHystrixFilterProvider returns HystrixFilter instance for provider
 func GetHystrixFilterProvider() filter.Filter {
 	providerConfigOnce.Do(func() {
 		if err := initHystrixConfigProvider(); err != nil {
diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go
index 8ec3929..23742c6 100644
--- a/filter/filter_impl/token_filter.go
+++ b/filter/filter_impl/token_filter.go
@@ -42,10 +42,10 @@
 	extension.SetFilter(TOKEN, GetTokenFilter)
 }
 
-// TokenFilter ...
+// TokenFilter will verify if the token is valid
 type TokenFilter struct{}
 
-// Invoke ...
+// Invoke verifies the incoming token with the service configured token
 func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	invokerTkn := invoker.GetUrl().GetParam(constant.TOKEN_KEY, "")
 	if len(invokerTkn) > 0 {
@@ -61,7 +61,7 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go
index 70ea173..c2f69bd 100644
--- a/filter/filter_impl/token_filter_test.go
+++ b/filter/filter_impl/token_filter_test.go
@@ -53,10 +53,10 @@
 func TestTokenFilterInvokeEmptyToken(t *testing.T) {
 	filter := GetTokenFilter()
 
-	url := common.URL{}
+	testUrl := common.URL{}
 	attch := make(map[string]string, 0)
 	attch[constant.TOKEN_KEY] = "ori_key"
-	result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
+	result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
 	assert.Nil(t, result.Error())
 	assert.Nil(t, result.Result())
 }
@@ -64,23 +64,23 @@
 func TestTokenFilterInvokeEmptyAttach(t *testing.T) {
 	filter := GetTokenFilter()
 
-	url := common.NewURLWithOptions(
+	testUrl := common.NewURLWithOptions(
 		common.WithParams(url.Values{}),
 		common.WithParamsValue(constant.TOKEN_KEY, "ori_key"))
 	attch := make(map[string]string, 0)
-	result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
+	result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
 	assert.NotNil(t, result.Error())
 }
 
 func TestTokenFilterInvokeNotEqual(t *testing.T) {
 	filter := GetTokenFilter()
 
-	url := common.NewURLWithOptions(
+	testUrl := common.NewURLWithOptions(
 		common.WithParams(url.Values{}),
 		common.WithParamsValue(constant.TOKEN_KEY, "ori_key"))
 	attch := make(map[string]string, 0)
 	attch[constant.TOKEN_KEY] = "err_key"
 	result := filter.Invoke(context.Background(),
-		protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
+		protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch))
 	assert.NotNil(t, result.Error())
 }
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 a9c2ac1..7419a45 100644
--- a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go
+++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go
@@ -39,8 +39,8 @@
 	extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, creator)
 }
 
+// FixedWindowTpsLimitStrategyImpl implements the TPS limit strategy base on requests count during the interval
 /**
- * FixedWindowTpsLimitStrategyImpl
  * It's the same as default implementation in Java
  * It's not a thread-safe implementation.
  * It you want to use the thread-safe implementation, please use ThreadSafeFixedWindowTpsLimitStrategyImpl
@@ -65,7 +65,8 @@
 	timestamp int64
 }
 
-// IsAllowable ...
+// IsAllowable determines if the requests over the TPS limit within the interval.
+// It is not thread-safe.
 func (impl *FixedWindowTpsLimitStrategyImpl) IsAllowable() bool {
 
 	current := time.Now().UnixNano()
@@ -82,6 +83,7 @@
 
 type fixedWindowStrategyCreator struct{}
 
+// Create returns a FixedWindowTpsLimitStrategyImpl instance with pre-configured limit rate and interval
 func (creator *fixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy {
 	return &FixedWindowTpsLimitStrategyImpl{
 		rate:      int32(rate),
diff --git a/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go
index a781cc7..cbbba19 100644
--- a/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go
+++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go
@@ -32,8 +32,8 @@
 	extension.SetTpsLimitStrategy("slidingWindow", &slidingWindowStrategyCreator{})
 }
 
+// SlidingWindowTpsLimitStrategyImpl implements a thread-safe TPS limit strategy base on requests count.
 /**
- * SlidingWindowTpsLimitStrategyImpl
  * it's thread-safe.
  * "UserProvider":
  *   registry: "hangzhouzk"
@@ -54,7 +54,8 @@
 	queue    *list.List
 }
 
-// IsAllowable ...
+// IsAllowable determins whether the number of requests within the time window overs the threshold
+// It is thread-safe.
 func (impl *SlidingWindowTpsLimitStrategyImpl) IsAllowable() bool {
 	impl.mutex.Lock()
 	defer impl.mutex.Unlock()
@@ -84,6 +85,7 @@
 
 type slidingWindowStrategyCreator struct{}
 
+// Create returns SlidingWindowTpsLimitStrategyImpl instance with configured limit rate and interval
 func (creator *slidingWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy {
 	return &SlidingWindowTpsLimitStrategyImpl{
 		rate:     rate,
diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go
index 18809fc..c228c73 100644
--- a/filter/filter_impl/tps/tps_limit_strategy_mock.go
+++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go
@@ -15,22 +15,6 @@
  * limitations under the License.
  */
 
-//  Licensed to the Apache Software Foundation (ASF) under one or more
-//  contributor license agreements.  See the NOTICE file distributed with
-//  this work for additional information regarding copyright ownership.
-//  The ASF licenses this file to You under the Apache License, Version 2.0
-//  (the "License"); you may not use this file except in compliance with
-//  the License.  You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//  Unless required by applicable law or agreed to in writing, software
-//  distributed under the License is distributed on an "AS IS" BASIS,
-//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//  See the License for the specific language governing permissions and
-//  limitations under the License.
-//
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: tps_limit_strategy.go
 
diff --git a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go
index 1662483..f78cd82 100644
--- a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go
+++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go
@@ -32,10 +32,9 @@
 	})
 }
 
+// ThreadSafeFixedWindowTpsLimitStrategyImpl is the thread-safe implementation.
+// It's also a thread-safe decorator of FixedWindowTpsLimitStrategyImpl
 /**
- * ThreadSafeFixedWindowTpsLimitStrategyImpl
- * it's the thread-safe implementation.
- * Also, it's a thread-safe decorator of FixedWindowTpsLimitStrategyImpl
  * "UserProvider":
  *   registry: "hangzhouzk"
  *   protocol : "dubbo"
@@ -53,7 +52,7 @@
 	fixedWindow *FixedWindowTpsLimitStrategyImpl
 }
 
-// IsAllowable ...
+// IsAllowable implements thread-safe then run the FixedWindowTpsLimitStrategy
 func (impl *ThreadSafeFixedWindowTpsLimitStrategyImpl) IsAllowable() bool {
 	impl.mutex.Lock()
 	defer impl.mutex.Unlock()
@@ -64,6 +63,7 @@
 	fixedWindowStrategyCreator *fixedWindowStrategyCreator
 }
 
+// Create returns ThreadSafeFixedWindowTpsLimitStrategyImpl instance
 func (creator *threadSafeFixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy {
 	fixedWindowStrategy := creator.fixedWindowStrategyCreator.Create(rate, interval).(*FixedWindowTpsLimitStrategyImpl)
 	return &ThreadSafeFixedWindowTpsLimitStrategyImpl{
diff --git a/filter/filter_impl/tps/tps_limiter_method_service.go b/filter/filter_impl/tps/tps_limiter_method_service.go
index 7fe8de9..5761579 100644
--- a/filter/filter_impl/tps/tps_limiter_method_service.go
+++ b/filter/filter_impl/tps/tps_limiter_method_service.go
@@ -44,9 +44,8 @@
 	extension.SetTpsLimiter(name, GetMethodServiceTpsLimiter)
 }
 
+// MethodServiceTpsLimiterImpl allows developer to config both method-level and service-level tps limiter.
 /**
- * MethodServiceTpsLimiterImpl
- * This implementation allows developer to config both method-level and service-level tps limiter.
  * for example:
  * "UserProvider":
  *   registry: "hangzhouzk"
@@ -115,7 +114,12 @@
 	tpsState *concurrent.Map
 }
 
-// IsAllowable ...
+// IsAllowable based on method-level and service-level.
+// The method-level has high priority which means that if there is any rate limit configuration for the method,
+// the service-level rate limit strategy will be ignored.
+// The key point is how to keep thread-safe
+// This implementation use concurrent map + loadOrStore to make implementation thread-safe
+// You can image that even multiple threads create limiter, but only one could store the limiter into tpsState
 func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocation protocol.Invocation) bool {
 
 	methodConfigPrefix := "methods." + invocation.MethodName() + "."
@@ -123,23 +127,30 @@
 	methodLimitRateConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_RATE_KEY, "")
 	methodIntervalConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_INTERVAL_KEY, "")
 
+	// service-level tps limit
 	limitTarget := url.ServiceKey()
 
 	// method-level tps limit
 	if len(methodIntervalConfig) > 0 || len(methodLimitRateConfig) > 0 {
+		// it means that if the method-level rate limit exist, we will use method-level rate limit strategy
 		limitTarget = limitTarget + "#" + invocation.MethodName()
 	}
 
+	// looking up the limiter from 'cache'
 	limitState, found := limiter.tpsState.Load(limitTarget)
 	if found {
+		// the limiter has been cached, we return its result
 		return limitState.(filter.TpsLimitStrategy).IsAllowable()
 	}
 
+	// we could not find the limiter, and try to create one.
+
 	limitRate := getLimitConfig(methodLimitRateConfig, url, invocation,
 		constant.TPS_LIMIT_RATE_KEY,
 		constant.DEFAULT_TPS_LIMIT_RATE)
 
 	if limitRate < 0 {
+		// the limitTarget is not necessary to be limited.
 		return true
 	}
 
@@ -150,13 +161,20 @@
 		panic(fmt.Sprintf("The interval must be positive, please check your configuration! url: %s", url.String()))
 	}
 
+	// find the strategy config and then create one
 	limitStrategyConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_STRATEGY_KEY,
 		url.GetParam(constant.TPS_LIMIT_STRATEGY_KEY, constant.DEFAULT_KEY))
 	limitStateCreator := extension.GetTpsLimitStrategyCreator(limitStrategyConfig)
+
+	// we using loadOrStore to ensure thread-safe
 	limitState, _ = limiter.tpsState.LoadOrStore(limitTarget, limitStateCreator.Create(int(limitRate), int(limitInterval)))
+
 	return limitState.(filter.TpsLimitStrategy).IsAllowable()
 }
 
+// getLimitConfig will try to fetch the configuration from url.
+// If we can convert the methodLevelConfig to int64, return;
+// Or, we will try to look up server-level configuration and then convert it to int64
 func getLimitConfig(methodLevelConfig string,
 	url common.URL,
 	invocation protocol.Invocation,
@@ -172,6 +190,8 @@
 		return result
 	}
 
+	// actually there is no method-level configuration, so we use the service-level configuration
+
 	result, err := strconv.ParseInt(url.GetParam(configKey, defaultVal), 0, 0)
 
 	if err != nil {
@@ -183,7 +203,7 @@
 var methodServiceTpsLimiterInstance *MethodServiceTpsLimiterImpl
 var methodServiceTpsLimiterOnce sync.Once
 
-// GetMethodServiceTpsLimiter ...
+// GetMethodServiceTpsLimiter will return an MethodServiceTpsLimiterImpl instance.
 func GetMethodServiceTpsLimiter() filter.TpsLimiter {
 	methodServiceTpsLimiterOnce.Do(func() {
 		methodServiceTpsLimiterInstance = &MethodServiceTpsLimiterImpl{
diff --git a/filter/filter_impl/tps/tps_limiter_mock.go b/filter/filter_impl/tps/tps_limiter_mock.go
index 131a2e5..b49084f 100644
--- a/filter/filter_impl/tps/tps_limiter_mock.go
+++ b/filter/filter_impl/tps/tps_limiter_mock.go
@@ -15,22 +15,6 @@
  * limitations under the License.
  */
 
-//  Licensed to the Apache Software Foundation (ASF) under one or more
-//  contributor license agreements.  See the NOTICE file distributed with
-//  this work for additional information regarding copyright ownership.
-//  The ASF licenses this file to You under the Apache License, Version 2.0
-//  (the "License"); you may not use this file except in compliance with
-//  the License.  You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//  Unless required by applicable law or agreed to in writing, software
-//  distributed under the License is distributed on an "AS IS" BASIS,
-//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//  See the License for the specific language governing permissions and
-//  limitations under the License.
-//
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: tps_limiter.go
 
diff --git a/filter/filter_impl/tps_limit_filter.go b/filter/filter_impl/tps_limit_filter.go
index fa78288..ea1e3bc 100644
--- a/filter/filter_impl/tps_limit_filter.go
+++ b/filter/filter_impl/tps_limit_filter.go
@@ -39,8 +39,8 @@
 	extension.SetFilter(TpsLimitFilterKey, GetTpsLimitFilter)
 }
 
+// TpsLimitFilter filters the requests by TPS
 /**
- * TpsLimitFilter
  * if you wish to use the TpsLimiter, please add the configuration into your service provider configuration:
  * for example:
  * "UserProvider":
@@ -56,7 +56,7 @@
 type TpsLimitFilter struct {
 }
 
-// Invoke ...
+// Invoke gets the configured limter to impose TPS limiting
 func (t TpsLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	url := invoker.GetUrl()
 	tpsLimiter := url.GetParam(constant.TPS_LIMITER_KEY, "")
@@ -72,13 +72,13 @@
 	return invoker.Invoke(ctx, invocation)
 }
 
-// OnResponse ...
+// OnResponse dummy process, returns the result directly
 func (t TpsLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker,
 	_ protocol.Invocation) protocol.Result {
 	return result
 }
 
-// GetTpsLimitFilter ...
+// GetTpsLimitFilter returns an TpsLimitFilter instance.
 func GetTpsLimitFilter() filter.Filter {
 	return &TpsLimitFilter{}
 }
diff --git a/filter/handler/rejected_execution_handler_mock.go b/filter/handler/rejected_execution_handler_mock.go
index 469c06b..bff5476 100644
--- a/filter/handler/rejected_execution_handler_mock.go
+++ b/filter/handler/rejected_execution_handler_mock.go
@@ -15,22 +15,6 @@
  * limitations under the License.
  */
 
-//  Licensed to the Apache Software Foundation (ASF) under one or more
-//  contributor license agreements.  See the NOTICE file distributed with
-//  this work for additional information regarding copyright ownership.
-//  The ASF licenses this file to You under the Apache License, Version 2.0
-//  (the "License"); you may not use this file except in compliance with
-//  the License.  You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//  Unless required by applicable law or agreed to in writing, software
-//  distributed under the License is distributed on an "AS IS" BASIS,
-//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//  See the License for the specific language governing permissions and
-//  limitations under the License.
-//
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: rejected_execution_handler.go
 
diff --git a/filter/handler/rejected_execution_handler_only_log.go b/filter/handler/rejected_execution_handler_only_log.go
index 0f9003c..52ac176 100644
--- a/filter/handler/rejected_execution_handler_only_log.go
+++ b/filter/handler/rejected_execution_handler_only_log.go
@@ -36,6 +36,7 @@
 )
 
 func init() {
+	// this implementation is the the default implementation of RejectedExecutionHandler
 	extension.SetRejectedExecutionHandler(HandlerName, GetOnlyLogRejectedExecutionHandler)
 	extension.SetRejectedExecutionHandler(constant.DEFAULT_KEY, GetOnlyLogRejectedExecutionHandler)
 }
@@ -43,8 +44,8 @@
 var onlyLogHandlerInstance *OnlyLogRejectedExecutionHandler
 var onlyLogHandlerOnce sync.Once
 
+// OnlyLogRejectedExecutionHandler implements the RejectedExecutionHandler
 /**
- * OnlyLogRejectedExecutionHandler
  * This implementation only logs the invocation info.
  * it always return en error inside the result.
  * "UserProvider":
@@ -56,11 +57,12 @@
  *   tps.limit.rejected.handler: "default" or "log"
  *   methods:
  *    - name: "GetUser"
+ * OnlyLogRejectedExecutionHandler is designed to be singleton
  */
 type OnlyLogRejectedExecutionHandler struct {
 }
 
-// RejectedExecution ...
+// RejectedExecution will do nothing, it only log the invocation.
 func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL,
 	_ protocol.Invocation) protocol.Result {
 
@@ -68,7 +70,7 @@
 	return &protocol.RPCResult{}
 }
 
-// GetOnlyLogRejectedExecutionHandler ...
+// GetOnlyLogRejectedExecutionHandler will return the instance of OnlyLogRejectedExecutionHandler
 func GetOnlyLogRejectedExecutionHandler() filter.RejectedExecutionHandler {
 	onlyLogHandlerOnce.Do(func() {
 		onlyLogHandlerInstance = &OnlyLogRejectedExecutionHandler{}
diff --git a/filter/rejected_execution_handler.go b/filter/rejected_execution_handler.go
index caeea1d..3d1e1c1 100644
--- a/filter/rejected_execution_handler.go
+++ b/filter/rejected_execution_handler.go
@@ -22,8 +22,8 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// RejectedExecutionHandler defines the handler to handle exceptions from invoking filters.
 /**
- * RejectedExecutionHandler
  * If the invocation cannot pass any validation in filter, like ExecuteLimitFilter and TpsLimitFilter,
  * the implementation will be used.
  * The common case is that sometimes you want to return the default value when the request was rejected.
@@ -31,5 +31,7 @@
  * In such situation, implement this interface and register it by invoking extension.SetRejectedExecutionHandler.
  */
 type RejectedExecutionHandler interface {
+
+	// RejectedExecution will be called if the invocation was rejected by some component.
 	RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result
 }
diff --git a/filter/tps_limit_strategy.go b/filter/tps_limit_strategy.go
index 5edf32c..2ee876a 100644
--- a/filter/tps_limit_strategy.go
+++ b/filter/tps_limit_strategy.go
@@ -17,8 +17,8 @@
 
 package filter
 
+// TpsLimitStrategy defines how to do the TPS limiting in method level.
 /*
- * TpsLimitStrategy
  * please register your implementation by invoking SetTpsLimitStrategy
  * "UserProvider":
  *   registry: "hangzhouzk"
@@ -33,10 +33,16 @@
  *      tps.limit.strategy: "name of implementation" # method-level
  */
 type TpsLimitStrategy interface {
+	// IsAllowable will return true if this invocation is not over limitation
 	IsAllowable() bool
 }
 
-// TpsLimitStrategyCreator ...
+// TpsLimitStrategyCreator is the creator abstraction for TpsLimitStrategy
 type TpsLimitStrategyCreator interface {
-	Create(rate int, interval int) TpsLimitStrategy
+	// Create will create an instance of TpsLimitStrategy
+	// It will be a little hard to understand this method.
+	// The unit of interval is ms
+	// for example, if the limit = 100, interval = 1000
+	// which means that the tps limitation is 100 times per 1000ms (100/1000ms)
+	Create(limit int, interval int) TpsLimitStrategy
 }
diff --git a/filter/tps_limiter.go b/filter/tps_limiter.go
index dbc9f76..8385d7b 100644
--- a/filter/tps_limiter.go
+++ b/filter/tps_limiter.go
@@ -22,8 +22,8 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
+// TpsLimiter defines the Limiter that judge if the TPS overs the threshold
 /*
- * TpsLimiter
  * please register your implementation by invoking SetTpsLimiter
  * The usage, for example:
  * "UserProvider":
@@ -34,5 +34,6 @@
  *   tps.limiter: "the name of limiter",
  */
 type TpsLimiter interface {
+	// IsAllowable will check whether this invocation should be enabled for further process
 	IsAllowable(common.URL, protocol.Invocation) bool
 }
diff --git a/go.mod b/go.mod
index 83091cf..e82a04b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,21 +3,20 @@
 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.4.0
-	github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
+	github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect
+	github.com/apache/dubbo-go-hessian2 v1.6.1
+	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	github.com/coreos/bbolt v1.3.3 // indirect
 	github.com/coreos/etcd v3.3.13+incompatible
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
 	github.com/creasty/defaults v1.3.0
-	github.com/dubbogo/getty v1.3.3
-	github.com/dubbogo/go-zookeeper v1.0.0
-	github.com/dubbogo/gost v1.5.2
+	github.com/dubbogo/getty v1.3.7
+	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,32 +28,28 @@
 	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/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/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
+	github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect
 	github.com/magiconair/properties v1.8.1
 	github.com/mitchellh/mapstructure v1.1.2
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
-	github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb
+	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.8.1
+	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.3 // indirect
-	go.etcd.io/etcd v3.3.13+incompatible
-	go.uber.org/atomic v1.4.0
-	go.uber.org/zap v1.10.0
+	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 813496b..8c8c4ef 100644
--- a/go.sum
+++ b/go.sum
@@ -35,11 +35,18 @@
 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.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA=
-github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
+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=
@@ -106,13 +113,15 @@
 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.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs=
-github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0=
-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/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.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.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8=
-github.com/dubbogo/gost v1.5.2/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=
 github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo=
 github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y=
@@ -136,6 +145,8 @@
 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/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=
@@ -196,6 +208,7 @@
 github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
 github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
@@ -206,6 +219,8 @@
 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=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
@@ -322,6 +337,12 @@
 github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg=
+github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
+github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0=
+github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b h1:VE6r2OwP5gj+Z9aCkSKl3MlmnZbfMAjhvR5T7abKHEo=
 github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
@@ -381,8 +402,10 @@
 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-20190723125407-0242d42e3dbb h1:lbmvw8r9W55w+aQgWn35W1nuleRIECMoqUrmwAOAvoI=
-github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb/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=
@@ -390,10 +413,14 @@
 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=
@@ -416,6 +443,8 @@
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -441,11 +470,15 @@
 github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o=
 github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 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=
@@ -496,23 +529,32 @@
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg=
 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY=
-go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw=
-go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
+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/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=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
+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/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=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -521,10 +563,14 @@
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+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=
@@ -533,6 +579,8 @@
 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=
@@ -542,10 +590,13 @@
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -561,6 +612,10 @@
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 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/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=
 google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
@@ -578,6 +633,7 @@
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
@@ -602,6 +658,7 @@
 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/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=
 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 h1:9MWtbqhwTyDvF4cS1qAhxDb9Mi8taXiAu+5nEacl7gY=
diff --git a/integrate_test.sh b/integrate_test.sh
new file mode 100644
index 0000000..c9c2f23
--- /dev/null
+++ b/integrate_test.sh
@@ -0,0 +1,66 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+#!/bin/bash
+
+set -e
+set -x
+
+echo 'start integrate-test'
+
+# set root workspace
+ROOT_DIR=$(pwd)
+echo "integrate-test root work-space -> ${ROOT_DIR}"
+
+# show all travis-env
+echo "travis current commit id  -> ${TRAVIS_COMMIT}"
+echo "travis pull request -> ${TRAVIS_PULL_REQUEST}"
+echo "travis pull request branch -> ${TRAVIS_PULL_REQUEST_BRANCH}"
+echo "travis pull request slug -> ${TRAVIS_PULL_REQUEST_SLUG}"
+echo "travis pull request sha -> ${TRAVIS_PULL_REQUEST_SHA}"
+echo "travis pull request repo slug -> ${TRAVIS_REPO_SLUG}"
+
+
+# #start etcd registry  insecure listen in [:]:2379
+# docker run -d --network host k8s.gcr.io/etcd:3.3.10 etcd
+# echo "etcdv3 listen in [:]2379"
+
+# #start consul registry insecure listen in [:]:8500
+# docker run -d --network host consul
+# echo "consul listen in [:]8500"
+
+# #start nacos registry insecure listen in [:]:8848
+# docker run -d --network host nacos/nacos-server:latest
+# echo "ncacos listen in [:]8848"
+
+# default use zk as registry
+#start zookeeper registry insecure listen in [:]:2181
+docker run -d --network host zookeeper
+echo "zookeeper listen in [:]2181"
+
+# build go-server image
+cd ./test/integrate/dubbo/go-server
+docker build . -t  ci-provider --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA}
+cd ${ROOT_DIR}
+docker run -d --network host ci-provider
+
+# build go-client image
+cd ./test/integrate/dubbo/go-client
+docker build . -t  ci-consumer --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA}
+cd ${ROOT_DIR}
+# run provider
+# check consumer status
+docker run -it --network host ci-consumer
diff --git a/metadata/definition/definition.go b/metadata/definition/definition.go
new file mode 100644
index 0000000..dbbc0c8
--- /dev/null
+++ b/metadata/definition/definition.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 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
+	Methods       []MethodDefinition
+	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
+	ReturnType     string
+	Parameters     []TypeDefinition
+}
+
+// TypeDefinition is the describer of type definition
+type TypeDefinition struct {
+	Id              string
+	Type            string
+	Items           []TypeDefinition
+	Enums           []string
+	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 0000000..958f932
--- /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 0000000..ca9e125
--- /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
new file mode 100644
index 0000000..2371f7c
--- /dev/null
+++ b/metadata/identifier/base_metadata_identifier.go
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+import (
+	"encoding/base64"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+// BaseMetadataIdentifier defined for describe the Metadata base identify
+type IMetadataIdentifier interface {
+	GetFilePathKey() string
+	GetIdentifierKey() string
+}
+
+// BaseMetadataIdentifier is the base implement of BaseMetadataIdentifier interface
+type BaseMetadataIdentifier struct {
+	ServiceInterface string
+	Version          string
+	Group            string
+	Side             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 {
+		joinedStr += joinChar
+		joinedStr += param
+	}
+	return joinedStr
+}
+
+// getIdentifierKey returns string that format is service:Version:Group:Side:param1:param2...
+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 *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) +
+		joinParams(constant.PATH_SEPARATOR, params)
+
+}
+
+// serviceToPath uss URL encode to decode the @serviceInterface
+func serviceToPath(serviceInterface string) string {
+	if serviceInterface == constant.ANY_VALUE {
+		return ""
+	} else {
+		decoded, err := base64.URLEncoding.DecodeString(serviceInterface)
+		if err != nil {
+			return ""
+		}
+		return string(decoded)
+	}
+
+}
+
+// withPathSeparator return "/" + @path
+func withPathSeparator(path string) string {
+	if len(path) != 0 {
+		path = constant.PATH_SEPARATOR + path
+	}
+	return path
+}
diff --git a/metadata/identifier/base_metadata_identifier_test.go b/metadata/identifier/base_metadata_identifier_test.go
new file mode 100644
index 0000000..5b60992
--- /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
new file mode 100644
index 0000000..7e50c4c
--- /dev/null
+++ b/metadata/identifier/metadata_identifier.go
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+// MetadataIdentifier is inherit baseMetaIdentifier with Application name
+type MetadataIdentifier struct {
+	Application string
+	BaseMetadataIdentifier
+}
+
+// GetIdentifierKey returns string that format is service:Version:Group:Side: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() 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 0000000..cba3c0d
--- /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
new file mode 100644
index 0000000..b9e6596
--- /dev/null
+++ b/metadata/identifier/service_metadata_identifier.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 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
+	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() 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() 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 0000000..d7ef44a
--- /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
new file mode 100644
index 0000000..b1e37db
--- /dev/null
+++ b/metadata/identifier/subscribe_metadata_identifier.go
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package identifier
+
+// SubscriberMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision
+type SubscriberMetadataIdentifier struct {
+	Revision string
+	MetadataIdentifier
+}
+
+// GetIdentifierKey returns string that format is service:Version:Group:Side: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() 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 0000000..215aa3c
--- /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/mapping/dynamic/service_name_mapping.go b/metadata/mapping/dynamic/service_name_mapping.go
new file mode 100644
index 0000000..84039ac
--- /dev/null
+++ b/metadata/mapping/dynamic/service_name_mapping.go
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dynamic
+
+import (
+	"strconv"
+	"sync"
+	"time"
+)
+
+import (
+	"github.com/dubbogo/gost/container/set"
+	perrors "github.com/pkg/errors"
+)
+
+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/mapping"
+)
+
+const (
+	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
+}
+
+// Map will map the service to this application-level service
+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 {
+		logger.Info("try to map the metadata service, will be ignored")
+		return nil
+	}
+
+	appName := config.GetApplicationConfig().Name
+	value := time.Now().UnixNano()
+
+	err := d.dc.PublishConfig(appName,
+		d.buildGroup(serviceInterface),
+		strconv.FormatInt(value, 10))
+	if err != nil {
+		return perrors.WithStack(err)
+	}
+	return nil
+}
+
+// Get will return the application-level services. If not found, the empty set will be returned.
+// if the dynamic configuration got error, the error will return
+func (d *DynamicConfigurationServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) {
+	return d.dc.GetConfigKeysByGroup(d.buildGroup(serviceInterface))
+}
+
+// buildGroup will return group, now it looks like defaultGroup/serviceInterface
+func (d *DynamicConfigurationServiceNameMapping) buildGroup(serviceInterface string) string {
+	// the issue : https://github.com/apache/dubbo/issues/4671
+	// so other params are ignored and remove, including group string, version string, protocol string
+	return defaultGroup + slash + serviceInterface
+}
+
+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/mapping/dynamic/service_name_mapping_test.go b/metadata/mapping/dynamic/service_name_mapping_test.go
new file mode 100644
index 0000000..2896b0f
--- /dev/null
+++ b/metadata/mapping/dynamic/service_name_mapping_test.go
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dynamic
+
+import (
+	"testing"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/config_center"
+)
+
+func TestDynamicConfigurationServiceNameMapping(t *testing.T) {
+
+	// mock data
+	appName := "myApp"
+	dc, err := (&config_center.MockDynamicConfigurationFactory{
+		Content: appName,
+	}).GetDynamicConfiguration(nil)
+	config.GetApplicationConfig().Name = appName
+
+	mapping := &DynamicConfigurationServiceNameMapping{dc: dc}
+	intf := constant.METADATA_SERVICE_NAME
+	group := "myGroup"
+	version := "myVersion"
+	protocol := "myProtocol"
+
+	err = mapping.Map(intf, group, version, protocol)
+	assert.Nil(t, err)
+	intf = "MyService"
+	err = mapping.Map(intf, group, version, protocol)
+	assert.Nil(t, err)
+
+	var result *gxset.HashSet
+	result, err = mapping.Get(intf, group, version, protocol)
+	assert.Nil(t, err)
+	assert.Equal(t, 1, result.Size())
+	assert.True(t, result.Contains(appName))
+}
diff --git a/metadata/mapping/memory/service_name_mapping.go b/metadata/mapping/memory/service_name_mapping.go
new file mode 100644
index 0000000..0965d52
--- /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/mapping/service_name_mapping.go b/metadata/mapping/service_name_mapping.go
new file mode 100644
index 0000000..6caed9f
--- /dev/null
+++ b/metadata/mapping/service_name_mapping.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 mapping
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+)
+
+// ServiceNameMapping try to build the mapping between application-level service and interface-level service.
+type ServiceNameMapping interface {
+
+	// Map will map the service to this application-level service
+	Map(serviceInterface string, group string, version string, protocol string) error
+
+	// Get will return the application-level services
+	Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error)
+}
diff --git a/metadata/report/consul/report.go b/metadata/report/consul/report.go
new file mode 100644
index 0000000..eb2bdc2
--- /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 0000000..34ee29d
--- /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 0000000..cdd29ab
--- /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 0000000..3dfca57
--- /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 0000000..097835c
--- /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 0000000..5dd8780
--- /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/report_factory.go b/metadata/report/factory/report_factory.go
new file mode 100644
index 0000000..9f00007
--- /dev/null
+++ b/metadata/report/factory/report_factory.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 factory
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/metadata/report"
+)
+
+// MetadataReportFactory interface will create metadata report
+type MetadataReportFactory interface {
+	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 0000000..d69913b
--- /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 0000000..be01eb2
--- /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 0000000..62a9055
--- /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 0000000..8f46bb0
--- /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 0000000..a1e46e2
--- /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/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go
new file mode 100644
index 0000000..f8b4b0c
--- /dev/null
+++ b/metadata/service/exporter/configurable/exporter.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configurable
+
+import (
+	"context"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/metadata/service"
+	"github.com/apache/dubbo-go/metadata/service/exporter"
+)
+
+// MetadataServiceExporter is the ConfigurableMetadataServiceExporter which implement MetadataServiceExporter interface
+type MetadataServiceExporter struct {
+	ServiceConfig   *config.ServiceConfig
+	lock            sync.RWMutex
+	metadataService service.MetadataService
+}
+
+// NewMetadataServiceExporter will return a service_exporter.MetadataServiceExporter with the specified  metadata service
+func NewMetadataServiceExporter(metadataService service.MetadataService) exporter.MetadataServiceExporter {
+	return &MetadataServiceExporter{
+		metadataService: metadataService,
+	}
+}
+
+// Export will export the metadataService
+func (exporter *MetadataServiceExporter) Export() error {
+	if !exporter.IsExported() {
+
+		serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background())
+		serviceConfig.Protocol = constant.DEFAULT_PROTOCOL
+		serviceConfig.Protocols = map[string]*config.ProtocolConfig{
+			constant.DEFAULT_PROTOCOL: generateMetadataProtocol(),
+		}
+		serviceConfig.InterfaceName = constant.METADATA_SERVICE_NAME
+		// identify this is a golang server
+		serviceConfig.Params = map[string]string{}
+		serviceConfig.Group = config.GetApplicationConfig().Name
+		// now the error will always be nil
+		serviceConfig.Version, _ = exporter.metadataService.Version()
+
+		var err error
+		func() {
+			exporter.lock.Lock()
+			defer exporter.lock.Unlock()
+			exporter.ServiceConfig = serviceConfig
+			exporter.ServiceConfig.Implement(exporter.metadataService)
+			err = exporter.ServiceConfig.Export()
+		}()
+
+		logger.Infof("The MetadataService exports urls : %v ", exporter.ServiceConfig.GetExportedUrls())
+		return err
+	}
+	logger.Warnf("The MetadataService has been exported : %v ", exporter.ServiceConfig.GetExportedUrls())
+	return nil
+}
+
+// Unexport will unexport the metadataService
+func (exporter *MetadataServiceExporter) Unexport() {
+	if exporter.IsExported() {
+		exporter.ServiceConfig.Unexport()
+	}
+}
+
+// GetExportedURLs will return the urls that export use.
+// Notice!The exported url is not same as url in registry , for example it lack the ip.
+func (exporter *MetadataServiceExporter) GetExportedURLs() []*common.URL {
+	return exporter.ServiceConfig.GetExportedUrls()
+}
+
+// isExported will return is metadataServiceExporter exported or not
+func (exporter *MetadataServiceExporter) IsExported() bool {
+	exporter.lock.RLock()
+	defer exporter.lock.RUnlock()
+	return exporter.ServiceConfig != nil && exporter.ServiceConfig.IsExport()
+}
+
+// generateMetadataProtocol will return a default ProtocolConfig
+func generateMetadataProtocol() *config.ProtocolConfig {
+	return &config.ProtocolConfig{
+		Name: constant.DEFAULT_PROTOCOL,
+		Port: "20000",
+	}
+}
diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go
new file mode 100644
index 0000000..4689c66
--- /dev/null
+++ b/metadata/service/exporter/configurable/exporter_test.go
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configurable
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
+	"github.com/apache/dubbo-go/config"
+	_ "github.com/apache/dubbo-go/filter/filter_impl"
+	"github.com/apache/dubbo-go/metadata/service/inmemory"
+	"github.com/apache/dubbo-go/protocol/dubbo"
+	_ "github.com/apache/dubbo-go/protocol/dubbo"
+)
+
+func TestConfigurableExporter(t *testing.T) {
+	dubbo.SetServerConfig(dubbo.ServerConfig{
+		SessionNumber:  700,
+		SessionTimeout: "20s",
+		GettySessionParam: dubbo.GettySessionParam{
+			CompressEncoding: false,
+			TcpNoDelay:       true,
+			TcpKeepAlive:     true,
+			KeepAlivePeriod:  "120s",
+			TcpRBufSize:      262144,
+			TcpWBufSize:      65536,
+			PkgWQSize:        512,
+			TcpReadTimeout:   "1s",
+			TcpWriteTimeout:  "5s",
+			WaitTimeout:      "1s",
+			MaxMsgLen:        10240000000,
+			SessionName:      "server",
+		}})
+	mockInitProviderWithSingleRegistry()
+	metadataService, _ := inmemory.NewMetadataService()
+	exported := NewMetadataServiceExporter(metadataService)
+	assert.Equal(t, false, exported.IsExported())
+	assert.NoError(t, exported.Export())
+	assert.Equal(t, true, exported.IsExported())
+	assert.Regexp(t, "dubbo://:20000/MetadataService*", exported.GetExportedURLs()[0].String())
+	exported.Unexport()
+	assert.Equal(t, false, exported.IsExported())
+}
+
+// mockInitProviderWithSingleRegistry will init a mocked providerConfig
+func mockInitProviderWithSingleRegistry() {
+	providerConfig := &config.ProviderConfig{
+
+		BaseConfig: config.BaseConfig{
+			ApplicationConfig: &config.ApplicationConfig{
+				Organization: "dubbo_org",
+				Name:         "dubbo",
+				Module:       "module",
+				Version:      "1.0.0",
+				Owner:        "dubbo",
+				Environment:  "test"},
+		},
+
+		Registry: &config.RegistryConfig{
+			Address:  "mock://127.0.0.1:2181",
+			Username: "user1",
+			Password: "pwd1",
+		},
+		Registries: map[string]*config.RegistryConfig{},
+
+		Services: map[string]*config.ServiceConfig{
+			"MockService": {
+				InterfaceName: "com.MockService",
+				Protocol:      "mock",
+				Cluster:       "failover",
+				Loadbalance:   "random",
+				Retries:       "3",
+				Group:         "huadong_idc",
+				Version:       "1.0.0",
+				Methods: []*config.MethodConfig{
+					{
+						Name:        "GetUser",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+					{
+						Name:        "GetUser1",
+						Retries:     "2",
+						Loadbalance: "random",
+						Weight:      200,
+					},
+				},
+			},
+		},
+		Protocols: map[string]*config.ProtocolConfig{
+			"mock": {
+				Name: "mock",
+				Ip:   "127.0.0.1",
+				Port: "20000",
+			},
+		},
+	}
+	providerConfig.Services["MockService"].InitExported()
+	config.SetProviderConfig(*providerConfig)
+}
diff --git a/metadata/service/exporter/exporter.go b/metadata/service/exporter/exporter.go
new file mode 100644
index 0000000..cfdef3a
--- /dev/null
+++ b/metadata/service/exporter/exporter.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 exporter
+
+import (
+	"github.com/apache/dubbo-go/common"
+)
+
+// 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 0000000..1f8eeaa
--- /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 0000000..96020e1
--- /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 0000000..8269e69
--- /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 0000000..7e01439
--- /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 0000000..0d75517
--- /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 0000000..048c286
--- /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 0000000..a1a8594
--- /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 0000000..ae83a69
--- /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 0000000..eaf7a02
--- /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 0000000..c284bb2
--- /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 0000000..734f098
--- /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 0000000..f6509d0
--- /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/metrics/prometheus/reporter.go b/metrics/prometheus/reporter.go
index 1636b14..bd1e798 100644
--- a/metrics/prometheus/reporter.go
+++ b/metrics/prometheus/reporter.go
@@ -68,9 +68,8 @@
 	extension.SetMetricReporter(reporterName, newPrometheusReporter)
 }
 
-// PrometheusReporter
-// it will collect the data for Prometheus
-// if you want to use this, you should initialize your prometheus.
+// PrometheusReporter will collect the data for Prometheus
+// if you want to use this feature, you need to initialize your prometheus.
 // https://prometheus.io/docs/guides/go-application/
 type PrometheusReporter struct {
 
@@ -85,7 +84,7 @@
 	consumerHistogramVec *prometheus.HistogramVec
 }
 
-// Report report the duration to Prometheus
+// Report reports the duration to Prometheus
 // the role in url must be consumer or provider
 // or it will be ignored
 func (reporter *PrometheusReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) {
@@ -99,7 +98,7 @@
 		sumVec = reporter.consumerSummaryVec
 		hisVec = reporter.consumerHistogramVec
 	} else {
-		logger.Warnf("The url is not the consumer's or provider's, "+
+		logger.Warnf("The url belongs neither the consumer nor the provider, "+
 			"so the invocation will be ignored. url: %s", url.String())
 		return
 	}
diff --git a/metrics/reporter.go b/metrics/reporter.go
index 85ef1dc..9a7094f 100644
--- a/metrics/reporter.go
+++ b/metrics/reporter.go
@@ -29,7 +29,7 @@
 	NameSpace = "dubbo"
 )
 
-// it will be use to report the invocation's duration
+// Reporter will be used to report the invocation's duration
 type Reporter interface {
 	// report the duration of an invocation
 	Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation,
diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go
index 5ec7db5..6d1b771 100644
--- a/protocol/dubbo/client.go
+++ b/protocol/dubbo/client.go
@@ -88,7 +88,7 @@
 	rand.Seed(time.Now().UnixNano())
 }
 
-// SetClientConf ...
+// SetClientConf set dubbo client config.
 func SetClientConf(c ClientConfig) {
 	clientConf = &c
 	err := clientConf.CheckValidity()
@@ -99,7 +99,7 @@
 	setClientGrpool()
 }
 
-// GetClientConf ...
+// GetClientConf get dubbo client config.
 func GetClientConf() ClientConfig {
 	return *clientConf
 }
@@ -111,7 +111,7 @@
 	}
 }
 
-// Options ...
+// Options is option for create dubbo client
 type Options struct {
 	// connect timeout
 	ConnectTimeout time.Duration
@@ -129,7 +129,7 @@
 	Reply     interface{}
 }
 
-// Client ...
+// Client is dubbo protocol client.
 type Client struct {
 	opts     Options
 	conf     ClientConfig
@@ -139,7 +139,7 @@
 	pendingResponses *sync.Map
 }
 
-// NewClient ...
+// NewClient create a new Client.
 func NewClient(opt Options) *Client {
 
 	switch {
@@ -167,7 +167,7 @@
 	return c
 }
 
-// Request ...
+// Request is dubbo protocol request.
 type Request struct {
 	addr   string
 	svcUrl common.URL
@@ -176,7 +176,7 @@
 	atta   map[string]string
 }
 
-// NewRequest ...
+// NewRequest create a new Request.
 func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request {
 	return &Request{
 		addr:   addr,
@@ -187,13 +187,13 @@
 	}
 }
 
-// Response ...
+// Response is dubbo protocol response.
 type Response struct {
 	reply interface{}
 	atta  map[string]string
 }
 
-// NewResponse ...
+// NewResponse  create a new Response.
 func NewResponse(reply interface{}, atta map[string]string) *Response {
 	return &Response{
 		reply: reply,
@@ -201,15 +201,14 @@
 	}
 }
 
-// CallOneway call one way
+// CallOneway call by one way
 func (c *Client) CallOneway(request *Request) error {
 
 	return perrors.WithStack(c.call(CT_OneWay, request, NewResponse(nil, nil), nil))
 }
 
-// Call if @response is nil, the transport layer will get the response without notify the invoker.
+// Call call remoting by two way or one way, if @response.reply is nil, the way of call is one way.
 func (c *Client) Call(request *Request, response *Response) error {
-
 	ct := CT_TwoWay
 	if response.reply == nil {
 		ct = CT_OneWay
@@ -218,14 +217,12 @@
 	return perrors.WithStack(c.call(ct, request, response, nil))
 }
 
-// AsyncCall ...
+// AsyncCall call remoting by async with callback.
 func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error {
-
 	return perrors.WithStack(c.call(CT_TwoWay, request, response, callback))
 }
 
 func (c *Client) call(ct CallType, request *Request, response *Response, callback common.AsyncCallback) error {
-
 	p := &DubboPackage{}
 	p.Service.Path = strings.TrimPrefix(request.svcUrl.Path, "/")
 	p.Service.Interface = request.svcUrl.GetParam(constant.INTERFACE_KEY, "")
@@ -293,7 +290,7 @@
 	return perrors.WithStack(err)
 }
 
-// Close ...
+// Close close the client pool.
 func (c *Client) Close() {
 	if c.pool != nil {
 		c.pool.close()
diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go
index 1e0a73f..8b0ba16 100644
--- a/protocol/dubbo/client_test.go
+++ b/protocol/dubbo/client_test.go
@@ -37,7 +37,13 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func TestClient_CallOneway(t *testing.T) {
+const (
+	mockMethodNameGetUser   = "GetUser"
+	mockMethodNameGetBigPkg = "GetBigPkg"
+	mockAddress             = "127.0.0.1:20000"
+)
+
+func TestClientCallOneway(t *testing.T) {
 	proto, url := InitTest(t)
 
 	c := &Client{
@@ -50,15 +56,14 @@
 	}
 	c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL))
 
-	//user := &User{}
-	err := c.CallOneway(NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil))
+	err := c.CallOneway(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil))
 	assert.NoError(t, err)
 
 	// destroy
 	proto.Destroy()
 }
 
-func TestClient_Call(t *testing.T) {
+func TestClientCall(t *testing.T) {
 	proto, url := InitTest(t)
 
 	c := &Client{
@@ -77,50 +82,50 @@
 	)
 
 	user = &User{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetBigPkg", []interface{}{nil}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetBigPkg, []interface{}{nil}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 	assert.NotEqual(t, "", user.Id)
 	assert.NotEqual(t, "", user.Name)
 
 	user = &User{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, User{Id: "1", Name: "username"}, *user)
 
 	user = &User{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser0", []interface{}{"1", nil, "username"}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser0", []interface{}{"1", nil, "username"}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, User{Id: "1", Name: "username"}, *user)
 
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser1", []interface{}{}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser1", []interface{}{}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser2", []interface{}{}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser2", []interface{}{}, nil), NewResponse(user, nil))
 	assert.EqualError(t, err, "error")
 
 	user2 := []interface{}{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser3", []interface{}{}, nil), NewResponse(&user2, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser3", []interface{}{}, nil), NewResponse(&user2, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0])
 
 	user2 = []interface{}{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil), NewResponse(&user2, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil), NewResponse(&user2, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0])
 
 	user3 := map[interface{}]interface{}{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil), NewResponse(&user3, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil), NewResponse(&user3, nil))
 	assert.NoError(t, err)
 	assert.NotNil(t, user3)
 	assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"])
 
 	user = &User{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser6", []interface{}{0}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser6", []interface{}{0}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, User{Id: "", Name: ""}, *user)
 
 	user = &User{}
-	err = c.Call(NewRequest("127.0.0.1:20000", url, "GetUser6", []interface{}{1}, nil), NewResponse(user, nil))
+	err = c.Call(NewRequest(mockAddress, url, "GetUser6", []interface{}{1}, nil), NewResponse(user, nil))
 	assert.NoError(t, err)
 	assert.Equal(t, User{Id: "1", Name: ""}, *user)
 
@@ -128,7 +133,7 @@
 	proto.Destroy()
 }
 
-func TestClient_AsyncCall(t *testing.T) {
+func TestClientAsyncCall(t *testing.T) {
 	proto, url := InitTest(t)
 
 	c := &Client{
@@ -144,7 +149,7 @@
 	user := &User{}
 	lock := sync.Mutex{}
 	lock.Lock()
-	err := c.AsyncCall(NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) {
+	err := c.AsyncCall(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) {
 		r := response.(AsyncCallbackResponse)
 		assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User))
 		lock.Unlock()
@@ -162,7 +167,7 @@
 
 	hessian.RegisterPOJO(&User{})
 
-	methods, err := common.ServiceMap.Register("dubbo", &UserProvider{})
+	methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "dubbo", &UserProvider{})
 	assert.NoError(t, err)
 	assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods)
 
diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go
index 76416b2..1f7d107 100644
--- a/protocol/dubbo/codec.go
+++ b/protocol/dubbo/codec.go
@@ -65,11 +65,12 @@
 	Err     error
 }
 
+// String prints dubbo package detail include header、path、body etc.
 func (p DubboPackage) String() string {
 	return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body)
 }
 
-// Marshal ...
+// Marshal encode hessian package.
 func (p *DubboPackage) Marshal() (*bytes.Buffer, error) {
 	codec := hessian.NewHessianCodec(nil)
 
@@ -81,7 +82,7 @@
 	return bytes.NewBuffer(pkg), nil
 }
 
-// Unmarshal ...
+// Unmarshal dncode hessian package.
 func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error {
 	// fix issue https://github.com/apache/dubbo-go/issues/380
 	bufLen := buf.Len()
@@ -125,7 +126,7 @@
 // PendingResponse
 ////////////////////////////////////////////
 
-// PendingResponse ...
+// PendingResponse is a pending response.
 type PendingResponse struct {
 	seq       uint64
 	err       error
@@ -136,7 +137,7 @@
 	done      chan struct{}
 }
 
-// NewPendingResponse ...
+// NewPendingResponse create a PendingResponses.
 func NewPendingResponse() *PendingResponse {
 	return &PendingResponse{
 		start:    time.Now(),
@@ -145,7 +146,7 @@
 	}
 }
 
-// GetCallResponse ...
+// GetCallResponse get AsyncCallbackResponse.
 func (r PendingResponse) GetCallResponse() common.CallbackResponse {
 	return AsyncCallbackResponse{
 		Cause:     r.err,
diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/codec_test.go
index 5dc71f0..c2ca443 100644
--- a/protocol/dubbo/codec_test.go
+++ b/protocol/dubbo/codec_test.go
@@ -29,7 +29,7 @@
 	"github.com/stretchr/testify/assert"
 )
 
-func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) {
+func TestDubboPackageMarshalAndUnmarshal(t *testing.T) {
 	pkg := &DubboPackage{}
 	pkg.Body = []interface{}{"a"}
 	pkg.Header.Type = hessian.PackageHeartbeat
diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go
index dbc6989..635d121 100644
--- a/protocol/dubbo/config.go
+++ b/protocol/dubbo/config.go
@@ -27,7 +27,7 @@
 )
 
 type (
-	// GettySessionParam ...
+	// GettySessionParam is session configuration for getty.
 	GettySessionParam struct {
 		CompressEncoding bool   `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"`
 		TcpNoDelay       bool   `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"`
@@ -47,8 +47,7 @@
 		SessionName      string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"`
 	}
 
-	// ServerConfig
-	//Config holds supported types by the multiconfig package
+	// ServerConfig holds supported types by the multiconfig package
 	ServerConfig struct {
 		// session
 		SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"`
@@ -64,8 +63,7 @@
 		GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"`
 	}
 
-	// ClientConfig
-	//Config holds supported types by the multiconfig package
+	// ClientConfig holds supported types by the multiconfig package
 	ClientConfig struct {
 		ReconnectInterval int `default:"0" yaml:"reconnect_interval" json:"reconnect_interval,omitempty"`
 
@@ -94,7 +92,7 @@
 	}
 )
 
-// GetDefaultClientConfig ...
+// GetDefaultClientConfig gets client default configuration.
 func GetDefaultClientConfig() ClientConfig {
 	return ClientConfig{
 		ReconnectInterval: 0,
@@ -122,7 +120,7 @@
 		}}
 }
 
-// GetDefaultServerConfig ...
+// GetDefaultServerConfig gets server default configuration.
 func GetDefaultServerConfig() ServerConfig {
 	return ServerConfig{
 		SessionTimeout: "180s",
@@ -147,7 +145,7 @@
 	}
 }
 
-// CheckValidity ...
+// CheckValidity confirm getty sessian params.
 func (c *GettySessionParam) CheckValidity() error {
 	var err error
 
@@ -170,7 +168,7 @@
 	return nil
 }
 
-// CheckValidity ...
+// CheckValidity confirm client params.
 func (c *ClientConfig) CheckValidity() error {
 	var err error
 
@@ -192,7 +190,7 @@
 	return perrors.WithStack(c.GettySessionParam.CheckValidity())
 }
 
-// CheckValidity ...
+// CheckValidity confirm server params.
 func (c *ServerConfig) CheckValidity() error {
 	var err error
 
diff --git a/protocol/dubbo/dubbo_exporter.go b/protocol/dubbo/dubbo_exporter.go
index f4cd0cc..dd80937 100644
--- a/protocol/dubbo/dubbo_exporter.go
+++ b/protocol/dubbo/dubbo_exporter.go
@@ -28,23 +28,24 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// DubboExporter ...
+// DubboExporter is dubbo service exporter.
 type DubboExporter struct {
 	protocol.BaseExporter
 }
 
-// NewDubboExporter ...
+// NewDubboExporter get a DubboExporter.
 func NewDubboExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *DubboExporter {
 	return &DubboExporter{
 		BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap),
 	}
 }
 
-// Unexport ...
+// Unexport unexport dubbo service exporter.
 func (de *DubboExporter) Unexport() {
 	serviceId := de.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
+	interfaceName := de.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
 	de.BaseExporter.Unexport()
-	err := common.ServiceMap.UnRegister(DUBBO, serviceId)
+	err := common.ServiceMap.UnRegister(interfaceName, DUBBO, serviceId)
 	if err != nil {
 		logger.Errorf("[DubboExporter.Unexport] error: %v", err)
 	}
diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go
index 09c3725..59202d5 100644
--- a/protocol/dubbo/dubbo_invoker.go
+++ b/protocol/dubbo/dubbo_invoker.go
@@ -39,8 +39,9 @@
 )
 
 var (
-	// ErrNoReply ...
-	ErrNoReply          = perrors.New("request need @response")
+	// ErrNoReply
+	ErrNoReply = perrors.New("request need @response")
+	// ErrDestroyedInvoker
 	ErrDestroyedInvoker = perrors.New("request Destroyed invoker")
 )
 
@@ -48,7 +49,7 @@
 	attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY}
 )
 
-// DubboInvoker ...
+// DubboInvoker is dubbo client invoker.
 type DubboInvoker struct {
 	protocol.BaseInvoker
 	client   *Client
@@ -57,7 +58,7 @@
 	reqNum int64
 }
 
-// NewDubboInvoker ...
+// NewDubboInvoker create dubbo client invoker.
 func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker {
 	return &DubboInvoker{
 		BaseInvoker: *protocol.NewBaseInvoker(url),
@@ -66,7 +67,7 @@
 	}
 }
 
-// Invoke ...
+// Invoke call remoting.
 func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	var (
 		err    error
@@ -122,7 +123,7 @@
 	return &result
 }
 
-// Destroy ...
+// Destroy destroy dubbo client invoker.
 func (di *DubboInvoker) Destroy() {
 	di.quitOnce.Do(func() {
 		for {
diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go
index 1a64301..c0640d5 100644
--- a/protocol/dubbo/dubbo_invoker_test.go
+++ b/protocol/dubbo/dubbo_invoker_test.go
@@ -35,7 +35,7 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestDubboInvoker_Invoke(t *testing.T) {
+func TestDubboInvokerInvoke(t *testing.T) {
 	proto, url := InitTest(t)
 
 	c := &Client{
@@ -51,7 +51,7 @@
 	invoker := NewDubboInvoker(url, c)
 	user := &User{}
 
-	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), invocation.WithArguments([]interface{}{"1", "username"}),
+	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(mockMethodNameGetUser), invocation.WithArguments([]interface{}{"1", "username"}),
 		invocation.WithReply(user), invocation.WithAttachments(map[string]string{"test_key": "test_value"}))
 
 	// Call
diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go
index 355dbc8..9eeefd0 100644
--- a/protocol/dubbo/dubbo_protocol.go
+++ b/protocol/dubbo/dubbo_protocol.go
@@ -33,7 +33,7 @@
 
 // dubbo protocol constant
 const (
-	// DUBBO ...
+	// DUBBO is dubbo protocol name
 	DUBBO = "dubbo"
 )
 
@@ -45,14 +45,14 @@
 	dubboProtocol *DubboProtocol
 )
 
-// DubboProtocol ...
+// DubboProtocol is a dubbo protocol implement.
 type DubboProtocol struct {
 	protocol.BaseProtocol
 	serverMap  map[string]*Server
 	serverLock sync.Mutex
 }
 
-// NewDubboProtocol ...
+// NewDubboProtocol create a dubbo protocol.
 func NewDubboProtocol() *DubboProtocol {
 	return &DubboProtocol{
 		BaseProtocol: protocol.NewBaseProtocol(),
@@ -60,7 +60,7 @@
 	}
 }
 
-// Export ...
+// Export export dubbo service.
 func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	url := invoker.GetUrl()
 	serviceKey := url.ServiceKey()
@@ -73,7 +73,7 @@
 	return exporter
 }
 
-// Refer ...
+// Refer create dubbo service reference.
 func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker {
 	//default requestTimeout
 	var requestTimeout = config.GetConsumerConfig().RequestTimeout
@@ -92,7 +92,7 @@
 	return invoker
 }
 
-// Destroy ...
+// Destroy destroy dubbo service.
 func (dp *DubboProtocol) Destroy() {
 	logger.Infof("DubboProtocol destroy.")
 
@@ -124,7 +124,7 @@
 	}
 }
 
-// GetProtocol ...
+// GetProtocol get a single dubbo protocol.
 func GetProtocol() protocol.Protocol {
 	if dubboProtocol == nil {
 		dubboProtocol = NewDubboProtocol()
diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go
index 14f6868..6f3892b 100644
--- a/protocol/dubbo/dubbo_protocol_test.go
+++ b/protocol/dubbo/dubbo_protocol_test.go
@@ -31,15 +31,19 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-func TestDubboProtocol_Export(t *testing.T) {
-	// Export
-	proto := GetProtocol()
-	srvConf = &ServerConfig{}
-	url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
+const (
+	mockCommonUrl = "dubbo://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")
+		"side=provider&timeout=3000&timestamp=1556509797245"
+)
+
+func TestDubboProtocolExport(t *testing.T) {
+	// Export
+	proto := GetProtocol()
+	srvConf = &ServerConfig{}
+	url, err := common.NewURL(mockCommonUrl)
 	assert.NoError(t, err)
 	exporter := proto.Export(protocol.NewBaseInvoker(url))
 
@@ -48,11 +52,7 @@
 	assert.True(t, eq)
 
 	// second service: the same path and the different version
-	url2, err := common.NewURL("dubbo://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", common.WithParamsValue(constant.VERSION_KEY, "v1.1"))
+	url2, err := common.NewURL(mockCommonUrl, common.WithParamsValue(constant.VERSION_KEY, "v1.1"))
 	assert.NoError(t, err)
 	exporter2 := proto.Export(protocol.NewBaseInvoker(url2))
 	// make sure url
@@ -74,14 +74,10 @@
 	assert.False(t, ok)
 }
 
-func TestDubboProtocol_Refer(t *testing.T) {
+func TestDubboProtocolRefer(t *testing.T) {
 	// Refer
 	proto := GetProtocol()
-	url, err := common.NewURL("dubbo://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(mockCommonUrl)
 	assert.NoError(t, err)
 	clientConf = &ClientConfig{}
 	invoker := proto.Refer(url)
diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go
index 0251b78..4834459 100644
--- a/protocol/dubbo/listener.go
+++ b/protocol/dubbo/listener.go
@@ -41,10 +41,9 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-// todo: WritePkg_Timeout will entry *.yml
+// todo: writePkg_Timeout will entry *.yml
 const (
-	// WritePkg_Timeout ...
-	WritePkg_Timeout = 5 * time.Second
+	writePkg_Timeout = 5 * time.Second
 )
 
 var (
@@ -56,10 +55,12 @@
 	reqNum  int32
 }
 
+// AddReqNum adds total request number safely
 func (s *rpcSession) AddReqNum(num int32) {
 	atomic.AddInt32(&s.reqNum, num)
 }
 
+// GetReqNum gets total request number safely
 func (s *rpcSession) GetReqNum() int32 {
 	return atomic.LoadInt32(&s.reqNum)
 }
@@ -68,35 +69,35 @@
 // RpcClientHandler
 // //////////////////////////////////////////
 
-// RpcClientHandler ...
+// RpcClientHandler is handler of RPC Client
 type RpcClientHandler struct {
 	conn *gettyRPCClient
 }
 
-// NewRpcClientHandler ...
+// NewRpcClientHandler creates RpcClientHandler with @gettyRPCClient
 func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler {
 	return &RpcClientHandler{conn: client}
 }
 
-// OnOpen ...
+// OnOpen notified when RPC client session opened
 func (h *RpcClientHandler) OnOpen(session getty.Session) error {
 	h.conn.addSession(session)
 	return nil
 }
 
-// OnError ...
+// OnError notified when RPC client session got any error
 func (h *RpcClientHandler) OnError(session getty.Session, err error) {
-	logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err)
+	logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err)
 	h.conn.removeSession(session)
 }
 
-// OnClose ...
+// OnOpen notified when RPC client session closed
 func (h *RpcClientHandler) OnClose(session getty.Session) {
 	logger.Infof("session{%s} is closing......", session.Stat())
 	h.conn.removeSession(session)
 }
 
-// OnMessage ...
+// OnMessage notified when RPC client session got any message in connection
 func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) {
 	p, ok := pkg.(*DubboPackage)
 	if !ok {
@@ -141,9 +142,9 @@
 	}
 }
 
-// OnCron ...
+// OnCron notified when RPC client session got any message in cron job
 func (h *RpcClientHandler) OnCron(session getty.Session) {
-	rpcSession, err := h.conn.getClientRpcSession(session)
+	clientRpcSession, err := h.conn.getClientRpcSession(session)
 	if err != nil {
 		logger.Errorf("client.getClientSession(session{%s}) = error{%v}",
 			session.Stat(), perrors.WithStack(err))
@@ -151,7 +152,7 @@
 	}
 	if h.conn.pool.rpcClient.conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() {
 		logger.Warnf("session{%s} timeout{%s}, reqNum{%d}",
-			session.Stat(), time.Since(session.GetActive()).String(), rpcSession.reqNum)
+			session.Stat(), time.Since(session.GetActive()).String(), clientRpcSession.reqNum)
 		h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn)
 		return
 	}
@@ -163,7 +164,7 @@
 // RpcServerHandler
 // //////////////////////////////////////////
 
-// RpcServerHandler ...
+// RpcServerHandler is handler of RPC Server
 type RpcServerHandler struct {
 	maxSessionNum  int
 	sessionTimeout time.Duration
@@ -171,7 +172,7 @@
 	rwlock         sync.RWMutex
 }
 
-// NewRpcServerHandler ...
+// NewRpcServerHandler creates RpcServerHandler with @maxSessionNum and @sessionTimeout
 func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler {
 	return &RpcServerHandler{
 		maxSessionNum:  maxSessionNum,
@@ -180,7 +181,7 @@
 	}
 }
 
-// OnOpen ...
+// OnOpen notified when RPC server session opened
 func (h *RpcServerHandler) OnOpen(session getty.Session) error {
 	var err error
 	h.rwlock.RLock()
@@ -199,15 +200,15 @@
 	return nil
 }
 
-// OnError ...
+// OnError notified when RPC server session got any error
 func (h *RpcServerHandler) OnError(session getty.Session, err error) {
-	logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err)
+	logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err)
 	h.rwlock.Lock()
 	delete(h.sessionMap, session)
 	h.rwlock.Unlock()
 }
 
-// OnClose ...
+// OnOpen notified when RPC server session closed
 func (h *RpcServerHandler) OnClose(session getty.Session) {
 	logger.Infof("session{%s} is closing......", session.Stat())
 	h.rwlock.Lock()
@@ -215,7 +216,7 @@
 	h.rwlock.Unlock()
 }
 
-// OnMessage ...
+// OnMessage notified when RPC server session got any message in connection
 func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) {
 	h.rwlock.Lock()
 	if _, ok := h.sessionMap[session]; ok {
@@ -306,7 +307,7 @@
 	reply(session, p, hessian.PackageResponse)
 }
 
-// OnCron ...
+// OnCron notified when RPC server session got any message in cron job
 func (h *RpcServerHandler) OnCron(session getty.Session) {
 	var (
 		flag   bool
@@ -363,7 +364,7 @@
 		resp.Body = nil
 	}
 
-	if err := session.WritePkg(resp, WritePkg_Timeout); err != nil {
+	if err := session.WritePkg(resp, writePkg_Timeout); err != nil {
 		logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), req.Header)
 	}
 }
diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go
index 918514c..c9f5e34 100644
--- a/protocol/dubbo/pool.go
+++ b/protocol/dubbo/pool.go
@@ -219,25 +219,25 @@
 
 func (c *gettyRPCClient) getClientRpcSession(session getty.Session) (rpcSession, error) {
 	var (
-		err        error
-		rpcSession rpcSession
+		err              error
+		rpcClientSession rpcSession
 	)
 	c.lock.RLock()
 	defer c.lock.RUnlock()
 	if c.sessions == nil {
-		return rpcSession, errClientClosed
+		return rpcClientSession, errClientClosed
 	}
 
 	err = errSessionNotExist
 	for _, s := range c.sessions {
 		if s.session == session {
-			rpcSession = *s
+			rpcClientSession = *s
 			err = nil
 			break
 		}
 	}
 
-	return rpcSession, perrors.WithStack(err)
+	return rpcClientSession, perrors.WithStack(err)
 }
 
 func (c *gettyRPCClient) isAvailable() bool {
@@ -319,8 +319,7 @@
 	conn, err := p.get()
 	if err == nil && conn == nil {
 		// create new conn
-		rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr)
-		return rpcClientConn, perrors.WithStack(err)
+		conn, err = newGettyRPCClientConn(p, protocol, addr)
 	}
 	return conn, perrors.WithStack(err)
 }
diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go
index b5c4f50..9cc7ea2 100644
--- a/protocol/dubbo/readwriter.go
+++ b/protocol/dubbo/readwriter.go
@@ -38,16 +38,17 @@
 // RpcClientPackageHandler
 ////////////////////////////////////////////
 
-// RpcClientPackageHandler ...
+// RpcClientPackageHandler handle package for client in getty.
 type RpcClientPackageHandler struct {
 	client *Client
 }
 
-// NewRpcClientPackageHandler ...
+// NewRpcClientPackageHandler create a RpcClientPackageHandler.
 func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler {
 	return &RpcClientPackageHandler{client: client}
 }
 
+// Read decode @data to DubboPackage.
 func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
 	pkg := &DubboPackage{}
 
@@ -72,6 +73,7 @@
 	return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil
 }
 
+// Write encode @pkg.
 func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) {
 	req, ok := pkg.(*DubboPackage)
 	if !ok {
@@ -96,9 +98,10 @@
 	rpcServerPkgHandler = &RpcServerPackageHandler{}
 )
 
-// RpcServerPackageHandler ...
+// RpcServerPackageHandler handle package for server in getty.
 type RpcServerPackageHandler struct{}
 
+// Read decode @data to DubboPackage.
 func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
 	pkg := &DubboPackage{
 		Body: make([]interface{}, 7),
@@ -169,6 +172,7 @@
 	return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil
 }
 
+// Write encode @pkg.
 func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) {
 	res, ok := pkg.(*DubboPackage)
 	if !ok {
diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go
index bd2b37b..8de353a 100644
--- a/protocol/dubbo/server.go
+++ b/protocol/dubbo/server.go
@@ -71,10 +71,10 @@
 	if err := srvConf.CheckValidity(); err != nil {
 		panic(err)
 	}
-	SetServerGrpool()
+	setServerGrpool()
 }
 
-// SetServerConfig ...
+// SetServerConfig set dubbo server config.
 func SetServerConfig(s ServerConfig) {
 	srvConf = &s
 	err := srvConf.CheckValidity()
@@ -82,30 +82,29 @@
 		logger.Warnf("[ServerConfig CheckValidity] error: %v", err)
 		return
 	}
-	SetServerGrpool()
+	setServerGrpool()
 }
 
-// GetServerConfig ...
+// GetServerConfig get dubbo server config.
 func GetServerConfig() ServerConfig {
 	return *srvConf
 }
 
-// SetServerGrpool ...
-func SetServerGrpool() {
+func setServerGrpool() {
 	if srvConf.GrPoolSize > 1 {
 		srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen),
 			gxsync.WithTaskPoolTaskQueueNumber(srvConf.QueueNumber))
 	}
 }
 
-// Server ...
+// Server is dubbo protocol server.
 type Server struct {
 	conf       ServerConfig
 	tcpServer  getty.Server
 	rpcHandler *RpcServerHandler
 }
 
-// NewServer ...
+// NewServer create a new Server.
 func NewServer() *Server {
 
 	s := &Server{
@@ -156,7 +155,7 @@
 	return nil
 }
 
-// Start ...
+// Start start dubbo server.
 func (s *Server) Start(url common.URL) {
 	var (
 		addr      string
@@ -173,7 +172,7 @@
 
 }
 
-// Stop ...
+// Stop stop dubbo server.
 func (s *Server) Stop() {
 	s.tcpServer.Close()
 }
diff --git a/protocol/grpc/client.go b/protocol/grpc/client.go
index 0c7499a..a0ab0be 100644
--- a/protocol/grpc/client.go
+++ b/protocol/grpc/client.go
@@ -25,27 +25,78 @@
 	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
 	"github.com/opentracing/opentracing-go"
 	"google.golang.org/grpc"
+	"gopkg.in/yaml.v2"
 )
 
 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"
 )
 
-// Client ...
+var (
+	clientConf *ClientConfig
+)
+
+func init() {
+	// load clientconfig from consumer_config
+	consumerConfig := config.GetConsumerConfig()
+
+	clientConfig := GetClientConfig()
+	clientConf = &clientConfig
+
+	// check client config and decide whether to use the default config
+	defer func() {
+		if clientConf == nil || len(clientConf.ContentSubType) == 0 {
+			defaultClientConfig := GetDefaultClientConfig()
+			clientConf = &defaultClientConfig
+		}
+		if err := clientConf.Validate(); err != nil {
+			panic(err)
+		}
+	}()
+
+	if consumerConfig.ApplicationConfig == nil {
+		return
+	}
+	protocolConf := config.GetConsumerConfig().ProtocolConf
+
+	if protocolConf == nil {
+		logger.Info("protocol_conf default use dubbo config")
+	} else {
+		grpcConf := protocolConf.(map[interface{}]interface{})[GRPC]
+		if grpcConf == nil {
+			logger.Warnf("grpcConf is nil")
+			return
+		}
+		grpcConfByte, err := yaml.Marshal(grpcConf)
+		if err != nil {
+			panic(err)
+		}
+		err = yaml.Unmarshal(grpcConfByte, clientConf)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+}
+
+// Client is gRPC client include client connection and invoker
 type Client struct {
 	*grpc.ClientConn
 	invoker reflect.Value
 }
 
-// NewClient ...
+// NewClient creates a new gRPC client.
 func NewClient(url common.URL) *Client {
 	// if global trace instance was set , it means trace function enabled. If not , will return Nooptracer
 	tracer := opentracing.GlobalTracer()
-	conn, err := grpc.Dial(url.Location, grpc.WithInsecure(), grpc.WithBlock(),
-		grpc.WithUnaryInterceptor(
-			otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads())))
+	dailOpts := make([]grpc.DialOption, 0, 4)
+	dailOpts = append(dailOpts, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithUnaryInterceptor(
+		otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads())),
+		grpc.WithDefaultCallOptions(grpc.CallContentSubtype(clientConf.ContentSubType)))
+	conn, err := grpc.Dial(url.Location, dailOpts...)
 	if err != nil {
 		panic(err)
 	}
diff --git a/protocol/grpc/codec.go b/protocol/grpc/codec.go
new file mode 100644
index 0000000..7235a39
--- /dev/null
+++ b/protocol/grpc/codec.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 grpc
+
+import (
+	"bytes"
+	"encoding/json"
+)
+
+import (
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/golang/protobuf/proto"
+	"google.golang.org/grpc/encoding"
+)
+
+const (
+	codecJson  = "json"
+	codecProto = "proto"
+)
+
+func init() {
+	encoding.RegisterCodec(grpcJson{
+		Marshaler: jsonpb.Marshaler{
+			EmitDefaults: true,
+			OrigName:     true,
+		},
+	})
+}
+
+type grpcJson struct {
+	jsonpb.Marshaler
+	jsonpb.Unmarshaler
+}
+
+// Name implements grpc encoding package Codec interface method,
+// returns the name of the Codec implementation.
+func (_ grpcJson) Name() string {
+	return codecJson
+}
+
+// Marshal implements grpc encoding package Codec interface method,returns the wire format of v.
+func (j grpcJson) Marshal(v interface{}) (out []byte, err error) {
+	if pm, ok := v.(proto.Message); ok {
+		b := new(bytes.Buffer)
+		err := j.Marshaler.Marshal(b, pm)
+		if err != nil {
+			return nil, err
+		}
+		return b.Bytes(), nil
+	}
+	return json.Marshal(v)
+}
+
+// Unmarshal implements grpc encoding package Codec interface method,Unmarshal parses the wire format into v.
+func (j grpcJson) Unmarshal(data []byte, v interface{}) (err error) {
+	if pm, ok := v.(proto.Message); ok {
+		b := bytes.NewBuffer(data)
+		return j.Unmarshaler.Unmarshal(b, pm)
+	}
+	return json.Unmarshal(data, v)
+}
diff --git a/protocol/grpc/common_test.go b/protocol/grpc/common_test.go
index 33c2fc6..b732283 100644
--- a/protocol/grpc/common_test.go
+++ b/protocol/grpc/common_test.go
@@ -106,7 +106,7 @@
 		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/config.go b/protocol/grpc/config.go
new file mode 100644
index 0000000..e8a9baa
--- /dev/null
+++ b/protocol/grpc/config.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 grpc
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+type (
+	// ServerConfig currently is empty struct,for future expansion
+	ServerConfig struct {
+	}
+
+	// ClientConfig wrap client call parameters
+	ClientConfig struct {
+		// content type, more information refer by https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
+		ContentSubType string `default:"proto" yaml:"content_sub_type" json:"content_sub_type,omitempty"`
+	}
+)
+
+// GetDefaultClientConfig return grpc client default call options
+func GetDefaultClientConfig() ClientConfig {
+	return ClientConfig{
+		ContentSubType: codecProto,
+	}
+}
+
+// GetDefaultServerConfig currently return empty struct,for future expansion
+func GetDefaultServerConfig() ServerConfig {
+	return ServerConfig{}
+}
+
+// GetClientConfig return grpc client custom call options
+func GetClientConfig() ClientConfig {
+	return ClientConfig{}
+}
+
+// Validate check if custom config encoding is supported in dubbo grpc
+func (c *ClientConfig) Validate() error {
+	if c.ContentSubType != codecJson && c.ContentSubType != codecProto {
+		return perrors.Errorf(" dubbo-go grpc codec currently only support proto、json, %s isn't supported,"+
+			" please check protocol content_sub_type config", c.ContentSubType)
+	}
+	return nil
+}
+
+// Validate currently return empty struct,for future expansion
+func (c *ServerConfig) Validate() error {
+	return nil
+}
diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go
index 1acd2fe..79962b5 100644
--- a/protocol/grpc/grpc_exporter.go
+++ b/protocol/grpc/grpc_exporter.go
@@ -33,18 +33,19 @@
 	*protocol.BaseExporter
 }
 
-// NewGrpcExporter ...
+// NewGrpcExporter creates a new gRPC exporter
 func NewGrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *GrpcExporter {
 	return &GrpcExporter{
 		BaseExporter: protocol.NewBaseExporter(key, invoker, exporterMap),
 	}
 }
 
-// Unexport ...
+// Unexport and unregister gRPC service from registry and memory.
 func (gg *GrpcExporter) Unexport() {
 	serviceId := gg.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
+	interfaceName := gg.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
 	gg.BaseExporter.Unexport()
-	err := common.ServiceMap.UnRegister(GRPC, serviceId)
+	err := common.ServiceMap.UnRegister(interfaceName, GRPC, serviceId)
 	if err != nil {
 		logger.Errorf("[GrpcExporter.Unexport] error: %v", err)
 	}
diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go
index 78439fc..e150d05 100644
--- a/protocol/grpc/grpc_invoker.go
+++ b/protocol/grpc/grpc_invoker.go
@@ -35,8 +35,7 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// ErrNoReply ...
-var ErrNoReply = errors.New("request need @response")
+var errNoReply = errors.New("request need @response")
 
 // GrpcInvoker ...
 type GrpcInvoker struct {
@@ -60,7 +59,7 @@
 	)
 
 	if invocation.Reply() == nil {
-		result.Err = ErrNoReply
+		result.Err = errNoReply
 	}
 
 	in := []reflect.Value{}
diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go
index 3054ada..d5ebbb4 100644
--- a/protocol/grpc/grpc_invoker_test.go
+++ b/protocol/grpc/grpc_invoker_test.go
@@ -33,11 +33,19 @@
 	"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.go b/protocol/grpc/grpc_protocol.go
index cc4aba1..68594a4 100644
--- a/protocol/grpc/grpc_protocol.go
+++ b/protocol/grpc/grpc_protocol.go
@@ -39,14 +39,14 @@
 
 var grpcProtocol *GrpcProtocol
 
-// GrpcProtocol ...
+// GrpcProtocol is gRPC protocol
 type GrpcProtocol struct {
 	protocol.BaseProtocol
 	serverMap  map[string]*Server
 	serverLock sync.Mutex
 }
 
-// NewGRPCProtocol ...
+// NewGRPCProtocol creates new gRPC protocol
 func NewGRPCProtocol() *GrpcProtocol {
 	return &GrpcProtocol{
 		BaseProtocol: protocol.NewBaseProtocol(),
@@ -54,7 +54,7 @@
 	}
 }
 
-// Export ...
+// Export gRPC service for remote invocation
 func (gp *GrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	url := invoker.GetUrl()
 	serviceKey := url.ServiceKey()
@@ -84,7 +84,7 @@
 	}
 }
 
-// Refer ...
+// Refer a remote gRPC service
 func (gp *GrpcProtocol) Refer(url common.URL) protocol.Invoker {
 	invoker := NewGrpcInvoker(url, NewClient(url))
 	gp.SetInvokers(invoker)
@@ -92,7 +92,7 @@
 	return invoker
 }
 
-// Destroy ...
+// Destroy will destroy gRPC all invoker and exporter, so it only is called once.
 func (gp *GrpcProtocol) Destroy() {
 	logger.Infof("GrpcProtocol destroy.")
 
@@ -104,7 +104,7 @@
 	}
 }
 
-// GetProtocol ...
+// GetProtocol gets gRPC protocol , will create if null.
 func GetProtocol() protocol.Protocol {
 	if grpcProtocol == nil {
 		grpcProtocol = NewGRPCProtocol()
diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go
index d028f8e..87ce714 100644
--- a/protocol/grpc/grpc_protocol_test.go
+++ b/protocol/grpc/grpc_protocol_test.go
@@ -32,12 +32,12 @@
 	"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 @@
 	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/grpc/internal/server.go b/protocol/grpc/internal/server.go
index a6b861c..f7b99fa 100644
--- a/protocol/grpc/internal/server.go
+++ b/protocol/grpc/internal/server.go
@@ -42,7 +42,7 @@
 	return &HelloReply{Message: "Hello " + in.GetName()}, nil
 }
 
-// InitGrpcServer ...
+// InitGrpcServer creates global gRPC server.
 func InitGrpcServer() {
 	port := ":30000"
 
@@ -57,7 +57,7 @@
 	}
 }
 
-// ShutdownGrpcServer ...
+// ShutdownGrpcServer shuts down gRPC server gracefully
 func ShutdownGrpcServer() {
 	if s == nil {
 		return
diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go
index 83d2851..1af4faf 100644
--- a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go
+++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go
@@ -107,7 +107,6 @@
 	g.P(`dgrpc "github.com/apache/dubbo-go/protocol/grpc"`)
 	g.P(`"github.com/apache/dubbo-go/protocol/invocation"`)
 	g.P(`"github.com/apache/dubbo-go/protocol"`)
-	g.P(`"github.com/apache/dubbo-go/config"`)
 	g.P(` ) `)
 }
 
@@ -266,7 +265,7 @@
 		g.P(`invo := invocation.NewRPCInvocation("`, methName, `", args, nil)`)
 
 		g.P("if interceptor == nil {")
-		g.P("result := base.GetProxyImpl().Invoke(invo)")
+		g.P("result := base.GetProxyImpl().Invoke(context.Background(), invo)")
 		g.P("return result.Result(), result.Error()")
 		g.P("}")
 
@@ -276,7 +275,7 @@
 		g.P("}")
 
 		g.P("handler := func(ctx ", contextPkg, ".Context, req interface{}) (interface{}, error) {")
-		g.P("result := base.GetProxyImpl().Invoke(invo)")
+		g.P("result := base.GetProxyImpl().Invoke(context.Background(), invo)")
 		g.P("return result.Result(), result.Error()")
 		g.P("}")
 
diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go
index 6378304..4017b65 100644
--- a/protocol/grpc/server.go
+++ b/protocol/grpc/server.go
@@ -37,24 +37,27 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// Server ...
+// Server is a gRPC server
 type Server struct {
 	grpcServer *grpc.Server
 }
 
-// NewServer ...
+// NewServer creates a new server
 func NewServer() *Server {
 	return &Server{}
 }
 
-// DubboGrpcService ...
+// DubboGrpcService is gRPC service
 type DubboGrpcService interface {
+	// SetProxyImpl sets proxy.
 	SetProxyImpl(impl protocol.Invoker)
+	// GetProxyImpl gets proxy.
 	GetProxyImpl() protocol.Invoker
+	// ServiceDesc gets an RPC service's specification.
 	ServiceDesc() *grpc.ServiceDesc
 }
 
-// Start ...
+// Start gRPC server with @url
 func (s *Server) Start(url common.URL) {
 	var (
 		addr string
@@ -106,7 +109,7 @@
 	}()
 }
 
-// Stop ...
+// Stop gRPC server
 func (s *Server) Stop() {
 	s.grpcServer.Stop()
 }
diff --git a/protocol/invocation.go b/protocol/invocation.go
index f32f2c3..ba59497 100644
--- a/protocol/invocation.go
+++ b/protocol/invocation.go
@@ -21,14 +21,26 @@
 	"reflect"
 )
 
-// Invocation ...
+// Invocation is a invocation for each remote method.
 type Invocation interface {
+	// MethodName gets invocation method name.
 	MethodName() string
+	// ParameterTypes gets invocation parameter types.
 	ParameterTypes() []reflect.Type
+	// ParameterValues gets invocation parameter values.
 	ParameterValues() []reflect.Value
+	// Arguments gets arguments.
 	Arguments() []interface{}
+	// Reply gets response of request
 	Reply() interface{}
+	// Attachments gets all attachments
 	Attachments() map[string]string
+	// AttachmentsByKey gets attachment by key , if nil then return default value
 	AttachmentsByKey(string, string) string
+	// Attributes refers to dubbo 2.7.6.  It is different from attachment. It is used in internal process.
+	Attributes() map[string]interface{}
+	// AttributeByKey gets attribute by key , if nil then return default value
+	AttributeByKey(string, interface{}) interface{}
+	// Invoker gets the invoker in current context.
 	Invoker() Invoker
 }
diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go
index b207fd0..b8b5b50 100644
--- a/protocol/invocation/rpcinvocation.go
+++ b/protocol/invocation/rpcinvocation.go
@@ -31,7 +31,7 @@
 // ///////////////////////////
 
 // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation
-// RPCInvocation ...
+// nolint
 type RPCInvocation struct {
 	methodName      string
 	parameterTypes  []reflect.Type
@@ -40,64 +40,70 @@
 	reply           interface{}
 	callBack        interface{}
 	attachments     map[string]string
-	invoker         protocol.Invoker
-	lock            sync.RWMutex
+	// Refer to dubbo 2.7.6.  It is different from attachment. It is used in internal process.
+	attributes map[string]interface{}
+	invoker    protocol.Invoker
+	lock       sync.RWMutex
 }
 
-// NewRPCInvocation ...
+// NewRPCInvocation creates a RPC invocation.
 func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]string) *RPCInvocation {
 	return &RPCInvocation{
 		methodName:  methodName,
 		arguments:   arguments,
 		attachments: attachments,
+		attributes:  make(map[string]interface{}, 8),
 	}
 }
 
-// NewRPCInvocationWithOptions ...
+// NewRPCInvocationWithOptions creates a RPC invocation with @opts.
 func NewRPCInvocationWithOptions(opts ...option) *RPCInvocation {
 	invo := &RPCInvocation{}
 	for _, opt := range opts {
 		opt(invo)
 	}
+	if invo.attributes == nil {
+		invo.attributes = make(map[string]interface{})
+	}
 	return invo
 }
 
-// MethodName ...
+// MethodName gets RPC invocation method name.
 func (r *RPCInvocation) MethodName() string {
 	return r.methodName
 }
 
-// ParameterTypes ...
+// ParameterTypes gets RPC invocation parameter types.
 func (r *RPCInvocation) ParameterTypes() []reflect.Type {
 	return r.parameterTypes
 }
 
-// ParameterValues ...
+// ParameterValues gets RPC invocation parameter values.
 func (r *RPCInvocation) ParameterValues() []reflect.Value {
 	return r.parameterValues
 }
 
-// Arguments ...
+// Arguments gets RPC arguments.
 func (r *RPCInvocation) Arguments() []interface{} {
 	return r.arguments
 }
 
-// Reply ...
+// Reply gets response of RPC request.
 func (r *RPCInvocation) Reply() interface{} {
 	return r.reply
 }
 
-// SetReply ...
+// SetReply sets response of RPC request.
 func (r *RPCInvocation) SetReply(reply interface{}) {
 	r.reply = reply
 }
 
-// Attachments ...
+// Attachments gets all attachments of RPC.
 func (r *RPCInvocation) Attachments() map[string]string {
 	return r.attachments
 }
 
-// AttachmentsByKey ...
+// AttachmentsByKey gets RPC attachment by key , if nil then return default value.
 func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string {
 	r.lock.RLock()
 	defer r.lock.RUnlock()
@@ -111,7 +117,23 @@
 	return defaultValue
 }
 
-// SetAttachments ...
+// Attributes gets all attributes of RPC.
+func (r *RPCInvocation) Attributes() map[string]interface{} {
+	return r.attributes
+}
+
+// AttributeByKey gets attribute by @key. If it is not exist, it will return default value.
+func (r *RPCInvocation) AttributeByKey(key string, defaultValue interface{}) interface{} {
+	r.lock.RLock()
+	defer r.lock.RUnlock()
+	value, ok := r.attributes[key]
+	if ok {
+		return value
+	}
+	return defaultValue
+}
+
+// SetAttachments sets attribute by @key and @value.
 func (r *RPCInvocation) SetAttachments(key string, value string) {
 	r.lock.Lock()
 	defer r.lock.Unlock()
@@ -121,22 +143,29 @@
 	r.attachments[key] = value
 }
 
-// Invoker ...
+// SetAttribute sets attribute by @key and @value.
+func (r *RPCInvocation) SetAttribute(key string, value interface{}) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	r.attributes[key] = value
+}
+
+// Invoker gets the invoker in current context.
 func (r *RPCInvocation) Invoker() protocol.Invoker {
 	return r.invoker
 }
 
-// SetInvoker ...
-func (r *RPCInvocation) SetInvoker() protocol.Invoker {
-	return r.invoker
+// nolint
+func (r *RPCInvocation) SetInvoker(invoker protocol.Invoker) {
+	r.invoker = invoker
 }
 
-// CallBack ...
+// CallBack sets RPC callback method.
 func (r *RPCInvocation) CallBack() interface{} {
 	return r.callBack
 }
 
-// SetCallBack ...
+// SetCallBack sets RPC callback method.
 func (r *RPCInvocation) SetCallBack(c interface{}) {
 	r.callBack = c
 }
@@ -147,56 +176,56 @@
 
 type option func(invo *RPCInvocation)
 
-// WithMethodName ...
+// WithMethodName creates option with @methodName.
 func WithMethodName(methodName string) option {
 	return func(invo *RPCInvocation) {
 		invo.methodName = methodName
 	}
 }
 
-// WithParameterTypes ...
+// WithParameterTypes creates option with @parameterTypes.
 func WithParameterTypes(parameterTypes []reflect.Type) option {
 	return func(invo *RPCInvocation) {
 		invo.parameterTypes = parameterTypes
 	}
 }
 
-// WithParameterValues ...
+// WithParameterValues creates option with @parameterValues
 func WithParameterValues(parameterValues []reflect.Value) option {
 	return func(invo *RPCInvocation) {
 		invo.parameterValues = parameterValues
 	}
 }
 
-// WithArguments ...
+// WithArguments creates option with @arguments function.
 func WithArguments(arguments []interface{}) option {
 	return func(invo *RPCInvocation) {
 		invo.arguments = arguments
 	}
 }
 
-// WithReply ...
+// WithReply creates option with @reply function.
 func WithReply(reply interface{}) option {
 	return func(invo *RPCInvocation) {
 		invo.reply = reply
 	}
 }
 
-// WithCallBack ...
+// WithCallBack creates option with @callback function.
 func WithCallBack(callBack interface{}) option {
 	return func(invo *RPCInvocation) {
 		invo.callBack = callBack
 	}
 }
 
-// WithAttachments ...
+// WithAttachments creates option with @attachments.
 func WithAttachments(attachments map[string]string) option {
 	return func(invo *RPCInvocation) {
 		invo.attachments = attachments
 	}
 }
 
-// WithInvoker ...
+// WithInvoker creates option with @invoker.
 func WithInvoker(invoker protocol.Invoker) option {
 	return func(invo *RPCInvocation) {
 		invo.invoker = invoker
diff --git a/protocol/invoker.go b/protocol/invoker.go
index bb71bab..3ca3704 100644
--- a/protocol/invoker.go
+++ b/protocol/invoker.go
@@ -31,6 +31,7 @@
 // Extension - Invoker
 type Invoker interface {
 	common.Node
+	// Invoke the invocation and return result.
 	Invoke(context.Context, Invocation) Result
 }
 
@@ -38,14 +39,14 @@
 // base invoker
 /////////////////////////////
 
-// BaseInvoker ...
+// BaseInvoker provides default invoker implement
 type BaseInvoker struct {
 	url       common.URL
 	available bool
 	destroyed bool
 }
 
-// NewBaseInvoker ...
+// NewBaseInvoker creates a new BaseInvoker
 func NewBaseInvoker(url common.URL) *BaseInvoker {
 	return &BaseInvoker{
 		url:       url,
@@ -54,27 +55,27 @@
 	}
 }
 
-// GetUrl ...
+// GetUrl gets base invoker URL
 func (bi *BaseInvoker) GetUrl() common.URL {
 	return bi.url
 }
 
-// IsAvailable ...
+// IsAvailable gets available flag
 func (bi *BaseInvoker) IsAvailable() bool {
 	return bi.available
 }
 
-// IsDestroyed ...
+// IsDestroyed gets destroyed flag
 func (bi *BaseInvoker) IsDestroyed() bool {
 	return bi.destroyed
 }
 
-// Invoke ...
+// Invoke provides default invoker implement
 func (bi *BaseInvoker) Invoke(context context.Context, invocation Invocation) Result {
 	return &RPCResult{}
 }
 
-// Destroy ...
+// Destroy changes available and destroyed flag
 func (bi *BaseInvoker) Destroy() {
 	logger.Infof("Destroy invoker: %s", bi.GetUrl().String())
 	bi.destroyed = true
diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go
index ba7197d..5fca66d 100644
--- a/protocol/jsonrpc/http.go
+++ b/protocol/jsonrpc/http.go
@@ -63,7 +63,7 @@
 // HTTP Client
 // ////////////////////////////////////////////
 
-// HTTPOptions ...
+// HTTPOptions is a HTTP option include HandshakeTimeout and HTTPTimeout.
 type HTTPOptions struct {
 	HandshakeTimeout time.Duration
 	HTTPTimeout      time.Duration
@@ -74,13 +74,13 @@
 	HTTPTimeout:      3 * time.Second,
 }
 
-// HTTPClient ...
+// HTTPClient is a HTTP client ,include ID and options.
 type HTTPClient struct {
 	ID      int64
 	options HTTPOptions
 }
 
-// NewHTTPClient ...
+// NewHTTPClient creates a new HTTP client with HTTPOptions.
 func NewHTTPClient(opt *HTTPOptions) *HTTPClient {
 	if opt == nil {
 		opt = &defaultHTTPOptions
@@ -100,7 +100,7 @@
 	}
 }
 
-// NewRequest ...
+// NewRequest creates a new HTTP request with @service ,@method and @arguments.
 func (c *HTTPClient) NewRequest(service common.URL, method string, args interface{}) *Request {
 
 	return &Request{
@@ -114,7 +114,7 @@
 	}
 }
 
-// Call ...
+// Call makes a HTTP call with @ctx , @service ,@req and @rsp
 func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, rsp interface{}) error {
 	// header
 	httpHeader := http.Header{}
@@ -172,7 +172,7 @@
 	httpReq.Close = true
 
 	reqBuf := bytes.NewBuffer(make([]byte, 0))
-	if err := httpReq.Write(reqBuf); err != nil {
+	if err = httpReq.Write(reqBuf); err != nil {
 		return nil, perrors.WithStack(err)
 	}
 
@@ -191,7 +191,7 @@
 	}
 	setNetConnTimeout(tcpConn, c.options.HTTPTimeout)
 
-	if _, err := reqBuf.WriteTo(tcpConn); err != nil {
+	if _, err = reqBuf.WriteTo(tcpConn); err != nil {
 		return nil, perrors.WithStack(err)
 	}
 
diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go
index 0cb88b3..5765919 100644
--- a/protocol/jsonrpc/http_test.go
+++ b/protocol/jsonrpc/http_test.go
@@ -48,19 +48,23 @@
 	}
 )
 
-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"
+)
 
-	methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{})
+func TestHTTPClientCall(t *testing.T) {
+
+	methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "jsonrpc", &UserProvider{})
 	assert.NoError(t, err)
 	assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods)
 
 	// 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 d1c2a85..7b05a22 100644
--- a/protocol/jsonrpc/json.go
+++ b/protocol/jsonrpc/json.go
@@ -37,7 +37,7 @@
 	VERSION = "2.0"
 )
 
-// CodecData ...
+// CodecData is codec data for json RPC.
 type CodecData struct {
 	ID     int64
 	Method string
@@ -64,11 +64,12 @@
 	Data    interface{} `json:"data,omitempty"`
 }
 
+// Error decodes response error for a string.
 func (e *Error) Error() string {
 	buf, err := json.Marshal(e)
 	if err != nil {
-		msg, err := json.Marshal(err.Error())
-		if err != nil {
+		msg, retryErr := json.Marshal(err.Error())
+		if retryErr != nil {
 			msg = []byte("jsonrpc2.Error: json.Marshal failed")
 		}
 		return fmt.Sprintf(`{"code":%d,"message":%s}`, -32001, string(msg))
@@ -114,6 +115,7 @@
 	}
 }
 
+// Write codec data as byte.
 func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) {
 	// If return error: it will be returned as is for this call.
 	// Allow param to be only Array, Slice, Map or Struct.
@@ -122,10 +124,8 @@
 	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() {
@@ -133,12 +133,10 @@
 			}
 		case reflect.Array, reflect.Struct:
 		case reflect.Ptr:
-			switch k := reflect.TypeOf(param).Elem().Kind(); k {
+			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() {
@@ -146,7 +144,7 @@
 				}
 			case reflect.Array, reflect.Struct:
 			default:
-				return nil, perrors.New("unsupported param type: Ptr to " + k.String())
+				return nil, perrors.New("unsupported param type: Ptr to " + ptrK.String())
 			}
 		default:
 			return nil, perrors.New("unsupported param type: " + k.String())
@@ -170,6 +168,7 @@
 	return buf.Bytes(), nil
 }
 
+// Read bytes as structured data
 func (c *jsonClientCodec) Read(streamBytes []byte, x interface{}) error {
 	c.rsp.reset()
 
@@ -223,6 +222,7 @@
 	}
 }
 
+// UnmarshalJSON unmarshals JSON for server request.
 func (r *serverRequest) UnmarshalJSON(raw []byte) error {
 	r.reset()
 
@@ -281,7 +281,7 @@
 	Error   interface{}      `json:"error,omitempty"`
 }
 
-// ServerCodec ...
+// ServerCodec is codec data for request server.
 type ServerCodec struct {
 	req serverRequest
 }
@@ -296,7 +296,7 @@
 	return &ServerCodec{}
 }
 
-// ReadHeader ...
+// ReadHeader reads header and unmarshal to server codec
 func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error {
 	if header["HttpMethod"] != "POST" {
 		return &Error{Code: -32601, Message: "Method not found"}
@@ -328,7 +328,7 @@
 	return nil
 }
 
-// ReadBody ...
+// ReadBody reads @x as request body.
 func (c *ServerCodec) ReadBody(x interface{}) error {
 	// If x!=nil and return error e:
 	// - Write() will be called with e.Error() in r.Error
@@ -339,7 +339,7 @@
 		return nil
 	}
 
-	// 在这里把请求参数json 字符串转换成了相应的struct
+	// the request parameter JSON string is converted to the corresponding struct
 	params := []byte(*c.req.Params)
 	if err := json.Unmarshal(*c.req.Params, x); err != nil {
 		// Note: if c.request.Params is nil it's not an error, it's an optional member.
@@ -362,7 +362,7 @@
 	return nil
 }
 
-// NewError ...
+// NewError creates a error with @code and @message
 func NewError(code int, message string) *Error {
 	return &Error{Code: code, Message: message}
 }
@@ -380,6 +380,7 @@
 	}
 }
 
+// Write responses as byte
 func (c *ServerCodec) Write(errMsg string, x interface{}) ([]byte, error) {
 	// If return error: nothing happens.
 	// In r.Error will be "" or .Error() of error returned by:
diff --git a/protocol/jsonrpc/json_test.go b/protocol/jsonrpc/json_test.go
index ade7424..a3814e9 100644
--- a/protocol/jsonrpc/json_test.go
+++ b/protocol/jsonrpc/json_test.go
@@ -30,7 +30,7 @@
 	Test string
 }
 
-func TestJsonClientCodec_Write(t *testing.T) {
+func TestJsonClientCodecWrite(t *testing.T) {
 	cd := &CodecData{
 		ID:     1,
 		Method: "GetUser",
@@ -46,7 +46,7 @@
 	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 @@
 	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 @@
 	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_exporter.go b/protocol/jsonrpc/jsonrpc_exporter.go
index 7f8fd49..6f75f6a 100644
--- a/protocol/jsonrpc/jsonrpc_exporter.go
+++ b/protocol/jsonrpc/jsonrpc_exporter.go
@@ -28,23 +28,24 @@
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// JsonrpcExporter ...
+// JsonrpcExporter is JSON RPC exporter and  extends from base invoker.
 type JsonrpcExporter struct {
 	protocol.BaseExporter
 }
 
-// NewJsonrpcExporter ...
+// NewJsonrpcExporter creates JSON RPC exporter with @key, @invoker and @exporterMap
 func NewJsonrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *JsonrpcExporter {
 	return &JsonrpcExporter{
 		BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap),
 	}
 }
 
-// Unexport ...
+// Unexport exported JSON RPC service.
 func (je *JsonrpcExporter) Unexport() {
 	serviceId := je.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
+	interfaceName := je.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
 	je.BaseExporter.Unexport()
-	err := common.ServiceMap.UnRegister(JSONRPC, serviceId)
+	err := common.ServiceMap.UnRegister(interfaceName, JSONRPC, serviceId)
 	if err != nil {
 		logger.Errorf("[JsonrpcExporter.Unexport] error: %v", err)
 	}
diff --git a/protocol/jsonrpc/jsonrpc_invoker.go b/protocol/jsonrpc/jsonrpc_invoker.go
index b6e194c..d84b980 100644
--- a/protocol/jsonrpc/jsonrpc_invoker.go
+++ b/protocol/jsonrpc/jsonrpc_invoker.go
@@ -29,13 +29,13 @@
 	invocation_impl "github.com/apache/dubbo-go/protocol/invocation"
 )
 
-// JsonrpcInvoker ...
+// JsonrpcInvoker is JSON RPC invoker
 type JsonrpcInvoker struct {
 	protocol.BaseInvoker
 	client *HTTPClient
 }
 
-// NewJsonrpcInvoker ...
+// NewJsonrpcInvoker creates JSON RPC invoker with @url and @client
 func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker {
 	return &JsonrpcInvoker{
 		BaseInvoker: *protocol.NewBaseInvoker(url),
@@ -43,7 +43,7 @@
 	}
 }
 
-// Invoke ...
+// Invoke the JSON RPC invocation and return result.
 func (ji *JsonrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 
 	var (
diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go
index 9e08eed..d7124ca 100644
--- a/protocol/jsonrpc/jsonrpc_invoker_test.go
+++ b/protocol/jsonrpc/jsonrpc_invoker_test.go
@@ -34,9 +34,9 @@
 	"github.com/apache/dubbo-go/protocol/invocation"
 )
 
-func TestJsonrpcInvoker_Invoke(t *testing.T) {
+func TestJsonrpcInvokerInvoke(t *testing.T) {
 
-	methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{})
+	methods, err := common.ServiceMap.Register("UserProvider", "jsonrpc", &UserProvider{})
 	assert.NoError(t, err)
 	assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods)
 
diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go
index bed7099..90a6bf5 100644
--- a/protocol/jsonrpc/jsonrpc_protocol.go
+++ b/protocol/jsonrpc/jsonrpc_protocol.go
@@ -44,14 +44,14 @@
 
 var jsonrpcProtocol *JsonrpcProtocol
 
-// JsonrpcProtocol ...
+// JsonrpcProtocol is JSON RPC protocol.
 type JsonrpcProtocol struct {
 	protocol.BaseProtocol
 	serverMap  map[string]*Server
 	serverLock sync.Mutex
 }
 
-// NewJsonrpcProtocol ...
+// NewJsonrpcProtocol creates JSON RPC protocol
 func NewJsonrpcProtocol() *JsonrpcProtocol {
 	return &JsonrpcProtocol{
 		BaseProtocol: protocol.NewBaseProtocol(),
@@ -59,7 +59,7 @@
 	}
 }
 
-// Export ...
+// Export JSON RPC service  for remote invocation
 func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	url := invoker.GetUrl()
 	serviceKey := strings.TrimPrefix(url.Path, "/")
@@ -74,7 +74,7 @@
 	return exporter
 }
 
-// Refer ...
+// Refer a remote JSON PRC service from registry
 func (jp *JsonrpcProtocol) Refer(url common.URL) protocol.Invoker {
 	//default requestTimeout
 	var requestTimeout = config.GetConsumerConfig().RequestTimeout
@@ -93,7 +93,7 @@
 	return invoker
 }
 
-// Destroy ...
+// Destroy will destroy all invoker and exporter, so it only is called once.
 func (jp *JsonrpcProtocol) Destroy() {
 	logger.Infof("jsonrpcProtocol destroy.")
 
@@ -109,8 +109,8 @@
 func (jp *JsonrpcProtocol) openServer(url common.URL) {
 	_, ok := jp.serverMap[url.Location]
 	if !ok {
-		_, ok := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/"))
-		if !ok {
+		_, loadOk := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/"))
+		if !loadOk {
 			panic("[JsonrpcProtocol]" + url.Key() + "is not existing")
 		}
 
@@ -125,7 +125,7 @@
 	}
 }
 
-// GetProtocol ...
+// GetProtocol gets JSON RPC protocol.
 func GetProtocol() protocol.Protocol {
 	if jsonrpcProtocol == nil {
 		jsonrpcProtocol = NewJsonrpcProtocol()
diff --git a/protocol/jsonrpc/jsonrpc_protocol_test.go b/protocol/jsonrpc/jsonrpc_protocol_test.go
index c00bed1..10a9016 100644
--- a/protocol/jsonrpc/jsonrpc_protocol_test.go
+++ b/protocol/jsonrpc/jsonrpc_protocol_test.go
@@ -34,7 +34,7 @@
 	"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 @@
 	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/jsonrpc/server.go b/protocol/jsonrpc/server.go
index 8600f02..aa458a1 100644
--- a/protocol/jsonrpc/server.go
+++ b/protocol/jsonrpc/server.go
@@ -59,7 +59,7 @@
 	PathPrefix = byte('/')
 )
 
-// Server ...
+// Server is JSON RPC server wrapper
 type Server struct {
 	done chan struct{}
 	once sync.Once
@@ -69,7 +69,7 @@
 	timeout time.Duration
 }
 
-// NewServer ...
+// NewServer creates new JSON RPC server.
 func NewServer() *Server {
 	return &Server{
 		done: make(chan struct{}),
@@ -228,7 +228,7 @@
 	}
 }
 
-// Start ...
+// Start JSON RPC server then ready for accept request.
 func (s *Server) Start(url common.URL) {
 	listener, err := net.Listen("tcp", url.Location)
 	if err != nil {
@@ -255,7 +255,7 @@
 	}()
 }
 
-// Stop ...
+// Stop JSON RPC server, just can be call once.
 func (s *Server) Stop() {
 	s.once.Do(func() {
 		close(s.done)
@@ -349,9 +349,9 @@
 			constant.PATH_KEY:    path,
 			constant.VERSION_KEY: codec.req.Version}))
 		if err := result.Error(); err != nil {
-			rspStream, err := codec.Write(err.Error(), invalidRequest)
-			if err != nil {
-				return perrors.WithStack(err)
+			rspStream, codecErr := codec.Write(err.Error(), invalidRequest)
+			if codecErr != nil {
+				return perrors.WithStack(codecErr)
 			}
 			if errRsp := sendErrorResp(header, rspStream); errRsp != nil {
 				logger.Warnf("Exporter: sendErrorResp(header:%#v, error:%v) = error:%s",
diff --git a/protocol/mock/mock_invoker.go b/protocol/mock/mock_invoker.go
index f22b803..0c88b47 100644
--- a/protocol/mock/mock_invoker.go
+++ b/protocol/mock/mock_invoker.go
@@ -15,22 +15,6 @@
  * limitations under the License.
  */
 
-//  Licensed to the Apache Software Foundation (ASF) under one or more
-//  contributor license agreements.  See the NOTICE file distributed with
-//  this work for additional information regarding copyright ownership.
-//  The ASF licenses this file to You under the Apache License, Version 2.0
-//  (the "License"); you may not use this file except in compliance with
-//  the License.  You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//  Unless required by applicable law or agreed to in writing, software
-//  distributed under the License is distributed on an "AS IS" BASIS,
-//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//  See the License for the specific language governing permissions and
-//  limitations under the License.
-//
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: invoker.go
 
diff --git a/protocol/protocol.go b/protocol/protocol.go
index a873469..6bed5ec 100644
--- a/protocol/protocol.go
+++ b/protocol/protocol.go
@@ -29,15 +29,20 @@
 // Protocol
 // Extension - protocol
 type Protocol interface {
+	// Export service for remote invocation
 	Export(invoker Invoker) Exporter
+	// Refer a remote service
 	Refer(url common.URL) Invoker
+	// Destroy will destroy all invoker and exporter, so it only is called once.
 	Destroy()
 }
 
 // Exporter
 // wrapping invoker
 type Exporter interface {
+	// GetInvoker gets invoker.
 	GetInvoker() Invoker
+	// Unexport exported service.
 	Unexport()
 }
 
@@ -45,45 +50,45 @@
 // base protocol
 /////////////////////////////
 
-// BaseProtocol ...
+// BaseProtocol is default protocol implement.
 type BaseProtocol struct {
 	exporterMap *sync.Map
 	invokers    []Invoker
 }
 
-// NewBaseProtocol ...
+// NewBaseProtocol creates a new BaseProtocol
 func NewBaseProtocol() BaseProtocol {
 	return BaseProtocol{
 		exporterMap: new(sync.Map),
 	}
 }
 
-// SetExporterMap ...
+// SetExporterMap set @exporter with @key to local memory.
 func (bp *BaseProtocol) SetExporterMap(key string, exporter Exporter) {
 	bp.exporterMap.Store(key, exporter)
 }
 
-// ExporterMap ...
+// ExporterMap gets exporter map.
 func (bp *BaseProtocol) ExporterMap() *sync.Map {
 	return bp.exporterMap
 }
 
-// SetInvokers ...
+// SetInvokers sets invoker into local memory
 func (bp *BaseProtocol) SetInvokers(invoker Invoker) {
 	bp.invokers = append(bp.invokers, invoker)
 }
 
-// Invokers ...
+// Invokers gets all invokers
 func (bp *BaseProtocol) Invokers() []Invoker {
 	return bp.invokers
 }
 
-// Export ...
+// Export is default export implement.
 func (bp *BaseProtocol) Export(invoker Invoker) Exporter {
 	return NewBaseExporter("base", invoker, bp.exporterMap)
 }
 
-// Refer ...
+// Refer is default refer implement.
 func (bp *BaseProtocol) Refer(url common.URL) Invoker {
 	return NewBaseInvoker(url)
 }
@@ -113,14 +118,14 @@
 // base exporter
 /////////////////////////////
 
-// BaseExporter ...
+// BaseExporter is default exporter implement.
 type BaseExporter struct {
 	key         string
 	invoker     Invoker
 	exporterMap *sync.Map
 }
 
-// NewBaseExporter ...
+// NewBaseExporter creates a new BaseExporter
 func NewBaseExporter(key string, invoker Invoker, exporterMap *sync.Map) *BaseExporter {
 	return &BaseExporter{
 		key:         key,
@@ -129,13 +134,13 @@
 	}
 }
 
-// GetInvoker ...
+// GetInvoker gets invoker
 func (de *BaseExporter) GetInvoker() Invoker {
 	return de.invoker
 
 }
 
-// Unexport ...
+// Unexport exported service.
 func (de *BaseExporter) Unexport() {
 	logger.Infof("Exporter unexport.")
 	de.invoker.Destroy()
diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go
index dedf8aa..2e9ffed 100644
--- a/protocol/protocolwrapper/mock_protocol_filter.go
+++ b/protocol/protocolwrapper/mock_protocol_filter.go
@@ -28,19 +28,22 @@
 
 type mockProtocolFilter struct{}
 
-// NewMockProtocolFilter ...
+// NewMockProtocolFilter creates a new mock protocol
 func NewMockProtocolFilter() protocol.Protocol {
 	return &mockProtocolFilter{}
 }
 
+// Export mock service for  remote invocation
 func (pfw *mockProtocolFilter) Export(invoker protocol.Invoker) protocol.Exporter {
 	return protocol.NewBaseExporter("key", invoker, &sync.Map{})
 }
 
+// Refer a mock remote service
 func (pfw *mockProtocolFilter) Refer(url common.URL) protocol.Invoker {
 	return protocol.NewBaseInvoker(url)
 }
 
+// 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 70d2da0..cba1d5d 100644
--- a/protocol/protocolwrapper/protocol_filter_wrapper.go
+++ b/protocol/protocolwrapper/protocol_filter_wrapper.go
@@ -31,7 +31,7 @@
 )
 
 const (
-	// FILTER ...
+	// FILTER is protocol key.
 	FILTER = "filter"
 )
 
@@ -45,7 +45,7 @@
 	protocol protocol.Protocol
 }
 
-// Export ...
+// Export service for remote invocation
 func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Exporter {
 	if pfw.protocol == nil {
 		pfw.protocol = extension.GetProtocol(invoker.GetUrl().Protocol)
@@ -54,7 +54,7 @@
 	return pfw.protocol.Export(invoker)
 }
 
-// Refer ...
+// Refer a remote service
 func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker {
 	if pfw.protocol == nil {
 		pfw.protocol = extension.GetProtocol(url.Protocol)
@@ -62,7 +62,7 @@
 	return buildInvokerChain(pfw.protocol.Refer(url), constant.REFERENCE_FILTER_KEY)
 }
 
-// Destroy ...
+// Destroy will destroy all invoker and exporter.
 func (pfw *ProtocolFilterWrapper) Destroy() {
 	pfw.protocol.Destroy()
 }
diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go
index 8491d57..b03ea7b 100644
--- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go
+++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go
@@ -36,7 +36,7 @@
 	"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 @@
 	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 aa6c231..f301d94 100644
--- a/protocol/rest/client/client_impl/resty_client.go
+++ b/protocol/rest/client/client_impl/resty_client.go
@@ -40,15 +40,17 @@
 	extension.SetRestClient(constant.DEFAULT_REST_CLIENT, NewRestyClient)
 }
 
+// RestyClient a rest client implement by Resty
 type RestyClient struct {
 	client *resty.Client
 }
 
+// NewRestyClient a constructor of RestyClient
 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
@@ -65,21 +67,21 @@
 	}
 }
 
-func (rc *RestyClient) Do(restRequest *client.RestRequest, res interface{}) error {
-	r, err := rc.client.R().
-		SetHeader("Content-Type", restRequest.Consumes).
-		SetHeader("Accept", restRequest.Produces).
+// Do send request by RestyClient
+func (rc *RestyClient) Do(restRequest *client.RestClientRequest, res interface{}) error {
+	req := rc.client.R()
+	req.Header = restRequest.Header
+	resp, err := req.
 		SetPathParams(restRequest.PathParams).
 		SetQueryParams(restRequest.QueryParams).
-		SetHeaders(restRequest.Headers).
 		SetBody(restRequest.Body).
 		SetResult(res).
 		Execute(restRequest.Method, "http://"+path.Join(restRequest.Location, restRequest.Path))
 	if err != nil {
 		return perrors.WithStack(err)
 	}
-	if r.IsError() {
-		return perrors.New(r.String())
+	if resp.IsError() {
+		return perrors.New(resp.String())
 	}
 	return nil
 }
diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go
index 7d020ab..d63c5e0 100644
--- a/protocol/rest/client/rest_client.go
+++ b/protocol/rest/client/rest_client.go
@@ -18,26 +18,28 @@
 package client
 
 import (
+	"net/http"
 	"time"
 )
 
+// RestOptions
 type RestOptions struct {
 	RequestTimeout time.Duration
 	ConnectTimeout time.Duration
 }
 
-type RestRequest struct {
+// RestClientRequest
+type RestClientRequest struct {
+	Header      http.Header
 	Location    string
 	Path        string
-	Produces    string
-	Consumes    string
 	Method      string
 	PathParams  map[string]string
 	QueryParams map[string]string
 	Body        interface{}
-	Headers     map[string]string
 }
 
+// RestClient user can implement this client interface to send request
 type RestClient interface {
-	Do(request *RestRequest, res interface{}) error
+	Do(request *RestClientRequest, res interface{}) error
 }
diff --git a/protocol/rest/config/reader/rest_config_reader_test.go b/protocol/rest/config/reader/rest_config_reader_test.go
index d2dba40..c6f8912 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 @@
 	"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 @@
 	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/rest_exporter.go b/protocol/rest/rest_exporter.go
index 470d525..1ee2086 100644
--- a/protocol/rest/rest_exporter.go
+++ b/protocol/rest/rest_exporter.go
@@ -40,8 +40,9 @@
 
 func (re *RestExporter) Unexport() {
 	serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "")
+	interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "")
 	re.BaseExporter.Unexport()
-	err := common.ServiceMap.UnRegister(REST, serviceId)
+	err := common.ServiceMap.UnRegister(interfaceName, REST, serviceId)
 	if err != nil {
 		logger.Errorf("[RestExporter.Unexport] error: %v", err)
 	}
diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go
index 0c82035..121d121 100644
--- a/protocol/rest/rest_invoker.go
+++ b/protocol/rest/rest_invoker.go
@@ -20,6 +20,7 @@
 import (
 	"context"
 	"fmt"
+	"net/http"
 )
 
 import (
@@ -56,7 +57,7 @@
 		body        interface{}
 		pathParams  map[string]string
 		queryParams map[string]string
-		headers     map[string]string
+		header      http.Header
 		err         error
 	)
 	if methodConfig == nil {
@@ -71,24 +72,21 @@
 		result.Err = err
 		return &result
 	}
-	if headers, err = restStringMapTransform(methodConfig.HeadersMap, inv.Arguments()); err != nil {
+	if header, err = getRestHttpHeader(methodConfig, inv.Arguments()); err != nil {
 		result.Err = err
 		return &result
 	}
 	if len(inv.Arguments()) > methodConfig.Body && methodConfig.Body >= 0 {
 		body = inv.Arguments()[methodConfig.Body]
 	}
-
-	req := &client.RestRequest{
+	req := &client.RestClientRequest{
 		Location:    ri.GetUrl().Location,
-		Produces:    methodConfig.Produces,
-		Consumes:    methodConfig.Consumes,
 		Method:      methodConfig.MethodType,
 		Path:        methodConfig.Path,
 		PathParams:  pathParams,
 		QueryParams: queryParams,
 		Body:        body,
-		Headers:     headers,
+		Header:      header,
 	}
 	result.Err = ri.client.Do(req, inv.Reply())
 	if result.Err == nil {
@@ -107,3 +105,17 @@
 	}
 	return resMap, nil
 }
+
+func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) {
+	header := http.Header{}
+	headersMap := methodConfig.HeadersMap
+	header.Set("Content-Type", methodConfig.Consumes)
+	header.Set("Accept", methodConfig.Produces)
+	for k, v := range headersMap {
+		if k >= len(args) || k < 0 {
+			return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k)
+		}
+		header.Set(v, fmt.Sprint(args[k]))
+	}
+	return header, nil
+}
diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go
index e44c5d9..9df97a2 100644
--- a/protocol/rest/rest_invoker_test.go
+++ b/protocol/rest/rest_invoker_test.go
@@ -39,7 +39,15 @@
 	"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,13 +63,9 @@
 		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(url.Protocol, &UserProvider{})
+	_, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{})
 	assert.NoError(t, err)
 	con := config.ProviderConfig{}
 	config.SetProviderConfig(con)
@@ -206,6 +210,6 @@
 	assert.Error(t, res.Error(), "test error")
 
 	assert.Equal(t, filterNum, 12)
-	err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider")
+	err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider")
 	assert.NoError(t, err)
 }
diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go
index 47ecb60..e15eeb3 100644
--- a/protocol/rest/rest_protocol.go
+++ b/protocol/rest/rest_protocol.go
@@ -75,7 +75,9 @@
 	}
 	rp.SetExporterMap(serviceKey, exporter)
 	restServer := rp.getServer(url, restServiceConfig.Server)
-	restServer.Deploy(invoker, restServiceConfig.RestMethodConfigsMap)
+	for _, methodConfig := range restServiceConfig.RestMethodConfigsMap {
+		restServer.Deploy(methodConfig, server.GetRouteFunc(invoker, methodConfig))
+	}
 	return exporter
 }
 
diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go
index 8af73a1..9ff4e7d 100644
--- a/protocol/rest/rest_protocol_test.go
+++ b/protocol/rest/rest_protocol_test.go
@@ -38,14 +38,10 @@
 	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,16 +67,12 @@
 	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(url.Protocol, &UserProvider{})
+	_, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{})
 	assert.NoError(t, err)
 	con := config.ProviderConfig{}
 	config.SetProviderConfig(con)
@@ -128,7 +120,7 @@
 	proto.Destroy()
 	_, ok = proto.(*RestProtocol).serverMap[url.Location]
 	assert.False(t, ok)
-	err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider")
+	err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider")
 	assert.NoError(t, err)
 }
 
diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go
index c10c98a..fbd6fb7 100644
--- a/protocol/rest/server/rest_server.go
+++ b/protocol/rest/server/rest_server.go
@@ -18,14 +18,305 @@
 package server
 
 import (
-	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/protocol"
-	"github.com/apache/dubbo-go/protocol/rest/config"
+	"context"
+	"errors"
+	"net/http"
+	"reflect"
+	"strconv"
+	"strings"
 )
 
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+	rest_config "github.com/apache/dubbo-go/protocol/rest/config"
+)
+
+const parseParameterErrorStr = "An error occurred while parsing parameters on the server"
+
+// RestServer user can implement this server interface
 type RestServer interface {
+	// Start rest server
 	Start(url common.URL)
-	Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig)
-	UnDeploy(restMethodConfig map[string]*config.RestMethodConfig)
+	// Deploy a http api
+	Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse))
+	// UnDeploy a http api
+	UnDeploy(restMethodConfig *rest_config.RestMethodConfig)
+	// Destroy rest server
 	Destroy()
 }
+
+// RestServerRequest interface
+type RestServerRequest interface {
+	// RawRequest get the Ptr of http.Request
+	RawRequest() *http.Request
+	// PathParameter get the path parameter by name
+	PathParameter(name string) string
+	// PathParameters get the map of the path parameters
+	PathParameters() map[string]string
+	// QueryParameter get the query parameter by name
+	QueryParameter(name string) string
+	// QueryParameters get the map of query parameters
+	QueryParameters(name string) []string
+	// BodyParameter get the body parameter of name
+	BodyParameter(name string) (string, error)
+	// HeaderParameter get the header parameter of name
+	HeaderParameter(name string) string
+	// ReadEntity checks the Accept header and reads the content into the entityPointer.
+	ReadEntity(entityPointer interface{}) error
+}
+
+// RestServerResponse interface
+type RestServerResponse interface {
+	http.ResponseWriter
+	// WriteError writes the http status and the error string on the response. err can be nil.
+	// Return an error if writing was not successful.
+	WriteError(httpStatus int, err error) (writeErr error)
+	// WriteEntity marshals the value using the representation denoted by the Accept Header.
+	WriteEntity(value interface{}) error
+}
+
+// GetRouteFunc
+// A route function will be invoked by http server
+func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) {
+	return func(req RestServerRequest, resp RestServerResponse) {
+		var (
+			err  error
+			args []interface{}
+		)
+		svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/"))
+		// get method
+		method := svc.Method()[methodConfig.MethodName]
+		argsTypes := method.ArgsType()
+		replyType := method.ReplyType()
+		// two ways to prepare arguments
+		// if method like this 'func1(req []interface{}, rsp *User) error'
+		// we don't have arguments type
+		if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) &&
+			argsTypes[0].String() == "[]interface {}" {
+			args, err = getArgsInterfaceFromRequest(req, methodConfig)
+		} else {
+			args, err = getArgsFromRequest(req, argsTypes, methodConfig)
+		}
+		if err != nil {
+			logger.Errorf("[Go Restful] parsing http parameters error:%v", err)
+			err = resp.WriteError(http.StatusInternalServerError, errors.New(parseParameterErrorStr))
+			if err != nil {
+				logger.Errorf("[Go Restful] WriteErrorString error:%v", err)
+			}
+		}
+		result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string)))
+		if result.Error() != nil {
+			err = resp.WriteError(http.StatusInternalServerError, result.Error())
+			if err != nil {
+				logger.Errorf("[Go Restful] WriteError error:%v", err)
+			}
+			return
+		}
+		err = resp.WriteEntity(result.Result())
+		if err != nil {
+			logger.Errorf("[Go Restful] WriteEntity error:%v", err)
+		}
+	}
+}
+
+// getArgsInterfaceFromRequest when service function like GetUser(req []interface{}, rsp *User) error
+// use this method to get arguments
+func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) {
+	argsMap := make(map[int]interface{}, 8)
+	maxKey := 0
+	for k, v := range methodConfig.PathParamsMap {
+		if maxKey < k {
+			maxKey = k
+		}
+		argsMap[k] = req.PathParameter(v)
+	}
+	for k, v := range methodConfig.QueryParamsMap {
+		if maxKey < k {
+			maxKey = k
+		}
+		params := req.QueryParameters(v)
+		if len(params) == 1 {
+			argsMap[k] = params[0]
+		} else {
+			argsMap[k] = params
+		}
+	}
+	for k, v := range methodConfig.HeadersMap {
+		if maxKey < k {
+			maxKey = k
+		}
+		argsMap[k] = req.HeaderParameter(v)
+	}
+	if methodConfig.Body >= 0 {
+		if maxKey < methodConfig.Body {
+			maxKey = methodConfig.Body
+		}
+		m := make(map[string]interface{})
+		// TODO read as a slice
+		if err := req.ReadEntity(&m); err != nil {
+			return nil, perrors.Errorf("[Go restful] Read body entity as map[string]interface{} error:%v", err)
+		}
+		argsMap[methodConfig.Body] = m
+	}
+	args := make([]interface{}, maxKey+1)
+	for k, v := range argsMap {
+		if k >= 0 {
+			args[k] = v
+		}
+	}
+	return args, nil
+}
+
+// getArgsFromRequest get arguments from server.RestServerRequest
+func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) {
+	argsLength := len(argsTypes)
+	args := make([]interface{}, argsLength)
+	for i, t := range argsTypes {
+		args[i] = reflect.Zero(t).Interface()
+	}
+	if err := assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args); err != nil {
+		return nil, err
+	}
+	if err := assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args); err != nil {
+		return nil, err
+	}
+	if err := assembleArgsFromBody(methodConfig, argsTypes, req, args); err != nil {
+		return nil, err
+	}
+	if err := assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args); err != nil {
+		return nil, err
+	}
+	return args, nil
+}
+
+// assembleArgsFromHeaders assemble arguments from headers
+func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) error {
+	for k, v := range methodConfig.HeadersMap {
+		param := req.HeaderParameter(v)
+		if k < 0 || k >= argsLength {
+			return perrors.Errorf("[Go restful] Header param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
+		}
+		t := argsTypes[k]
+		if t.Kind() == reflect.Ptr {
+			t = t.Elem()
+		}
+		if t.Kind() == reflect.String {
+			args[k] = param
+		} else {
+			return perrors.Errorf("[Go restful] Header param parse error, the index %v args's type isn't string", k)
+		}
+	}
+	return nil
+}
+
+// assembleArgsFromBody assemble arguments from body
+func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
+	if methodConfig.Body >= 0 && methodConfig.Body < len(argsTypes) {
+		t := argsTypes[methodConfig.Body]
+		kind := t.Kind()
+		if kind == reflect.Ptr {
+			t = t.Elem()
+		}
+		var ni interface{}
+		if t.String() == "[]interface {}" {
+			ni = make([]map[string]interface{}, 0)
+		} else if t.String() == "interface {}" {
+			ni = make(map[string]interface{})
+		} else {
+			n := reflect.New(t)
+			if n.CanInterface() {
+				ni = n.Interface()
+			}
+		}
+		if err := req.ReadEntity(&ni); err != nil {
+			return perrors.Errorf("[Go restful] Read body entity error, error is %v", perrors.WithStack(err))
+		}
+		args[methodConfig.Body] = ni
+	}
+	return nil
+}
+
+// assembleArgsFromQueryParams assemble arguments from query params
+func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
+	var (
+		err   error
+		param interface{}
+		i64   int64
+	)
+	for k, v := range methodConfig.QueryParamsMap {
+		if k < 0 || k >= argsLength {
+			return perrors.Errorf("[Go restful] Query param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
+		}
+		t := argsTypes[k]
+		kind := t.Kind()
+		if kind == reflect.Ptr {
+			t = t.Elem()
+		}
+		if kind == reflect.Slice {
+			param = req.QueryParameters(v)
+		} else if kind == reflect.String {
+			param = req.QueryParameter(v)
+		} else if kind == reflect.Int {
+			param, err = strconv.Atoi(req.QueryParameter(v))
+		} else if kind == reflect.Int32 {
+			i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32)
+			if err == nil {
+				param = int32(i64)
+			}
+		} else if kind == reflect.Int64 {
+			param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64)
+		} else {
+			return perrors.Errorf("[Go restful] Query param parse error, the index %v args's type isn't int or string or slice", k)
+		}
+		if err != nil {
+			return perrors.Errorf("[Go restful] Query param parse error, error:%v", perrors.WithStack(err))
+		}
+		args[k] = param
+	}
+	return nil
+}
+
+// assembleArgsFromPathParams assemble arguments from path params
+func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
+	var (
+		err   error
+		param interface{}
+		i64   int64
+	)
+	for k, v := range methodConfig.PathParamsMap {
+		if k < 0 || k >= argsLength {
+			return perrors.Errorf("[Go restful] Path param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
+		}
+		t := argsTypes[k]
+		kind := t.Kind()
+		if kind == reflect.Ptr {
+			t = t.Elem()
+		}
+		if kind == reflect.Int {
+			param, err = strconv.Atoi(req.PathParameter(v))
+		} else if kind == reflect.Int32 {
+			i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32)
+			if err == nil {
+				param = int32(i64)
+			}
+		} else if kind == reflect.Int64 {
+			param, err = strconv.ParseInt(req.PathParameter(v), 10, 64)
+		} else if kind == reflect.String {
+			param = req.PathParameter(v)
+		} else {
+			return perrors.Errorf("[Go restful] Path param parse error, the index %v args's type isn't int or string", k)
+		}
+		if err != nil {
+			return perrors.Errorf("[Go restful] Path param parse error, error is %v", perrors.WithStack(err))
+		}
+		args[k] = param
+	}
+	return nil
+}
diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go
index 69f36a5..c7d971f 100644
--- a/protocol/rest/server/server_impl/go_restful_server.go
+++ b/protocol/rest/server/server_impl/go_restful_server.go
@@ -22,8 +22,6 @@
 	"fmt"
 	"net"
 	"net/http"
-	"reflect"
-	"strconv"
 	"strings"
 	"time"
 )
@@ -38,27 +36,29 @@
 	"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/protocol"
-	"github.com/apache/dubbo-go/protocol/invocation"
 	"github.com/apache/dubbo-go/protocol/rest/config"
 	"github.com/apache/dubbo-go/protocol/rest/server"
 )
 
 func init() {
-	extension.SetRestServer(constant.DEFAULT_REST_SERVER, GetNewGoRestfulServer)
+	extension.SetRestServer(constant.DEFAULT_REST_SERVER, NewGoRestfulServer)
 }
 
 var filterSlice []restful.FilterFunction
 
+// GoRestfulServer a rest server implement by go-restful
 type GoRestfulServer struct {
 	srv       *http.Server
 	container *restful.Container
 }
 
-func NewGoRestfulServer() *GoRestfulServer {
+// NewGoRestfulServer a constructor of GoRestfulServer
+func NewGoRestfulServer() server.RestServer {
 	return &GoRestfulServer{}
 }
 
+// Start go-restful server
+// It will add all go-restful filters
 func (grs *GoRestfulServer) Start(url common.URL) {
 	grs.container = restful.NewContainer()
 	for _, filter := range filterSlice {
@@ -80,61 +80,32 @@
 	}()
 }
 
-func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) {
-	svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/"))
-	for methodName, config := range restMethodConfig {
-		// get method
-		method := svc.Method()[methodName]
-		argsTypes := method.ArgsType()
-		replyType := method.ReplyType()
-		ws := new(restful.WebService)
-		ws.Path(config.Path).
-			Produces(strings.Split(config.Produces, ",")...).
-			Consumes(strings.Split(config.Consumes, ",")...).
-			Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, argsTypes, replyType, config)))
-		grs.container.Add(ws)
+// Publish a http api in go-restful server
+// The routeFunc should be invoked when the server receive a request
+func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) {
+	ws := &restful.WebService{}
+	rf := func(req *restful.Request, resp *restful.Response) {
+		routeFunc(NewGoRestfulRequestAdapter(req), resp)
 	}
+	ws.Path(restMethodConfig.Path).
+		Produces(strings.Split(restMethodConfig.Produces, ",")...).
+		Consumes(strings.Split(restMethodConfig.Consumes, ",")...).
+		Route(ws.Method(restMethodConfig.MethodType).To(rf))
+	grs.container.Add(ws)
 
 }
 
-func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type,
-	replyType reflect.Type, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) {
-	return func(req *restful.Request, resp *restful.Response) {
-		var (
-			err  error
-			args []interface{}
-		)
-		if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) &&
-			argsTypes[0].String() == "[]interface {}" {
-			args = getArgsInterfaceFromRequest(req, config)
-		} else {
-			args = getArgsFromRequest(req, argsTypes, config)
-		}
-		result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string)))
-		if result.Error() != nil {
-			err = resp.WriteError(http.StatusInternalServerError, result.Error())
-			if err != nil {
-				logger.Errorf("[Go Restful] WriteError error:%v", err)
-			}
-			return
-		}
-		err = resp.WriteEntity(result.Result())
-		if err != nil {
-			logger.Error("[Go Restful] WriteEntity error:%v", err)
-		}
-	}
-}
-func (grs *GoRestfulServer) UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) {
-	for _, config := range restMethodConfig {
-		ws := new(restful.WebService)
-		ws.Path(config.Path)
-		err := grs.container.Remove(ws)
-		if err != nil {
-			logger.Warnf("[Go restful] Remove web service error:%v", err)
-		}
+// Delete a http api in go-restful server
+func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) {
+	ws := new(restful.WebService)
+	ws.Path(restMethodConfig.Path)
+	err := grs.container.Remove(ws)
+	if err != nil {
+		logger.Warnf("[Go restful] Remove web service error:%v", err)
 	}
 }
 
+// Destroy the go-restful server
 func (grs *GoRestfulServer) Destroy() {
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
@@ -144,179 +115,59 @@
 	logger.Infof("[Go Restful] Server exiting")
 }
 
-func getArgsInterfaceFromRequest(req *restful.Request, config *config.RestMethodConfig) []interface{} {
-	argsMap := make(map[int]interface{}, 8)
-	maxKey := 0
-	for k, v := range config.PathParamsMap {
-		if maxKey < k {
-			maxKey = k
-		}
-		argsMap[k] = req.PathParameter(v)
-	}
-	for k, v := range config.QueryParamsMap {
-		if maxKey < k {
-			maxKey = k
-		}
-		params := req.QueryParameters(v)
-		if len(params) == 1 {
-			argsMap[k] = params[0]
-		} else {
-			argsMap[k] = params
-		}
-	}
-	for k, v := range config.HeadersMap {
-		if maxKey < k {
-			maxKey = k
-		}
-		argsMap[k] = req.HeaderParameter(v)
-	}
-	if config.Body >= 0 {
-		if maxKey < config.Body {
-			maxKey = config.Body
-		}
-		m := make(map[string]interface{})
-		// TODO read as a slice
-		if err := req.ReadEntity(&m); err != nil {
-			logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err))
-		} else {
-			argsMap[config.Body] = m
-		}
-	}
-	args := make([]interface{}, maxKey+1)
-	for k, v := range argsMap {
-		if k >= 0 {
-			args[k] = v
-		}
-	}
-	return args
-}
-
-func getArgsFromRequest(req *restful.Request, argsTypes []reflect.Type, config *config.RestMethodConfig) []interface{} {
-	argsLength := len(argsTypes)
-	args := make([]interface{}, argsLength)
-	for i, t := range argsTypes {
-		args[i] = reflect.Zero(t).Interface()
-	}
-	var (
-		err   error
-		param interface{}
-		i64   int64
-	)
-	for k, v := range config.PathParamsMap {
-		if k < 0 || k >= argsLength {
-			logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k)
-			continue
-		}
-		t := argsTypes[k]
-		kind := t.Kind()
-		if kind == reflect.Ptr {
-			t = t.Elem()
-		}
-		if kind == reflect.Int {
-			param, err = strconv.Atoi(req.PathParameter(v))
-		} else if kind == reflect.Int32 {
-			i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32)
-			if err == nil {
-				param = int32(i64)
-			}
-		} else if kind == reflect.Int64 {
-			param, err = strconv.ParseInt(req.PathParameter(v), 10, 64)
-		} else if kind == reflect.String {
-			param = req.PathParameter(v)
-		} else {
-			logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k)
-			continue
-		}
-		if err != nil {
-			logger.Errorf("[Go restful] Path param parse error, error is %v", err)
-			continue
-		}
-		args[k] = param
-	}
-	for k, v := range config.QueryParamsMap {
-		if k < 0 || k >= argsLength {
-			logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k)
-			continue
-		}
-		t := argsTypes[k]
-		kind := t.Kind()
-		if kind == reflect.Ptr {
-			t = t.Elem()
-		}
-		if kind == reflect.Slice {
-			param = req.QueryParameters(v)
-		} else if kind == reflect.String {
-			param = req.QueryParameter(v)
-		} else if kind == reflect.Int {
-			param, err = strconv.Atoi(req.QueryParameter(v))
-		} else if kind == reflect.Int32 {
-			i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32)
-			if err == nil {
-				param = int32(i64)
-			}
-		} else if kind == reflect.Int64 {
-			param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64)
-		} else {
-			logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k)
-			continue
-		}
-		if err != nil {
-			logger.Errorf("[Go restful] Query param parse error, error is %v", err)
-			continue
-		}
-		args[k] = param
-	}
-
-	if config.Body >= 0 && config.Body < len(argsTypes) {
-		t := argsTypes[config.Body]
-		kind := t.Kind()
-		if kind == reflect.Ptr {
-			t = t.Elem()
-		}
-		var ni interface{}
-		if t.String() == "[]interface {}" {
-			ni = make([]map[string]interface{}, 0)
-		} else if t.String() == "interface {}" {
-			ni = make(map[string]interface{})
-		} else {
-			n := reflect.New(t)
-			if n.CanInterface() {
-				ni = n.Interface()
-			}
-		}
-		if err := req.ReadEntity(&ni); err != nil {
-			logger.Errorf("[Go restful] Read body entity error:%v", err)
-		} else {
-			args[config.Body] = ni
-		}
-	}
-
-	for k, v := range config.HeadersMap {
-		param := req.HeaderParameter(v)
-		if k < 0 || k >= argsLength {
-			logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k)
-			continue
-		}
-		t := argsTypes[k]
-		if t.Kind() == reflect.Ptr {
-			t = t.Elem()
-		}
-		if t.Kind() == reflect.String {
-			args[k] = param
-		} else {
-			logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k)
-		}
-	}
-
-	return args
-}
-
-func GetNewGoRestfulServer() server.RestServer {
-	return NewGoRestfulServer()
-}
-
-// Let user addFilter
+// AddGoRestfulServerFilter let user add the http server of go-restful
 // addFilter should before config.Load()
 func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) {
 	filterSlice = append(filterSlice, filterFuc)
 }
+
+// GoRestfulRequestAdapter a adapter struct about RestServerRequest
+type GoRestfulRequestAdapter struct {
+	server.RestServerRequest
+	request *restful.Request
+}
+
+// NewGoRestfulRequestAdapter a constructor of GoRestfulRequestAdapter
+func NewGoRestfulRequestAdapter(request *restful.Request) *GoRestfulRequestAdapter {
+	return &GoRestfulRequestAdapter{request: request}
+}
+
+// RawRequest a adapter function of server.RestServerRequest's RawRequest
+func (grra *GoRestfulRequestAdapter) RawRequest() *http.Request {
+	return grra.request.Request
+}
+
+// PathParameter a adapter function of server.RestServerRequest's PathParameter
+func (grra *GoRestfulRequestAdapter) PathParameter(name string) string {
+	return grra.request.PathParameter(name)
+}
+
+// PathParameters a adapter function of server.RestServerRequest's QueryParameter
+func (grra *GoRestfulRequestAdapter) PathParameters() map[string]string {
+	return grra.request.PathParameters()
+}
+
+// QueryParameter a adapter function of server.RestServerRequest's QueryParameters
+func (grra *GoRestfulRequestAdapter) QueryParameter(name string) string {
+	return grra.request.QueryParameter(name)
+}
+
+// QueryParameters a adapter function of server.RestServerRequest's QueryParameters
+func (grra *GoRestfulRequestAdapter) QueryParameters(name string) []string {
+	return grra.request.QueryParameters(name)
+}
+
+// BodyParameter a adapter function of server.RestServerRequest's BodyParameter
+func (grra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) {
+	return grra.request.BodyParameter(name)
+}
+
+// HeaderParameter a adapter function of server.RestServerRequest's HeaderParameter
+func (grra *GoRestfulRequestAdapter) HeaderParameter(name string) string {
+	return grra.request.HeaderParameter(name)
+}
+
+// ReadEntity a adapter func of server.RestServerRequest's ReadEntity
+func (grra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error {
+	return grra.request.ReadEntity(entityPointer)
+}
diff --git a/protocol/result.go b/protocol/result.go
index 34e76d2..2e7a6e4 100644
--- a/protocol/result.go
+++ b/protocol/result.go
@@ -17,15 +17,23 @@
 
 package protocol
 
-// Result ...
+// Result is a RPC result
 type Result interface {
+	// SetError sets error.
 	SetError(error)
+	// Error gets error.
 	Error() error
+	// SetResult sets invoker result.
 	SetResult(interface{})
+	// Result gets invoker result.
 	Result() interface{}
+	// SetAttachments replaces the existing attachments with the specified param.
 	SetAttachments(map[string]string)
+	// Attachments gets all attachments
 	Attachments() map[string]string
+	// AddAttachment adds the specified map to existing attachments in this instance.
 	AddAttachment(string, string)
+	// Attachment gets attachment by key with default value.
 	Attachment(string, string) string
 }
 
@@ -33,48 +41,49 @@
 // Result Impletment of RPC
 /////////////////////////////
 
-// RPCResult ...
+// RPCResult is default RPC result.
 type RPCResult struct {
 	Attrs map[string]string
 	Err   error
 	Rest  interface{}
 }
 
-// SetError ...
+// SetError sets error.
 func (r *RPCResult) SetError(err error) {
 	r.Err = err
 }
 
+// Error gets error.
 func (r *RPCResult) Error() error {
 	return r.Err
 }
 
-// SetResult ...
+// SetResult sets invoker result.
 func (r *RPCResult) SetResult(rest interface{}) {
 	r.Rest = rest
 }
 
-// Result ...
+// Result gets invoker result.
 func (r *RPCResult) Result() interface{} {
 	return r.Rest
 }
 
-// SetAttachments ...
+// SetAttachments replaces the existing attachments with the specified param.
 func (r *RPCResult) SetAttachments(attr map[string]string) {
 	r.Attrs = attr
 }
 
-// Attachments ...
+// Attachments gets all attachments
 func (r *RPCResult) Attachments() map[string]string {
 	return r.Attrs
 }
 
-// AddAttachment ...
+// AddAttachment adds the specified map to existing attachments in this instance.
 func (r *RPCResult) AddAttachment(key, value string) {
 	r.Attrs[key] = value
 }
 
-// Attachment ...
+// Attachment gets attachment by key with default value.
 func (r *RPCResult) Attachment(key, defaultValue string) string {
 	v, ok := r.Attrs[key]
 	if !ok {
diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go
index 13be47c..60becfb 100644
--- a/protocol/rpc_status.go
+++ b/protocol/rpc_status.go
@@ -32,7 +32,7 @@
 	serviceStatistic sync.Map // url -> RPCStatus
 )
 
-// RPCStatus ...
+// RPCStatus is URL statistics.
 type RPCStatus struct {
 	active                        int32
 	failed                        int32
@@ -46,63 +46,63 @@
 	lastRequestFailedTimestamp    int64
 }
 
-// GetActive ...
+// GetActive gets active.
 func (rpc *RPCStatus) GetActive() int32 {
 	return atomic.LoadInt32(&rpc.active)
 }
 
-// GetFailed ...
+// GetFailed gets failed.
 func (rpc *RPCStatus) GetFailed() int32 {
 	return atomic.LoadInt32(&rpc.failed)
 }
 
-// GetTotal ...
+// GetTotal gets total.
 func (rpc *RPCStatus) GetTotal() int32 {
 	return atomic.LoadInt32(&rpc.total)
 }
 
-// GetTotalElapsed ...
+// GetTotalElapsed gets total elapsed.
 func (rpc *RPCStatus) GetTotalElapsed() int64 {
 	return atomic.LoadInt64(&rpc.totalElapsed)
 }
 
-// GetFailedElapsed ...
+// GetFailedElapsed gets failed elapsed.
 func (rpc *RPCStatus) GetFailedElapsed() int64 {
 	return atomic.LoadInt64(&rpc.failedElapsed)
 }
 
-// GetMaxElapsed ...
+// GetMaxElapsed gets max elapsed.
 func (rpc *RPCStatus) GetMaxElapsed() int64 {
 	return atomic.LoadInt64(&rpc.maxElapsed)
 }
 
-// GetFailedMaxElapsed ...
+// GetFailedMaxElapsed gets failed max elapsed.
 func (rpc *RPCStatus) GetFailedMaxElapsed() int64 {
 	return atomic.LoadInt64(&rpc.failedMaxElapsed)
 }
 
-// GetSucceededMaxElapsed ...
+// GetSucceededMaxElapsed gets succeeded max elapsed.
 func (rpc *RPCStatus) GetSucceededMaxElapsed() int64 {
 	return atomic.LoadInt64(&rpc.succeededMaxElapsed)
 }
 
-// GetLastRequestFailedTimestamp ...
+// GetLastRequestFailedTimestamp gets last request failed timestamp.
 func (rpc *RPCStatus) GetLastRequestFailedTimestamp() int64 {
 	return atomic.LoadInt64(&rpc.lastRequestFailedTimestamp)
 }
 
-// GetSuccessiveRequestFailureCount ...
+// GetSuccessiveRequestFailureCount gets successive request failure count.
 func (rpc *RPCStatus) GetSuccessiveRequestFailureCount() int32 {
 	return atomic.LoadInt32(&rpc.successiveRequestFailureCount)
 }
 
-// GetURLStatus ...
+// GetURLStatus get URL RPC status.
 func GetURLStatus(url common.URL) *RPCStatus {
 	rpcStatus, _ := serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{})
 	return rpcStatus.(*RPCStatus)
 }
 
-// GetMethodStatus ...
+// GetMethodStatus get method RPC status.
 func GetMethodStatus(url common.URL, methodName string) *RPCStatus {
 	identifier := url.Key()
 	methodMap, found := methodStatistics.Load(identifier)
@@ -122,13 +122,13 @@
 	return status
 }
 
-// BeginCount ...
+// BeginCount gets begin count.
 func BeginCount(url common.URL, methodName string) {
 	beginCount0(GetURLStatus(url))
 	beginCount0(GetMethodStatus(url, methodName))
 }
 
-// EndCount ...
+// EndCount gets end count.
 func EndCount(url common.URL, methodName string, elapsed int64, succeeded bool) {
 	endCount0(GetURLStatus(url), elapsed, succeeded)
 	endCount0(GetMethodStatus(url, methodName), elapsed, succeeded)
@@ -163,19 +163,19 @@
 	}
 }
 
-// CurrentTimeMillis ...
+// CurrentTimeMillis get current timestamp
 func CurrentTimeMillis() int64 {
 	return time.Now().UnixNano() / int64(time.Millisecond)
 }
 
 // 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 611b7cb..cc12753 100644
--- a/protocol/rpc_status_test.go
+++ b/protocol/rpc_status_test.go
@@ -30,10 +30,14 @@
 	"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 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 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 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 b36929d..3b36510 100644
--- a/registry/base_configuration_listener.go
+++ b/registry/base_configuration_listener.go
@@ -29,19 +29,19 @@
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// BaseConfigurationListener ...
+// nolint
 type BaseConfigurationListener struct {
 	configurators           []config_center.Configurator
 	dynamicConfiguration    config_center.DynamicConfiguration
 	defaultConfiguratorFunc func(url *common.URL) config_center.Configurator
 }
 
-// Configurators ...
+// Configurators gets Configurator from config center
 func (bcl *BaseConfigurationListener) Configurators() []config_center.Configurator {
 	return bcl.configurators
 }
 
-// InitWith ...
+// InitWith will init BaseConfigurationListener by @key+@Listener+@f
 func (bcl *BaseConfigurationListener) InitWith(key string, listener config_center.ConfigurationListener, f func(url *common.URL) config_center.Configurator) {
 	bcl.dynamicConfiguration = config.GetEnvInstance().GetDynamicConfiguration()
 	if bcl.dynamicConfiguration == nil {
@@ -60,7 +60,7 @@
 	}
 }
 
-// Process ...
+// Process the notification event once there's any change happens on the config.
 func (bcl *BaseConfigurationListener) Process(event *config_center.ConfigChangeEvent) {
 	logger.Infof("Notification of overriding rule, change type is: %v , raw config content is:%v", event.ConfigType, event.Value)
 	if event.ConfigType == remoting.EventTypeDel {
@@ -82,14 +82,14 @@
 	return nil
 }
 
-// OverrideUrl ...
+// OverrideUrl gets existing configuration rule and overrides provider url before exporting.
 func (bcl *BaseConfigurationListener) OverrideUrl(url *common.URL) {
 	for _, v := range bcl.configurators {
 		v.Configure(url)
 	}
 }
 
-// ToConfigurators ...
+// ToConfigurators converts @urls by @f to config_center.Configurators
 func ToConfigurators(urls []*common.URL, f func(url *common.URL) config_center.Configurator) []config_center.Configurator {
 	if len(urls) == 0 {
 		return nil
diff --git a/registry/base_registry.go b/registry/base_registry.go
index 3b64e93..ad1a3b6 100644
--- a/registry/base_registry.go
+++ b/registry/base_registry.go
@@ -56,6 +56,8 @@
 	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 @@
 	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 @@
 	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
 }
 
@@ -121,6 +127,7 @@
 	close(r.done)
 	// wait waitgroup done (wait listeners outside close over)
 	r.wg.Wait()
+
 	//close registry client
 	r.closeRegisters()
 }
@@ -153,6 +160,43 @@
 	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())
@@ -178,13 +222,28 @@
 		}
 		logger.Infof("success to re-register service :%v", confIf.Key())
 	}
-	r.facadeBasedRegistry.InitListeners()
+
+	if flag {
+		r.facadeBasedRegistry.InitListeners()
+	}
 
 	return flag
 }
 
 // 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
@@ -209,15 +268,15 @@
 	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)
@@ -225,8 +284,15 @@
 	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
@@ -236,28 +302,20 @@
 		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)
 	}
-	params.Add("anyhost", "true")
+	params.Add(constant.ANYHOST_KEY, "true")
 
 	// Dubbo java consumer to start looking for the provider url,because the category does not match,
 	// the provider will not find, causing the consumer can not start, so we use consumers.
-	// DubboRole               = [...]string{"consumer", "", "", "provider"}
-	// params.Add("category", (RoleType(PROVIDER)).Role())
-	params.Add("category", (common.RoleType(common.PROVIDER)).String())
-	params.Add("dubbo", "dubbo-provider-golang-"+constant.Version)
-
-	params.Add("side", (common.RoleType(common.PROVIDER)).Role())
 
 	if len(c.Methods) == 0 {
-		params.Add("methods", strings.Join(c.Methods, ","))
+		params.Add(constant.METHODS_KEY, strings.Join(c.Methods, ","))
 	}
 	logger.Debugf("provider url params:%#v", params)
 	var host string
@@ -276,7 +334,7 @@
 }
 
 // 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
@@ -284,23 +342,18 @@
 	)
 	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))
@@ -308,9 +361,6 @@
 	}
 
 	params.Add("protocol", c.Protocol)
-	params.Add("category", (common.RoleType(common.CONSUMER)).String())
-	params.Add("dubbo", "dubbogo-consumer-"+constant.Version)
-
 	rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode())
 	dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.CONSUMER)).String())
 
@@ -328,20 +378,20 @@
 }
 
 // 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)
@@ -363,6 +413,37 @@
 	}
 }
 
+// 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/listener.go b/registry/consul/listener.go
index b047a4c..cf3888d 100644
--- a/registry/consul/listener.go
+++ b/registry/consul/listener.go
@@ -142,7 +142,6 @@
 func (l *consulListener) handler(idx uint64, raw interface{}) {
 	var (
 		service *consul.ServiceEntry
-		event   *registry.ServiceEvent
 		url     common.URL
 		ok      bool
 		err     error
@@ -183,11 +182,12 @@
 	}
 
 	l.urls = newUrls
-	for _, event = range events {
+	for _, event := range events {
 		l.eventCh <- event
 	}
 }
 
+// Next returns the service event from consul.
 func (l *consulListener) Next() (*registry.ServiceEvent, error) {
 	select {
 	case event := <-l.eventCh:
@@ -197,6 +197,7 @@
 	}
 }
 
+// Close closes this listener
 func (l *consulListener) Close() {
 	close(l.done)
 	l.plan.Stop()
diff --git a/registry/consul/registry.go b/registry/consul/registry.go
index c5b8510..c425c5e 100644
--- a/registry/consul/registry.go
+++ b/registry/consul/registry.go
@@ -36,8 +36,7 @@
 )
 
 const (
-	// RegistryConnDelay ...
-	RegistryConnDelay = 3
+	registryConnDelay = 3
 )
 
 func init() {
@@ -74,6 +73,8 @@
 	return r, nil
 }
 
+// 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 @@
 	return nil
 }
 
+// register actually register the @url
 func (r *consulRegistry) register(url common.URL) error {
 	service, err := buildService(url)
 	if err != nil {
@@ -95,7 +97,9 @@
 	return r.client.Agent().ServiceRegister(service)
 }
 
-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, ""))
@@ -108,17 +112,27 @@
 	return nil
 }
 
+// unregister actually unregister the @url
 func (r *consulRegistry) unregister(url common.URL) error {
 	return r.client.Agent().ServiceDeregister(buildId(url))
 }
 
-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() {
@@ -133,7 +147,7 @@
 				return
 			}
 			logger.Warnf("getListener() = err:%v", perrors.WithStack(err))
-			time.Sleep(time.Duration(RegistryConnDelay) * time.Second)
+			time.Sleep(time.Duration(registryConnDelay) * time.Second)
 			continue
 		}
 
@@ -156,10 +170,12 @@
 	return listener, err
 }
 
+// GetUrl get registry URL of consul registry center
 func (r *consulRegistry) GetUrl() common.URL {
 	return *r.URL
 }
 
+// IsAvailable checks consul registry center whether is available
 func (r *consulRegistry) IsAvailable() bool {
 	select {
 	case <-r.done:
@@ -169,6 +185,7 @@
 	}
 }
 
+// Destroy consul registry center
 func (r *consulRegistry) Destroy() {
 	close(r.done)
 }
diff --git a/registry/consul/registry_test.go b/registry/consul/registry_test.go
index bb6842c..94718f5 100644
--- a/registry/consul/registry_test.go
+++ b/registry/consul/registry_test.go
@@ -44,7 +44,7 @@
 
 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 d66600b..327dd95 100644
--- a/registry/consul/utils_test.go
+++ b/registry/consul/utils_test.go
@@ -19,24 +19,19 @@
 
 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 @@
 )
 
 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 @@
 
 // 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 @@
 
 // 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 a6d2cdf..2fbf941 100644
--- a/registry/directory/directory.go
+++ b/registry/directory/directory.go
@@ -19,7 +19,6 @@
 
 import (
 	"sync"
-	"time"
 )
 
 import (
@@ -28,6 +27,7 @@
 )
 
 import (
+	"github.com/apache/dubbo-go/cluster"
 	"github.com/apache/dubbo-go/cluster/directory"
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
@@ -42,67 +42,59 @@
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// Options ...
-type Options struct {
-	serviceTTL time.Duration
+func init() {
+	extension.SetDefaultRegistryDirectory(NewRegistryDirectory)
 }
 
-// Option ...
-type Option func(*Options)
-
-type registryDirectory struct {
+// RegistryDirectory implementation of Directory:
+// Invoker list returned from this Directory's list method have been filtered by Routers
+type RegistryDirectory struct {
 	directory.BaseDirectory
 	cacheInvokers                  []protocol.Invoker
 	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
 	referenceConfigurationListener *referenceConfigurationListener
-	Options
-	serviceKey string
-	forbidden  atomic.Bool
+	serviceKey                     string
+	forbidden                      atomic.Bool
 }
 
-// NewRegistryDirectory ...
-func NewRegistryDirectory(url *common.URL, registry registry.Registry, opts ...Option) (*registryDirectory, error) {
-	options := Options{
-		//default 300s
-		serviceTTL: time.Duration(300e9),
-	}
-	for _, opt := range opts {
-		opt(&options)
-	}
+// NewRegistryDirectory will create a new RegistryDirectory
+func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster.Directory, error) {
 	if url.SubURL == nil {
 		return nil, perrors.Errorf("url is invalid, suburl can not be nil")
 	}
-	dir := &registryDirectory{
+	dir := &RegistryDirectory{
 		BaseDirectory:    directory.NewBaseDirectory(url),
 		cacheInvokers:    []protocol.Invoker{},
 		cacheInvokersMap: &sync.Map{},
 		serviceType:      url.SubURL.Service(),
 		registry:         registry,
-		Options:          options,
 	}
 	dir.consumerConfigurationListener = newConsumerConfigurationListener(dir)
+
+	go dir.subscribe(url.SubURL)
 	return dir, nil
 }
 
-//subscribe from registry
-func (dir *registryDirectory) Subscribe(url *common.URL) {
+// subscribe from registry
+func (dir *RegistryDirectory) subscribe(url *common.URL) {
 	dir.consumerConfigurationListener.addNotifyListener(dir)
 	dir.referenceConfigurationListener = newReferenceConfigurationListener(dir, url)
 	dir.registry.Subscribe(url, dir)
 }
 
-func (dir *registryDirectory) Notify(event *registry.ServiceEvent) {
+// Notify monitor changes from registry,and update the cacheServices
+func (dir *RegistryDirectory) Notify(event *registry.ServiceEvent) {
 	go dir.update(event)
 }
 
-//subscribe service from registry, and update the cacheServices
-func (dir *registryDirectory) update(res *registry.ServiceEvent) {
+// update the cacheServices and subscribe service from registry
+func (dir *RegistryDirectory) update(res *registry.ServiceEvent) {
 	if res == nil {
 		return
 	}
@@ -111,37 +103,36 @@
 	dir.refreshInvokers(res)
 }
 
-func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) {
+func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) {
 	var (
 		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
+
 		}
 		switch res.Action {
 		case remoting.EventTypeAdd, remoting.EventTypeUpdate:
 			logger.Infof("selector add service url{%s}", res.Service)
-			var urls []*common.URL
 
-			for _, v := range directory.GetRouterURLSet().Values() {
+			var urls []*common.URL
+			for _, v := range config.GetRouterURLSet().Values() {
 				urls = append(urls, v.(*common.URL))
 			}
 
 			if len(urls) > 0 {
 				dir.SetRouters(urls)
 			}
-
-			//dir.cacheService.EventTypeAdd(res.Path, dir.serviceTTL)
 			oldInvoker = dir.cacheInvoker(url)
 		case remoting.EventTypeDel:
 			oldInvoker = dir.uncacheInvoker(url)
@@ -163,10 +154,12 @@
 
 }
 
-func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker {
-	newInvokersList := []protocol.Invoker{}
+func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker {
+	var (
+		err             error
+		newInvokersList []protocol.Invoker
+	)
 	groupInvokersMap := make(map[string][]protocol.Invoker)
-	groupInvokersList := []protocol.Invoker{}
 
 	dir.cacheInvokersMap.Range(func(key, value interface{}) bool {
 		newInvokersList = append(newInvokersList, value.(protocol.Invoker))
@@ -182,25 +175,30 @@
 			groupInvokersMap[group] = []protocol.Invoker{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
 		}
 	} else {
 		for _, invokers := range groupInvokersMap {
 			staticDir := directory.NewStaticDirectory(invokers)
-			cluster := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER))
-			staticDir.BuildRouterChain(invokers)
-			groupInvokersList = append(groupInvokersList, cluster.Join(staticDir))
+			cst := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER))
+			err = staticDir.BuildRouterChain(invokers)
+			if err != nil {
+				logger.Error(err)
+				continue
+			}
+			groupInvokersList = append(groupInvokersList, cst.Join(staticDir))
 		}
 	}
 
 	return groupInvokersList
 }
 
-// uncacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil
-func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker {
+// uncacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil
+func (dir *RegistryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker {
 	logger.Debugf("service will be deleted in cache invokers: invokers key is  %s!", url.Key())
 	if cacheInvoker, ok := dir.cacheInvokersMap.Load(url.Key()); ok {
 		dir.cacheInvokersMap.Delete(url.Key())
@@ -209,8 +207,8 @@
 	return nil
 }
 
-// cacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil
-func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
+// cacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil
+func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
 	dir.overrideUrl(dir.GetDirectoryUrl())
 	referenceUrl := dir.GetDirectoryUrl().SubURL
 
@@ -223,7 +221,7 @@
 		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)
@@ -245,8 +243,8 @@
 	return nil
 }
 
-//select the protocol invokers from the directory
-func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
+// List selected protocol invokers from the directory
+func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
 	invokers := dir.cacheInvokers
 	routerChain := dir.RouterChain()
 
@@ -256,7 +254,8 @@
 	return routerChain.Route(invokers, dir.cacheOriginUrl, invocation)
 }
 
-func (dir *registryDirectory) IsAvailable() bool {
+// IsAvailable  whether the directory is available
+func (dir *RegistryDirectory) IsAvailable() bool {
 	if !dir.BaseDirectory.IsAvailable() {
 		return dir.BaseDirectory.IsAvailable()
 	}
@@ -270,8 +269,9 @@
 	return false
 }
 
-func (dir *registryDirectory) Destroy() {
-	//TODO:unregister & unsubscribe
+// Destroy method
+func (dir *RegistryDirectory) Destroy() {
+	// TODO:unregister & unsubscribe
 	dir.BaseDirectory.Destroy(func() {
 		invokers := dir.cacheInvokers
 		dir.cacheInvokers = []protocol.Invoker{}
@@ -281,7 +281,7 @@
 	})
 }
 
-func (dir *registryDirectory) overrideUrl(targetUrl *common.URL) {
+func (dir *RegistryDirectory) overrideUrl(targetUrl *common.URL) {
 	doOverrideUrl(dir.configurators, targetUrl)
 	doOverrideUrl(dir.consumerConfigurationListener.Configurators(), targetUrl)
 	doOverrideUrl(dir.referenceConfigurationListener.Configurators(), targetUrl)
@@ -295,11 +295,11 @@
 
 type referenceConfigurationListener struct {
 	registry.BaseConfigurationListener
-	directory *registryDirectory
+	directory *RegistryDirectory
 	url       *common.URL
 }
 
-func newReferenceConfigurationListener(dir *registryDirectory, url *common.URL) *referenceConfigurationListener {
+func newReferenceConfigurationListener(dir *RegistryDirectory, url *common.URL) *referenceConfigurationListener {
 	listener := &referenceConfigurationListener{directory: dir, url: url}
 	listener.InitWith(
 		url.EncodedServiceKey()+constant.CONFIGURATORS_SUFFIX,
@@ -309,6 +309,7 @@
 	return listener
 }
 
+// Process handle events and update Invokers
 func (l *referenceConfigurationListener) Process(event *config_center.ConfigChangeEvent) {
 	l.BaseConfigurationListener.Process(event)
 	l.directory.refreshInvokers(nil)
@@ -317,10 +318,10 @@
 type consumerConfigurationListener struct {
 	registry.BaseConfigurationListener
 	listeners []registry.NotifyListener
-	directory *registryDirectory
+	directory *RegistryDirectory
 }
 
-func newConsumerConfigurationListener(dir *registryDirectory) *consumerConfigurationListener {
+func newConsumerConfigurationListener(dir *RegistryDirectory) *consumerConfigurationListener {
 	listener := &consumerConfigurationListener{directory: dir}
 	listener.InitWith(
 		config.GetConsumerConfig().ApplicationConfig.Name+constant.CONFIGURATORS_SUFFIX,
@@ -334,6 +335,7 @@
 	l.listeners = append(l.listeners, listener)
 }
 
+// Process handles events from Configuration Center and update Invokers
 func (l *consumerConfigurationListener) Process(event *config_center.ConfigChangeEvent) {
 	l.BaseConfigurationListener.Process(event)
 	l.directory.refreshInvokers(nil)
diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go
index 0dde44e..f2b2f8e 100644
--- a/registry/directory/directory_test.go
+++ b/registry/directory/directory_test.go
@@ -25,6 +25,7 @@
 )
 
 import (
+	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -37,13 +38,18 @@
 	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/config"
 	"github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/mock"
 	"github.com/apache/dubbo-go/protocol/protocolwrapper"
 	"github.com/apache/dubbo-go/registry"
 	"github.com/apache/dubbo-go/remoting"
 )
 
 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) {
@@ -58,7 +64,7 @@
 //	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)
 //}
@@ -79,10 +85,9 @@
 	suburl.SetParam(constant.CLUSTER_KEY, "mock")
 	regurl.SubURL = &suburl
 	mockRegistry, _ := registry.NewMockRegistry(&common.URL{})
-	registryDirectory, _ := NewRegistryDirectory(&regurl, mockRegistry)
+	dir, _ := NewRegistryDirectory(&regurl, mockRegistry)
 
-	go registryDirectory.Subscribe(common.NewURLWithOptions(common.WithPath("testservice")))
-
+	go dir.(*RegistryDirectory).subscribe(common.NewURLWithOptions(common.WithPath("testservice")))
 	//for group1
 	urlmap := url.Values{}
 	urlmap.Set(constant.GROUP_KEY, "group1")
@@ -101,7 +106,7 @@
 	}
 
 	time.Sleep(1e9)
-	assert.Len(t, registryDirectory.cacheInvokers, 2)
+	assert.Len(t, dir.(*RegistryDirectory).cacheInvokers, 2)
 }
 
 func Test_Destroy(t *testing.T) {
@@ -170,10 +175,24 @@
 			break Loop1
 		}
 	}
-
 }
 
-func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockRegistry) {
+func Test_toGroupInvokers(t *testing.T) {
+	registryDirectory, _ := normalRegistryDir()
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+	invoker := mock.NewMockInvoker(ctrl)
+	newUrl, _ := common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")
+	invoker.EXPECT().GetUrl().Return(newUrl).AnyTimes()
+
+	registryDirectory.cacheInvokersMap.Store("group1", invoker)
+	registryDirectory.cacheInvokersMap.Store("group2", invoker)
+	registryDirectory.cacheInvokersMap.Store("group1", invoker)
+	groupInvokers := registryDirectory.toGroupInvokers()
+	assert.Len(t, groupInvokers, 2)
+}
+
+func normalRegistryDir(noMockEvent ...bool) (*RegistryDirectory, *registry.MockRegistry) {
 	extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter)
 
 	url, _ := common.NewURL("mock://127.0.0.1:1111")
@@ -185,9 +204,9 @@
 	)
 	url.SubURL = &suburl
 	mockRegistry, _ := registry.NewMockRegistry(&common.URL{})
-	registryDirectory, _ := NewRegistryDirectory(&url, mockRegistry)
+	dir, _ := NewRegistryDirectory(&url, mockRegistry)
 
-	go registryDirectory.Subscribe(&suburl)
+	go dir.(*RegistryDirectory).subscribe(&suburl)
 	if len(noMockEvent) == 0 {
 		for i := 0; i < 3; i++ {
 			mockRegistry.(*registry.MockRegistry).MockEvent(
@@ -201,5 +220,5 @@
 			)
 		}
 	}
-	return registryDirectory, mockRegistry.(*registry.MockRegistry)
+	return dir.(*RegistryDirectory), mockRegistry.(*registry.MockRegistry)
 }
diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go
index 51fdf21..436b6ec 100644
--- a/registry/etcdv3/listener.go
+++ b/registry/etcdv3/listener.go
@@ -38,15 +38,17 @@
 	listener      config_center.ConfigurationListener
 }
 
-// NewRegistryDataListener
+// NewRegistryDataListener creates a data listener for etcd
 func NewRegistryDataListener(listener config_center.ConfigurationListener) *dataListener {
 	return &dataListener{listener: listener}
 }
 
+// AddInterestedURL adds a registration @url to listen
 func (l *dataListener) AddInterestedURL(url *common.URL) {
 	l.interestedURL = append(l.interestedURL, url)
 }
 
+// DataChange processes the data change event from registry center of etcd
 func (l *dataListener) DataChange(eventType remoting.Event) bool {
 
 	index := strings.Index(eventType.Path, "/providers/")
@@ -88,10 +90,12 @@
 	return &configurationListener{registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)}
 }
 
+// Process data change event from config center of etcd
 func (l *configurationListener) Process(configType *config_center.ConfigChangeEvent) {
 	l.events <- configType
 }
 
+// Next returns next service event once received
 func (l *configurationListener) Next() (*registry.ServiceEvent, error) {
 	for {
 		select {
@@ -114,6 +118,7 @@
 	}
 }
 
+// Close etcd registry center
 func (l *configurationListener) Close() {
 	l.registry.WaitGroup().Done()
 }
diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go
index e691ae3..f27e7ce 100644
--- a/registry/etcdv3/listener_test.go
+++ b/registry/etcdv3/listener_test.go
@@ -24,9 +24,9 @@
 )
 
 import (
+	"github.com/coreos/etcd/embed"
 	"github.com/dubbogo/getty"
 	"github.com/stretchr/testify/suite"
-	"go.etcd.io/etcd/embed"
 )
 
 import (
diff --git a/registry/etcdv3/registry.go b/registry/etcdv3/registry.go
index e1c2576..2fec8ea 100644
--- a/registry/etcdv3/registry.go
+++ b/registry/etcdv3/registry.go
@@ -57,17 +57,17 @@
 	configListener *configurationListener
 }
 
-// Client get the etcdv3 client
+// Client gets the etcdv3 client
 func (r *etcdV3Registry) Client() *etcdv3.Client {
 	return r.client
 }
 
-//SetClient set the etcdv3 client
+// SetClient sets the etcdv3 client
 func (r *etcdV3Registry) SetClient(client *etcdv3.Client) {
 	r.client = client
 }
 
-//
+// ClientLock returns lock for client
 func (r *etcdV3Registry) ClientLock() *sync.Mutex {
 	return &r.cltLock
 }
@@ -104,27 +104,37 @@
 	return r, nil
 }
 
+// InitListeners init listeners of etcd registry center
 func (r *etcdV3Registry) InitListeners() {
 	r.listener = etcdv3.NewEventListener(r.client)
 	r.configListener = NewConfigurationListener(r)
 	r.dataListener = NewRegistryDataListener(r.configListener)
 }
 
+// DoRegister actually do the register job in the registry center of etcd
 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()
 	r.client = nil
 }
 
+// CloseListener closes listeners
 func (r *etcdV3Registry) CloseListener() {
 	if r.configListener != nil {
 		r.configListener.Close()
 	}
 }
 
+// CreatePath create the path in the registry center of etcd
 func (r *etcdV3Registry) CreatePath(k string) error {
 	var tmpPath string
 	for _, str := range strings.Split(k, "/")[1:] {
@@ -137,6 +147,7 @@
 	return nil
 }
 
+// DoSubscribe actually subscribe the provider URL
 func (r *etcdV3Registry) DoSubscribe(svc *common.URL) (registry.Listener, error) {
 
 	var (
@@ -164,9 +175,11 @@
 
 	//register the svc to dataListener
 	r.dataListener.AddInterestedURL(svc)
-	for _, v := range strings.Split(svc.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") {
-		go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, svc.Service()), r.dataListener)
-	}
+	go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, svc.Service()), r.dataListener)
 
 	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/registry_test.go b/registry/etcdv3/registry_test.go
index dc4e382..164fe9c 100644
--- a/registry/etcdv3/registry_test.go
+++ b/registry/etcdv3/registry_test.go
@@ -63,7 +63,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*provider", children)
+	assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock", children)
 	assert.NoError(t, err)
 }
 
diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go
new file mode 100644
index 0000000..1039604
--- /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 0000000..d8e3f1a
--- /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 37d863d..39fb00c 100644
--- a/registry/event.go
+++ b/registry/event.go
@@ -25,6 +25,7 @@
 
 import (
 	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/observer"
 	"github.com/apache/dubbo-go/remoting"
 )
 
@@ -32,16 +33,41 @@
 	rand.Seed(time.Now().UnixNano())
 }
 
-//////////////////////////////////////////
+// ////////////////////////////////////////
 // service event
-//////////////////////////////////////////
+// ////////////////////////////////////////
 
-// ServiceEvent ...
+// ServiceEvent includes create, update, delete event
 type ServiceEvent struct {
 	Action  remoting.EventType
 	Service common.URL
 }
 
+// String return the description of event
 func (e ServiceEvent) String() string {
 	return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}}", e.Action, e.Service)
 }
+
+// ServiceInstancesChangedEvent represents service instances make some changing
+type ServiceInstancesChangedEvent struct {
+	observer.BaseEvent
+	ServiceName string
+	Instances   []ServiceInstance
+}
+
+// String return the description of the event
+func (s *ServiceInstancesChangedEvent) String() string {
+	return fmt.Sprintf("ServiceInstancesChangedEvent[source=%s]", s.ServiceName)
+}
+
+// NewServiceInstancesChangedEvent will create the ServiceInstanceChangedEvent instance
+func NewServiceInstancesChangedEvent(serviceName string, instances []ServiceInstance) *ServiceInstancesChangedEvent {
+	return &ServiceInstancesChangedEvent{
+		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 0000000..07e84c1
--- /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 0000000..1c81ece
--- /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 0000000..54752c0
--- /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 0000000..3ee2f4a
--- /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 0000000..0781a6d
--- /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 0000000..3136564
--- /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 0000000..6d8f99b
--- /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 0000000..98ae2df
--- /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 0000000..a58471c
--- /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 0000000..5ec027d
--- /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 0000000..13afa1a
--- /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 0000000..d4f23c2
--- /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 0000000..a4ac8b2
--- /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 0000000..fd21e8f
--- /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
new file mode 100644
index 0000000..9e9ec2d
--- /dev/null
+++ b/registry/event_listener.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 registry
+
+import (
+	"reflect"
+)
+
+import (
+	"github.com/apache/dubbo-go/common/observer"
+)
+
+// The Service Discovery Changed  Event Listener
+type ServiceInstancesChangedListener struct {
+	ServiceName   string
+	ChangedNotify observer.ChangedNotify
+}
+
+// OnEvent on ServiceInstancesChangedEvent the service instances change event
+func (lstn *ServiceInstancesChangedListener) OnEvent(e observer.Event) error {
+	lstn.ChangedNotify.Notify(e)
+	return nil
+}
+
+// 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/listener.go b/registry/kubernetes/listener.go
index f8869fe..24c8d81 100644
--- a/registry/kubernetes/listener.go
+++ b/registry/kubernetes/listener.go
@@ -38,12 +38,12 @@
 	listener      config_center.ConfigurationListener
 }
 
-// NewRegistryDataListener
+// NewRegistryDataListener creates a data listener for kubernetes
 func NewRegistryDataListener(listener config_center.ConfigurationListener) *dataListener {
 	return &dataListener{listener: listener}
 }
 
-// AddInterestedURL
+// AddInterestedURL adds the @url of registry center to the listener
 func (l *dataListener) AddInterestedURL(url *common.URL) {
 	l.interestedURL = append(l.interestedURL, url)
 }
@@ -91,10 +91,12 @@
 	return &configurationListener{registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)}
 }
 
+// Process processes the data change event from config center of kubernetes
 func (l *configurationListener) Process(configType *config_center.ConfigChangeEvent) {
 	l.events <- configType
 }
 
+// Next returns next service event once received
 func (l *configurationListener) Next() (*registry.ServiceEvent, error) {
 	for {
 		select {
@@ -103,7 +105,7 @@
 			return nil, perrors.New("listener stopped")
 
 		case e := <-l.events:
-			logger.Infof("got kubernetes event %#v", e)
+			logger.Debugf("got kubernetes event %#v", e)
 			if e.ConfigType == remoting.EventTypeDel && !l.registry.client.Valid() {
 				select {
 				case <-l.registry.Done():
@@ -116,6 +118,8 @@
 		}
 	}
 }
+
+// Close kubernetes registry center
 func (l *configurationListener) Close() {
 	l.registry.WaitGroup().Done()
 }
diff --git a/registry/kubernetes/listener_test.go b/registry/kubernetes/listener_test.go
index c50b5b6..ccaaf80 100644
--- a/registry/kubernetes/listener_test.go
+++ b/registry/kubernetes/listener_test.go
@@ -18,24 +18,15 @@
 package kubernetes
 
 import (
-	"encoding/json"
-	"os"
-	"strconv"
 	"testing"
-	"time"
 )
 
 import (
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/suite"
-	"k8s.io/api/core/v1"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/fake"
 )
 
 import (
 	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/config_center"
 	"github.com/apache/dubbo-go/remoting"
 )
@@ -184,67 +175,7 @@
 
 func (*MockDataListener) Process(configType *config_center.ConfigChangeEvent) {}
 
-type KubernetesRegistryTestSuite struct {
-	suite.Suite
-
-	currentPod v1.Pod
-}
-
-func (s *KubernetesRegistryTestSuite) initRegistry() *kubernetesRegistry {
-
-	t := s.T()
-
-	regurl, err := common.NewURL("registry://127.0.0.1:443", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	mock, err := newMockKubernetesRegistry(&regurl, s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) {
-
-		out := fake.NewSimpleClientset()
-
-		// mock current pod
-		if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil {
-			t.Fatal(err)
-		}
-		return out, nil
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	time.Sleep(time.Second)
-	return mock.(*kubernetesRegistry)
-}
-
-func (s *KubernetesRegistryTestSuite) SetupSuite() {
-
-	t := s.T()
-
-	const (
-		// kubernetes inject the var
-		podNameKey   = "HOSTNAME"
-		nameSpaceKey = "NAMESPACE"
-	)
-
-	// 1. install test data
-	if err := json.Unmarshal([]byte(clientPodJsonData), &s.currentPod); err != nil {
-		t.Fatal(err)
-	}
-
-	// 2. set downward-api inject env
-	if err := os.Setenv(podNameKey, s.currentPod.GetName()); err != nil {
-		t.Fatal(err)
-	}
-	if err := os.Setenv(nameSpaceKey, s.currentPod.GetNamespace()); err != nil {
-		t.Fatal(err)
-	}
-
-}
-
-func (s *KubernetesRegistryTestSuite) TestDataChange() {
-
-	t := s.T()
+func TestDataChange(t *testing.T) {
 
 	listener := NewRegistryDataListener(&MockDataListener{})
 	url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100")
@@ -253,7 +184,3 @@
 		t.Fatal("data change not ok")
 	}
 }
-
-func TestKubernetesRegistrySuite(t *testing.T) {
-	suite.Run(t, &KubernetesRegistryTestSuite{})
-}
diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go
index 7212a83..7c51626 100644
--- a/registry/kubernetes/registry.go
+++ b/registry/kubernetes/registry.go
@@ -21,7 +21,6 @@
 	"fmt"
 	"os"
 	"path"
-	"strings"
 	"sync"
 	"time"
 )
@@ -30,7 +29,7 @@
 	"github.com/dubbogo/getty"
 	"github.com/dubbogo/gost/net"
 	perrors "github.com/pkg/errors"
-	k8s "k8s.io/client-go/kubernetes"
+	v1 "k8s.io/api/core/v1"
 )
 
 import (
@@ -69,23 +68,28 @@
 	configListener *configurationListener
 }
 
+// Client gets the etcdv3 kubernetes
 func (r *kubernetesRegistry) Client() *kubernetes.Client {
 	r.cltLock.RLock()
 	client := r.client
 	r.cltLock.RUnlock()
 	return client
 }
+
+// SetClient sets the kubernetes client
 func (r *kubernetesRegistry) SetClient(client *kubernetes.Client) {
 	r.cltLock.Lock()
 	r.client = client
 	r.cltLock.Unlock()
 }
 
+// CloseAndNilClient closes listeners and clear client
 func (r *kubernetesRegistry) CloseAndNilClient() {
 	r.client.Close()
 	r.client = nil
 }
 
+// CloseListener closes listeners
 func (r *kubernetesRegistry) CloseListener() {
 
 	r.cltLock.Lock()
@@ -97,6 +101,7 @@
 	r.configListener = nil
 }
 
+// CreatePath create the path in the registry center of kubernetes
 func (r *kubernetesRegistry) CreatePath(k string) error {
 	if err := r.client.Create(k, ""); err != nil {
 		return perrors.WithMessagef(err, "create path %s in kubernetes", k)
@@ -104,10 +109,16 @@
 	return nil
 }
 
+// DoRegister actually do the register job in the registry center of kubernetes
 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) {
 
 	var (
@@ -135,13 +146,17 @@
 
 	//register the svc to dataListener
 	r.dataListener.AddInterestedURL(svc)
-	for _, v := range strings.Split(svc.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") {
-		go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, svc.Service()), r.dataListener)
-	}
+	go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, svc.Service()), r.dataListener)
 
 	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)
 	r.configListener = NewConfigurationListener(r)
@@ -163,15 +178,14 @@
 	go r.HandleClientRestart()
 	r.InitListeners()
 
-	logger.Debugf("the kubernetes registry started")
+	logger.Debugf("kubernetes registry started")
 
 	return r, nil
 }
 
 func newMockKubernetesRegistry(
 	url *common.URL,
-	namespace string,
-	clientGeneratorFunc func() (k8s.Interface, error),
+	podsList *v1.PodList,
 ) (registry.Registry, error) {
 
 	var err error
@@ -179,7 +193,7 @@
 	r := &kubernetesRegistry{}
 
 	r.InitBaseRegistry(url, r)
-	r.client, err = kubernetes.NewMockClient(namespace, clientGeneratorFunc)
+	r.client, err = kubernetes.NewMockClient(podsList)
 	if err != nil {
 		return nil, perrors.WithMessage(err, "new mock client")
 	}
@@ -187,6 +201,7 @@
 	return r, nil
 }
 
+// HandleClientRestart will reconnect to  kubernetes registry center
 func (r *kubernetesRegistry) HandleClientRestart() {
 
 	var (
diff --git a/registry/kubernetes/registry_test.go b/registry/kubernetes/registry_test.go
index ea6d766..347dadc 100644
--- a/registry/kubernetes/registry_test.go
+++ b/registry/kubernetes/registry_test.go
@@ -18,12 +18,17 @@
 package kubernetes
 
 import (
+	"encoding/json"
+	"os"
 	"strconv"
+	"sync"
+	"testing"
 	"time"
 )
 
 import (
 	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
 )
 
 import (
@@ -31,11 +36,212 @@
 	"github.com/apache/dubbo-go/common/constant"
 )
 
-func (s *KubernetesRegistryTestSuite) TestRegister() {
+var clientPodListJsonData = `{
+    "apiVersion": "v1",
+    "items": [
+        {
+            "apiVersion": "v1",
+            "kind": "Pod",
+            "metadata": {
+                "annotations": {
+                    "dubbo.io/annotation": "W3siayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNiUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YXV0aCUzRCUyNmJlYW4ubmFtZSUzRFVzZXJQcm92aWRlciUyNmNsdXN0ZXIlM0RmYWlsb3ZlciUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC42JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBhcmFtLnNpZ24lM0QlMjZwaWQlM0Q2JTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZWxlYXNlJTNEZHViYm8tZ29sYW5nLTEuMy4wJTI2cmV0cmllcyUzRCUyNnNlcnZpY2UuZmlsdGVyJTNEZWNobyUyNTJDdG9rZW4lMjUyQ2FjY2Vzc2xvZyUyNTJDdHBzJTI1MkNnZW5lcmljX3NlcnZpY2UlMjUyQ2V4ZWN1dGUlMjUyQ3BzaHV0ZG93biUyNnNpZGUlM0Rwcm92aWRlciUyNnRpbWVzdGFtcCUzRDE1OTExNTYxNTUlMjZ0cHMubGltaXQuaW50ZXJ2YWwlM0QlMjZ0cHMubGltaXQucmF0ZSUzRCUyNnRwcy5saW1pdC5yZWplY3RlZC5oYW5kbGVyJTNEJTI2dHBzLmxpbWl0LnN0cmF0ZWd5JTNEJTI2dHBzLmxpbWl0ZXIlM0QlMjZ2ZXJzaW9uJTNEJTI2d2FybXVwJTNEMTAwIiwidiI6IiJ9XQ=="
+                },
+                "creationTimestamp": "2020-06-03T03:49:14Z",
+                "generateName": "server-84c864f5bc-",
+                "labels": {
+                    "dubbo.io/label": "dubbo.io-value",
+                    "pod-template-hash": "84c864f5bc",
+                    "role": "server"
+                },
+                "name": "server-84c864f5bc-r8qvz",
+                "namespace": "default",
+                "ownerReferences": [
+                    {
+                        "apiVersion": "apps/v1",
+                        "blockOwnerDeletion": true,
+                        "controller": true,
+                        "kind": "ReplicaSet",
+                        "name": "server-84c864f5bc",
+                        "uid": "fa376dbb-4f37-4705-8e80-727f592c19b3"
+                    }
+                ],
+                "resourceVersion": "517460",
+                "selfLink": "/api/v1/namespaces/default/pods/server-84c864f5bc-r8qvz",
+                "uid": "f4fc811c-200c-4445-8d4f-532144957dcc"
+            },
+            "spec": {
+                "containers": [
+                    {
+                        "env": [
+                            {
+                                "name": "DUBBO_NAMESPACE",
+                                "value": "default"
+                            },
+                            {
+                                "name": "NAMESPACE",
+                                "valueFrom": {
+                                    "fieldRef": {
+                                        "apiVersion": "v1",
+                                        "fieldPath": "metadata.namespace"
+                                    }
+                                }
+                            }
+                        ],
+                        "image": "192.168.240.101:5000/scott/go-server",
+                        "imagePullPolicy": "Always",
+                        "name": "server",
+                        "resources": {},
+                        "terminationMessagePath": "/dev/termination-log",
+                        "terminationMessagePolicy": "File",
+                        "volumeMounts": [
+                            {
+                                "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+                                "name": "dubbo-sa-token-5qbtb",
+                                "readOnly": true
+                            }
+                        ]
+                    }
+                ],
+                "dnsPolicy": "ClusterFirst",
+                "enableServiceLinks": true,
+                "nodeName": "minikube",
+                "priority": 0,
+                "restartPolicy": "Always",
+                "schedulerName": "default-scheduler",
+                "securityContext": {},
+                "serviceAccount": "dubbo-sa",
+                "serviceAccountName": "dubbo-sa",
+                "terminationGracePeriodSeconds": 30,
+                "tolerations": [
+                    {
+                        "effect": "NoExecute",
+                        "key": "node.kubernetes.io/not-ready",
+                        "operator": "Exists",
+                        "tolerationSeconds": 300
+                    },
+                    {
+                        "effect": "NoExecute",
+                        "key": "node.kubernetes.io/unreachable",
+                        "operator": "Exists",
+                        "tolerationSeconds": 300
+                    }
+                ],
+                "volumes": [
+                    {
+                        "name": "dubbo-sa-token-5qbtb",
+                        "secret": {
+                            "defaultMode": 420,
+                            "secretName": "dubbo-sa-token-5qbtb"
+                        }
+                    }
+                ]
+            },
+            "status": {
+                "conditions": [
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:14Z",
+                        "status": "True",
+                        "type": "Initialized"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:15Z",
+                        "status": "True",
+                        "type": "Ready"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:15Z",
+                        "status": "True",
+                        "type": "ContainersReady"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:14Z",
+                        "status": "True",
+                        "type": "PodScheduled"
+                    }
+                ],
+                "containerStatuses": [
+                    {
+                        "containerID": "docker://b6421e05ce44f8a1c4fa6b72274980777c7c0f945516209f7c0558cd0cd65406",
+                        "image": "192.168.240.101:5000/scott/go-server:latest",
+                        "imageID": "docker-pullable://192.168.240.101:5000/scott/go-server@sha256:4eecf895054f0ff93d80db64992a561d10504e55582def6dcb6093a6d6d92461",
+                        "lastState": {},
+                        "name": "server",
+                        "ready": true,
+                        "restartCount": 0,
+                        "started": true,
+                        "state": {
+                            "running": {
+                                "startedAt": "2020-06-03T03:49:15Z"
+                            }
+                        }
+                    }
+                ],
+                "hostIP": "10.0.2.15",
+                "phase": "Running",
+                "podIP": "172.17.0.6",
+                "podIPs": [
+                    {
+                        "ip": "172.17.0.6"
+                    }
+                ],
+                "qosClass": "BestEffort",
+                "startTime": "2020-06-03T03:49:14Z"
+            }
+        }
+    ],
+    "kind": "List",
+    "metadata": {
+        "resourceVersion": "",
+        "selfLink": ""
+    }
+}
+`
 
-	t := s.T()
+func getTestRegistry(t *testing.T) *kubernetesRegistry {
 
-	r := s.initRegistry()
+	const (
+		podNameKey              = "HOSTNAME"
+		nameSpaceKey            = "NAMESPACE"
+		needWatchedNameSpaceKey = "DUBBO_NAMESPACE"
+	)
+	pl := &v1.PodList{}
+	// 1. install test data
+	if err := json.Unmarshal([]byte(clientPodListJsonData), &pl); err != nil {
+		t.Fatal(err)
+	}
+	currentPod := pl.Items[0]
+
+	env := map[string]string{
+		nameSpaceKey:            currentPod.GetNamespace(),
+		podNameKey:              currentPod.GetName(),
+		needWatchedNameSpaceKey: "default",
+	}
+
+	for k, v := range env {
+		if err := os.Setenv(k, v); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	regurl, err := common.NewURL("registry://127.0.0.1:443", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
+	if err != nil {
+		t.Fatal(err)
+	}
+	out, err := newMockKubernetesRegistry(&regurl, pl)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return out.(*kubernetesRegistry)
+}
+
+func TestRegister(t *testing.T) {
+
+	r := getTestRegistry(t)
 	defer r.Destroy()
 
 	url, _ := common.NewURL(
@@ -52,41 +258,44 @@
 	}
 }
 
-func (s *KubernetesRegistryTestSuite) TestSubscribe() {
+func TestSubscribe(t *testing.T) {
 
-	t := s.T()
-
-	r := s.initRegistry()
+	r := getTestRegistry(t)
 	defer r.Destroy()
 
-	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
+	url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"}))
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	listener, err := r.DoSubscribe(&url)
 	if err != nil {
 		t.Fatal(err)
 	}
-	time.Sleep(1e9)
 
+	wg := sync.WaitGroup{}
+	wg.Add(1)
 	go func() {
-		err := r.Register(url)
-		if err != nil {
-			t.Fatal(err)
+
+		defer wg.Done()
+		registerErr := r.Register(url)
+		if registerErr != nil {
+			t.Fatal(registerErr)
 		}
 	}()
 
+	wg.Wait()
+
 	serviceEvent, err := listener.Next()
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	t.Logf("got event %s", serviceEvent)
+	t.Logf("get service event %s", serviceEvent)
 }
 
-func (s *KubernetesRegistryTestSuite) TestConsumerDestroy() {
+func TestConsumerDestroy(t *testing.T) {
 
-	t := s.T()
-
-	r := s.initRegistry()
+	r := getTestRegistry(t)
 
 	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider",
 		common.WithParamsValue(constant.CLUSTER_KEY, "mock"),
@@ -105,11 +314,9 @@
 
 }
 
-func (s *KubernetesRegistryTestSuite) TestProviderDestroy() {
+func TestProviderDestroy(t *testing.T) {
 
-	t := s.T()
-
-	r := s.initRegistry()
+	r := getTestRegistry(t)
 
 	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider",
 		common.WithParamsValue(constant.CLUSTER_KEY, "mock"),
@@ -122,9 +329,7 @@
 	assert.Equal(t, false, r.IsAvailable())
 }
 
-func (s *KubernetesRegistryTestSuite) TestNewRegistry() {
-
-	t := s.T()
+func TestNewRegistry(t *testing.T) {
 
 	regUrl, err := common.NewURL("registry://127.0.0.1:443",
 		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)))
@@ -137,9 +342,9 @@
 	}
 }
 
-func (s *KubernetesRegistryTestSuite) TestHandleClientRestart() {
+func TestHandleClientRestart(t *testing.T) {
 
-	r := s.initRegistry()
+	r := getTestRegistry(t)
 	r.WaitGroup().Add(1)
 	go r.HandleClientRestart()
 	time.Sleep(timeSecondDuration(1))
diff --git a/registry/mock_registry.go b/registry/mock_registry.go
index 9591928..10561d0 100644
--- a/registry/mock_registry.go
+++ b/registry/mock_registry.go
@@ -30,13 +30,13 @@
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-// MockRegistry ...
+// MockRegistry is used as mock registry
 type MockRegistry struct {
 	listener  *listener
 	destroyed *atomic.Bool
 }
 
-// NewMockRegistry ...
+// NewMockRegistry creates a mock registry
 func NewMockRegistry(url *common.URL) (Registry, error) {
 	registry := &MockRegistry{
 		destroyed: atomic.NewBool(false),
@@ -46,23 +46,28 @@
 	return registry, nil
 }
 
-// Register ...
+// Register is used as a mock registry
 func (*MockRegistry) Register(url common.URL) error {
 	return nil
 }
 
-// Destroy ...
+// nolint
+func (r *MockRegistry) UnRegister(conf common.URL) error {
+	return nil
+}
+
+// nolint
 func (r *MockRegistry) Destroy() {
 	if r.destroyed.CAS(false, true) {
 	}
 }
 
-// IsAvailable ...
+// IsAvailable is use for determine a mock registry available
 func (r *MockRegistry) IsAvailable() bool {
 	return !r.destroyed.Load()
 }
 
-// GetUrl ...
+// nolint
 func (r *MockRegistry) GetUrl() common.URL {
 	return common.URL{}
 }
@@ -71,8 +76,8 @@
 	return r.listener, nil
 }
 
-// Subscribe ...
-func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) {
+// nolint
+func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error {
 	go func() {
 		for {
 			if !r.IsAvailable() {
@@ -104,6 +109,12 @@
 			}
 		}
 	}()
+	return nil
+}
+
+// UnSubscribe :
+func (r *MockRegistry) UnSubscribe(url *common.URL, notifyListener NotifyListener) error {
+	return nil
 }
 
 type listener struct {
@@ -123,7 +134,7 @@
 
 }
 
-// MockEvent ...
+// nolint
 func (r *MockRegistry) MockEvent(event *ServiceEvent) {
 	r.listener.listenChan <- event
 }
diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go
index a2237dc..36f733d 100644
--- a/registry/nacos/listener.go
+++ b/registry/nacos/listener.go
@@ -51,7 +51,7 @@
 	subscribeParam *vo.SubscribeParam
 }
 
-// NewNacosListener ...
+// NewRegistryDataListener creates a data listener for nacos
 func NewNacosListener(url common.URL, namingClient naming_client.INamingClient) (*nacosListener, error) {
 	listener := &nacosListener{
 		namingClient: namingClient,
@@ -109,6 +109,7 @@
 	)
 }
 
+// Callback will be invoked when got subscribed events.
 func (nl *nacosListener) Callback(services []model.SubscribeService, err error) {
 	if err != nil {
 		logger.Errorf("nacos subscribe callback error:%s , subscribe:%+v ", err.Error(), nl.subscribeParam)
@@ -198,6 +199,7 @@
 	nl.events <- configType
 }
 
+// Next returns the service event from nacos.
 func (nl *nacosListener) Next() (*registry.ServiceEvent, error) {
 	for {
 		select {
@@ -212,6 +214,7 @@
 	}
 }
 
+// nolint
 func (nl *nacosListener) Close() {
 	nl.stopListen()
 	close(nl.done)
diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go
index 965e91e..51d3e2f 100644
--- a/registry/nacos/registry.go
+++ b/registry/nacos/registry.go
@@ -47,7 +47,7 @@
 )
 
 const (
-	//RegistryConnDelay registry connection delay
+	// RegistryConnDelay registry connection delay
 	RegistryConnDelay = 3
 )
 
@@ -61,62 +61,6 @@
 	namingClient naming_client.INamingClient
 }
 
-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
-}
-
-func newNacosRegistry(url *common.URL) (registry.Registry, error) {
-	nacosConfig, err := getNacosConfig(url)
-	if err != nil {
-		return nil, err
-	}
-	client, err := clients.CreateNamingClient(nacosConfig)
-	if err != nil {
-		return nil, err
-	}
-	registry := nacosRegistry{
-		URL:          url,
-		namingClient: client,
-	}
-	return &registry, nil
-}
-
 func getCategory(url common.URL) string {
 	role, _ := strconv.Atoi(url.GetParam(constant.ROLE_KEY, strconv.Itoa(constant.NACOS_DEFAULT_ROLETYPE)))
 	category := common.DubboNodes[role]
@@ -173,6 +117,7 @@
 	return instance
 }
 
+// Register will register the service @url to its nacos registry center
 func (nr *nacosRegistry) Register(url common.URL) error {
 	serviceName := getServiceName(url)
 	param := createRegisterParam(url, serviceName)
@@ -186,23 +131,28 @@
 	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) {
+// subscribe from registry
+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)
@@ -214,7 +164,7 @@
 			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())
@@ -222,16 +172,85 @@
 		}
 
 	}
+	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
 func (nr *nacosRegistry) GetUrl() common.URL {
 	return *nr.URL
 }
 
+// IsAvailable determines nacos registry center whether it is available
 func (nr *nacosRegistry) IsAvailable() bool {
+	// TODO
 	return true
 }
 
+// nolint
 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/registry_test.go b/registry/nacos/registry_test.go
index 7475b45..d0311b2 100644
--- a/registry/nacos/registry_test.go
+++ b/registry/nacos/registry_test.go
@@ -42,7 +42,7 @@
 	urlMap.Set(constant.INTERFACE_KEY, "com.ikurento.user.UserProvider")
 	urlMap.Set(constant.VERSION_KEY, "1.0.0")
 	urlMap.Set(constant.CLUSTER_KEY, "mock")
-	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"}))
+	testUrl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"}))
 
 	reg, err := newNacosRegistry(&regurl)
 	assert.Nil(t, err)
@@ -50,7 +50,7 @@
 		t.Errorf("new nacos registry error:%s \n", err.Error())
 		return
 	}
-	err = reg.Register(url)
+	err = reg.Register(testUrl)
 	assert.Nil(t, err)
 	if err != nil {
 		t.Errorf("register error:%s \n", err.Error())
@@ -72,10 +72,10 @@
 	urlMap.Set(constant.VERSION_KEY, "1.0.0")
 	urlMap.Set(constant.CLUSTER_KEY, "mock")
 	urlMap.Set(constant.NACOS_PATH_KEY, "")
-	url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"}))
+	testUrl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"}))
 
 	reg, _ := newNacosRegistry(&regurl)
-	err := reg.Register(url)
+	err := reg.Register(testUrl)
 	assert.Nil(t, err)
 	if err != nil {
 		t.Errorf("new nacos registry error:%s \n", err.Error())
@@ -84,7 +84,7 @@
 
 	regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))
 	reg2, _ := newNacosRegistry(&regurl)
-	listener, err := reg2.(*nacosRegistry).subscribe(&url)
+	listener, err := reg2.(*nacosRegistry).subscribe(&testUrl)
 	assert.Nil(t, err)
 	if err != nil {
 		t.Errorf("subscribe error:%s \n", err.Error())
diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go
new file mode 100644
index 0000000..63d92d7
--- /dev/null
+++ b/registry/nacos/service_discovery.go
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 (
+	"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/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 = constant.SERVICE_DISCOVERY_DEFAULT_GROUP
+	idKey        = "id"
+)
+
+// init will put the service discovery into extension
+func init() {
+	extension.SetServiceDiscovery(constant.NACOS_KEY, newNacosServiceDiscovery)
+}
+
+// nacosServiceDiscovery is the implementation of service discovery based on nacos.
+// 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 {
+	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.
+// Actually, it only marks the naming client as null and then return
+func (n *nacosServiceDiscovery) Destroy() error {
+	n.namingClient = nil
+	return nil
+}
+
+// Register will register the service to nacos
+func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) error {
+	ins := n.toRegisterInstance(instance)
+	ok, err := n.namingClient.RegisterInstance(ins)
+	if err != nil || !ok {
+		return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName())
+	}
+	return nil
+}
+
+// Update will update the information
+// However, because nacos client doesn't support the update API,
+// so we should unregister the instance and then register it again.
+// the error handling is hard to implement
+func (n *nacosServiceDiscovery) Update(instance registry.ServiceInstance) error {
+	// TODO(wait for nacos support)
+	err := n.Unregister(instance)
+	if err != nil {
+		return perrors.WithStack(err)
+	}
+	return n.Register(instance)
+}
+
+// Unregister will unregister the instance
+func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) error {
+	ok, err := n.namingClient.DeregisterInstance(n.toDeregisterInstance(instance))
+	if err != nil || !ok {
+		return perrors.WithMessage(err, "Could not unregister the instance. "+instance.GetServiceName())
+	}
+	return nil
+}
+
+// GetDefaultPageSize will return the constant registry.DefaultPageSize
+func (n *nacosServiceDiscovery) GetDefaultPageSize() int {
+	return registry.DefaultPageSize
+}
+
+// GetServices will return the all services
+func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet {
+	services, err := n.namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
+		GroupName: n.group,
+	})
+
+	res := gxset.NewSet()
+	if err != nil {
+		logger.Errorf("Could not query the services: %v", err)
+		return res
+	}
+
+	for _, e := range services {
+		res.Add(e.Name)
+	}
+	return res
+}
+
+// GetInstances will return the instances of serviceName and the group
+func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance {
+	instances, err := n.namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
+		ServiceName: serviceName,
+		GroupName:   n.group,
+	})
+	if err != nil {
+		logger.Errorf("Could not query the instances for service: " + serviceName + ", group: " + n.group)
+		return make([]registry.ServiceInstance, 0, 0)
+	}
+	res := make([]registry.ServiceInstance, 0, len(instances))
+	for _, ins := range instances {
+		metadata := ins.Metadata
+		id := metadata[idKey]
+
+		delete(metadata, idKey)
+
+		res = append(res, &registry.DefaultServiceInstance{
+			Id:          id,
+			ServiceName: ins.ServiceName,
+			Host:        ins.Ip,
+			Port:        int(ins.Port),
+			Enable:      ins.Enable,
+			Healthy:     ins.Healthy,
+			Metadata:    metadata,
+		})
+	}
+
+	return res
+}
+
+// GetInstancesByPage will return the instances
+// Due to nacos client does not support pagination, so we have to query all instances and then return part of them
+func (n *nacosServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager {
+	all := n.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
+// The nacos client has an API SelectInstances, which has a parameter call HealthyOnly.
+// 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 (n *nacosServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager {
+	all := n.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
+// The nacos client doesn't have batch API, so we should query those serviceNames one by one.
+func (n *nacosServiceDiscovery) 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] = n.GetInstancesByPage(name, offset, requestedSize)
+	}
+	return res
+}
+
+// AddListener will add a listener
+func (n *nacosServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error {
+	return n.namingClient.Subscribe(&vo.SubscribeParam{
+		ServiceName: listener.ServiceName,
+		SubscribeCallback: func(services []model.SubscribeService, err error) {
+			if err != nil {
+				logger.Errorf("Could not handle the subscribe notification because the err is not nil."+
+					" service name: %s, err: %v", listener.ServiceName, err)
+			}
+			instances := make([]registry.ServiceInstance, 0, len(services))
+			for _, service := range services {
+				// we won't use the nacos instance id here but use our instance id
+				metadata := service.Metadata
+				id := metadata[idKey]
+
+				delete(metadata, idKey)
+
+				instances = append(instances, &registry.DefaultServiceInstance{
+					Id:          id,
+					ServiceName: service.ServiceName,
+					Host:        service.Ip,
+					Port:        int(service.Port),
+					Enable:      service.Enable,
+					Healthy:     true,
+					Metadata:    metadata,
+				})
+			}
+
+			e := n.DispatchEventForInstances(listener.ServiceName, instances)
+			if e != nil {
+				logger.Errorf("Dispatching event got exception, service name: %s, err: %v", listener.ServiceName, err)
+			}
+		},
+	})
+}
+
+// DispatchEventByServiceName will dispatch the event for the service with the service name
+func (n *nacosServiceDiscovery) DispatchEventByServiceName(serviceName string) error {
+	return n.DispatchEventForInstances(serviceName, n.GetInstances(serviceName))
+}
+
+// DispatchEventForInstances will dispatch the event to those instances
+func (n *nacosServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error {
+	return n.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances))
+}
+
+// DispatchEvent will dispatch the event
+func (n *nacosServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error {
+	extension.GetGlobalDispatcher().Dispatch(event)
+	return nil
+}
+
+// toRegisterInstance convert the ServiceInstance to RegisterInstanceParam
+// the Ephemeral will be true
+func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInstance) vo.RegisterInstanceParam {
+	metadata := instance.GetMetadata()
+	if metadata == nil {
+		metadata = make(map[string]string, 1)
+	}
+	metadata[idKey] = instance.GetId()
+	return vo.RegisterInstanceParam{
+		ServiceName: instance.GetServiceName(),
+		Ip:          instance.GetHost(),
+		Port:        uint64(instance.GetPort()),
+		Metadata:    metadata,
+		// 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,
+	}
+}
+
+// toDeregisterInstance will convert the ServiceInstance to DeregisterInstanceParam
+func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceInstance) vo.DeregisterInstanceParam {
+	return vo.DeregisterInstanceParam{
+		ServiceName: instance.GetServiceName(),
+		Ip:          instance.GetHost(),
+		Port:        uint64(instance.GetPort()),
+		GroupName:   n.group,
+	}
+}
+
+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")
+	}
+
+	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.WithMessage(err, "create nacos client failed.")
+	}
+
+	descriptor := fmt.Sprintf("nacos-service-discovery[%s]", remoteConfig.Address)
+
+	return &nacosServiceDiscovery{
+		group:        group,
+		namingClient: client,
+		descriptor:   descriptor,
+	}, nil
+}
diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go
new file mode 100644
index 0000000..720c44a
--- /dev/null
+++ b/registry/nacos/service_discovery_test.go
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+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/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) {
+	prepareData()
+	serviceDiscovery, err := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
+	assert.Nil(t, err)
+	assert.NotNil(t, serviceDiscovery)
+	err = serviceDiscovery.Destroy()
+	assert.Nil(t, err)
+	assert.Nil(t, serviceDiscovery.(*nacosServiceDiscovery).namingClient)
+}
+
+func TestNacosServiceDiscovery_CRUD(t *testing.T) {
+	prepareData()
+	extension.SetEventDispatcher("mock", func() observer.EventDispatcher {
+		return &dispatcher.MockEventDispatcher{}
+	})
+
+	extension.SetAndInitGlobalDispatcher("mock")
+
+	serviceName := "service-name"
+	id := "id"
+	host := "host"
+	port := 123
+	instance := &registry.DefaultServiceInstance{
+		Id:          id,
+		ServiceName: serviceName,
+		Host:        host,
+		Port:        port,
+		Enable:      true,
+		Healthy:     true,
+		Metadata:    nil,
+	}
+
+	// clean data
+
+	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
+
+	// clean data for local test
+	serviceDiscovry.Unregister(&registry.DefaultServiceInstance{
+		Id:          id,
+		ServiceName: serviceName,
+		Host:        host,
+		Port:        port,
+	})
+
+	err := serviceDiscovry.Register(instance)
+	assert.Nil(t, err)
+
+	page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true)
+	assert.NotNil(t, page)
+
+	assert.Equal(t, 0, page.GetOffset())
+	assert.Equal(t, 10, page.GetPageSize())
+	assert.Equal(t, 1, page.GetDataSize())
+
+	instance = page.GetData()[0].(*registry.DefaultServiceInstance)
+	assert.NotNil(t, instance)
+	assert.Equal(t, id, instance.GetId())
+	assert.Equal(t, host, instance.GetHost())
+	assert.Equal(t, port, instance.GetPort())
+	assert.Equal(t, serviceName, instance.GetServiceName())
+	assert.Equal(t, 0, len(instance.GetMetadata()))
+
+	instance.Metadata["a"] = "b"
+
+	err = serviceDiscovry.Update(instance)
+	assert.Nil(t, err)
+
+	pageMap := serviceDiscovry.GetRequestInstances([]string{serviceName}, 0, 1)
+	assert.Equal(t, 1, len(pageMap))
+	page = pageMap[serviceName]
+	assert.NotNil(t, page)
+	assert.Equal(t, 1, len(page.GetData()))
+
+	instance = page.GetData()[0].(*registry.DefaultServiceInstance)
+	v, _ := instance.Metadata["a"]
+	assert.Equal(t, "b", v)
+
+	// test dispatcher event
+	err = serviceDiscovry.DispatchEventByServiceName(serviceName)
+	assert.Nil(t, err)
+
+	// test AddListener
+	err = serviceDiscovry.AddListener(&registry.ServiceInstancesChangedListener{})
+	assert.Nil(t, err)
+}
+
+func TestNacosServiceDiscovery_GetDefaultPageSize(t *testing.T) {
+	prepareData()
+	serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName)
+	assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize())
+}
+
+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.go b/registry/protocol/protocol.go
index a7678ba..4c669b2 100644
--- a/registry/protocol/protocol.go
+++ b/registry/protocol/protocol.go
@@ -22,7 +22,6 @@
 	"strings"
 	"sync"
 )
-
 import (
 	gxset "github.com/dubbogo/gost/container/set"
 )
@@ -39,20 +38,26 @@
 	"github.com/apache/dubbo-go/protocol"
 	"github.com/apache/dubbo-go/protocol/protocolwrapper"
 	"github.com/apache/dubbo-go/registry"
-	directory2 "github.com/apache/dubbo-go/registry/directory"
+	_ "github.com/apache/dubbo-go/registry/directory"
 	"github.com/apache/dubbo-go/remoting"
 )
 
 var (
-	regProtocol *registryProtocol
+	regProtocol   *registryProtocol
+	once          sync.Once
+	reserveParams = []string{
+		"application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group",
+		"loadbalance", "mock", "path", "timeout", "token", "version", "warmup", "weight", "timestamp", "dubbo",
+		"release", "interface",
+	}
 )
 
 type registryProtocol struct {
 	invokers []protocol.Invoker
 	// Registry  Map<RegistryAddress, Registry>
 	registries *sync.Map
-	//To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed.
-	//providerurl <--> exporter
+	// To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed.
+	// providerurl <--> exporter
 	bounds                        *sync.Map
 	overrideListeners             *sync.Map
 	serviceConfigurationListeners *sync.Map
@@ -65,10 +70,8 @@
 }
 
 func getCacheKey(url *common.URL) string {
-	newUrl := url.Clone()
 	delKeys := gxset.NewSet("dynamic", "enabled")
-	newUrl.RemoveParams(delKeys)
-	return newUrl.String()
+	return url.CloneExceptParams(delKeys).String()
 }
 
 func newRegistryProtocol() *registryProtocol {
@@ -87,12 +90,34 @@
 	return reg
 }
 
+func getUrlToRegistry(providerUrl *common.URL, registryUrl *common.URL) *common.URL {
+	if registryUrl.GetParamBool("simplified", false) {
+		return providerUrl.CloneWithParams(reserveParams)
+	} else {
+		return filterHideKey(providerUrl)
+	}
+}
+
+// filterHideKey filter the parameters that do not need to be output in url(Starting with .)
+func filterHideKey(url *common.URL) *common.URL {
+
+	// be careful params maps in url is map type
+	removeSet := gxset.NewSet()
+	for k, _ := range url.GetParams() {
+		if strings.HasPrefix(k, ".") {
+			removeSet.Add(k)
+		}
+	}
+	return url.CloneExceptParams(removeSet)
+}
+
 func (proto *registryProtocol) initConfigurationListeners() {
 	proto.overrideListeners = &sync.Map{}
 	proto.serviceConfigurationListeners = &sync.Map{}
 	proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners)
 }
 
+// Refer provider service from registry center
 func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
 	var registryUrl = url
 	var serviceUrl = registryUrl.SubURL
@@ -110,8 +135,8 @@
 		reg = regI.(registry.Registry)
 	}
 
-	//new registry directory for store service url from registry
-	directory, err := directory2.NewRegistryDirectory(&registryUrl, reg)
+	// new registry directory for store service url from registry
+	directory, err := extension.GetDefaultRegistryDirectory(&registryUrl, reg)
 	if err != nil {
 		logger.Errorf("consumer service %v  create registry directory  error, error message is %s, and will return nil invoker!",
 			serviceUrl.String(), err.Error())
@@ -123,9 +148,8 @@
 		logger.Errorf("consumer service %v register registry %v error, error message is %s",
 			serviceUrl.String(), registryUrl.String(), err.Error())
 	}
-	go directory.Subscribe(serviceUrl)
 
-	//new cluster invoker
+	// new cluster invoker
 	cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER))
 
 	invoker := cluster.Join(directory)
@@ -133,6 +157,7 @@
 	return invoker
 }
 
+// Export provider service to registry center
 func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	proto.once.Do(func() {
 		proto.initConfigurationListeners()
@@ -150,7 +175,6 @@
 	serviceConfigurationListener.OverrideUrl(providerUrl)
 
 	var reg registry.Registry
-
 	if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded {
 		reg = getRegistry(registryUrl)
 		proto.registries.Store(registryUrl.Key(), reg)
@@ -158,7 +182,8 @@
 		reg = regI.(registry.Registry)
 	}
 
-	err := reg.Register(*providerUrl)
+	registeredProviderUrl := getUrlToRegistry(providerUrl, registryUrl)
+	err := reg.Register(*registeredProviderUrl)
 	if err != nil {
 		logger.Errorf("provider service %v register registry %v error, error message is %s",
 			providerUrl.Key(), registryUrl.Key(), err.Error())
@@ -190,7 +215,7 @@
 		oldExporter.(protocol.Exporter).Unexport()
 		proto.bounds.Delete(key)
 		proto.Export(wrappedNewInvoker)
-		//TODO:  unregister & unsubscribe
+		// TODO:  unregister & unsubscribe
 
 	}
 }
@@ -206,6 +231,7 @@
 	return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto}
 }
 
+// Notify will be triggered when a service change notification is received.
 func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) {
 	if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd {
 		nl.configurator = extension.GetDefaultConfigurator(&(event.Service))
@@ -273,7 +299,7 @@
 	providerGroup := providerUrl.GetParam(constant.GROUP_KEY, "")
 	providerVersion := providerUrl.GetParam(constant.VERSION_KEY, "")
 	providerClassifier := providerUrl.GetParam(constant.CLASSIFIER_KEY, "")
-	//todo: public static boolean isContains(String values, String value) {
+	// todo: public static boolean isContains(String values, String value) {
 	//        return isNotEmpty(values) && isContains(COMMA_SPLIT_PATTERN.split(values), value);
 	//    }
 	return (consumerGroup == constant.ANY_VALUE || consumerGroup == providerGroup ||
@@ -302,6 +328,7 @@
 	return newUrl
 }
 
+// Destroy registry protocol
 func (proto *registryProtocol) Destroy() {
 	for _, ivk := range proto.invokers {
 		ivk.Destroy()
@@ -326,9 +353,9 @@
 }
 
 func getRegistryUrl(invoker protocol.Invoker) *common.URL {
-	//here add * for return a new url
+	// here add * for return a new url
 	url := invoker.GetUrl()
-	//if the protocol == registry ,set protocol the registry value in url.params
+	// if the protocol == registry ,set protocol the registry value in url.params
 	if url.Protocol == constant.REGISTRY_PROTOCOL {
 		protocol := url.GetParam(constant.REGISTRY_KEY, "")
 		url.Protocol = protocol
@@ -338,7 +365,7 @@
 
 func getProviderUrl(invoker protocol.Invoker) *common.URL {
 	url := invoker.GetUrl()
-	//be careful params maps in url is map type
+	// be careful params maps in url is map type
 	return url.SubURL.Clone()
 }
 
@@ -346,12 +373,12 @@
 	regURL.SubURL = providerURL
 }
 
-// GetProtocol ...
+// GetProtocol return the singleton RegistryProtocol
 func GetProtocol() protocol.Protocol {
-	if regProtocol != nil {
-		return regProtocol
-	}
-	return newRegistryProtocol()
+	once.Do(func() {
+		regProtocol = newRegistryProtocol()
+	})
+	return regProtocol
 }
 
 type wrappedInvoker struct {
@@ -366,6 +393,7 @@
 	}
 }
 
+// Invoke remote service base on URL of wrappedInvoker
 func (ivk *wrappedInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
 	// get right url
 	ivk.invoker.(*proxy_factory.ProxyInvoker).BaseInvoker = *protocol.NewBaseInvoker(ivk.GetUrl())
@@ -388,6 +416,7 @@
 	return listener
 }
 
+// Process notified once there's any change happens on the provider config
 func (listener *providerConfigurationListener) Process(event *config_center.ConfigChangeEvent) {
 	listener.BaseConfigurationListener.Process(event)
 	listener.overrideListeners.Range(func(key, value interface{}) bool {
@@ -412,6 +441,7 @@
 	return listener
 }
 
+// Process notified once there's any change happens on the service config
 func (listener *serviceConfigurationListener) Process(event *config_center.ConfigChangeEvent) {
 	listener.BaseConfigurationListener.Process(event)
 	listener.overrideListener.doOverrideIfNecessary()
diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go
index cee2a6a..2d6e024 100644
--- a/registry/protocol/protocol_test.go
+++ b/registry/protocol/protocol_test.go
@@ -42,7 +42,9 @@
 )
 
 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 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)
 }
@@ -284,3 +287,12 @@
 	v2, _ := regProtocol.bounds.Load(getCacheKey(newUrl))
 	assert.NotNil(t, v2)
 }
+
+func TestGetProviderUrlWithHideKey(t *testing.T) {
+	url, _ := common.NewURL("dubbo://127.0.0.1:1111?a=a1&b=b1&.c=c1&.d=d1&e=e1&protocol=registry")
+	providerUrl := getUrlToRegistry(&url, &url)
+	assert.NotContains(t, providerUrl.GetParams(), ".c")
+	assert.NotContains(t, providerUrl.GetParams(), ".d")
+	assert.Contains(t, providerUrl.GetParams(), "a")
+
+}
diff --git a/registry/registry.go b/registry/registry.go
index d673864..bb09ead 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -34,24 +34,40 @@
 	//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
 }
 
-// NotifyListener ...
+// nolint
 type NotifyListener interface {
+	// Notify supports notifications on the service interface and the dimension of the data type.
 	Notify(*ServiceEvent)
 }
 
 // Listener Deprecated!
 type Listener interface {
+	// Next returns next service event once received
 	Next() (*ServiceEvent, error)
+	// Close closes this listener
 	Close()
 }
diff --git a/registry/service_discovery.go b/registry/service_discovery.go
new file mode 100644
index 0000000..cb7a3c0
--- /dev/null
+++ b/registry/service_discovery.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 registry
+
+import (
+	"fmt"
+)
+
+import (
+	gxset "github.com/dubbogo/gost/container/set"
+	gxpage "github.com/dubbogo/gost/page"
+)
+
+const DefaultPageSize = 100
+
+// ServiceDiscovery is the common operations of Service Discovery
+type ServiceDiscovery interface {
+	fmt.Stringer
+
+	// ----------------- lifecycle -------------------
+
+	// Destroy will destroy the service discovery.
+	// If the discovery cannot be destroy, it will return an error.
+	Destroy() error
+
+	// ----------------- registration ----------------
+
+	// Register will register an instance of ServiceInstance to registry
+	Register(instance ServiceInstance) error
+
+	// Update will update the data of the instance in registry
+	Update(instance ServiceInstance) error
+
+	// Unregister will unregister this instance from registry
+	Unregister(instance ServiceInstance) error
+
+	// ----------------- discovery -------------------
+	// GetDefaultPageSize will return the default page size
+	GetDefaultPageSize() int
+
+	// GetServices will return the all service names.
+	GetServices() *gxset.HashSet
+
+	// GetInstances will return all service instances with serviceName
+	GetInstances(serviceName string) []ServiceInstance
+
+	// GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName
+	// the page will start at offset
+	GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager
+
+	// 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
+	GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager
+
+	// Batch get all instances by the specified service names
+	GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager
+
+	// ----------------- event ----------------------
+	// AddListener adds a new ServiceInstancesChangedListener
+	// see addServiceInstancesChangedListener in Java
+	AddListener(listener *ServiceInstancesChangedListener) error
+
+	// DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName
+	DispatchEventByServiceName(serviceName string) error
+
+	// DispatchEventForInstances dispatches the ServiceInstancesChangedEvent to target instances
+	DispatchEventForInstances(serviceName string, instances []ServiceInstance) error
+
+	// DispatchEvent dispatches the event
+	DispatchEvent(event *ServiceInstancesChangedEvent) error
+}
diff --git a/registry/service_instance.go b/registry/service_instance.go
new file mode 100644
index 0000000..dbb4582
--- /dev/null
+++ b/registry/service_instance.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 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 {
+
+	// GetId will return this instance's id. It should be unique.
+	GetId() string
+
+	// GetServiceName will return the serviceName
+	GetServiceName() string
+
+	// GetHost will return the hostname
+	GetHost() string
+
+	// GetPort will return the port.
+	GetPort() int
+
+	// IsEnable will return the enable status of this instance
+	IsEnable() bool
+
+	// IsHealthy will return the value represent the instance whether healthy or not
+	IsHealthy() bool
+
+	// GetMetadata will return the metadata
+	GetMetadata() map[string]string
+}
+
+// DefaultServiceInstance the default implementation of ServiceInstance
+// or change the ServiceInstance to be struct???
+type DefaultServiceInstance struct {
+	Id          string
+	ServiceName string
+	Host        string
+	Port        int
+	Enable      bool
+	Healthy     bool
+	Metadata    map[string]string
+}
+
+// GetId will return this instance's id. It should be unique.
+func (d *DefaultServiceInstance) GetId() string {
+	return d.Id
+}
+
+// GetServiceName will return the serviceName
+func (d *DefaultServiceInstance) GetServiceName() string {
+	return d.ServiceName
+}
+
+// GetHost will return the hostname
+func (d *DefaultServiceInstance) GetHost() string {
+	return d.Host
+}
+
+// GetPort will return the port.
+func (d *DefaultServiceInstance) GetPort() int {
+	return d.Port
+}
+
+// IsEnable will return the enable status of this instance
+func (d *DefaultServiceInstance) IsEnable() bool {
+	return d.Enable
+}
+
+// IsHealthy will return the value represent the instance whether healthy or not
+func (d *DefaultServiceInstance) IsHealthy() bool {
+	return d.Healthy
+}
+
+// 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 0000000..3f8f30d
--- /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 0000000..cddeb42
--- /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/registry/servicediscovery/instance/service_instance_selector.go b/registry/servicediscovery/instance/service_instance_selector.go
new file mode 100644
index 0000000..82fb345
--- /dev/null
+++ b/registry/servicediscovery/instance/service_instance_selector.go
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package instance
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/registry"
+)
+
+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 0000000..061d832
--- /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 0000000..53eb865
--- /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 0000000..086a26d
--- /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 0000000..b52cc23
--- /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 0000000..949a582
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package synthesizer
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/registry"
+)
+
+type SubscribedURLsSynthesizer interface {
+	// Supports the synthesis of the subscribed url or not
+	Support(subscribedURL *common.URL) bool
+	// synthesize the subscribed url
+	Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL
+}
diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go
new file mode 100644
index 0000000..c9b1449
--- /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 bef1760..ec82fa0 100644
--- a/registry/zookeeper/listener.go
+++ b/registry/zookeeper/listener.go
@@ -35,23 +35,38 @@
 	zk "github.com/apache/dubbo-go/remoting/zookeeper"
 )
 
-// RegistryDataListener ...
+// RegistryDataListener contains all URL information subscribed by zookeeper registry
 type RegistryDataListener struct {
-	interestedURL []*common.URL
-	listener      config_center.ConfigurationListener
+	subscribed map[string]config_center.ConfigurationListener
+	mutex      sync.Mutex
+	closed     bool
 }
 
-// NewRegistryDataListener ...
-func NewRegistryDataListener(listener config_center.ConfigurationListener) *RegistryDataListener {
-	return &RegistryDataListener{listener: listener}
+// NewRegistryDataListener constructs a new RegistryDataListener
+func NewRegistryDataListener() *RegistryDataListener {
+	return &RegistryDataListener{
+		subscribed: make(map[string]config_center.ConfigurationListener)}
 }
 
-// AddInterestedURL ...
-func (l *RegistryDataListener) AddInterestedURL(url *common.URL) {
-	l.interestedURL = append(l.interestedURL, url)
+// SubscribeURL is used to set a watch listener for url
+func (l *RegistryDataListener) SubscribeURL(url *common.URL, listener config_center.ConfigurationListener) {
+	if l.closed {
+		return
+	}
+	l.subscribed[url.ServiceKey()] = listener
 }
 
-// DataChange ...
+// 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
 func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
 	// Intercept the last bit
 	index := strings.Index(eventType.Path, "/providers/")
@@ -65,10 +80,14 @@
 		logger.Errorf("Listen NewURL(r{%s}) = error{%v} eventType.Path={%v}", url, err, eventType.Path)
 		return false
 	}
-
-	for _, v := range l.interestedURL {
-		if serviceURL.URLEqual(*v) {
-			l.listener.Process(
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+	if l.closed {
+		return false
+	}
+	for serviceKey, listener := range l.subscribed {
+		if serviceURL.ServiceKey() == serviceKey {
+			listener.Process(
 				&config_center.ConfigChangeEvent{
 					Key:        eventType.Path,
 					Value:      serviceURL,
@@ -81,38 +100,55 @@
 	return false
 }
 
-// RegistryConfigurationListener ...
+// Close all RegistryConfigurationListener in subscribed
+func (l *RegistryDataListener) Close() {
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+	for _, listener := range l.subscribed {
+		listener.(*RegistryConfigurationListener).Close()
+	}
+}
+
+// RegistryConfigurationListener represent the processor of zookeeper watcher
 type RegistryConfigurationListener struct {
-	client    *zk.ZookeeperClient
-	registry  *zkRegistry
-	events    chan *config_center.ConfigChangeEvent
-	isClosed  bool
-	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}
+	return &RegistryConfigurationListener{
+		client:       client,
+		registry:     reg,
+		events:       make(chan *config_center.ConfigChangeEvent, 32),
+		isClosed:     false,
+		close:        make(chan struct{}, 1),
+		subscribeURL: conf}
 }
 
-// Process ...
+// Process submit the ConfigChangeEvent to the event chan to notify all observer
 func (l *RegistryConfigurationListener) Process(configType *config_center.ConfigChangeEvent) {
 	l.events <- configType
 }
 
-// Next ...
+// Next will observe the registry state and events chan
 func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) {
 	for {
 		select {
 		case <-l.client.Done():
-			logger.Warnf("listener's zk client connection is broken, so zk event listener exit now.")
-			return nil, perrors.New("listener stopped")
-
+			logger.Warnf("listener's zk client connection (address {%s}) is broken, so zk event listener exit now.", l.client.ZkAddrs)
+			return nil, perrors.New("zookeeper client stopped")
+		case <-l.close:
+			return nil, perrors.New("listener have been closed")
 		case <-l.registry.Done():
-			logger.Warnf("zk consumer register has quit, so zk event listener exit now.")
-			return nil, perrors.New("listener stopped")
-
+			logger.Warnf("zk consumer register has quit, so zk event listener exit now. (registry url {%v}", l.registry.BaseRegistry.URL)
+			return nil, perrors.New("zookeeper registry, (registry url{%v}) stopped")
 		case e := <-l.events:
 			logger.Debugf("got zk event %s", e)
 			if e.ConfigType == remoting.EventTypeDel && !l.valid() {
@@ -127,15 +163,17 @@
 	}
 }
 
-// Close ...
+// Close RegistryConfigurationListener only once
 func (l *RegistryConfigurationListener) Close() {
 	// ensure that the listener will be closed at most once.
 	l.closeOnce.Do(func() {
 		l.isClosed = true
+		l.close <- struct{}{}
 		l.registry.WaitGroup().Done()
 	})
 }
 
+// valid return the true if the client conn isn't nil
 func (l *RegistryConfigurationListener) valid() bool {
 	return l.client.ZkConnValid()
 }
diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go
index 1a76b29..a0e9147 100644
--- a/registry/zookeeper/listener_test.go
+++ b/registry/zookeeper/listener_test.go
@@ -32,15 +32,15 @@
 )
 
 func Test_DataChange(t *testing.T) {
-	listener := NewRegistryDataListener(&MockDataListener{})
+	listener := NewRegistryDataListener()
 	url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100")
-	listener.AddInterestedURL(&url)
+	listener.SubscribeURL(&url, &MockConfigurationListener{})
 	int := listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"})
 	assert.Equal(t, true, int)
 }
 
-type MockDataListener struct {
+type MockConfigurationListener struct {
 }
 
-func (*MockDataListener) Process(configType *config_center.ConfigChangeEvent) {
+func (*MockConfigurationListener) Process(configType *config_center.ConfigChangeEvent) {
 }
diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go
index e13443d..8f2ac10 100644
--- a/registry/zookeeper/registry.go
+++ b/registry/zookeeper/registry.go
@@ -20,7 +20,7 @@
 import (
 	"fmt"
 	"net/url"
-	"strings"
+	"path"
 	"sync"
 	"time"
 )
@@ -54,12 +54,11 @@
 
 type zkRegistry struct {
 	registry.BaseRegistry
-	client         *zookeeper.ZookeeperClient
-	listenerLock   sync.Mutex
-	listener       *zookeeper.ZkEventListener
-	dataListener   *RegistryDataListener
-	configListener *RegistryConfigurationListener
-	cltLock        sync.Mutex
+	client       *zookeeper.ZookeeperClient
+	listenerLock sync.Mutex
+	listener     *zookeeper.ZkEventListener
+	dataListener *RegistryDataListener
+	cltLock      sync.Mutex
 	//for provider
 	zkPath map[string]int // key = protocol://ip:port/interface
 }
@@ -78,23 +77,22 @@
 	if err != nil {
 		return nil, err
 	}
-	r.WaitGroup().Add(1) //zk client start successful, then wg +1
 
 	go zookeeper.HandleClientRestart(r)
 
 	r.listener = zookeeper.NewZkEventListener(r.client)
-	r.configListener = NewRegistryConfigurationListener(r.client, r)
-	r.dataListener = NewRegistryDataListener(r.configListener)
+
+	r.dataListener = NewRegistryDataListener()
 
 	return r, nil
 }
 
-// Options ...
+// nolint
 type Options struct {
 	client *zookeeper.ZookeeperClient
 }
 
-// Option ...
+// nolint
 type Option func(*Options)
 
 func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestCluster, *zkRegistry, error) {
@@ -119,44 +117,90 @@
 	return c, r, nil
 }
 
+// InitListeners initializes listeners of zookeeper registry center
 func (r *zkRegistry) InitListeners() {
 	r.listener = zookeeper.NewZkEventListener(r.client)
-	r.configListener = NewRegistryConfigurationListener(r.client, r)
-	r.dataListener = NewRegistryDataListener(r.configListener)
+	newDataListener := NewRegistryDataListener()
+	// should recover if dataListener isn't nil before
+	if r.dataListener != nil {
+		// close all old listener
+		oldDataListener := r.dataListener
+		oldDataListener.mutex.Lock()
+		defer oldDataListener.mutex.Unlock()
+		recoverd := r.dataListener.subscribed
+		if recoverd != nil && len(recoverd) > 0 {
+			// recover all subscribed url
+			for _, oldListener := range recoverd {
+				var (
+					regConfigListener *RegistryConfigurationListener
+					ok                bool
+				)
+
+				if regConfigListener, ok = oldListener.(*RegistryConfigurationListener); ok {
+					regConfigListener.Close()
+				}
+				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)
+
+			}
+		}
+	}
+	r.dataListener = newDataListener
 }
 
+// CreatePath creates the path in the registry center of zookeeper
 func (r *zkRegistry) CreatePath(path string) error {
 	return r.ZkClient().Create(path)
 }
 
+// DoRegister actually do the register job in the registry center of zookeeper
 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()
 	r.client = nil
 }
 
+// nolint
 func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient {
 	return r.client
 }
 
+// nolint
 func (r *zkRegistry) SetZkClient(client *zookeeper.ZookeeperClient) {
 	r.client = client
 }
 
+// nolint
 func (r *zkRegistry) ZkClientLock() *sync.Mutex {
 	return &r.cltLock
 }
 
+// CloseListener closes listeners
 func (r *zkRegistry) CloseListener() {
-	if r.configListener != nil {
-		r.configListener.Close()
+	if r.dataListener != nil {
+		r.dataListener.Close()
 	}
 }
 
@@ -173,32 +217,49 @@
 		logger.Errorf("zk.Create(root{%s}) = err{%v}", root, perrors.WithStack(err))
 		return perrors.WithStack(err)
 	}
+
+	// try to register the node
 	zkPath, err = r.client.RegisterTemp(root, node)
 	if err != nil {
-		if err == zk.ErrNodeExists {
-			logger.Warnf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
-		} else {
-			logger.Errorf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+		logger.Errorf("Register temp node(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+		if perrors.Cause(err) == zk.ErrNodeExists {
+			// should delete the old node
+			logger.Info("Register temp node failed, try to delete the old and recreate  (root{%s}, node{%s}) , ignore!", root, node)
+			if err = r.client.Delete(zkPath); err == nil {
+				_, err = r.client.RegisterTemp(root, node)
+			}
+			if err != nil {
+				logger.Errorf("Recreate the temp node failed, (root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err))
+			}
 		}
 		return perrors.WithMessagef(err, "RegisterTempNode(root{%s}, node{%s})", root, node)
 	}
-	logger.Debugf("create a zookeeper node:%s", zkPath)
+	logger.Debugf("Create a zookeeper node:%s", zkPath)
 
 	return nil
 }
 
 func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListener, error) {
-	var (
-		zkListener *RegistryConfigurationListener
-	)
 
-	r.listenerLock.Lock()
-	if r.configListener.isClosed {
-		r.listenerLock.Unlock()
-		return nil, perrors.New("configListener already been closed")
+	var zkListener *RegistryConfigurationListener
+	dataListener := r.dataListener
+	dataListener.mutex.Lock()
+	defer dataListener.mutex.Unlock()
+	if r.dataListener.subscribed[conf.ServiceKey()] != nil {
+
+		zkListener, _ := r.dataListener.subscribed[conf.ServiceKey()].(*RegistryConfigurationListener)
+		if zkListener != nil {
+			r.listenerLock.Lock()
+			defer r.listenerLock.Unlock()
+			if zkListener.isClosed {
+				return nil, perrors.New("configListener already been closed")
+			} else {
+				return zkListener, nil
+			}
+		}
 	}
-	zkListener = r.configListener
-	r.listenerLock.Unlock()
+
+	zkListener = NewRegistryConfigurationListener(r.client, r, conf)
 	if r.listener == nil {
 		r.cltLock.Lock()
 		client := r.client
@@ -216,10 +277,43 @@
 	}
 
 	//Interested register to dataconfig.
-	r.dataListener.AddInterestedURL(conf)
-	for _, v := range strings.Split(conf.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") {
-		go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, url.QueryEscape(conf.Service())), r.dataListener)
+	r.dataListener.SubscribeURL(conf, zkListener)
+
+	go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), r.dataListener)
+
+	return zkListener, nil
+}
+
+func (r *zkRegistry) getCloseListener(conf *common.URL) (*RegistryConfigurationListener, error) {
+
+	var zkListener *RegistryConfigurationListener
+	r.dataListener.mutex.Lock()
+	configurationListener := r.dataListener.subscribed[conf.ServiceKey()]
+	if configurationListener != nil {
+
+		zkListener, _ := configurationListener.(*RegistryConfigurationListener)
+		if zkListener != nil {
+			if zkListener.isClosed {
+				return nil, perrors.New("configListener already been closed")
+			}
+		}
 	}
 
+	zkListener = r.dataListener.UnSubscribeURL(conf).(*RegistryConfigurationListener)
+	r.dataListener.mutex.Unlock()
+
+	if r.listener == nil {
+		return nil, perrors.New("listener is null can not close.")
+	}
+
+	//Interested register to dataconfig.
+	r.listenerLock.Lock()
+	listener := r.listener
+	r.listener = nil
+	r.listenerLock.Unlock()
+
+	r.dataListener.Close()
+	listener.Close()
+
 	return zkListener, nil
 }
diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go
index 0d7623c..d915fc2 100644
--- a/registry/zookeeper/registry_test.go
+++ b/registry/zookeeper/registry_test.go
@@ -41,10 +41,35 @@
 	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%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*.serviceid%3Dsoa.mock%26.*provider", children)
+	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_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 @@
 	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 0000000..5ad83ef
--- /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 0000000..ea3c7dd
--- /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 0000000..1744da7
--- /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 0000000..8cf0ac6
--- /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 ba3ea6e..7632a7c 100644
--- a/remoting/etcdv3/client.go
+++ b/remoting/etcdv3/client.go
@@ -19,7 +19,6 @@
 
 import (
 	"context"
-	"path"
 	"sync"
 	"time"
 )
@@ -42,16 +41,17 @@
 	MaxFailTimes = 15
 	// RegistryETCDV3Client client name
 	RegistryETCDV3Client = "etcd registry"
+	// metadataETCDV3Client client name
+	MetadataETCDV3Client = "etcd metadata"
 )
 
 var (
-	// ErrNilETCDV3Client ...
+	// Defines related errors
 	ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR
-	// ErrKVPairNotFound ...
-	ErrKVPairNotFound = perrors.New("k/v pair not found")
+	ErrKVPairNotFound  = perrors.New("k/v pair not found")
 )
 
-// Options ...
+// nolint
 type Options struct {
 	name      string
 	endpoints []string
@@ -60,38 +60,38 @@
 	heartbeat int // heartbeat second
 }
 
-// Option ...
+// Option will define a function of handling Options
 type Option func(*Options)
 
-// WithEndpoints ...
+// WithEndpoints sets etcd client endpoints
 func WithEndpoints(endpoints ...string) Option {
 	return func(opt *Options) {
 		opt.endpoints = endpoints
 	}
 }
 
-// WithName ...
+// WithName sets etcd client name
 func WithName(name string) Option {
 	return func(opt *Options) {
 		opt.name = name
 	}
 }
 
-// WithTimeout ...
+// WithTimeout sets etcd client timeout
 func WithTimeout(timeout time.Duration) Option {
 	return func(opt *Options) {
 		opt.timeout = timeout
 	}
 }
 
-// WithHeartbeat ...
+// WithHeartbeat sets etcd client heartbeat
 func WithHeartbeat(heartbeat int) Option {
 	return func(opt *Options) {
 		opt.heartbeat = heartbeat
 	}
 }
 
-// ValidateClient ...
+// ValidateClient validates client and sets options
 func ValidateClient(container clientFacade, opts ...Option) error {
 
 	options := &Options{
@@ -107,7 +107,7 @@
 
 	// 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)
@@ -119,7 +119,7 @@
 	// 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)
@@ -131,7 +131,27 @@
 	return nil
 }
 
-// Client ...
+//  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
 
@@ -142,14 +162,15 @@
 	heartbeat int
 
 	ctx       context.Context    // if etcd server connection lose, the ctx.Done will be sent msg
-	cancel    context.CancelFunc // cancel the ctx,  all watcher will stopped
+	cancel    context.CancelFunc // cancel the ctx, all watcher will stopped
 	rawClient *clientv3.Client
 
 	exit chan 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{
@@ -206,7 +227,7 @@
 	return false
 }
 
-// Close ...
+// nolint
 func (c *Client) Close() {
 
 	if c == nil {
@@ -265,8 +286,7 @@
 	}
 }
 
-// if k not exist will put k/v in etcd
-// if k is already exist in etcd, return nil
+// if k not exist will put k/v in etcd, otherwise return nil
 func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error {
 
 	c.lock.RLock()
@@ -287,6 +307,28 @@
 	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()
@@ -325,7 +367,7 @@
 	return string(resp.Kvs[0].Value), nil
 }
 
-// CleanKV ...
+// nolint
 func (c *Client) CleanKV() error {
 
 	c.lock.RLock()
@@ -425,12 +467,12 @@
 	return nil
 }
 
-// Done ...
+// nolint
 func (c *Client) Done() <-chan struct{} {
 	return c.exit
 }
 
-// Valid ...
+// nolint
 func (c *Client) Valid() bool {
 	select {
 	case <-c.exit:
@@ -447,7 +489,7 @@
 	return true
 }
 
-// Create ...
+// nolint
 func (c *Client) Create(k string, v string) error {
 
 	err := c.put(k, v)
@@ -457,7 +499,16 @@
 	return nil
 }
 
-// Delete ...
+// 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 {
 
 	err := c.delete(k)
@@ -468,20 +519,18 @@
 	return nil
 }
 
-// RegisterTemp ...
-func (c *Client) RegisterTemp(basePath string, node string) (string, error) {
+// RegisterTemp registers a temporary node
+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 ...
+// GetChildrenKVList gets children kv list by @k
 func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) {
 
 	kList, vList, err := c.getChildren(k)
@@ -491,7 +540,7 @@
 	return kList, vList, nil
 }
 
-// Get ...
+// Get gets value by @k
 func (c *Client) Get(k string) (string, error) {
 
 	v, err := c.get(k)
@@ -502,7 +551,7 @@
 	return v, nil
 }
 
-// Watch ...
+// Watch watches on spec key
 func (c *Client) Watch(k string) (clientv3.WatchChan, error) {
 
 	wc, err := c.watch(k)
@@ -512,7 +561,7 @@
 	return wc, nil
 }
 
-// WatchWithPrefix ...
+// WatchWithPrefix watches on spec prefix
 func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) {
 
 	wc, err := c.watchWithPrefix(prefix)
diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go
index 895cc29..3de266f 100644
--- a/remoting/etcdv3/client_test.go
+++ b/remoting/etcdv3/client_test.go
@@ -29,11 +29,11 @@
 )
 
 import (
+	"github.com/coreos/etcd/embed"
 	"github.com/coreos/etcd/mvcc/mvccpb"
 	perrors "github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/suite"
-	"go.etcd.io/etcd/embed"
 	"google.golang.org/grpc/connectivity"
 )
 
@@ -120,7 +120,7 @@
 }
 
 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 @@
 	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 @@
 
 	c.Wait.Wait()
 
-	if c.Valid() == true {
+	if c.Valid() {
 		suite.T().Fatal("client should be invalid then")
 	}
 }
@@ -384,7 +384,7 @@
 		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 35befc8..2edbb66 100644
--- a/remoting/etcdv3/facade.go
+++ b/remoting/etcdv3/facade.go
@@ -43,7 +43,7 @@
 	common.Node
 }
 
-// HandleClientRestart ...
+// HandleClientRestart keeps the connection between client and server
 func HandleClientRestart(r clientFacade) {
 
 	var (
@@ -85,10 +85,8 @@
 				)
 				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 e3cb74e..4f80a89 100644
--- a/remoting/etcdv3/listener.go
+++ b/remoting/etcdv3/listener.go
@@ -33,7 +33,7 @@
 	"github.com/apache/dubbo-go/remoting"
 )
 
-// EventListener ...
+// nolint
 type EventListener struct {
 	client     *Client
 	keyMapLock sync.Mutex
@@ -41,7 +41,7 @@
 	wg         sync.WaitGroup
 }
 
-// NewEventListener ...
+// NewEventListener returns a EventListener instance
 func NewEventListener(client *Client) *EventListener {
 	return &EventListener{
 		client: client,
@@ -49,7 +49,7 @@
 	}
 }
 
-// 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 {
@@ -92,12 +92,10 @@
 			}
 		}
 	}
-
-	return false
 }
 
-// return true mean the event type is DELETE
-// return false mean the event type is CREATE || UPDATE
+// return true means the event type is DELETE
+// return false means the event type is CREATE || UPDATE
 func (l *EventListener) handleEvents(event *clientv3.Event, listeners ...remoting.DataListener) bool {
 
 	logger.Infof("got a etcd event {type: %s, key: %s}", event.Type, event.Kv.Key)
@@ -135,7 +133,7 @@
 	panic("unreachable")
 }
 
-// ListenServiceNodeEventWithPrefix Listen on a set of key with spec prefix
+// ListenServiceNodeEventWithPrefix listens on a set of key with spec prefix
 func (l *EventListener) ListenServiceNodeEventWithPrefix(prefix string, listener ...remoting.DataListener) {
 	defer l.wg.Done()
 	for {
@@ -151,12 +149,12 @@
 			logger.Warnf("etcd client stopped")
 			return
 
-			// client ctx stop
+		// client ctx stop
 		case <-l.client.ctx.Done():
 			logger.Warnf("etcd client ctx cancel")
 			return
 
-			// etcd event stream
+		// etcd event stream
 		case e, ok := <-wc:
 
 			if !ok {
@@ -180,9 +178,9 @@
 }
 
 // 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()
@@ -230,7 +228,7 @@
 	}(key)
 }
 
-// Close ...
+// nolint
 func (l *EventListener) Close() {
 	l.wg.Wait()
 }
diff --git a/remoting/kubernetes/client.go b/remoting/kubernetes/client.go
index 0c9ffd2..0a05489 100644
--- a/remoting/kubernetes/client.go
+++ b/remoting/kubernetes/client.go
@@ -19,444 +19,62 @@
 
 import (
 	"context"
-	"encoding/base64"
-	"encoding/json"
-	"os"
+	"strconv"
 	"sync"
-	"time"
 )
 
 import (
 	perrors "github.com/pkg/errors"
-	"k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/fields"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/strategicpatch"
-	"k8s.io/apimachinery/pkg/watch"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/rest"
+	"k8s.io/client-go/kubernetes/fake"
 )
 
 import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/logger"
 )
 
-const (
-	// kubernetes inject the var
-	podNameKey   = "HOSTNAME"
-	nameSpaceKey = "NAMESPACE"
-	// all pod annotation key
-	DubboIOAnnotationKey = "dubbo.io/annotation"
-
-	DubboIOLabelKey   = "dubbo.io/label"
-	DubboIOLabelValue = "dubbo.io-value"
-)
-
-var (
-	ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist")
-)
-
 type Client struct {
-
-	// kubernetes connection config
-	cfg *rest.Config
-
-	// the kubernetes interface
-	rawClient kubernetes.Interface
-
-	// current pod config
-	currentPodName string
-
-	ns string
-
-	// current resource version
-	lastResourceVersion string
-
-	// the memory watcherSet
-	watcherSet WatcherSet
-
-	// protect the wg && currentPod
 	lock sync.RWMutex
-	// current pod status
-	currentPod *v1.Pod
-	// protect the watchPods loop && watcher
-	wg sync.WaitGroup
 
 	// manage the  client lifecycle
 	ctx    context.Context
 	cancel context.CancelFunc
+
+	controller *dubboRegistryController
 }
 
-// load CurrentPodName
-func getCurrentPodName() (string, error) {
-
-	v := os.Getenv(podNameKey)
-	if len(v) == 0 {
-		return "", perrors.New("read value from env by key (HOSTNAME)")
-	}
-	return v, nil
-}
-
-// load CurrentNameSpace
-func getCurrentNameSpace() (string, error) {
-
-	v := os.Getenv(nameSpaceKey)
-	if len(v) == 0 {
-		return "", perrors.New("read value from env by key (NAMESPACE)")
-	}
-	return v, nil
-}
-
-// NewMockClient
-// export for registry package test
-func NewMockClient(namespace string, mockClientGenerator func() (kubernetes.Interface, error)) (*Client, error) {
-	return newMockClient(namespace, mockClientGenerator)
-}
-
-// newMockClient
-// new a client for  test
-func newMockClient(namespace string, mockClientGenerator func() (kubernetes.Interface, error)) (*Client, error) {
-
-	rawClient, err := mockClientGenerator()
-	if err != nil {
-		return nil, perrors.WithMessage(err, "call mock generator")
-	}
-
-	currentPodName, err := getCurrentPodName()
-	if err != nil {
-		return nil, perrors.WithMessage(err, "get pod name")
-	}
+// newClient returns Client instance for registry
+func newClient(url common.URL) (*Client, error) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 
-	c := &Client{
-		currentPodName: currentPodName,
-		ns:             namespace,
-		rawClient:      rawClient,
-		ctx:            ctx,
-		watcherSet:     newWatcherSet(ctx),
-		cancel:         cancel,
-	}
-
-	currentPod, err := c.initCurrentPod()
+	// read type
+	r, err := strconv.Atoi(url.GetParams().Get(constant.ROLE_KEY))
 	if err != nil {
-		return nil, perrors.WithMessage(err, "init current pod")
+		return nil, perrors.WithMessage(err, "atoi role")
+	}
+	controller, err := newDubboRegistryController(ctx, common.RoleType(r), GetInClusterKubernetesClient)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "new dubbo-registry controller")
 	}
 
-	// record current status
-	c.currentPod = currentPod
-
-	// init the watcherSet by current pods
-	if err := c.initWatchSet(); err != nil {
-		return nil, perrors.WithMessage(err, "init watcherSet")
+	c := &Client{
+		ctx:        ctx,
+		cancel:     cancel,
+		controller: controller,
 	}
 
-	c.lastResourceVersion = c.currentPod.GetResourceVersion()
-
-	// start kubernetes watch loop
-	if err := c.watchPods(); err != nil {
-		return nil, perrors.WithMessage(err, "watch pods")
+	if r == common.CONSUMER {
+		// only consumer have to start informer factory
+		c.controller.startALLInformers()
 	}
-
-	logger.Infof("init kubernetes registry client success @namespace = %q @Podname = %q", namespace, c.currentPod.Name)
 	return c, nil
 }
 
-// newClient
-// new a client for registry
-func newClient(namespace string) (*Client, error) {
-
-	cfg, err := rest.InClusterConfig()
-	if err != nil {
-		return nil, perrors.WithMessage(err, "get in-cluster config")
-	}
-
-	rawClient, err := kubernetes.NewForConfig(cfg)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "new kubernetes client by in cluster config")
-	}
-
-	currentPodName, err := getCurrentPodName()
-	if err != nil {
-		return nil, perrors.WithMessage(err, "get pod name")
-	}
-
-	ctx, cancel := context.WithCancel(context.Background())
-
-	c := &Client{
-		currentPodName: currentPodName,
-		ns:             namespace,
-		cfg:            cfg,
-		rawClient:      rawClient,
-		ctx:            ctx,
-		watcherSet:     newWatcherSet(ctx),
-		cancel:         cancel,
-	}
-
-	currentPod, err := c.initCurrentPod()
-	if err != nil {
-		return nil, perrors.WithMessage(err, "init current pod")
-	}
-
-	// record current status
-	c.currentPod = currentPod
-
-	// init the watcherSet by current pods
-	if err := c.initWatchSet(); err != nil {
-		return nil, perrors.WithMessage(err, "init watcherSet")
-	}
-
-	// start kubernetes watch loop
-	if err := c.watchPods(); err != nil {
-		return nil, perrors.WithMessage(err, "watch pods")
-	}
-
-	logger.Infof("init kubernetes registry client success @namespace = %q @Podname = %q", namespace, c.currentPod.Name)
-	return c, nil
-}
-
-// initCurrentPod
-// 1. get current pod
-// 2. give the dubbo-label for this pod
-func (c *Client) initCurrentPod() (*v1.Pod, error) {
-
-	// read the current pod status
-	currentPod, err := c.rawClient.CoreV1().Pods(c.ns).Get(c.currentPodName, metav1.GetOptions{})
-	if err != nil {
-		return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.currentPodName, c.ns)
-	}
-
-	oldPod, newPod, err := c.assembleDUBBOLabel(currentPod)
-	if err != nil {
-		if err != ErrDubboLabelAlreadyExist {
-			return nil, perrors.WithMessage(err, "assemble dubbo label")
-		}
-		// current pod don't have label
-	}
-
-	p, err := c.getPatch(oldPod, newPod)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "get patch")
-	}
-
-	currentPod, err = c.patchCurrentPod(p)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "patch to current pod")
-	}
-
-	return currentPod, nil
-}
-
-// initWatchSet
-// 1. get all with dubbo label pods
-// 2. put every element to watcherSet
-func (c *Client) initWatchSet() error {
-
-	pods, err := c.rawClient.CoreV1().Pods(c.ns).List(metav1.ListOptions{
-		LabelSelector: fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(),
-	})
-	if err != nil {
-		return perrors.WithMessagef(err, "list pods  in namespace (%s)", c.ns)
-	}
-
-	// set resource version
-	c.lastResourceVersion = pods.GetResourceVersion()
-
-	for _, pod := range pods.Items {
-		logger.Debugf("got the pod (name: %s), (label: %v), (annotations: %v)", pod.Name, pod.GetLabels(), pod.GetAnnotations())
-		c.handleWatchedPodEvent(&pod, watch.Added)
-	}
-
-	return nil
-}
-
-// watchPods
-// try to watch kubernetes pods
-func (c *Client) watchPods() error {
-
-	// try once
-	watcher, err := c.rawClient.CoreV1().Pods(c.ns).Watch(metav1.ListOptions{
-		LabelSelector:   fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(),
-		Watch:           true,
-		ResourceVersion: c.lastResourceVersion,
-	})
-	if err != nil {
-		return perrors.WithMessagef(err, "try to watch the namespace (%s) pods", c.ns)
-	}
-
-	watcher.Stop()
-
-	c.wg.Add(1)
-	// add wg, grace close the client
-	go c.watchPodsLoop()
-	return nil
-}
-
-type resourceVersionGetter interface {
-	GetResourceVersion() string
-}
-
-// watchPods
-// try to notify
-func (c *Client) watchPodsLoop() {
-
-	defer func() {
-		// notify other goroutine, this loop over
-		c.wg.Done()
-		logger.Info("watchPodsLoop goroutine game over")
-	}()
-
-	for {
-	onceWatch:
-		wc, err := c.rawClient.CoreV1().Pods(c.ns).Watch(metav1.ListOptions{
-			LabelSelector:   fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(),
-			Watch:           true,
-			ResourceVersion: c.lastResourceVersion,
-		})
-		if err != nil {
-			logger.Warnf("watch the namespace (%s) pods: %v, retry after 2 seconds", c.ns, err)
-			time.Sleep(2 * time.Second)
-			continue
-		}
-
-		logger.Infof("the old kubernetes client broken, collect the resource status from resource version (%s)", c.lastResourceVersion)
-
-		for {
-			select {
-			// double check ctx
-			case <-c.ctx.Done():
-				logger.Infof("the kubernetes client stopped, resultChan len %d", len(wc.ResultChan()))
-				return
-
-				// get one element from result-chan
-			case event, ok := <-wc.ResultChan():
-				if !ok {
-					wc.Stop()
-					logger.Info("kubernetes watch chan die, create new")
-					goto onceWatch
-				}
-
-				if event.Type == watch.Error {
-					// watched a error event
-					logger.Warnf("kubernetes watch api report err (%#v)", event)
-					continue
-				}
-
-				o, ok := event.Object.(resourceVersionGetter)
-				if !ok {
-					logger.Warnf("kubernetes response object not a versioned object, its real type %T", event.Object)
-					continue
-				}
-
-				// record the last resource version avoid to sync all pod
-				c.lastResourceVersion = o.GetResourceVersion()
-				logger.Infof("kubernetes get the current resource version %v", c.lastResourceVersion)
-
-				// check event object type
-				p, ok := event.Object.(*v1.Pod)
-				if !ok {
-					logger.Warnf("kubernetes response object not a Pod, its real type %T", event.Object)
-					continue
-				}
-
-				logger.Debugf("kubernetes got pod %#v", p)
-				// handle the watched pod
-				go c.handleWatchedPodEvent(p, event.Type)
-			}
-		}
-	}
-}
-
-// handleWatchedPodEvent
-// handle watched pod event
-func (c *Client) handleWatchedPodEvent(p *v1.Pod, eventType watch.EventType) {
-
-	for ak, av := range p.GetAnnotations() {
-
-		// not dubbo interest annotation
-		if ak != DubboIOAnnotationKey {
-			continue
-		}
-
-		ol, err := c.unmarshalRecord(av)
-		if err != nil {
-			logger.Errorf("there a pod with dubbo annotation, but unmarshal dubbo value %v", err)
-			return
-		}
-
-		for _, o := range ol {
-
-			switch eventType {
-			case watch.Added:
-				// if pod is added, the record always be create
-				o.EventType = Create
-			case watch.Modified:
-				o.EventType = Update
-			case watch.Deleted:
-				o.EventType = Delete
-			default:
-				logger.Errorf("no valid kubernetes event-type (%s) ", eventType)
-				return
-			}
-
-			logger.Debugf("prepare to put object (%#v) to kubernetes-watcherSet", o)
-
-			if err := c.watcherSet.Put(o); err != nil {
-				logger.Errorf("put (%#v) to cache watcherSet: %v ", o, err)
-				return
-			}
-
-		}
-
-	}
-}
-
-// unmarshalRecord
-// unmarshal the kubernetes dubbo annotation value
-func (c *Client) unmarshalRecord(record string) ([]*WatcherEvent, error) {
-
-	if len(record) == 0 {
-		// []*WatcherEvent is nil.
-		return nil, nil
-	}
-
-	rawMsg, err := base64.URLEncoding.DecodeString(record)
-	if err != nil {
-		return nil, perrors.WithMessagef(err, "decode record (%s)", record)
-	}
-
-	var out []*WatcherEvent
-	if err := json.Unmarshal(rawMsg, &out); err != nil {
-		return nil, perrors.WithMessage(err, "decode json")
-	}
-	return out, nil
-}
-
-// marshalRecord
-// marshal the kubernetes dubbo annotation value
-func (c *Client) marshalRecord(ol []*WatcherEvent) (string, error) {
-
-	msg, err := json.Marshal(ol)
-	if err != nil {
-		return "", perrors.WithMessage(err, "json encode object list")
-	}
-	return base64.URLEncoding.EncodeToString(msg), nil
-}
-
-// readCurrentPod
-// read the current pod status from kubernetes api
-func (c *Client) readCurrentPod() (*v1.Pod, error) {
-
-	currentPod, err := c.rawClient.CoreV1().Pods(c.ns).Get(c.currentPodName, metav1.GetOptions{})
-	if err != nil {
-		return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.currentPodName, c.ns)
-	}
-	return currentPod, nil
-}
-
-// Create
-// create k/v pair in watcher-set
+// Create creates k/v pair in watcher-set
 func (c *Client) Create(k, v string) error {
 
 	// the read current pod must be lock, protect every
@@ -464,132 +82,18 @@
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
-	// 1. accord old pod && (k, v) assemble new pod dubbo annotion v
-	// 2. get patch data
-	// 3. PATCH the pod
-	currentPod, err := c.readCurrentPod()
-	if err != nil {
-		return perrors.WithMessage(err, "read current pod")
+	if err := c.controller.addAnnotationForCurrentPod(k, v); err != nil {
+		return perrors.WithMessagef(err, "add annotation @key = %s @value = %s", k, v)
 	}
 
-	oldPod, newPod, err := c.assembleDUBBOAnnotations(k, v, currentPod)
-	if err != nil {
-		return perrors.WithMessage(err, "assemble")
-	}
-
-	patchBytes, err := c.getPatch(oldPod, newPod)
-	if err != nil {
-		return perrors.WithMessage(err, "get patch")
-	}
-
-	updatedPod, err := c.patchCurrentPod(patchBytes)
-	if err != nil {
-		return perrors.WithMessage(err, "patch current pod")
-	}
-
-	c.currentPod = updatedPod
 	logger.Debugf("put the @key = %s @value = %s success", k, v)
-	// not update the watcherSet, the watcherSet should be write by the  watchPodsLoop
 	return nil
 }
 
-// patch current pod
-// write new meta for current pod
-func (c *Client) patchCurrentPod(patch []byte) (*v1.Pod, error) {
-
-	updatedPod, err := c.rawClient.CoreV1().Pods(c.ns).Patch(c.currentPodName, types.StrategicMergePatchType, patch)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "patch in kubernetes pod ")
-	}
-	return updatedPod, nil
-}
-
-// assemble the dubbo kubernetes label
-// every dubbo instance should be labeled spec {"dubbo.io/label":"dubbo.io/label-value"} label
-func (c *Client) assembleDUBBOLabel(currentPod *v1.Pod) (*v1.Pod, *v1.Pod, error) {
-
-	var (
-		oldPod = &v1.Pod{}
-		newPod = &v1.Pod{}
-	)
-
-	oldPod.Labels = make(map[string]string, 8)
-	newPod.Labels = make(map[string]string, 8)
-
-	if currentPod.GetLabels() != nil {
-
-		if currentPod.GetLabels()[DubboIOLabelKey] == DubboIOLabelValue {
-			// already have label
-			return nil, nil, ErrDubboLabelAlreadyExist
-		}
-	}
-
-	// copy current pod labels to oldPod && newPod
-	for k, v := range currentPod.GetLabels() {
-		oldPod.Labels[k] = v
-		newPod.Labels[k] = v
-	}
-	// assign new label for current pod
-	newPod.Labels[DubboIOLabelKey] = DubboIOLabelValue
-	return oldPod, newPod, nil
-}
-
-// assemble the dubbo kubernetes annotations
-// accord the current pod && (k,v) assemble the old-pod, new-pod
-func (c *Client) assembleDUBBOAnnotations(k, v string, currentPod *v1.Pod) (oldPod *v1.Pod, newPod *v1.Pod, err error) {
-
-	oldPod = &v1.Pod{}
-	newPod = &v1.Pod{}
-	oldPod.Annotations = make(map[string]string, 8)
-	newPod.Annotations = make(map[string]string, 8)
-
-	for k, v := range currentPod.GetAnnotations() {
-		oldPod.Annotations[k] = v
-		newPod.Annotations[k] = v
-	}
-
-	al, err := c.unmarshalRecord(oldPod.GetAnnotations()[DubboIOAnnotationKey])
-	if err != nil {
-		err = perrors.WithMessage(err, "unmarshal record")
-		return
-	}
-
-	newAnnotations, err := c.marshalRecord(append(al, &WatcherEvent{Key: k, Value: v}))
-	if err != nil {
-		err = perrors.WithMessage(err, "marshal record")
-		return
-	}
-
-	newPod.Annotations[DubboIOAnnotationKey] = newAnnotations
-	return
-}
-
-// getPatch
-// get the kubernetes pod patch bytes
-func (c *Client) getPatch(oldPod, newPod *v1.Pod) ([]byte, error) {
-
-	oldData, err := json.Marshal(oldPod)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "marshal old pod")
-	}
-
-	newData, err := json.Marshal(newPod)
-	if err != nil {
-		return nil, perrors.WithMessage(err, "marshal newPod pod")
-	}
-
-	patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{})
-	if err != nil {
-		return nil, perrors.WithMessage(err, "create two-way-merge-patch")
-	}
-	return patchBytes, nil
-}
-
-// GetChildren
-// get k children list from kubernetes-watcherSet
+// GetChildren gets k children list from kubernetes-watcherSet
 func (c *Client) GetChildren(k string) ([]string, []string, error) {
 
-	objectList, err := c.watcherSet.Get(k, true)
+	objectList, err := c.controller.watcherSet.Get(k, true)
 	if err != nil {
 		return nil, nil, perrors.WithMessagef(err, "get children from watcherSet on (%s)", k)
 	}
@@ -605,11 +109,10 @@
 	return kList, vList, nil
 }
 
-// Watch
-// watch on spec key
+// Watch watches on spec key
 func (c *Client) Watch(k string) (<-chan *WatcherEvent, <-chan struct{}, error) {
 
-	w, err := c.watcherSet.Watch(k, false)
+	w, err := c.controller.watcherSet.Watch(k, false)
 	if err != nil {
 		return nil, nil, perrors.WithMessagef(err, "watch on (%s)", k)
 	}
@@ -617,11 +120,10 @@
 	return w.ResultChan(), w.done(), nil
 }
 
-// Watch
-// watch on spec prefix
+// WatchWithPrefix watches on spec prefix
 func (c *Client) WatchWithPrefix(prefix string) (<-chan *WatcherEvent, <-chan struct{}, error) {
 
-	w, err := c.watcherSet.Watch(prefix, true)
+	w, err := c.controller.watcherSet.Watch(prefix, true)
 	if err != nil {
 		return nil, nil, perrors.WithMessagef(err, "watch on prefix (%s)", prefix)
 	}
@@ -629,9 +131,7 @@
 	return w.ResultChan(), w.done(), nil
 }
 
-// Valid
-// Valid the client
-// if return false, the client is die
+// if returns false, the client is die
 func (c *Client) Valid() bool {
 
 	select {
@@ -641,17 +141,15 @@
 	}
 	c.lock.RLock()
 	defer c.lock.RUnlock()
-	return c.rawClient != nil
+	return c.controller != nil
 }
 
-// Done
-// read the client status
+// nolint
 func (c *Client) Done() <-chan struct{} {
 	return c.ctx.Done()
 }
 
-// Stop
-// read the client status
+// nolint
 func (c *Client) Close() {
 
 	select {
@@ -665,28 +163,44 @@
 	// the client ctx be canceled
 	// will trigger the watcherSet watchers all stopped
 	// so, just wait
-	c.wg.Wait()
 }
 
-// ValidateClient
-// validate the kubernetes client
+// ValidateClient validates the kubernetes client
 func ValidateClient(container clientFacade) error {
 
 	client := container.Client()
 
 	// new Client
 	if client == nil || client.Valid() {
-		ns, err := getCurrentNameSpace()
+
+		newClient, err := newClient(container.GetUrl())
 		if err != nil {
-			return perrors.WithMessage(err, "get current namespace")
-		}
-		newClient, err := newClient(ns)
-		if err != nil {
-			logger.Warnf("new kubernetes client (namespace{%s}: %v)", ns, err)
-			return perrors.WithMessagef(err, "new kubernetes client (:%+v)", ns)
+			logger.Warnf("new kubernetes client: %v)", err)
+			return perrors.WithMessage(err, "new kubernetes client")
 		}
 		container.SetClient(newClient)
 	}
 
 	return nil
 }
+
+// NewMockClient exports for registry package test
+func NewMockClient(podList *v1.PodList) (*Client, error) {
+
+	ctx, cancel := context.WithCancel(context.Background())
+	controller, err := newDubboRegistryController(ctx, common.CONSUMER, func() (kubernetes.Interface, error) {
+		return fake.NewSimpleClientset(podList), nil
+	})
+	if err != nil {
+		return nil, perrors.WithMessage(err, "new dubbo-registry controller")
+	}
+
+	c := &Client{
+		ctx:        ctx,
+		cancel:     cancel,
+		controller: controller,
+	}
+
+	c.controller.startALLInformers()
+	return c, nil
+}
diff --git a/remoting/kubernetes/client_test.go b/remoting/kubernetes/client_test.go
index 342285b..d6c5a2e 100644
--- a/remoting/kubernetes/client_test.go
+++ b/remoting/kubernetes/client_test.go
@@ -20,20 +20,15 @@
 import (
 	"encoding/json"
 	"fmt"
-	"net/http"
+	_ "net/http/pprof"
 	"os"
-	"runtime"
 	"strings"
 	"sync"
 	"testing"
-	"time"
 )
 
 import (
-	"github.com/stretchr/testify/suite"
 	v1 "k8s.io/api/core/v1"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/fake"
 )
 
 // tests dataset
@@ -64,255 +59,236 @@
 // test dataset prefix
 const prefix = "name"
 
-var clientPodJsonData = `{
+var (
+	watcherStopLog = "the watcherSet watcher was stopped"
+)
+var clientPodListJsonData = `{
     "apiVersion": "v1",
-    "kind": "Pod",
-    "metadata": {
-        "annotations": {
-            "dubbo.io/annotation": "W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzL2NvbnN1bWVyJTNBJTJGJTJGMTcyLjE3LjAuOCUyRlVzZXJQcm92aWRlciUzRmNhdGVnb3J5JTNEY29uc3VtZXJzJTI2ZHViYm8lM0RkdWJib2dvLWNvbnN1bWVyLTIuNi4wJTI2cHJvdG9jb2wlM0RkdWJibyIsInYiOiIifV0="
-        },
-        "creationTimestamp": "2020-03-13T03:38:57Z",
-        "labels": {
-            "dubbo.io/label": "dubbo.io-value"
-        },
-        "name": "client",
-        "namespace": "default",
-        "resourceVersion": "2449700",
-        "selfLink": "/api/v1/namespaces/default/pods/client",
-        "uid": "3ec394f5-dcc6-49c3-8061-57b4b2b41344"
-    },
-    "spec": {
-        "containers": [
-            {
-                "env": [
+    "items": [
+        {
+            "apiVersion": "v1",
+            "kind": "Pod",
+            "metadata": {
+                "annotations": {
+                    "dubbo.io/annotation": "W3siayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNiUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YXV0aCUzRCUyNmJlYW4ubmFtZSUzRFVzZXJQcm92aWRlciUyNmNsdXN0ZXIlM0RmYWlsb3ZlciUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC42JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBhcmFtLnNpZ24lM0QlMjZwaWQlM0Q2JTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZWxlYXNlJTNEZHViYm8tZ29sYW5nLTEuMy4wJTI2cmV0cmllcyUzRCUyNnNlcnZpY2UuZmlsdGVyJTNEZWNobyUyNTJDdG9rZW4lMjUyQ2FjY2Vzc2xvZyUyNTJDdHBzJTI1MkNnZW5lcmljX3NlcnZpY2UlMjUyQ2V4ZWN1dGUlMjUyQ3BzaHV0ZG93biUyNnNpZGUlM0Rwcm92aWRlciUyNnRpbWVzdGFtcCUzRDE1OTExNTYxNTUlMjZ0cHMubGltaXQuaW50ZXJ2YWwlM0QlMjZ0cHMubGltaXQucmF0ZSUzRCUyNnRwcy5saW1pdC5yZWplY3RlZC5oYW5kbGVyJTNEJTI2dHBzLmxpbWl0LnN0cmF0ZWd5JTNEJTI2dHBzLmxpbWl0ZXIlM0QlMjZ2ZXJzaW9uJTNEJTI2d2FybXVwJTNEMTAwIiwidiI6IiJ9XQ=="
+                },
+                "creationTimestamp": "2020-06-03T03:49:14Z",
+                "generateName": "server-84c864f5bc-",
+                "labels": {
+                    "dubbo.io/label": "dubbo.io-value",
+                    "pod-template-hash": "84c864f5bc",
+                    "role": "server"
+                },
+                "name": "server-84c864f5bc-r8qvz",
+                "namespace": "default",
+                "ownerReferences": [
                     {
-                        "name": "NAMESPACE",
-                        "valueFrom": {
-                            "fieldRef": {
-                                "apiVersion": "v1",
-                                "fieldPath": "metadata.namespace"
+                        "apiVersion": "apps/v1",
+                        "blockOwnerDeletion": true,
+                        "controller": true,
+                        "kind": "ReplicaSet",
+                        "name": "server-84c864f5bc",
+                        "uid": "fa376dbb-4f37-4705-8e80-727f592c19b3"
+                    }
+                ],
+                "resourceVersion": "517460",
+                "selfLink": "/api/v1/namespaces/default/pods/server-84c864f5bc-r8qvz",
+                "uid": "f4fc811c-200c-4445-8d4f-532144957dcc"
+            },
+            "spec": {
+                "containers": [
+                    {
+                        "env": [
+                            {
+                                "name": "DUBBO_NAMESPACE",
+                                "value": "default"
+                            },
+                            {
+                                "name": "NAMESPACE",
+                                "valueFrom": {
+                                    "fieldRef": {
+                                        "apiVersion": "v1",
+                                        "fieldPath": "metadata.namespace"
+                                    }
+                                }
+                            }
+                        ],
+                        "image": "192.168.240.101:5000/scott/go-server",
+                        "imagePullPolicy": "Always",
+                        "name": "server",
+                        "resources": {},
+                        "terminationMessagePath": "/dev/termination-log",
+                        "terminationMessagePolicy": "File",
+                        "volumeMounts": [
+                            {
+                                "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+                                "name": "dubbo-sa-token-5qbtb",
+                                "readOnly": true
+                            }
+                        ]
+                    }
+                ],
+                "dnsPolicy": "ClusterFirst",
+                "enableServiceLinks": true,
+                "nodeName": "minikube",
+                "priority": 0,
+                "restartPolicy": "Always",
+                "schedulerName": "default-scheduler",
+                "securityContext": {},
+                "serviceAccount": "dubbo-sa",
+                "serviceAccountName": "dubbo-sa",
+                "terminationGracePeriodSeconds": 30,
+                "tolerations": [
+                    {
+                        "effect": "NoExecute",
+                        "key": "node.kubernetes.io/not-ready",
+                        "operator": "Exists",
+                        "tolerationSeconds": 300
+                    },
+                    {
+                        "effect": "NoExecute",
+                        "key": "node.kubernetes.io/unreachable",
+                        "operator": "Exists",
+                        "tolerationSeconds": 300
+                    }
+                ],
+                "volumes": [
+                    {
+                        "name": "dubbo-sa-token-5qbtb",
+                        "secret": {
+                            "defaultMode": 420,
+                            "secretName": "dubbo-sa-token-5qbtb"
+                        }
+                    }
+                ]
+            },
+            "status": {
+                "conditions": [
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:14Z",
+                        "status": "True",
+                        "type": "Initialized"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:15Z",
+                        "status": "True",
+                        "type": "Ready"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:15Z",
+                        "status": "True",
+                        "type": "ContainersReady"
+                    },
+                    {
+                        "lastProbeTime": null,
+                        "lastTransitionTime": "2020-06-03T03:49:14Z",
+                        "status": "True",
+                        "type": "PodScheduled"
+                    }
+                ],
+                "containerStatuses": [
+                    {
+                        "containerID": "docker://b6421e05ce44f8a1c4fa6b72274980777c7c0f945516209f7c0558cd0cd65406",
+                        "image": "192.168.240.101:5000/scott/go-server:latest",
+                        "imageID": "docker-pullable://192.168.240.101:5000/scott/go-server@sha256:4eecf895054f0ff93d80db64992a561d10504e55582def6dcb6093a6d6d92461",
+                        "lastState": {},
+                        "name": "server",
+                        "ready": true,
+                        "restartCount": 0,
+                        "started": true,
+                        "state": {
+                            "running": {
+                                "startedAt": "2020-06-03T03:49:15Z"
                             }
                         }
                     }
                 ],
-                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client",
-                "imagePullPolicy": "Always",
-                "name": "client",
-                "resources": {},
-                "terminationMessagePath": "/dev/termination-log",
-                "terminationMessagePolicy": "File",
-                "volumeMounts": [
+                "hostIP": "10.0.2.15",
+                "phase": "Running",
+                "podIP": "172.17.0.6",
+                "podIPs": [
                     {
-                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
-                        "name": "dubbo-sa-token-l2lzh",
-                        "readOnly": true
+                        "ip": "172.17.0.6"
                     }
-                ]
+                ],
+                "qosClass": "BestEffort",
+                "startTime": "2020-06-03T03:49:14Z"
             }
-        ],
-        "dnsPolicy": "ClusterFirst",
-        "enableServiceLinks": true,
-        "nodeName": "minikube",
-        "priority": 0,
-        "restartPolicy": "Never",
-        "schedulerName": "default-scheduler",
-        "securityContext": {},
-        "serviceAccount": "dubbo-sa",
-        "serviceAccountName": "dubbo-sa",
-        "terminationGracePeriodSeconds": 30,
-        "tolerations": [
-            {
-                "effect": "NoExecute",
-                "key": "node.kubernetes.io/not-ready",
-                "operator": "Exists",
-                "tolerationSeconds": 300
-            },
-            {
-                "effect": "NoExecute",
-                "key": "node.kubernetes.io/unreachable",
-                "operator": "Exists",
-                "tolerationSeconds": 300
-            }
-        ],
-        "volumes": [
-            {
-                "name": "dubbo-sa-token-l2lzh",
-                "secret": {
-                    "defaultMode": 420,
-                    "secretName": "dubbo-sa-token-l2lzh"
-                }
-            }
-        ]
-    },
-    "status": {
-        "conditions": [
-            {
-                "lastProbeTime": null,
-                "lastTransitionTime": "2020-03-13T03:38:57Z",
-                "status": "True",
-                "type": "Initialized"
-            },
-            {
-                "lastProbeTime": null,
-                "lastTransitionTime": "2020-03-13T03:40:18Z",
-                "status": "True",
-                "type": "Ready"
-            },
-            {
-                "lastProbeTime": null,
-                "lastTransitionTime": "2020-03-13T03:40:18Z",
-                "status": "True",
-                "type": "ContainersReady"
-            },
-            {
-                "lastProbeTime": null,
-                "lastTransitionTime": "2020-03-13T03:38:57Z",
-                "status": "True",
-                "type": "PodScheduled"
-            }
-        ],
-        "containerStatuses": [
-            {
-                "containerID": "docker://2870d6abc19ca7fe22ca635ebcfac5d48c6d5550a659bafd74fb48104f6dfe3c",
-                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client:latest",
-                "imageID": "docker-pullable://registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client@sha256:1f075131f708a0d400339e81549d7c4d4ed917ab0b6bd38ef458dd06ad25a559",
-                "lastState": {},
-                "name": "client",
-                "ready": true,
-                "restartCount": 0,
-                "state": {
-                    "running": {
-                        "startedAt": "2020-03-13T03:40:17Z"
-                    }
-                }
-            }
-        ],
-        "hostIP": "10.0.2.15",
-        "phase": "Running",
-        "podIP": "172.17.0.8",
-        "qosClass": "BestEffort",
-        "startTime": "2020-03-13T03:38:57Z"
+        }
+    ],
+    "kind": "List",
+    "metadata": {
+        "resourceVersion": "",
+        "selfLink": ""
     }
 }
 `
 
-type KubernetesClientTestSuite struct {
-	suite.Suite
+func getTestClient(t *testing.T) *Client {
 
-	currentPod v1.Pod
-}
+	pl := &v1.PodList{}
+	// 1. install test data
+	if err := json.Unmarshal([]byte(clientPodListJsonData), &pl); err != nil {
+		t.Fatal(err)
+	}
+	currentPod := pl.Items[0]
 
-func (s *KubernetesClientTestSuite) initClient() *Client {
+	env := map[string]string{
+		nameSpaceKey:            currentPod.GetNamespace(),
+		podNameKey:              currentPod.GetName(),
+		needWatchedNameSpaceKey: "default",
+	}
 
-	t := s.T()
-
-	client, err := newMockClient(s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) {
-
-		out := fake.NewSimpleClientset()
-
-		// mock current pod
-		if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil {
+	for k, v := range env {
+		if err := os.Setenv(k, v); err != nil {
 			t.Fatal(err)
 		}
-		return out, nil
-	})
+	}
+
+	client, err := NewMockClient(pl)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	time.Sleep(time.Second)
 	return client
 }
 
-func (s *KubernetesClientTestSuite) SetupSuite() {
+func TestClientValid(t *testing.T) {
 
-	runtime.GOMAXPROCS(1)
-
-	t := s.T()
-
-	// 1. install test data
-	if err := json.Unmarshal([]byte(clientPodJsonData), &s.currentPod); err != nil {
-		t.Fatal(err)
-	}
-
-	// 2. set downward-api inject env
-	if err := os.Setenv(podNameKey, s.currentPod.GetName()); err != nil {
-		t.Fatal(err)
-	}
-	if err := os.Setenv(nameSpaceKey, s.currentPod.GetNamespace()); err != nil {
-		t.Fatal(err)
-	}
-
-	go http.ListenAndServe(":6061", nil)
-
-}
-
-func (s *KubernetesClientTestSuite) TestReadCurrentPodName() {
-	t := s.T()
-
-	n, err := getCurrentPodName()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if n != s.currentPod.GetName() {
-		t.Fatalf("expect %s but got %s", s.currentPod.GetName(), n)
-	}
-
-}
-func (s *KubernetesClientTestSuite) TestReadCurrentNameSpace() {
-	t := s.T()
-
-	ns, err := getCurrentNameSpace()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if ns != s.currentPod.GetNamespace() {
-		t.Fatalf("expect %s but got %s", s.currentPod.GetNamespace(), ns)
-	}
-
-}
-func (s *KubernetesClientTestSuite) TestClientValid() {
-
-	t := s.T()
-
-	client := s.initClient()
+	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")
 	}
 }
 
-func (s *KubernetesClientTestSuite) TestClientDone() {
+func TestClientDone(t *testing.T) {
 
-	t := s.T()
-
-	client := s.initClient()
+	client := getTestClient(t)
 
 	go func() {
-		time.Sleep(time.Second)
 		client.Close()
 	}()
 
 	<-client.Done()
 
-	if client.Valid() == true {
-		t.Fatal("client should be invalid then")
+	if client.Valid() {
+		t.Fatal("client should be invalid")
 	}
 }
 
-func (s *KubernetesClientTestSuite) TestClientCreateKV() {
+func TestClientCreateKV(t *testing.T) {
 
-	t := s.T()
-
-	client := s.initClient()
+	client := getTestClient(t)
 	defer client.Close()
 
 	for _, tc := range tests {
@@ -327,11 +303,9 @@
 	}
 }
 
-func (s *KubernetesClientTestSuite) TestClientGetChildrenKVList() {
+func TestClientGetChildrenKVList(t *testing.T) {
 
-	t := s.T()
-
-	client := s.initClient()
+	client := getTestClient(t)
 	defer client.Close()
 
 	wg := sync.WaitGroup{}
@@ -360,7 +334,7 @@
 					return
 				}
 			case <-done:
-				t.Log("the watcherSet watcher was stopped")
+				t.Log(watcherStopLog)
 				return
 			}
 		}
@@ -407,11 +381,9 @@
 
 }
 
-func (s *KubernetesClientTestSuite) TestClientWatchPrefix() {
+func TestClientWatchPrefix(t *testing.T) {
 
-	t := s.T()
-
-	client := s.initClient()
+	client := getTestClient(t)
 
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -430,7 +402,7 @@
 			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
 			}
 		}
@@ -452,22 +424,9 @@
 	client.Close()
 }
 
-func (s *KubernetesClientTestSuite) TestNewClient() {
+func TestClientWatch(t *testing.T) {
 
-	t := s.T()
-
-	_, err := newClient(s.currentPod.GetNamespace())
-	if err == nil {
-		t.Fatal("the out of cluster test should fail")
-	}
-
-}
-
-func (s *KubernetesClientTestSuite) TestClientWatch() {
-
-	t := s.T()
-
-	client := s.initClient()
+	client := getTestClient(t)
 
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -485,7 +444,7 @@
 			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
 			}
 		}
@@ -507,7 +466,3 @@
 
 	client.Close()
 }
-
-func TestKubernetesClient(t *testing.T) {
-	suite.Run(t, new(KubernetesClientTestSuite))
-}
diff --git a/remoting/kubernetes/facade.go b/remoting/kubernetes/facade.go
index dd15c91..dc060bb 100644
--- a/remoting/kubernetes/facade.go
+++ b/remoting/kubernetes/facade.go
@@ -17,7 +17,10 @@
 
 package kubernetes
 
+import "github.com/apache/dubbo-go/common"
+
 type clientFacade interface {
 	Client() *Client
 	SetClient(*Client)
+	common.Node
 }
diff --git a/remoting/kubernetes/facade_test.go b/remoting/kubernetes/facade_test.go
index 024264f..65c5d71 100644
--- a/remoting/kubernetes/facade_test.go
+++ b/remoting/kubernetes/facade_test.go
@@ -18,14 +18,18 @@
 package kubernetes
 
 import (
+	"strconv"
 	"sync"
+	"testing"
 )
+
 import (
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/fake"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
 )
 
 type mockFacade struct {
+	*common.URL
 	client  *Client
 	cltLock sync.Mutex
 	done    chan struct{}
@@ -39,25 +43,32 @@
 	r.client = client
 }
 
-func (s *KubernetesClientTestSuite) Test_Facade() {
+func (r *mockFacade) GetUrl() common.URL {
+	return *r.URL
+}
 
-	t := s.T()
+func (r *mockFacade) Destroy() {
+	// TODO implementation me
+}
 
-	mockClient, err := newMockClient(s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) {
+func (r *mockFacade) RestartCallBack() bool {
+	return true
+}
 
-		out := fake.NewSimpleClientset()
+func (r *mockFacade) IsAvailable() bool {
+	return true
+}
+func Test_Facade(t *testing.T) {
 
-		// mock current pod
-		if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil {
-			t.Fatal(err)
-		}
-		return out, nil
-	})
+	regUrl, err := common.NewURL("registry://127.0.0.1:443",
+		common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)))
 	if err != nil {
 		t.Fatal(err)
 	}
 
+	mockClient := getTestClient(t)
 	m := &mockFacade{
+		URL:    &regUrl,
 		client: mockClient,
 	}
 
diff --git a/remoting/kubernetes/listener.go b/remoting/kubernetes/listener.go
index 4c198c6..a737f4e 100644
--- a/remoting/kubernetes/listener.go
+++ b/remoting/kubernetes/listener.go
@@ -45,8 +45,8 @@
 }
 
 // Listen on a spec key
-// this method will return true when spec key deleted,
-// this method will return false when deep layer connection lose
+// this method returns true when spec key deleted,
+// this method returns false when deep layer connection lose
 func (l *EventListener) ListenServiceNodeEvent(key string, listener ...remoting.DataListener) bool {
 	defer l.wg.Done()
 	for {
@@ -81,12 +81,10 @@
 			}
 		}
 	}
-
-	return false
 }
 
-// return true mean the event type is DELETE
-// return false mean the event type is CREATE || UPDATE
+// return true means the event type is DELETE
+// return false means the event type is CREATE || UPDATE
 func (l *EventListener) handleEvents(event *WatcherEvent, listeners ...remoting.DataListener) bool {
 
 	logger.Infof("got a kubernetes-watcherSet event {type: %d, key: %s}", event.EventType, event.Key)
diff --git a/remoting/kubernetes/listener_test.go b/remoting/kubernetes/listener_test.go
index a944678..1f39848 100644
--- a/remoting/kubernetes/listener_test.go
+++ b/remoting/kubernetes/listener_test.go
@@ -18,7 +18,7 @@
 package kubernetes
 
 import (
-	"time"
+	"testing"
 )
 
 import (
@@ -67,9 +67,7 @@
 	return true
 }
 
-func (s *KubernetesClientTestSuite) TestListener() {
-
-	t := s.T()
+func TestListener(t *testing.T) {
 
 	var tests = []struct {
 		input struct {
@@ -83,15 +81,13 @@
 		}{k: "/dubbo", v: changedData}},
 	}
 
-	c := s.initClient()
+	c := getTestClient(t)
 	defer c.Close()
 
 	listener := NewEventListener(c)
 	dataListener := &mockDataListener{client: c, changedData: changedData, rc: make(chan remoting.Event)}
 	listener.ListenServiceEvent("/dubbo", dataListener)
 
-	// NOTICE:  direct listen will lose create msg
-	time.Sleep(time.Second)
 	for _, tc := range tests {
 
 		k := tc.input.k
diff --git a/remoting/kubernetes/registry_controller.go b/remoting/kubernetes/registry_controller.go
new file mode 100644
index 0000000..f93a00a
--- /dev/null
+++ b/remoting/kubernetes/registry_controller.go
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kubernetes
+
+import (
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/selection"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/strategicpatch"
+	"k8s.io/apimachinery/pkg/watch"
+	"k8s.io/client-go/informers"
+	informerscorev1 "k8s.io/client-go/informers/core/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/util/workqueue"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/logger"
+)
+
+const (
+	// kubernetes inject env var
+	podNameKey              = "HOSTNAME"
+	nameSpaceKey            = "NAMESPACE"
+	needWatchedNameSpaceKey = "DUBBO_NAMESPACE"
+
+	// all pod annotation key
+	DubboIOAnnotationKey = "dubbo.io/annotation"
+	// all pod label key and value pair
+	DubboIOLabelKey           = "dubbo.io/label"
+	DubboIOConsumerLabelValue = "dubbo.io.consumer"
+	DubboIOProviderLabelValue = "dubbo.io.provider"
+
+	// kubernetes suggest resync
+	defaultResync = 5 * time.Minute
+)
+
+var (
+	ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist")
+)
+
+// dubboRegistryController works like a kubernetes controller
+type dubboRegistryController struct {
+
+	// clone from client
+	// manage lifecycle
+	ctx context.Context
+
+	role common.RoleType
+
+	// protect patch current pod operation
+	lock sync.Mutex
+
+	// current pod config
+	needWatchedNamespace map[string]struct{}
+	namespace            string
+	name                 string
+
+	watcherSet WatcherSet
+
+	// kubernetes
+	kc                               kubernetes.Interface
+	listAndWatchStartResourceVersion uint64
+	namespacedInformerFactory        map[string]informers.SharedInformerFactory
+	namespacedPodInformers           map[string]informerscorev1.PodInformer
+	queue                            workqueue.Interface //shared by namespaced informers
+}
+
+func newDubboRegistryController(
+	ctx context.Context,
+	// different provider and consumer have behavior
+	roleType common.RoleType,
+	// used to inject mock kubernetes client
+	kcGetter func() (kubernetes.Interface, error),
+) (*dubboRegistryController, error) {
+
+	kc, err := kcGetter()
+	if err != nil {
+		return nil, perrors.WithMessage(err, "get kubernetes client")
+	}
+
+	c := &dubboRegistryController{
+		ctx:                       ctx,
+		role:                      roleType,
+		watcherSet:                newWatcherSet(ctx),
+		needWatchedNamespace:      make(map[string]struct{}),
+		namespacedInformerFactory: make(map[string]informers.SharedInformerFactory),
+		namespacedPodInformers:    make(map[string]informerscorev1.PodInformer),
+		kc:                        kc,
+	}
+
+	if err := c.readConfig(); err != nil {
+		return nil, perrors.WithMessage(err, "read config")
+	}
+
+	if err := c.initCurrentPod(); err != nil {
+		return nil, perrors.WithMessage(err, "init current pod")
+	}
+
+	if err := c.initWatchSet(); err != nil {
+		return nil, perrors.WithMessage(err, "init watch set")
+	}
+
+	if err := c.initPodInformer(); err != nil {
+		return nil, perrors.WithMessage(err, "init pod informer")
+	}
+
+	go c.run()
+
+	return c, nil
+}
+
+// GetInClusterKubernetesClient
+// current pod running in kubernetes-cluster
+func GetInClusterKubernetesClient() (kubernetes.Interface, error) {
+
+	// read in-cluster config
+	cfg, err := rest.InClusterConfig()
+	if err != nil {
+		return nil, perrors.WithMessage(err, "get in-cluster config")
+	}
+
+	return kubernetes.NewForConfig(cfg)
+}
+
+// initWatchSet
+// 1. get all with dubbo label pods
+// 2. put every element to watcherSet
+// 3. refresh watch book-mark
+func (c *dubboRegistryController) initWatchSet() error {
+
+	req, err := labels.NewRequirement(DubboIOLabelKey, selection.In, []string{DubboIOConsumerLabelValue, DubboIOProviderLabelValue})
+	if err != nil {
+		return perrors.WithMessage(err, "new requirement")
+	}
+
+	for ns := range c.needWatchedNamespace {
+		pods, err := c.kc.CoreV1().Pods(ns).List(metav1.ListOptions{
+			LabelSelector: req.String(),
+		})
+		if err != nil {
+			return perrors.WithMessagef(err, "list pods in namespace (%s)", ns)
+		}
+		for _, p := range pods.Items {
+			// set resource version
+			rv, err := strconv.ParseUint(p.GetResourceVersion(), 10, 0)
+			if err != nil {
+				return perrors.WithMessagef(err, "parse resource version %s", p.GetResourceVersion())
+			}
+			if c.listAndWatchStartResourceVersion < rv {
+				c.listAndWatchStartResourceVersion = rv
+			}
+			c.handleWatchedPodEvent(&p, watch.Added)
+		}
+	}
+	return nil
+}
+
+// read dubbo-registry controller config
+// 1. current pod name
+// 2. current pod working namespace
+func (c *dubboRegistryController) readConfig() error {
+
+	// read current pod name && namespace
+	c.name = os.Getenv(podNameKey)
+	if len(c.name) == 0 {
+		return perrors.New("read value from env by key (HOSTNAME)")
+	}
+	c.namespace = os.Getenv(nameSpaceKey)
+	if len(c.namespace) == 0 {
+		return perrors.New("read value from env by key (NAMESPACE)")
+	}
+	return nil
+}
+
+func (c *dubboRegistryController) initNamespacedPodInformer(ns string) error {
+
+	req, err := labels.NewRequirement(DubboIOLabelKey, selection.In, []string{DubboIOConsumerLabelValue, DubboIOProviderLabelValue})
+	if err != nil {
+		return perrors.WithMessage(err, "new requirement")
+	}
+
+	informersFactory := informers.NewSharedInformerFactoryWithOptions(
+		c.kc,
+		defaultResync,
+		informers.WithNamespace(ns),
+		informers.WithTweakListOptions(func(options *metav1.ListOptions) {
+			options.LabelSelector = req.String()
+			options.ResourceVersion = strconv.FormatUint(c.listAndWatchStartResourceVersion, 10)
+		}),
+	)
+	podInformer := informersFactory.Core().V1().Pods()
+
+	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc:    c.addPod,
+		UpdateFunc: c.updatePod,
+		DeleteFunc: c.deletePod,
+	})
+
+	c.namespacedInformerFactory[ns] = informersFactory
+	c.namespacedPodInformers[ns] = podInformer
+
+	return nil
+}
+
+func (c *dubboRegistryController) initPodInformer() error {
+
+	if c.role == common.PROVIDER {
+		return nil
+	}
+
+	// read need watched namespaces list
+	needWatchedNameSpaceList := os.Getenv(needWatchedNameSpaceKey)
+	if len(needWatchedNameSpaceList) == 0 {
+		return perrors.New("read value from env by key (DUBBO_NAMESPACE)")
+	}
+	for _, ns := range strings.Split(needWatchedNameSpaceList, ",") {
+		c.needWatchedNamespace[ns] = struct{}{}
+	}
+	// current work namespace should be watched
+	c.needWatchedNamespace[c.namespace] = struct{}{}
+
+	c.queue = workqueue.New()
+
+	// init all watch needed pod-informer
+	for watchedNS := range c.needWatchedNamespace {
+		if err := c.initNamespacedPodInformer(watchedNS); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type kubernetesEvent struct {
+	p *v1.Pod
+	t watch.EventType
+}
+
+func (c *dubboRegistryController) addPod(obj interface{}) {
+	p, ok := obj.(*v1.Pod)
+	if !ok {
+		logger.Warnf("pod-informer got object %T not *v1.Pod", obj)
+		return
+	}
+	c.queue.Add(&kubernetesEvent{
+		t: watch.Added,
+		p: p,
+	})
+}
+
+func (c *dubboRegistryController) updatePod(oldObj, newObj interface{}) {
+	op, ok := oldObj.(*v1.Pod)
+	if !ok {
+		logger.Warnf("pod-informer got object %T not *v1.Pod", oldObj)
+		return
+	}
+	np, ok := newObj.(*v1.Pod)
+	if !ok {
+		logger.Warnf("pod-informer got object %T not *v1.Pod", newObj)
+		return
+	}
+	if op.GetResourceVersion() == np.GetResourceVersion() {
+		return
+	}
+	c.queue.Add(&kubernetesEvent{
+		p: np,
+		t: watch.Modified,
+	})
+}
+
+func (c *dubboRegistryController) deletePod(obj interface{}) {
+	p, ok := obj.(*v1.Pod)
+	if !ok {
+		logger.Warnf("pod-informer got object %T not *v1.Pod", obj)
+		return
+	}
+	c.queue.Add(&kubernetesEvent{
+		p: p,
+		t: watch.Deleted,
+	})
+}
+
+func (c *dubboRegistryController) startALLInformers() {
+	logger.Debugf("starting namespaced informer-factory")
+	for _, factory := range c.namespacedInformerFactory {
+		go factory.Start(c.ctx.Done())
+	}
+}
+
+// run
+// controller process every event in work-queue
+func (c *dubboRegistryController) run() {
+
+	if c.role == common.PROVIDER {
+		return
+	}
+
+	defer logger.Warn("dubbo registry controller work stopped")
+	defer c.queue.ShutDown()
+
+	for ns, podInformer := range c.namespacedPodInformers {
+		if !cache.WaitForCacheSync(c.ctx.Done(), podInformer.Informer().HasSynced) {
+			logger.Errorf("wait for cache sync finish @namespace %s fail", ns)
+			return
+		}
+	}
+
+	logger.Infof("kubernetes registry-controller running @Namespace = %q @PodName = %q", c.namespace, c.name)
+
+	// start work
+	go c.work()
+	// block wait context cancel
+	<-c.ctx.Done()
+}
+
+func (c *dubboRegistryController) work() {
+	for c.processNextWorkItem() {
+	}
+}
+
+// processNextWorkItem process work-queue elements
+func (c *dubboRegistryController) processNextWorkItem() bool {
+	item, shutdown := c.queue.Get()
+	if shutdown {
+		return false
+	}
+	defer c.queue.Done(item)
+	o := item.(*kubernetesEvent)
+	c.handleWatchedPodEvent(o.p, o.t)
+	return true
+}
+
+// handleWatchedPodEvent handles watched pod event
+func (c *dubboRegistryController) handleWatchedPodEvent(p *v1.Pod, eventType watch.EventType) {
+	logger.Debugf("get @type = %s event from @pod = %s", eventType, p.GetName())
+
+	for ak, av := range p.GetAnnotations() {
+		// not dubbo interest annotation
+		if ak != DubboIOAnnotationKey {
+			continue
+		}
+		ol, err := c.unmarshalRecord(av)
+		if err != nil {
+			logger.Errorf("there a pod with dubbo annotation, but unmarshal dubbo value %v", err)
+			return
+		}
+		for _, o := range ol {
+			switch eventType {
+			case watch.Added:
+				// if pod is added, the record always be create
+				o.EventType = Create
+			case watch.Modified:
+				o.EventType = Update
+			case watch.Deleted:
+				o.EventType = Delete
+			default:
+				logger.Errorf("no valid kubernetes event-type (%s) ", eventType)
+				return
+			}
+
+			logger.Debugf("putting @key=%s @value=%s to watcherSet", o.Key, o.Value)
+			if err := c.watcherSet.Put(o); err != nil {
+				logger.Errorf("put (%#v) to cache watcherSet: %v ", o, err)
+				return
+			}
+		}
+	}
+}
+
+// unmarshalRecord unmarshals the kubernetes dubbo annotation value
+func (c *dubboRegistryController) unmarshalRecord(record string) ([]*WatcherEvent, error) {
+
+	if len(record) == 0 {
+		// []*WatcherEvent is nil.
+		return nil, nil
+	}
+
+	rawMsg, err := base64.URLEncoding.DecodeString(record)
+	if err != nil {
+		return nil, perrors.WithMessagef(err, "decode record (%s)", record)
+	}
+
+	var out []*WatcherEvent
+	if err := json.Unmarshal(rawMsg, &out); err != nil {
+		return nil, perrors.WithMessage(err, "decode json")
+	}
+	return out, nil
+}
+
+// initCurrentPod
+// 1. get current pod
+// 2. give the dubbo-label for this pod
+func (c *dubboRegistryController) initCurrentPod() error {
+	currentPod, err := c.readCurrentPod()
+	if err != nil {
+		return perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.name, c.namespace)
+	}
+
+	oldPod, newPod, err := c.assembleDUBBOLabel(currentPod)
+	if err != nil {
+		if err == ErrDubboLabelAlreadyExist {
+			return nil
+		}
+		return perrors.WithMessage(err, "assemble dubbo label")
+	}
+	// current pod don't have label
+	p, err := c.getPatch(oldPod, newPod)
+	if err != nil {
+		return perrors.WithMessage(err, "get patch")
+	}
+
+	currentPod, err = c.patchCurrentPod(p)
+	if err != nil {
+		return perrors.WithMessage(err, "patch to current pod")
+	}
+
+	return nil
+}
+
+// patchCurrentPod writes new meta for current pod
+func (c *dubboRegistryController) patchCurrentPod(patch []byte) (*v1.Pod, error) {
+	updatedPod, err := c.kc.CoreV1().Pods(c.namespace).Patch(c.name, types.StrategicMergePatchType, patch)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "patch in kubernetes pod ")
+	}
+	return updatedPod, nil
+}
+
+// assembleDUBBOLabel assembles the dubbo kubernetes label
+// every dubbo instance should be labeled spec {"dubbo.io/label":"dubbo.io/label-value"} label
+func (c *dubboRegistryController) assembleDUBBOLabel(p *v1.Pod) (*v1.Pod, *v1.Pod, error) {
+	var (
+		oldPod = &v1.Pod{}
+		newPod = &v1.Pod{}
+	)
+	oldPod.Labels = make(map[string]string, 8)
+	newPod.Labels = make(map[string]string, 8)
+
+	if p.GetLabels() != nil {
+		if _, ok := p.GetLabels()[DubboIOLabelKey]; ok {
+			// already have label
+			return nil, nil, ErrDubboLabelAlreadyExist
+		}
+	}
+
+	// copy current pod labels to oldPod && newPod
+	for k, v := range p.GetLabels() {
+		oldPod.Labels[k] = v
+		newPod.Labels[k] = v
+	}
+
+	// assign new label for current pod
+	switch c.role {
+	case common.CONSUMER:
+		newPod.Labels[DubboIOLabelKey] = DubboIOConsumerLabelValue
+	case common.PROVIDER:
+		newPod.Labels[DubboIOLabelKey] = DubboIOProviderLabelValue
+	default:
+		return nil, nil, perrors.New(fmt.Sprintf("unknown role %s", c.role))
+	}
+	return oldPod, newPod, nil
+}
+
+// assembleDUBBOAnnotations assembles the dubbo kubernetes annotations
+// accord the current pod && (k,v) assemble the old-pod, new-pod
+func (c *dubboRegistryController) assembleDUBBOAnnotations(k, v string, currentPod *v1.Pod) (oldPod *v1.Pod, newPod *v1.Pod, err error) {
+
+	oldPod = &v1.Pod{}
+	newPod = &v1.Pod{}
+	oldPod.Annotations = make(map[string]string, 8)
+	newPod.Annotations = make(map[string]string, 8)
+
+	for k, v := range currentPod.GetAnnotations() {
+		oldPod.Annotations[k] = v
+		newPod.Annotations[k] = v
+	}
+
+	al, err := c.unmarshalRecord(oldPod.GetAnnotations()[DubboIOAnnotationKey])
+	if err != nil {
+		err = perrors.WithMessage(err, "unmarshal record")
+		return
+	}
+
+	newAnnotations, err := c.marshalRecord(append(al, &WatcherEvent{Key: k, Value: v}))
+	if err != nil {
+		err = perrors.WithMessage(err, "marshal record")
+		return
+	}
+
+	newPod.Annotations[DubboIOAnnotationKey] = newAnnotations
+	return
+}
+
+// getPatch gets the kubernetes pod patch bytes
+func (c *dubboRegistryController) getPatch(oldPod, newPod *v1.Pod) ([]byte, error) {
+	oldData, err := json.Marshal(oldPod)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "marshal old pod")
+	}
+
+	newData, err := json.Marshal(newPod)
+	if err != nil {
+		return nil, perrors.WithMessage(err, "marshal newPod pod")
+	}
+
+	patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{})
+	if err != nil {
+		return nil, perrors.WithMessage(err, "create two-way-merge-patch")
+	}
+	return patchBytes, nil
+}
+
+// marshalRecord marshals the kubernetes dubbo annotation value
+func (c *dubboRegistryController) marshalRecord(ol []*WatcherEvent) (string, error) {
+	msg, err := json.Marshal(ol)
+	if err != nil {
+		return "", perrors.WithMessage(err, "json encode object list")
+	}
+	return base64.URLEncoding.EncodeToString(msg), nil
+}
+
+// readCurrentPod reads from kubernetes-env current pod status
+func (c *dubboRegistryController) readCurrentPod() (*v1.Pod, error) {
+	currentPod, err := c.kc.CoreV1().Pods(c.namespace).Get(c.name, metav1.GetOptions{})
+	if err != nil {
+		return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.name, c.namespace)
+	}
+	return currentPod, nil
+}
+
+// addAnnotationForCurrentPod adds annotation for current pod
+func (c *dubboRegistryController) addAnnotationForCurrentPod(k string, v string) error {
+
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	// 1. accord old pod && (k, v) assemble new pod dubbo annotion v
+	// 2. get patch data
+	// 3. PATCH the pod
+	currentPod, err := c.readCurrentPod()
+	if err != nil {
+		return perrors.WithMessage(err, "read current pod")
+	}
+
+	oldPod, newPod, err := c.assembleDUBBOAnnotations(k, v, currentPod)
+	if err != nil {
+		return perrors.WithMessage(err, "assemble")
+	}
+
+	patchBytes, err := c.getPatch(oldPod, newPod)
+	if err != nil {
+		return perrors.WithMessage(err, "get patch")
+	}
+
+	_, err = c.patchCurrentPod(patchBytes)
+	if err != nil {
+		return perrors.WithMessage(err, "patch current pod")
+	}
+
+	return c.watcherSet.Put(&WatcherEvent{
+		Key:       k,
+		Value:     v,
+		EventType: Create,
+	})
+}
diff --git a/remoting/kubernetes/watch.go b/remoting/kubernetes/watch.go
index c99a3eb..07eeb09 100644
--- a/remoting/kubernetes/watch.go
+++ b/remoting/kubernetes/watch.go
@@ -71,6 +71,7 @@
 }
 
 // Watchable WatcherSet
+// thread-safe
 type WatcherSet interface {
 
 	// put the watch event to the watch set
@@ -139,17 +140,15 @@
 	return s.addWatcher(key, prefix)
 }
 
-// Done
-// get the watcher-set status
+// Done gets the watcher-set status
 func (s *watcherSetImpl) Done() <-chan struct{} {
 	return s.ctx.Done()
 }
 
-// Put
-// put the watch event to watcher-set
+// Put puts the watch event to watcher-set
 func (s *watcherSetImpl) Put(watcherEvent *WatcherEvent) error {
 
-	sendMsg := func(object *WatcherEvent, w *watcher) {
+	blockSendMsg := func(object *WatcherEvent, w *watcher) {
 
 		select {
 		case <-w.done():
@@ -167,40 +166,40 @@
 	}
 
 	// put to watcher-set
-	if watcherEvent.EventType == Delete {
+	switch watcherEvent.EventType {
+	case Delete:
+		// delete from store
 		delete(s.cache, watcherEvent.Key)
-	} else {
-
-		old, ok := s.cache[watcherEvent.Key]
-		if ok {
-			if old.Value == watcherEvent.Value {
-				// already have this k/v pair
-				return nil
-			}
+	case Update, Create:
+		o, ok := s.cache[watcherEvent.Key]
+		if !ok {
+			// pod update, but create new k/v pair
+			watcherEvent.EventType = Create
+			s.cache[watcherEvent.Key] = watcherEvent
+			break
 		}
-
-		// refresh the watcherEvent
+		// k/v pair already latest
+		if o.Value == watcherEvent.Value {
+			return nil
+		}
+		// update to latest status
 		s.cache[watcherEvent.Key] = watcherEvent
 	}
 
 	// notify watcher
 	for _, w := range s.watchers {
-
-		w := w
-
 		if !strings.Contains(watcherEvent.Key, w.interested.key) {
 			//  this watcher no interest in this element
 			continue
 		}
-
 		if !w.interested.prefix {
 			if watcherEvent.Key == w.interested.key {
-				go sendMsg(watcherEvent, w)
+				blockSendMsg(watcherEvent, w)
 			}
 			// not interest
 			continue
 		}
-		go sendMsg(watcherEvent, w)
+		blockSendMsg(watcherEvent, w)
 	}
 	return nil
 }
@@ -242,8 +241,7 @@
 	return w, nil
 }
 
-// Get
-// get elements from watcher-set
+// Get gets elements from watcher-set
 func (s *watcherSetImpl) Get(key string, prefix bool) ([]*WatcherEvent, error) {
 
 	s.lock.RLock()
@@ -296,19 +294,17 @@
 	exit      chan struct{}
 }
 
-// ResultChan
+// nolint
 func (w *watcher) ResultChan() <-chan *WatcherEvent {
 	return w.ch
 }
 
-// ID
-// the watcher's id
+// nolint
 func (w *watcher) ID() string {
 	return strconv.FormatUint(w.id, 10)
 }
 
-// stop
-// stop the watcher
+// nolint
 func (w *watcher) stop() {
 
 	// double close will panic
@@ -317,14 +313,12 @@
 	})
 }
 
-// done
-// check watcher status
+// done checks watcher status
 func (w *watcher) done() <-chan struct{} {
 	return w.exit
 }
 
-// newWatcherSet
-// new watcher set from parent context
+// newWatcherSet returns new watcher set from parent context
 func newWatcherSet(ctx context.Context) WatcherSet {
 	s := &watcherSetImpl{
 		ctx:      ctx,
diff --git a/remoting/listener.go b/remoting/listener.go
index 3713ba0..6cbb883 100644
--- a/remoting/listener.go
+++ b/remoting/listener.go
@@ -21,7 +21,7 @@
 	"fmt"
 )
 
-// DataListener ...
+// DataListener defines common data listener interface
 type DataListener interface {
 	DataChange(eventType Event) bool //bool is return for interface implement is interesting
 }
@@ -30,15 +30,15 @@
 // event type
 //////////////////////////////////////////
 
-// EventType ...
+// EventType means SourceObjectEventType
 type EventType int
 
 const (
-	// EventTypeAdd ...
+	// EventTypeAdd means add event
 	EventTypeAdd = iota
-	// EventTypeDel ...
+	// EventTypeDel means del event
 	EventTypeDel
-	// EventTypeUpdate ...
+	// EventTypeUpdate means update event
 	EventTypeUpdate
 )
 
@@ -56,7 +56,7 @@
 // service event
 //////////////////////////////////////////
 
-// Event ...
+// Event defines common elements for service event
 type Event struct {
 	Path    string
 	Action  EventType
diff --git a/remoting/nacos/builder.go b/remoting/nacos/builder.go
new file mode 100644
index 0000000..8a247e2
--- /dev/null
+++ b/remoting/nacos/builder.go
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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"
+	"strconv"
+	"strings"
+	"time"
+)
+
+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"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/config"
+)
+
+// 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 nil, err
+	}
+	return clients.CreateConfigClient(nacosConfig)
+}
+
+// getNacosConfig will return the nacos config
+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.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/remoting/nacos/builder_test.go b/remoting/nacos/builder_test.go
new file mode 100644
index 0000000..61d13ef
--- /dev/null
+++ b/remoting/nacos/builder_test.go
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nacos
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/config"
+)
+
+func TestNewNacosClient(t *testing.T) {
+	rc := &config.RemoteConfig{}
+	client, err := NewNacosClient(rc)
+
+	// address is nil
+	assert.NotNil(t, err)
+
+	rc.Address = "console.nacos.io:80:123"
+	client, err = NewNacosClient(rc)
+	// invalid address
+	assert.NotNil(t, err)
+
+	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 21486aa..4ca34a6 100644
--- a/remoting/zookeeper/client.go
+++ b/remoting/zookeeper/client.go
@@ -42,24 +42,26 @@
 )
 
 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 ...
+// 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
 }
 
-// StateToString ...
+// nolint
 func StateToString(state zk.State) string {
 	switch state {
 	case zk.StateDisconnected:
@@ -87,11 +89,9 @@
 	default:
 		return state.String()
 	}
-
-	return "zookeeper unknown state"
 }
 
-// Options ...
+// nolint
 type Options struct {
 	zkName string
 	client *ZookeeperClient
@@ -99,27 +99,26 @@
 	ts *zk.TestCluster
 }
 
-// Option ...
+// Option will define a function of handling Options
 type Option func(*Options)
 
-// WithZkName ...
+// WithZkName sets zk client name
 func WithZkName(name string) Option {
 	return func(opt *Options) {
 		opt.zkName = name
 	}
 }
 
-// ValidateZookeeperClient ...
-func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error {
+// ValidateZookeeperClient validates client and sets options
+func ValidateZookeeperClient(container ZkClientFacade, opts ...Option) error {
 	var (
 		err error
 	)
-	opions := &Options{}
+	options := &Options{}
 	for _, opt := range opts {
-		opt(opions)
+		opt(options)
 	}
-
-	err = nil
+	connected := false
 
 	lock := container.ZkClientLock()
 	url := container.GetUrl()
@@ -128,7 +127,7 @@
 	defer lock.Unlock()
 
 	if container.ZkClient() == nil {
-		//in dubbo ,every registry only connect one node ,so this is []string{r.Address}
+		// 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",
@@ -136,13 +135,14 @@
 			return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location)
 		}
 		zkAddresses := strings.Split(url.Location, ",")
-		newClient, err := newZookeeperClient(opions.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}",
-				opions.zkName, url.Location, timeout.String(), err)
+				options.zkName, url.Location, timeout.String(), err)
 			return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location)
 		}
 		container.SetZkClient(newClient)
+		connected = true
 	}
 
 	if container.ZkClient().Conn == nil {
@@ -150,14 +150,21 @@
 		container.ZkClient().Conn, event, err = zk.Connect(container.ZkClient().ZkAddrs, container.ZkClient().Timeout)
 		if err == nil {
 			container.ZkClient().Wait.Add(1)
+			connected = true
 			go container.ZkClient().HandleZkEvent(event)
 		}
 	}
 
+	if connected {
+		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
@@ -183,14 +190,14 @@
 	return z, nil
 }
 
-// WithTestCluster ...
+// WithTestCluster sets test cluser for zk client
 func WithTestCluster(ts *zk.TestCluster) Option {
 	return func(opt *Options) {
 		opt.ts = ts
 	}
 }
 
-// NewMockZookeeperClient ...
+// NewMockZookeeperClient returns a mock client instance
 func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Event, error) {
 	var (
 		err   error
@@ -207,14 +214,14 @@
 		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 {
@@ -222,21 +229,15 @@
 		}
 	}
 
-	//callbackChan := make(chan zk.Event)
-	//f := func(event zk.Event) {
-	//	callbackChan <- event
-	//}
-
 	z.Conn, event, err = ts.ConnectWithOptions(timeout)
 	if err != nil {
 		return nil, nil, nil, perrors.WithMessagef(err, "zk.Connect")
 	}
-	//z.wait.Add(1)
 
 	return ts, z, event, nil
 }
 
-// HandleZkEvent ...
+// HandleZkEvent handles zookeeper events
 func (z *ZookeeperClient) HandleZkEvent(session <-chan zk.Event) {
 	var (
 		state int
@@ -248,13 +249,12 @@
 		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):
@@ -267,11 +267,10 @@
 				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",
@@ -281,52 +280,53 @@
 						}
 					}
 				}
-				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)
 		}
 	}
 }
 
-// RegisterEvent ...
+// RegisterEvent registers zookeeper events
 func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) {
 	if zkPath == "" || event == nil {
 		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 ...
+// UnregisterEvent unregisters zookeeper events
 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)
 		}
 	}
@@ -339,7 +339,7 @@
 	}
 }
 
-// Done ...
+// nolint
 func (z *ZookeeperClient) Done() <-chan struct{} {
 	return z.exit
 }
@@ -355,7 +355,7 @@
 	return false
 }
 
-// ZkConnValid ...
+// ZkConnValid validates zookeeper connection
 func (z *ZookeeperClient) ZkConnValid() bool {
 	select {
 	case <-z.exit:
@@ -364,16 +364,16 @@
 	}
 
 	valid := true
-	z.Lock()
+	z.RLock()
 	if z.Conn == nil {
 		valid = false
 	}
-	z.Unlock()
+	z.RUnlock()
 
 	return valid
 }
 
-// Close ...
+// nolint
 func (z *ZookeeperClient) Close() {
 	if z == nil {
 		return
@@ -386,35 +386,44 @@
 	z.Conn = nil
 	z.Unlock()
 	if conn != nil {
+		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 ...
+// Create will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first.
+// And the value for the basePath is ""
 func (z *ZookeeperClient) Create(basePath string) error {
+	return z.CreateWithValue(basePath, []byte(""))
+}
+
+// CreateWithValue will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first.
+func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error {
 	var (
 		err     error
 		tmpPath string
 	)
 
 	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, []byte(""), 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)
 			}
 		}
@@ -423,16 +432,52 @@
 	return nil
 }
 
-// Delete ...
-func (z *ZookeeperClient) Delete(basePath string) error {
+// CreateTempWithValue will create the node recursively, which means that if the parent node is absent,
+// it will create parent node first,and set value in last child path
+// If the path exist, it will update data
+func (z *ZookeeperClient) CreateTempWithValue(basePath string, value []byte) error {
 	var (
-		err error
+		err     error
+		tmpPath string
 	)
 
+	logger.Debugf("zookeeperClient.Create(basePath{%s})", basePath)
+	conn := z.getConn()
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	if conn == nil {
+		return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
+	}
+
+	pathSlice := strings.Split(basePath, "/")[1:]
+	length := len(pathSlice)
+	for i, str := range pathSlice {
+		tmpPath = path.Join(tmpPath, "/", str)
+		// last child need be ephemeral
+		if i == length-1 {
+			_, err = conn.Create(tmpPath, value, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
+			if err == zk.ErrNodeExists {
+				return err
+			}
+		} else {
+			_, err = conn.Create(tmpPath, []byte{}, 0, zk.WorldACL(zk.PermAll))
+		}
+		if err != nil {
+			if err == zk.ErrNodeExists {
+				logger.Debugf("zk.create(\"%s\") exists", tmpPath)
+			} else {
+				logger.Errorf("zk.create(\"%s\") error(%v)", tmpPath, perrors.WithStack(err))
+				return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
+			}
+		}
+	}
+
+	return nil
+}
+
+// nolint
+func (z *ZookeeperClient) Delete(basePath string) error {
+	err := errNilZkClientConn
+	conn := z.getConn()
 	if conn != nil {
 		err = conn.Delete(basePath, -1)
 	}
@@ -440,36 +485,31 @@
 	return perrors.WithMessagef(err, "Delete(basePath:%s)", basePath)
 }
 
-// RegisterTemp ...
+// RegisterTemp registers temporary node by @basePath and @node
 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 && err != zk.ErrNodeExists {
 	if err != nil {
-		logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err))
-		return "", 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
 }
 
-// RegisterTempSeq ...
+// RegisterTempSeq register temporary sequence node by @basePath and @data
 func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, error) {
 	var (
 		err     error
@@ -477,9 +517,7 @@
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		tmpPath, err = conn.Create(
 			path.Join(basePath)+"/",
@@ -491,16 +529,16 @@
 
 	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
 }
 
-// GetChildrenW ...
+// GetChildrenW gets children watch by @path
 func (z *ZookeeperClient) GetChildrenW(path string) ([]string, <-chan zk.Event, error) {
 	var (
 		err      error
@@ -510,9 +548,7 @@
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		children, stat, watcher, err = conn.ChildrenW(path)
 	}
@@ -537,7 +573,7 @@
 	return children, watcher.EvtCh, nil
 }
 
-// GetChildren ...
+// GetChildren gets children by @path
 func (z *ZookeeperClient) GetChildren(path string) ([]string, error) {
 	var (
 		err      error
@@ -546,9 +582,7 @@
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		children, stat, err = conn.Children(path)
 	}
@@ -570,7 +604,7 @@
 	return children, nil
 }
 
-// ExistW ...
+// ExistW to judge watch whether it exists or not by @zkPath
 func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) {
 	var (
 		exist   bool
@@ -579,9 +613,7 @@
 	)
 
 	err = errNilZkClientConn
-	z.Lock()
-	conn := z.Conn
-	z.Unlock()
+	conn := z.getConn()
 	if conn != nil {
 		exist, _, watcher, err = conn.ExistsW(zkPath)
 	}
@@ -598,7 +630,19 @@
 	return watcher.EvtCh, nil
 }
 
-// GetContent ...
+// GetContent gets content by @zkPath
 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 cb41eb3..3474170 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 @@
 	"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 @@
 			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 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,22 +101,24 @@
 }
 
 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)
-	err2 := z.Delete("/test1/test2/test3/test4")
-	assert.NoError(t, err2)
-	//verifyEventOrder(t, event, []zk.EventType{zk.EventNodeCreated}, "event channel")
+	err = z.Delete("/test1/test2/test3/test4")
+	assert.NoError(t, err)
+	// 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")
@@ -123,9 +129,10 @@
 }
 
 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 0000000..9566b54
--- /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 0000000..f8d2bc7
--- /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 055db4f..d5d9e6e 100644
--- a/remoting/zookeeper/facade.go
+++ b/remoting/zookeeper/facade.go
@@ -30,29 +30,29 @@
 	"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 ...
-func HandleClientRestart(r zkClientFacade) {
+// HandleClientRestart keeps the connection between client and server
+func HandleClientRestart(r ZkClientFacade) {
 	var (
 		err error
 
 		failTimes int
 	)
 
-	defer r.WaitGroup().Done()
 LOOP:
 	for {
 		select {
 		case <-r.Done():
+			r.WaitGroup().Done() // dec the wg when registry is closed
 			logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...")
 			break LOOP
 			// re-register all services
@@ -63,12 +63,14 @@
 			zkAddress := r.ZkClient().ZkAddrs
 			r.SetZkClient(nil)
 			r.ZkClientLock().Unlock()
+			r.WaitGroup().Done() // dec the wg when zk client is closed
 
 			// Connect zk until success.
 			failTimes = 0
 			for {
 				select {
 				case <-r.Done():
+					r.WaitGroup().Done() // dec the wg when registry is closed
 					logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...")
 					break LOOP
 				case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): // Prevent crazy reconnection zk.
@@ -76,10 +78,8 @@
 				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 a41f6cd..1cd8f06 100644
--- a/remoting/zookeeper/facade_test.go
+++ b/remoting/zookeeper/facade_test.go
@@ -38,6 +38,16 @@
 	done    chan struct{}
 }
 
+func newMockFacade(client *ZookeeperClient, url *common.URL) ZkClientFacade {
+	mock := &mockFacade{
+		client: client,
+		URL:    url,
+	}
+
+	mock.wg.Add(1)
+	return mock
+}
+
 func (r *mockFacade) ZkClient() *ZookeeperClient {
 	return r.client
 }
@@ -80,7 +90,7 @@
 	assert.NoError(t, err)
 	defer ts.Stop()
 	url, _ := common.NewURL("mock://127.0.0.1")
-	mock := &mockFacade{client: z, URL: &url}
+	mock := newMockFacade(z, &url)
 	go HandleClientRestart(mock)
 	states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession}
 	verifyEventStateOrder(t, event, states, "event channel")
diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go
index eaf259f..9a4874d 100644
--- a/remoting/zookeeper/listener.go
+++ b/remoting/zookeeper/listener.go
@@ -31,12 +31,13 @@
 )
 
 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/remoting"
 )
 
-// ZkEventListener ...
+// nolint
 type ZkEventListener struct {
 	client      *ZookeeperClient
 	pathMapLock sync.Mutex
@@ -44,7 +45,7 @@
 	wg          sync.WaitGroup
 }
 
-// NewZkEventListener ...
+// NewZkEventListener returns a EventListener instance
 func NewZkEventListener(client *ZookeeperClient) *ZkEventListener {
 	return &ZkEventListener{
 		client:  client,
@@ -52,13 +53,25 @@
 	}
 }
 
-// SetClient ...
+// nolint
 func (l *ZkEventListener) SetClient(client *ZookeeperClient) {
 	l.client = client
 }
 
-// ListenServiceNodeEvent ...
-func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool {
+// 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 {
 	defer l.wg.Done()
 	var zkEvent zk.Event
 	for {
@@ -76,13 +89,21 @@
 			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:
@@ -95,8 +116,6 @@
 			return false
 		}
 	}
-
-	return false
 }
 
 func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, listener remoting.DataListener) {
@@ -147,7 +166,7 @@
 		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})
 			}
@@ -173,7 +192,7 @@
 	}
 }
 
-func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataListener) {
+func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listener remoting.DataListener) {
 	defer l.wg.Done()
 
 	var (
@@ -224,23 +243,38 @@
 		}
 		failTimes = 0
 		for _, c := range children {
+
+			// Only need to compare Path when subscribing to provider
+			if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) != -1 {
+				provider, _ := common.NewURL(c)
+				if provider.ServiceKey() != conf.ServiceKey() {
+					continue
+				}
+			}
+
 			// 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()
 			if ok {
-				logger.Warnf("@zkPath %s has already been listened.", zkPath)
+				logger.Warnf("@zkPath %s has already been listened.", dubboPath)
 				continue
 			}
 
 			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))
 			}
@@ -251,19 +285,19 @@
 			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)
 				go func(zkPath string, listener remoting.DataListener) {
-					l.listenDirEvent(zkPath, listener)
+					l.listenDirEvent(conf, zkPath, listener)
 					logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath)
 				}(dubboPath, listener)
 			}
@@ -288,14 +322,14 @@
 }
 
 // ListenServiceEvent is invoked by ZkConsumerRegistry::Register/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener
-// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent
+// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent
 //                            |
-//                            --------> ListenServiceNodeEvent
-func (l *ZkEventListener) ListenServiceEvent(zkPath string, listener remoting.DataListener) {
+//                            --------> listenServiceNodeEvent
+func (l *ZkEventListener) ListenServiceEvent(conf *common.URL, zkPath string, listener remoting.DataListener) {
 	logger.Infof("listen dubbo path{%s}", zkPath)
 	l.wg.Add(1)
 	go func(zkPath string, listener remoting.DataListener) {
-		l.listenDirEvent(zkPath, listener)
+		l.listenDirEvent(conf, zkPath, listener)
 		logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath)
 	}(zkPath, listener)
 }
@@ -304,7 +338,8 @@
 	return l.client.ZkConnValid()
 }
 
-// Close ...
+// 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 7301cd5..37ef1b4 100644
--- a/remoting/zookeeper/listener_test.go
+++ b/remoting/zookeeper/listener_test.go
@@ -32,6 +32,10 @@
 	"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 @@
 	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
@@ -97,9 +101,9 @@
 	go client.HandleZkEvent(event)
 	listener := NewZkEventListener(client)
 	dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait}
-	listener.ListenServiceEvent("/dubbo", dataListener)
+	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)
diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile
new file mode 100644
index 0000000..d48df36
--- /dev/null
+++ b/test/integrate/dubbo/go-client/Dockerfile
@@ -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.
+#
+
+FROM golang
+
+WORKDIR /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client
+
+ENV CONF_CONSUMER_FILE_PATH "client.yml"
+ENV APP_LOG_CONF_FILE "log.yml"
+
+ARG PR_ORIGIN_REPO
+ARG PR_ORIGIN_COMMITID
+
+ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client
+
+# update dubbo-go to current commit id
+RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop'
+RUN test ${PR_ORIGIN_REPO} && go mod edit  -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop
+
+RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client
+
+CMD go-client
\ No newline at end of file
diff --git a/test/integrate/dubbo/go-client/client.go b/test/integrate/dubbo/go-client/client.go
new file mode 100644
index 0000000..4c62674
--- /dev/null
+++ b/test/integrate/dubbo/go-client/client.go
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
+	"github.com/apache/dubbo-go/config"
+	_ "github.com/apache/dubbo-go/protocol/dubbo"
+	_ "github.com/apache/dubbo-go/registry/protocol"
+
+	_ "github.com/apache/dubbo-go/filter/filter_impl"
+
+	_ "github.com/apache/dubbo-go/cluster/cluster_impl"
+	_ "github.com/apache/dubbo-go/cluster/loadbalance"
+	_ "github.com/apache/dubbo-go/registry/zookeeper"
+)
+
+var (
+	survivalTimeout int = 10e9
+)
+
+func println(format string, args ...interface{}) {
+	fmt.Printf("\033[32;40m"+format+"\033[0m\n", args...)
+}
+
+// they are necessary:
+// 		export CONF_CONSUMER_FILE_PATH="xxx"
+// 		export APP_LOG_CONF_FILE="xxx"
+func main() {
+	hessian.RegisterPOJO(&User{})
+	config.Load()
+	time.Sleep(3e9)
+
+	println("\n\n\nstart to test dubbo")
+
+	go func() {
+		select {
+		case <-time.After(time.Minute):
+			panic("provider not start after client already running 1min")
+		}
+	}()
+	user := &User{}
+	err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)
+	if err != nil {
+		panic(err)
+	}
+	println("response result: %v\n", user)
+}
diff --git a/test/integrate/dubbo/go-client/client.yml b/test/integrate/dubbo/go-client/client.yml
new file mode 100644
index 0000000..df44a13
--- /dev/null
+++ b/test/integrate/dubbo/go-client/client.yml
@@ -0,0 +1,61 @@
+# dubbo client yaml configure file
+
+
+check: true
+# client
+request_timeout : "3s"
+# connect timeout
+connect_timeout : "3s"
+
+# application config
+application:
+  organization : "ikurento.com"
+  name  : "BDTService"
+  module : "dubbogo user-info client"
+  version : "0.0.1"
+  owner : "ZX"
+  environment : "dev"
+
+registries :
+  "demoZk":
+    protocol: "zookeeper"
+    timeout	: "3s"
+    address: "127.0.0.1:2181"
+    username: ""
+    password: ""
+
+
+references:
+  "UserProvider":
+    # 可以指定多个registry,使用逗号隔开;不指定默认向所有注册中心注册
+    registry: "demoZk"
+    protocol : "dubbo"
+    interface : "com.ikurento.user.UserProvider"
+    cluster: "failover"
+    methods :
+    - name: "GetUser"
+      retries: 3
+
+
+protocol_conf:
+  dubbo:
+    reconnect_interval: 0
+    connection_number: 2
+    heartbeat_period: "5s"
+    session_timeout: "20s"
+    pool_size: 64
+    pool_ttl: 600
+    getty_session_param:
+      compress_encoding: false
+      tcp_no_delay: true
+      tcp_keep_alive: true
+      keep_alive_period: "120s"
+      tcp_r_buf_size: 262144
+      tcp_w_buf_size: 65536
+      pkg_rq_size: 1024
+      pkg_wq_size: 512
+      tcp_read_timeout: "1s"
+      tcp_write_timeout: "5s"
+      wait_timeout: "1s"
+      max_msg_len: 1024000
+      session_name: "client"
diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod
new file mode 100644
index 0000000..4708eb1
--- /dev/null
+++ b/test/integrate/dubbo/go-client/go.mod
@@ -0,0 +1,3 @@
+module github.com/apache/dubbo-go/test/integrate/dubbo/go-client
+
+go 1.13
diff --git a/test/integrate/dubbo/go-client/log.yml b/test/integrate/dubbo/go-client/log.yml
new file mode 100644
index 0000000..59fa427
--- /dev/null
+++ b/test/integrate/dubbo/go-client/log.yml
@@ -0,0 +1,28 @@
+

+level: "debug"

+development: true

+disableCaller: false

+disableStacktrace: false

+sampling:

+encoding: "console"

+

+# encoder

+encoderConfig:

+  messageKey: "message"

+  levelKey: "level"

+  timeKey: "time"

+  nameKey: "logger"

+  callerKey: "caller"

+  stacktraceKey: "stacktrace"

+  lineEnding: ""

+  levelEncoder: "capitalColor"

+  timeEncoder: "iso8601"

+  durationEncoder: "seconds"

+  callerEncoder: "short"

+  nameEncoder: ""

+

+outputPaths:

+  - "stderr"

+errorOutputPaths:

+  - "stderr"

+initialFields:

diff --git a/test/integrate/dubbo/go-client/user.go b/test/integrate/dubbo/go-client/user.go
new file mode 100644
index 0000000..ff4486f
--- /dev/null
+++ b/test/integrate/dubbo/go-client/user.go
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+	"context"
+	"time"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	"github.com/apache/dubbo-go/config"
+)
+
+var userProvider = new(UserProvider)
+
+func init() {
+	config.SetConsumerService(userProvider)
+	hessian.RegisterPOJO(&User{})
+}
+
+type User struct {
+	Id   string
+	Name string
+	Age  int32
+	Time time.Time
+}
+
+type UserProvider struct {
+	GetUser func(ctx context.Context, req []interface{}, rsp *User) error
+}
+
+func (u *UserProvider) Reference() string {
+	return "UserProvider"
+}
+
+func (User) JavaClassName() string {
+	return "com.ikurento.user.User"
+}
diff --git a/test/integrate/dubbo/go-client/version.go b/test/integrate/dubbo/go-client/version.go
new file mode 100644
index 0000000..c613858
--- /dev/null
+++ b/test/integrate/dubbo/go-client/version.go
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+var (
+	Version = "2.6.0"
+)
diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile
new file mode 100644
index 0000000..c2f2d63
--- /dev/null
+++ b/test/integrate/dubbo/go-server/Dockerfile
@@ -0,0 +1,35 @@
+#
+#Licensed to the Apache Software Foundation (ASF) under one or more
+#contributor license agreements.  See the NOTICE file distributed with
+#this work for additional information regarding copyright ownership.
+#The ASF licenses this file to You under the Apache License, Version 2.0
+#(the "License"); you may not use this file except in compliance with
+#the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+#Unless required by applicable law or agreed to in writing, software
+#distributed under the License is distributed on an "AS IS" BASIS,
+#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#See the License for the specific language governing permissions and
+#limitations under the License.
+#
+
+FROM golang
+
+WORKDIR /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server
+
+ENV CONF_PROVIDER_FILE_PATH "server.yml"
+ENV APP_LOG_CONF_FILE "log.yml"
+
+ARG PR_ORIGIN_REPO
+ARG PR_ORIGIN_COMMITID
+
+ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server
+# update dubbo-go to current commit id
+RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop'
+RUN test ${PR_ORIGIN_REPO} && go mod edit  -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop
+
+RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server
+
+CMD go-server
diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod
new file mode 100644
index 0000000..9e11623
--- /dev/null
+++ b/test/integrate/dubbo/go-server/go.mod
@@ -0,0 +1,3 @@
+module github.com/apache/dubbo-go/test/integrate/dubbo/go-server
+
+go 1.13
diff --git a/test/integrate/dubbo/go-server/log.yml b/test/integrate/dubbo/go-server/log.yml
new file mode 100644
index 0000000..59fa427
--- /dev/null
+++ b/test/integrate/dubbo/go-server/log.yml
@@ -0,0 +1,28 @@
+

+level: "debug"

+development: true

+disableCaller: false

+disableStacktrace: false

+sampling:

+encoding: "console"

+

+# encoder

+encoderConfig:

+  messageKey: "message"

+  levelKey: "level"

+  timeKey: "time"

+  nameKey: "logger"

+  callerKey: "caller"

+  stacktraceKey: "stacktrace"

+  lineEnding: ""

+  levelEncoder: "capitalColor"

+  timeEncoder: "iso8601"

+  durationEncoder: "seconds"

+  callerEncoder: "short"

+  nameEncoder: ""

+

+outputPaths:

+  - "stderr"

+errorOutputPaths:

+  - "stderr"

+initialFields:

diff --git a/test/integrate/dubbo/go-server/server.go b/test/integrate/dubbo/go-server/server.go
new file mode 100644
index 0000000..115bf0a
--- /dev/null
+++ b/test/integrate/dubbo/go-server/server.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 main
+
+import (
+	"time"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	_ "github.com/apache/dubbo-go/cluster/cluster_impl"
+	_ "github.com/apache/dubbo-go/cluster/loadbalance"
+	_ "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/protocol/dubbo"
+	_ "github.com/apache/dubbo-go/registry/protocol"
+	_ "github.com/apache/dubbo-go/registry/zookeeper"
+)
+
+var (
+	stopC = make(chan struct{})
+)
+
+// they are necessary:
+// 		export CONF_PROVIDER_FILE_PATH="xxx"
+// 		export APP_LOG_CONF_FILE="xxx"
+func main() {
+
+	hessian.RegisterPOJO(&User{})
+	config.Load()
+
+	select {
+	case <-stopC:
+		// wait getty send resp to consumer
+		time.Sleep(3*time.Second)
+		return
+	case <-time.After(time.Minute):
+		panic("provider already running 1 min, but can't be call by consumer")
+	}
+}
diff --git a/test/integrate/dubbo/go-server/server.yml b/test/integrate/dubbo/go-server/server.yml
new file mode 100644
index 0000000..8a17297
--- /dev/null
+++ b/test/integrate/dubbo/go-server/server.yml
@@ -0,0 +1,57 @@
+# dubbo server yaml configure file
+
+
+# application config
+application:
+  organization : "ikurento.com"
+  name : "BDTService"
+  module : "dubbogo user-info server"
+  version : "0.0.1"
+  owner : "ZX"
+  environment : "dev"
+
+registries :
+  "demoZk":
+    protocol: "zookeeper"
+    timeout	: "3s"
+    address: "127.0.0.1:2181"
+
+services:
+  "UserProvider":
+    # 可以指定多个registry,使用逗号隔开;不指定默认向所有注册中心注册
+    registry: "demoZk"
+    protocol : "dubbo"
+    # 相当于dubbo.xml中的interface
+    interface : "com.ikurento.user.UserProvider"
+    loadbalance: "random"
+    warmup: "100"
+    cluster: "failover"
+    methods:
+    - name: "GetUser"
+      retries: 1
+      loadbalance: "random"
+
+protocols:
+  "dubbo":
+    name: "dubbo"
+    port: 20000
+
+
+protocol_conf:
+  dubbo:
+    session_number: 700
+    session_timeout: "20s"
+    getty_session_param:
+      compress_encoding: false
+      tcp_no_delay: true
+      tcp_keep_alive: true
+      keep_alive_period: "120s"
+      tcp_r_buf_size: 262144
+      tcp_w_buf_size: 65536
+      pkg_rq_size: 1024
+      pkg_wq_size: 512
+      tcp_read_timeout: "1s"
+      tcp_write_timeout: "5s"
+      wait_timeout: "1s"
+      max_msg_len: 1024000
+      session_name: "server"
diff --git a/test/integrate/dubbo/go-server/user.go b/test/integrate/dubbo/go-server/user.go
new file mode 100644
index 0000000..7bff415
--- /dev/null
+++ b/test/integrate/dubbo/go-server/user.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 main
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+	"github.com/apache/dubbo-go/config"
+)
+
+func init() {
+	config.SetProviderService(new(UserProvider))
+	// ------for hessian2------
+	hessian.RegisterPOJO(&User{})
+}
+
+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) {
+	println("req:%#v", req)
+	rsp := User{"A001", "Alex Stocks", 18, time.Now()}
+	println("rsp:%#v", rsp)
+	close(stopC)
+	return &rsp, nil
+}
+
+func (u *UserProvider) Reference() string {
+	return "UserProvider"
+}
+
+func (u User) JavaClassName() string {
+	return "com.ikurento.user.User"
+}
+
+func println(format string, args ...interface{}) {
+	fmt.Printf("\033[32;40m"+format+"\033[0m\n", args...)
+}
diff --git a/test/integrate/dubbo/go-server/version.go b/test/integrate/dubbo/go-server/version.go
new file mode 100644
index 0000000..c613858
--- /dev/null
+++ b/test/integrate/dubbo/go-server/version.go
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+var (
+	Version = "2.6.0"
+)