diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 193ae883e..b8b32e9f3 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -468,6 +468,14 @@ For the reactive implementation, you can additionally set: For the reactive implementation, you can also implement your own `LoadBalancerRetryPolicy` to have more detailed control over the load-balanced call retries. +If you would like to configure retries for specific clients, you need to set `spring.cloud.loadbalancer.clients.retry.configuration.enabled=true`. +Then you can set properties: + +- `spring.cloud.loadbalancer.clients.*clientName*.retry.maxRetriesOnSameServiceInstance` +- `spring.cloud.loadbalancer.clients.*clientName*.retry.maxRetriesOnNextServiceInstance` +- `spring.cloud.loadbalancer.clients.*clientName*.retry.retryOnAllOperations` +- `spring.cloud.loadbalancer.clients.*clientName*.retry.retryableStatusCodes` + 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`. ==== diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ClientLoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ClientLoadBalancerProperties.java new file mode 100644 index 000000000..3957afe5a --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/ClientLoadBalancerProperties.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.client.loadbalancer; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * A {@link ConfigurationProperties} bean for client specific Spring Cloud LoadBalancer. + * + * @author Andrii Bohutskyi + */ +@ConfigurationProperties("spring.cloud.loadbalancer") +public class ClientLoadBalancerProperties { + + private Map clients = new HashMap<>(); + + public Map getClients() { + return clients; + } + + public void setClients(Map clients) { + this.clients = clients; + } + + public Optional getClientLoadBalancerProperties(String service) { + return Optional.ofNullable(clients.get(service)); + } + +} diff --git a/spring-cloud-commons/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-commons/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3439d82ee..e14148c52 100644 --- a/spring-cloud-commons/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-commons/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -52,6 +52,12 @@ "name": "spring.cloud.loadbalancer.retry.enabled", "description": "Enables LoadBalancer retries.", "type": "java.lang.Boolean" + }, + { + "defaultValue": false, + "name": "spring.cloud.loadbalancer.clients.retry.configuration.enabled", + "description": "Enables LoadBalancer retries configuration per client.", + "type": "java.lang.Boolean" } ] } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/ClientBlockingLoadBalancedRetryFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/ClientBlockingLoadBalancedRetryFactory.java new file mode 100644 index 000000000..114a1c7b1 --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/ClientBlockingLoadBalancedRetryFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.loadbalancer.blocking.retry; + +import org.springframework.cloud.client.loadbalancer.ClientLoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; +import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; + +/** + * An implementation of {@link LoadBalancedRetryFactory} for client specific + * {@link BlockingLoadBalancerClient}. + * + * @author Andrii Bohutskyi + */ +public class ClientBlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory { + + private final LoadBalancerProperties loadBalancerProperties; + + private final ClientLoadBalancerProperties clientLoadBalancerProperties; + + public ClientBlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties, + ClientLoadBalancerProperties clientLoadBalancerProperties) { + this.loadBalancerProperties = loadBalancerProperties; + this.clientLoadBalancerProperties = clientLoadBalancerProperties; + } + + @Override + public LoadBalancedRetryPolicy createRetryPolicy(String service, ServiceInstanceChooser serviceInstanceChooser) { + final LoadBalancerProperties properties = clientLoadBalancerProperties.getClientLoadBalancerProperties(service) + .orElse(loadBalancerProperties); + + return new BlockingLoadBalancedRetryPolicy(properties); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java index f9fad6065..b342c516d 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java @@ -24,12 +24,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; +import org.springframework.cloud.client.loadbalancer.ClientLoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; +import org.springframework.cloud.loadbalancer.blocking.retry.ClientBlockingLoadBalancedRetryFactory; import org.springframework.cloud.loadbalancer.core.LoadBalancerServiceInstanceCookieTransformer; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; @@ -70,15 +72,25 @@ public LoadBalancerServiceInstanceCookieTransformer loadBalancerServiceInstanceC @Configuration @ConditionalOnClass(RetryTemplate.class) - @EnableConfigurationProperties(LoadBalancerProperties.class) + @EnableConfigurationProperties({ LoadBalancerProperties.class, ClientLoadBalancerProperties.class }) protected static class BlockingLoadBalancerRetryConfig { @Bean @ConditionalOnMissingBean + @ConditionalOnProperty(value = "spring.cloud.loadbalancer.clients.retry.configuration.enabled", + havingValue = "false", matchIfMissing = true) LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) { return new BlockingLoadBalancedRetryFactory(properties); } + @Bean + @ConditionalOnProperty(value = "spring.cloud.loadbalancer.clients.retry.configuration.enabled", + havingValue = "true") + LoadBalancedRetryFactory clientLoadBalancedRetryFactory(LoadBalancerProperties properties, + ClientLoadBalancerProperties clientProperties) { + return new ClientBlockingLoadBalancedRetryFactory(properties, clientProperties); + } + } } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java index c2d2ea3ff..c69166105 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfigurationTests.java @@ -23,6 +23,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; +import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; +import org.springframework.cloud.loadbalancer.blocking.retry.ClientBlockingLoadBalancedRetryFactory; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +33,7 @@ * @author Spencer Gibb * @author Olga Maciaszek-Sharma * @author Tim Ysewyn + * @author Andrii Bohutskyi */ class BlockingLoadBalancerClientAutoConfigurationTests { @@ -43,9 +46,19 @@ void beansCreatedNormally() { applicationContextRunner.run(ctxt -> { assertThat(ctxt).hasSingleBean(BlockingLoadBalancerClient.class); assertThat(ctxt).hasSingleBean(LoadBalancedRetryFactory.class); + assertThat(ctxt.getBean(LoadBalancedRetryFactory.class)).isInstanceOf(BlockingLoadBalancedRetryFactory.class); }); } + @Test + void enableClientRetryConfigurationShouldLoadContext() { + applicationContextRunner.withPropertyValues("spring.cloud.loadbalancer.clients.retry.configuration.enabled=true") + .run(context -> { + assertThat(context).hasSingleBean(LoadBalancedRetryFactory.class); + assertThat(context.getBean(LoadBalancedRetryFactory.class)).isInstanceOf(ClientBlockingLoadBalancedRetryFactory.class); + }); + } + @Test public void worksWithoutSpringWeb() { applicationContextRunner.withClassLoader(new FilteredClassLoader(RestTemplate.class)).run(context -> {