1
0
mirror of https://gitee.com/mirrors/Spring-Cloud-Alibaba.git synced 2021-06-26 13:25:11 +08:00

Merge pull request #486 from fangjian0423/master

Optimize CircuitBreaker in RestTemplate & update examples
This commit is contained in:
format 2019-03-27 16:29:54 +08:00 committed by GitHub
commit eec3beb366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 163 additions and 31 deletions

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
n=1
while [ $n -le 10 ]
do
echo `curl -s http://localhost:18083/test`
let n++
done

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
n=1
while [ $n -le 10 ]
do
echo `curl -s http://localhost:18083/divide-feign?a=1\&b=0`
let n++
done

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
n=1
while [ $n -le 10 ]
do
echo `curl -s http://localhost:18083/index`
let n++
done

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
n=1
while [ $n -le 10 ]
do
echo `curl -s http://localhost:18083/sleep`
let n++
done

View File

@ -3,6 +3,7 @@ package org.springframework.cloud.alibaba.cloud.examples;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService; import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService;
import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
@ -28,6 +29,13 @@ public class ConsumerApplication {
return new RestTemplate(); return new RestTemplate();
} }
@LoadBalanced
@Bean
@SentinelRestTemplate
public RestTemplate restTemplate1() {
return new RestTemplate();
}
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args); SpringApplication.run(ConsumerApplication.class, args);
} }

View File

@ -18,18 +18,52 @@ public class TestController {
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Autowired
private RestTemplate restTemplate1;
@Autowired @Autowired
private EchoService echoService; private EchoService echoService;
@Autowired @Autowired
private DiscoveryClient discoveryClient; private DiscoveryClient discoveryClient;
// @PostConstruct
// public void init() {
// restTemplate1.setErrorHandler(new ResponseErrorHandler() {
// @Override
// public boolean hasError(ClientHttpResponse response) throws IOException {
// return false;
// }
//
// @Override
// public void handleError(ClientHttpResponse response) throws IOException {
// System.err.println("handle error");
// }
// });
// }
@RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET) @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
public String rest(@PathVariable String str) { public String rest(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str, return restTemplate.getForObject("http://service-provider/echo/" + str,
String.class); String.class);
} }
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index() {
return restTemplate1.getForObject("http://service-provider", String.class);
}
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return restTemplate1.getForObject("http://service-provider/test", String.class);
}
@RequestMapping(value = "/sleep", method = RequestMethod.GET)
public String sleep() {
return restTemplate1.getForObject("http://service-provider/sleep", String.class);
}
@RequestMapping(value = "/notFound-feign", method = RequestMethod.GET) @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET)
public String notFound() { public String notFound() {
return echoService.notFound(); return echoService.notFound();

View File

@ -10,4 +10,8 @@ spring.cloud.sentinel.eager=true
spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json
spring.cloud.sentinel.datasource.ds2.file.data-type=json
spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade

View File

@ -0,0 +1,26 @@
[
{
"resource": "GET:http://service-provider/test",
"count": 0.5,
"grade": 1,
"timeWindow": 30
},
{
"resource": "GET:http://service-provider",
"count": 0.5,
"grade": 1,
"timeWindow": 10
},
{
"resource": "GET:http://service-provider/sleep",
"count": 20.0,
"grade": 0,
"timeWindow": 30
},
{
"resource": "GET:http://service-provider/divide",
"count": 0.5,
"grade": 1,
"timeWindow": 30
}
]

View File

@ -3,6 +3,8 @@ package org.springframework.cloud.alibaba.cloud.examples;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -22,6 +24,28 @@ public class ProviderApplication {
@RestController @RestController
class EchoController { class EchoController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity index() {
return new ResponseEntity("index error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity test() {
return new ResponseEntity("error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@RequestMapping(value = "/sleep", method = RequestMethod.GET)
public String sleep() {
try {
Thread.sleep(1000L);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) { public String echo(@PathVariable String string) {
return "hello Nacos Discovery " + string; return "hello Nacos Discovery " + string;

View File

@ -168,9 +168,9 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces
throws BeansException { throws BeansException {
if (cache.containsKey(beanName)) { if (cache.containsKey(beanName)) {
// add interceptor for each RestTemplate with @SentinelRestTemplate annotation // add interceptor for each RestTemplate with @SentinelRestTemplate annotation
StringBuilder interceptorBeanName = new StringBuilder(); StringBuilder interceptorBeanNamePrefix = new StringBuilder();
SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); SentinelRestTemplate sentinelRestTemplate = cache.get(beanName);
interceptorBeanName interceptorBeanNamePrefix
.append(StringUtils.uncapitalize( .append(StringUtils.uncapitalize(
SentinelProtectInterceptor.class.getSimpleName())) SentinelProtectInterceptor.class.getSimpleName()))
.append("_") .append("_")
@ -179,26 +179,25 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces
.append(sentinelRestTemplate.fallbackClass().getSimpleName()) .append(sentinelRestTemplate.fallbackClass().getSimpleName())
.append(sentinelRestTemplate.fallback()); .append(sentinelRestTemplate.fallback());
RestTemplate restTemplate = (RestTemplate) bean; RestTemplate restTemplate = (RestTemplate) bean;
registerBean(interceptorBeanName.toString(), sentinelRestTemplate); String interceptorBeanName = interceptorBeanNamePrefix + "@"
+ bean.toString();
registerBean(interceptorBeanName, sentinelRestTemplate, (RestTemplate) bean);
SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext
.getBean(interceptorBeanName.toString(), .getBean(interceptorBeanName, SentinelProtectInterceptor.class);
SentinelProtectInterceptor.class);
restTemplate.getInterceptors().add(0, sentinelProtectInterceptor); restTemplate.getInterceptors().add(0, sentinelProtectInterceptor);
} }
return bean; return bean;
} }
private void registerBean(String interceptorBeanName, private void registerBean(String interceptorBeanName,
SentinelRestTemplate sentinelRestTemplate) { SentinelRestTemplate sentinelRestTemplate, RestTemplate restTemplate) {
// register SentinelProtectInterceptor bean // register SentinelProtectInterceptor bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory(); .getAutowireCapableBeanFactory();
if (beanFactory.containsBean(interceptorBeanName)) {
return;
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(SentinelProtectInterceptor.class); .genericBeanDefinition(SentinelProtectInterceptor.class);
beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate);
beanDefinitionBuilder.addConstructorArgValue(restTemplate);
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition(); .getRawBeanDefinition();
beanFactory.registerBeanDefinition(interceptorBeanName, beanFactory.registerBeanDefinition(interceptorBeanName,

View File

@ -27,11 +27,12 @@ import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
@ -44,8 +45,12 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
private final SentinelRestTemplate sentinelRestTemplate; private final SentinelRestTemplate sentinelRestTemplate;
public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) { private final RestTemplate restTemplate;
public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate,
RestTemplate restTemplate) {
this.sentinelRestTemplate = sentinelRestTemplate; this.sentinelRestTemplate = sentinelRestTemplate;
this.restTemplate = restTemplate;
} }
@Override @Override
@ -61,32 +66,33 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
entryWithPath = false; entryWithPath = false;
} }
Entry hostEntry = null, hostWithPathEntry = null; Entry hostEntry = null, hostWithPathEntry = null;
ClientHttpResponse response; ClientHttpResponse response = null;
try { try {
ContextUtil.enter(hostWithPathResource); hostEntry = SphU.entry(hostResource, EntryType.OUT);
if (entryWithPath) { if (entryWithPath) {
hostWithPathEntry = SphU.entry(hostWithPathResource); hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT);
} }
hostEntry = SphU.entry(hostResource);
response = execution.execute(request, body); response = execution.execute(request, body);
if (this.restTemplate.getErrorHandler().hasError(response)) {
Tracer.trace(
new IllegalStateException("RestTemplate ErrorHandler has error"));
}
} }
catch (Throwable e) { catch (Throwable e) {
if (!BlockException.isBlockException(e)) { if (!BlockException.isBlockException(e)) {
Tracer.trace(e); Tracer.trace(e);
throw new IllegalStateException(e);
} }
else { else {
return handleBlockException(request, body, execution, (BlockException) e); return handleBlockException(request, body, execution, (BlockException) e);
} }
} }
finally { finally {
if (hostEntry != null) {
hostEntry.exit();
}
if (hostWithPathEntry != null) { if (hostWithPathEntry != null) {
hostWithPathEntry.exit(); hostWithPathEntry.exit();
} }
ContextUtil.exit(); if (hostEntry != null) {
hostEntry.exit();
}
} }
return response; return response;
} }

View File

@ -95,7 +95,7 @@ public class SentinelInvocationHandler implements InvocationHandler {
+ Feign.configKey(method.getDeclaringClass(), method)); + Feign.configKey(method.getDeclaringClass(), method));
// resource default is HttpMethod:protocol://url // resource default is HttpMethod:protocol://url
String resourceName = methodMetadata.template().method().toUpperCase() + ":" String resourceName = methodMetadata.template().method().toUpperCase() + ":"
+ hardCodedTarget.url() + methodMetadata.template().url(); + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null; Entry entry = null;
try { try {
ContextUtil.enter(resourceName); ContextUtil.enter(resourceName);
@ -128,7 +128,7 @@ public class SentinelInvocationHandler implements InvocationHandler {
} }
finally { finally {
if (entry != null) { if (entry != null) {
entry.exit(); entry.exit(1, args);
} }
ContextUtil.exit(); ContextUtil.exit();
} }

View File

@ -106,14 +106,16 @@ public class SentinelAutoConfigurationTests {
@LocalServerPort @LocalServerPort
private int port; private int port;
private String url = "http://localhost:" + port; private String flowUrl = "http://localhost:" + port + "/flow";
private String degradeUrl = "http://localhost:" + port + "/degrade";
@Before @Before
public void setUp() { public void setUp() {
FlowRule rule = new FlowRule(); FlowRule rule = new FlowRule();
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(0); rule.setCount(0);
rule.setResource("GET:" + url); rule.setResource("GET:" + flowUrl);
rule.setLimitApp("default"); rule.setLimitApp("default");
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule.setStrategy(RuleConstant.STRATEGY_DIRECT); rule.setStrategy(RuleConstant.STRATEGY_DIRECT);
@ -121,7 +123,7 @@ public class SentinelAutoConfigurationTests {
DegradeRule degradeRule = new DegradeRule(); DegradeRule degradeRule = new DegradeRule();
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
degradeRule.setResource("GET:" + url + "/test"); degradeRule.setResource("GET:" + degradeUrl);
degradeRule.setCount(0); degradeRule.setCount(0);
degradeRule.setTimeWindow(60); degradeRule.setTimeWindow(60);
DegradeRuleManager.loadRules(Arrays.asList(degradeRule)); DegradeRuleManager.loadRules(Arrays.asList(degradeRule));
@ -247,14 +249,15 @@ public class SentinelAutoConfigurationTests {
restTemplate.getInterceptors().size()); restTemplate.getInterceptors().size());
assertEquals("RestTemplateWithBlockClass interceptors size was wrong", 1, assertEquals("RestTemplateWithBlockClass interceptors size was wrong", 1,
restTemplateWithBlockClass.getInterceptors().size()); restTemplateWithBlockClass.getInterceptors().size());
ResponseEntity responseEntityBlock = restTemplateWithBlockClass.getForEntity(url, ResponseEntity responseEntityBlock = restTemplateWithBlockClass
String.class); .getForEntity(flowUrl, String.class);
assertEquals("RestTemplateWithBlockClass Sentinel Block Message was wrong", assertEquals("RestTemplateWithBlockClass Sentinel Block Message was wrong",
"Oops", responseEntityBlock.getBody()); "Oops", responseEntityBlock.getBody());
assertEquals( assertEquals(
"RestTemplateWithBlockClass Sentinel Block Http Status Code was wrong", "RestTemplateWithBlockClass Sentinel Block Http Status Code was wrong",
HttpStatus.OK, responseEntityBlock.getStatusCode()); HttpStatus.OK, responseEntityBlock.getStatusCode());
ResponseEntity responseEntityRaw = restTemplate.getForEntity(url, String.class); ResponseEntity responseEntityRaw = restTemplate.getForEntity(flowUrl,
String.class);
assertEquals("RestTemplate Sentinel Block Message was wrong", assertEquals("RestTemplate Sentinel Block Message was wrong",
"RestTemplate request block by sentinel", responseEntityRaw.getBody()); "RestTemplate request block by sentinel", responseEntityRaw.getBody());
assertEquals("RestTemplate Sentinel Block Http Status Code was wrong", assertEquals("RestTemplate Sentinel Block Http Status Code was wrong",
@ -266,14 +269,14 @@ public class SentinelAutoConfigurationTests {
assertEquals("RestTemplateWithoutBlockClass interceptors size was wrong", 0, assertEquals("RestTemplateWithoutBlockClass interceptors size was wrong", 0,
restTemplateWithoutBlockClass.getInterceptors().size()); restTemplateWithoutBlockClass.getInterceptors().size());
assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> { assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> {
restTemplateWithoutBlockClass.getForEntity(url, String.class); restTemplateWithoutBlockClass.getForEntity(flowUrl, String.class);
}); });
} }
@Test @Test
public void testFallbackRestTemplate() { public void testFallbackRestTemplate() {
ResponseEntity responseEntity = restTemplateWithFallbackClass ResponseEntity responseEntity = restTemplateWithFallbackClass
.getForEntity(url + "/test", String.class); .getForEntity(degradeUrl, String.class);
assertEquals("RestTemplateWithFallbackClass Sentinel Message was wrong", assertEquals("RestTemplateWithFallbackClass Sentinel Message was wrong",
"Oops fallback", responseEntity.getBody()); "Oops fallback", responseEntity.getBody());
assertEquals("RestTemplateWithFallbackClass Sentinel Http Status Code was wrong", assertEquals("RestTemplateWithFallbackClass Sentinel Http Status Code was wrong",