Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/main/asciidoc/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
|spring.cloud.loadbalancer.health-check.interval | 25s | Interval for rerunning the HealthCheck scheduler.
|spring.cloud.loadbalancer.health-check.path | |
|spring.cloud.loadbalancer.retry.enabled | true |
|spring.cloud.loadbalancer.retry.max-retries-on-next-service-instance | 1 | Number of retries to be executed on the next <code>ServiceInstance</code>. A <code>ServiceInstance</code> is chosen before each retry call.
|spring.cloud.loadbalancer.retry.max-retries-on-same-service-instance | 0 | Number of retries to be executed on the same <code>ServiceInstance</code>.
|spring.cloud.loadbalancer.retry.retry-on-all-operations | false | Indicates retries should be attempted on operations other than {@link HttpMethod#GET}.
|spring.cloud.loadbalancer.retry.retryable-status-codes | | A {@link Set} of status codes that should trigger a retry.
|spring.cloud.loadbalancer.ribbon.enabled | true | Causes `RibbonLoadBalancerClient` to be used by default.
|spring.cloud.loadbalancer.service-discovery.timeout | | String representation of Duration of the timeout for calls to service discovery.
|spring.cloud.loadbalancer.zone | | Spring Cloud LoadBalancer zone.
Expand Down
21 changes: 18 additions & 3 deletions docs/src/main/asciidoc/spring-cloud-commons.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,27 @@ set the `spring.cloud.loadbalancer.ribbon.enabled` property to `false`.
A load-balanced `RestTemplate` can be configured to retry failed requests.
By default, this logic is disabled.
You can enable it by adding link:https://github.com/spring-projects/spring-retry[Spring Retry] to your application's classpath.
The load-balanced `RestTemplate` honors some of the Ribbon configuration values related to retrying failed requests.
You can use `client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and `client.ribbon.OkToRetryOnAllOperations` properties.

If you would like to disable the retry logic with Spring Retry on the classpath, you can set `spring.cloud.loadbalancer.retry.enabled=false`.

If you would like to implement a `BackOffPolicy` in your retries, you need to create a bean of type `LoadBalancedRetryFactory` and override the `createBackOffPolicy()` method.

===== Ribbon-based retries

For the Ribbon-backed implementation, the load-balanced `RestTemplate` honors some of the Ribbon configuration values related to retrying failed requests.
You can use the `client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and `client.ribbon.OkToRetryOnAllOperations` properties.

See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation] for a description of what these properties do.

If you would like to implement a `BackOffPolicy` in your retries, you need to create a bean of type `LoadBalancedRetryFactory` and override the `createBackOffPolicy` method:
===== Spring Cloud LoadBalancer-based retries

For the Spring Cloud LoadBalancer-backed implementation, you can set:

- `spring.cloud.loadbalancer.retry.maxRetriesOnSameServiceInstance` - indicates how many times a request should be retried on the same `ServiceInstance` (counted separately for every selected instance)
- `spring.cloud.loadbalancer.retry.maxRetriesOnNextServiceInstance` - indicates how many times a request should be retried a newly selected `ServiceInstance`
- `spring.cloud.loadbalancer.retry.retryableStatusCodes` - the status codes on which to always retry a failed request.

WARN:: If you chose to override the `LoadBalancedRetryFactory` while using the Spring Cloud LoadBalancer-backed approach, make sure you annotate your bean with `@Order` and set it to a higher precedence than `1000`, which is the order set on the `BlockingLoadBalancedRetryFactory`.

====
[source,java,indent=0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
*/
public class InterceptorRetryPolicy implements RetryPolicy {

private HttpRequest request;
private final HttpRequest request;

private LoadBalancedRetryPolicy policy;
private final LoadBalancedRetryPolicy policy;

private ServiceInstanceChooser serviceInstanceChooser;
private final ServiceInstanceChooser serviceInstanceChooser;

private String serviceName;
private final String serviceName;

/**
* Creates a new retry policy.
Expand All @@ -56,21 +56,20 @@ public boolean canRetry(RetryContext context) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
// We haven't even tried to make the request yet so return true so we do
lbContext.setServiceInstance(
this.serviceInstanceChooser.choose(this.serviceName));
lbContext.setServiceInstance(serviceInstanceChooser.choose(serviceName));
return true;
}
return this.policy.canRetryNextServer(lbContext);
return policy.canRetryNextServer(lbContext);
}

@Override
public RetryContext open(RetryContext parent) {
return new LoadBalancedRetryContext(parent, this.request);
return new LoadBalancedRetryContext(parent, request);
}

@Override
public void close(RetryContext context) {
this.policy.close((LoadBalancedRetryContext) context);
policy.close((LoadBalancedRetryContext) context);
}

@Override
Expand All @@ -80,7 +79,7 @@ public void registerThrowable(RetryContext context, Throwable throwable) {
// increases the retry count
lbContext.registerThrowable(throwable);
// let the policy know about the exception as well
this.policy.registerThrowable(lbContext, throwable);
policy.registerThrowable(lbContext, throwable);
}

@Override
Expand All @@ -94,25 +93,25 @@ public boolean equals(Object o) {

InterceptorRetryPolicy that = (InterceptorRetryPolicy) o;

if (!this.request.equals(that.request)) {
if (!request.equals(that.request)) {
return false;
}
if (!this.policy.equals(that.policy)) {
if (!policy.equals(that.policy)) {
return false;
}
if (!this.serviceInstanceChooser.equals(that.serviceInstanceChooser)) {
if (!serviceInstanceChooser.equals(that.serviceInstanceChooser)) {
return false;
}
return this.serviceName.equals(that.serviceName);
return serviceName.equals(that.serviceName);

}

@Override
public int hashCode() {
int result = this.request.hashCode();
result = 31 * result + this.policy.hashCode();
result = 31 * result + this.serviceInstanceChooser.hashCode();
result = 31 * result + this.serviceName.hashCode();
int result = request.hashCode();
result = 31 * result + policy.hashCode();
result = 31 * result + serviceInstanceChooser.hashCode();
result = 31 * result + serviceName.hashCode();
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;

/**
* Auto-configuration for Ribbon (client-side load balancing).
* Auto-configuration for blocking client-side load balancing.
*
* @author Spencer Gibb
* @author Dave Syer
Expand Down Expand Up @@ -79,7 +80,7 @@ public LoadBalancerRequestFactory loadBalancerRequestFactory(
static class LoadBalancerInterceptorConfig {

@Bean
public LoadBalancerInterceptor ribbonInterceptor(
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
Expand Down Expand Up @@ -124,13 +125,14 @@ public static class RetryInterceptorAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
requestFactory, loadBalancedRetryFactories.get(0));
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

package org.springframework.cloud.client.loadbalancer;

import java.util.HashSet;
import java.util.Set;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpMethod;

/**
* Configuration properties for the {@link LoadBalancerClient}.
Expand All @@ -28,6 +32,28 @@ public class LoadBalancerRetryProperties {

private boolean enabled = true;

/**
* Indicates retries should be attempted on operations other than
* {@link HttpMethod#GET}.
*/
private boolean retryOnAllOperations = false;

/**
* Number of retries to be executed on the same <code>ServiceInstance</code>.
*/
private int maxRetriesOnSameServiceInstance = 0;

/**
* Number of retries to be executed on the next <code>ServiceInstance</code>. A
* <code>ServiceInstance</code> is chosen before each retry call.
*/
private int maxRetriesOnNextServiceInstance = 1;

/**
* A {@link Set} of status codes that should trigger a retry.
*/
private Set<Integer> retryableStatusCodes = new HashSet<>();

/**
* Returns true if the load balancer should retry failed requests.
* @return True if the load balancer should retry failed requests; false otherwise.
Expand All @@ -44,4 +70,36 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public boolean isRetryOnAllOperations() {
return retryOnAllOperations;
}

public void setRetryOnAllOperations(boolean retryOnAllOperations) {
this.retryOnAllOperations = retryOnAllOperations;
}

public int getMaxRetriesOnSameServiceInstance() {
return maxRetriesOnSameServiceInstance;
}

public void setMaxRetriesOnSameServiceInstance(int maxRetriesOnSameServiceInstance) {
this.maxRetriesOnSameServiceInstance = maxRetriesOnSameServiceInstance;
}

public int getMaxRetriesOnNextServiceInstance() {
return maxRetriesOnNextServiceInstance;
}

public void setMaxRetriesOnNextServiceInstance(int maxRetriesOnNextServiceInstance) {
this.maxRetriesOnNextServiceInstance = maxRetriesOnNextServiceInstance;
}

public Set<Integer> getRetryableStatusCodes() {
return retryableStatusCodes;
}

public void setRetryableStatusCodes(Set<Integer> retryableStatusCodes) {
this.retryableStatusCodes = retryableStatusCodes;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
*/
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {

private LoadBalancerClient loadBalancer;
private final LoadBalancerClient loadBalancer;

private LoadBalancerRetryProperties lbProperties;
private final LoadBalancerRetryProperties lbProperties;

private LoadBalancerRequestFactory requestFactory;
private final LoadBalancerRequestFactory requestFactory;

private LoadBalancedRetryFactory lbRetryFactory;
private final LoadBalancedRetryFactory lbRetryFactory;

public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRetryProperties lbProperties,
Expand All @@ -65,8 +65,8 @@ public ClientHttpResponse intercept(final HttpRequest request, final byte[] body
final String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory
.createRetryPolicy(serviceName, this.loadBalancer);
final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory
.createRetryPolicy(serviceName, loadBalancer);
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
return template.execute(context -> {
ServiceInstance serviceInstance = null;
Expand All @@ -75,11 +75,11 @@ public ClientHttpResponse intercept(final HttpRequest request, final byte[] body
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = this.loadBalancer.choose(serviceName);
serviceInstance = loadBalancer.choose(serviceName);
}
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
.execute(serviceName, serviceInstance,
this.requestFactory.createRequest(request, body, execution));
ClientHttpResponse response = loadBalancer.execute(serviceName,
serviceInstance,
requestFactory.createRequest(request, body, execution));
int statusCode = response.getRawStatusCode();
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
Expand All @@ -103,19 +103,17 @@ protected ClientHttpResponse createResponse(ClientHttpResponse response,
private RetryTemplate createRetryTemplate(String serviceName, HttpRequest request,
LoadBalancedRetryPolicy retryPolicy) {
RetryTemplate template = new RetryTemplate();
BackOffPolicy backOffPolicy = this.lbRetryFactory
.createBackOffPolicy(serviceName);
BackOffPolicy backOffPolicy = lbRetryFactory.createBackOffPolicy(serviceName);
template.setBackOffPolicy(
backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
template.setThrowLastExceptionOnExhausted(true);
RetryListener[] retryListeners = this.lbRetryFactory
.createRetryListeners(serviceName);
RetryListener[] retryListeners = lbRetryFactory.createRetryListeners(serviceName);
if (retryListeners != null && retryListeners.length != 0) {
template.setListeners(retryListeners);
}
template.setRetryPolicy(!this.lbProperties.isEnabled() || retryPolicy == null
template.setRetryPolicy(!lbProperties.isEnabled() || retryPolicy == null
? new NeverRetryPolicy() : new InterceptorRetryPolicy(request,
retryPolicy, this.loadBalancer, serviceName));
retryPolicy, loadBalancer, serviceName));
return template;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.cloud.client.loadbalancer;

import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
Expand Down Expand Up @@ -156,7 +155,7 @@ public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
LoadBalancerRequest<T> request) {
try {
return request.apply(choose(serviceId));
}
Expand Down
Loading