From d31c0e2a1709958d37b78d40f951d22faeb8bd12 Mon Sep 17 00:00:00 2001 From: "VOPEN.XYZ" Date: Thu, 21 Jul 2022 23:04:29 +0800 Subject: [PATCH] [Feature] Optimize feign & rest-template circuit-breaker logic (2020) . (#453) --- CHANGELOG.md | 1 + .../pom.xml | 1 + .../AbstractPolarisCircuitBreakAdapter.java | 92 +++++++++++++ .../PolarisCircuitBreakerProperties.java | 113 ++++++++++++++++ ...bstractPolarisCircuitBreakAdapterTest.java | 117 +++++++++++++++++ .../src/main/resources/bootstrap.yml | 6 + .../AbstractPolarisReporterAdapter.java | 121 ++++++++++++++++++ .../RpcEnhancementAutoConfiguration.java | 11 +- .../config/RpcEnhancementProperties.java | 99 ++++++++++++++ .../reporter/SuccessPolarisReporter.java | 11 +- .../EnhancedRestTemplateReporter.java | 11 +- .../AbstractPolarisReporterAdapterTest.java | 116 +++++++++++++++++ .../reporter/SuccessPolarisReporterTest.java | 2 +- .../EnhancedRestTemplateReporterTest.java | 4 +- 14 files changed, 693 insertions(+), 12 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapter.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerProperties.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapterTest.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementProperties.java create mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 53111bed0..5fd31c88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/439) - [Add configurable heartbeat interval support](https://github.com/Tencent/spring-cloud-tencent/pull/443) - [feat:enhance Feign and RestTemplate and support Polaris monitor.](https://github.com/Tencent/spring-cloud-tencent/pull/446) +- [Optimize feign & rest-template circuit-breaker logic](https://github.com/Tencent/spring-cloud-tencent/pull/453) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml index f1c42d41c..20bf4bdf2 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml @@ -18,6 +18,7 @@ com.tencent.cloud spring-cloud-tencent-polaris-loadbalancer + com.tencent.cloud spring-cloud-tencent-rpc-enhancement diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapter.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapter.java new file mode 100644 index 000000000..42137fe62 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapter.java @@ -0,0 +1,92 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.polaris.circuitbreaker; + +import java.util.List; +import java.util.Objects; + +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + +/** + * Abstract Polaris Circuit-Break Adapter . + * + * @author Elve.Xu 2022-07-11 + */ +public abstract class AbstractPolarisCircuitBreakAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisCircuitBreakAdapter.class); + + protected final PolarisCircuitBreakerProperties properties; + + /** + * Constructor With {@link PolarisCircuitBreakerProperties} . + * + * @param properties instance of {@link PolarisCircuitBreakerProperties}. + */ + protected AbstractPolarisCircuitBreakAdapter(PolarisCircuitBreakerProperties properties) { + this.properties = properties; + } + + /** + * Callback after completion of request processing, Check if business meltdown reporting is required. + * + * @param httpStatus request http status code + * @return true , otherwise return false . + */ + protected boolean apply(@Nullable HttpStatus httpStatus) { + if (Objects.isNull(httpStatus)) { + return false; + } + else { + // statuses > series + List status = properties.getStatuses(); + + if (status.isEmpty()) { + List series = properties.getSeries(); + // Check INTERNAL_SERVER_ERROR (500) status. + if (properties.getIgnoreInternalServerError() && Objects.equals(httpStatus, INTERNAL_SERVER_ERROR)) { + return false; + } + if (series.isEmpty()) { + return false; + } + else { + try { + return series.contains(HttpStatus.Series.valueOf(httpStatus)); + } + catch (Exception e) { + LOG.warn("Decode http status failed.", e); + } + } + } + else { + // Use the user-specified fuse status code. + return status.contains(httpStatus); + } + } + // DEFAULT RETURN FALSE. + return false; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerProperties.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerProperties.java new file mode 100644 index 000000000..a47ad20a7 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerProperties.java @@ -0,0 +1,113 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.polaris.circuitbreaker.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_GATEWAY; +import static org.springframework.http.HttpStatus.BANDWIDTH_LIMIT_EXCEEDED; +import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT; +import static org.springframework.http.HttpStatus.HTTP_VERSION_NOT_SUPPORTED; +import static org.springframework.http.HttpStatus.INSUFFICIENT_STORAGE; +import static org.springframework.http.HttpStatus.LOOP_DETECTED; +import static org.springframework.http.HttpStatus.NETWORK_AUTHENTICATION_REQUIRED; +import static org.springframework.http.HttpStatus.NOT_EXTENDED; +import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED; +import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE; +import static org.springframework.http.HttpStatus.VARIANT_ALSO_NEGOTIATES; + +/** + * Properties of Polaris CircuitBreaker . + * + * @author Elve.Xu 2022-07-08 + */ +@ConfigurationProperties("spring.cloud.polaris.circuitbreaker") +public class PolarisCircuitBreakerProperties { + + /** + * If circuit-breaker enabled. + */ + private Boolean enabled = true; + + /** + * Specify the Http status code(s) that needs to be fused. + */ + private List statuses = toList(NOT_IMPLEMENTED, BAD_GATEWAY, + SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT, HTTP_VERSION_NOT_SUPPORTED, VARIANT_ALSO_NEGOTIATES, + INSUFFICIENT_STORAGE, LOOP_DETECTED, BANDWIDTH_LIMIT_EXCEEDED, NOT_EXTENDED, NETWORK_AUTHENTICATION_REQUIRED); + + /** + * Specify List of HTTP status series. + */ + private List series = toList(HttpStatus.Series.SERVER_ERROR); + + /** + * Ignore Internal Server Error Http Status Code, + * Only takes effect if the attribute {@link PolarisCircuitBreakerProperties#series} is not empty. + */ + private Boolean ignoreInternalServerError = true; + + /** + * Convert items to List. + * + * @param items item arrays + * @param Object Generics. + * @return list + */ + @SafeVarargs + private static List toList(T... items) { + return new ArrayList<>(Arrays.asList(items)); + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public List getStatuses() { + return statuses; + } + + public void setStatuses(List statuses) { + this.statuses = statuses; + } + + public List getSeries() { + return series; + } + + public void setSeries(List series) { + this.series = series; + } + + public Boolean getIgnoreInternalServerError() { + return ignoreInternalServerError; + } + + public void setIgnoreInternalServerError(Boolean ignoreInternalServerError) { + this.ignoreInternalServerError = ignoreInternalServerError; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapterTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapterTest.java new file mode 100644 index 000000000..0775c8313 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/AbstractPolarisCircuitBreakAdapterTest.java @@ -0,0 +1,117 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.polaris.circuitbreaker; + +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import org.springframework.http.HttpStatus; + +/** + * Test For {@link AbstractPolarisCircuitBreakAdapter}. + * + * @author Elve.Xu + * @version ${project.version} - 2022/7/11 + */ +public class AbstractPolarisCircuitBreakAdapterTest { + + @Test + public void testApplyWithDefaultConfig() { + PolarisCircuitBreakerProperties properties = new PolarisCircuitBreakerProperties(); + // Mock Condition + SimplePolarisCircuitBreakAdapter adapter = new SimplePolarisCircuitBreakAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithoutIgnoreInternalServerError() { + PolarisCircuitBreakerProperties properties = new PolarisCircuitBreakerProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.setIgnoreInternalServerError(false); + + SimplePolarisCircuitBreakAdapter adapter = new SimplePolarisCircuitBreakAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithIgnoreInternalServerError() { + PolarisCircuitBreakerProperties properties = new PolarisCircuitBreakerProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.setIgnoreInternalServerError(true); + + SimplePolarisCircuitBreakAdapter adapter = new SimplePolarisCircuitBreakAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithoutSeries() { + PolarisCircuitBreakerProperties properties = new PolarisCircuitBreakerProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + + SimplePolarisCircuitBreakAdapter adapter = new SimplePolarisCircuitBreakAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false); + } + + @Test + public void testApplyWithSeries() { + PolarisCircuitBreakerProperties properties = new PolarisCircuitBreakerProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisCircuitBreakAdapter adapter = new SimplePolarisCircuitBreakAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true); + } + + /** + * Simple Polaris CircuitBreak Adapter Implements . + */ + public static class SimplePolarisCircuitBreakAdapter extends AbstractPolarisCircuitBreakAdapter { + + public SimplePolarisCircuitBreakAdapter(PolarisCircuitBreakerProperties properties) { + super(properties); + } + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml index 91a7570cd..a3b008536 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml @@ -15,6 +15,12 @@ spring: port: 28081 loadbalancer: configurations: polaris +# tencent: +# rpc-enhancement: +# enabled: true +# ignore-internal-server-error: true +# series: server_error +# statuses: gateway_timeout, bad_gateway, service_unavailable feign: circuitbreaker: enabled: true diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java new file mode 100644 index 000000000..41b9fe154 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapter.java @@ -0,0 +1,121 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.rpc.enhancement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +import static org.springframework.http.HttpStatus.BAD_GATEWAY; +import static org.springframework.http.HttpStatus.BANDWIDTH_LIMIT_EXCEEDED; +import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT; +import static org.springframework.http.HttpStatus.HTTP_VERSION_NOT_SUPPORTED; +import static org.springframework.http.HttpStatus.INSUFFICIENT_STORAGE; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.LOOP_DETECTED; +import static org.springframework.http.HttpStatus.NETWORK_AUTHENTICATION_REQUIRED; +import static org.springframework.http.HttpStatus.NOT_EXTENDED; +import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED; +import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE; +import static org.springframework.http.HttpStatus.VARIANT_ALSO_NEGOTIATES; + +/** + * Abstract Polaris Reporter Adapter . + * + * @author Elve.Xu 2022-07-11 + */ +public abstract class AbstractPolarisReporterAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisReporterAdapter.class); + + protected final RpcEnhancementProperties properties; + + private static final List HTTP_STATUSES = toList(NOT_IMPLEMENTED, BAD_GATEWAY, + SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT, HTTP_VERSION_NOT_SUPPORTED, VARIANT_ALSO_NEGOTIATES, + INSUFFICIENT_STORAGE, LOOP_DETECTED, BANDWIDTH_LIMIT_EXCEEDED, NOT_EXTENDED, NETWORK_AUTHENTICATION_REQUIRED); + + /** + * Constructor With {@link RpcEnhancementProperties} . + * + * @param properties instance of {@link RpcEnhancementProperties}. + */ + protected AbstractPolarisReporterAdapter(RpcEnhancementProperties properties) { + this.properties = properties; + } + + /** + * Convert items to List. + * + * @param items item arrays + * @param Object Generics. + * @return list + */ + @SafeVarargs + private static List toList(T... items) { + return new ArrayList<>(Arrays.asList(items)); + } + + /** + * Callback after completion of request processing, Check if business meltdown reporting is required. + * + * @param httpStatus request http status code + * @return true , otherwise return false . + */ + protected boolean apply(@Nullable HttpStatus httpStatus) { + if (Objects.isNull(httpStatus)) { + return false; + } + else { + // statuses > series + List status = properties.getStatuses(); + + if (status.isEmpty()) { + List series = properties.getSeries(); + // Check INTERNAL_SERVER_ERROR (500) status. + if (properties.isIgnoreInternalServerError() && Objects.equals(httpStatus, INTERNAL_SERVER_ERROR)) { + return false; + } + if (series.isEmpty()) { + return HTTP_STATUSES.contains(httpStatus); + } + else { + try { + return series.contains(HttpStatus.Series.valueOf(httpStatus)); + } + catch (Exception e) { + LOG.warn("Decode http status failed.", e); + } + } + } + else { + // Use the user-specified fuse status code. + return status.contains(httpStatus); + } + } + // DEFAULT RETURN FALSE. + return false; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index b44400a33..4dc25c30b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -49,6 +50,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(RpcEnhancementProperties.class) @AutoConfigureAfter(PolarisContextAutoConfiguration.class) public class RpcEnhancementAutoConfiguration { @@ -74,8 +76,8 @@ public class RpcEnhancementAutoConfiguration { static class PolarisReporterConfig { @Bean - public SuccessPolarisReporter successPolarisReporter() { - return new SuccessPolarisReporter(); + public SuccessPolarisReporter successPolarisReporter(RpcEnhancementProperties properties) { + return new SuccessPolarisReporter(properties); } @Bean @@ -97,8 +99,9 @@ public class RpcEnhancementAutoConfiguration { @Bean public EnhancedRestTemplateReporter polarisRestTemplateResponseErrorHandler( - ConsumerAPI consumerAPI, @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) { - return new EnhancedRestTemplateReporter(consumerAPI, polarisResponseErrorHandler); + RpcEnhancementProperties properties, ConsumerAPI consumerAPI, + @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) { + return new EnhancedRestTemplateReporter(properties, consumerAPI, polarisResponseErrorHandler); } @Bean diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementProperties.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementProperties.java new file mode 100644 index 000000000..612a97fbe --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementProperties.java @@ -0,0 +1,99 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.rpc.enhancement.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpStatus; + +/** + * Properties of Polaris CircuitBreaker . + * + * @author Elve.Xu 2022-07-08 + */ +@ConfigurationProperties("spring.cloud.tencent.rpc-enhancement") +public class RpcEnhancementProperties { + + /** + * If circuit-breaker enabled. + */ + private boolean enabled = true; + + /** + * Specify the Http status code(s) that needs to be fused. + */ + private List statuses = new ArrayList<>(); + + /** + * Specify List of HTTP status series. + */ + private List series = toList(HttpStatus.Series.SERVER_ERROR); + + /** + * Ignore Internal Server Error Http Status Code, + * Only takes effect if the attribute {@link RpcEnhancementProperties#series} is not empty. + */ + private boolean ignoreInternalServerError = true; + + /** + * Convert items to List. + * + * @param items item arrays + * @param Object Generics. + * @return list + */ + @SafeVarargs + private static List toList(T... items) { + return new ArrayList<>(Arrays.asList(items)); + } + + public List getStatuses() { + return statuses; + } + + public void setStatuses(List statuses) { + this.statuses = statuses; + } + + public List getSeries() { + return series; + } + + public void setSeries(List series) { + this.series = series; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isIgnoreInternalServerError() { + return ignoreInternalServerError; + } + + public void setIgnoreInternalServerError(boolean ignoreInternalServerError) { + this.ignoreInternalServerError = ignoreInternalServerError; + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java index 332711eab..e2bb22b32 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporter.java @@ -17,6 +17,8 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignContext; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; @@ -30,16 +32,21 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; +import org.springframework.http.HttpStatus; /** * Polaris reporter when feign call is successful. * * @author Haotian Zhang */ -public class SuccessPolarisReporter implements EnhancedFeignPlugin { +public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin { private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class); + public SuccessPolarisReporter(RpcEnhancementProperties properties) { + super(properties); + } + @Autowired(required = false) private ConsumerAPI consumerAPI; @@ -59,7 +66,7 @@ public class SuccessPolarisReporter implements EnhancedFeignPlugin { Request request = context.getRequest(); Response response = context.getResponse(); RetStatus retStatus = RetStatus.RetSuccess; - if (response.status() > 500) { + if (apply(HttpStatus.resolve(response.status()))) { retStatus = RetStatus.RetFail; } LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java index 7618793a9..dd841b4b6 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java @@ -25,6 +25,8 @@ import java.util.Objects; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ReflectionUtils; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.ServiceKey; @@ -43,7 +45,7 @@ import org.springframework.web.client.ResponseErrorHandler; * * @author wh 2022/6/21 */ -public class EnhancedRestTemplateReporter implements ResponseErrorHandler { +public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler { private static final Logger LOG = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); @@ -53,8 +55,9 @@ public class EnhancedRestTemplateReporter implements ResponseErrorHandler { private final PolarisResponseErrorHandler polarisResponseErrorHandler; - public EnhancedRestTemplateReporter( - ConsumerAPI consumerAPI, PolarisResponseErrorHandler polarisResponseErrorHandler) { + public EnhancedRestTemplateReporter(RpcEnhancementProperties properties, ConsumerAPI consumerAPI, + PolarisResponseErrorHandler polarisResponseErrorHandler) { + super(properties); this.consumerAPI = consumerAPI; this.polarisResponseErrorHandler = polarisResponseErrorHandler; } @@ -85,7 +88,7 @@ public class EnhancedRestTemplateReporter implements ResponseErrorHandler { resultRequest.setPort(realURL.getPort()); } - if (response.getStatusCode().value() > 500) { + if (apply(response.getStatusCode())) { resultRequest.setRetStatus(RetStatus.RetFail); } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java new file mode 100644 index 000000000..db205c313 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/AbstractPolarisReporterAdapterTest.java @@ -0,0 +1,116 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT 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 com.tencent.cloud.rpc.enhancement; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import org.springframework.http.HttpStatus; + +/** + * Test For {@link AbstractPolarisReporterAdapter}. + * + * @author Elve.Xu 2022/7/11 + */ +public class AbstractPolarisReporterAdapterTest { + + @Test + public void testApplyWithDefaultConfig() { + RpcEnhancementProperties properties = new RpcEnhancementProperties(); + // Mock Condition + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithoutIgnoreInternalServerError() { + RpcEnhancementProperties properties = new RpcEnhancementProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.setIgnoreInternalServerError(false); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(true); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithIgnoreInternalServerError() { + RpcEnhancementProperties properties = new RpcEnhancementProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.setIgnoreInternalServerError(true); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithoutSeries() { + RpcEnhancementProperties properties = new RpcEnhancementProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(true); + } + + @Test + public void testApplyWithSeries() { + RpcEnhancementProperties properties = new RpcEnhancementProperties(); + // Mock Condition + properties.getStatuses().clear(); + properties.getSeries().clear(); + properties.getSeries().add(HttpStatus.Series.CLIENT_ERROR); + + SimplePolarisReporterAdapter adapter = new SimplePolarisReporterAdapter(properties); + + // Assert + Assertions.assertThat(adapter.apply(HttpStatus.OK)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.INTERNAL_SERVER_ERROR)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.BAD_GATEWAY)).isEqualTo(false); + Assertions.assertThat(adapter.apply(HttpStatus.FORBIDDEN)).isEqualTo(true); + } + + /** + * Simple Polaris CircuitBreak Adapter Implements . + */ + public static class SimplePolarisReporterAdapter extends AbstractPolarisReporterAdapter { + + public SimplePolarisReporterAdapter(RpcEnhancementProperties properties) { + super(properties); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java index cdcc594f2..b3e28a5ca 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/SuccessPolarisReporterTest.java @@ -43,7 +43,7 @@ import static org.mockito.Mockito.mock; * * @author Haotian Zhang */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class SuccessPolarisReporterTest { private static MockedStatic mockedReporterUtils; diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java index 53e7ef652..631155b8f 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporterTest.java @@ -22,6 +22,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties; import com.tencent.polaris.api.core.ConsumerAPI; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,7 +48,8 @@ public class EnhancedRestTemplateReporterTest { @Test public void handleError() throws Exception { ConsumerAPI consumerAPI = mock(ConsumerAPI.class); - EnhancedRestTemplateReporter enhancedRestTemplateReporter = new EnhancedRestTemplateReporter(consumerAPI, null); + EnhancedRestTemplateReporter enhancedRestTemplateReporter = + new EnhancedRestTemplateReporter(mock(RpcEnhancementProperties.class), consumerAPI, null); URI uri = mock(URI.class); when(uri.getPath()).thenReturn("/test"); when(uri.getHost()).thenReturn("host");