diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java index 394b88aa..2d33c924 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java @@ -17,7 +17,9 @@ package org.springframework.cloud.loadbalancer.core; import java.util.AbstractList; +import java.util.ArrayDeque; import java.util.List; +import java.util.Queue; import org.springframework.cloud.client.ServiceInstance; @@ -30,48 +32,36 @@ import org.springframework.cloud.client.ServiceInstance; */ class LazyWeightedServiceInstanceList extends AbstractList { - private final List instances; - - private final int[] weights; - - private SmoothServiceInstanceSelector selector; + private final InterleavedWeightedServiceInstanceSelector selector; private volatile int position; - /* for testing */ ServiceInstance[] expanded; + /* for testing */ final ServiceInstance[] expanded; private final Object expandingLock = new Object(); LazyWeightedServiceInstanceList(List instances, int[] weights) { - this.instances = instances; - this.weights = weights; - this.init(); - } - - private void init() { - // Calculate the greatest common divisor (GCD) of weights, max weight and the + // Calculate the greatest common divisor (GCD) of weights, and the // total number of elements after expansion. int greatestCommonDivisor = 0; - int max = 0; int total = 0; for (int weight : weights) { greatestCommonDivisor = greatestCommonDivisor(greatestCommonDivisor, weight); - max = Math.max(max, weight); total += weight; } - selector = new SmoothServiceInstanceSelector(instances, weights, max, greatestCommonDivisor); + selector = new InterleavedWeightedServiceInstanceSelector(instances.toArray(new ServiceInstance[0]), weights, + greatestCommonDivisor); position = 0; expanded = new ServiceInstance[total / greatestCommonDivisor]; } @Override public ServiceInstance get(int index) { - if (index < position) { - return expanded[index]; - } - synchronized (expandingLock) { - for (; position <= index && position < expanded.length; position++) { - expanded[position] = selector.next(); + if (index >= position) { + synchronized (expandingLock) { + for (; position <= index && position < expanded.length; position++) { + expanded[position] = selector.next(); + } } } return expanded[index]; @@ -92,44 +82,79 @@ class LazyWeightedServiceInstanceList extends AbstractList { return a; } - static class SmoothServiceInstanceSelector { + static class InterleavedWeightedServiceInstanceSelector { - final List instances; + static final int MODE_LIST = 0; + + static final int MODE_QUEUE = 1; + + final ServiceInstance[] instances; final int[] weights; - final int maxWeight; + final int greatestCommonDivisor; - final int gcd; + final Queue queue; + + int mode; int position; - int currentWeight; - - SmoothServiceInstanceSelector(List instances, int[] weights, int maxWeight, int gcd) { + InterleavedWeightedServiceInstanceSelector(ServiceInstance[] instances, int[] weights, + int greatestCommonDivisor) { this.instances = instances; this.weights = weights; - this.maxWeight = maxWeight; - this.gcd = gcd; - this.currentWeight = 0; + this.greatestCommonDivisor = greatestCommonDivisor; + queue = new ArrayDeque<>(instances.length); + mode = MODE_LIST; + position = 0; } ServiceInstance next() { - // The weight of all instances is greater than 0, so it must be able to exit - // the loop. - while (true) { - for (int picked = position; picked < weights.length; picked++) { - if (weights[picked] > currentWeight) { - position = picked + 1; - return instances.get(picked); - } + if (mode == MODE_LIST) { + ServiceInstance instance = instances[position]; + int weight = weights[position]; + + weight = weight - greatestCommonDivisor; + if (weight > 0) { + queue.add(new Entry(instance, weight)); } - position = 0; - currentWeight = currentWeight + gcd; - if (currentWeight >= maxWeight) { - currentWeight = 0; + + position++; + if (position == instances.length) { + mode = MODE_QUEUE; + position = 0; } + + return instance; } + else { + if (queue.isEmpty()) { + mode = MODE_LIST; + return next(); + } + + Entry entry = queue.poll(); + entry.weight = entry.weight - greatestCommonDivisor; + if (entry.weight > 0) { + queue.add(entry); + } + + return entry.instance; + } + } + + static class Entry { + + final ServiceInstance instance; + + int weight; + + Entry(ServiceInstance instance, int weight) { + this.instance = instance; + this.weight = weight; + } + } } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceListTest.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceListTest.java index f33d9814..723bf4bb 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceListTest.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceListTest.java @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; class LazyWeightedServiceInstanceListTest { @Test - void shouldCreateListWithSizeEqualToSumofRatio() { + void shouldCreateListWithSizeEqualToSumOfRatio() { List serviceInstances = new ArrayList<>(); int[] weights = new int[10]; for (int i = 0; i < 10; i++) {