Browse Source

Merge remote-tracking branch 'origin/3.1.x' into 4.0.x

# Conflicts:
#	docs/src/main/asciidoc/spring-cloud-commons.adoc
#	spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java
#	spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java
#	spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java
pull/1278/head
Olga MaciaszekSharma 1 year ago
parent
commit
cc1aaa8883
  1. 1
      docs/src/main/asciidoc/_configprops.adoc
  2. 27
      docs/src/main/asciidoc/spring-cloud-commons.adoc
  3. 43
      spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java
  4. 22
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java
  5. 4
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java
  6. 19
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java
  7. 32
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java
  8. 21
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java
  9. 8
      spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java
  10. 32
      spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java
  11. 6
      spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java
  12. 33
      spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplierTests.java

1
docs/src/main/asciidoc/_configprops.adoc

@ -37,6 +37,7 @@
|spring.cloud.loadbalancer.cache.capacity | `+++256+++` | Initial cache capacity expressed as int. |spring.cloud.loadbalancer.cache.capacity | `+++256+++` | Initial cache capacity expressed as int.
|spring.cloud.loadbalancer.cache.enabled | `+++true+++` | Enables Spring Cloud LoadBalancer caching mechanism. |spring.cloud.loadbalancer.cache.enabled | `+++true+++` | Enables Spring Cloud LoadBalancer caching mechanism.
|spring.cloud.loadbalancer.cache.ttl | `+++35s+++` | Time To Live - time counted from writing of the record, after which cache entries are expired, expressed as a {@link Duration}. The property {@link String} has to be in keeping with the appropriate syntax as specified in Spring Boot <code>StringToDurationConverter</code>. @see <a href= "https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java">StringToDurationConverter.java</a> |spring.cloud.loadbalancer.cache.ttl | `+++35s+++` | Time To Live - time counted from writing of the record, after which cache entries are expired, expressed as a {@link Duration}. The property {@link String} has to be in keeping with the appropriate syntax as specified in Spring Boot <code>StringToDurationConverter</code>. @see <a href= "https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java">StringToDurationConverter.java</a>
|spring.cloud.loadbalancer.call-get-with-request-on-delegates | `+++false+++` | If this flag is set to {@code true}, {@code ServiceInstanceListSupplier#get(Request request)} method will be implemented to call {@code delegate.get(request)} in classes assignable from {@code DelegatingServiceInstanceListSupplier} that don't already implement that method, with the exclusion of {@code CachingServiceInstanceListSupplier} and {@code HealthCheckServiceInstanceListSupplier}, which should be placed in the instance supplier hierarchy directly after the supplier performing instance retrieval over the network, before any request-based filtering is done. Note: in 4.1, this behaviour will become the default
|spring.cloud.loadbalancer.clients | | |spring.cloud.loadbalancer.clients | |
|spring.cloud.loadbalancer.configurations | `+++default+++` | Enables a predefined LoadBalancer configuration. |spring.cloud.loadbalancer.configurations | `+++default+++` | Enables a predefined LoadBalancer configuration.
|spring.cloud.loadbalancer.eager-load.clients | | Names of the clients. |spring.cloud.loadbalancer.eager-load.clients | | Names of the clients.

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

@ -947,6 +947,12 @@ to `false`.
WARNING: Although the basic, non-cached, implementation is useful for prototyping and testing, it's much less efficient than the cached versions, so we recommend always using the cached version in production. If the caching is already done by the `DiscoveryClient` implementation, for example `EurekaDiscoveryClient`, the load-balancer caching should be disabled to prevent double caching. WARNING: Although the basic, non-cached, implementation is useful for prototyping and testing, it's much less efficient than the cached versions, so we recommend always using the cached version in production. If the caching is already done by the `DiscoveryClient` implementation, for example `EurekaDiscoveryClient`, the load-balancer caching should be disabled to prevent double caching.
====
NOTE: When you create your own configuration, if you use `CachingServiceInstanceListSupplier` make sure to place it in the hierarchy directly after the supplier that retrieves the instances over the network, for example, `DiscoveryClientServiceInstanceListSupplier`, before any other filtering suppliers.
====
=== Weighted Load-Balancing === Weighted Load-Balancing
To enable weighted load-balancing, we provide the `WeightedServiceInstanceListSupplier`. We use `WeightFunction` to calculate the weight of each instance. To enable weighted load-balancing, we provide the `WeightedServiceInstanceListSupplier`. We use `WeightFunction` to calculate the weight of each instance.
@ -1011,7 +1017,7 @@ If the zone is `null` or there are no instances within the same zone, it returns
In order to use the zone-based load-balancing approach, you will have to instantiate a `ZonePreferenceServiceInstanceListSupplier` bean in a <<custom-loadbalancer-configuration,custom configuration>>. In order to use the zone-based load-balancing approach, you will have to instantiate a `ZonePreferenceServiceInstanceListSupplier` bean in a <<custom-loadbalancer-configuration,custom configuration>>.
We use delegates to work with `ServiceInstanceListSupplier` beans. We use delegates to work with `ServiceInstanceListSupplier` beans.
We suggest passing a `DiscoveryClientServiceInstanceListSupplier` delegate in the constructor of `ZonePreferenceServiceInstanceListSupplier` and, in turn, wrapping the latter with a `CachingServiceInstanceListSupplier` to leverage <<loadbalancer-caching, LoadBalancer caching mechanism>>. We suggest using a `DiscoveryClientServiceInstanceListSupplier` delegate, wrapping it with a `CachingServiceInstanceListSupplier` to leverage <<loadbalancer-caching, LoadBalancer caching mechanism>>, and then passing the resulting bean in the constructor of `ZonePreferenceServiceInstanceListSupplier`.
You can use this sample configuration to set it up: You can use this sample configuration to set it up:
@ -1025,8 +1031,8 @@ public class CustomLoadBalancerConfiguration {
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder() return ServiceInstanceListSupplier.builder()
.withDiscoveryClient() .withDiscoveryClient()
.withCaching()
.withZonePreference() .withZonePreference()
.withCaching()
.build(context); .build(context);
} }
} }
@ -1089,6 +1095,12 @@ You can also pass your own `WebClient` or `RestTemplate` instance to be used for
WARNING: `HealthCheckServiceInstanceListSupplier` has its own caching mechanism based on Reactor Flux `replay()`. Therefore, if it's being used, you may want to skip wrapping that supplier with `CachingServiceInstanceListSupplier`. WARNING: `HealthCheckServiceInstanceListSupplier` has its own caching mechanism based on Reactor Flux `replay()`. Therefore, if it's being used, you may want to skip wrapping that supplier with `CachingServiceInstanceListSupplier`.
====
NOTE: When you create your own configuration, `HealthCheckServiceInstanceListSupplier`, make sure to place it in the hierarchy directly after the supplier that retrieves the instances over the network, for example, `DiscoveryClientServiceInstanceListSupplier`, before any other filtering suppliers.
====
=== Same instance preference for LoadBalancer === Same instance preference for LoadBalancer
You can set up the LoadBalancer in such a way that it prefers the instance that was previously selected, if that instance is available. You can set up the LoadBalancer in such a way that it prefers the instance that was previously selected, if that instance is available.
@ -1173,8 +1185,8 @@ public class CustomLoadBalancerConfiguration {
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder() return ServiceInstanceListSupplier.builder()
.withDiscoveryClient() .withDiscoveryClient()
.withCaching()
.withHints() .withHints()
.withCaching()
.build(context); .build(context);
} }
} }
@ -1284,11 +1296,18 @@ public class MyConfiguration {
} }
} }
---- ----
====
NOTE: The classes you pass as `@LoadBalancerClient` or `@LoadBalancerClients` configuration arguments should either not be annotated with `@Configuration` or be outside component scan scope. NOTE: The classes you pass as `@LoadBalancerClient` or `@LoadBalancerClients` configuration arguments should either not be annotated with `@Configuration` or be outside component scan scope.
==== ====
====
NOTE: When you create your own configuration, if you use `CachingServiceInstanceListSupplier` or `HealthCheckServiceInstanceListSupplier`, makes sure to use one of them, not both, and make sure to place it in the hierarchy directly after the supplier that retrieves the instances over the network, for example, `DiscoveryClientServiceInstanceListSupplier`, before any other filtering suppliers.
====
[[loadbalancer-lifecycle]] [[loadbalancer-lifecycle]]
=== Spring Cloud LoadBalancer Lifecycle === Spring Cloud LoadBalancer Lifecycle
@ -1356,6 +1375,8 @@ The per-client configuration properties work for most of the properties, apart f
NOTE: For the properties where maps where already used, where you can specify a different value per-client without using the `clients` keyword (for example, `hints`, `health-check.path`), we have kept that behaviour in order to keep the library backwards compatible. It will be modified in the next major release. NOTE: For the properties where maps where already used, where you can specify a different value per-client without using the `clients` keyword (for example, `hints`, `health-check.path`), we have kept that behaviour in order to keep the library backwards compatible. It will be modified in the next major release.
NOTE: Starting with `4.0.4`, we have introduced the `callGetWithRequestOnDelegates` flag in `LoadBalancerProperties`. If this flag is set to `true`, `ServiceInstanceListSupplier#get(Request request)` method will be implemented to call `delegate.get(request)` in classes assignable from `DelegatingServiceInstanceListSupplier` that don't already implement that method, with the exclusion of `CachingServiceInstanceListSupplier` and `HealthCheckServiceInstanceListSupplier`, which should be placed in the instance supplier hierarchy directly after the supplier performing instance retrieval over the network, before any request-based filtering is done. For `4.0.x` the flag is set to `false` by default, however, since `4.1.0` it's going to be set to `true` by default.
=== AOT and Native Image Support === AOT and Native Image Support
Since `4.0.0`, Spring Cloud LoadBalancer supports Spring AOT transformations and native images. However, to use this feature, you need to explicitly define your `LoadBalancerClient` service IDs. You can do so by using the `value` or `name` attributes of the `@LoadBalancerClient` annotation or as values of the `spring.cloud.loadbalancer.eager-load.clients` property. Since `4.0.0`, Spring Cloud LoadBalancer supports Spring AOT transformations and native images. However, to use this feature, you need to explicitly define your `LoadBalancerClient` service IDs. You can do so by using the `value` or `name` attributes of the `@LoadBalancerClient` annotation or as values of the `spring.cloud.loadbalancer.eager-load.clients` property.

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

@ -72,6 +72,19 @@ public class LoadBalancerProperties {
*/ */
private StickySession stickySession = new StickySession(); private StickySession stickySession = new StickySession();
/**
* If this flag is set to {@code true},
* {@code ServiceInstanceListSupplier#get(Request request)} method will be implemented
* to call {@code delegate.get(request)} in classes assignable from
* {@code DelegatingServiceInstanceListSupplier} that don't already implement that
* method, with the exclusion of {@code CachingServiceInstanceListSupplier} and
* {@code HealthCheckServiceInstanceListSupplier}, which should be placed in the
* instance supplier hierarchy directly after the supplier performing instance
* retrieval over the network, before any request-based filtering is done. Note: in
* 4.1, this behaviour will become the default
*/
private boolean callGetWithRequestOnDelegates;
public HealthCheck getHealthCheck() { public HealthCheck getHealthCheck() {
return healthCheck; return healthCheck;
} }
@ -125,6 +138,36 @@ public class LoadBalancerProperties {
return xForwarded; return xForwarded;
} }
/**
* If this flag is set to {@code true},
* {@code ServiceInstanceListSupplier#get(Request request)} method will be implemented
* to call {@code delegate.get(request)} in classes assignable from
* {@code DelegatingServiceInstanceListSupplier} that don't already implement that
* method, with the exclusion of {@code CachingServiceInstanceListSupplier} and
* {@code HealthCheckServiceInstanceListSupplier}, which should be placed in the
* instance supplier hierarchy directly after the supplier performing instance
* retrieval over the network, before any request-based filtering is done. Note: in
* 4.1, this behaviour will become the default
*/
public boolean isCallGetWithRequestOnDelegates() {
return callGetWithRequestOnDelegates;
}
/**
* If this flag is set to {@code true},
* {@code ServiceInstanceListSupplier#get(Request request)} method will be implemented
* to call {@code delegate.get(request)} in classes assignable from
* {@code DelegatingServiceInstanceListSupplier} that don't already implement that
* method, with the exclusion of {@code CachingServiceInstanceListSupplier} and
* {@code HealthCheckServiceInstanceListSupplier}, which should be placed in the
* instance supplier hierarchy directly after the supplier performing instance
* retrieval over the network, before any request-based filtering is done. Note: in
* 4.1, this behaviour will become the default
*/
public void setCallGetWithRequestOnDelegates(boolean callGetWithRequestOnDelegates) {
this.callGetWithRequestOnDelegates = callGetWithRequestOnDelegates;
}
public static class StickySession { public static class StickySession {
/** /**

22
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java

@ -94,7 +94,7 @@ public class LoadBalancerClientConfiguration {
@Conditional(ZonePreferenceConfigurationCondition.class) @Conditional(ZonePreferenceConfigurationCondition.class)
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withDiscoveryClient().withZonePreference().withCaching() return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching().withZonePreference()
.build(context); .build(context);
} }
@ -120,8 +120,8 @@ public class LoadBalancerClientConfiguration {
@Conditional(RequestBasedStickySessionConfigurationCondition.class) @Conditional(RequestBasedStickySessionConfigurationCondition.class)
public ServiceInstanceListSupplier requestBasedStickySessionDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier requestBasedStickySessionDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withDiscoveryClient().withRequestBasedStickySession() return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching()
.withCaching().build(context); .withRequestBasedStickySession().build(context);
} }
@Bean @Bean
@ -130,8 +130,8 @@ public class LoadBalancerClientConfiguration {
@Conditional(SameInstancePreferenceConfigurationCondition.class) @Conditional(SameInstancePreferenceConfigurationCondition.class)
public ServiceInstanceListSupplier sameInstancePreferenceServiceInstanceListSupplier( public ServiceInstanceListSupplier sameInstancePreferenceServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withDiscoveryClient().withSameInstancePreference() return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching()
.withCaching().build(context); .withSameInstancePreference().build(context);
} }
@Bean @Bean
@ -165,8 +165,8 @@ public class LoadBalancerClientConfiguration {
@Conditional(ZonePreferenceConfigurationCondition.class) @Conditional(ZonePreferenceConfigurationCondition.class)
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withZonePreference() return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching()
.withCaching().build(context); .withZonePreference().build(context);
} }
@Bean @Bean
@ -185,8 +185,8 @@ public class LoadBalancerClientConfiguration {
@Conditional(RequestBasedStickySessionConfigurationCondition.class) @Conditional(RequestBasedStickySessionConfigurationCondition.class)
public ServiceInstanceListSupplier requestBasedStickySessionDiscoveryClientServiceInstanceListSupplier( public ServiceInstanceListSupplier requestBasedStickySessionDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withRequestBasedStickySession() return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching()
.withCaching().build(context); .withRequestBasedStickySession().build(context);
} }
@Bean @Bean
@ -195,8 +195,8 @@ public class LoadBalancerClientConfiguration {
@Conditional(SameInstancePreferenceConfigurationCondition.class) @Conditional(SameInstancePreferenceConfigurationCondition.class)
public ServiceInstanceListSupplier sameInstancePreferenceServiceInstanceListSupplier( public ServiceInstanceListSupplier sameInstancePreferenceServiceInstanceListSupplier(
ConfigurableApplicationContext context) { ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withSameInstancePreference() return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching()
.withCaching().build(context); .withSameInstancePreference().build(context);
} }
@Bean @Bean

4
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/DelegatingServiceInstanceListSupplier.java

@ -40,12 +40,12 @@ public abstract class DelegatingServiceInstanceListSupplier
} }
public ServiceInstanceListSupplier getDelegate() { public ServiceInstanceListSupplier getDelegate() {
return this.delegate; return delegate;
} }
@Override @Override
public String getServiceId() { public String getServiceId() {
return this.delegate.getServiceId(); return delegate.getServiceId();
} }
@Override @Override

19
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplier.java

@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
/** /**
* An implementation of {@link ServiceInstanceListSupplier} that selects the previously * An implementation of {@link ServiceInstanceListSupplier} that selects the previously
@ -40,10 +42,19 @@ public class SameInstancePreferenceServiceInstanceListSupplier extends Delegatin
private ServiceInstance previouslyReturnedInstance; private ServiceInstance previouslyReturnedInstance;
private boolean callGetWithRequestOnDelegates;
public SameInstancePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) { public SameInstancePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
super(delegate); super(delegate);
} }
public SameInstancePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
super(delegate);
callGetWithRequestOnDelegates = loadBalancerClientFactory.getProperties(getServiceId())
.isCallGetWithRequestOnDelegates();
}
@Override @Override
public String getServiceId() { public String getServiceId() {
return delegate.getServiceId(); return delegate.getServiceId();
@ -54,6 +65,14 @@ public class SameInstancePreferenceServiceInstanceListSupplier extends Delegatin
return delegate.get().map(this::filteredBySameInstancePreference); return delegate.get().map(this::filteredBySameInstancePreference);
} }
@Override
public Flux<List<ServiceInstance>> get(Request request) {
if (callGetWithRequestOnDelegates) {
return delegate.get(request).map(this::filteredBySameInstancePreference);
}
return get();
}
private List<ServiceInstance> filteredBySameInstancePreference(List<ServiceInstance> serviceInstances) { private List<ServiceInstance> filteredBySameInstancePreference(List<ServiceInstance> serviceInstances) {
if (previouslyReturnedInstance != null && serviceInstances.contains(previouslyReturnedInstance)) { if (previouslyReturnedInstance != null && serviceInstances.contains(previouslyReturnedInstance)) {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {

32
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java

@ -58,8 +58,6 @@ public final class ServiceInstanceListSupplierBuilder {
private Creator baseCreator; private Creator baseCreator;
private DelegateCreator cachingCreator;
private final List<DelegateCreator> creators = new ArrayList<>(); private final List<DelegateCreator> creators = new ArrayList<>();
ServiceInstanceListSupplierBuilder() { ServiceInstanceListSupplierBuilder() {
@ -174,8 +172,10 @@ public final class ServiceInstanceListSupplierBuilder {
* @return the {@link ServiceInstanceListSupplierBuilder} object * @return the {@link ServiceInstanceListSupplierBuilder} object
*/ */
public ServiceInstanceListSupplierBuilder withSameInstancePreference() { public ServiceInstanceListSupplierBuilder withSameInstancePreference() {
DelegateCreator creator = (context, DelegateCreator creator = (context, delegate) -> {
delegate) -> new SameInstancePreferenceServiceInstanceListSupplier(delegate); LoadBalancerClientFactory loadBalancerClientFactory = context.getBean(LoadBalancerClientFactory.class);
return new SameInstancePreferenceServiceInstanceListSupplier(delegate, loadBalancerClientFactory);
};
this.creators.add(creator); this.creators.add(creator);
return this; return this;
} }
@ -217,8 +217,9 @@ public final class ServiceInstanceListSupplierBuilder {
*/ */
public ServiceInstanceListSupplierBuilder withZonePreference() { public ServiceInstanceListSupplierBuilder withZonePreference() {
DelegateCreator creator = (context, delegate) -> { DelegateCreator creator = (context, delegate) -> {
LoadBalancerClientFactory loadBalancerClientFactory = context.getBean(LoadBalancerClientFactory.class);
LoadBalancerZoneConfig zoneConfig = context.getBean(LoadBalancerZoneConfig.class); LoadBalancerZoneConfig zoneConfig = context.getBean(LoadBalancerZoneConfig.class);
return new ZonePreferenceServiceInstanceListSupplier(delegate, zoneConfig); return new ZonePreferenceServiceInstanceListSupplier(delegate, zoneConfig, loadBalancerClientFactory);
}; };
this.creators.add(creator); this.creators.add(creator);
return this; return this;
@ -232,8 +233,9 @@ public final class ServiceInstanceListSupplierBuilder {
*/ */
public ServiceInstanceListSupplierBuilder withZonePreference(String zoneName) { public ServiceInstanceListSupplierBuilder withZonePreference(String zoneName) {
DelegateCreator creator = (context, delegate) -> { DelegateCreator creator = (context, delegate) -> {
LoadBalancerClientFactory loadBalancerClientFactory = context.getBean(LoadBalancerClientFactory.class);
LoadBalancerZoneConfig zoneConfig = new LoadBalancerZoneConfig(zoneName); LoadBalancerZoneConfig zoneConfig = new LoadBalancerZoneConfig(zoneName);
return new ZonePreferenceServiceInstanceListSupplier(delegate, zoneConfig); return new ZonePreferenceServiceInstanceListSupplier(delegate, zoneConfig, loadBalancerClientFactory);
}; };
this.creators.add(creator); this.creators.add(creator);
return this; return this;
@ -254,19 +256,15 @@ public final class ServiceInstanceListSupplierBuilder {
} }
/** /**
* If {@link LoadBalancerCacheManager} is available in the context, wraps created * If {@link LoadBalancerCacheManager} is available in the context, adds a
* {@link ServiceInstanceListSupplier} hierarchy with a * {@link CachingServiceInstanceListSupplier} instance to the
* {@link CachingServiceInstanceListSupplier} instance to provide a caching mechanism * {@link ServiceInstanceListSupplier} hierarchy to provide a caching mechanism for
* for service instances. Uses {@link ObjectProvider} to lazily resolve * service instances. Uses {@link ObjectProvider} to lazily resolve
* {@link LoadBalancerCacheManager}. * {@link LoadBalancerCacheManager}.
* @return the {@link ServiceInstanceListSupplierBuilder} object * @return the {@link ServiceInstanceListSupplierBuilder} object
*/ */
public ServiceInstanceListSupplierBuilder withCaching() { public ServiceInstanceListSupplierBuilder withCaching() {
if (cachingCreator != null && LOG.isWarnEnabled()) { DelegateCreator creator = (context, delegate) -> {
LOG.warn(
"Overriding a previously set cachingCreator with a CachingServiceInstanceListSupplier-based cachingCreator.");
}
this.cachingCreator = (context, delegate) -> {
ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context
.getBeanProvider(LoadBalancerCacheManager.class); .getBeanProvider(LoadBalancerCacheManager.class);
if (cacheManagerProvider.getIfAvailable() != null) { if (cacheManagerProvider.getIfAvailable() != null) {
@ -277,6 +275,7 @@ public final class ServiceInstanceListSupplierBuilder {
} }
return delegate; return delegate;
}; };
creators.add(creator);
return this; return this;
} }
@ -323,9 +322,6 @@ public final class ServiceInstanceListSupplierBuilder {
supplier = creator.apply(context, supplier); supplier = creator.apply(context, supplier);
} }
if (this.cachingCreator != null) {
supplier = this.cachingCreator.apply(context, supplier);
}
return supplier; return supplier;
} }

21
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplier.java

@ -23,6 +23,8 @@ import java.util.Map;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig;
/** /**
@ -43,17 +45,36 @@ public class ZonePreferenceServiceInstanceListSupplier extends DelegatingService
private String zone; private String zone;
private boolean callGetWithRequestOnDelegates;
public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
LoadBalancerZoneConfig zoneConfig) { LoadBalancerZoneConfig zoneConfig) {
super(delegate); super(delegate);
this.zoneConfig = zoneConfig; this.zoneConfig = zoneConfig;
} }
public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
LoadBalancerZoneConfig zoneConfig,
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
super(delegate);
this.zoneConfig = zoneConfig;
callGetWithRequestOnDelegates = loadBalancerClientFactory.getProperties(getServiceId())
.isCallGetWithRequestOnDelegates();
}
@Override @Override
public Flux<List<ServiceInstance>> get() { public Flux<List<ServiceInstance>> get() {
return getDelegate().get().map(this::filteredByZone); return getDelegate().get().map(this::filteredByZone);
} }
@Override
public Flux<List<ServiceInstance>> get(Request request) {
if (callGetWithRequestOnDelegates) {
return getDelegate().get(request).map(this::filteredByZone);
}
return get();
}
private List<ServiceInstance> filteredByZone(List<ServiceInstance> serviceInstances) { private List<ServiceInstance> filteredByZone(List<ServiceInstance> serviceInstances) {
if (zone == null) { if (zone == null) {
zone = zoneConfig.getZone(); zone = zoneConfig.getZone();

8
spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfigurationTests.java

@ -93,10 +93,10 @@ class LoadBalancerClientConfigurationTests {
reactiveDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=zone-preference") reactiveDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.configurations=zone-preference")
.run(context -> { .run(context -> {
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); then(supplier).isInstanceOf(ZonePreferenceServiceInstanceListSupplier.class);
ServiceInstanceListSupplier delegate = ((DelegatingServiceInstanceListSupplier) supplier) ServiceInstanceListSupplier delegate = ((DelegatingServiceInstanceListSupplier) supplier)
.getDelegate(); .getDelegate();
then(delegate).isInstanceOf(ZonePreferenceServiceInstanceListSupplier.class); then(delegate).isInstanceOf(CachingServiceInstanceListSupplier.class);
ServiceInstanceListSupplier secondDelegate = ((DelegatingServiceInstanceListSupplier) delegate) ServiceInstanceListSupplier secondDelegate = ((DelegatingServiceInstanceListSupplier) delegate)
.getDelegate(); .getDelegate();
then(secondDelegate).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); then(secondDelegate).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class);
@ -136,10 +136,10 @@ class LoadBalancerClientConfigurationTests {
.withPropertyValues("spring.cloud.loadbalancer.configurations=request-based-sticky-session") .withPropertyValues("spring.cloud.loadbalancer.configurations=request-based-sticky-session")
.run(context -> { .run(context -> {
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class); ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); then(supplier).isInstanceOf(RequestBasedStickySessionServiceInstanceListSupplier.class);
ServiceInstanceListSupplier delegate = ((DelegatingServiceInstanceListSupplier) supplier) ServiceInstanceListSupplier delegate = ((DelegatingServiceInstanceListSupplier) supplier)
.getDelegate(); .getDelegate();
then(delegate).isInstanceOf(RequestBasedStickySessionServiceInstanceListSupplier.class); then(delegate).isInstanceOf(CachingServiceInstanceListSupplier.class);
ServiceInstanceListSupplier secondDelegate = ((DelegatingServiceInstanceListSupplier) delegate) ServiceInstanceListSupplier secondDelegate = ((DelegatingServiceInstanceListSupplier) delegate)
.getDelegate(); .getDelegate();
then(secondDelegate).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); then(secondDelegate).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class);

32
spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SameInstancePreferenceServiceInstanceListSupplierTests.java

@ -19,11 +19,17 @@ package org.springframework.cloud.loadbalancer.core;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
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.loadbalancer.support.LoadBalancerClientFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -43,8 +49,9 @@ class SameInstancePreferenceServiceInstanceListSupplierTests {
private final DiscoveryClientServiceInstanceListSupplier delegate = mock( private final DiscoveryClientServiceInstanceListSupplier delegate = mock(
DiscoveryClientServiceInstanceListSupplier.class); DiscoveryClientServiceInstanceListSupplier.class);
private final SameInstancePreferenceServiceInstanceListSupplier supplier = new SameInstancePreferenceServiceInstanceListSupplier( private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
delegate);
private SameInstancePreferenceServiceInstanceListSupplier supplier;
private final ServiceInstance first = serviceInstance("test-1"); private final ServiceInstance first = serviceInstance("test-1");
@ -52,6 +59,14 @@ class SameInstancePreferenceServiceInstanceListSupplierTests {
private final ServiceInstance third = serviceInstance("test-3"); private final ServiceInstance third = serviceInstance("test-3");
@BeforeEach
void setUp() {
LoadBalancerProperties properties = new LoadBalancerProperties();
properties.setCallGetWithRequestOnDelegates(true);
when(loadBalancerClientFactory.getProperties(any())).thenReturn(properties);
supplier = new SameInstancePreferenceServiceInstanceListSupplier(delegate, loadBalancerClientFactory);
}
@Test @Test
void shouldReturnPreviouslySelectedInstanceIfAvailable() { void shouldReturnPreviouslySelectedInstanceIfAvailable() {
when(delegate.get()).thenReturn(Flux.just(Arrays.asList(first, second, third))); when(delegate.get()).thenReturn(Flux.just(Arrays.asList(first, second, third)));
@ -73,7 +88,7 @@ class SameInstancePreferenceServiceInstanceListSupplierTests {
} }
@Test @Test
void shouldReturnAllInstancesFromDelegateIfPreviouslySelectedInstanceIfAvailable() { void shouldReturnAllInstancesFromDelegateIfPreviouslySelectedInstanceIsNotAvailable() {
when(delegate.get()).thenReturn(Flux.just(Arrays.asList(second, third))); when(delegate.get()).thenReturn(Flux.just(Arrays.asList(second, third)));
supplier.selectedServiceInstance(first); supplier.selectedServiceInstance(first);
@ -82,6 +97,17 @@ class SameInstancePreferenceServiceInstanceListSupplierTests {
assertThat(instances).hasSize(2); assertThat(instances).hasSize(2);
} }
@Test
void shouldCallGetRequestOnDelegate() {
Request<DefaultRequestContext> request = new DefaultRequest<>(new DefaultRequestContext());
when(delegate.get()).thenReturn(Flux.just(Arrays.asList(first, second, third)));
when(delegate.get(request)).thenReturn(Flux.just(Arrays.asList(first, second)));
List<ServiceInstance> instances = supplier.get(request).blockFirst();
assertThat(instances).hasSize(2);
}
@Test @Test
void shouldCallSelectedServiceInstanceOnItsDelegate() { void shouldCallSelectedServiceInstanceOnItsDelegate() {
ServiceInstance firstInstance = serviceInstance("test-4"); ServiceInstance firstInstance = serviceInstance("test-4");

6
spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java

@ -37,11 +37,9 @@ public class ServiceInstanceListSupplierBuilderTests {
public void testBuilder() { public void testBuilder() {
new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class).run(context -> { new ApplicationContextRunner().withUserConfiguration(CacheTestConfig.class).run(context -> {
ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder().withDiscoveryClient() ServiceInstanceListSupplier supplier = ServiceInstanceListSupplier.builder().withDiscoveryClient()
.withHealthChecks().withWeighted().withCaching().build(context); .withHealthChecks().withWeighted().build(context);
assertThat(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class); assertThat(supplier).isInstanceOf(WeightedServiceInstanceListSupplier.class);
DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier; DelegatingServiceInstanceListSupplier delegating = (DelegatingServiceInstanceListSupplier) supplier;
assertThat(delegating.getDelegate()).isInstanceOf(WeightedServiceInstanceListSupplier.class);
delegating = (DelegatingServiceInstanceListSupplier) delegating.getDelegate();
assertThat(delegating.getDelegate()).isInstanceOf(HealthCheckServiceInstanceListSupplier.class); assertThat(delegating.getDelegate()).isInstanceOf(HealthCheckServiceInstanceListSupplier.class);
delegating = (DelegatingServiceInstanceListSupplier) delegating.getDelegate(); delegating = (DelegatingServiceInstanceListSupplier) delegating.getDelegate();
assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class); assertThat(delegating.getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class);

33
spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ZonePreferenceServiceInstanceListSupplierTests.java

@ -22,15 +22,22 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
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.loadbalancer.config.LoadBalancerZoneConfig; import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -46,8 +53,9 @@ class ZonePreferenceServiceInstanceListSupplierTests {
private final LoadBalancerZoneConfig zoneConfig = new LoadBalancerZoneConfig(null); private final LoadBalancerZoneConfig zoneConfig = new LoadBalancerZoneConfig(null);
private final ZonePreferenceServiceInstanceListSupplier supplier = new ZonePreferenceServiceInstanceListSupplier( private ZonePreferenceServiceInstanceListSupplier supplier;
delegate, zoneConfig);
private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
private final ServiceInstance first = serviceInstance("test-1", buildZoneMetadata("zone1")); private final ServiceInstance first = serviceInstance("test-1", buildZoneMetadata("zone1"));
@ -59,6 +67,14 @@ class ZonePreferenceServiceInstanceListSupplierTests {
private final ServiceInstance fifth = serviceInstance("test-5", buildZoneMetadata(null)); private final ServiceInstance fifth = serviceInstance("test-5", buildZoneMetadata(null));
@BeforeEach
void setUp() {
LoadBalancerProperties properties = new LoadBalancerProperties();
properties.setCallGetWithRequestOnDelegates(true);
when(loadBalancerClientFactory.getProperties(any())).thenReturn(properties);
supplier = new ZonePreferenceServiceInstanceListSupplier(delegate, zoneConfig, loadBalancerClientFactory);
}
@Test @Test
void shouldFilterInstancesByZone() { void shouldFilterInstancesByZone() {
zoneConfig.setZone("zone1"); zoneConfig.setZone("zone1");
@ -73,6 +89,19 @@ class ZonePreferenceServiceInstanceListSupplierTests {
assertThat(filtered).doesNotContain(fifth); assertThat(filtered).doesNotContain(fifth);
} }
@Test
void shouldCallGetRequestOnDelegate() {
zoneConfig.setZone("zone1");
Request<DefaultRequestContext> request = new DefaultRequest<>(new DefaultRequestContext());
when(delegate.get()).thenReturn(Flux.just(Arrays.asList(first, second, third, fourth, fifth)));
when(delegate.get(request)).thenReturn(Flux.just(Arrays.asList(first, third, fourth, fifth)));
List<ServiceInstance> filtered = supplier.get(request).blockFirst();
assertThat(filtered).hasSize(1);
assertThat(filtered).containsOnly(first);
}
@Test @Test
void shouldReturnAllInstancesIfNoZoneInstances() { void shouldReturnAllInstancesIfNoZoneInstances() {
zoneConfig.setZone("zone1"); zoneConfig.setZone("zone1");

Loading…
Cancel
Save