Skip to content

Commit aee2354

Browse files
author
纪卓志
authored
Optimizing selectors to ensure that selection is always O(1) time (#1184)
1 parent e4bd5b3 commit aee2354

File tree

2 files changed

+70
-45
lines changed

2 files changed

+70
-45
lines changed

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

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.springframework.cloud.loadbalancer.core;
1818

1919
import java.util.AbstractList;
20+
import java.util.ArrayDeque;
2021
import java.util.List;
22+
import java.util.Queue;
2123

2224
import org.springframework.cloud.client.ServiceInstance;
2325

@@ -30,48 +32,36 @@
3032
*/
3133
class LazyWeightedServiceInstanceList extends AbstractList<ServiceInstance> {
3234

33-
private final List<ServiceInstance> instances;
34-
35-
private final int[] weights;
36-
37-
private SmoothServiceInstanceSelector selector;
35+
private final InterleavedWeightedServiceInstanceSelector selector;
3836

3937
private volatile int position;
4038

41-
/* for testing */ ServiceInstance[] expanded;
39+
/* for testing */ final ServiceInstance[] expanded;
4240

4341
private final Object expandingLock = new Object();
4442

4543
LazyWeightedServiceInstanceList(List<ServiceInstance> instances, int[] weights) {
46-
this.instances = instances;
47-
this.weights = weights;
48-
this.init();
49-
}
50-
51-
private void init() {
52-
// Calculate the greatest common divisor (GCD) of weights, max weight and the
44+
// Calculate the greatest common divisor (GCD) of weights, and the
5345
// total number of elements after expansion.
5446
int greatestCommonDivisor = 0;
55-
int max = 0;
5647
int total = 0;
5748
for (int weight : weights) {
5849
greatestCommonDivisor = greatestCommonDivisor(greatestCommonDivisor, weight);
59-
max = Math.max(max, weight);
6050
total += weight;
6151
}
62-
selector = new SmoothServiceInstanceSelector(instances, weights, max, greatestCommonDivisor);
52+
selector = new InterleavedWeightedServiceInstanceSelector(instances.toArray(new ServiceInstance[0]), weights,
53+
greatestCommonDivisor);
6354
position = 0;
6455
expanded = new ServiceInstance[total / greatestCommonDivisor];
6556
}
6657

6758
@Override
6859
public ServiceInstance get(int index) {
69-
if (index < position) {
70-
return expanded[index];
71-
}
72-
synchronized (expandingLock) {
73-
for (; position <= index && position < expanded.length; position++) {
74-
expanded[position] = selector.next();
60+
if (index >= position) {
61+
synchronized (expandingLock) {
62+
for (; position <= index && position < expanded.length; position++) {
63+
expanded[position] = selector.next();
64+
}
7565
}
7666
}
7767
return expanded[index];
@@ -92,44 +82,79 @@ static int greatestCommonDivisor(int a, int b) {
9282
return a;
9383
}
9484

95-
static class SmoothServiceInstanceSelector {
85+
static class InterleavedWeightedServiceInstanceSelector {
86+
87+
static final int MODE_LIST = 0;
9688

97-
final List<ServiceInstance> instances;
89+
static final int MODE_QUEUE = 1;
90+
91+
final ServiceInstance[] instances;
9892

9993
final int[] weights;
10094

101-
final int maxWeight;
95+
final int greatestCommonDivisor;
10296

103-
final int gcd;
97+
final Queue<Entry> queue;
10498

105-
int position;
99+
int mode;
106100

107-
int currentWeight;
101+
int position;
108102

109-
SmoothServiceInstanceSelector(List<ServiceInstance> instances, int[] weights, int maxWeight, int gcd) {
103+
InterleavedWeightedServiceInstanceSelector(ServiceInstance[] instances, int[] weights,
104+
int greatestCommonDivisor) {
110105
this.instances = instances;
111106
this.weights = weights;
112-
this.maxWeight = maxWeight;
113-
this.gcd = gcd;
114-
this.currentWeight = 0;
107+
this.greatestCommonDivisor = greatestCommonDivisor;
108+
queue = new ArrayDeque<>(instances.length);
109+
mode = MODE_LIST;
110+
position = 0;
115111
}
116112

117113
ServiceInstance next() {
118-
// The weight of all instances is greater than 0, so it must be able to exit
119-
// the loop.
120-
while (true) {
121-
for (int picked = position; picked < weights.length; picked++) {
122-
if (weights[picked] > currentWeight) {
123-
position = picked + 1;
124-
return instances.get(picked);
125-
}
114+
if (mode == MODE_LIST) {
115+
ServiceInstance instance = instances[position];
116+
int weight = weights[position];
117+
118+
weight = weight - greatestCommonDivisor;
119+
if (weight > 0) {
120+
queue.add(new Entry(instance, weight));
126121
}
127-
position = 0;
128-
currentWeight = currentWeight + gcd;
129-
if (currentWeight >= maxWeight) {
130-
currentWeight = 0;
122+
123+
position++;
124+
if (position == instances.length) {
125+
mode = MODE_QUEUE;
126+
position = 0;
131127
}
128+
129+
return instance;
132130
}
131+
else {
132+
if (queue.isEmpty()) {
133+
mode = MODE_LIST;
134+
return next();
135+
}
136+
137+
Entry entry = queue.poll();
138+
entry.weight = entry.weight - greatestCommonDivisor;
139+
if (entry.weight > 0) {
140+
queue.add(entry);
141+
}
142+
143+
return entry.instance;
144+
}
145+
}
146+
147+
static class Entry {
148+
149+
final ServiceInstance instance;
150+
151+
int weight;
152+
153+
Entry(ServiceInstance instance, int weight) {
154+
this.instance = instance;
155+
this.weight = weight;
156+
}
157+
133158
}
134159

135160
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
class LazyWeightedServiceInstanceListTest {
4545

4646
@Test
47-
void shouldCreateListWithSizeEqualToSumofRatio() {
47+
void shouldCreateListWithSizeEqualToSumOfRatio() {
4848
List<ServiceInstance> serviceInstances = new ArrayList<>();
4949
int[] weights = new int[10];
5050
for (int i = 0; i < 10; i++) {

0 commit comments

Comments
 (0)