From 9472b6fa0c70016ef6da3799605f0c2bd7bb6c5a Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Tue, 10 Oct 2023 19:26:47 +0800 Subject: [PATCH 01/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../SubsetServiceInstanceListSupplier.java | 80 +++++++++++++ ...SubsetServiceInstanceListSupplierTest.java | 111 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java create mode 100644 spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java new file mode 100644 index 000000000..38cb56a70 --- /dev/null +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2023 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.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.UUID; + +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * A {@link ServiceInstanceListSupplier} implementation that uses + * deterministic + * subsetting to limit the number of instances provided by delegate. + * + * @author Zhuozhi Ji + */ +public class SubsetServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { + + static final int DEFAULT_SUBSET_SIZE = 1000; + + final String instanceId; + + final int subsetSize; + + public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) { + this(delegate, DEFAULT_SUBSET_SIZE); + } + + public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, int subsetSize) { + this(delegate, UUID.randomUUID().toString(), subsetSize); + } + + public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, String instanceId, int subsetSize) { + super(delegate); + this.instanceId = instanceId; + this.subsetSize = subsetSize; + } + + @Override + public Flux> get() { + return delegate.get().map(instances -> { + if (instances.isEmpty()) { + return instances; + } + + instances = new ArrayList<>(instances); + + int clientId = instanceId.hashCode() & Integer.MAX_VALUE; + int subsetCount = instances.size() / subsetSize; + int round = clientId / subsetCount; + + Random random = new Random(round); + Collections.shuffle(instances, random); + + int subsetId = clientId % subsetCount; + int start = subsetId * subsetSize; + return instances.subList(start, start + subsetSize); + }); + } + +} diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java new file mode 100644 index 000000000..b5dbf077d --- /dev/null +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2023 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.core; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SubsetServiceInstanceListSupplierTest { + + private final DiscoveryClientServiceInstanceListSupplier delegate = mock( + DiscoveryClientServiceInstanceListSupplier.class); + + @Test + void shouldConstructCorrect() { + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate); + assertThat(supplier.instanceId).isNotNull(); + assertThat(supplier.subsetSize).isEqualTo(SubsetServiceInstanceListSupplier.DEFAULT_SUBSET_SIZE); + + supplier = new SubsetServiceInstanceListSupplier(delegate, 11); + assertThat(supplier.instanceId).isNotNull(); + assertThat(supplier.subsetSize).isEqualTo(11); + + supplier = new SubsetServiceInstanceListSupplier(delegate, "foobar", 11); + assertThat(supplier.instanceId).isEqualTo("foobar"); + assertThat(supplier.subsetSize).isEqualTo(11); + } + + @Test + void shouldReturnEmptyWhenDelegateReturnedEmpty() { + when(delegate.get()).thenReturn(Flux.just(Collections.emptyList())); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate); + + List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); + assertThat(serviceInstances).isEmpty(); + } + + @Test + void shouldReturnSublistWithGivenSubsetSize() { + List instances = IntStream.range(0, 101) + .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) + .collect(Collectors.toList()); + + when(delegate.get()).thenReturn(Flux.just(instances)); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, 5); + + List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); + assertThat(serviceInstances).hasSize(5); + } + + @Test + void shouldReturnSameSublistForSameInstanceId() { + List instances = IntStream.range(0, 101) + .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) + .collect(Collectors.toList()); + + when(delegate.get()).thenReturn(Flux.just(instances)); + + SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, "foobar", 5); + List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); + + SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, "foobar", 5); + List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); + + assertThat(serviceInstances1).isEqualTo(serviceInstances2); + } + + @Test + void shouldReturnDifferentSublistForDifferentInstanceId() { + List instances = IntStream.range(0, 101) + .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) + .collect(Collectors.toList()); + + when(delegate.get()).thenReturn(Flux.just(instances)); + + SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, "foobar1", 5); + List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); + + SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, "foobar2", 5); + List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); + + assertThat(serviceInstances1).isNotEqualTo(serviceInstances2); + } + +} From 22e212a8934d913c20f6e567eeea3cdf8646251c Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Tue, 10 Oct 2023 19:37:57 +0800 Subject: [PATCH 02/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../loadbalancer/core/SubsetServiceInstanceListSupplier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index 38cb56a70..83637686e 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -35,7 +35,7 @@ */ public class SubsetServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { - static final int DEFAULT_SUBSET_SIZE = 1000; + static final int DEFAULT_SUBSET_SIZE = 100; final String instanceId; From 48bbf986a5353ed67b1f12cfe457ed7199af8ef0 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Sun, 29 Oct 2023 01:47:43 +0800 Subject: [PATCH 03/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../core/SubsetServiceInstanceListSupplier.java | 2 +- .../core/SubsetServiceInstanceListSupplierTest.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index 83637686e..aefcc3054 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -58,7 +58,7 @@ public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, S @Override public Flux> get() { return delegate.get().map(instances -> { - if (instances.isEmpty()) { + if (instances.size() <= subsetSize) { return instances; } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index b5dbf077d..f6cfcea1f 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -74,6 +74,19 @@ void shouldReturnSublistWithGivenSubsetSize() { assertThat(serviceInstances).hasSize(5); } + @Test + void shouldReturnRawWhenLessThanSubsetSize() { + List instances = IntStream.range(0, 101) + .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) + .collect(Collectors.toList()); + + when(delegate.get()).thenReturn(Flux.just(instances)); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, 1000); + + List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); + assertThat(serviceInstances).hasSize(101); + } + @Test void shouldReturnSameSublistForSameInstanceId() { List instances = IntStream.range(0, 101) From 6ff2aa72a67d2006b69fe6e1cbcd7d24b29cc9dd Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Wed, 1 Nov 2023 00:04:20 +0800 Subject: [PATCH 04/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../loadbalancer/LoadBalancerProperties.java | 44 +++++++++++++ .../SubsetServiceInstanceListSupplier.java | 41 +++++------- ...SubsetServiceInstanceListSupplierTest.java | 62 +++++++++++++------ 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index dd93f89af..ad3bca53b 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -29,6 +29,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.commons.util.IdUtils; import org.springframework.http.HttpMethod; import org.springframework.util.LinkedCaseInsensitiveMap; @@ -85,6 +86,11 @@ public class LoadBalancerProperties { */ private boolean callGetWithRequestOnDelegates = true; + /** + * Properties for SubsetServiceInstanceListSupplier. + */ + private Subset subset = new Subset(); + public HealthCheck getHealthCheck() { return healthCheck; } @@ -142,6 +148,14 @@ public boolean isCallGetWithRequestOnDelegates() { return callGetWithRequestOnDelegates; } + public Subset getSubset() { + return subset; + } + + public void setSubset(Subset subset) { + this.subset = subset; + } + public void setCallGetWithRequestOnDelegates(boolean callGetWithRequestOnDelegates) { this.callGetWithRequestOnDelegates = callGetWithRequestOnDelegates; } @@ -490,4 +504,34 @@ public void setEnabled(boolean enabled) { } + public static class Subset { + + /** + * Instance id of deterministic subsetting. + */ + private String instanceId = IdUtils.DEFAULT_SERVICE_ID_STRING; + + /** + * Max subset size of deterministic subsetting. + */ + private int size = 100; + + public String getInstanceId() { + return instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + } + } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index aefcc3054..e46ad4a18 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -20,60 +20,53 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.UUID; import reactor.core.publisher.Flux; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.core.env.PropertyResolver; /** * A {@link ServiceInstanceListSupplier} implementation that uses * deterministic - * subsetting to limit the number of instances provided by delegate. + * subsetting algorithm to limit the number of instances provided by delegate. * * @author Zhuozhi Ji + * @since 4.1.0 */ public class SubsetServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { - static final int DEFAULT_SUBSET_SIZE = 100; + private final String instanceId; - final String instanceId; + private final int size; - final int subsetSize; - - public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) { - this(delegate, DEFAULT_SUBSET_SIZE); - } - - public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, int subsetSize) { - this(delegate, UUID.randomUUID().toString(), subsetSize); - } - - public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, String instanceId, int subsetSize) { + public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, PropertyResolver resolver, + LoadBalancerProperties properties) { super(delegate); - this.instanceId = instanceId; - this.subsetSize = subsetSize; + this.instanceId = resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); + this.size = properties.getSubset().getSize(); } @Override public Flux> get() { return delegate.get().map(instances -> { - if (instances.size() <= subsetSize) { + if (instances.size() <= size) { return instances; } instances = new ArrayList<>(instances); - int clientId = instanceId.hashCode() & Integer.MAX_VALUE; - int subsetCount = instances.size() / subsetSize; - int round = clientId / subsetCount; + int instanceId = this.instanceId.hashCode() & Integer.MAX_VALUE; + int count = instances.size() / size; + int round = instanceId / count; Random random = new Random(round); Collections.shuffle(instances, random); - int subsetId = clientId % subsetCount; - int start = subsetId * subsetSize; - return instances.subList(start, start + subsetSize); + int bucket = instanceId % count; + int start = bucket * size; + return instances.subList(start, start + size); }); } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index f6cfcea1f..8b1c8ffb8 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -22,40 +22,47 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +/** + * Tests for {@link SubsetServiceInstanceListSupplier} + * + * @author Zhuozhi Ji + */ class SubsetServiceInstanceListSupplierTest { private final DiscoveryClientServiceInstanceListSupplier delegate = mock( DiscoveryClientServiceInstanceListSupplier.class); - @Test - void shouldConstructCorrect() { - SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate); - assertThat(supplier.instanceId).isNotNull(); - assertThat(supplier.subsetSize).isEqualTo(SubsetServiceInstanceListSupplier.DEFAULT_SUBSET_SIZE); - - supplier = new SubsetServiceInstanceListSupplier(delegate, 11); - assertThat(supplier.instanceId).isNotNull(); - assertThat(supplier.subsetSize).isEqualTo(11); - - supplier = new SubsetServiceInstanceListSupplier(delegate, "foobar", 11); - assertThat(supplier.instanceId).isEqualTo("foobar"); - assertThat(supplier.subsetSize).isEqualTo(11); + private MockEnvironment env; + + @BeforeEach + public void setup() { + env = new MockEnvironment(); + } + + @AfterEach + public void destroy() { + env = null; } @Test void shouldReturnEmptyWhenDelegateReturnedEmpty() { when(delegate.get()).thenReturn(Flux.just(Collections.emptyList())); - SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar", 100)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).isEmpty(); @@ -68,7 +75,8 @@ void shouldReturnSublistWithGivenSubsetSize() { .collect(Collectors.toList()); when(delegate.get()).thenReturn(Flux.just(instances)); - SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, 5); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar", 5)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).hasSize(5); @@ -81,7 +89,8 @@ void shouldReturnRawWhenLessThanSubsetSize() { .collect(Collectors.toList()); when(delegate.get()).thenReturn(Flux.just(instances)); - SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, 1000); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar", 1000)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).hasSize(101); @@ -95,10 +104,12 @@ void shouldReturnSameSublistForSameInstanceId() { when(delegate.get()).thenReturn(Flux.just(instances)); - SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, "foobar", 5); + SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar", 5)); List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); - SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, "foobar", 5); + SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar", 5)); List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); assertThat(serviceInstances1).isEqualTo(serviceInstances2); @@ -112,13 +123,24 @@ void shouldReturnDifferentSublistForDifferentInstanceId() { when(delegate.get()).thenReturn(Flux.just(instances)); - SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, "foobar1", 5); + SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar1", 5)); List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); - SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, "foobar2", 5); + SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, env, + subsetProperties("foobar2", 5)); List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); assertThat(serviceInstances1).isNotEqualTo(serviceInstances2); } + LoadBalancerProperties subsetProperties(String instanceId, int size) { + LoadBalancerProperties properties = new LoadBalancerProperties(); + LoadBalancerProperties.Subset subset = new LoadBalancerProperties.Subset(); + subset.setInstanceId(instanceId); + subset.setSize(size); + properties.setSubset(subset); + return properties; + } + } From 9935bc541517a2538d20b3a7c66ab86d3c9195b1 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Wed, 1 Nov 2023 00:26:59 +0800 Subject: [PATCH 05/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../ServiceInstanceListSupplierBuilder.java | 11 ++++++++ .../SubsetServiceInstanceListSupplier.java | 4 ++- ...SubsetServiceInstanceListSupplierTest.java | 26 ++++++++++++------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java index 74ee2e386..ff40abf8a 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java @@ -35,6 +35,7 @@ import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.PropertyResolver; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -301,6 +302,16 @@ public ServiceInstanceListSupplierBuilder withHints() { return this; } + public ServiceInstanceListSupplierBuilder withSubset() { + DelegateCreator creator = (context, delegate) -> { + PropertyResolver resolver = context.getBean(PropertyResolver.class); + LoadBalancerClientFactory factory = context.getBean(LoadBalancerClientFactory.class); + return new SubsetServiceInstanceListSupplier(delegate, resolver, factory); + }; + creators.add(creator); + return this; + } + /** * Support {@link ServiceInstanceListSupplierBuilder} can be added to the expansion * implementation of {@link ServiceInstanceListSupplier} by this method. diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index e46ad4a18..13b71802b 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -25,6 +25,7 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.core.env.PropertyResolver; /** @@ -42,8 +43,9 @@ public class SubsetServiceInstanceListSupplier extends DelegatingServiceInstance private final int size; public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, PropertyResolver resolver, - LoadBalancerProperties properties) { + ReactiveLoadBalancer.Factory factory) { super(delegate); + LoadBalancerProperties properties = factory.getProperties(getServiceId()); this.instanceId = resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); this.size = properties.getSubset().getSize(); } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index 8b1c8ffb8..d4dd04265 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -30,11 +30,13 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.cloud.loadbalancer.core.LoadBalancerTestUtils.buildLoadBalancerClientFactory; /** * Tests for {@link SubsetServiceInstanceListSupplier} @@ -60,9 +62,10 @@ public void destroy() { @Test void shouldReturnEmptyWhenDelegateReturnedEmpty() { + when(delegate.getServiceId()).thenReturn("test"); when(delegate.get()).thenReturn(Flux.just(Collections.emptyList())); SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar", 100)); + factory("foobar", 100)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).isEmpty(); @@ -74,9 +77,10 @@ void shouldReturnSublistWithGivenSubsetSize() { .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) .collect(Collectors.toList()); + when(delegate.getServiceId()).thenReturn("test"); when(delegate.get()).thenReturn(Flux.just(instances)); SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar", 5)); + factory("foobar", 5)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).hasSize(5); @@ -88,9 +92,10 @@ void shouldReturnRawWhenLessThanSubsetSize() { .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) .collect(Collectors.toList()); + when(delegate.getServiceId()).thenReturn("test"); when(delegate.get()).thenReturn(Flux.just(instances)); SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar", 1000)); + factory("foobar", 1000)); List serviceInstances = Objects.requireNonNull(supplier.get().blockFirst()); assertThat(serviceInstances).hasSize(101); @@ -102,14 +107,15 @@ void shouldReturnSameSublistForSameInstanceId() { .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) .collect(Collectors.toList()); + when(delegate.getServiceId()).thenReturn("test"); when(delegate.get()).thenReturn(Flux.just(instances)); SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar", 5)); + factory("foobar", 5)); List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar", 5)); + factory("foobar", 5)); List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); assertThat(serviceInstances1).isEqualTo(serviceInstances2); @@ -121,26 +127,28 @@ void shouldReturnDifferentSublistForDifferentInstanceId() { .mapToObj(i -> new DefaultServiceInstance(Integer.toString(i), "test", "host" + i, 8080, false, null)) .collect(Collectors.toList()); + when(delegate.getServiceId()).thenReturn("test"); when(delegate.get()).thenReturn(Flux.just(instances)); SubsetServiceInstanceListSupplier supplier1 = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar1", 5)); + factory("foobar1", 5)); List serviceInstances1 = Objects.requireNonNull(supplier1.get().blockFirst()); SubsetServiceInstanceListSupplier supplier2 = new SubsetServiceInstanceListSupplier(delegate, env, - subsetProperties("foobar2", 5)); + factory("foobar2", 5)); List serviceInstances2 = Objects.requireNonNull(supplier2.get().blockFirst()); assertThat(serviceInstances1).isNotEqualTo(serviceInstances2); } - LoadBalancerProperties subsetProperties(String instanceId, int size) { + ReactiveLoadBalancer.Factory factory(String instanceId, int size) { LoadBalancerProperties properties = new LoadBalancerProperties(); LoadBalancerProperties.Subset subset = new LoadBalancerProperties.Subset(); subset.setInstanceId(instanceId); subset.setSize(size); properties.setSubset(subset); - return properties; + + return buildLoadBalancerClientFactory("test", properties); } } From 31bb1b956247728a7d5165c9e0dcdcf470441d39 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Wed, 1 Nov 2023 01:18:33 +0800 Subject: [PATCH 06/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../loadbalancer/LoadBalancerProperties.java | 6 ++-- .../SubsetServiceInstanceListSupplier.java | 17 +++++++++- ...SubsetServiceInstanceListSupplierTest.java | 33 ++++++++++++++----- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index ad3bca53b..4414d12d6 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -30,6 +30,7 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.commons.util.IdUtils; +import org.springframework.core.env.PropertyResolver; import org.springframework.http.HttpMethod; import org.springframework.util.LinkedCaseInsensitiveMap; @@ -507,9 +508,10 @@ public void setEnabled(boolean enabled) { public static class Subset { /** - * Instance id of deterministic subsetting. + * Instance id of deterministic subsetting. If not set, + * {@link IdUtils#getDefaultInstanceId(PropertyResolver)} will be used. */ - private String instanceId = IdUtils.DEFAULT_SERVICE_ID_STRING; + private String instanceId = ""; /** * Max subset size of deterministic subsetting. diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index 13b71802b..d2e292a61 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -26,7 +26,9 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.commons.util.IdUtils; import org.springframework.core.env.PropertyResolver; +import org.springframework.util.StringUtils; /** * A {@link ServiceInstanceListSupplier} implementation that uses @@ -46,7 +48,13 @@ public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, P ReactiveLoadBalancer.Factory factory) { super(delegate); LoadBalancerProperties properties = factory.getProperties(getServiceId()); - this.instanceId = resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); + String instanceId = properties.getSubset().getInstanceId(); + if (StringUtils.hasText(instanceId)) { + this.instanceId = resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); + } + else { + this.instanceId = IdUtils.getDefaultInstanceId(resolver); + } this.size = properties.getSubset().getSize(); } @@ -72,4 +80,11 @@ public Flux> get() { }); } + public String getInstanceId() { + return instanceId; + } + + public int getSize() { + return size; + } } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index d4dd04265..05673a8d5 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -16,22 +16,22 @@ package org.springframework.cloud.loadbalancer.core; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; +import org.springframework.cloud.commons.util.IdUtils; import org.springframework.mock.env.MockEnvironment; +import reactor.core.publisher.Flux; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -60,6 +60,23 @@ public void destroy() { env = null; } + @Test + void shouldResolvePlaceholderWhenInstanceIdSet() { + env.setProperty("foo", "bar"); + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, + factory("${foo}", 100)); + + assertThat(supplier.getInstanceId()).isEqualTo("bar"); + } + + @Test + void shouldUseIdUtilsWhenInstanceIdNotSet() { + SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, + factory("", 100)); + + assertThat(supplier.getInstanceId()).isEqualTo(IdUtils.getDefaultInstanceId(env)); + } + @Test void shouldReturnEmptyWhenDelegateReturnedEmpty() { when(delegate.getServiceId()).thenReturn("test"); From d0308cd8d5e8aeb59ca90cd1b9c0ef0fb00b4f40 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Wed, 1 Nov 2023 01:24:09 +0800 Subject: [PATCH 07/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../core/SubsetServiceInstanceListSupplier.java | 1 + .../SubsetServiceInstanceListSupplierTest.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index d2e292a61..64097ab34 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -87,4 +87,5 @@ public String getInstanceId() { public int getSize() { return size; } + } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index 05673a8d5..c8780598c 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -16,22 +16,23 @@ package org.springframework.cloud.loadbalancer.core; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; + import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.commons.util.IdUtils; import org.springframework.mock.env.MockEnvironment; -import reactor.core.publisher.Flux; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; From 75d46918e46acf6105ba9622e18b0753196bf767 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Wed, 1 Nov 2023 01:44:17 +0800 Subject: [PATCH 08/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../core/SubsetServiceInstanceListSupplierTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java index c8780598c..71772e20a 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplierTest.java @@ -64,6 +64,7 @@ public void destroy() { @Test void shouldResolvePlaceholderWhenInstanceIdSet() { env.setProperty("foo", "bar"); + when(delegate.getServiceId()).thenReturn("test"); SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, factory("${foo}", 100)); @@ -75,6 +76,7 @@ void shouldUseIdUtilsWhenInstanceIdNotSet() { SubsetServiceInstanceListSupplier supplier = new SubsetServiceInstanceListSupplier(delegate, env, factory("", 100)); + when(delegate.getServiceId()).thenReturn("test"); assertThat(supplier.getInstanceId()).isEqualTo(IdUtils.getDefaultInstanceId(env)); } From 6ac12e2a9d7b5b5ebf31eaf441f5401ac0cd8c54 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Fri, 10 Nov 2023 01:27:40 +0800 Subject: [PATCH 09/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../loadbalancer/LoadBalancerProperties.java | 4 +++- .../LoadBalancerClientConfiguration.java | 19 +++++++++++++++++++ .../SubsetServiceInstanceListSupplier.java | 16 +++++++++------- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java index 4414d12d6..53965a344 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java @@ -42,6 +42,7 @@ * * @author Olga Maciaszek-Sharma * @author Gandhimathi Velusamy + * @author Zhuozhi Ji * @since 2.2.1 */ public class LoadBalancerProperties { @@ -88,7 +89,8 @@ public class LoadBalancerProperties { private boolean callGetWithRequestOnDelegates = true; /** - * Properties for SubsetServiceInstanceListSupplier. + * Properties for + * {@link org.springframework.cloud.loadbalancer.core.SubsetServiceInstanceListSupplier}. */ private Subset subset = new Subset(); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java index f0aae50c8..415112954 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java @@ -143,6 +143,15 @@ public ServiceInstanceListSupplier weightedServiceInstanceListSupplier(Configura .build(context); } + @Bean + @ConditionalOnBean(ReactiveDiscoveryClient.class) + @ConditionalOnMissingBean + @Conditional(SubsetConfigurationCondition.class) + public ServiceInstanceListSupplier subsetServiceInstanceListSupplier(ConfigurableApplicationContext context) { + return ServiceInstanceListSupplier.builder().withDiscoveryClient().withSubset().withCaching() + .build(context); + } + } @Configuration(proxyBeanMethods = false) @@ -353,4 +362,14 @@ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) } + static class SubsetConfigurationCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return LoadBalancerEnvironmentPropertyUtils.equalToForClientOrDefault(context.getEnvironment(), + "configurations", "subset"); + } + + } + } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java index 64097ab34..26df97748 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/SubsetServiceInstanceListSupplier.java @@ -48,13 +48,7 @@ public SubsetServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, P ReactiveLoadBalancer.Factory factory) { super(delegate); LoadBalancerProperties properties = factory.getProperties(getServiceId()); - String instanceId = properties.getSubset().getInstanceId(); - if (StringUtils.hasText(instanceId)) { - this.instanceId = resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); - } - else { - this.instanceId = IdUtils.getDefaultInstanceId(resolver); - } + this.instanceId = resolveInstanceId(properties, resolver); this.size = properties.getSubset().getSize(); } @@ -80,6 +74,14 @@ public Flux> get() { }); } + private static String resolveInstanceId(LoadBalancerProperties properties, PropertyResolver resolver) { + String instanceId = properties.getSubset().getInstanceId(); + if (StringUtils.hasText(instanceId)) { + return resolver.resolvePlaceholders(properties.getSubset().getInstanceId()); + } + return IdUtils.getDefaultInstanceId(resolver); + } + public String getInstanceId() { return instanceId; } From d15c6680a27e96404b5fb526d3528b5cc1f66831 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Fri, 10 Nov 2023 01:36:44 +0800 Subject: [PATCH 10/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../annotation/LoadBalancerClientConfiguration.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java index 415112954..8e7a56834 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration.java @@ -217,6 +217,15 @@ public ServiceInstanceListSupplier weightedServiceInstanceListSupplier(Configura .build(context); } + @Bean + @ConditionalOnBean(DiscoveryClient.class) + @ConditionalOnMissingBean + @Conditional(SubsetConfigurationCondition.class) + public ServiceInstanceListSupplier subsetServiceInstanceListSupplier(ConfigurableApplicationContext context) { + return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withSubset().withCaching() + .build(context); + } + } @Configuration(proxyBeanMethods = false) From 292abced1fc70f2faceab2796f8544709ed8c13a Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Fri, 10 Nov 2023 02:17:36 +0800 Subject: [PATCH 11/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../spring-cloud-commons/loadbalancer.adoc | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc index 045d8ddc6..1c73bd05f 100644 --- a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc @@ -379,6 +379,34 @@ For `WebClient`, you need to implement and define `LoadBalancerClientRequestTran If multiple transformers are defined, they are applied in the order in which Beans are defined. Alternatively, you can use `LoadBalancerRequestTransformer.DEFAULT_ORDER` or `LoadBalancerClientRequestTransformer.DEFAULT_ORDER` to specify the order. +[[loadbalancer-subset]] +== Spring Cloud LoadBalancer Subset + +`SubsetServiceInstanceListSupplier` implements a https://sre.google/sre-book/load-balancing-datacenter/[deterministic subsetting algorithm] to select a limited number of instances in the `ServiceInstanceListSupplier` delegates hierarchy. + +You can configure it either by setting the value of `spring.cloud.loadbalancer.configurations` to `subset` or by providing your own `ServiceInstanceListSupplier` bean -- for example: + +[[subset-custom-loadbalancer-configuration-example]] +[source,java,indent=0] +---- +public class CustomLoadBalancerConfiguration { + + @Bean + public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( + ConfigurableApplicationContext context) { + return ServiceInstanceListSupplier.builder() + .withDiscoveryClient() + .withSubset() + .withCaching() + .build(context); + } + } +---- + +TIP: Each service instance will be assigned a unique `instanceId` by default, and different `instanceId` will often select different subsets. Normally you don't need to pay attention to it, but if you need to have multiple instances select the same subset, you can set it via `spring.cloud.loadbalancer.subset.instance-id` (supports placeholders). + +TIP: The size of the subset is set to 100 by default, you can also set it via `spring.cloud.loadbalancer.subset.size` + [[spring-cloud-loadbalancer-starter]] == Spring Cloud LoadBalancer Starter From f488bedecf5cf7397f9c48ae1fe2fd69a848d625 Mon Sep 17 00:00:00 2001 From: jizhuozhi Date: Tue, 14 Nov 2023 00:36:25 +0800 Subject: [PATCH 12/12] feat(lb): deterministic subsetting algorithm to limit the number of instances --- .../modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc index 1c73bd05f..b8be4c338 100644 --- a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc @@ -403,9 +403,9 @@ public class CustomLoadBalancerConfiguration { } ---- -TIP: Each service instance will be assigned a unique `instanceId` by default, and different `instanceId` will often select different subsets. Normally you don't need to pay attention to it, but if you need to have multiple instances select the same subset, you can set it via `spring.cloud.loadbalancer.subset.instance-id` (supports placeholders). +TIP: By default, each service instance is assigned a unique `instanceId`, and different `instanceId` values often select different subsets. Normally, you need not pay attention to it. However, if you need to have multiple instances select the same subset, you can set it with `spring.cloud.loadbalancer.subset.instance-id` (which supports placeholders). -TIP: The size of the subset is set to 100 by default, you can also set it via `spring.cloud.loadbalancer.subset.size` +TIP: By default, the size of the subset is set to 100. You can also set it with `spring.cloud.loadbalancer.subset.size`. [[spring-cloud-loadbalancer-starter]] == Spring Cloud LoadBalancer Starter