diff --git a/.gitignore b/.gitignore
index e5ba291004ab0d89c1ef1db6f353361232fddcc8..568e9f24541dd6f02dd8670436fd48db481b7f21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,5 @@ coverage.txt
 remoting/zookeeper/zookeeper-4unittest/
 config_center/zookeeper/zookeeper-4unittest/
 registry/zookeeper/zookeeper-4unittest/
-registry/consul/agent*
\ No newline at end of file
+registry/consul/agent*
+config_center/apollo/mockDubbog.properties.json
diff --git a/.travis.yml b/.travis.yml
index 707e64481416b2c090bad05cddce2b3ccebf4535..7f30febe7bbd95ffbf1c25abce997408c7681074 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,11 @@
 language: go
 
+os:
+  - linux
+  - osx
+
 go:
-  - "1.12"
+  - "1.13"
 
 env:
   - GO111MODULE=on
@@ -10,10 +14,7 @@ install: true
 
 script:
   - go fmt ./... && [[ -z `git status -s` ]]
-  - mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar
-  - wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar
-  - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/
-  - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/
+  - chmod u+x before_ut.sh && ./before_ut.sh
   - go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic
 
 after_success:
diff --git a/CHANGE.md b/CHANGE.md
index 947a695ca854fe8c3d91d8ea989b52dcddbe1523..ea2fe351cfffb81e0f89f340f402ce218b32fbf1 100644
--- a/CHANGE.md
+++ b/CHANGE.md
@@ -1,64 +1,98 @@
 # Release Notes
+---
+
+## 1.3.0
+
+### New Features
+
+- [Add apollo config center support](https://github.com/apache/dubbo-go/pull/250)
+- [Gracefully shutdown](https://github.com/apache/dubbo-go/pull/255)
+- [Add consistent hash load balance support](https://github.com/apache/dubbo-go/pull/261)
+- [Add sticky connection support](https://github.com/apache/dubbo-go/pull/270)
+- [Add async call for dubbo protocol](https://github.com/apache/dubbo-go/pull/272)
+- [Add generic implement](https://github.com/apache/dubbo-go/pull/291)
+- [Add request timeout for method](https://github.com/apache/dubbo-go/pull/284)
+- [Add grpc protocol](https://github.com/apache/dubbo-go/pull/311)
+
+### Enhancement
+    
+- [The SIGSYS and SIGSTOP are not supported in windows platform](https://github.com/apache/dubbo-go/pull/262)
+- [Error should be returned when `NewURL` failed](https://github.com/apache/dubbo-go/pull/266)
+- [Split config center GetConfig method](https://github.com/apache/dubbo-go/pull/267)
+- [Modify closing method for dubbo protocol](https://github.com/apache/dubbo-go/pull/268)
+- [Add SetLoggerLevel method](https://github.com/apache/dubbo-go/pull/271)
+- [Change the position of the lock](https://github.com/apache/dubbo-go/pull/286)
+- [Change zk version and add base_registry](https://github.com/apache/dubbo-go/pull/355)
+
+### Bugfixes
+    
+- [Fix negative wait group count](https://github.com/apache/dubbo-go/pull/253)
+- [After disconnection with ZK registry, cosumer can't listen to provider changes](https://github.com/apache/dubbo-go/pull/258)
+- [The generic filter and default reference filters lack ','](https://github.com/apache/dubbo-go/pull/260)
+- [Url encode zkpath](https://github.com/apache/dubbo-go/pull/283)
+- [Fix jsonrpc about HTTP/1.1](https://github.com/apache/dubbo-go/pull/327)
+- [Fix zk bug](https://github.com/apache/dubbo-go/pull/346)
+- [HessianCodec failed to check package header length](https://github.com/apache/dubbo-go/pull/381)
 
 ## 1.2.0
 
 ### New Features
 
-- Add etcdv3 registry support<https://github.com/apache/dubbo-go/pull/148>
-- Add nacos registry support<https://github.com/apache/dubbo-go/pull/151>
-- Add fail fast cluster support<https://github.com/apache/dubbo-go/pull/140>
-- Add available cluster support<https://github.com/apache/dubbo-go/pull/155>
-- Add broadcast cluster support<https://github.com/apache/dubbo-go/pull/158>
-- Add forking cluster support<https://github.com/apache/dubbo-go/pull/161>
-- Add service token authorization support<https://github.com/apache/dubbo-go/pull/202>
-- Add accessLog filter support<https://github.com/apache/dubbo-go/pull/214>
-- Add tps limit support<https://github.com/apache/dubbo-go/pull/237>
-- Add execute limit support<https://github.com/apache/dubbo-go/pull/246>
-- Move callService to invoker & support attachments<https://github.com/apache/dubbo-go/pull/193>
-- Move example in dubbo-go project away<https://github.com/apache/dubbo-go/pull/228>
-- Support dynamic config center which compatible with dubbo 2.6.x & 2.7.x and commit the zookeeper impl<https://github.com/apache/dubbo-go/pull/194>
+- [Add etcdv3 registry support](https://github.com/apache/dubbo-go/pull/148)
+- [Add nacos registry support](https://github.com/apache/dubbo-go/pull/151)
+- [Add fail fast cluster support](https://github.com/apache/dubbo-go/pull/140)
+- [Add available cluster support](https://github.com/apache/dubbo-go/pull/155)
+- [Add broadcast cluster support](https://github.com/apache/dubbo-go/pull/158)
+- [Add forking cluster support](https://github.com/apache/dubbo-go/pull/161)
+- [Add service token authorization support](https://github.com/apache/dubbo-go/pull/202)
+- [Add accessLog filter support](https://github.com/apache/dubbo-go/pull/214)
+- [Add tps limit support](https://github.com/apache/dubbo-go/pull/237)
+- [Add execute limit support](https://github.com/apache/dubbo-go/pull/246)
+- [Move callService to invoker & support attachments](https://github.com/apache/dubbo-go/pull/193)
+- [Move example in dubbo-go project away](https://github.com/apache/dubbo-go/pull/228)
+- [Support dynamic config center which compatible with dubbo 2.6.x & 2.7.x and commit the zookeeper impl](https://github.com/apache/dubbo-go/pull/194)
 
 ### Enhancement
 
-- Split gettyRPCClient.close and gettyRPCClientPool.remove in protocol/dubbo/pool.go<https://github.com/apache/dubbo-go/pull/186>
-- Remove client from pool before closing it<https://github.com/apache/dubbo-go/pull/190>
-- Enhance the logic for fetching the local address<https://github.com/apache/dubbo-go/pull/209>
-- Add protocol_conf default values<https://github.com/apache/dubbo-go/pull/221>
-- Add task pool for getty<https://github.com/apache/dubbo-go/pull/141>
-- Update getty: remove read queue<https://github.com/apache/dubbo-go/pull/137>
-- Clean heartbeat from PendingResponse<https://github.com/apache/dubbo-go/pull/166>
+- [Split gettyRPCClient.close and gettyRPCClientPool.remove in protocol/dubbo/pool.go](https://github.com/apache/dubbo-go/pull/186)
+- [Remove client from pool before closing it](https://github.com/apache/dubbo-go/pull/190)
+- [Enhance the logic for fetching the local address](https://github.com/apache/dubbo-go/pull/209)
+- [Add protocol_conf default values](https://github.com/apache/dubbo-go/pull/221)
+- [Add task pool for getty](https://github.com/apache/dubbo-go/pull/141)
+- [Update getty: remove read queue](https://github.com/apache/dubbo-go/pull/137)
+- [Clean heartbeat from PendingResponse](https://github.com/apache/dubbo-go/pull/166)
 
 ### Bugfixes
 
-- GettyRPCClientPool remove deadlock<https://github.com/apache/dubbo-go/pull/183/files>
-- Fix failover cluster bug and url parameter retries change int to string type<https://github.com/apache/dubbo-go/pull/195>
-- Fix url params unsafe map<https://github.com/apache/dubbo-go/pull/201>
-- Read protocol config by map key in config yaml instead of protocol name<https://github.com/apache/dubbo-go/pull/218>
-- Fix dubbo group issues #238<https://github.com/apache/dubbo-go/pull/243>/<https://github.com/apache/dubbo-go/pull/244>
-- Fix bug in reference_config<https://github.com/apache/dubbo-go/pull/157>
-- Fix high memory bug in zookeeper listener<https://github.com/apache/dubbo-go/pull/168>
+- [GettyRPCClientPool remove deadlock](https://github.com/apache/dubbo-go/pull/183/files)
+- [Fix failover cluster bug and url parameter retries change int to string type](https://github.com/apache/dubbo-go/pull/195)
+- [Fix url params unsafe map](https://github.com/apache/dubbo-go/pull/201)
+- [Read protocol config by map key in config yaml instead of protocol name](https://github.com/apache/dubbo-go/pull/218)
+- *Fix dubbo group issues #238* [pr #243](https://github.com/apache/dubbo-go/pull/243) and [pr #244](https://github.com/apache/dubbo-go/pull/244)
+- [Fix bug in reference_config](https://github.com/apache/dubbo-go/pull/157)
+- [Fix high memory bug in zookeeper listener](https://github.com/apache/dubbo-go/pull/168)
 
 ## 1.1.0
 
 ### New Features
 
-- Support Java bigdecimal<https://github.com/apache/dubbo-go/pull/126>锛�
-- Support all JDK exceptions<https://github.com/apache/dubbo-go/pull/120>锛�
-- Support multi-version of service<https://github.com/apache/dubbo-go/pull/119>锛�
-- Allow user set custom params for registry<https://github.com/apache/dubbo-go/pull/117>锛�
-- Support zookeeper config center<https://github.com/apache/dubbo-go/pull/99>锛�
-- Failsafe/Failback  Cluster Strategy<https://github.com/apache/dubbo-go/pull/136>;
+- [Support Java bigdecimal](https://github.com/apache/dubbo-go/pull/126)
+- [Support all JDK exceptions](https://github.com/apache/dubbo-go/pull/120)
+- [Support multi-version of service](https://github.com/apache/dubbo-go/pull/119)
+- [Allow user set custom params for registry](https://github.com/apache/dubbo-go/pull/117)
+- [Support zookeeper config center](https://github.com/apache/dubbo-go/pull/99)
+- [Failsafe/Failback  Cluster Strategy](https://github.com/apache/dubbo-go/pull/136)
 
 ### Enhancement
 
-- Use time wheel instead of time.After to defeat timer object memory leakage<https://github.com/apache/dubbo-go/pull/130> 锛�
+- [Use time wheel instead of time.After to defeat timer object memory leakage](https://github.com/apache/dubbo-go/pull/130)
 
 ### Bugfixes
 
-- Preventing dead loop when got zookeeper unregister event<https://github.com/apache/dubbo-go/pull/129>锛�
-- Delete ineffassign<https://github.com/apache/dubbo-go/pull/127>锛�
-- Add wg.Done() for mockDataListener<https://github.com/apache/dubbo-go/pull/118>锛�
-- Delete wrong spelling words<https://github.com/apache/dubbo-go/pull/107>锛�
-- Use sync.Map to defeat from gettyClientPool deadlock<https://github.com/apache/dubbo-go/pull/106>锛�
-- Handle panic when function args list is empty<https://github.com/apache/dubbo-go/pull/98>锛�
-- url.Values is not safe map<https://github.com/apache/dubbo-go/pull/172>;
+- [Preventing dead loop when got zookeeper unregister event](https://github.com/apache/dubbo-go/pull/129)
+- [Delete ineffassign](https://github.com/apache/dubbo-go/pull/127)
+- [Add wg.Done() for mockDataListener](https://github.com/apache/dubbo-go/pull/118)
+- [Delete wrong spelling words](https://github.com/apache/dubbo-go/pull/107)
+- [Use sync.Map to defeat from gettyClientPool deadlock](https://github.com/apache/dubbo-go/pull/106)
+- [Handle panic when function args list is empty](https://github.com/apache/dubbo-go/pull/98)
+- [url.Values is not safe map](https://github.com/apache/dubbo-go/pull/172)
diff --git a/README.md b/README.md
index 2030664dba256e005d8b43c4098e42e04cac6fc1..1dde951d350e6ee51f3f2aeeac7bb516b1b999be 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,13 @@ Apache License, Version 2.0
 
 ## Release note ##
 
-[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0)
+[v1.3.0 - Mar 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.3.0)
+
+[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0)
 
 [v1.1.0 - Sep 7, 2019 the first release after transferred to apache](https://github.com/apache/dubbo-go/releases/tag/v1.1.0)
 
-[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0)
+[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0)
 
 ## Project Architecture ##
 
@@ -43,18 +45,18 @@ Finished List:
 - Codec
     * JsonRPC V2
     * Hessian V2
-    
+
 - Protocol
     * Dubbo
     * Jsonrpc2.0
     * [gRPC](https://github.com/apache/dubbo-go/pull/311)
-    
+
 - Registry
     * ZooKeeper
     * [etcd v3](https://github.com/apache/dubbo-go/pull/148)
     * [nacos](https://github.com/apache/dubbo-go/pull/151)
     * [consul](https://github.com/apache/dubbo-go/pull/121)
-    
+
 - Dynamic Configure Center & Service Management Configurator
     * Zookeeper
     * [apollo](https://github.com/apache/dubbo-go/pull/250)
@@ -66,12 +68,13 @@ Finished List:
     * [Available](https://github.com/apache/dubbo-go/pull/155)
     * [Broadcast](https://github.com/apache/dubbo-go/pull/158)
     * [Forking](https://github.com/apache/dubbo-go/pull/161)
-    
+
 - Load Balance
     * Random
     * [RoundRobin](https://github.com/apache/dubbo-go/pull/66)
     * [LeastActive](https://github.com/apache/dubbo-go/pull/65)
-    
+    * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261)
+
 - Filter
     * Echo Health Check
     * [Circuit break and service downgrade](https://github.com/apache/dubbo-go/pull/133)
@@ -80,10 +83,10 @@ Finished List:
     * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237)
     * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246)
     * [GenericServiceFilter](https://github.com/apache/dubbo-go/pull/291)
-    
+
 - Invoke
     * [generic invoke](https://github.com/apache/dubbo-go/pull/122)
-    
+
 - Others:
     * start check
     * connecting certain provider
@@ -94,14 +97,13 @@ Finished List:
 
 Working List:
 
-- Load Balance: ConsistentHash
 - Registry: k8s
 - Metadata Center (dubbo v2.7.x)
 - Metrics: Opentracing/Promethus(dubbo v2.7.x)
 
 You can know more about dubbo-go by its [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap).
 
-![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/arch.png)
+![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/dubbo-go-arch.png)
 
 ## Document
 
@@ -125,7 +127,7 @@ Windows
 before_ut.bat
 ```
 
-# Run
+### Run
 ```bash
 go test ./...
 
@@ -133,6 +135,10 @@ go test ./...
 go test ./... -coverprofile=coverage.txt -covermode=atomic
 ```
 
+## Build
+
+Please move to [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples)
+
 ## Contributing
 
 If you are willing to do some code contributions and document contributions to [Apache/dubbo-go](https://github.com/apache/dubbo-go), please visit [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md).
@@ -147,11 +153,6 @@ About dubbo-go benchmarking report, please refer to [dubbo benchmarking report](
 
 If you are using [apache/dubbo-go](github.com/apache/dubbo-go) and think that it helps you or want do some contributions to it, please add your company to to [the user list](https://github.com/apache/dubbo-go/issues/2) to let us know your needs.
 
-
-![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png)
-![ctrip](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png)
-
-## Stargazers
-
-[![Stargazers over time](https://starchart.cc/apache/dubbo-go.svg)](https://starchart.cc/apache/dubbo-go)
-
+![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)
+![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png)
+![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png)
diff --git a/README_CN.md b/README_CN.md
index 22af253416017403eaad2579ff977c6925936d7a..ade924e7a9a6206b6e935e084d68679957dd7fcb 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -2,6 +2,7 @@
 
 [![Build Status](https://travis-ci.org/apache/dubbo-go.svg?branch=master)](https://travis-ci.org/apache/dubbo-go)
 [![codecov](https://codecov.io/gh/apache/dubbo-go/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-go)
+[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/apache/dubbo-go?tab=doc)
 
 ---
 Apache Dubbo Go 璇█瀹炵幇
@@ -12,11 +13,13 @@ Apache License, Version 2.0
 
 ## 鍙戝竷鏃ュ織 ##
 
-[v1.0.0 - 2019骞�5鏈�29鏃� 鍏煎dubbo v2.6.5 鐗堟湰](https://github.com/apache/dubbo-go/releases/tag/v1.0.0)
+[v1.3.0 - 2020骞�3鏈�1鏃(https://github.com/apache/dubbo-go/releases/tag/v1.3.0)
+
+[v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0)
 
 [v1.1.0 - 2019骞�9鏈�7鏃� 鎹愮尞缁橝pache涔嬪悗鐨勭涓€娆elease](https://github.com/apache/dubbo-go/releases/tag/v1.1.0)
 
-[v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0)
+[v1.0.0 - 2019骞�5鏈�29鏃� 鍏煎dubbo v2.6.5 鐗堟湰](https://github.com/apache/dubbo-go/releases/tag/v1.0.0)
 
 ## 宸ョ▼鏋舵瀯 ##
 
@@ -33,7 +36,7 @@ Apache License, Version 2.0
 - 瑙掕壊绔�
     * Consumer
     * Provider
-    
+
 - 浼犺緭鍗忚
     * HTTP
     * TCP
@@ -46,17 +49,17 @@ Apache License, Version 2.0
     * Dubbo
     * Jsonrpc2.0
     * [gRPC](https://github.com/apache/dubbo-go/pull/311)
-    
+
 - 娉ㄥ唽涓績
     * ZooKeeper
     * [etcd v3](https://github.com/apache/dubbo-go/pull/148)
     * [nacos](https://github.com/apache/dubbo-go/pull/151)
     * [consul](https://github.com/apache/dubbo-go/pull/121)
-    
+
 - 鍔ㄦ€侀厤缃腑蹇冧笌鏈嶅姟娌荤悊閰嶇疆鍣�
     * Zookeeper
     * [apollo](https://github.com/apache/dubbo-go/pull/250)
-    
+
 - 闆嗙兢绛栫暐
     * Failover
     * [Failfast](https://github.com/apache/dubbo-go/pull/140)
@@ -64,12 +67,13 @@ Apache License, Version 2.0
     * [Available](https://github.com/apache/dubbo-go/pull/155)
     * [Broadcast](https://github.com/apache/dubbo-go/pull/158)
     * [Forking](https://github.com/apache/dubbo-go/pull/161)
-   
+
 - 璐熻浇鍧囪 绛栫暐
     * Random
     * [RoundRobin](https://github.com/apache/dubbo-go/pull/66)
     * [LeastActive](https://github.com/apache/dubbo-go/pull/65)
-    
+    * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261)
+
 - 杩囨护鍣�
     * Echo Health Check
     * [鏈嶅姟鐔旀柇&闄嶇骇](https://github.com/apache/dubbo-go/pull/133)
@@ -77,10 +81,10 @@ Apache License, Version 2.0
     * [AccessLogFilter](https://github.com/apache/dubbo-go/pull/214)
     * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237)
     * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246)
-    
+
 - 璋冪敤
     * [娉涘寲璋冪敤](https://github.com/apache/dubbo-go/pull/122)
-    
+
 - 鍏朵粬鍔熻兘鏀寔:
     * 鍚姩鏃舵鏌�
     * 鏈嶅姟鐩磋繛
@@ -91,14 +95,13 @@ Apache License, Version 2.0
 
 寮€鍙戜腑鍒楄〃:
 
-- 璐熻浇鍧囪 绛栫暐: ConsistentHash
 - 娉ㄥ唽涓績: k8s
 - 鍏冩暟鎹腑蹇� (dubbo v2.7.x)
 - Metrics: Opentracing/Promethus(dubbo v2.7.x)
 
 浣犲彲浠ラ€氳繃璁块棶 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 鐭ラ亾鏇村鍏充簬 dubbo-go 鐨勪俊鎭€�
 
-![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/arch.png)
+![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/dubbo-go-arch.png)
 
 ## 鏂囨。
 
@@ -122,7 +125,7 @@ Windows
 before_ut.bat
 ```
 
-# 鎵ц
+### 鎵ц
 ```bash
 go test ./...
 
@@ -130,6 +133,10 @@ go test ./...
 go test ./... -coverprofile=coverage.txt -covermode=atomic
 ```
 
+## 缂栬瘧
+
+璇风Щ姝� [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples)
+
 ## 濡備綍璐$尞
 
 濡傛灉鎮ㄦ効鎰忕粰 [Apache/dubbo-go](https://github.com/apache/dubbo-go) 璐$尞浠g爜鎴栬€呮枃妗o紝鎴戜滑閮界儹鐑堟杩庛€傚叿浣撹鍙傝€� [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md)銆�
@@ -145,8 +152,5 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic
 鑻ヤ綘姝e湪浣跨敤 [apache/dubbo-go](github.com/apache/dubbo-go) 涓旇涓哄叾鏈夌敤鎴栬€呭悜瀵瑰叾鍋氭敼杩涳紝璇峰繚鍒楄吹鍙镐俊鎭簬 [鐢ㄦ埛鍒楄〃](https://github.com/apache/dubbo-go/issues/2)锛屼互渚挎垜浠煡鏅撲箣銆�
 
 ![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)
-
-## Stargazers
-
-[![Stargazers over time](https://starchart.cc/apache/dubbo-go.svg)](https://starchart.cc/apache/dubbo-go)
-
+![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png)
+![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png)
diff --git a/before_ut.bat b/before_ut.bat
index 5296d0f8769b7b9f521f82e68bf3b10f4b5d16b4..abe7fc250ef44bf01396ae20c4dacc9db3f60ce2 100644
--- a/before_ut.bat
+++ b/before_ut.bat
@@ -14,8 +14,24 @@
 ::  See the License for the specific language governing permissions and
 ::  limitations under the License.
 
-set zkJar=zookeeper-3.4.9-fatjar.jar
-md remoting\zookeeper\zookeeper-4unittest\contrib\fatjar config_center\zookeeper\zookeeper-4unittest\contrib\fatjar registry\zookeeper\zookeeper-4unittest\contrib\fatjar
-curl -L https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJar% -o remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%
-xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/"
-xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/"
\ No newline at end of file
+set zkJarName="zookeeper-3.4.9-fatjar.jar"
+set remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJarName%"
+set zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar"
+set zkJar="%zkJarPath%/%zkJarName%"
+
+if not exist "%zkJar%" (
+   md %zkJarPath%
+   curl -L %remoteJarUrl% -o %zkJar%
+)
+
+md config_center\zookeeper\zookeeper-4unittest\contrib\fatjar
+xcopy /f "%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/"
+
+md registry\zookeeper\zookeeper-4unittest\contrib\fatjar
+xcopy /f "%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/"
+
+md cluster\router\chain\zookeeper-4unittest\contrib\fatjar
+xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/"
+
+md cluster\router\condition\zookeeper-4unittest\contrib\fatjar
+xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/"
\ No newline at end of file
diff --git a/before_ut.sh b/before_ut.sh
old mode 100644
new mode 100755
index 323173bcc64c3cbe9916747e10dd3ea8538457ea..7ee92e57a26cbdbb1d1a0b3e792726ad5e1954f8
--- a/before_ut.sh
+++ b/before_ut.sh
@@ -14,8 +14,24 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+zkJarName="zookeeper-3.4.9-fatjar.jar"
+remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${zkJarName}"
+zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar"
+zkJar="${zkJarPath}/${zkJarName}"
 
-mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar
-wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar
-cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/
-cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/
\ No newline at end of file
+if [ ! -f "${zkJar}" ]; then
+    mkdir -p ${zkJarPath}
+    wget -P "${zkJarPath}" ${remoteJarUrl}
+fi
+
+mkdir -p 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/
+
+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
diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go
index 644f67c5244350897bbc3e291e66e2421758fce5..12799994125c4bf5d968dfc811cda374effbf85c 100644
--- a/cluster/cluster_impl/base_cluster_invoker.go
+++ b/cluster/cluster_impl/base_cluster_invoker.go
@@ -45,6 +45,7 @@ func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker {
 		destroyed:      atomic.NewBool(false),
 	}
 }
+
 func (invoker *baseClusterInvoker) GetUrl() common.URL {
 	return invoker.directory.GetUrl()
 }
diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go
index 49df78c41b3c3cc7dacf92153fc7e4515a0caec0..d074697b85a3cf5b770de90da4847043d98c9df1 100644
--- a/cluster/cluster_impl/base_cluster_invoker_test.go
+++ b/cluster/cluster_impl/base_cluster_invoker_test.go
@@ -47,6 +47,7 @@ func Test_StickyNormal(t *testing.T) {
 	result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked)
 	assert.Equal(t, result, result1)
 }
+
 func Test_StickyNormalWhenError(t *testing.T) {
 	invokers := []protocol.Invoker{}
 	for i := 0; i < 10; i++ {
diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go
index 99c584bb583c1d59ece505feafd74ad6e11f6b9a..1be21067a6a9045cb6ae6f84655d516fea1f844b 100644
--- a/cluster/cluster_impl/failover_cluster_test.go
+++ b/cluster/cluster_impl/failover_cluster_test.go
@@ -118,6 +118,7 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio
 	}
 	return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{})
 }
+
 func Test_FailoverInvokeSuccess(t *testing.T) {
 	urlParams := url.Values{}
 	result := normalInvoke(t, 3, urlParams)
diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go
index e1a38c4c82cbac61fdfd96cd284c4eea44c97ccc..75d9ef26567df0fbd83f5d9f94c8548d1e8e633d 100644
--- a/cluster/directory/base_directory.go
+++ b/cluster/directory/base_directory.go
@@ -20,39 +20,94 @@ package directory
 import (
 	"sync"
 )
+
 import (
+	"github.com/dubbogo/gost/container/set"
 	"go.uber.org/atomic"
 )
+
 import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/cluster/router/chain"
 	"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"
 )
 
-// BaseDirectory ...
+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
 	destroyed *atomic.Bool
-	mutex     sync.Mutex
+	// this mutex for change the properties in BaseDirectory, like routerChain , destroyed etc
+	mutex       sync.Mutex
+	routerChain router.Chain
 }
 
-// NewBaseDirectory ...
+// NewBaseDirectory Create BaseDirectory with URL
 func NewBaseDirectory(url *common.URL) BaseDirectory {
 	return BaseDirectory{
-		url:       url,
-		destroyed: atomic.NewBool(false),
+		url:         url,
+		destroyed:   atomic.NewBool(false),
+		routerChain: &chain.RouterChain{},
 	}
 }
 
-// GetUrl ...
+// RouterChain Return router chain in directory
+func (dir *BaseDirectory) RouterChain() router.Chain {
+	return dir.routerChain
+}
+
+// SetRouterChain Set router chain in directory
+func (dir *BaseDirectory) SetRouterChain(routerChain router.Chain) {
+	dir.mutex.Lock()
+	defer dir.mutex.Unlock()
+	dir.routerChain = routerChain
+}
+
+// GetUrl Get URL
 func (dir *BaseDirectory) GetUrl() common.URL {
 	return *dir.url
 }
 
-// GetDirectoryUrl ...
+// GetDirectoryUrl Get URL instance
 func (dir *BaseDirectory) GetDirectoryUrl() *common.URL {
 	return dir.url
 }
 
-// Destroy ...
+// SetRouters Convert url to routers and add them into dir.routerChain
+func (dir *BaseDirectory) SetRouters(urls []*common.URL) {
+	if len(urls) == 0 {
+		return
+	}
+
+	routers := make([]router.Router, 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)
+			if err != nil {
+				logger.Errorf("Create router fail. router key: %s, error: %v", routerKey, url.Service(), err)
+				return
+			}
+			routers = append(routers, r)
+		}
+	}
+
+	logger.Infof("Init file condition router success, size: %v", len(routers))
+	dir.mutex.Lock()
+	rc := dir.routerChain
+	dir.mutex.Unlock()
+
+	rc.AddRouters(routers)
+}
+
+// Destroy Destroy
 func (dir *BaseDirectory) Destroy(doDestroy func()) {
 	if dir.destroyed.CAS(false, true) {
 		dir.mutex.Lock()
@@ -61,7 +116,18 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) {
 	}
 }
 
-// IsAvailable ...
+// IsAvailable Once directory init finish, it will change to true
 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
new file mode 100644
index 0000000000000000000000000000000000000000..d5993959f1d37f343a612e2bee305461d49535d0
--- /dev/null
+++ b/cluster/directory/base_directory_test.go
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package directory
+
+import (
+	"encoding/base64"
+	"fmt"
+	"testing"
+)
+
+import (
+	gxnet "github.com/dubbogo/gost/net"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	_ "github.com/apache/dubbo-go/cluster/router/condition"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+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)
+
+	localIP, _ := gxnet.GetLocalIP()
+	rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP))
+	routeURL := getRouteUrl(rule)
+	routerURLs := make([]*common.URL, 0)
+	routerURLs = append(routerURLs, routeURL)
+	directory.SetRouters(routerURLs)
+	chain := directory.RouterChain()
+
+	assert.NotNil(t, chain)
+}
+
+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")
+	return &url
+}
diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go
index 7d2d5490b02d22b12d55385458715fa8b31f2cac..9f600fedc40cf29a40abca6c11652935f20473b4 100644
--- a/cluster/directory/static_directory.go
+++ b/cluster/directory/static_directory.go
@@ -18,6 +18,11 @@
 package directory
 
 import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router/chain"
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/protocol"
 )
@@ -27,7 +32,7 @@ type staticDirectory struct {
 	invokers []protocol.Invoker
 }
 
-// NewStaticDirectory ...
+// NewStaticDirectory Create a new staticDirectory with invokers
 func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory {
 	var url common.URL
 
@@ -53,11 +58,21 @@ func (dir *staticDirectory) IsAvailable() bool {
 	return true
 }
 
+// List List invokers
 func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
-	//TODO:Here should add router
-	return dir.invokers
+	l := len(dir.invokers)
+	invokers := make([]protocol.Invoker, l, l)
+	copy(invokers, dir.invokers)
+	routerChain := dir.RouterChain()
+
+	if routerChain == nil {
+		return invokers
+	}
+	dirUrl := dir.GetUrl()
+	return routerChain.Route(invokers, &dirUrl, invocation)
 }
 
+// Destroy Destroy
 func (dir *staticDirectory) Destroy() {
 	dir.BaseDirectory.Destroy(func() {
 		for _, ivk := range dir.invokers {
@@ -66,3 +81,17 @@ func (dir *staticDirectory) Destroy() {
 		dir.invokers = []protocol.Invoker{}
 	})
 }
+
+// BuildRouterChain build router chain by invokers
+func (dir *staticDirectory) BuildRouterChain(invokers []protocol.Invoker) error {
+	if len(invokers) == 0 {
+		return perrors.Errorf("invokers == null")
+	}
+	url := invokers[0].GetUrl()
+	routerChain, e := chain.NewRouterChain(&url)
+	if e != nil {
+		return e
+	}
+	dir.SetRouterChain(routerChain)
+	return nil
+}
diff --git a/cluster/directory/static_directory_test.go b/cluster/directory/static_directory_test.go
index 42ef1bcd0b51ce94040b3cbfac37a1f6de686fbb..c50c9a4063bd1a372c27e47687cbf63850f76cef 100644
--- a/cluster/directory/static_directory_test.go
+++ b/cluster/directory/static_directory_test.go
@@ -40,7 +40,9 @@ func Test_StaticDirList(t *testing.T) {
 	}
 
 	staticDir := NewStaticDirectory(invokers)
-	assert.Len(t, staticDir.List(&invocation.RPCInvocation{}), 10)
+	list := staticDir.List(&invocation.RPCInvocation{})
+
+	assert.Len(t, list, 10)
 }
 
 func Test_StaticDirDestroy(t *testing.T) {
diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go
new file mode 100644
index 0000000000000000000000000000000000000000..d48a837eba2080867f370b0cd90e38a0bdf5e417
--- /dev/null
+++ b/cluster/router/chain/chain.go
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package chain
+
+import (
+	"math"
+	"sort"
+	"sync"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"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/protocol"
+)
+
+// RouterChain Router chain
+type RouterChain struct {
+	// 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
+	// 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
+
+	mutex sync.RWMutex
+}
+
+// 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)))
+	c.mutex.RLock()
+	copy(rs, c.routers)
+	c.mutex.RUnlock()
+
+	for _, r := range rs {
+		finalInvokers = r.Route(finalInvokers, url, invocation)
+	}
+	return finalInvokers
+}
+
+// AddRouters Add routers to router chain
+// 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))
+	newRouters = append(newRouters, c.builtinRouters...)
+	newRouters = append(newRouters, routers...)
+	sortRouter(newRouters)
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+	c.routers = newRouters
+}
+
+// NewRouterChain Use url to init router chain
+// Loop routerFactories and call NewRouter method
+func NewRouterChain(url *common.URL) (*RouterChain, error) {
+	routerFactories := extension.GetRouterFactories()
+	if len(routerFactories) == 0 {
+		return nil, perrors.Errorf("No routerFactory exits , create one please")
+	}
+	routers := make([]router.Router, 0, len(routerFactories))
+	for key, routerFactory := range routerFactories {
+		r, err := routerFactory().NewRouter(url)
+		if r == nil || err != nil {
+			logger.Errorf("router chain build router fail! routerFactories key:%s  error:%s", key, err.Error())
+			continue
+		}
+		routers = append(routers, r)
+	}
+
+	newRouters := make([]router.Router, len(routers))
+	copy(newRouters, routers)
+
+	sortRouter(newRouters)
+
+	chain := &RouterChain{
+		builtinRouters: routers,
+		routers:        newRouters,
+	}
+
+	return chain, nil
+}
+
+// sortRouter Sort router instance by priority with stable algorithm
+func sortRouter(routers []router.Router) {
+	sort.Stable(byPriority(routers))
+}
+
+// byPriority Sort by priority
+type byPriority []router.Router
+
+func (a byPriority) Len() int           { return len(a) }
+func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byPriority) Less(i, j int) bool { return a[i].Priority() < a[j].Priority() }
diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0cb47c4a185fe19b5f70ea4db2b80aab2f1aada5
--- /dev/null
+++ b/cluster/router/chain/chain_test.go
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package chain
+
+import (
+	"encoding/base64"
+	"fmt"
+	"strconv"
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/cluster/router/condition"
+	"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/zookeeper"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/remoting/zookeeper"
+)
+
+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")
+	assert.NoError(t, err)
+
+	testyml := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+`
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, configuration)
+
+	chain, err := NewRouterChain(getRouteUrl("test-condition"))
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(chain.routers))
+	appRouter := chain.routers[0].(*condition.AppRouter)
+
+	assert.NotNil(t, appRouter)
+	assert.NotNil(t, appRouter.RouterRule())
+	rule := appRouter.RouterRule()
+	assert.Equal(t, "", rule.Scope)
+	assert.True(t, rule.Force)
+	assert.True(t, rule.Enabled)
+	assert.True(t, rule.Valid)
+
+	assert.Equal(t, testyml, rule.RawRule)
+	assert.Equal(t, false, rule.Runtime)
+	assert.Equal(t, false, rule.Dynamic)
+	assert.Equal(t, "", rule.Key)
+}
+
+func TestNewRouterChainURLNil(t *testing.T) {
+	chain, err := NewRouterChain(nil)
+	assert.NoError(t, err)
+	assert.NotNil(t, chain)
+}
+
+func TestRouterChain_AddRouters(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")
+	assert.NoError(t, err)
+
+	testyml := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+`
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	assert.Nil(t, err)
+	assert.Equal(t, 2, len(chain.routers))
+
+	url := getConditionRouteUrl("test-condition")
+	assert.NotNil(t, url)
+	factory := extension.GetRouterFactory(url.Protocol)
+	r, err := factory.NewRouter(url)
+	assert.Nil(t, err)
+	assert.NotNil(t, r)
+
+	routers := make([]router.Router, 0)
+	routers = append(routers, r)
+	chain.AddRouters(routers)
+	assert.Equal(t, 3, len(chain.routers))
+}
+
+func TestRouterChain_Route(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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(chain.routers))
+
+	url := getConditionRouteUrl("test-condition")
+	assert.NotNil(t, url)
+
+	invokers := []protocol.Invoker{}
+	dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService"))
+	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
+
+	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	inv := &invocation.RPCInvocation{}
+	finalInvokers := chain.Route(invokers, &targetURL, inv)
+
+	assert.Equal(t, 1, len(finalInvokers))
+}
+
+func TestRouterChain_Route_AppRouter(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")
+	assert.NoError(t, err)
+
+	testyml := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host = 1.1.1.1 => host != 1.2.3.4
+`
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	chain, err := NewRouterChain(getConditionRouteUrl("test-condition"))
+	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"))
+	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
+
+	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	inv := &invocation.RPCInvocation{}
+	finalInvokers := chain.Route(invokers, &targetURL, inv)
+
+	assert.Equal(t, 0, len(finalInvokers))
+}
+
+func TestRouterChain_Route_NoRoute(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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	chain, err := NewRouterChain(getConditionNoRouteUrl("test-condition"))
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(chain.routers))
+
+	url := getConditionRouteUrl("test-condition")
+	assert.NotNil(t, url)
+
+	invokers := []protocol.Invoker{}
+	dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService"))
+	invokers = append(invokers, protocol.NewBaseInvoker(dubboURL))
+
+	targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService"))
+	inv := &invocation.RPCInvocation{}
+	finalInvokers := chain.Route(invokers, &targetURL, inv)
+
+	assert.Equal(t, 0, len(finalInvokers))
+}
+
+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")
+	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")
+	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")
+	return &url
+}
diff --git a/cluster/router/condition/app_router.go b/cluster/router/condition/app_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..056e32851c11696c80d18a2a55b109fcdae06627
--- /dev/null
+++ b/cluster/router/condition/app_router.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 condition
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+const (
+	// Default priority for application router
+	appRouterDefaultPriority = int64(150)
+)
+
+// AppRouter For listen application router with config center
+type AppRouter struct {
+	listenableRouter
+}
+
+// NewAppRouter Init AppRouter by url
+func NewAppRouter(url *common.URL) (*AppRouter, error) {
+	if url == nil {
+		return nil, perrors.Errorf("No route URL for create app router!")
+	}
+	appRouter, err := newListenableRouter(url, url.GetParam(constant.APPLICATION_KEY, ""))
+	if err != nil {
+		return nil, err
+	}
+	appRouter.priority = appRouterDefaultPriority
+	return appRouter, nil
+}
diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bd817af36c8c144295479fb07ada9411f4115bbc
--- /dev/null
+++ b/cluster/router/condition/app_router_test.go
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package condition
+
+import (
+	"strconv"
+	"testing"
+	"time"
+)
+
+import (
+	_ "github.com/apache/dubbo-go/config_center/zookeeper"
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/config"
+	"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"
+)
+
+func TestNewAppRouter(t *testing.T) {
+
+	testYML := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+`
+	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
+	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	assert.NoError(t, err)
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, configuration)
+
+	appRouteURL := getAppRouteURL("test-condition")
+	appRouter, err := NewAppRouter(appRouteURL)
+	assert.Nil(t, err)
+	assert.NotNil(t, appRouter)
+
+	assert.NotNil(t, appRouter)
+	assert.NotNil(t, appRouter.RouterRule())
+	rule := appRouter.RouterRule()
+	assert.Equal(t, "", rule.Scope)
+	assert.True(t, rule.Force)
+	assert.True(t, rule.Enabled)
+	assert.True(t, rule.Valid)
+
+	assert.Equal(t, testYML, rule.RawRule)
+	assert.Equal(t, false, rule.Runtime)
+	assert.Equal(t, false, rule.Dynamic)
+	assert.Equal(t, "", rule.Key)
+	assert.Equal(t, 0, rule.Priority)
+}
+
+func TestGenerateConditions(t *testing.T) {
+
+	testYML := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+  - host = 192.168.199.208 => host = 192.168.199.208 
+`
+	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
+	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	assert.NoError(t, err)
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, configuration)
+
+	appRouteURL := getAppRouteURL("test-condition")
+	appRouter, err := NewAppRouter(appRouteURL)
+	assert.Nil(t, err)
+	assert.NotNil(t, appRouter)
+
+	rule, err := Parse(testYML)
+	assert.Nil(t, err)
+	appRouter.generateConditions(rule)
+
+	assert.Equal(t, 2, len(appRouter.conditionRouters))
+}
+
+func TestProcess(t *testing.T) {
+
+	testYML := `enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+`
+	ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second)
+	assert.NoError(t, err)
+	err = z.Create("/dubbo/config/dubbo/test-condition.condition-router")
+	assert.NoError(t, err)
+
+	_, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []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)
+	config.GetEnvInstance().SetDynamicConfiguration(configuration)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, configuration)
+
+	appRouteURL := getAppRouteURL("test-condition")
+	appRouter, err := NewAppRouter(appRouteURL)
+	assert.Nil(t, err)
+	assert.NotNil(t, appRouter)
+
+	assert.Equal(t, 1, len(appRouter.conditionRouters))
+
+	testNewYML := `
+enabled: true
+force: true
+runtime: false
+conditions:
+  - => host != 172.22.3.91
+  - host = 192.168.199.208 => host = 192.168.199.208
+`
+
+	appRouter.Process(&config_center.ConfigChangeEvent{ConfigType: remoting.EventTypeDel})
+
+	assert.Equal(t, 0, len(appRouter.conditionRouters))
+
+	appRouter.Process(&config_center.ConfigChangeEvent{Value: testNewYML, ConfigType: remoting.EventTypeAdd})
+
+	assert.Equal(t, 2, len(appRouter.conditionRouters))
+}
+
+func getAppRouteURL(applicationKey string) *common.URL {
+	url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService")
+	url.AddParam("application", applicationKey)
+	url.AddParam("force", "true")
+	return &url
+}
diff --git a/cluster/router/condition/factory.go b/cluster/router/condition/factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..66512a138706e9b9ab565f7537a15c37a75deefd
--- /dev/null
+++ b/cluster/router/condition/factory.go
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package condition
+
+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.ConditionRouterName, newConditionRouterFactory)
+	extension.SetRouterFactory(constant.ConditionAppRouterName, newAppRouterFactory)
+}
+
+// ConditionRouterFactory Condition router factory
+type ConditionRouterFactory struct{}
+
+func newConditionRouterFactory() router.RouterFactory {
+	return &ConditionRouterFactory{}
+}
+
+// NewRouter Create ConditionRouterFactory by URL
+func (c *ConditionRouterFactory) NewRouter(url *common.URL) (router.Router, error) {
+	return NewConditionRouter(url)
+}
+
+// NewRouter Create FileRouterFactory by Content
+func (c *ConditionRouterFactory) NewFileRouter(content []byte) (router.Router, error) {
+	return NewFileConditionRouter(content)
+}
+
+// AppRouterFactory Application router factory
+type AppRouterFactory struct{}
+
+func newAppRouterFactory() router.RouterFactory {
+	return &AppRouterFactory{}
+}
+
+// NewRouter Create AppRouterFactory by URL
+func (c *AppRouterFactory) NewRouter(url *common.URL) (router.Router, error) {
+	return NewAppRouter(url)
+}
diff --git a/cluster/router/condition_router_test.go b/cluster/router/condition/factory_test.go
similarity index 74%
rename from cluster/router/condition_router_test.go
rename to cluster/router/condition/factory_test.go
index fc639b9fc77b3b83d01c663fa02a7185a2d03756..99cec34096a55d3c2a967b63afdf5f6d0a77279a 100644
--- a/cluster/router/condition_router_test.go
+++ b/cluster/router/condition/factory_test.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package router
+package condition
 
 import (
 	"context"
@@ -119,33 +119,33 @@ func (bi *MockInvoker) Destroy() {
 func TestRoute_matchWhen(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4"))
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
 	cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService")
-	matchWhen, _ := router.(*ConditionRouter).MatchWhen(cUrl, inv)
+	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().Router(getRouteUrl(rule1))
-	matchWhen1, _ := router1.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router1, _ := newConditionRouterFactory().NewRouter(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().Router(getRouteUrl(rule2))
-	matchWhen2, _ := router2.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router2, _ := newConditionRouterFactory().NewRouter(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().Router(getRouteUrl(rule3))
-	matchWhen3, _ := router3.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router3, _ := newConditionRouterFactory().NewRouter(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().Router(getRouteUrl(rule4))
-	matchWhen4, _ := router4.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router4, _ := newConditionRouterFactory().NewRouter(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().Router(getRouteUrl(rule5))
-	matchWhen5, _ := router5.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router5, _ := newConditionRouterFactory().NewRouter(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().Router(getRouteUrl(rule6))
-	matchWhen6, _ := router6.(*ConditionRouter).MatchWhen(cUrl, inv)
+	router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6))
+	matchWhen6 := router6.(*ConditionRouter).MatchWhen(&cUrl, inv)
 	assert.Equal(t, true, matchWhen6)
 }
 
@@ -162,19 +162,19 @@ func TestRoute_matchFilter(t *testing.T) {
 	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().Router(getRouteUrl(rule1))
-	router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2))
-	router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3))
-	router4, _ := NewConditionRouterFactory().Router(getRouteUrl(rule4))
-	router5, _ := NewConditionRouterFactory().Router(getRouteUrl(rule5))
-	router6, _ := NewConditionRouterFactory().Router(getRouteUrl(rule6))
+	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")
-	fileredInvokers1 := router1.Route(invokers, cUrl, &invocation.RPCInvocation{})
-	fileredInvokers2 := router2.Route(invokers, cUrl, &invocation.RPCInvocation{})
-	fileredInvokers3 := router3.Route(invokers, cUrl, &invocation.RPCInvocation{})
-	fileredInvokers4 := router4.Route(invokers, cUrl, &invocation.RPCInvocation{})
-	fileredInvokers5 := router5.Route(invokers, cUrl, &invocation.RPCInvocation{})
-	fileredInvokers6 := router6.Route(invokers, cUrl, &invocation.RPCInvocation{})
+	fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{})
+	fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{})
+	fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{})
+	fileredInvokers4 := router4.Route(invokers, &cUrl, &invocation.RPCInvocation{})
+	fileredInvokers5 := router5.Route(invokers, &cUrl, &invocation.RPCInvocation{})
+	fileredInvokers6 := router6.Route(invokers, &cUrl, &invocation.RPCInvocation{})
 	assert.Equal(t, 1, len(fileredInvokers1))
 	assert.Equal(t, 0, len(fileredInvokers2))
 	assert.Equal(t, 0, len(fileredInvokers3))
@@ -187,22 +187,22 @@ func TestRoute_matchFilter(t *testing.T) {
 func TestRoute_methodRoute(t *testing.T) {
 	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{}))
 	rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4"))
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
 	url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo")
-	matchWhen, _ := router.(*ConditionRouter).MatchWhen(url, inv)
+	matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv)
 	assert.Equal(t, true, matchWhen)
 	url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo")
-	matchWhen, _ = router.(*ConditionRouter).MatchWhen(url1, inv)
+	matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv)
 	assert.Equal(t, true, matchWhen)
 	url2, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo")
 	rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4"))
-	router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2))
-	matchWhen, _ = router2.(*ConditionRouter).MatchWhen(url2, inv)
+	router2, _ := newConditionRouterFactory().NewRouter(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")
 	rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4"))
-	router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3))
-	matchWhen, _ = router3.(*ConditionRouter).MatchWhen(url3, inv)
+	router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3))
+	matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv)
 	assert.Equal(t, true, matchWhen)
 
 }
@@ -214,8 +214,8 @@ func TestRoute_ReturnFalse(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false"))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
 }
 
@@ -226,19 +226,24 @@ func TestRoute_ReturnEmpty(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => "))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
 }
 
 func TestRoute_ReturnAll(t *testing.T) {
 	localIP, _ := gxnet.GetLocalIP()
-	invokers := []protocol.Invoker{&MockInvoker{}, &MockInvoker{}, &MockInvoker{}}
+	urlString := "dubbo://" + localIP + "/com.foo.BarService"
+	dubboURL, _ := common.NewURL(urlString)
+	mockInvoker1 := NewMockInvoker(dubboURL, 1)
+	mockInvoker2 := NewMockInvoker(dubboURL, 1)
+	mockInvoker3 := NewMockInvoker(dubboURL, 1)
+	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().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, invokers, fileredInvokers)
 }
 
@@ -254,8 +259,8 @@ func TestRoute_HostFilter(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
 	assert.Equal(t, invoker2, fileredInvokers[0])
 	assert.Equal(t, invoker3, fileredInvokers[1])
@@ -273,8 +278,8 @@ func TestRoute_Empty_HostFilter(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
 	assert.Equal(t, invoker2, fileredInvokers[0])
 	assert.Equal(t, invoker3, fileredInvokers[1])
@@ -292,8 +297,8 @@ func TestRoute_False_HostFilter(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
 	assert.Equal(t, invoker2, fileredInvokers[0])
 	assert.Equal(t, invoker3, fileredInvokers[1])
@@ -311,8 +316,8 @@ func TestRoute_Placeholder(t *testing.T) {
 	inv := &invocation.RPCInvocation{}
 	rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host"))
 	curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService")
-	router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 2, len(fileredInvokers))
 	assert.Equal(t, invoker2, fileredInvokers[0])
 	assert.Equal(t, invoker3, fileredInvokers[1])
@@ -330,8 +335,8 @@ func TestRoute_NoForce(t *testing.T) {
 	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().Router(getRouteUrlWithNoForce(rule))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithNoForce(rule))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, invokers, fileredInvokers)
 }
 
@@ -347,7 +352,17 @@ func TestRoute_Force(t *testing.T) {
 	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().Router(getRouteUrlWithForce(rule, "true"))
-	fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv)
+	router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithForce(rule, "true"))
+	fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv)
 	assert.Equal(t, 0, len(fileredInvokers))
 }
+
+func TestNewConditionRouterFactory(t *testing.T) {
+	factory := newConditionRouterFactory()
+	assert.NotNil(t, factory)
+}
+
+func TestNewAppRouterFactory(t *testing.T) {
+	factory := newAppRouterFactory()
+	assert.NotNil(t, factory)
+}
diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go
new file mode 100644
index 0000000000000000000000000000000000000000..efeec53efc840d93c4b6906adfd19820a57b36fd
--- /dev/null
+++ b/cluster/router/condition/file.go
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package condition
+
+import (
+	"encoding/base64"
+	"net/url"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+)
+
+// FileConditionRouter Use for parse config file of condition router
+type FileConditionRouter struct {
+	listenableRouter
+	parseOnce sync.Once
+	url       common.URL
+}
+
+// NewFileConditionRouter Create file condition router instance with content ( from config file)
+func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) {
+	fileRouter := &FileConditionRouter{}
+	rule, err := Parse(string(content))
+	if err != nil {
+		return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err))
+	}
+
+	if !rule.Valid {
+		return nil, perrors.Errorf("rule content is not verify for condition router , error:%v", perrors.WithStack(err))
+	}
+
+	fileRouter.generateConditions(rule)
+
+	return fileRouter, nil
+}
+
+// URL Return URL in file condition router n
+func (f *FileConditionRouter) URL() common.URL {
+	f.parseOnce.Do(func() {
+		routerRule := f.routerRule
+		rule := parseCondition(routerRule.Conditions)
+		f.url = *common.NewURLWithOptions(
+			common.WithProtocol(constant.CONDITION_ROUTE_PROTOCOL),
+			common.WithIp(constant.ANYHOST_VALUE),
+			common.WithParams(url.Values{}),
+			common.WithParamsValue(constant.RouterForce, strconv.FormatBool(routerRule.Force)),
+			common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)),
+			common.WithParamsValue(constant.RULE_KEY, base64.URLEncoding.EncodeToString([]byte(rule))),
+			common.WithParamsValue(constant.ROUTER_KEY, constant.CONDITION_ROUTE_PROTOCOL),
+			common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY))
+	})
+	return f.url
+}
+
+func parseCondition(conditions []string) string {
+	var (
+		when string
+		then string
+	)
+	for _, condition := range conditions {
+		condition = strings.Trim(condition, " ")
+		if strings.Contains(condition, "=>") {
+			array := strings.SplitN(condition, "=>", 2)
+			consumer := strings.Trim(array[0], " ")
+			provider := strings.Trim(array[1], " ")
+			if len(consumer) != 0 {
+				if len(when) != 0 {
+					when = strings.Join([]string{when, consumer}, " & ")
+				} else {
+					when = consumer
+				}
+			}
+			if len(provider) != 0 {
+				if len(then) != 0 {
+					then = strings.Join([]string{then, provider}, " & ")
+				} else {
+					then = provider
+				}
+			}
+
+		}
+
+	}
+
+	return strings.Join([]string{when, then}, " => ")
+}
diff --git a/cluster/router/condition/file_test.go b/cluster/router/condition/file_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3092b12ff88dcacc9108c7cdd46ba1ac9f74eb2b
--- /dev/null
+++ b/cluster/router/condition/file_test.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 condition
+
+import (
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLoadYmlConfig(t *testing.T) {
+	router, e := NewFileConditionRouter([]byte(`priority: 1
+force: true
+conditions :
+  - "a => b"
+  - "c => d"`))
+	assert.Nil(t, e)
+	assert.NotNil(t, router)
+	assert.Equal(t, router.routerRule.Priority, 1)
+	assert.Equal(t, router.routerRule.Force, true)
+	assert.Equal(t, len(router.routerRule.Conditions), 2)
+}
+
+func TestParseCondition(t *testing.T) {
+	s := make([]string, 2)
+	s = append(s, "a => b")
+	s = append(s, "c => d")
+	condition := parseCondition(s)
+	assert.Equal(t, "a & c => b & d", condition)
+}
+
+func TestFileRouterURL(t *testing.T) {
+	router, e := NewFileConditionRouter([]byte(`priority: 1
+force: true
+conditions :
+  - "a => b"
+  - "c => d"`))
+	assert.Nil(t, e)
+	assert.NotNil(t, router)
+	assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D", router.URL().String())
+}
diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba2fbb0eb2f482dfde215c1b078ecad60e66bc14
--- /dev/null
+++ b/cluster/router/condition/listenable_router.go
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package condition
+
+import (
+	"fmt"
+)
+
+import (
+	perrors "github.com/pkg/errors"
+)
+
+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/logger"
+	"github.com/apache/dubbo-go/config_center"
+	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/remoting"
+)
+
+const (
+	// Default priority for listenable router, use the maximum int64 value
+	listenableRouterDefaultPriority = ^int64(0)
+)
+
+// listenableRouter Abstract router which listens to dynamic configuration
+type listenableRouter struct {
+	conditionRouters []*ConditionRouter
+	routerRule       *RouterRule
+	url              *common.URL
+	force            bool
+	priority         int64
+}
+
+// RouterRule Get RouterRule instance from listenableRouter
+func (l *listenableRouter) RouterRule() *RouterRule {
+	return l.routerRule
+}
+
+func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) {
+	if ruleKey == "" {
+		return nil, perrors.Errorf("NewListenableRouter ruleKey is nil, can't create Listenable router")
+	}
+	l := &AppRouter{}
+
+	l.url = url
+	l.priority = listenableRouterDefaultPriority
+
+	routerKey := ruleKey + constant.ConditionRouterRuleSuffix
+	//add listener
+	dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration()
+	if dynamicConfiguration == nil {
+		return nil, perrors.Errorf("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please")
+	}
+
+	dynamicConfiguration.AddListener(routerKey, l)
+	//get rule
+	rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP))
+	if len(rule) == 0 || err != nil {
+		return nil, perrors.Errorf("Get rule fail, config rule{%s},  error{%v}", rule, err)
+	}
+	l.Process(&config_center.ConfigChangeEvent{
+		Key:        routerKey,
+		Value:      rule,
+		ConfigType: remoting.EventTypeUpdate})
+
+	logger.Info("Init app router success")
+	return l, nil
+}
+
+// Process Process config change event , generate routers and set them to the listenableRouter instance
+func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) {
+	logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value)
+	if remoting.EventTypeDel == event.ConfigType {
+		l.routerRule = nil
+		if l.conditionRouters != nil {
+			l.conditionRouters = l.conditionRouters[:0]
+		}
+		return
+	}
+	content, ok := event.Value.(string)
+	if !ok {
+		msg := fmt.Sprintf("Convert event content fail,raw content:[%s] ", event.Value)
+		logger.Error(msg)
+		return
+	}
+
+	routerRule, err := Parse(content)
+	if err != nil {
+		logger.Errorf("Parse condition router rule fail,error:[%s] ", err)
+		return
+	}
+	l.generateConditions(routerRule)
+}
+
+func (l *listenableRouter) generateConditions(rule *RouterRule) {
+	if rule == nil || !rule.Valid {
+		return
+	}
+	l.conditionRouters = make([]*ConditionRouter, 0, len(rule.Conditions))
+	l.routerRule = rule
+	for _, c := range rule.Conditions {
+		router, e := NewConditionRouterWithRule(c)
+		if e != nil {
+			logger.Errorf("Create condition router with rule fail,raw rule:[%s] ", c)
+			continue
+		}
+		router.Force = rule.Force
+		router.enabled = rule.Enabled
+		l.conditionRouters = append(l.conditionRouters, router)
+	}
+}
+
+// Route Determine the target invokers list.
+func (l *listenableRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if len(invokers) == 0 || len(l.conditionRouters) == 0 {
+		return invokers
+	}
+	//We will check enabled status inside each router.
+	for _, r := range l.conditionRouters {
+		invokers = r.Route(invokers, url, invocation)
+	}
+	return invokers
+}
+
+// Priority Return Priority in listenable router
+func (l *listenableRouter) Priority() int64 {
+	return l.priority
+}
+
+// URL Return URL in listenable router
+func (l *listenableRouter) URL() common.URL {
+	return *l.url
+}
diff --git a/cluster/router/condition_router.go b/cluster/router/condition/router.go
similarity index 63%
rename from cluster/router/condition_router.go
rename to cluster/router/condition/router.go
index c38e9718cca6d5e67a874a61225bac7d27d5d352..c5d46444bde921386d14a8be7eb0a89d855f8ece 100644
--- a/cluster/router/condition_router.go
+++ b/cluster/router/condition/router.go
@@ -15,57 +15,55 @@
  * limitations under the License.
  */
 
-package router
+package condition
 
 import (
-	"reflect"
 	"regexp"
 	"strings"
 )
 
 import (
-	gxset "github.com/dubbogo/gost/container/set"
-	gxnet "github.com/dubbogo/gost/net"
 	perrors "github.com/pkg/errors"
 )
 
 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"
 	"github.com/apache/dubbo-go/protocol"
+	"github.com/dubbogo/gost/container/set"
+	"github.com/dubbogo/gost/net"
 )
 
 const (
-	//ROUTE_PATTERN route pattern regex
-	ROUTE_PATTERN = `([&!=,]*)\\s*([^&!=,\\s]+)`
-	// FORCE ...
-	FORCE = "force"
-	// PRIORITY ...
-	PRIORITY = "priority"
+	//pattern route pattern regex
+	pattern = `([&!=,]*)\\s*([^&!=,\\s]+)`
 )
 
-//ConditionRouter condition router struct
+var (
+	routerPatternReg = regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`)
+)
+
+// ConditionRouter Condition router struct
 type ConditionRouter struct {
 	Pattern       string
-	Url           *common.URL
-	Priority      int64
+	url           *common.URL
+	priority      int64
 	Force         bool
+	enabled       bool
 	WhenCondition map[string]MatchPair
 	ThenCondition map[string]MatchPair
 }
 
-func newConditionRouter(url *common.URL) (*ConditionRouter, error) {
+// NewConditionRouterWithRule Init condition router by raw rule
+func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) {
 	var (
 		whenRule string
 		thenRule string
 		when     map[string]MatchPair
 		then     map[string]MatchPair
 	)
-	rule, err := url.GetParamAndDecoded(constant.RULE_KEY)
-	if err != nil || len(rule) == 0 {
-		return nil, perrors.Errorf("Illegal route rule!")
-	}
 	rule = strings.Replace(rule, "consumer.", "", -1)
 	rule = strings.Replace(rule, "provider.", "", -1)
 	i := strings.Index(rule, "=>")
@@ -98,31 +96,61 @@ func newConditionRouter(url *common.URL) (*ConditionRouter, error) {
 		then = t
 	}
 	return &ConditionRouter{
-		ROUTE_PATTERN,
-		url,
-		url.GetParamInt(PRIORITY, 0),
-		url.GetParamBool(FORCE, false),
-		when,
-		then,
+		Pattern:       pattern,
+		WhenCondition: when,
+		ThenCondition: then,
 	}, nil
 }
 
-// Route
-// Router determine the target server list.
-func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, invocation protocol.Invocation) []protocol.Invoker {
-	if len(invokers) == 0 {
-		return invokers
+// NewConditionRouter Init condition router by URL
+func NewConditionRouter(url *common.URL) (*ConditionRouter, error) {
+	if url == nil {
+		return nil, perrors.Errorf("Illegal route URL!")
+	}
+	rule, err := url.GetParamAndDecoded(constant.RULE_KEY)
+	if err != nil || len(rule) == 0 {
+		return nil, perrors.Errorf("Illegal route rule!")
 	}
-	isMatchWhen, err := c.MatchWhen(url, invocation)
+
+	router, err := NewConditionRouterWithRule(rule)
 	if err != nil {
+		return nil, err
+	}
 
-		var urls []string
-		for _, invo := range invokers {
-			urls = append(urls, reflect.TypeOf(invo).String())
-		}
-		logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err)
+	router.url = url
+	router.priority = url.GetParamInt(constant.RouterPriority, 0)
+	router.Force = url.GetParamBool(constant.RouterForce, false)
+	router.enabled = url.GetParamBool(constant.RouterEnabled, true)
+
+	return router, nil
+}
+
+// Priority Return Priority in condition router
+func (c *ConditionRouter) Priority() int64 {
+	return c.priority
+}
+
+// URL Return URL in condition router
+func (c *ConditionRouter) URL() common.URL {
+	return *c.url
+}
+
+// Enabled Return is condition router is enabled
+// true: enabled
+// false: disabled
+func (c *ConditionRouter) Enabled() bool {
+	return c.enabled
+}
+
+// Route Determine the target invokers list.
+func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if !c.Enabled() {
+		return invokers
+	}
+	if len(invokers) == 0 {
 		return invokers
 	}
+	isMatchWhen := c.MatchWhen(url, invocation)
 	if !isMatchWhen {
 		return invokers
 	}
@@ -130,17 +158,9 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv
 	if len(c.ThenCondition) == 0 {
 		return result
 	}
-	localIP, _ := gxnet.GetLocalIP()
 	for _, invoker := range invokers {
-		isMatchThen, err := c.MatchThen(invoker.GetUrl(), url)
-		if err != nil {
-			var urls []string
-			for _, invo := range invokers {
-				urls = append(urls, reflect.TypeOf(invo).String())
-			}
-			logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err)
-			return invokers
-		}
+		invokerUrl := invoker.GetUrl()
+		isMatchThen := c.MatchThen(&invokerUrl, url)
 		if isMatchThen {
 			result = append(result, invoker)
 		}
@@ -149,6 +169,7 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv
 		return result
 	} else if c.Force {
 		rule, _ := url.GetParamAndDecoded(constant.RULE_KEY)
+		localIP, _ := gxnet.GetLocalIP()
 		logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule)
 		return result
 	}
@@ -162,15 +183,10 @@ func parseRule(rule string) (map[string]MatchPair, error) {
 	}
 
 	var (
-		pair       MatchPair
-		startIndex int
+		pair MatchPair
 	)
 	values := gxset.NewSet()
-	reg := regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`)
-	if indexTuple := reg.FindIndex([]byte(rule)); len(indexTuple) > 0 {
-		startIndex = indexTuple[0]
-	}
-	matches := reg.FindAllSubmatch([]byte(rule), -1)
+	matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1)
 	for _, groups := range matches {
 		separator := string(groups[1])
 		content := string(groups[2])
@@ -193,22 +209,26 @@ func parseRule(rule string) (map[string]MatchPair, error) {
 			}
 		case "=":
 			if &pair == nil {
+				var startIndex = getStartIndex(rule)
 				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
 			}
 			values = pair.Matches
 			values.Add(content)
 		case "!=":
 			if &pair == nil {
+				var startIndex = getStartIndex(rule)
 				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
 			}
 			values = pair.Mismatches
 			values.Add(content)
 		case ",":
 			if values.Empty() {
+				var startIndex = getStartIndex(rule)
 				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
 			}
 			values.Add(content)
 		default:
+			var startIndex = getStartIndex(rule)
 			return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
 
 		}
@@ -216,23 +236,31 @@ func parseRule(rule string) (map[string]MatchPair, error) {
 	return condition, nil
 }
 
-//MatchWhen  MatchWhen
-func (c *ConditionRouter) MatchWhen(url common.URL, invocation protocol.Invocation) (bool, error) {
-	condition, err := MatchCondition(c.WhenCondition, &url, nil, invocation)
-	return len(c.WhenCondition) == 0 || condition, err
+func getStartIndex(rule string) int {
+	if indexTuple := routerPatternReg.FindIndex([]byte(rule)); len(indexTuple) > 0 {
+		return indexTuple[0]
+	}
+	return -1
 }
 
-//MatchThen MatchThen
-func (c *ConditionRouter) MatchThen(url common.URL, param common.URL) (bool, error) {
-	condition, err := MatchCondition(c.ThenCondition, &url, &param, nil)
-	return len(c.ThenCondition) > 0 && condition, err
+// MatchWhen MatchWhen
+func (c *ConditionRouter) MatchWhen(url *common.URL, invocation protocol.Invocation) bool {
+	condition := matchCondition(c.WhenCondition, url, nil, invocation)
+	return len(c.WhenCondition) == 0 || condition
 }
 
-//MatchCondition MatchCondition
-func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) (bool, error) {
+// MatchThen MatchThen
+func (c *ConditionRouter) MatchThen(url *common.URL, param *common.URL) bool {
+	condition := matchCondition(c.ThenCondition, url, param, nil)
+	return len(c.ThenCondition) > 0 && condition
+}
+
+// MatchCondition MatchCondition
+func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) bool {
 	sample := url.ToMap()
 	if sample == nil {
-		return true, perrors.Errorf("url is not allowed be nil")
+		// because url.ToMap() may return nil, but it should continue to process make condition
+		sample = make(map[string]string)
 	}
 	var result bool
 	for key, matchPair := range pairs {
@@ -248,22 +276,22 @@ func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.U
 		}
 		if len(sampleValue) > 0 {
 			if !matchPair.isMatch(sampleValue, param) {
-				return false, nil
+				return false
 			}
 
 			result = true
 		} else {
 			if !(matchPair.Matches.Empty()) {
-				return false, nil
+				return false
 			}
 
 			result = true
 		}
 	}
-	return result, nil
+	return result
 }
 
-// MatchPair ...
+// MatchPair Match key pair , condition process
 type MatchPair struct {
 	Matches    *gxset.HashSet
 	Mismatches *gxset.HashSet
@@ -273,7 +301,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool {
 	if !pair.Matches.Empty() && pair.Mismatches.Empty() {
 
 		for match := range pair.Matches.Items {
-			if isMatchGlobPattern(match.(string), value, param) {
+			if matcher.IsMatchGlobalPattern(match.(string), value, param) {
 				return true
 			}
 		}
@@ -282,20 +310,21 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool {
 	if !pair.Mismatches.Empty() && pair.Matches.Empty() {
 
 		for mismatch := range pair.Mismatches.Items {
-			if isMatchGlobPattern(mismatch.(string), value, param) {
+			if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) {
 				return false
 			}
 		}
 		return true
 	}
 	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 isMatchGlobPattern(mismatch.(string), value, param) {
+			if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) {
 				return false
 			}
 		}
 		for match := range pair.Matches.Items {
-			if isMatchGlobPattern(match.(string), value, param) {
+			if matcher.IsMatchGlobalPattern(match.(string), value, param) {
 				return true
 			}
 		}
@@ -303,31 +332,3 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool {
 	}
 	return false
 }
-
-func isMatchGlobPattern(pattern string, value string, param *common.URL) bool {
-	if param != nil && strings.HasPrefix(pattern, "$") {
-		pattern = param.GetRawParam(pattern[1:])
-	}
-	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:
-		return value == pattern
-	case len(pattern) - 1:
-		return strings.HasPrefix(value, pattern[0:i])
-	case 0:
-		return strings.HasSuffix(value, pattern[:i+1])
-	default:
-		prefix := pattern[0:1]
-		suffix := pattern[i+1:]
-		return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix)
-	}
-}
diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go
new file mode 100644
index 0000000000000000000000000000000000000000..1374cf9de2585f78a27e3de99f356c6900268927
--- /dev/null
+++ b/cluster/router/condition/router_rule.go
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package condition
+
+import (
+	"gopkg.in/yaml.v2"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+)
+
+// RouterRule RouterRule config read from config file or config center
+type RouterRule struct {
+	router.BaseRouterRule `yaml:",inline""`
+	Conditions            []string
+}
+
+/* Parse Router raw rule parser
+ * example :
+ * scope: application
+ * runtime: true
+ * force: false
+ * conditions:
+ *   - >
+ *     method!=sayHello =>
+ *   - >
+ *     ip=127.0.0.1
+ *     =>
+ *     1.1.1.1
+ */
+func Parse(rawRule string) (*RouterRule, error) {
+	r := &RouterRule{}
+	err := yaml.Unmarshal([]byte(rawRule), r)
+	if err != nil {
+		return r, err
+	}
+	r.RawRule = rawRule
+	if len(r.Conditions) != 0 {
+		r.Valid = true
+	}
+
+	return r, nil
+}
diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5acc7283917a7aa662b60cd90daba89d312db0cd
--- /dev/null
+++ b/cluster/router/condition/router_rule_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 condition
+
+import (
+	"testing"
+)
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParse(t *testing.T) {
+	testyml := `
+scope: application
+runtime: true
+force: false
+conditions:
+  - >
+    method!=sayHello =>
+  - >
+    ip=127.0.0.1
+    =>
+    1.1.1.1`
+	rule, e := Parse(testyml)
+
+	assert.Nil(t, e)
+	assert.NotNil(t, rule)
+	assert.Equal(t, 2, len(rule.Conditions))
+	assert.Equal(t, "application", rule.Scope)
+	assert.True(t, rule.Runtime)
+	assert.Equal(t, false, rule.Force)
+	assert.Equal(t, testyml, rule.RawRule)
+	assert.True(t, true, rule.Valid)
+	assert.Equal(t, false, rule.Enabled)
+	assert.Equal(t, false, rule.Dynamic)
+	assert.Equal(t, "", rule.Key)
+}
diff --git a/cluster/router.go b/cluster/router/health_checker.go
similarity index 67%
rename from cluster/router.go
rename to cluster/router/health_checker.go
index 589eb9a2696e5772070a94e8c764c78c8e0ca8a2..d9e3087a272dd500cdd1dc9dc6680d436891f88b 100644
--- a/cluster/router.go
+++ b/cluster/router/health_checker.go
@@ -15,31 +15,14 @@
  * limitations under the License.
  */
 
-package cluster
+package router
 
 import (
-	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/protocol"
 )
 
-// Extension - Router
-
-// RouterFactory ...
-type RouterFactory interface {
-	Router(*common.URL) (Router, error)
-}
-
-// Router ...
-type Router interface {
-	Route([]protocol.Invoker, common.URL, protocol.Invocation) []protocol.Invoker
-}
-
-// RouterChain ...
-type RouterChain struct {
-	routers []Router
-}
-
-// NewRouterChain ...
-func NewRouterChain(url common.URL) {
-
+// HealthChecker is used to determine whether the invoker is healthy or not
+type HealthChecker interface {
+	// IsHealthy evaluates the healthy state on the given Invoker
+	IsHealthy(invoker protocol.Invoker) bool
 }
diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go
new file mode 100644
index 0000000000000000000000000000000000000000..a26f86ddac45aa6e999cd4453aa296d0786a02ba
--- /dev/null
+++ b/cluster/router/healthcheck/default_health_check.go
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package healthcheck
+
+import (
+	"math"
+)
+
+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"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+func init() {
+	extension.SethealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker)
+}
+
+// DefaultHealthChecker is the default implementation of HealthChecker, which determines the health status of
+// the invoker based on the number of successive bad request and the current active request.
+type DefaultHealthChecker struct {
+	// the limit of outstanding request
+	outStandingRequestConutLimit int32
+	// the threshold of successive-failure-request
+	requestSuccessiveFailureThreshold int32
+	// value of circuit-tripped timeout factor
+	circuitTrippedTimeoutFactor int32
+}
+
+// IsHealthy evaluates the healthy state on the given Invoker based on the number of successive bad request
+// and the current active request
+func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool {
+	urlStatus := protocol.GetURLStatus(invoker.GetUrl())
+	if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestConutLimit() {
+		logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key())
+		return false
+	}
+	return true
+}
+
+// isCircuitBreakerTripped determine whether the invoker is in the tripped state by the number of successive bad request
+func (c *DefaultHealthChecker) isCircuitBreakerTripped(status *protocol.RPCStatus) bool {
+	circuitBreakerTimeout := c.getCircuitBreakerTimeout(status)
+	currentTime := protocol.CurrentTimeMillis()
+	if circuitBreakerTimeout <= 0 {
+		return false
+	}
+	return circuitBreakerTimeout > currentTime
+}
+
+// getCircuitBreakerTimeout get the timestamp recovered from tripped state, the unit is millisecond
+func (c *DefaultHealthChecker) getCircuitBreakerTimeout(status *protocol.RPCStatus) int64 {
+	sleepWindow := c.getCircuitBreakerSleepWindowTime(status)
+	if sleepWindow <= 0 {
+		return 0
+	}
+	return status.GetLastRequestFailedTimestamp() + sleepWindow
+}
+
+// getCircuitBreakerSleepWindowTime get the sleep window time of invoker, the unit is millisecond
+func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol.RPCStatus) int64 {
+
+	successiveFailureCount := status.GetSuccessiveRequestFailureCount()
+	diff := successiveFailureCount - c.GetRequestSuccessiveFailureThreshold()
+	if diff < 0 {
+		return 0
+	} else if diff > constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF {
+		diff = constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF
+	}
+	sleepWindow := (1 << diff) * c.GetCircuitTrippedTimeoutFactor()
+	if sleepWindow > constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS {
+		sleepWindow = constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS
+	}
+	return int64(sleepWindow)
+}
+
+// GetOutStandingRequestConutLimit return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker
+func (c *DefaultHealthChecker) GetRequestSuccessiveFailureThreshold() int32 {
+	return c.requestSuccessiveFailureThreshold
+}
+
+// GetOutStandingRequestConutLimit return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker
+func (c *DefaultHealthChecker) GetCircuitTrippedTimeoutFactor() int32 {
+	return c.circuitTrippedTimeoutFactor
+}
+
+// GetOutStandingRequestConutLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker
+func (c *DefaultHealthChecker) GetOutStandingRequestConutLimit() int32 {
+	return c.outStandingRequestConutLimit
+}
+
+// NewDefaultHealthChecker constructs a new DefaultHealthChecker based on the url
+func NewDefaultHealthChecker(url *common.URL) router.HealthChecker {
+	return &DefaultHealthChecker{
+		outStandingRequestConutLimit:      int32(url.GetParamInt(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, math.MaxInt32)),
+		requestSuccessiveFailureThreshold: int32(url.GetParamInt(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF)),
+		circuitTrippedTimeoutFactor:       int32(url.GetParamInt(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR)),
+	}
+}
diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..74aa3940743a012f907cfe3d8811a618f07ff800
--- /dev/null
+++ b/cluster/router/healthcheck/default_health_check_test.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 healthcheck
+
+import (
+	"math"
+	"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/protocol"
+)
+
+func TestDefaultHealthChecker_IsHealthy(t *testing.T) {
+
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
+	invoker := NewMockInvoker(url, 1)
+	healthy := hc.IsHealthy(invoker)
+	assert.True(t, healthy)
+
+	url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10")
+	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)
+	}
+	hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
+	healthy = hc.IsHealthy(invoker)
+	// the outgoing request is more than OUTSTANDING_REQUEST_COUNT_LIMIT, go to unhealthy
+	assert.False(t, hc.IsHealthy(invoker))
+
+	// 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)
+	}
+	url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10")
+	url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000")
+	hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
+	healthy = hc.IsHealthy(invoker)
+	assert.False(t, hc.IsHealthy(invoker))
+
+	// reset successive failed count and go to healthy
+	request(url, "test", 0, false, true)
+	healthy = hc.IsHealthy(invoker)
+	assert.True(t, hc.IsHealthy(invoker))
+}
+
+func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) {
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker)
+	// Increase the number of failed requests
+	for i := 0; i < 100; i++ {
+		request(url, "test", 1, false, false)
+	}
+	sleepWindowTime := defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url))
+	assert.True(t, sleepWindowTime == constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS)
+
+	// Adjust the threshold size to 1000
+	url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "1000")
+	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")
+	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)
+	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) {
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	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)
+	timeout = defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url1))
+	// timeout must after the current time
+	assert.True(t, timeout > protocol.CurrentTimeMillis())
+
+}
+
+func TestDefaultHealthChecker_isCircuitBreakerTripped(t *testing.T) {
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	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)
+	}
+	tripped = defaultHc.isCircuitBreakerTripped(protocol.GetURLStatus(url))
+	assert.True(t, tripped)
+
+}
+
+func TestNewDefaultHealthChecker(t *testing.T) {
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	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.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10")
+	url1.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10")
+	nondefaultHc := NewDefaultHealthChecker(&url1).(*DefaultHealthChecker)
+	assert.NotNil(t, nondefaultHc)
+	assert.Equal(t, nondefaultHc.outStandingRequestConutLimit, int32(10))
+	assert.Equal(t, nondefaultHc.requestSuccessiveFailureThreshold, int32(10))
+}
+
+func request(url common.URL, method string, elapsed int64, active, succeeded bool) {
+	protocol.BeginCount(url, method)
+	if !active {
+		protocol.EndCount(url, method, elapsed, succeeded)
+	}
+}
diff --git a/cluster/router/healthcheck/factory.go b/cluster/router/healthcheck/factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..32d84d145ceb2aa05f5a75de352e52d13dd9d6b3
--- /dev/null
+++ b/cluster/router/healthcheck/factory.go
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package healthcheck
+
+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.HealthCheckRouterName, newHealthCheckRouteFactory)
+}
+
+// HealthCheckRouteFactory
+type HealthCheckRouteFactory struct {
+}
+
+// newHealthCheckRouteFactory construct a new HealthCheckRouteFactory
+func newHealthCheckRouteFactory() router.RouterFactory {
+	return &HealthCheckRouteFactory{}
+}
+
+// NewRouter construct a new NewHealthCheckRouter via url
+func (f *HealthCheckRouteFactory) NewRouter(url *common.URL) (router.Router, error) {
+	return NewHealthCheckRouter(url)
+}
diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a9d94da7c37f0e0c9640de1386998a85823e80a6
--- /dev/null
+++ b/cluster/router/healthcheck/factory_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 healthcheck
+
+import (
+	"context"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+type MockInvoker struct {
+	url common.URL
+}
+
+func NewMockInvoker(url common.URL, successCount int) *MockInvoker {
+	return &MockInvoker{
+		url: url,
+	}
+}
+
+func (bi *MockInvoker) GetUrl() common.URL {
+	return bi.url
+}
+func (bi *MockInvoker) IsAvailable() bool {
+	return true
+}
+
+func (bi *MockInvoker) IsDestroyed() bool {
+	return true
+}
+
+func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result {
+	return nil
+}
+
+func (bi *MockInvoker) Destroy() {
+}
+
+func TestHealthCheckRouteFactory(t *testing.T) {
+	factory := newHealthCheckRouteFactory()
+	assert.NotNil(t, factory)
+}
diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..1ddc9ccb173881a87bc5351711326f02ab2da3f6
--- /dev/null
+++ b/cluster/router/healthcheck/health_check_route.go
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package healthcheck
+
+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"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+const (
+	HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled"
+)
+
+// HealthCheckRouter provides a health-first routing mechanism through HealthChecker
+type HealthCheckRouter struct {
+	url     *common.URL
+	enabled bool
+	checker router.HealthChecker
+}
+
+// NewHealthCheckRouter construct an HealthCheckRouter via url
+func NewHealthCheckRouter(url *common.URL) (router.Router, error) {
+	r := &HealthCheckRouter{
+		url:     url,
+		enabled: url.GetParamBool(HEALTH_ROUTE_ENABLED_KEY, false),
+	}
+	if r.enabled {
+		checkerName := url.GetParam(constant.HEALTH_CHECKER, constant.DEFAULT_HEALTH_CHECKER)
+		r.checker = extension.GetHealthChecker(checkerName, url)
+	}
+	return r, nil
+}
+
+// Route gets a list of healthy invoker
+func (r *HealthCheckRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker {
+	if !r.enabled {
+		return invokers
+	}
+	healthyInvokers := make([]protocol.Invoker, 0, len(invokers))
+	// Add healthy invoker to the list
+	for _, invoker := range invokers {
+		if r.checker.IsHealthy(invoker) {
+			healthyInvokers = append(healthyInvokers, invoker)
+		}
+	}
+	// If all Invoke are considered unhealthy, downgrade to all inovker
+	if len(healthyInvokers) == 0 {
+		logger.Warnf(" Now all invokers are unhealthy, so downgraded to all! Service: [%s]", url.ServiceKey())
+		return invokers
+	}
+	return healthyInvokers
+}
+
+// Priority
+func (r *HealthCheckRouter) Priority() int64 {
+	return 0
+}
+
+// URL Return URL in router
+func (r *HealthCheckRouter) URL() common.URL {
+	return *r.url
+}
+
+// HealthyChecker returns the HealthChecker bound to this HealthCheckRouter
+func (r *HealthCheckRouter) HealthyChecker() router.HealthChecker {
+	return r.checker
+}
diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..759ef93dbeb8d91a82eefd59060afbe8a10a4440
--- /dev/null
+++ b/cluster/router/healthcheck/health_check_route_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 healthcheck
+
+import (
+	"math"
+	"testing"
+	"time"
+)
+
+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/protocol"
+	"github.com/apache/dubbo-go/protocol/invocation"
+)
+
+func TestHealthCheckRouter_Route(t *testing.T) {
+	defer protocol.CleanAllStatus()
+	consumerURL, _ := common.NewURL("dubbo://192.168.10.1/com.ikurento.user.UserProvider")
+	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")
+	hcr, _ := NewHealthCheckRouter(&consumerURL)
+
+	var invokers []protocol.Invoker
+	invoker1 := NewMockInvoker(url1, 1)
+	invoker2 := NewMockInvoker(url2, 1)
+	invoker3 := NewMockInvoker(url3, 1)
+	invokers = append(invokers, invoker1, invoker2, invoker3)
+	inv := invocation.NewRPCInvocation("test", 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)
+	}
+	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)
+	}
+
+	res = hcr.Route(invokers, &consumerURL, inv)
+	// only invokers3  is healthy now
+	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)
+	}
+
+	res = hcr.Route(invokers, &consumerURL, inv)
+	// now all invokers are unhealthy, so downgraded to all
+	assert.True(t, len(res) == 3)
+
+	// reset the invoker1 successive failed count, so invoker1 go to healthy
+	request(url1, "test", 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)
+	}
+	// now all invokers are unhealthy, so downgraded to all again
+	res = hcr.Route(invokers, &consumerURL, inv)
+	assert.True(t, len(res) == 3)
+	time.Sleep(time.Second * 2)
+	// invoker1 go to healthy again after 2s
+	res = hcr.Route(invokers, &consumerURL, inv)
+	assert.True(t, contains(res, invoker1))
+
+}
+
+func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool {
+	for _, e := range invokers {
+		if e == invoker {
+			return true
+		}
+	}
+	return false
+}
+
+func TestNewHealthCheckRouter(t *testing.T) {
+	defer protocol.CleanAllStatus()
+	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
+	hcr, _ := NewHealthCheckRouter(&url)
+	h := hcr.(*HealthCheckRouter)
+	assert.Nil(t, h.checker)
+
+	url.SetParam(HEALTH_ROUTE_ENABLED_KEY, "true")
+	hcr, _ = NewHealthCheckRouter(&url)
+	h = hcr.(*HealthCheckRouter)
+	assert.NotNil(t, h.checker)
+
+	dhc := h.checker.(*DefaultHealthChecker)
+	assert.Equal(t, dhc.outStandingRequestConutLimit, int32(math.MaxInt32))
+	assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(constant.DEFAULT_SUCCESSIVE_FAILED_THRESHOLD))
+	assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR))
+
+	url.SetParam(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, "500")
+	url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10")
+	url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000")
+	hcr, _ = NewHealthCheckRouter(&url)
+	h = hcr.(*HealthCheckRouter)
+	dhc = h.checker.(*DefaultHealthChecker)
+	assert.Equal(t, dhc.outStandingRequestConutLimit, int32(1000))
+	assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(10))
+	assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(500))
+}
diff --git a/cluster/router/match/match_utils.go b/cluster/router/match/match_utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..28fe7151c5126c41fbadf9f4d54da2b9df74a7fe
--- /dev/null
+++ b/cluster/router/match/match_utils.go
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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
new file mode 100644
index 0000000000000000000000000000000000000000..f16480f1d3b7dd5ca820c81d5d04d837c129687f
--- /dev/null
+++ b/cluster/router/match/match_utils_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 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
new file mode 100644
index 0000000000000000000000000000000000000000..a28002a09e3b7217549b896d452f70997504ac8f
--- /dev/null
+++ b/cluster/router/router.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 router
+
+import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/protocol"
+)
+
+// Extension - Router
+
+// RouterFactory Router create factory
+type RouterFactory interface {
+	// NewRouter Create router instance with URL
+	NewRouter(*common.URL) (Router, error)
+}
+
+// RouterFactory Router create factory use for parse config file
+type FIleRouterFactory interface {
+	// NewFileRouters Create file router with config file
+	NewFileRouter([]byte) (Router, error)
+}
+
+// Router
+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)
+}
diff --git a/cluster/router/rule.go b/cluster/router/rule.go
new file mode 100644
index 0000000000000000000000000000000000000000..42c08a7009a9509749c27e17c465187fe2c85c03
--- /dev/null
+++ b/cluster/router/rule.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 router
+
+// BaseRouterRule
+type BaseRouterRule struct {
+	RawRule  string
+	Runtime  bool
+	Force    bool
+	Valid    bool
+	Enabled  bool
+	Priority int
+	Dynamic  bool
+	Scope    string
+	Key      string
+}
diff --git a/common/constant/env.go b/common/constant/env.go
index cb5394bb82ec29d1d24e02627e9d8fafff212efa..5376323328f431083a47395c9e2ebbab5b37f307 100644
--- a/common/constant/env.go
+++ b/common/constant/env.go
@@ -24,4 +24,6 @@ const (
 	CONF_PROVIDER_FILE_PATH = "CONF_PROVIDER_FILE_PATH"
 	// APP_LOG_CONF_FILE ...
 	APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE"
+	// CONF_ROUTER_FILE_PATH Specify Path variable of router config file
+	CONF_ROUTER_FILE_PATH = "CONF_ROUTER_FILE_PATH"
 )
diff --git a/common/constant/key.go b/common/constant/key.go
index 33ce0a38b0be11c17a9cb4e31a44dafd3a1e3ba7..07335bed599788b0240b28b096c7d7a395ee9c11 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -40,6 +40,7 @@ const (
 	TOKEN_KEY              = "token"
 	LOCAL_ADDR             = "local-addr"
 	REMOTE_ADDR            = "remote-addr"
+	PATH_SEPARATOR         = "/"
 )
 
 const (
@@ -89,16 +90,23 @@ const (
 )
 
 const (
-	APPLICATION_KEY  = "application"
-	ORGANIZATION_KEY = "organization"
-	NAME_KEY         = "name"
-	MODULE_KEY       = "module"
-	APP_VERSION_KEY  = "app.version"
-	OWNER_KEY        = "owner"
-	ENVIRONMENT_KEY  = "environment"
-	METHOD_KEY       = "method"
-	METHOD_KEYS      = "methods"
-	RULE_KEY         = "rule"
+	APPLICATION_KEY          = "application"
+	ORGANIZATION_KEY         = "organization"
+	NAME_KEY                 = "name"
+	MODULE_KEY               = "module"
+	APP_VERSION_KEY          = "app.version"
+	OWNER_KEY                = "owner"
+	ENVIRONMENT_KEY          = "environment"
+	METHOD_KEY               = "method"
+	METHOD_KEYS              = "methods"
+	RULE_KEY                 = "rule"
+	RUNTIME_KEY              = "runtime"
+	BACKUP_KEY               = "backup"
+	ROUTERS_CATEGORY         = "routers"
+	ROUTE_PROTOCOL           = "route"
+	CONDITION_ROUTE_PROTOCOL = "condition"
+	PROVIDERS_CATEGORY       = "providers"
+	ROUTER_KEY               = "router"
 )
 
 const (
@@ -120,6 +128,7 @@ const (
 	ProviderConfigPrefix       = "dubbo.provider."
 	ConsumerConfigPrefix       = "dubbo.consumer."
 	ShutdownConfigPrefix       = "dubbo.shutdown."
+	RouterConfigPrefix         = "dubbo.router."
 )
 
 const (
@@ -142,6 +151,28 @@ const (
 	TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx"
 )
 
+// Use for router module
+const (
+	// ConditionRouterName Specify file condition router name
+	ConditionRouterName = "condition"
+	// ConditionAppRouterName Specify listenable application router name
+	ConditionAppRouterName = "app"
+	// ListenableRouterName Specify listenable router name
+	ListenableRouterName = "listenable"
+	// HealthCheckRouterName Specify the name of HealthCheckRouter
+	HealthCheckRouterName = "health_check"
+
+	// ConditionRouterRuleSuffix Specify condition router suffix
+	ConditionRouterRuleSuffix = ".condition-router"
+
+	// Force Force key in router module
+	RouterForce = "force"
+	// Enabled Enabled key in router module
+	RouterEnabled = "enabled"
+	// Priority Priority key in router module
+	RouterPriority = "priority"
+)
+
 const (
 	// name of consumer sign filter
 	CONSUMER_SIGN_FILTER = "sign"
@@ -174,3 +205,25 @@ const (
 	// key of secret access key
 	SECRET_ACCESS_KEY_KEY = "secretAccessKey"
 )
+
+// HealthCheck Router
+const (
+	// The key of HealthCheck SPI
+	HEALTH_CHECKER = "health.checker"
+	// The name of the default implementation of HealthChecker
+	DEFAULT_HEALTH_CHECKER = "default"
+	// The key of oustanding-request-limit
+	OUTSTANDING_REQUEST_COUNT_LIMIT_KEY = "outstanding.request.limit"
+	// The key of successive-failed-request's threshold
+	SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY = "successive.failed.threshold"
+	// The key of circuit-tripped timeout factor
+	CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY = "circuit.tripped.timeout.factor"
+	// The default threshold of  successive-failed-request if not specfied
+	DEFAULT_SUCCESSIVE_FAILED_THRESHOLD = 5
+	// The default maximum diff between successive-failed-request's threshold and actual successive-failed-request's count
+	DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF = 5
+	// The default factor of  circuit-tripped timeout if not specfied
+	DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR = 1000
+	// The default time window of circuit-tripped  in millisecond if not specfied
+	MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000
+)
diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go
new file mode 100644
index 0000000000000000000000000000000000000000..365c5d0910812efb00eb94408bb226115b037c02
--- /dev/null
+++ b/common/extension/health_checker.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 extension
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
+	"github.com/apache/dubbo-go/common"
+)
+
+var (
+	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) {
+	healthCheckers[name] = fcn
+}
+
+// GetHealthChecker get 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.")
+	}
+	return healthCheckers[name](url)
+}
diff --git a/cluster/router/router_factory.go b/common/extension/health_checker_test.go
similarity index 60%
rename from cluster/router/router_factory.go
rename to common/extension/health_checker_test.go
index 723050939e5080f1fefd230986dc679dfbdc06ed..ec934e6e9cedc5acbef350f17b87b0b2e37bc844 100644
--- a/cluster/router/router_factory.go
+++ b/common/extension/health_checker_test.go
@@ -15,27 +15,35 @@
  * limitations under the License.
  */
 
-package router
+package extension
 
 import (
-	"github.com/apache/dubbo-go/cluster"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
 	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/protocol"
 )
 
-func init() {
-	extension.SetRouterFactory("condition", NewConditionRouterFactory)
+func TestGetHealthChecker(t *testing.T) {
+	SethealthChecker("mock", newMockhealthCheck)
+	checker := GetHealthChecker("mock", common.NewURLWithOptions())
+	assert.NotNil(t, checker)
 }
 
-// ConditionRouterFactory ...
-type ConditionRouterFactory struct{}
+type mockHealthChecker struct {
+}
 
-// NewConditionRouterFactory ...
-func NewConditionRouterFactory() cluster.RouterFactory {
-	return ConditionRouterFactory{}
+func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool {
+	return true
 }
 
-// Router ...
-func (c ConditionRouterFactory) Router(url *common.URL) (cluster.Router, error) {
-	return newConditionRouter(url)
+func newMockhealthCheck(url *common.URL) router.HealthChecker {
+	return &mockHealthChecker{}
 }
diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go
index c77cc291369ab02c5f58dfc6c283902ac0df4b95..70d71dfa859b996030c865775a588da20039f9a5 100644
--- a/common/extension/router_factory.go
+++ b/common/extension/router_factory.go
@@ -18,23 +18,50 @@
 package extension
 
 import (
-	"github.com/apache/dubbo-go/cluster"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/router"
 )
 
 var (
-	routers = make(map[string]func() cluster.RouterFactory)
+	routers               = make(map[string]func() router.RouterFactory)
+	fileRouterFactoryOnce sync.Once
+	fileRouterFactories   = make(map[string]router.FIleRouterFactory)
 )
 
-// SetRouterFactory ...
-func SetRouterFactory(name string, fun func() cluster.RouterFactory) {
+// SetRouterFactory Set create router factory function by name
+func SetRouterFactory(name string, fun func() router.RouterFactory) {
 	routers[name] = fun
 }
 
-// GetRouterFactory ...
-func GetRouterFactory(name string) cluster.RouterFactory {
+// GetRouterFactory Get create router factory function by name
+func GetRouterFactory(name string) router.RouterFactory {
 	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 {
+	return routers
+}
+
+// GetFileRouterFactories Get all create file router factory instance
+func GetFileRouterFactories() map[string]router.FIleRouterFactory {
+	l := len(routers)
+	if l == 0 {
+		return nil
+	}
+	fileRouterFactoryOnce.Do(func() {
+		for k := range routers {
+			factory := GetRouterFactory(k)
+			if fr, ok := factory.(router.FIleRouterFactory); ok {
+				fileRouterFactories[k] = fr
+			}
+		}
+	})
+	return fileRouterFactories
 }
diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go
index 43ca720d0e71577a446829f702c1d2fe23a32905..6765a810a5ed48d95f49b5b97fbf660dd8587715 100644
--- a/common/proxy/proxy.go
+++ b/common/proxy/proxy.go
@@ -59,6 +59,7 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str
 // 		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.
@@ -142,7 +143,7 @@ func (p *Proxy) Implement(v common.RPCService) {
 			result := p.invoke.Invoke(invCtx, inv)
 
 			err = result.Error()
-			logger.Infof("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
+			logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err)
 			if len(outs) == 1 {
 				return []reflect.Value{reflect.ValueOf(&err).Elem()}
 			}
diff --git a/common/url.go b/common/url.go
index 360f0aa4caa185d8086eb07497176ae627f47d47..ebb648db27c3efff534f0d0a545f2211f335aa89 100644
--- a/common/url.go
+++ b/common/url.go
@@ -59,8 +59,8 @@ const (
 var (
 	// DubboNodes ...
 	DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"}
-	// DubboRole ...
-	DubboRole = [...]string{"consumer", "", "", "provider"}
+	// DubboRole Dubbo service role
+	DubboRole = [...]string{"consumer", "", "routers", "provider"}
 )
 
 // RoleType ...
@@ -240,7 +240,7 @@ func NewURL(urlString string, opts ...option) (URL, error) {
 	if strings.Contains(s.Location, ":") {
 		s.Ip, s.Port, err = net.SplitHostPort(s.Location)
 		if err != nil {
-			return s, perrors.Errorf("net.SplitHostPort(Url.Host{%s}), error{%v}", s.Location, err)
+			return s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err)
 		}
 	}
 	for _, opt := range opts {
@@ -277,6 +277,7 @@ func (c URL) URLEqual(url URL) bool {
 	}
 	return true
 }
+
 func isMatchCategory(category1 string, category2 string) bool {
 	if len(category2) == 0 {
 		return category1 == constant.DEFAULT_CATEGORY
@@ -288,6 +289,7 @@ func isMatchCategory(category1 string, category2 string) bool {
 		return strings.Contains(category2, category1)
 	}
 }
+
 func (c URL) String() string {
 	var buildString string
 	if len(c.Username) == 0 && len(c.Password) == 0 {
@@ -373,7 +375,7 @@ func (c URL) Service() string {
 		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
 		}
 	}
diff --git a/common/url_test.go b/common/url_test.go
index 835973065b6d7426e5487fe76602ca27701130a1..2372de520e88b0949023e88cec64871736dd6aa0 100644
--- a/common/url_test.go
+++ b/common/url_test.go
@@ -164,6 +164,7 @@ func TestURL_GetParamAndDecoded(t *testing.T) {
 	v, _ := u.GetParamAndDecoded("rule")
 	assert.Equal(t, rule, v)
 }
+
 func TestURL_GetRawParam(t *testing.T) {
 	u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
 	u.Username = "test"
@@ -176,6 +177,7 @@ func TestURL_GetRawParam(t *testing.T) {
 	assert.Equal(t, "/com.foo.BarService", u.GetRawParam("path"))
 	assert.Equal(t, "fastjson", u.GetRawParam("serialization"))
 }
+
 func TestURL_ToMap(t *testing.T) {
 	u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
 	u.Username = "test"
diff --git a/config/base_config.go b/config/base_config.go
index 942e966eb31e9570e957ad74aa696aa9ef29c5b0..6d5ec7e2498ba65b2a6833b6c9cefcb3394e60df 100644
--- a/config/base_config.go
+++ b/config/base_config.go
@@ -18,6 +18,8 @@
 package config
 
 import (
+	"io/ioutil"
+	"path"
 	"reflect"
 	"strconv"
 	"strings"
@@ -25,6 +27,7 @@ import (
 
 import (
 	perrors "github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
 )
 
 import (
@@ -138,6 +141,7 @@ func getKeyPrefix(val reflect.Value) []string {
 	return retPrefixs
 
 }
+
 func getPtrElement(v reflect.Value) reflect.Value {
 	if v.Kind() == reflect.Ptr {
 		v = v.Elem()
@@ -147,6 +151,7 @@ func getPtrElement(v reflect.Value) reflect.Value {
 	}
 	return v
 }
+
 func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryConfiguration) {
 	for i := 0; i < val.NumField(); i++ {
 		if key := val.Type().Field(i).Tag.Get("property"); key != "-" && key != "" {
@@ -300,6 +305,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC
 		}
 	}
 }
+
 func (c *BaseConfig) fresh() {
 	configList := config.GetEnvInstance().Configuration()
 	for element := configList.Front(); element != nil; element = element.Next() {
@@ -360,3 +366,25 @@ func initializeStruct(t reflect.Type, v reflect.Value) {
 	}
 
 }
+
+// loadYMLConfig Load yml config byte from file
+func loadYMLConfig(confProFile string) ([]byte, error) {
+	if len(confProFile) == 0 {
+		return nil, perrors.Errorf("application configure(provider) file name is nil")
+	}
+
+	if path.Ext(confProFile) != ".yml" {
+		return nil, perrors.Errorf("application configure file name{%v} suffix must be .yml", confProFile)
+	}
+
+	return ioutil.ReadFile(confProFile)
+}
+
+// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object
+func unmarshalYMLConfig(confProFile string, out interface{}) error {
+	confFileStream, err := loadYMLConfig(confProFile)
+	if err != nil {
+		return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err))
+	}
+	return yaml.Unmarshal(confFileStream, out)
+}
diff --git a/config/base_config_test.go b/config/base_config_test.go
index d16b2420922ece60ef2135729cd47d5aa73a3760..6973a4a18b5e3a78d9039bc818a5a2a046613783 100644
--- a/config/base_config_test.go
+++ b/config/base_config_test.go
@@ -18,6 +18,7 @@ package config
 
 import (
 	"fmt"
+	"path/filepath"
 	"reflect"
 	"testing"
 )
@@ -517,3 +518,13 @@ func Test_initializeStruct(t *testing.T) {
 		return consumerConfig.References != nil
 	})
 }
+
+func TestUnmarshalYMLConfig(t *testing.T) {
+	conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml")
+	assert.NoError(t, err)
+	c := &ConsumerConfig{}
+	assert.NoError(t, unmarshalYMLConfig(conPath, c))
+	assert.Equal(t, "default", c.ProxyFactory)
+	assert.Equal(t, "dubbo.properties", c.ConfigCenterConfig.ConfigFile)
+	assert.Equal(t, "100ms", c.Connect_Timeout)
+}
diff --git a/config/condition_router_config.go b/config/condition_router_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..a95b2d2b1265a4c069abd8cbc682a9474c15f454
--- /dev/null
+++ b/config/condition_router_config.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 config
+
+import (
+	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"
+)
+
+//RouterInit Load config file to init router config
+func RouterInit(confRouterFile string) error {
+	fileRouterFactories := extension.GetFileRouterFactories()
+	bytes, err := loadYMLConfig(confRouterFile)
+	if err != nil {
+		return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err))
+	}
+	for k, factory := range fileRouterFactories {
+		r, e := factory.NewFileRouter(bytes)
+		if e == nil {
+			url := r.URL()
+			directory.AddRouterURLSet(&url)
+			return nil
+		}
+		logger.Warnf("router config type %s create fail \n", k)
+	}
+	return perrors.Errorf("no file router exists for parse %s , implement router.FIleRouterFactory please.", confRouterFile)
+}
diff --git a/config/condition_router_config_test.go b/config/condition_router_config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f0a38b2fdf59578c77076680c05b3eca5c26a1c
--- /dev/null
+++ b/config/condition_router_config_test.go
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 (
+	"strings"
+	"testing"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+import (
+	"github.com/apache/dubbo-go/cluster/directory"
+	_ "github.com/apache/dubbo-go/cluster/router/condition"
+)
+
+const testYML = "testdata/router_config.yml"
+const errorTestYML = "testdata/router_config_error.yml"
+
+func TestString(t *testing.T) {
+
+	s := "a1=>a2"
+	s1 := "=>a2"
+	s2 := "a1=>"
+
+	n := strings.SplitN(s, "=>", 2)
+	n1 := strings.SplitN(s1, "=>", 2)
+	n2 := strings.SplitN(s2, "=>", 2)
+
+	assert.Equal(t, n[0], "a1")
+	assert.Equal(t, n[1], "a2")
+
+	assert.Equal(t, n1[0], "")
+	assert.Equal(t, n1[1], "a2")
+
+	assert.Equal(t, n2[0], "a1")
+	assert.Equal(t, n2[1], "")
+}
+
+func TestRouterInit(t *testing.T) {
+	errPro := RouterInit(errorTestYML)
+	assert.Error(t, errPro)
+
+	assert.Equal(t, 0, directory.GetRouterURLSet().Size())
+
+	errPro = RouterInit(testYML)
+	assert.NoError(t, errPro)
+
+	assert.Equal(t, 1, directory.GetRouterURLSet().Size())
+}
diff --git a/config/config_loader.go b/config/config_loader.go
index 875d1f6ddb84434d32296076cd31be96c1385b8a..437f4d7323e66afcf62808b3c8d6bf51cc5bce88 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -36,21 +36,26 @@ var (
 	metricConfig      *MetricConfig
 	applicationConfig *ApplicationConfig
 	maxWait           = 3
+	confRouterFile    string
 )
 
 // loaded consumer & provider config from xxx.yml, and log config from xxx.xml
 // Namely: dubbo.consumer.xml & dubbo.provider.xml in java dubbo
 func init() {
 	var (
-		confConFile, confProFile string
+		confConFile string
+		confProFile string
 	)
 
 	confConFile = os.Getenv(constant.CONF_CONSUMER_FILE_PATH)
 	confProFile = os.Getenv(constant.CONF_PROVIDER_FILE_PATH)
+	confRouterFile = os.Getenv(constant.CONF_ROUTER_FILE_PATH)
+
 	if errCon := ConsumerInit(confConFile); errCon != nil {
 		log.Printf("[consumerInit] %#v", errCon)
 		consumerConfig = nil
 	}
+
 	if errPro := ProviderInit(confProFile); errPro != nil {
 		log.Printf("[providerInit] %#v", errPro)
 		providerConfig = nil
@@ -73,6 +78,13 @@ func checkApplicationName(config *ApplicationConfig) {
 
 // Load Dubbo Init
 func Load() {
+	// init router
+	if confRouterFile != "" {
+		if errPro := RouterInit(confRouterFile); errPro != nil {
+			log.Printf("[routerConfig init] %#v", errPro)
+		}
+	}
+
 	// reference config
 	if consumerConfig == nil {
 		logger.Warnf("consumerConfig is nil!")
@@ -100,6 +112,7 @@ func Load() {
 			ref.Refer(rpcService)
 			ref.Implement(rpcService)
 		}
+
 		//wait for invoker is available, if wait over default 3s, then panic
 		var count int
 		checkok := true
diff --git a/config/protocol_config.go b/config/protocol_config.go
index 4828d6e5bd28de19d896340f39c5633d0acd4874..33de976bc6f5bf7341ddcff8d51c505cf23bbccd 100644
--- a/config/protocol_config.go
+++ b/config/protocol_config.go
@@ -38,14 +38,13 @@ func (c *ProtocolConfig) Prefix() string {
 }
 
 func loadProtocol(protocolsIds string, protocols map[string]*ProtocolConfig) []*ProtocolConfig {
-	returnProtocols := []*ProtocolConfig{}
+	returnProtocols := make([]*ProtocolConfig, 0, len(protocols))
 	for _, v := range strings.Split(protocolsIds, ",") {
-		for k, prot := range protocols {
+		for k, protocol := range protocols {
 			if v == k {
-				returnProtocols = append(returnProtocols, prot)
+				returnProtocols = append(returnProtocols, protocol)
 			}
 		}
-
 	}
 	return returnProtocols
 }
diff --git a/config/reference_config.go b/config/reference_config.go
index edfa17a27e88a605b71bc7f6dec1b133bd29abe9..7ce0013194f5c1a1d09e014a858433833aa07f0e 100644
--- a/config/reference_config.go
+++ b/config/reference_config.go
@@ -77,7 +77,6 @@ func NewReferenceConfig(id string, ctx context.Context) *ReferenceConfig {
 
 // UnmarshalYAML ...
 func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
-
 	type rf ReferenceConfig
 	raw := rf{} // Put your defaults here
 	if err := unmarshal(&raw); err != nil {
@@ -101,8 +100,8 @@ func (c *ReferenceConfig) Refer(_ interface{}) {
 		common.WithParamsValue(constant.BEAN_NAME_KEY, c.id),
 	)
 
-	//1. user specified URL, could be peer-to-peer address, or register center's address.
 	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*")
 		for _, urlStr := range urlStrings {
 			serviceUrl, err := common.NewURL(urlStr)
@@ -120,21 +119,21 @@ func (c *ReferenceConfig) Refer(_ interface{}) {
 				newUrl := common.MergeUrl(&serviceUrl, cfgURL)
 				c.urls = append(c.urls, newUrl)
 			}
-
 		}
 	} else {
-		//2. assemble SubURL from register center's configuration妯″紡
+		// 2. assemble SubURL from register center's configuration mode
 		c.urls = loadRegistries(c.Registry, consumerConfig.Registries, common.CONSUMER)
 
-		//set url to regUrls
+		// set url to regUrls
 		for _, regUrl := range c.urls {
 			regUrl.SubURL = cfgURL
 		}
 	}
+
 	if len(c.urls) == 1 {
 		c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(*c.urls[0])
 	} else {
-		invokers := []protocol.Invoker{}
+		invokers := make([]protocol.Invoker, 0, len(c.urls))
 		var regUrl *common.URL
 		for _, u := range c.urls {
 			invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(*u))
@@ -151,7 +150,7 @@ func (c *ReferenceConfig) Refer(_ interface{}) {
 		}
 	}
 
-	//create proxy
+	// create proxy
 	if c.Async {
 		callback := GetCallback(c.id)
 		c.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetAsyncProxy(c.invoker, callback, cfgURL)
@@ -219,7 +218,6 @@ func (c *ReferenceConfig) getUrlMap() url.Values {
 	}
 
 	return urlMap
-
 }
 
 // GenericLoad ...
diff --git a/config/registry_config_test.go b/config/registry_config_test.go
index 45d38b29cc7089dabc5d7b7e34390ee48a58dc97..6c2fed605d6c50b483f7ad2900e5a483b3986e1b 100644
--- a/config/registry_config_test.go
+++ b/config/registry_config_test.go
@@ -46,6 +46,7 @@ func Test_loadRegistries(t *testing.T) {
 	fmt.Println(urls[0])
 	assert.Equal(t, "127.0.0.2:2181,128.0.0.1:2181", urls[0].Location)
 }
+
 func Test_loadRegistries1(t *testing.T) {
 	target := "shanghai1"
 	regs := map[string]*RegistryConfig{
diff --git a/config/service_config.go b/config/service_config.go
index 2111838395d507ebac4f72883c99dd2bb1615850..7d97fa4d1e95bd79e051f77deaeafa1afcc58b0f 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -96,14 +96,12 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 
 // NewServiceConfig The only way to get a new ServiceConfig
 func NewServiceConfig(id string, context context.Context) *ServiceConfig {
-
 	return &ServiceConfig{
 		context:    context,
 		id:         id,
 		unexported: atomic.NewBool(false),
 		exported:   atomic.NewBool(false),
 	}
-
 }
 
 // Export ...
@@ -171,10 +169,8 @@ func (c *ServiceConfig) Export() error {
 				panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL)))
 			}
 		}
-
 	}
 	return nil
-
 }
 
 // Implement ...
@@ -242,5 +238,4 @@ func (c *ServiceConfig) getUrlMap() url.Values {
 	}
 
 	return urlMap
-
 }
diff --git a/config/testdata/router_config.yml b/config/testdata/router_config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6b91f5da7d95256e6279924b884bfd450c45a08
--- /dev/null
+++ b/config/testdata/router_config.yml
@@ -0,0 +1,6 @@
+# dubbo router yaml configure file
+priority: 1
+force: true
+conditions :
+  - "a => b"
+  - "c => d"
\ No newline at end of file
diff --git a/config/testdata/router_config_error.yml b/config/testdata/router_config_error.yml
new file mode 100644
index 0000000000000000000000000000000000000000..37894ac96474281d10131b53d8e644f10a18b14e
--- /dev/null
+++ b/config/testdata/router_config_error.yml
@@ -0,0 +1,6 @@
+# dubbo router yaml configure file
+priority: 1
+force: true
+noConditions :
+  - "a => b"
+  - "c => d"
\ No newline at end of file
diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go
index 85dff14a1ec9ba3905890bf37dc1e1827d59d80f..4dc19817846fe5c9c0552738f2058a15d20efabc 100644
--- a/config_center/apollo/impl.go
+++ b/config_center/apollo/impl.go
@@ -163,6 +163,7 @@ func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) stri
 func (c *apolloConfiguration) Parser() parser.ConfigurationParser {
 	return c.parser
 }
+
 func (c *apolloConfiguration) SetParser(p parser.ConfigurationParser) {
 	c.parser = p
 }
diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go
index d0b23ef2f20d065135547536c2cebcec3eec0ce1..18415bee3a28b37ffc2f3f73cc7309b685de5408 100644
--- a/config_center/configurator/override.go
+++ b/config_center/configurator/override.go
@@ -36,6 +36,7 @@ import (
 func init() {
 	extension.SetDefaultConfigurator(newConfigurator)
 }
+
 func newConfigurator(url *common.URL) config_center.Configurator {
 	return &overrideConfigurator{configuratorUrl: url}
 }
diff --git a/config_center/configurator/override_test.go b/config_center/configurator/override_test.go
index 329c598efe8ef79d7fc1b79ae182c59b238283ac..c0aeb15130e7862fcb00d6cb82cbef60df777acb 100644
--- a/config_center/configurator/override_test.go
+++ b/config_center/configurator/override_test.go
@@ -40,8 +40,8 @@ func Test_configureVerison2p6(t *testing.T) {
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
 	assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
-
 }
+
 func Test_configureVerisonOverrideAddr(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)
@@ -52,8 +52,8 @@ func Test_configureVerisonOverrideAddr(t *testing.T) {
 	assert.NoError(t, err)
 	configurator.Configure(&providerUrl)
 	assert.Equal(t, "failover", providerUrl.GetParam(constant.CLUSTER_KEY, ""))
-
 }
+
 func Test_configureVerison2p6WithIp(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)
diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go
index 90cd3bbb1d502a0e9ceb8fed5c94a4091bc0578e..d6c3b06b327f16c709b09121e589db6694d3663e 100644
--- a/config_center/dynamic_configuration.go
+++ b/config_center/dynamic_configuration.go
@@ -22,6 +22,7 @@ import (
 )
 
 import (
+	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/config_center/parser"
 )
 
@@ -73,3 +74,8 @@ func WithTimeout(time time.Duration) Option {
 		opt.Timeout = time
 	}
 }
+
+//GetRuleKey The format is '{interfaceName}:[version]:[group]'
+func GetRuleKey(url common.URL) string {
+	return url.ColonSeparatedKey()
+}
diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go
index 06db101d888701d73c9d2bdf87c3715a73c4ee46..d3373e249bf99873dd3aa05b7488b0e7f38730ec 100644
--- a/config_center/nacos/client.go
+++ b/config_center/nacos/client.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package nacos
 
 import (
diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go
index 96b4c9d4ac7af8d48e570f0b702b7529cc5603dd..ef63eeff6ddf4e5cd6fa2ba7da7996b3dbed94ac 100644
--- a/config_center/nacos/client_test.go
+++ b/config_center/nacos/client_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package nacos
 
 import (
diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go
index 07bf8638d2d80123545db02c16544001c06e335b..b4e6f1d0259979eba28dd81e8f480ab4ae03a39f 100644
--- a/config_center/nacos/impl_test.go
+++ b/config_center/nacos/impl_test.go
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package nacos
 
 import (
diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go
index 58fcdb49dad2c53270894a65bf4ebd9595dc420e..f342dc62e765f8d38c9e64ba3be03f3362f0bf61 100644
--- a/config_center/parser/configuration_parser.go
+++ b/config_center/parser/configuration_parser.go
@@ -109,6 +109,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common
 	}
 	return allUrls, nil
 }
+
 func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) {
 	var addresses = item.Addresses
 	if len(addresses) == 0 {
@@ -154,6 +155,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR
 	}
 	return urls, nil
 }
+
 func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) {
 	var addresses = item.Addresses
 	if len(addresses) == 0 {
@@ -246,6 +248,7 @@ func getParamString(item ConfigItem) (string, error) {
 
 	return retStr, nil
 }
+
 func getEnabledString(item ConfigItem, config ConfiguratorConfig) string {
 	retStr := "&enabled="
 	if len(item.Type) == 0 || item.Type == GeneralType {
diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go
index 70fb196a1eedb994eae38576de35d36deb450aaa..404243d4751146d1edc9a61d51cbb81d73c2ffb1 100644
--- a/config_center/zookeeper/impl.go
+++ b/config_center/zookeeper/impl.go
@@ -155,6 +155,7 @@ func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_cente
 func (c *zookeeperDynamicConfiguration) Parser() parser.ConfigurationParser {
 	return c.parser
 }
+
 func (c *zookeeperDynamicConfiguration) SetParser(p parser.ConfigurationParser) {
 	c.parser = p
 }
diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go
index 1d62f3df86f5706823cab7c9ed0bc1a7d9b380f3..22e15193cba1b533a2b1b965a44bf9665a6a4e5e 100644
--- a/config_center/zookeeper/impl_test.go
+++ b/config_center/zookeeper/impl_test.go
@@ -77,6 +77,7 @@ func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicC
 
 	return ts, reg
 }
+
 func Test_GetConfig(t *testing.T) {
 	ts, reg := initZkData("dubbo", t)
 	defer ts.Stop()
diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png
new file mode 100644
index 0000000000000000000000000000000000000000..8f8f19957b2a8639470e5c59a676a22762cc9778
Binary files /dev/null and b/doc/pic/arch/dubbo-go-arch.png differ
diff --git a/filter/access_key.go b/filter/access_key.go
index c9bdd4ff8993d51e4d5002a1216225e2da074df5..40d4157b31d13ed8fd8b1ba8cc9d16b53638ac6a 100644
--- a/filter/access_key.go
+++ b/filter/access_key.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package filter
 
 import (
diff --git a/filter/authenticator.go b/filter/authenticator.go
index ce0547b36b03b7078784a6c05c08cd3f89611ca4..ac2c8601d4a0d2e5ae3aed56415d9d23856cb502 100644
--- a/filter/authenticator.go
+++ b/filter/authenticator.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package filter
 
 import (
diff --git a/filter/filter_impl/auth/accesskey_storage.go b/filter/filter_impl/auth/accesskey_storage.go
index 0a2bf47cbd377899ba8a0edf4a67026dd827d41f..5adb9d9ee37329228d1d02dc8802deeede68d327 100644
--- a/filter/filter_impl/auth/accesskey_storage.go
+++ b/filter/filter_impl/auth/accesskey_storage.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/accesskey_storage_test.go b/filter/filter_impl/auth/accesskey_storage_test.go
index 6ab861a8673b191be0a8063980e1dc53e4e70f60..aa566b81761d1f51a5b9141f8f10e66844f272bb 100644
--- a/filter/filter_impl/auth/accesskey_storage_test.go
+++ b/filter/filter_impl/auth/accesskey_storage_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go
index be86b5c74bb9fd02b96483edb18571d47d205ee7..062744771acf8ccd505265875a103d24afeb06af 100644
--- a/filter/filter_impl/auth/consumer_sign.go
+++ b/filter/filter_impl/auth/consumer_sign.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
@@ -38,6 +55,7 @@ func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invo
 func (csf *ConsumerSignFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
+
 func getConsumerSignFilter() filter.Filter {
 	return &ConsumerSignFilter{}
 }
diff --git a/filter/filter_impl/auth/consumer_sign_test.go b/filter/filter_impl/auth/consumer_sign_test.go
index 9a90970b898b75f3c3f1b195062538e62505a082..b02380e28f51356efae385a2e20a6b1ee4e9aa5c 100644
--- a/filter/filter_impl/auth/consumer_sign_test.go
+++ b/filter/filter_impl/auth/consumer_sign_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go
index 73eb9cddc0e1b7b4747da4b0f3e883075e349226..2b8d55927807407f350ecc6cfc28b6913a6d1a81 100644
--- a/filter/filter_impl/auth/default_authenticator.go
+++ b/filter/filter_impl/auth/default_authenticator.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go
index 72220eec99533b73cc2c9159e3443d6f566471fa..5b107b5960ff5adc383d52aa5e393d9fc6e71d14 100644
--- a/filter/filter_impl/auth/default_authenticator_test.go
+++ b/filter/filter_impl/auth/default_authenticator_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go
index 90804934f6b01a61f021f61f4ee549d744ccee72..0d5772e5508894111a88443bfe2d1b02ebfac54a 100644
--- a/filter/filter_impl/auth/provider_auth.go
+++ b/filter/filter_impl/auth/provider_auth.go
@@ -1,7 +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 auth
 
 import (
 	"context"
+)
+
+import (
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/extension"
 	"github.com/apache/dubbo-go/common/logger"
@@ -38,6 +58,7 @@ func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invo
 func (paf *ProviderAuthFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
 	return result
 }
+
 func getProviderAuthFilter() filter.Filter {
 	return &ProviderAuthFilter{}
 }
diff --git a/filter/filter_impl/auth/provider_auth_test.go b/filter/filter_impl/auth/provider_auth_test.go
index 88d6423458d5534f18da4316ffe1bca0b374e43c..626782ae8390f046f441c1f162700a883e6f22d0 100644
--- a/filter/filter_impl/auth/provider_auth_test.go
+++ b/filter/filter_impl/auth/provider_auth_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go
index 60698439c5abc1ff0cc555b2ceec77bf2e0e53d5..043a549a849dde66712e1bef389dd91a024660df 100644
--- a/filter/filter_impl/auth/sign_util.go
+++ b/filter/filter_impl/auth/sign_util.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/auth/sign_util_test.go b/filter/filter_impl/auth/sign_util_test.go
index de6154e8854af99f8e862d94ee45aefcbf26b12b..a4aaf2da27a8dd14969e0e3f93eaee16dfc31b03 100644
--- a/filter/filter_impl/auth/sign_util_test.go
+++ b/filter/filter_impl/auth/sign_util_test.go
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package auth
 
 import (
diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go
index e8ff2679b0294d6519aecd1cc1fe37bdeab89e46..9bc131ef8903942b84df2b8fc14fd11143d1a7b6 100644
--- a/filter/filter_impl/generic_filter.go
+++ b/filter/filter_impl/generic_filter.go
@@ -83,6 +83,7 @@ func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _
 func GetGenericFilter() filter.Filter {
 	return &GenericFilter{}
 }
+
 func struct2MapAll(obj interface{}) interface{} {
 	if obj == nil {
 		return obj
@@ -127,6 +128,7 @@ func struct2MapAll(obj interface{}) interface{} {
 		return obj
 	}
 }
+
 func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
 	result = m
 	if tagName := structField.Tag.Get("m"); tagName == "" {
@@ -136,6 +138,7 @@ func setInMap(m map[string]interface{}, structField reflect.StructField, value i
 	}
 	return
 }
+
 func headerAtoa(a string) (b string) {
 	b = strings.ToLower(a[:1]) + a[1:]
 	return
diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go
index 22948353fc16a99696a85489ce5df7dc9b18a7ba..b08229199898a30657682d47c32689dc084f5bf4 100644
--- a/filter/filter_impl/generic_filter_test.go
+++ b/filter/filter_impl/generic_filter_test.go
@@ -88,6 +88,7 @@ func Test_struct2MapAll_Slice(t *testing.T) {
 	assert.Equal(t, reflect.Slice, reflect.TypeOf(m["caCa"]).Kind())
 	assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind())
 }
+
 func Test_struct2MapAll_Map(t *testing.T) {
 	var testData struct {
 		AaAa string
diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go
index 66c17d920c14e23f1562773c152e99955a48bfb9..71fc097c8bf4752e0cb2b451b0da7e16480b0701 100644
--- a/filter/filter_impl/hystrix_filter_test.go
+++ b/filter/filter_impl/hystrix_filter_test.go
@@ -213,6 +213,7 @@ func TestGetHystrixFilterConsumer(t *testing.T) {
 	assert.NotNil(t, get)
 	assert.True(t, get.(*HystrixFilter).COrP)
 }
+
 func TestGetHystrixFilterProvider(t *testing.T) {
 	get := GetHystrixFilterProvider()
 	assert.NotNil(t, get)
diff --git a/go.mod b/go.mod
index 0eeef0e096609a6157efa3a8ab9c69607bde9a29..73046bc1866176d5e6b52b7bbdea70851fe32269 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ 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.3.1-0.20200111150223-4ce8c8d0d7ac
+	github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3
 	github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
 	github.com/coreos/bbolt v1.3.3 // indirect
 	github.com/coreos/etcd v3.3.13+incompatible
@@ -12,7 +12,7 @@ require (
 	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.2
+	github.com/dubbogo/getty v1.3.3
 	github.com/dubbogo/go-zookeeper v1.0.0
 	github.com/dubbogo/gost v1.5.2
 	github.com/emicklei/go-restful/v3 v3.0.0
@@ -44,7 +44,7 @@ require (
 	github.com/satori/go.uuid v1.2.0
 	github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
 	github.com/soheilhy/cmux v0.1.4 // indirect
-	github.com/stretchr/testify v1.4.0
+	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
diff --git a/go.sum b/go.sum
index 31cf688f1aa7aa838df8883dd398735f1b0ff8a3..d3570af4839139dc884ff730da333f6fdbe97651 100644
--- a/go.sum
+++ b/go.sum
@@ -35,8 +35,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 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/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
-github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac h1:QKRMidg/RbdI5oaQWMb8Lxo63S+fLmsgMxsFoOCftKw=
-github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
+github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 h1:1HM47ILUkLaMxLKUub+WHPncqrJGEQ0KRJzSJueMDpY=
+github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8=
 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=
@@ -102,8 +102,8 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF
 github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dubbogo/getty v1.3.2 h1:l1KVSs/1CtTKbIPTrkTtBT6S9ddvmswDGoAnnl2CDpM=
-github.com/dubbogo/getty v1.3.2/go.mod h1:ANbVQ9tbpZ2b0xdR8nRrgS/oXIsZAeRxzvPSOn/7mbk=
+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/gost v1.5.1 h1:oG5dzaWf1KYynBaBoUIOkgT+YD0niHV6xxI0Odq7hDg=
@@ -452,6 +452,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
 github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
 github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8=
@@ -494,7 +496,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 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-20190320064053-1272bf9dcd53/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 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go
index 0765a330b534b4f88a955d1ab898780e9fa60713..5ec7db51af0ddfa6e49d3c65910355f0bf2de414 100644
--- a/protocol/dubbo/client.go
+++ b/protocol/dubbo/client.go
@@ -18,6 +18,7 @@
 package dubbo
 
 import (
+	"math/rand"
 	"strings"
 	"sync"
 	"time"
@@ -83,6 +84,8 @@ func init() {
 		return
 	}
 	setClientGrpool()
+
+	rand.Seed(time.Now().UnixNano())
 }
 
 // SetClientConf ...
@@ -147,11 +150,18 @@ func NewClient(opt Options) *Client {
 		opt.RequestTimeout = 3 * time.Second
 	}
 
+	// make sure that client request sequence is an odd number
+	initSequence := uint64(rand.Int63n(time.Now().UnixNano()))
+	if initSequence%2 == 0 {
+		initSequence++
+	}
+
 	c := &Client{
 		opts:             opt,
 		pendingResponses: new(sync.Map),
 		conf:             *clientConf,
 	}
+	c.sequence.Store(initSequence)
 	c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL))
 
 	return c
diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go
index 3e50eb901dbe1e549aea4ea7414d9617851b5363..76416b2baf1e1db516c00d92ecb8ad618bf186bd 100644
--- a/protocol/dubbo/codec.go
+++ b/protocol/dubbo/codec.go
@@ -83,7 +83,13 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) {
 
 // Unmarshal ...
 func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error {
-	codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, buf.Len()))
+	// fix issue https://github.com/apache/dubbo-go/issues/380
+	bufLen := buf.Len()
+	if bufLen < hessian.HEADER_LENGTH {
+		return perrors.WithStack(hessian.ErrHeaderNotEnough)
+	}
+
+	codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen))
 
 	// read header
 	err := codec.ReadHeader(&p.Header)
diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/codec_test.go
index 149644a6eadc23a72b672ae9fa653a1991802dc0..5dc71f0d080c8c862d68029c7983a4407913307e 100644
--- a/protocol/dubbo/codec_test.go
+++ b/protocol/dubbo/codec_test.go
@@ -18,12 +18,14 @@
 package dubbo
 
 import (
+	"bytes"
 	"testing"
 	"time"
 )
 
 import (
 	hessian "github.com/apache/dubbo-go-hessian2"
+	perrors "github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -72,3 +74,10 @@ func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) {
 	assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5])
 	assert.Equal(t, map[string]string{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6])
 }
+
+func TestIssue380(t *testing.T) {
+	pkg := &DubboPackage{}
+	buf := bytes.NewBuffer([]byte("hello"))
+	err := pkg.Unmarshal(buf)
+	assert.True(t, perrors.Cause(err) == hessian.ErrHeaderNotEnough)
+}
diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go
index ef5016e22e7600449a9ace739f06562bae192db0..09c3725710d2a0b821d8e641b0cb7b367189c244 100644
--- a/protocol/dubbo/dubbo_invoker.go
+++ b/protocol/dubbo/dubbo_invoker.go
@@ -128,7 +128,7 @@ func (di *DubboInvoker) Destroy() {
 		for {
 			if di.reqNum == 0 {
 				di.reqNum = -1
-				logger.Info("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key())
+				logger.Infof("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key())
 				di.BaseInvoker.Destroy()
 				if di.client != nil {
 					di.client.Close()
diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go
index 430c4e49d81d4d5d151a545e1a130fd4ac3fbdc5..0251b78a2b0d27a68461c16c284b1af53bcb08aa 100644
--- a/protocol/dubbo/listener.go
+++ b/protocol/dubbo/listener.go
@@ -124,6 +124,7 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) {
 
 	pendingResponse := h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID))
 	if pendingResponse == nil {
+		logger.Errorf("failed to get pending response context for response package %s", *p)
 		return
 	}
 
diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go
index 9d381585b56ec439f8ebb8938109baf47bb502b2..918514c2676cfc69336a9f53e6d16d7f23cf7dca 100644
--- a/protocol/dubbo/pool.go
+++ b/protocol/dubbo/pool.go
@@ -78,7 +78,7 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge
 		}
 		time.Sleep(1e6)
 	}
-	logger.Infof("client init ok")
+	logger.Debug("client init ok")
 	c.updateActive(time.Now().Unix())
 
 	return c, nil
@@ -319,9 +319,10 @@ func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPC
 	conn, err := p.get()
 	if err == nil && conn == nil {
 		// create new conn
-		return newGettyRPCClientConn(p, protocol, addr)
+		rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr)
+		return rpcClientConn, perrors.WithStack(err)
 	}
-	return conn, err
+	return conn, perrors.WithStack(err)
 }
 
 func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) {
diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto
index d68e1dd37b3276760623d214662854e931bbdd09..e73f72b1e06c90bd917e905f992efbddd744b4ad 100644
--- a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto
+++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto
@@ -1,16 +1,19 @@
-// Copyright 2015 The gRPC Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
 syntax = "proto3";
 
 option java_multiple_files = true;
diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go
index 639fd559aa16689a249d035895fc037dc3bc3f8b..13be47c98ece1cc006250ad49ab2b9a8c3b1f625 100644
--- a/protocol/rpc_status.go
+++ b/protocol/rpc_status.go
@@ -153,7 +153,7 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) {
 		}
 		atomic.StoreInt32(&rpcStatus.successiveRequestFailureCount, 0)
 	} else {
-		atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, time.Now().Unix())
+		atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, CurrentTimeMillis())
 		atomic.AddInt32(&rpcStatus.successiveRequestFailureCount, 1)
 		atomic.AddInt32(&rpcStatus.failed, 1)
 		atomic.AddInt64(&rpcStatus.failedElapsed, elapsed)
@@ -167,3 +167,17 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) {
 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 {
+		methodStatistics.Delete(key)
+		return true
+	}
+	methodStatistics.Range(delete1)
+	delete2 := func(key interface{}, value interface{}) bool {
+		serviceStatistic.Delete(key)
+		return true
+	}
+	serviceStatistic.Range(delete2)
+}
diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go
index ffdb3b535667f32e96c3af2be84851655abf5954..5a07f44eab0db60ba65a155d6c6190ab4ce2d716 100644
--- a/protocol/rpc_status_test.go
+++ b/protocol/rpc_status_test.go
@@ -14,7 +14,7 @@ import (
 )
 
 func TestBeginCount(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	BeginCount(url, "test")
@@ -28,7 +28,7 @@ func TestBeginCount(t *testing.T) {
 }
 
 func TestEndCount(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	EndCount(url, "test", 100, true)
@@ -41,7 +41,7 @@ func TestEndCount(t *testing.T) {
 }
 
 func TestGetMethodStatus(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	status := GetMethodStatus(url, "test")
@@ -50,7 +50,7 @@ func TestGetMethodStatus(t *testing.T) {
 }
 
 func TestGetUrlStatus(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	status := GetURLStatus(url)
@@ -59,7 +59,7 @@ func TestGetUrlStatus(t *testing.T) {
 }
 
 func Test_beginCount0(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	status := GetURLStatus(url)
@@ -68,7 +68,7 @@ func Test_beginCount0(t *testing.T) {
 }
 
 func Test_All(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 
 	url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider")
 	request(url, "test", 100, false, true)
@@ -129,23 +129,10 @@ func request(url common.URL, method string, elapsed int64, active, succeeded boo
 }
 
 func TestCurrentTimeMillis(t *testing.T) {
-	defer destroy()
+	defer CleanAllStatus()
 	c := CurrentTimeMillis()
 	assert.NotNil(t, c)
 	str := strconv.FormatInt(c, 10)
 	i, _ := strconv.ParseInt(str, 10, 64)
 	assert.Equal(t, c, i)
 }
-
-func destroy() {
-	delete1 := func(key interface{}, value interface{}) bool {
-		methodStatistics.Delete(key)
-		return true
-	}
-	methodStatistics.Range(delete1)
-	delete2 := func(key interface{}, value interface{}) bool {
-		serviceStatistic.Delete(key)
-		return true
-	}
-	serviceStatistic.Range(delete2)
-}
diff --git a/registry/directory/directory.go b/registry/directory/directory.go
index 42d03e40bef4f078d9fd8e746119523d3d0725b2..a6d2cdf49b0935b2402e03208d1ff5f702e1cc52 100644
--- a/registry/directory/directory.go
+++ b/registry/directory/directory.go
@@ -24,6 +24,7 @@ import (
 
 import (
 	perrors "github.com/pkg/errors"
+	"go.uber.org/atomic"
 )
 
 import (
@@ -61,6 +62,8 @@ type registryDirectory struct {
 	consumerConfigurationListener  *consumerConfigurationListener
 	referenceConfigurationListener *referenceConfigurationListener
 	Options
+	serviceKey string
+	forbidden  atomic.Bool
 }
 
 // NewRegistryDirectory ...
@@ -124,14 +127,23 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) {
 		} else if url.Protocol == constant.ROUTER_PROTOCOL || //2.for router
 			url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY {
 			url = nil
-			//TODO: router
 		}
 		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() {
+				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:
-			//dir.cacheService.EventTypeDel(res.Path, dir.serviceTTL)
 			oldInvoker = dir.uncacheInvoker(url)
 			logger.Infof("selector delete service url{%s}", res.Service)
 		default:
@@ -179,6 +191,7 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker {
 		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))
 		}
 	}
@@ -215,13 +228,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
 		newUrl := common.MergeUrl(url, referenceUrl)
 		dir.overrideUrl(newUrl)
 		if cacheInvoker, ok := dir.cacheInvokersMap.Load(newUrl.Key()); !ok {
-			logger.Infof("service will be added in cache invokers: invokers url is  %s!", newUrl)
+			logger.Debugf("service will be added in cache invokers: invokers url is  %s!", newUrl)
 			newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl)
 			if newInvoker != nil {
 				dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker)
 			}
 		} else {
-			logger.Infof("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl())
+			logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl())
 			newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl)
 			if newInvoker != nil {
 				dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker)
@@ -234,8 +247,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker {
 
 //select the protocol invokers from the directory
 func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker {
-	//TODO:router
-	return dir.cacheInvokers
+	invokers := dir.cacheInvokers
+	routerChain := dir.RouterChain()
+
+	if routerChain == nil {
+		return invokers
+	}
+	return routerChain.Route(invokers, dir.cacheOriginUrl, invocation)
 }
 
 func (dir *registryDirectory) IsAvailable() bool {
diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go
index 8ebd130d7d1797a9d8707628d7e5920be758e389..0dde44e73c18f65f262e01f499e198995907dece 100644
--- a/registry/directory/directory_test.go
+++ b/registry/directory/directory_test.go
@@ -30,6 +30,8 @@ import (
 
 import (
 	"github.com/apache/dubbo-go/cluster/cluster_impl"
+	_ "github.com/apache/dubbo-go/cluster/router"
+	_ "github.com/apache/dubbo-go/cluster/router/condition"
 	"github.com/apache/dubbo-go/common"
 	"github.com/apache/dubbo-go/common/constant"
 	"github.com/apache/dubbo-go/common/extension"
@@ -43,6 +45,7 @@ import (
 func init() {
 	config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}})
 }
+
 func TestSubscribe(t *testing.T) {
 	registryDirectory, _ := normalRegistryDir()
 
@@ -104,7 +107,7 @@ func TestSubscribe_Group(t *testing.T) {
 func Test_Destroy(t *testing.T) {
 	registryDirectory, _ := normalRegistryDir()
 
-	time.Sleep(1e9)
+	time.Sleep(3e9)
 	assert.Len(t, registryDirectory.cacheInvokers, 3)
 	assert.Equal(t, true, registryDirectory.IsAvailable())
 
@@ -116,11 +119,12 @@ func Test_Destroy(t *testing.T) {
 func Test_List(t *testing.T) {
 	registryDirectory, _ := normalRegistryDir()
 
-	time.Sleep(1e9)
+	time.Sleep(4e9)
 	assert.Len(t, registryDirectory.List(&invocation.RPCInvocation{}), 3)
 	assert.Equal(t, true, registryDirectory.IsAvailable())
 
 }
+
 func Test_MergeProviderUrl(t *testing.T) {
 	registryDirectory, mockRegistry := normalRegistryDir(true)
 	providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService",
diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go
index 79e3ad514584937e742db4bbc993202dd6a9f5b9..f9b046a2c52814cd4e5ea38f9ea4c58c8bdb5bc4 100644
--- a/registry/etcdv3/listener.go
+++ b/registry/etcdv3/listener.go
@@ -83,6 +83,7 @@ func NewConfigurationListener(reg *etcdV3Registry) *configurationListener {
 	reg.WaitGroup().Add(1)
 	return &configurationListener{registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)}
 }
+
 func (l *configurationListener) Process(configType *config_center.ConfigChangeEvent) {
 	l.events <- configType
 }
@@ -108,6 +109,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) {
 		}
 	}
 }
+
 func (l *configurationListener) Close() {
 	l.registry.WaitGroup().Done()
 }
diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go
index 748b8204d97e60c9803821290184fc5717c41025..a7678ba4e2f38cfeb77f202103e03066a7efdbef 100644
--- a/registry/protocol/protocol.go
+++ b/registry/protocol/protocol.go
@@ -77,6 +77,7 @@ func newRegistryProtocol() *registryProtocol {
 		bounds:     &sync.Map{},
 	}
 }
+
 func getRegistry(regUrl *common.URL) registry.Registry {
 	reg, err := extension.GetRegistry(regUrl.Protocol, regUrl)
 	if err != nil {
@@ -85,13 +86,14 @@ func getRegistry(regUrl *common.URL) registry.Registry {
 	}
 	return reg
 }
+
 func (proto *registryProtocol) initConfigurationListeners() {
 	proto.overrideListeners = &sync.Map{}
 	proto.serviceConfigurationListeners = &sync.Map{}
 	proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners)
 }
-func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
 
+func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
 	var registryUrl = url
 	var serviceUrl = registryUrl.SubURL
 	if registryUrl.Protocol == constant.REGISTRY_PROTOCOL {
@@ -115,6 +117,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
 			serviceUrl.String(), err.Error())
 		return nil
 	}
+
 	err = reg.Register(*serviceUrl)
 	if err != nil {
 		logger.Errorf("consumer service %v register registry %v error, error message is %s",
@@ -131,7 +134,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
 }
 
 func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
-
 	proto.once.Do(func() {
 		proto.initConfigurationListeners()
 	})
@@ -172,13 +174,14 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte
 		wrappedInvoker := newWrappedInvoker(invoker, providerUrl)
 		cachedExporter = extension.GetProtocol(protocolwrapper.FILTER).Export(wrappedInvoker)
 		proto.bounds.Store(key, cachedExporter)
-		logger.Infof("The exporter has not been cached, and will return a new  exporter!")
+		logger.Infof("The exporter has not been cached, and will return a new exporter!")
 	}
 
 	go reg.Subscribe(overriderUrl, overrideSubscribeListener)
 	return cachedExporter.(protocol.Exporter)
 
 }
+
 func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) {
 	url := getProviderUrl(invoker)
 	key := getCacheKey(url)
@@ -202,12 +205,14 @@ type overrideSubscribeListener struct {
 func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener {
 	return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto}
 }
+
 func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) {
 	if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd {
 		nl.configurator = extension.GetDefaultConfigurator(&(event.Service))
 		nl.doOverrideIfNecessary()
 	}
 }
+
 func (nl *overrideSubscribeListener) doOverrideIfNecessary() {
 	providerUrl := getProviderUrl(nl.originInvoker)
 	key := getCacheKey(providerUrl)
@@ -276,6 +281,7 @@ func isMatched(providerUrl *common.URL, consumerUrl *common.URL) bool {
 		consumerVersion == providerVersion) && (len(consumerClassifier) == 0 ||
 		consumerClassifier == constant.ANY_VALUE || consumerClassifier == providerClassifier)
 }
+
 func isMatchCategory(category string, categories string) bool {
 	if len(categories) == 0 {
 		return category == constant.DEFAULT_CATEGORY
@@ -287,6 +293,7 @@ func isMatchCategory(category string, categories string) bool {
 		return strings.Contains(categories, category)
 	}
 }
+
 func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL {
 	newUrl := providerUrl.Clone()
 	newUrl.Protocol = constant.PROVIDER_PROTOCOL
@@ -334,6 +341,7 @@ func getProviderUrl(invoker protocol.Invoker) *common.URL {
 	//be careful params maps in url is map type
 	return url.SubURL.Clone()
 }
+
 func setProviderUrl(regURL *common.URL, providerURL *common.URL) {
 	regURL.SubURL = providerURL
 }
diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go
index de57a0afa7529dd5c77c1fe5440b336cdd212fca..cee2a6a625368f655d1b9bc5fe8cc37031e1aef7 100644
--- a/registry/protocol/protocol_test.go
+++ b/registry/protocol/protocol_test.go
@@ -44,6 +44,7 @@ import (
 func init() {
 	config.SetProviderConfig(config.ProviderConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}})
 }
+
 func referNormal(t *testing.T, regProtocol *registryProtocol) {
 	extension.SetProtocol("registry", GetProtocol)
 	extension.SetRegistry("mock", registry.NewMockRegistry)
diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go
index 588e0c519288ed32a2453fac87d226b41d4a5194..fe8e42db9f39190e34142149a6b67c9638a84ed2 100644
--- a/registry/zookeeper/listener.go
+++ b/registry/zookeeper/listener.go
@@ -56,7 +56,7 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool {
 	// Intercept the last bit
 	index := strings.Index(eventType.Path, "/providers/")
 	if index == -1 {
-		logger.Warn("Listen with no url, event.path={%v}", eventType.Path)
+		logger.Warnf("Listen with no url, event.path={%v}", eventType.Path)
 		return false
 	}
 	url := eventType.Path[index+len("/providers/"):]
diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go
index f4e53dcc4219d947fea93a10bccc420811afd2b9..e13443d57d7dae9fb5d50b2e1c28f618780fd850 100644
--- a/registry/zookeeper/registry.go
+++ b/registry/zookeeper/registry.go
@@ -141,6 +141,7 @@ func (r *zkRegistry) CloseAndNilClient() {
 	r.client.Close()
 	r.client = nil
 }
+
 func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient {
 	return r.client
 }
diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go
index 8f9b80cd30a9791709a0b2e83b9e59e0046f4c6c..d9166fc8eac78b8d1c5a93f05b7cf5fc9705e10f 100644
--- a/remoting/etcdv3/client_test.go
+++ b/remoting/etcdv3/client_test.go
@@ -31,6 +31,7 @@ import (
 import (
 	"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"
@@ -171,6 +172,10 @@ func (suite *ClientTestSuite) TestClientDone() {
 	}()
 
 	c.Wait.Wait()
+
+	if c.Valid() == true {
+		suite.T().Fatal("client should be invalid then")
+	}
 }
 
 func (suite *ClientTestSuite) TestClientCreateKV() {
@@ -295,13 +300,26 @@ func (suite *ClientTestSuite) TestClientWatch() {
 			t.Fatal(err)
 		}
 
+		events := make([]mvccpb.Event, 0)
+		var eCreate, eDelete mvccpb.Event
+
 		for e := range wc {
 
 			for _, event := range e.Events {
+				events = append(events, (mvccpb.Event)(*event))
+				if event.Type == mvccpb.PUT {
+					eCreate = (mvccpb.Event)(*event)
+				}
+				if event.Type == mvccpb.DELETE {
+					eDelete = (mvccpb.Event)(*event)
+				}
 				t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value)
 			}
 		}
 
+		assert.Equal(t, 2, len(events))
+		assert.Contains(t, events, eCreate)
+		assert.Contains(t, events, eDelete)
 	}()
 
 	for _, tc := range tests {
@@ -334,25 +352,35 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() {
 	wg.Add(1)
 
 	go func() {
+		defer wg.Done()
+
 		completePath := path.Join("scott", "wang")
 		wc, err := observeC.watch(completePath)
 		if err != nil {
 			t.Fatal(err)
 		}
 
+		events := make([]mvccpb.Event, 0)
+		var eCreate, eDelete mvccpb.Event
+
 		for e := range wc {
 
 			for _, event := range e.Events {
-
+				events = append(events, (mvccpb.Event)(*event))
 				if event.Type == mvccpb.DELETE {
+					eDelete = (mvccpb.Event)(*event)
 					t.Logf("complete key (%s) is delete", completePath)
-					wg.Done()
 					observeC.Close()
-					return
+					break
 				}
+				eCreate = (mvccpb.Event)(*event)
 				t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value)
 			}
 		}
+
+		assert.Equal(t, 2, len(events))
+		assert.Contains(t, events, eCreate)
+		assert.Contains(t, events, eDelete)
 	}()
 
 	_, err := c.RegisterTemp("scott", "wang")
diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go
index f95231b374230c93036e0fbd74aeca4ecfe57f46..21486aab59c3f9b44c25b68d7433f864a990149a 100644
--- a/remoting/zookeeper/client.go
+++ b/remoting/zookeeper/client.go
@@ -412,7 +412,7 @@ func (z *ZookeeperClient) Create(basePath string) error {
 
 		if err != nil {
 			if err == zk.ErrNodeExists {
-				logger.Infof("zk.create(\"%s\") exists\n", tmpPath)
+				logger.Debugf("zk.create(\"%s\") exists\n", tmpPath)
 			} else {
 				logger.Errorf("zk.create(\"%s\") error(%v)\n", tmpPath, perrors.WithStack(err))
 				return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath)
diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go
index 97ea775652cf82ce86388fb376832ccb7e07a205..a41f6cd3230700332519ce1c2d3489bfcc4b6ef0 100644
--- a/remoting/zookeeper/facade_test.go
+++ b/remoting/zookeeper/facade_test.go
@@ -70,6 +70,7 @@ func (r *mockFacade) Destroy() {
 func (r *mockFacade) RestartCallBack() bool {
 	return true
 }
+
 func (r *mockFacade) IsAvailable() bool {
 	return true
 }
diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go
index 0d1d4bd27a430a6211a960ab1ca882a73f91a01e..77aa05ee9eada327475fa5bf86c7af2c65de0ef2 100644
--- a/remoting/zookeeper/listener.go
+++ b/remoting/zookeeper/listener.go
@@ -245,7 +245,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi
 			if err != nil {
 				logger.Errorf("Get new node path {%v} 's content error,message is  {%v}", dubboPath, perrors.WithStack(err))
 			}
-			logger.Infof("Get children!{%s}", dubboPath)
+			logger.Debugf("Get children!{%s}", dubboPath)
 			if !listener.DataChange(remoting.Event{Path: dubboPath, Action: remoting.EventTypeAdd, Content: string(content)}) {
 				continue
 			}
diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go
index 43e9aca3f44470873c3c97ec2447bebcc57e5545..7301cd52c392b6950b3a49f78e8124eae532b083 100644
--- a/remoting/zookeeper/listener_test.go
+++ b/remoting/zookeeper/listener_test.go
@@ -66,6 +66,7 @@ func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Even
 
 	return ts, client, event
 }
+
 func TestListener(t *testing.T) {
 	changedData := `
 	dubbo.consumer.request_timeout=3s