diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 595d2233..629bad6e 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -2735,6 +2735,27 @@ https://github.com/spring-projects/spring-retry[Spring Retry] on your applicatio When Spring Retry is present load balanced `RestTemplates`, Feign, and Zuul will automatically retry any failed requests (assuming you configuration allows it to). +==== BackOff Policies +By default no backoff policy is used when retrying requests. If you would like to configure +a backoff policy you will need to create a bean of type `LoadBalancedBackOffPolicyFactory` +which will be used to create a `BackOffPolicy` for a given service. + +[source,java,indent=0] +---- +@Configuration +public class MyConfiguration { + @Bean + LoadBalancedBackOffPolicyFactory backOffPolciyFactory() { + return new LoadBalancedBackOffPolicyFactory() { + @Override + public BackOffPolicy createBackOffPolicy(String service) { + return new ExponentialBackOffPolicy(); + } + }; + } +} +---- + ==== Configuration Anytime Ribbon is used with Spring Retry you can control the retry functionality by configuring diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/CachingSpringLoadBalancerFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/CachingSpringLoadBalancerFactory.java index c2e0f940..3930a663 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/CachingSpringLoadBalancerFactory.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/CachingSpringLoadBalancerFactory.java @@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.feign.ribbon; import java.util.Map; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; @@ -32,11 +33,13 @@ import com.netflix.loadbalancer.ILoadBalancer; * * @author Spencer Gibb * @author Dave Syer + * @author Ryan Baxter */ public class CachingSpringLoadBalancerFactory { private final SpringClientFactory factory; private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; + private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory; private boolean enableRetry = false; private volatile Map cache = new ConcurrentReferenceHashMap<>(); @@ -44,19 +47,35 @@ public class CachingSpringLoadBalancerFactory { public CachingSpringLoadBalancerFactory(SpringClientFactory factory) { this.factory = factory; this.loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(factory); + this.loadBalancedBackOffPolicyFactory = null; } + @Deprecated + //TODO remove in 2.0.x public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { this.factory = factory; this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + this.loadBalancedBackOffPolicyFactory = null; } + @Deprecated + //TODO remove in 2.0.0x public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, boolean enableRetry) { this.factory = factory; this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; this.enableRetry = enableRetry; + this.loadBalancedBackOffPolicyFactory = null; + } + + public CachingSpringLoadBalancerFactory(SpringClientFactory factory, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { + this.factory = factory; + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory; + this.enableRetry = true; } public FeignLoadBalancer create(String clientName) { @@ -67,7 +86,7 @@ public class CachingSpringLoadBalancerFactory { ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, - loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); + loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client); return client; } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java index 7d304986..c9ca5d33 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java @@ -21,6 +21,7 @@ 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.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties; @@ -65,8 +66,9 @@ public class FeignRibbonClientAutoConfiguration { @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory( SpringClientFactory factory, - LoadBalancedRetryPolicyFactory retryPolicyFactory) { - return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, true); + LoadBalancedRetryPolicyFactory retryPolicyFactory, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { + return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory); } @Bean diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancer.java index d82578ee..12546e2a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancer.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancer.java @@ -23,6 +23,7 @@ import feign.Response; import java.io.IOException; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; @@ -32,6 +33,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; +import org.springframework.retry.backoff.BackOffPolicy; +import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import com.netflix.client.DefaultLoadBalancerRetryHandler; @@ -48,12 +51,26 @@ import com.netflix.loadbalancer.Server; public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser { private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; + private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory; + @Deprecated + //TODO remove in 2.0.x public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { super(lb, clientConfig, serverIntrospector); this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig)); + this.loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); + } + + public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, + ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { + super(lb, clientConfig, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig)); + this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ? + new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory; } @Override @@ -72,6 +89,8 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser } final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); RetryTemplate retryTemplate = new RetryTemplate(); + BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName()); + retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy() : new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName())); return retryTemplate.execute(new RetryCallback() { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java index 8d03633c..9f9127f6 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java @@ -35,6 +35,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; @@ -102,6 +104,13 @@ public class RibbonAutoConfiguration { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); } + @Bean + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") + @ConditionalOnMissingBean + public LoadBalancedBackOffPolicyFactory loadBalancedBackoffPolicyFactory() { + return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); + } + @Bean @ConditionalOnMissingBean public PropertiesFactory propertiesFactory() { 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 index 6f2b6c27..2eb8e41c 100644 --- 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 @@ -31,6 +31,7 @@ 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.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; @@ -151,9 +152,11 @@ public class HttpClientRibbonConfiguration { public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( IClientConfig config, ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, RetryHandler retryHandler, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient) { + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( - httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory); + httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory, + loadBalancedBackOffPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); Monitors.registerObject("Client_" + this.name, client); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java index 769c75bc..949343b7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java @@ -24,6 +24,7 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; @@ -35,6 +36,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.http.HttpRequest; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; +import org.springframework.retry.backoff.BackOffPolicy; +import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -51,7 +54,11 @@ import com.netflix.loadbalancer.Server; public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient implements ServiceInstanceChooser { private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); + private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = + new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); + @Deprecated + //TODO remove in 2.0.x public RetryableRibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { @@ -59,6 +66,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; } + @Deprecated + //TODO remove in 2.0.x public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { @@ -66,6 +75,15 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; } + public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate, + IClientConfig config, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { + super(delegate, config, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory; + } + @Override public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { @@ -137,6 +155,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy() : new RetryPolicy(request, retryPolicy, this, this.getClientName())); + BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName()); + retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); return retryTemplate.execute(callback); } 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 index 1380df83..1e1f2fbf 100644 --- 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 @@ -34,6 +34,7 @@ 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.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; @@ -115,9 +116,10 @@ public class OkHttpRibbonConfiguration { ILoadBalancer loadBalancer, RetryHandler retryHandler, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, - OkHttpClient delegate) { + OkHttpClient delegate, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(delegate, config, - serverIntrospector, loadBalancedRetryPolicyFactory); + serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); Monitors.registerObject("Client_" + this.name, client); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java index 3fd53528..67876d47 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java @@ -22,6 +22,7 @@ import okhttp3.Response; import java.net.URI; import org.apache.commons.lang.BooleanUtils; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; @@ -33,6 +34,8 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.http.HttpRequest; import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; +import org.springframework.retry.backoff.BackOffPolicy; +import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -48,17 +51,31 @@ import com.netflix.loadbalancer.Server; public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser { private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; + private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = + new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); + @Deprecated + //TODO remove in 2.0.x public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { super(delegate, config, serverIntrospector); this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; } + public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) { + super(delegate, config, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory; + } + private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy, RetryCallback callback) throws Exception { RetryTemplate retryTemplate = new RetryTemplate(); + BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName()); + retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); boolean retryable = request.getContext() == null ? true : BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true); retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy() diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancerTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancerTests.java index d37bf7a7..4a86ceef 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancerTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/ribbon/RetryableFeignLoadBalancerTests.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; @@ -43,6 +44,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.http.HttpRequest; +import org.springframework.retry.RetryContext; +import org.springframework.retry.backoff.BackOffContext; +import org.springframework.retry.backoff.BackOffInterruptedException; +import org.springframework.retry.backoff.BackOffPolicy; import com.netflix.client.RequestSpecificRetryHandler; import com.netflix.client.config.CommonClientConfigKey; @@ -80,6 +85,8 @@ public class RetryableFeignLoadBalancerTests { @Mock private IClientConfig config; private ServerIntrospector inspector = new DefaultServerIntrospector(); + private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = + new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); private Integer defaultConnectTimeout = 10000; private Integer defaultReadTimeout = 10000; @@ -117,7 +124,8 @@ public class RetryableFeignLoadBalancerTests { FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo")); Response response = Response.builder().status(200).headers(new HashMap>()).build(); doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class)); - RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory); + RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, + loadBalancedBackOffPolicyFactory); FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null); assertEquals(200, ribbonResponse.toResponse().status()); verify(client, times(1)).execute(any(Request.class), any(Request.Options.class)); @@ -136,7 +144,7 @@ public class RetryableFeignLoadBalancerTests { public LoadBalancedRetryPolicy create(String s, ServiceInstanceChooser serviceInstanceChooser) { return null; } - }); + }, loadBalancedBackOffPolicyFactory); try { feignLb.execute(request, null); } catch(Exception e) { @@ -167,10 +175,13 @@ public class RetryableFeignLoadBalancerTests { FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo")); Response response = Response.builder().status(200).headers(new HashMap>()).build(); doThrow(new IOException("boom")).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class)); - RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory); + MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory(); + RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, + backOffPolicyFactory); FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null); assertEquals(200, ribbonResponse.toResponse().status()); verify(client, times(2)).execute(any(Request.class), any(Request.Options.class)); + assertEquals(1, backOffPolicyFactory.getCount()); } @Test @@ -195,10 +206,13 @@ public class RetryableFeignLoadBalancerTests { Response response = Response.builder().status(200).headers(new HashMap>()).build(); Response fourOFourResponse = Response.builder().status(404).headers(new HashMap>()).build(); doReturn(fourOFourResponse).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class)); - RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory); + MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory(); + RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, + backOffPolicyFactory); FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null); assertEquals(200, ribbonResponse.toResponse().status()); verify(client, times(2)).execute(any(Request.class), any(Request.Options.class)); + assertEquals(1, backOffPolicyFactory.getCount()); } @Test @@ -214,7 +228,8 @@ public class RetryableFeignLoadBalancerTests { FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo")); Response response = Response.builder().status(200).headers(new HashMap>()).build(); doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class)); - RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory); + RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, + loadBalancedBackOffPolicyFactory); RequestSpecificRetryHandler retryHandler = feignLb.getRequestSpecificRetryHandler(request, config); assertEquals(1, retryHandler.getMaxRetriesOnNextServer()); assertEquals(1, retryHandler.getMaxRetriesOnSameServer()); @@ -265,11 +280,35 @@ public class RetryableFeignLoadBalancerTests { public List getAllServers() { return null; } - }, config, inspector, loadBalancedRetryPolicyFactory); + }, config, inspector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory); ServiceInstance serviceInstance = feignLb.choose("foo"); assertEquals("foo", serviceInstance.getHost()); assertEquals(80, serviceInstance.getPort()); } + class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy { + + private int count = 0; + + @Override + public BackOffContext start(RetryContext retryContext) { + return null; + } + + @Override + public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException { + count++; + } + + public int getCount() { + return count; + } + + @Override + public BackOffPolicy createBackOffPolicy(String service) { + return this; + } + } + } \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java index c20fb9aa..f3464873 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java @@ -30,6 +30,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; @@ -42,6 +43,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.retry.RetryContext; +import org.springframework.retry.backoff.BackOffContext; +import org.springframework.retry.backoff.BackOffInterruptedException; +import org.springframework.retry.backoff.BackOffPolicy; import org.springframework.test.util.ReflectionTestUtils; import com.netflix.client.DefaultLoadBalancerRetryHandler; @@ -54,6 +59,7 @@ import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; @@ -72,6 +78,7 @@ import static org.mockito.Mockito.verify; public class RibbonLoadBalancingHttpClientTests { private ILoadBalancer loadBalancer; + private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory(); @Before public void setup() { @@ -195,9 +202,10 @@ public class RibbonLoadBalancingHttpClientTests { } private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer, - boolean retryable, boolean retryOnAllOps, - String serviceName, String host, int port, - HttpClient delegate, ILoadBalancer lb, String statusCodes) throws Exception { + boolean retryable, boolean retryOnAllOps, + String serviceName, String host, int port, + CloseableHttpClient delegate, ILoadBalancer lb, String statusCodes, + LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) throws Exception { ServerIntrospector introspector = mock(ServerIntrospector.class); RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable); doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName)); @@ -212,7 +220,8 @@ public class RibbonLoadBalancingHttpClientTests { doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName)); doReturn(clientConfig).when(clientFactory).getClientConfig(eq(serviceName)); LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory); - RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(clientConfig, introspector, factory); + RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(delegate, clientConfig, + introspector, factory, loadBalancedBackOffPolicyFactory); client.setLoadBalancer(lb); ReflectionTestUtils.setField(client, "delegate", delegate); return client; @@ -237,7 +246,7 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, - serviceName, host, port, delegate, lb, ""); + serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); @@ -269,8 +278,9 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); + MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory(); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, - serviceName, host, port, delegate, lb, ""); + serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); @@ -281,6 +291,7 @@ public class RibbonLoadBalancingHttpClientTests { RibbonApacheHttpResponse returnedResponse = client.execute(request, null); verify(delegate, times(3)).execute(any(HttpUriRequest.class)); verify(lb, times(1)).chooseServer(eq(serviceName)); + assertEquals(2, myBackOffPolicyFactory.getCount()); } @Test @@ -302,8 +313,9 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); + MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory(); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, - serviceName, host, port, delegate, lb, ""); + serviceName, host, port, delegate, lb, "", myBackOffPolicyFactory); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); doReturn(uri).when(request).getURI(); @@ -314,6 +326,7 @@ public class RibbonLoadBalancingHttpClientTests { verify(response, times(0)).close(); verify(delegate, times(3)).execute(any(HttpUriRequest.class)); verify(lb, times(1)).chooseServer(eq(serviceName)); + assertEquals(2, myBackOffPolicyFactory.getCount()); } @Test @@ -333,7 +346,7 @@ public class RibbonLoadBalancingHttpClientTests { when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, - serviceName, host, port, delegate, lb, ""); + serviceName, host, port, delegate, lb, "", loadBalancedBackOffPolicyFactory); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); doReturn(uri).when(request).getURI(); @@ -373,8 +386,9 @@ public class RibbonLoadBalancingHttpClientTests { doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine(); doReturn(fourOFourResponse).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); + MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory(); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, - serviceName, host, port, delegate, lb, "404"); + serviceName, host, port, delegate, lb, "404", myBackOffPolicyFactory); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); @@ -386,6 +400,7 @@ public class RibbonLoadBalancingHttpClientTests { verify(fourOFourResponse, times(1)).close(); verify(delegate, times(2)).execute(any(HttpUriRequest.class)); verify(lb, times(0)).chooseServer(eq(serviceName)); + assertEquals(1, myBackOffPolicyFactory.getCount()); } @Configuration @@ -477,4 +492,28 @@ public class RibbonLoadBalancingHttpClientTests { return requestConfigCaptor.getValue(); } + class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy { + + private int count = 0; + + @Override + public BackOffContext start(RetryContext retryContext) { + return null; + } + + @Override + public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException { + count++; + } + + public int getCount() { + return count; + } + + @Override + public BackOffPolicy createBackOffPolicy(String service) { + return this; + } + } + }