Skip to content

Commit 25c1708

Browse files
Avoid retrying on same instance (#834)
* cherry-pick switching to properties * Pass information on previous ServiceInstance to RequestContext. # Conflicts: # spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryPolicy.java * Add a RoundRobinLoadBalancer implementation that avoids same service instance while retrying. * Wrap instances in ArrayList. Add tests. * Enable AvoidPreviousInstanceRoundRobinLoadBalancer by default if SpringRetry on classpath. * Fix failing tests. Add javadocs and author tags. * Fix properties. * Add documentation. * Fix docs after review. * Fix docs after review. * Handle avoiding previous instance with ServiceInstanceListSupplier in place of LoadBalancer. * Fix property name. * Change spelling.
1 parent cbe4bb1 commit 25c1708

26 files changed

+521
-220
lines changed

docs/src/main/asciidoc/_configprops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
|spring.cloud.loadbalancer.health-check.interval | 25s | Interval for rerunning the HealthCheck scheduler.
3636
|spring.cloud.loadbalancer.health-check.path | |
3737
|spring.cloud.loadbalancer.hint | | Allows setting the value of <code>hint</code> that is passed on to the LoadBalancer request and can subsequently be used in {@link ReactiveLoadBalancer} implementations.
38+
|spring.cloud.loadbalancer.retry.avoid-previous-instance | true | Enables wrapping ServiceInstanceListSupplier beans with `RetryAwareServiceInstanceListSupplier` if Spring-Retry is in the classpath.
3839
|spring.cloud.loadbalancer.retry.enabled | true |
3940
|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.
4041
|spring.cloud.loadbalancer.retry.max-retries-on-same-service-instance | 0 | Number of retries to be executed on the same <code>ServiceInstance</code>.

docs/src/main/asciidoc/spring-cloud-commons.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,9 @@ You can set:
448448
- `spring.cloud.loadbalancer.retry.maxRetriesOnSameServiceInstance` - indicates how many times a request should be retried on the same `ServiceInstance` (counted separately for every selected instance)
449449
- `spring.cloud.loadbalancer.retry.maxRetriesOnNextServiceInstance` - indicates how many times a request should be retried a newly selected `ServiceInstance`
450450
- `spring.cloud.loadbalancer.retry.retryableStatusCodes` - the status codes on which to always retry a failed request.
451+
452+
NOTE: For load-balanced retries, by default, we wrap the `ServiceInstanceListSupplier` bean with `RetryAwareServiceInstanceListSupplier` to select a different instance from the one previously chosen, if available. You can disable this behavior by setting the value of `spring.cloud.loadbalancer.retry.avoidPreviousInstance` to `false`.
453+
451454
====
452455
[source,java,indent=0]
453456
----

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/InterceptorRetryPolicy.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* requests.
2626
*
2727
* @author Ryan Baxter
28+
* @author Olga Maciaszek-Sharma
2829
*/
2930
public class InterceptorRetryPolicy implements RetryPolicy {
3031

@@ -56,7 +57,7 @@ public boolean canRetry(RetryContext context) {
5657
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
5758
if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
5859
// We haven't even tried to make the request yet so return true so we do
59-
lbContext.setServiceInstance(serviceInstanceChooser.choose(serviceName));
60+
lbContext.setServiceInstance(null);
6061
return true;
6162
}
6263
return policy.canRetryNextServer(lbContext);

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancedRetryContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@
2525
* {@link RetryContext} for load-balanced retries.
2626
*
2727
* @author Ryan Baxter
28+
* @author Olga Maciaszek-Sharma
2829
*/
2930
public class LoadBalancedRetryContext extends RetryContextSupport {
3031

3132
private HttpRequest request;
3233

3334
private ServiceInstance serviceInstance;
3435

36+
private ServiceInstance previousServiceInstance;
37+
3538
/**
3639
* Creates a new load-balanced context.
3740
* @param parent The parent context.
@@ -71,7 +74,16 @@ public ServiceInstance getServiceInstance() {
7174
* @param serviceInstance The service instance to use during the retry.
7275
*/
7376
public void setServiceInstance(ServiceInstance serviceInstance) {
77+
setPreviousServiceInstance(this.serviceInstance);
7478
this.serviceInstance = serviceInstance;
7579
}
7680

81+
public ServiceInstance getPreviousServiceInstance() {
82+
return previousServiceInstance;
83+
}
84+
85+
public void setPreviousServiceInstance(ServiceInstance previousServiceInstance) {
86+
this.previousServiceInstance = previousServiceInstance;
87+
}
88+
7789
}

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@
4444
* @author Dave Syer
4545
* @author Will Tran
4646
* @author Gang Li
47+
* @author Olga Maciaszek-Sharma
4748
*/
4849
@Configuration(proxyBeanMethods = false)
4950
@ConditionalOnClass(RestTemplate.class)
5051
@ConditionalOnBean(LoadBalancerClient.class)
51-
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
52+
@EnableConfigurationProperties(LoadBalancerProperties.class)
5253
public class LoadBalancerAutoConfiguration {
5354

5455
@LoadBalanced
@@ -125,11 +126,11 @@ public static class RetryInterceptorAutoConfiguration {
125126
@Bean
126127
@ConditionalOnMissingBean
127128
public RetryLoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
128-
LoadBalancerRetryProperties retryProperties, LoadBalancerRequestFactory requestFactory,
129-
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
129+
LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory,
130+
LoadBalancedRetryFactory loadBalancedRetryFactory,
130131
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
131-
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryProperties, requestFactory,
132-
loadBalancedRetryFactory, properties, loadBalancerFactory);
132+
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory,
133+
loadBalancedRetryFactory, loadBalancerFactory);
133134
}
134135

135136
@Bean

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRetryProperties.java

Lines changed: 0 additions & 105 deletions
This file was deleted.

spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,15 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
5252

5353
private final LoadBalancedRetryFactory lbRetryFactory;
5454

55-
private final LoadBalancerRetryProperties retryProperties;
56-
5755
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
5856

59-
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRetryProperties retryProperties,
57+
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties,
6058
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory,
61-
LoadBalancerProperties properties, ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
59+
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
6260
this.loadBalancer = loadBalancer;
63-
this.retryProperties = retryProperties;
61+
this.properties = properties;
6462
this.requestFactory = requestFactory;
6563
this.lbRetryFactory = lbRetryFactory;
66-
this.properties = properties;
6764
this.loadBalancerFactory = loadBalancerFactory;
6865
}
6966

@@ -85,11 +82,21 @@ public ClientHttpResponse intercept(final HttpRequest request, final byte[] body
8582
.getSupportedLifecycleProcessors(
8683
loadBalancerFactory.getInstances(serviceName, LoadBalancerLifecycle.class),
8784
HttpRequestContext.class, ClientHttpResponse.class, ServiceInstance.class);
88-
String hint = getHint(serviceName);
89-
DefaultRequest<HttpRequestContext> lbRequest = new DefaultRequest<>(new HttpRequestContext(request, hint));
90-
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
9185
if (serviceInstance == null) {
86+
ServiceInstance previousServiceInstance = null;
87+
if (context instanceof LoadBalancedRetryContext) {
88+
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
89+
previousServiceInstance = lbContext.getPreviousServiceInstance();
90+
}
91+
String hint = getHint(serviceName);
92+
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
93+
new RetryableRequestContext(previousServiceInstance, request, hint));
94+
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
9295
serviceInstance = loadBalancer.choose(serviceName, lbRequest);
96+
if (context instanceof LoadBalancedRetryContext) {
97+
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
98+
lbContext.setServiceInstance(serviceInstance);
99+
}
93100
}
94101
Response<ServiceInstance> lbResponse = new DefaultResponse(serviceInstance);
95102
if (serviceInstance == null) {
@@ -127,7 +134,7 @@ private RetryTemplate createRetryTemplate(String serviceName, HttpRequest reques
127134
if (retryListeners != null && retryListeners.length != 0) {
128135
template.setListeners(retryListeners);
129136
}
130-
template.setRetryPolicy(!retryProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
137+
template.setRetryPolicy(!properties.getRetry().isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
131138
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName));
132139
return template;
133140
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.client.loadbalancer;
18+
19+
import org.springframework.cloud.client.ServiceInstance;
20+
21+
/**
22+
* A request context object that allows storing information on previously used service
23+
* instances.
24+
*
25+
* @author Olga Maciaszek-Sharma
26+
*/
27+
public class RetryableRequestContext extends DefaultRequestContext {
28+
29+
private final ServiceInstance previousServiceInstance;
30+
31+
public RetryableRequestContext(ServiceInstance previousServiceInstance) {
32+
this.previousServiceInstance = previousServiceInstance;
33+
}
34+
35+
public RetryableRequestContext(ServiceInstance previousServiceInstance, Object clientRequest) {
36+
super(clientRequest);
37+
this.previousServiceInstance = previousServiceInstance;
38+
}
39+
40+
public RetryableRequestContext(ServiceInstance previousServiceInstance, Object clientRequest, String hint) {
41+
super(clientRequest, hint);
42+
this.previousServiceInstance = previousServiceInstance;
43+
}
44+
45+
public ServiceInstance getPreviousServiceInstance() {
46+
return previousServiceInstance;
47+
}
48+
49+
}

0 commit comments

Comments
 (0)