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

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git

commit 4167f3b03b3b47cb0e415717ee5c2a47c8090271
Author: liubao <[email protected]>
AuthorDate: Tue Jan 5 10:59:04 2021 +0800

    [SCB-2116]add consumer implementation and add test cases
---
 .../pom.xml                                        |   4 +
 .../demo/zeroconfig/client/GovernanceEndpoint.java |  61 ++++++++
 .../src/main/resources/application.yml             |  65 ++++++++-
 .../pom.xml                                        |   4 +
 .../demo/zeroconfig/server/GovernanceEndpoint.java |  67 +++++++++
 .../src/main/resources/application.yml             |   8 +-
 .../demo/zeroconfig/tests/GovernanceTest.java      | 157 +++++++++++++++++++++
 .../src/main/resources/registry.yaml               |   1 +
 dependencies/bom/pom.xml                           |   5 +
 distribution/pom.xml                               |   4 +
 .../servicecomb/governance/policy/RetryPolicy.java |   4 +-
 .../governance/ConsumerGovernanceHandler.java      | 157 +++++++++++++++++++++
 .../governance/ProviderGovernanceHandler.java      |  46 ++++--
 .../governance/ServiceCombInvocationContext.java   |  60 ++++++++
 .../governance/ServiceCombRetryExtension.java      |  69 +++++++++
 .../src/main/resources/config/cse.handler.xml      |   2 +
 .../loadbalance/LoadbalanceHandler.java            |  14 +-
 solutions/solution-basic/pom.xml                   |   4 +
 18 files changed, 711 insertions(+), 21 deletions(-)

diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/pom.xml
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/pom.xml
index f087a38..0f154b1 100644
--- 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/pom.xml
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/pom.xml
@@ -42,6 +42,10 @@
     </dependency>
     <dependency>
       <groupId>org.apache.servicecomb</groupId>
+      <artifactId>handler-governance</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
       <artifactId>registry-schema-discovery</artifactId>
     </dependency>
     <dependency>
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/GovernanceEndpoint.java
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/GovernanceEndpoint.java
new file mode 100644
index 0000000..0d99ffe
--- /dev/null
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/java/org/apache/servicecomb/demo/zeroconfig/client/GovernanceEndpoint.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.demo.zeroconfig.client;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.client.RestTemplate;
+
+@RestSchema(schemaId = "GovernanceEndpoint")
+@RequestMapping("/governance")
+public class GovernanceEndpoint {
+  private static final String SERVER = 
"servicecomb://demo-zeroconfig-schemadiscovery-registry-server";
+
+  private RestTemplate restTemplate = RestTemplateBuilder.create();
+
+  private int count = 0;
+
+  @GetMapping("/hello")
+  public String hello() {
+    return restTemplate.getForObject(SERVER + "/governance/hello", 
String.class);
+  }
+
+  @GetMapping("/retry")
+  public String retry(@RequestParam(name = "invocationID") String 
invocationID) {
+    return restTemplate
+        .getForObject(SERVER + "/governance/retry?invocationID={1}", 
String.class,
+            invocationID);
+  }
+
+  @GetMapping("/circuitBreaker")
+  public String circuitBreaker() throws Exception {
+    count++;
+    if (count % 3 == 0) {
+      return "ok";
+    }
+    throw new RuntimeException("test error");
+  }
+
+  @GetMapping("/bulkhead")
+  public String bulkhead() {
+    return restTemplate.getForObject(SERVER + "/governance/hello", 
String.class);
+  }
+}
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/resources/application.yml
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/resources/application.yml
index 6bbfed6..3d8453f 100644
--- 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/resources/application.yml
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-client/src/main/resources/application.yml
@@ -25,9 +25,68 @@ service_description:
   name: demo-zeroconfig-schemadiscovery-registry-client
   version: 0.0.2
 servicecomb:
+  rest:
+    address: 0.0.0.0:8082
+
   handler:
     chain:
       Consumer:
-        default: loadbalance
-  rest:
-    address: 0.0.0.0:8082
\ No newline at end of file
+        default: governance-consumer,loadbalance
+      Provider:
+        default: governance-provider
+
+  matchGroup:
+    demo-rateLimiting: |
+      matches:
+        - apiPath:
+            exact: "GovernanceEndpoint.hello"
+          name: xx
+      ## services is optional in configuration file
+      services: demo-zeroconfig-schemadiscovery-registry-client
+    demo-retry: |
+      matches:
+        - apiPath:
+            exact: "GovernanceEndpoint.retry"
+          name: xx
+      ## services is optional in configuration file
+      services: demo-zeroconfig-schemadiscovery-registry-client
+    demo-circuitBreaker: |
+      matches:
+        - apiPath:
+            exact: "GovernanceEndpoint.circuitBreaker"
+          name: xx
+      ## services is optional in configuration file
+      services: demo-zeroconfig-schemadiscovery-registry-client
+    demo-bulkhead: |
+      matches:
+        - apiPath:
+            exact: "GovernanceEndpoint.bulkhead"
+          name: xx
+      ## services is optional in configuration file
+      services: demo-zeroconfig-schemadiscovery-registry-client
+  rateLimiting:
+    demo-rateLimiting: |
+      rules:
+        match: demo-rateLimiting.xx
+      rate: 10
+      name: xx
+  retry:
+    demo-retry: |
+      rules:
+        match: demo-retry.xx
+      maxAttempts: 3
+      name: xx
+  circuitBreaker:
+    demo-circuitBreaker: |
+      rules:
+        match: demo-circuitBreaker.xx
+      minimumNumberOfCalls: 10
+      slidingWindowSize: 10
+      failureRateThreshold: 20
+      name: xx
+  bulkhead:
+    demo-bulkhead: |
+      rules:
+        match: demo-bulkhead.xx
+      maxConcurrentCalls: 5
+      name: xx
\ No newline at end of file
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/pom.xml
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/pom.xml
index 77d7783..4ad699f 100644
--- 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/pom.xml
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/pom.xml
@@ -42,6 +42,10 @@
     </dependency>
     <dependency>
       <groupId>org.apache.servicecomb</groupId>
+      <artifactId>handler-governance</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
       <artifactId>registry-schema-discovery</artifactId>
     </dependency>
     <dependency>
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/GovernanceEndpoint.java
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/GovernanceEndpoint.java
new file mode 100644
index 0000000..718a242
--- /dev/null
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/java/org/apache/servicecomb/demo/zeroconfig/server/GovernanceEndpoint.java
@@ -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 org.apache.servicecomb.demo.zeroconfig.server;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+@RestSchema(schemaId = "GovernanceEndpoint")
+@RequestMapping("/governance")
+public class GovernanceEndpoint {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(GovernanceEndpoint.class);
+
+  private Map<String, Integer> retryTimes = new HashMap<>();
+
+  @GetMapping("/hello")
+  public String sayHello() {
+    return "Hello world!";
+  }
+
+  @GetMapping("/retry")
+  @ApiResponses({
+      @ApiResponse(code = 200, response = String.class, message = ""),
+      @ApiResponse(code = 502, response = String.class, message = "")})
+  public String retry(@RequestParam(name = "invocationID") String 
invocationID) {
+    LOGGER.info("invoke service: {}", invocationID);
+    retryTimes.putIfAbsent(invocationID, 0);
+    retryTimes.put(invocationID, retryTimes.get(invocationID) + 1);
+
+    int retry = retryTimes.get(invocationID);
+
+    if (retry == 3) {
+      return "try times: " + retry;
+    }
+    throw new InvocationException(502, "retry", "retry");
+  }
+
+  @GetMapping("/circuitBreaker")
+  public String circuitBreaker() {
+    throw new RuntimeException("circuitBreaker by provider.");
+  }
+}
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/resources/application.yml
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/resources/application.yml
index 99e7de1..9f6d98b 100644
--- 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/resources/application.yml
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-server/src/main/resources/application.yml
@@ -26,4 +26,10 @@ service_description:
   version: 0.0.2
 servicecomb:
   rest:
-    address: 0.0.0.0:8080
\ No newline at end of file
+    address: 0.0.0.0:8080
+  handler:
+    chain:
+      Consumer:
+        default: governance-consumer,loadbalance
+      Provider:
+        default: governance-provider
\ No newline at end of file
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/GovernanceTest.java
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/GovernanceTest.java
new file mode 100644
index 0000000..40161bf
--- /dev/null
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/java/org/apache/servicecomb/demo/zeroconfig/tests/GovernanceTest.java
@@ -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 org.apache.servicecomb.demo.zeroconfig.tests;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.servicecomb.demo.CategorizedTestCase;
+import org.apache.servicecomb.demo.TestMgr;
+import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class GovernanceTest implements CategorizedTestCase {
+  String url = 
"servicecomb://demo-zeroconfig-schemadiscovery-registry-client/governance";
+
+  RestTemplate template = RestTemplateBuilder.create();
+
+  @Override
+  public void testRestTransport() throws Exception {
+    testCircuitBreaker();
+    testBulkhead();
+    testRateLimiting();
+    testRetry();
+  }
+
+  private void testRetry() {
+    String invocationID = UUID.randomUUID().toString();
+    String result = template.getForObject(url + "/retry?invocationID={1}", 
String.class, invocationID);
+    TestMgr.check(result, "try times: 3");
+  }
+
+  private void testCircuitBreaker() throws Exception {
+    CountDownLatch latch = new CountDownLatch(100);
+    AtomicBoolean expectedFailed = new AtomicBoolean(false);
+    AtomicBoolean notExpectedFailed = new AtomicBoolean(false);
+
+    for (int i = 0; i < 10; i++) {
+      for (int j = 0; j < 10; j++) {
+        String name = "t-" + i + "-" + j;
+        new Thread(name) {
+          public void run() {
+            try {
+              String result = template.getForObject(url + "/circuitBreaker", 
String.class);
+              if (!"ok".equals(result)) {
+                notExpectedFailed.set(true);
+              }
+            } catch (Exception e) {
+              if (!"InvocationException: code=429;msg=CommonExceptionData 
[message=circuitBreaker is open.]"
+                  .equals(e.getMessage())
+                  && !e.getMessage().equals(
+                  "InvocationException: code=500;msg={message=Unexpected 
exception when processing the request.}")) {
+                notExpectedFailed.set(true);
+              }
+              if ("InvocationException: code=429;msg=CommonExceptionData 
[message=circuitBreaker is open.]"
+                  .equals(e.getMessage())) {
+                expectedFailed.set(true);
+              }
+            }
+            latch.countDown();
+          }
+        }.start();
+      }
+      Thread.sleep(100);
+    }
+
+    latch.await(20, TimeUnit.SECONDS);
+    TestMgr.check(true, expectedFailed.get());
+    TestMgr.check(false, notExpectedFailed.get());
+  }
+
+  private void testBulkhead() throws Exception {
+    CountDownLatch latch = new CountDownLatch(100);
+    AtomicBoolean expectedFailed = new AtomicBoolean(false);
+    AtomicBoolean notExpectedFailed = new AtomicBoolean(false);
+
+    for (int i = 0; i < 10; i++) {
+      for (int j = 0; j < 10; j++) {
+        String name = "t-" + i + "-" + j;
+        new Thread(name) {
+          public void run() {
+            try {
+              String result = template.getForObject(url + "/bulkhead", 
String.class);
+              if (!"Hello world!".equals(result)) {
+                notExpectedFailed.set(true);
+              }
+            } catch (Exception e) {
+              if (!"InvocationException: code=429;msg=CommonExceptionData 
[message=bulkhead is full and does not permit further calls.]"
+                  .equals(e.getMessage())) {
+                notExpectedFailed.set(true);
+              }
+              expectedFailed.set(true);
+            }
+            latch.countDown();
+          }
+        }.start();
+      }
+      Thread.sleep(100);
+    }
+
+    latch.await(20, TimeUnit.SECONDS);
+    TestMgr.check(true, expectedFailed.get());
+    TestMgr.check(false, notExpectedFailed.get());
+  }
+
+  private void testRateLimiting() throws Exception {
+    CountDownLatch latch = new CountDownLatch(100);
+    AtomicBoolean expectedFailed = new AtomicBoolean(false);
+    AtomicBoolean notExpectedFailed = new AtomicBoolean(false);
+
+    for (int i = 0; i < 10; i++) {
+      for (int j = 0; j < 10; j++) {
+        String name = "t-" + i + "-" + j;
+        new Thread(name) {
+          public void run() {
+            try {
+              String result = template.getForObject(url + "/hello", 
String.class);
+              if (!"Hello world!".equals(result)) {
+                notExpectedFailed.set(true);
+              }
+            } catch (Exception e) {
+              if (!"InvocationException: code=429;msg=CommonExceptionData 
[message=rate limited.]"
+                  .equals(e.getMessage())) {
+                notExpectedFailed.set(true);
+              }
+              expectedFailed.set(true);
+            }
+            latch.countDown();
+          }
+        }.start();
+      }
+      Thread.sleep(100);
+    }
+
+    latch.await(20, TimeUnit.SECONDS);
+    TestMgr.check(true, expectedFailed.get());
+    TestMgr.check(false, notExpectedFailed.get());
+  }
+}
diff --git 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/registry.yaml
 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/registry.yaml
index df728fc..cdd8fc7 100644
--- 
a/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/registry.yaml
+++ 
b/demo/demo-zeroconfig-schemadiscovery-registry/demo-zeroconfig-schemadiscovery-registry-tests/src/main/resources/registry.yaml
@@ -22,6 +22,7 @@ demo-zeroconfig-schemadiscovery-registry-client:
     schemaIds:
       - ClientServerEndpoint
       - SchemaDiscoveryEndpoint
+      - GovernanceEndpoint
     instances:
       - endpoints:
           - rest://localhost:8082
diff --git a/dependencies/bom/pom.xml b/dependencies/bom/pom.xml
index 56c7626..f1a877e 100644
--- a/dependencies/bom/pom.xml
+++ b/dependencies/bom/pom.xml
@@ -205,6 +205,11 @@
       </dependency>
       <dependency>
         <groupId>org.apache.servicecomb</groupId>
+        <artifactId>handler-governance</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.servicecomb</groupId>
         <artifactId>handler-loadbalance</artifactId>
         <version>${project.version}</version>
       </dependency>
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 9b070d2..7a5793b 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -170,6 +170,10 @@
     </dependency>
     <dependency>
       <groupId>org.apache.servicecomb</groupId>
+      <artifactId>handler-governance</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
       <artifactId>handler-loadbalance</artifactId>
     </dependency>
     <dependency>
diff --git 
a/governance/src/main/java/org/apache/servicecomb/governance/policy/RetryPolicy.java
 
b/governance/src/main/java/org/apache/servicecomb/governance/policy/RetryPolicy.java
index 4efd065..b6ec4b9 100644
--- 
a/governance/src/main/java/org/apache/servicecomb/governance/policy/RetryPolicy.java
+++ 
b/governance/src/main/java/org/apache/servicecomb/governance/policy/RetryPolicy.java
@@ -30,14 +30,14 @@ public class RetryPolicy extends AbstractPolicy {
 
   public static final int DEFAULT_MAX_ATTEMPTS = 3;
 
-  public static final int DEFAULT_WAIT_DURATION = 0;
+  public static final int DEFAULT_WAIT_DURATION = 1;
 
   public static final String DEFAULT_RETRY_ON_RESPONSE_STATUS = "502";
 
   //最多尝试次数
   private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
 
-  //每次重试尝试等待的时间,默认给0
+  //每次重试尝试等待的时间,默认给1。 在异步场景下,这个值必须大于0,否则不会重试。
   private int waitDuration = DEFAULT_WAIT_DURATION;
 
   //需要重试的http status, 逗号分隔
diff --git 
a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerGovernanceHandler.java
 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerGovernanceHandler.java
new file mode 100644
index 0000000..8443e73
--- /dev/null
+++ 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerGovernanceHandler.java
@@ -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 org.apache.servicecomb.handler.governance;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import org.apache.servicecomb.core.Handler;
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.provider.consumer.SyncResponseExecutor;
+import org.apache.servicecomb.foundation.common.utils.BeanUtils;
+import org.apache.servicecomb.governance.MatchersManager;
+import org.apache.servicecomb.governance.handler.RetryHandler;
+import org.apache.servicecomb.governance.marker.GovHttpRequest;
+import org.apache.servicecomb.governance.policy.RetryPolicy;
+import org.apache.servicecomb.governance.properties.RetryProperties;
+import org.apache.servicecomb.registry.RegistrationManager;
+import org.apache.servicecomb.swagger.invocation.AsyncResponse;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.github.resilience4j.decorators.Decorators;
+import io.github.resilience4j.decorators.Decorators.DecorateCompletionStage;
+
+public class ConsumerGovernanceHandler implements Handler {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(ConsumerGovernanceHandler.class);
+
+  private MatchersManager matchersManager = 
BeanUtils.getBean(MatchersManager.class);
+
+  private RetryHandler retryHandler = BeanUtils.getBean(RetryHandler.class);
+
+  private RetryProperties retryProperties = 
BeanUtils.getBean(RetryProperties.class);
+
+  private static final ScheduledExecutorService RETRY_POOL = 
Executors.newScheduledThreadPool(2, new ThreadFactory() {
+    private AtomicInteger count = new AtomicInteger(0);
+
+    @Override
+    public Thread newThread(Runnable r) {
+      Thread thread = new Thread(r, "governance-retry-pool-thread-" + 
count.getAndIncrement());
+      // avoid block shutdown
+      thread.setDaemon(true);
+      return thread;
+    }
+  });
+
+  @Override
+  public void handle(Invocation invocation, AsyncResponse asyncResp) throws 
Exception {
+    Supplier<CompletionStage<Response>> next = 
createBusinessCompletionStageSupplier(invocation);
+    DecorateCompletionStage<Response> dcs = Decorators.ofCompletionStage(next);
+    GovHttpRequest request = createGovHttpRequest(invocation);
+
+    try {
+      ServiceCombInvocationContext.setInvocationContext(invocation);
+      addRetry(dcs, request);
+    } finally {
+      ServiceCombInvocationContext.removeInvocationContext();
+    }
+
+    final SyncResponseExecutor originalExecutor;
+    final Executor newExecutor;
+    if (invocation.getResponseExecutor() instanceof SyncResponseExecutor) {
+      originalExecutor = (SyncResponseExecutor) 
invocation.getResponseExecutor();
+      newExecutor = command -> {
+        // retry的场景,对于同步调用, 同步调用的主线程已经被挂起,无法再主线程中进行重试;
+        // 重试的场景,主线程等待响应线程唤醒。因此需要转换主线程,响应唤醒新的主线程,在重试逻辑成功后,再唤醒原来的主线程。
+        // 重试也不能在网络线程(event-loop)中进行,未被保护的阻塞操作会导致网络线程挂起
+        RETRY_POOL.submit(command);
+      };
+      invocation.setResponseExecutor(newExecutor);
+    } else {
+      originalExecutor = null;
+      newExecutor = null;
+    }
+
+    dcs.get().whenComplete((r, e) -> {
+      if (e == null) {
+        if (originalExecutor != null) {
+          originalExecutor.execute(() -> {
+            asyncResp.complete(r);
+          });
+        } else {
+          asyncResp.complete(r);
+        }
+        return;
+      }
+
+      if (originalExecutor != null) {
+        originalExecutor.execute(() -> {
+          asyncResp.consumerFail(e);
+        });
+      } else {
+        asyncResp.consumerFail(e);
+      }
+    });
+  }
+
+  private void addRetry(DecorateCompletionStage<Response> dcs, GovHttpRequest 
request) {
+    RetryPolicy retryPolicy = matchersManager.match(request, 
retryProperties.getParsedEntity());
+    if (retryPolicy != null) {
+      dcs.withRetry(retryHandler.getActuator(retryPolicy), RETRY_POOL);
+    }
+  }
+
+  private Supplier<CompletionStage<Response>> 
createBusinessCompletionStageSupplier(Invocation invocation) {
+    final int currentHandler = invocation.getHandlerIndex();
+    final AtomicBoolean isRetryHolder = new AtomicBoolean(false);
+
+    return () -> {
+      CompletableFuture<Response> result = new CompletableFuture<>();
+      if (isRetryHolder.getAndSet(true)) {
+        invocation.setHandlerIndex(currentHandler);
+        LOGGER.info("retry operation {}, trace id {}",
+            invocation.getOperationMeta().getMicroserviceQualifiedName(), 
invocation.getTraceId());
+      }
+      try {
+        invocation.next(response -> {
+          result.complete(response);
+        });
+      } catch (Exception e) {
+        result.completeExceptionally(e);
+      }
+      return result;
+    };
+  }
+
+  private GovHttpRequest createGovHttpRequest(Invocation invocation) {
+    GovHttpRequest request = new 
GovHttpRequest(RegistrationManager.INSTANCE.getMicroservice().getServiceName(),
+        RegistrationManager.INSTANCE.getMicroservice().getVersion());
+    request.setUri(invocation.getSchemaId() + "." + 
invocation.getOperationName());
+    request.setMethod(invocation.getOperationMeta().getHttpMethod());
+    request.setHeaders(invocation.getContext());
+    return request;
+  }
+}
diff --git 
a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java
 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java
index 06d0c50..b3623c3 100644
--- 
a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java
+++ 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ProviderGovernanceHandler.java
@@ -73,13 +73,14 @@ public class ProviderGovernanceHandler implements Handler {
     DecorateCompletionStage<Response> dcs = Decorators.ofCompletionStage(next);
     GovHttpRequest request = createGovHttpRequest(invocation);
 
-    RateLimitingPolicy rateLimitingPolicy = matchersManager.match(request, 
rateLimitProperties.getParsedEntity());
-    dcs.withRateLimiter(rateLimitingHandler.getActuator(rateLimitingPolicy));
-    CircuitBreakerPolicy circuitBreakerPolicy = matchersManager
-        .match(request, circuitBreakerProperties.getParsedEntity());
-    
dcs.withCircuitBreaker(circuitBreakerHandler.getActuator(circuitBreakerPolicy));
-    BulkheadPolicy bulkheadPolicy = matchersManager.match(request, 
bulkheadProperties.getParsedEntity());
-    dcs.withBulkhead(bulkheadHandler.getActuator(bulkheadPolicy));
+    try {
+      ServiceCombInvocationContext.setInvocationContext(invocation);
+      addRateLimiting(dcs, request);
+      addCircuitBreaker(dcs, request);
+      addBulkhead(dcs, request);
+    } finally {
+      ServiceCombInvocationContext.removeInvocationContext();
+    }
 
     dcs.get().whenComplete((r, e) -> {
       if (e == null) {
@@ -107,12 +108,41 @@ public class ProviderGovernanceHandler implements Handler 
{
     });
   }
 
+  private void addBulkhead(DecorateCompletionStage<Response> dcs, 
GovHttpRequest request) {
+    BulkheadPolicy bulkheadPolicy = matchersManager.match(request, 
bulkheadProperties.getParsedEntity());
+    if (bulkheadPolicy != null) {
+      dcs.withBulkhead(bulkheadHandler.getActuator(bulkheadPolicy));
+    }
+  }
+
+  private void addCircuitBreaker(DecorateCompletionStage<Response> dcs, 
GovHttpRequest request) {
+    CircuitBreakerPolicy circuitBreakerPolicy = matchersManager
+        .match(request, circuitBreakerProperties.getParsedEntity());
+    if (circuitBreakerPolicy != null) {
+      
dcs.withCircuitBreaker(circuitBreakerHandler.getActuator(circuitBreakerPolicy));
+    }
+  }
+
+  private void addRateLimiting(DecorateCompletionStage<Response> dcs, 
GovHttpRequest request) {
+    RateLimitingPolicy rateLimitingPolicy = matchersManager.match(request, 
rateLimitProperties.getParsedEntity());
+    if (rateLimitingPolicy != null) {
+      dcs.withRateLimiter(rateLimitingHandler.getActuator(rateLimitingPolicy));
+    }
+  }
+
   private Supplier<CompletionStage<Response>> 
createBusinessCompletionStageSupplier(Invocation invocation) {
     return () -> {
       CompletableFuture<Response> result = new CompletableFuture<>();
       try {
         invocation.next(response -> {
-          result.complete(response);
+          if (response.isFailed()) {
+            // For failed response, create a fail to make circuit breaker work.
+            // Users application maybe much complicated than this simple logic,
+            // while they need to customize which error will cause circuit 
breaker.
+            result.completeExceptionally(response.getResult());
+          } else {
+            result.complete(response);
+          }
         });
       } catch (Exception e) {
         result.completeExceptionally(e);
diff --git 
a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombInvocationContext.java
 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombInvocationContext.java
new file mode 100644
index 0000000..fdedd0a
--- /dev/null
+++ 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombInvocationContext.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.handler.governance;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.governance.InvocationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ServiceCombInvocationContext implements InvocationContext {
+  private static final String CONTEXT_KEY = "x-servicecomb-governance-match";
+
+  private static 
ThreadLocal<org.apache.servicecomb.swagger.invocation.context.InvocationContext>
 contextMgr = new ThreadLocal<>();
+
+  public static void setInvocationContext(
+      org.apache.servicecomb.swagger.invocation.context.InvocationContext 
invocationContext) {
+    contextMgr.set(invocationContext);
+  }
+
+  public static void removeInvocationContext() {
+    contextMgr.remove();
+  }
+
+  @Override
+  public Map<String, Boolean> getCalculatedMatches() {
+    Map<String, Boolean> result = 
contextMgr.get().getLocalContext(CONTEXT_KEY);
+    if (result == null) {
+      return Collections.emptyMap();
+    }
+    return result;
+  }
+
+  @Override
+  public void addMatch(String key, Boolean value) {
+    Map<String, Boolean> result = 
contextMgr.get().getLocalContext(CONTEXT_KEY);
+    if (result == null) {
+      result = new HashMap<>();
+      contextMgr.get().addLocalContext(CONTEXT_KEY, result);
+    }
+    result.put(key, value);
+  }
+}
diff --git 
a/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombRetryExtension.java
 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombRetryExtension.java
new file mode 100644
index 0000000..031fc9a
--- /dev/null
+++ 
b/handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ServiceCombRetryExtension.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.handler.governance;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.NoRouteToHostException;
+import java.net.SocketTimeoutException;
+import java.util.List;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.servicecomb.governance.handler.ext.RetryExtension;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.springframework.stereotype.Component;
+
+import io.vertx.core.VertxException;
+
+@Component
+public class ServiceCombRetryExtension implements RetryExtension {
+  @Override
+  public boolean isRetry(List<Integer> statusList, Object result) {
+    if (result instanceof Response) {
+      Response resp = (Response) result;
+      if (resp.isFailed()) {
+        if (InvocationException.class.isInstance(resp.getResult())) {
+          InvocationException e = resp.getResult();
+          return e.getStatusCode() == 
ExceptionFactory.CONSUMER_INNER_STATUS_CODE
+              || e.getStatusCode() == 
Status.SERVICE_UNAVAILABLE.getStatusCode()
+              || e.getStatusCode() == Status.BAD_GATEWAY.getStatusCode();
+        } else {
+          return true;
+        }
+      } else {
+        return false;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public Class<? extends Throwable>[] retryExceptions() {
+    return new Class[] {
+        ConnectException.class,
+        SocketTimeoutException.class,
+        IOException.class,
+        VertxException.class,
+        NoRouteToHostException.class,
+        InvocationException.class};
+  }
+}
diff --git 
a/handlers/handler-governance/src/main/resources/config/cse.handler.xml 
b/handlers/handler-governance/src/main/resources/config/cse.handler.xml
index f99f63d..c7383f0 100644
--- a/handlers/handler-governance/src/main/resources/config/cse.handler.xml
+++ b/handlers/handler-governance/src/main/resources/config/cse.handler.xml
@@ -18,4 +18,6 @@
 <config>
   <handler id="governance-provider"
     
class="org.apache.servicecomb.handler.governance.ProviderGovernanceHandler"/>
+  <handler id="governance-consumer"
+    
class="org.apache.servicecomb.handler.governance.ConsumerGovernanceHandler"/>
 </config>
diff --git 
a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
 
b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
index dc507c6..58dd4c0 100644
--- 
a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
+++ 
b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
@@ -298,10 +298,10 @@ public class LoadbalanceHandler implements Handler {
     // retry in loadbalance, 2.0 feature
     int currentHandler = invocation.getHandlerIndex();
 
-    SyncResponseExecutor orginExecutor;
+    SyncResponseExecutor originalExecutor;
     Executor newExecutor;
     if (invocation.getResponseExecutor() instanceof SyncResponseExecutor) {
-      orginExecutor = (SyncResponseExecutor) invocation.getResponseExecutor();
+      originalExecutor = (SyncResponseExecutor) 
invocation.getResponseExecutor();
       newExecutor = new Executor() {
         @Override
         public void execute(Runnable command) {
@@ -312,7 +312,7 @@ public class LoadbalanceHandler implements Handler {
       };
       invocation.setResponseExecutor(newExecutor);
     } else {
-      orginExecutor = null;
+      originalExecutor = null;
       newExecutor = null;
     }
 
@@ -346,8 +346,8 @@ public class LoadbalanceHandler implements Handler {
               context.getRequest().getInvocationQualifiedName(),
               context.getRequest().getEndpoint());
         }
-        if (orginExecutor != null) {
-          orginExecutor.execute(() -> {
+        if (originalExecutor != null) {
+          originalExecutor.execute(() -> {
             asyncResp.complete(response);
           });
         } else {
@@ -361,8 +361,8 @@ public class LoadbalanceHandler implements Handler {
         context.getRequest().getTraceIdLogger().error(LOGGER, "Invoke all 
server failed. Operation {}, e={}",
             context.getRequest().getInvocationQualifiedName(),
             ExceptionUtils.getExceptionMessageWithoutTrace(finalException));
-        if (orginExecutor != null) {
-          orginExecutor.execute(() -> {
+        if (originalExecutor != null) {
+          originalExecutor.execute(() -> {
             fail(finalException);
           });
         } else {
diff --git a/solutions/solution-basic/pom.xml b/solutions/solution-basic/pom.xml
index 29e5ce4..0806478 100644
--- a/solutions/solution-basic/pom.xml
+++ b/solutions/solution-basic/pom.xml
@@ -73,6 +73,10 @@
     </dependency>
     <dependency>
       <groupId>org.apache.servicecomb</groupId>
+      <artifactId>handler-governance</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
       <artifactId>handler-loadbalance</artifactId>
     </dependency>
     <dependency>

Reply via email to