From e525ab7d6430696e8d2b368eb16ffb4980f62801 Mon Sep 17 00:00:00 2001 From: Jayanth Date: Wed, 19 Jul 2017 02:48:01 +0530 Subject: [PATCH 1/2] Hystrix open threshold documentation (#2136) fixes gh-1943 --- docs/src/main/asciidoc/spring-cloud-netflix.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 3b0dd846..5bb1cbcc 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -494,7 +494,7 @@ Netflix has created a library called https://github.com/Netflix/Hystrix[Hystrix] .Microservice Graph image::HystrixGraph.png[] -A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service reach a certain threshold (20 failures in 5 seconds is the default in Hystrix), the circuit opens and the call is not made. In cases of error and an open circuit a fallback can be provided by the developer. +A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service is greater than `circuitBreaker.requestVolumeThreshold` (default: 20 requests) and failue percentage is greater than `circuitBreaker.errorThresholdPercentage` (default: >50%) in a rolling window defined by `metrics.rollingStats.timeInMilliseconds` (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit a fallback can be provided by the developer. .Hystrix fallback prevents cascading failures image::HystrixFallback.png[] From 6b530b026966faad7e88efd0c67a68e93195bfcd Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 19 Jul 2017 12:11:07 -0600 Subject: [PATCH 2/2] Ensure ordering of ribbon client configuration. On certain platforms and compilers the generated bytecode is not what the configuration expected. By moving inner classes to the upper level and using @Import this will guarantee the proper order. fixes gh-2086 --- .../ribbon/RestClientRibbonConfiguration.java | 64 +++++++++ .../ribbon/RibbonClientConfiguration.java | 123 +----------------- .../apache/HttpClientRibbonConfiguration.java | 72 ++++++++++ .../okhttp/OkHttpRibbonConfiguration.java | 75 +++++++++++ .../RibbonClientConfigurationTests.java | 3 + 5 files changed, 220 insertions(+), 117 deletions(-) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RestClientRibbonConfiguration.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/HttpClientRibbonConfiguration.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpRibbonConfiguration.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RestClientRibbonConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RestClientRibbonConfiguration.java new file mode 100644 index 00000000..ca829560 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RestClientRibbonConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.ribbon; + +import com.netflix.client.AbstractLoadBalancerAwareClient; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.niws.client.http.RestClient; +import com.netflix.servo.monitor.Monitors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +/** + * @author Spencer Gibb + */ +@SuppressWarnings("deprecation") +@Configuration +@RibbonAutoConfiguration.ConditionalOnRibbonRestClient +class RestClientRibbonConfiguration { + @Value("${ribbon.client.name}") + private String name = "client"; + + /** + * Create a Netflix {@link RestClient} integrated with Ribbon if none already exists + * in the application context. It is not required for Ribbon to work properly and is + * therefore created lazily if ever another component requires it. + * + * @param config the configuration to use by the underlying Ribbon instance + * @param loadBalancer the load balancer to use by the underlying Ribbon instance + * @param serverIntrospector server introspector to use by the underlying Ribbon instance + * @param retryHandler retry handler to use by the underlying Ribbon instance + * @return a {@link RestClient} instances backed by Ribbon + */ + @Bean + @Lazy + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer, + ServerIntrospector serverIntrospector, RetryHandler retryHandler) { + RestClient client = new RibbonClientConfiguration.OverrideRestClient(config, serverIntrospector); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java index d33c214e..c1be2009 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java @@ -24,21 +24,14 @@ import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; -import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; -import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; -import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; -import org.springframework.cloud.netflix.ribbon.okhttp.RetryableOkHttpLoadBalancingClient; +import org.springframework.cloud.netflix.ribbon.apache.HttpClientRibbonConfiguration; +import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Import; -import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.DefaultLoadBalancerRetryHandler; import com.netflix.client.RetryHandler; import com.netflix.client.config.DefaultClientConfigImpl; @@ -56,7 +49,6 @@ import com.netflix.loadbalancer.ServerListUpdater; import com.netflix.loadbalancer.ZoneAvoidanceRule; import com.netflix.loadbalancer.ZoneAwareLoadBalancer; import com.netflix.niws.client.http.RestClient; -import com.netflix.servo.monitor.Monitors; import com.sun.jersey.api.client.Client; import com.sun.jersey.client.apache4.ApacheHttpClient4; @@ -70,6 +62,9 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps @SuppressWarnings("deprecation") @Configuration @EnableConfigurationProperties +//Order is important here, last should be the default, first should be optional +// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 +@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class}) public class RibbonClientConfiguration { @Value("${ribbon.client.name}") @@ -121,112 +116,6 @@ public class RibbonClientConfiguration { return serverList; } - @Configuration - @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) - protected static class HttpClientRibbonConfiguration { - @Value("${ribbon.client.name}") - private String name = "client"; - - @Bean - @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") - public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( - IClientConfig config, ServerIntrospector serverIntrospector, - ILoadBalancer loadBalancer, RetryHandler retryHandler) { - RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( - config, serverIntrospector); - client.setLoadBalancer(loadBalancer); - client.setRetryHandler(retryHandler); - Monitors.registerObject("Client_" + this.name, client); - return client; - } - - @Bean - @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") - public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( - IClientConfig config, ServerIntrospector serverIntrospector, - ILoadBalancer loadBalancer, RetryHandler retryHandler, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( - config, serverIntrospector, loadBalancedRetryPolicyFactory); - client.setLoadBalancer(loadBalancer); - client.setRetryHandler(retryHandler); - Monitors.registerObject("Client_" + this.name, client); - return client; - } - } - - @Configuration - @ConditionalOnProperty("ribbon.okhttp.enabled") - @ConditionalOnClass(name = "okhttp3.OkHttpClient") - protected static class OkHttpRibbonConfiguration { - @Value("${ribbon.client.name}") - private String name = "client"; - - - - @Bean - @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") - public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, - ServerIntrospector serverIntrospector, - ILoadBalancer loadBalancer, - RetryHandler retryHandler, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(config, - serverIntrospector, loadBalancedRetryPolicyFactory); - client.setLoadBalancer(loadBalancer); - client.setRetryHandler(retryHandler); - Monitors.registerObject("Client_" + this.name, client); - return client; - } - - @Bean - @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") - public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(IClientConfig config, - ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, - RetryHandler retryHandler) { - OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, - serverIntrospector); - client.setLoadBalancer(loadBalancer); - client.setRetryHandler(retryHandler); - Monitors.registerObject("Client_" + this.name, client); - return client; - } - } - - @Configuration - @RibbonAutoConfiguration.ConditionalOnRibbonRestClient - protected static class RestClientRibbonConfiguration { - @Value("${ribbon.client.name}") - private String name = "client"; - - /** - * Create a Netflix {@link RestClient} integrated with Ribbon if none already exists - * in the application context. It is not required for Ribbon to work properly and is - * therefore created lazily if ever another component requires it. - * - * @param config the configuration to use by the underlying Ribbon instance - * @param loadBalancer the load balancer to use by the underlying Ribbon instance - * @param serverIntrospector server introspector to use by the underlying Ribbon instance - * @param retryHandler retry handler to use by the underlying Ribbon instance - * @return a {@link RestClient} instances backed by Ribbon - */ - @Bean - @Lazy - @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer, - ServerIntrospector serverIntrospector, RetryHandler retryHandler) { - RestClient client = new OverrideRestClient(config, serverIntrospector); - client.setLoadBalancer(loadBalancer); - client.setRetryHandler(retryHandler); - Monitors.registerObject("Client_" + this.name, client); - return client; - } - } - @Bean @ConditionalOnMissingBean public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/HttpClientRibbonConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/HttpClientRibbonConfiguration.java new file mode 100644 index 00000000..af0e64bd --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/HttpClientRibbonConfiguration.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.ribbon.apache; + +import com.netflix.client.AbstractLoadBalancerAwareClient; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.servo.monitor.Monitors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) +public class HttpClientRibbonConfiguration { + @Value("${ribbon.client.name}") + private String name = "client"; + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") + public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( + IClientConfig config, ServerIntrospector serverIntrospector, + ILoadBalancer loadBalancer, RetryHandler retryHandler) { + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( + config, serverIntrospector); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") + public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( + IClientConfig config, ServerIntrospector serverIntrospector, + ILoadBalancer loadBalancer, RetryHandler retryHandler, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( + config, serverIntrospector, loadBalancedRetryPolicyFactory); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpRibbonConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpRibbonConfiguration.java new file mode 100644 index 00000000..3dd481d2 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpRibbonConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.ribbon.okhttp; + +import com.netflix.client.AbstractLoadBalancerAwareClient; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.servo.monitor.Monitors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnProperty("ribbon.okhttp.enabled") +@ConditionalOnClass(name = "okhttp3.OkHttpClient") +public class OkHttpRibbonConfiguration { + @Value("${ribbon.client.name}") + private String name = "client"; + + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") + public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, + ServerIntrospector serverIntrospector, + ILoadBalancer loadBalancer, + RetryHandler retryHandler, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(config, + serverIntrospector, loadBalancedRetryPolicyFactory); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") + public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(IClientConfig config, + ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, + RetryHandler retryHandler) { + OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, + serverIntrospector); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfigurationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfigurationTests.java index efdac8ac..9e30bb53 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfigurationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfigurationTests.java @@ -148,18 +148,21 @@ public class RibbonClientConfigurationTests { return clients; } + @SuppressWarnings("deprecation") @Test public void testDefaultsToApacheHttpClient() { testClient(RibbonLoadBalancingHttpClient.class, null, RestClient.class, OkHttpLoadBalancingClient.class); testClient(RibbonLoadBalancingHttpClient.class, "ribbon.httpclient.enabled", RestClient.class, OkHttpLoadBalancingClient.class); } + @SuppressWarnings("deprecation") @Test public void testEnableRestClient() { testClient(RestClient.class, "ribbon.restclient.enabled", RibbonLoadBalancingHttpClient.class, OkHttpLoadBalancingClient.class); } + @SuppressWarnings("deprecation") @Test public void testEnableOkHttpClient() { testClient(OkHttpLoadBalancingClient.class, "ribbon.okhttp.enabled", RibbonLoadBalancingHttpClient.class,