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
23 changes: 23 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-commons.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ 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.

NOTE: Individual Loadbalancer clients may be configured individually with the same properties as above except the prefix is `spring.cloud.loadbalancer.clients.<clientId>.*` where `clientId` is the name of the loadbalancer.

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`.

====
Expand Down Expand Up @@ -1246,6 +1248,27 @@ NOTE: The meters are registered in the registry when at least one record is adde

TIP: You can further configure the behavior of those metrics (for example, add https://micrometer.io/docs/concepts#_histograms_and_percentiles[publishing percentiles and histograms]) by https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-per-meter-properties[adding `MeterFilters`].

=== Configuring Individual LoadBalancerClients

Individual Loadbalancer clients may be configured individually with a different prefix `spring.cloud.loadbalancer.clients.<clientId>.*` where `clientId` is the name of the loadbalancer. Default configuration values may be set in the `spring.cloud.loadbalancer.*` namespace and will be merged with the client specific values taking precedence

.application.yml
====
----
spring:
cloud:
loadbalancer:
health-check:
initial-delay: 1s
clients:
myclient:
health-check:
interval: 30s
----
====

The above example will result in a merged health-check `@ConfigurationProperties` object with `initial-delay=1s` and `interval=30s`.

== Spring Cloud Circuit Breaker

include::spring-cloud-circuitbreaker.adoc[leveloffset=+1]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
public class LoadBalancerAutoConfiguration {

@LoadBalanced
Expand Down Expand Up @@ -147,11 +147,10 @@ public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory,
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory,
loadBalancedRetryFactory, loadBalancerFactory);
return new RetryLoadBalancerInterceptor(loadBalancerClient, requestFactory, loadBalancedRetryFactory,
loadBalancerFactory);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2013-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 org.springframework.boot.context.properties.ConfigurationProperties;

/**
* A {@link ConfigurationProperties} bean for Spring Cloud Loadbalancer.
*
* Individual clients are configured via the {@link LoadBalancerClientsProperties#clients}
* field. Defaults and other properties are located in the {@link LoadBalancerProperties}
* base class.
*
* @author Spencer Gibb
* @since 3.1.0
*/
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerClientsProperties extends LoadBalancerProperties {

private Map<String, LoadBalancerProperties> clients = new HashMap<>();

public Map<String, LoadBalancerProperties> getClients() {
return this.clients;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2012-2020 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 org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.cloud.commons.config.DefaultsBindHandlerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Spencer Gibb
* @since 3.1.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(name = "org.springframework.cloud.commons.config.CommonsConfigAutoConfiguration")
public class LoadBalancerDefaultMappingsProviderAutoConfiguration {

@Bean
public DefaultsBindHandlerAdvisor.MappingsProvider loadBalancerClientsDefaultsMappingsProvider() {
return () -> {
Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings = new HashMap<>();
mappings.put(ConfigurationPropertyName.of("spring.cloud.loadbalancer.clients"),
ConfigurationPropertyName.of("spring.cloud.loadbalancer"));
return mappings;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks like that's what we were missing in the previous considerations for this issue.

};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
import org.springframework.util.LinkedCaseInsensitiveMap;

/**
* A {@link ConfigurationProperties} bean for Spring Cloud LoadBalancer.
* The base configuration bean for Spring Cloud LoadBalancer.
*
* See {@link LoadBalancerClientsProperties} for the {@link ConfigurationProperties}
* annotation.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.1
*/
@ConfigurationProperties("spring.cloud.loadbalancer")
public class LoadBalancerProperties {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
Expand Down Expand Up @@ -50,19 +51,26 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto

private final LoadBalancerClient loadBalancer;

private final LoadBalancerProperties properties;

private final LoadBalancerRequestFactory requestFactory;

private final LoadBalancedRetryFactory lbRetryFactory;

private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;

@Deprecated
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties,
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
this.loadBalancer = loadBalancer;
this.properties = properties;
this.requestFactory = requestFactory;
this.lbRetryFactory = lbRetryFactory;
this.loadBalancerFactory = loadBalancerFactory;
}

public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory lbRetryFactory,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.lbRetryFactory = lbRetryFactory;
this.loadBalancerFactory = loadBalancerFactory;
Expand Down Expand Up @@ -159,14 +167,17 @@ private RetryTemplate createRetryTemplate(String serviceName, HttpRequest reques
if (retryListeners != null && retryListeners.length != 0) {
template.setListeners(retryListeners);
}
template.setRetryPolicy(!properties.getRetry().isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName));
template.setRetryPolicy(
!loadBalancerFactory.getProperties(serviceName).getRetry().isEnabled() || retryPolicy == null
? new NeverRetryPolicy()
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName));
return template;
}

private String getHint(String serviceId) {
String defaultHint = properties.getHint().getOrDefault("default", "default");
String hintPropertyValue = properties.getHint().get(serviceId);
Map<String, String> hint = loadBalancerFactory.getProperties(serviceId).getHint();
String defaultHint = hint.getOrDefault("default", "default");
String hintPropertyValue = hint.get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}

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

package org.springframework.cloud.client.loadbalancer.reactive;

import org.springframework.cglib.core.internal.Function;
import org.springframework.http.HttpMethod;

/**
Expand Down Expand Up @@ -55,4 +56,8 @@ public interface LoadBalancerRetryPolicy {
*/
boolean canRetryOnMethod(HttpMethod method);

interface Factory extends Function<String, LoadBalancerRetryPolicy> {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;

Expand Down Expand Up @@ -53,6 +54,10 @@ default Publisher<Response<T>> choose() { // conflicting name

interface Factory<T> {

default LoadBalancerProperties getProperties(String serviceId) {
return null;
}

ReactiveLoadBalancer<T> getInstance(String serviceId);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
Expand All @@ -42,35 +43,37 @@
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
public class ReactorLoadBalancerClientAutoConfiguration {

@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false",
matchIfMissing = true)
@Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
ObjectProvider<List<LoadBalancerClientRequestTransformer>> transformers) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties,
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory,
transformers.getIfAvailable(Collections::emptyList));
}

@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
@Bean
public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
LoadBalancerRetryPolicy retryPolicy,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
LoadBalancerRetryPolicy.Factory retryPolicyFactory,
ObjectProvider<List<LoadBalancerClientRequestTransformer>> transformers) {
return new RetryableLoadBalancerExchangeFilterFunction(retryPolicy, loadBalancerFactory, properties,
return new RetryableLoadBalancerExchangeFilterFunction(retryPolicyFactory, loadBalancerFactory,
transformers.getIfAvailable(Collections::emptyList));
}

@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
@Bean
public LoadBalancerRetryPolicy loadBalancerRetryPolicy(LoadBalancerProperties properties) {
return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties);
public LoadBalancerRetryPolicy.Factory loadBalancerRetryPolicy(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.Factory(loadBalancerFactory);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx

private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;

private final LoadBalancerProperties properties;

private final List<LoadBalancerClientRequestTransformer> transformers;

/**
Expand All @@ -77,10 +75,16 @@ public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory<Se
this(loadBalancerFactory, properties, Collections.emptyList());
}

@Deprecated
public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
LoadBalancerProperties properties, List<LoadBalancerClientRequestTransformer> transformers) {
this.loadBalancerFactory = loadBalancerFactory;
this.properties = properties;
this.transformers = transformers;
}

public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
List<LoadBalancerClientRequestTransformer> transformers) {
this.loadBalancerFactory = loadBalancerFactory;
this.transformers = transformers;
}

Expand All @@ -99,7 +103,7 @@ public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction
.getSupportedLifecycleProcessors(
loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
String hint = getHint(serviceId, properties.getHint());
String hint = getHint(serviceId, loadBalancerFactory.getProperties(serviceId).getHint());
RequestData requestData = new RequestData(clientRequest);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData, hint));
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
Expand All @@ -120,7 +124,8 @@ public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction
LOG.debug(String.format("LoadBalancer has retrieved the instance for service %s: %s", serviceId,
instance.getUri()));
}
LoadBalancerProperties.StickySession stickySessionProperties = properties.getStickySession();
LoadBalancerProperties.StickySession stickySessionProperties = loadBalancerFactory.getProperties(serviceId)
.getStickySession();
ClientRequest newRequest = buildClientRequest(clientRequest, instance,
stickySessionProperties.getInstanceIdCookieName(),
stickySessionProperties.isAddServiceInstanceCookie(), transformers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.cloud.client.loadbalancer.reactive;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.http.HttpMethod;

Expand Down Expand Up @@ -53,4 +54,20 @@ public boolean canRetryOnMethod(HttpMethod method) {
return HttpMethod.GET.equals(method) || properties.getRetry().isRetryOnAllOperations();
}

static class Factory implements LoadBalancerRetryPolicy.Factory {

final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;

Factory(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
this.loadBalancerFactory = loadBalancerFactory;
}

@Override
public LoadBalancerRetryPolicy apply(String serviceId) {
return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(
loadBalancerFactory.getProperties(serviceId));
}

}

}
Loading