Browse Source

Optimizing selectors to ensure that selection is always `O(1)` time (#1184)

pull/1199/head
纪卓志 2 years ago committed by GitHub
parent
commit
aee2354798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 113
      spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java
  2. 2
      spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceListTest.java

113
spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LazyWeightedServiceInstanceList.java

@ -17,7 +17,9 @@ @@ -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; @@ -30,48 +32,36 @@ import org.springframework.cloud.client.ServiceInstance;
*/
class LazyWeightedServiceInstanceList extends AbstractList<ServiceInstance> {
private final List<ServiceInstance> 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<ServiceInstance> 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<ServiceInstance> { @@ -92,44 +82,79 @@ class LazyWeightedServiceInstanceList extends AbstractList<ServiceInstance> {
return a;
}
static class SmoothServiceInstanceSelector {
static class InterleavedWeightedServiceInstanceSelector {
static final int MODE_LIST = 0;
final List<ServiceInstance> instances;
static final int MODE_QUEUE = 1;
final ServiceInstance[] instances;
final int[] weights;
final int maxWeight;
final int greatestCommonDivisor;
final int gcd;
final Queue<Entry> queue;
int position;
int mode;
int currentWeight;
int position;
SmoothServiceInstanceSelector(List<ServiceInstance> 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;
}
}
}

2
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; @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class LazyWeightedServiceInstanceListTest {
@Test
void shouldCreateListWithSizeEqualToSumofRatio() {
void shouldCreateListWithSizeEqualToSumOfRatio() {
List<ServiceInstance> serviceInstances = new ArrayList<>();
int[] weights = new int[10];
for (int i = 0; i < 10; i++) {

Loading…
Cancel
Save