[Feature] Optimize feign & rest-template circuit-breaker logic (2021). (#454)

pull/459/head
VOPEN.XYZ 2 years ago committed by GitHub
parent 30644858ec
commit 2155b3bd9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,3 +6,4 @@
- [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/437) - [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/437)
- [Add configurable heartbeat interval support](https://github.com/Tencent/spring-cloud-tencent/pull/444) - [Add configurable heartbeat interval support](https://github.com/Tencent/spring-cloud-tencent/pull/444)
- [feat:enhance Feign and RestTemplate and support Polaris monitor.](https://github.com/Tencent/spring-cloud-tencent/pull/447) - [feat:enhance Feign and RestTemplate and support Polaris monitor.](https://github.com/Tencent/spring-cloud-tencent/pull/447)
- [Optimize feign & rest-template circuit-breaker logic](https://github.com/Tencent/spring-cloud-tencent/pull/454)

@ -18,6 +18,7 @@
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId> <artifactId>spring-cloud-tencent-polaris-loadbalancer</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId> <artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>

@ -15,6 +15,12 @@ spring:
port: 28081 port: 28081
loadbalancer: loadbalancer:
configurations: polaris configurations: polaris
# tencent:
# rpc-enhancement:
# enabled: true
# ignore-internal-server-error: true
# series: server_error
# statuses: gateway_timeout, bad_gateway, service_unavailable
feign: feign:
circuitbreaker: circuitbreaker:
enabled: true enabled: true

@ -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 <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 2022-07-11
*/
public abstract class AbstractPolarisReporterAdapter {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPolarisReporterAdapter.class);
protected final RpcEnhancementProperties properties;
private static final List<HttpStatus> 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 <T> Object Generics.
* @return list
*/
@SafeVarargs
private static <T> List<T> 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<HttpStatus> status = properties.getStatuses();
if (status.isEmpty()) {
List<HttpStatus.Series> 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;
}
}

@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@ -49,6 +50,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(RpcEnhancementProperties.class)
@AutoConfigureAfter(PolarisContextAutoConfiguration.class) @AutoConfigureAfter(PolarisContextAutoConfiguration.class)
public class RpcEnhancementAutoConfiguration { public class RpcEnhancementAutoConfiguration {
@ -74,8 +76,8 @@ public class RpcEnhancementAutoConfiguration {
static class PolarisReporterConfig { static class PolarisReporterConfig {
@Bean @Bean
public SuccessPolarisReporter successPolarisReporter() { public SuccessPolarisReporter successPolarisReporter(RpcEnhancementProperties properties) {
return new SuccessPolarisReporter(); return new SuccessPolarisReporter(properties);
} }
@Bean @Bean
@ -97,8 +99,9 @@ public class RpcEnhancementAutoConfiguration {
@Bean @Bean
public EnhancedRestTemplateReporter polarisRestTemplateResponseErrorHandler( public EnhancedRestTemplateReporter polarisRestTemplateResponseErrorHandler(
ConsumerAPI consumerAPI, @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) { RpcEnhancementProperties properties, ConsumerAPI consumerAPI,
return new EnhancedRestTemplateReporter(consumerAPI, polarisResponseErrorHandler); @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) {
return new EnhancedRestTemplateReporter(properties, consumerAPI, polarisResponseErrorHandler);
} }
@Bean @Bean

@ -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 <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 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<HttpStatus> statuses = new ArrayList<>();
/**
* Specify List of HTTP status series.
*/
private List<HttpStatus.Series> 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 <T> Object Generics.
* @return list
*/
@SafeVarargs
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
public List<HttpStatus> getStatuses() {
return statuses;
}
public void setStatuses(List<HttpStatus> statuses) {
this.statuses = statuses;
}
public List<HttpStatus.Series> getSeries() {
return series;
}
public void setSeries(List<HttpStatus.Series> 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;
}
}

@ -17,6 +17,8 @@
package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; 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.EnhancedFeignContext;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin; import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPlugin;
import com.tencent.cloud.rpc.enhancement.feign.plugin.EnhancedFeignPluginType; 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.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
/** /**
* Polaris reporter when feign call is successful. * Polaris reporter when feign call is successful.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
public class SuccessPolarisReporter implements EnhancedFeignPlugin { public class SuccessPolarisReporter extends AbstractPolarisReporterAdapter implements EnhancedFeignPlugin {
private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class); private static final Logger LOG = LoggerFactory.getLogger(SuccessPolarisReporter.class);
public SuccessPolarisReporter(RpcEnhancementProperties properties) {
super(properties);
}
@Autowired(required = false) @Autowired(required = false)
private ConsumerAPI consumerAPI; private ConsumerAPI consumerAPI;
@ -59,7 +66,7 @@ public class SuccessPolarisReporter implements EnhancedFeignPlugin {
Request request = context.getRequest(); Request request = context.getRequest();
Response response = context.getResponse(); Response response = context.getResponse();
RetStatus retStatus = RetStatus.RetSuccess; RetStatus retStatus = RetStatus.RetSuccess;
if (response.status() > 500) { if (apply(HttpStatus.resolve(response.status()))) {
retStatus = RetStatus.RetFail; retStatus = RetStatus.RetFail;
} }
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response); LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].", retStatus.name(), request, response);

@ -25,6 +25,8 @@ import java.util.Objects;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ReflectionUtils; 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.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.pojo.ServiceKey;
@ -43,7 +45,7 @@ import org.springframework.web.client.ResponseErrorHandler;
* *
* @author wh 2022/6/21 * @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); private static final Logger LOG = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class);
@ -53,8 +55,9 @@ public class EnhancedRestTemplateReporter implements ResponseErrorHandler {
private final PolarisResponseErrorHandler polarisResponseErrorHandler; private final PolarisResponseErrorHandler polarisResponseErrorHandler;
public EnhancedRestTemplateReporter( public EnhancedRestTemplateReporter(RpcEnhancementProperties properties, ConsumerAPI consumerAPI,
ConsumerAPI consumerAPI, PolarisResponseErrorHandler polarisResponseErrorHandler) { PolarisResponseErrorHandler polarisResponseErrorHandler) {
super(properties);
this.consumerAPI = consumerAPI; this.consumerAPI = consumerAPI;
this.polarisResponseErrorHandler = polarisResponseErrorHandler; this.polarisResponseErrorHandler = polarisResponseErrorHandler;
} }
@ -85,7 +88,7 @@ public class EnhancedRestTemplateReporter implements ResponseErrorHandler {
resultRequest.setPort(realURL.getPort()); resultRequest.setPort(realURL.getPort());
} }
if (response.getStatusCode().value() > 500) { if (apply(response.getStatusCode())) {
resultRequest.setRetStatus(RetStatus.RetFail); resultRequest.setRetStatus(RetStatus.RetFail);
} }
} }

@ -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 <a href="mailto:iskp.me@gmail.com">Elve.Xu</a> 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);
}
}
}

@ -43,7 +43,7 @@ import static org.mockito.Mockito.mock;
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.Silent.class)
public class SuccessPolarisReporterTest { public class SuccessPolarisReporterTest {
private static MockedStatic<ReporterUtils> mockedReporterUtils; private static MockedStatic<ReporterUtils> mockedReporterUtils;

@ -22,6 +22,7 @@ import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementProperties;
import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.core.ConsumerAPI;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -47,7 +48,8 @@ public class EnhancedRestTemplateReporterTest {
@Test @Test
public void handleError() throws Exception { public void handleError() throws Exception {
ConsumerAPI consumerAPI = mock(ConsumerAPI.class); 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); URI uri = mock(URI.class);
when(uri.getPath()).thenReturn("/test"); when(uri.getPath()).thenReturn("/test");
when(uri.getHost()).thenReturn("host"); when(uri.getHost()).thenReturn("host");

Loading…
Cancel
Save