Skip to content

Commit 5fbc5e0

Browse files
jasonjoo2010jason-joo
authored andcommitted
Smooth Round Robin selection
1 parent 2e826a6 commit 5fbc5e0

File tree

4 files changed

+332
-111
lines changed

4 files changed

+332
-111
lines changed

dubbo-cluster/src/main/java/com/alibaba/dubbo/rpc/cluster/loadbalance/RoundRobinLoadBalance.java

Lines changed: 109 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,87 +17,140 @@
1717
package com.alibaba.dubbo.rpc.cluster.loadbalance;
1818

1919
import com.alibaba.dubbo.common.URL;
20-
import com.alibaba.dubbo.common.utils.AtomicPositiveInteger;
2120
import com.alibaba.dubbo.rpc.Invocation;
2221
import com.alibaba.dubbo.rpc.Invoker;
2322

24-
import java.util.LinkedHashMap;
23+
import java.util.Collection;
24+
import java.util.HashMap;
25+
import java.util.Iterator;
2526
import java.util.List;
2627
import java.util.Map;
28+
import java.util.Map.Entry;
2729
import java.util.concurrent.ConcurrentHashMap;
2830
import java.util.concurrent.ConcurrentMap;
31+
import java.util.concurrent.atomic.AtomicBoolean;
32+
import java.util.concurrent.atomic.AtomicLong;
2933

3034
/**
3135
* Round robin load balance.
36+
*
37+
* Smoothly round robin's implementation @since 2.6.5
38+
* @author jason
3239
*
3340
*/
3441
public class RoundRobinLoadBalance extends AbstractLoadBalance {
35-
3642
public static final String NAME = "roundrobin";
43+
44+
private static int RECYCLE_PERIOD = 60000;
45+
46+
protected static class WeightedRoundRobin {
47+
private int weight;
48+
private AtomicLong current = new AtomicLong(0);
49+
private long lastUpdate;
50+
public int getWeight() {
51+
return weight;
52+
}
53+
public void setWeight(int weight) {
54+
this.weight = weight;
55+
current.set(0);
56+
}
57+
public long increaseCurrent() {
58+
return current.addAndGet(weight);
59+
}
60+
public void sel(int total) {
61+
current.addAndGet(-1 * total);
62+
}
63+
public long getLastUpdate() {
64+
return lastUpdate;
65+
}
66+
public void setLastUpdate(long lastUpdate) {
67+
this.lastUpdate = lastUpdate;
68+
}
69+
}
3770

38-
private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
39-
71+
private ConcurrentMap<String, Map<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, Map<String, WeightedRoundRobin>>();
72+
private AtomicBoolean updateLock = new AtomicBoolean();
73+
74+
/**
75+
* get invoker addr list cached for specified invocation
76+
* <p>
77+
* <b>for unit test only</b>
78+
*
79+
* @param invokers
80+
* @param invocation
81+
* @return
82+
*/
83+
protected <T> Collection<String> getInvokerAddrList(List<Invoker<T>> invokers, Invocation invocation) {
84+
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
85+
Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
86+
if (map != null) {
87+
return map.keySet();
88+
}
89+
return null;
90+
}
91+
4092
@Override
4193
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
4294
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
43-
int length = invokers.size(); // Number of invokers
44-
int maxWeight = 0; // The maximum weight
45-
int minWeight = Integer.MAX_VALUE; // The minimum weight
46-
final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
47-
int weightSum = 0;
48-
for (int i = 0; i < length; i++) {
49-
int weight = getWeight(invokers.get(i), invocation);
50-
maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
51-
minWeight = Math.min(minWeight, weight); // Choose the minimum weight
52-
if (weight > 0) {
53-
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
54-
weightSum += weight;
55-
}
95+
Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
96+
if (map == null) {
97+
methodWeightMap.putIfAbsent(key, new HashMap<String, WeightedRoundRobin>());
98+
map = methodWeightMap.get(key);
5699
}
57-
AtomicPositiveInteger sequence = sequences.get(key);
58-
if (sequence == null) {
59-
sequences.putIfAbsent(key, new AtomicPositiveInteger());
60-
sequence = sequences.get(key);
100+
int totalWeight = 0;
101+
long maxCurrent = Long.MIN_VALUE;
102+
long now = System.currentTimeMillis();
103+
Invoker<T> selectedInvoker = null;
104+
WeightedRoundRobin selectedWRR = null;
105+
for (Invoker<T> invoker : invokers) {
106+
String identifyString = invoker.getUrl().toIdentityString();
107+
WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
108+
int weight = getWeight(invoker, invocation);
109+
if (weight < 0) {
110+
weight = 0;
111+
}
112+
if (weightedRoundRobin == null) {
113+
weightedRoundRobin = new WeightedRoundRobin();
114+
weightedRoundRobin.setWeight(weight);
115+
map.put(identifyString, weightedRoundRobin);
116+
}
117+
if (weight != weightedRoundRobin.getWeight()) {
118+
//weight changed
119+
weightedRoundRobin.setWeight(weight);
120+
}
121+
long cur = weightedRoundRobin.increaseCurrent();
122+
weightedRoundRobin.setLastUpdate(now);
123+
if (cur > maxCurrent) {
124+
maxCurrent = cur;
125+
selectedInvoker = invoker;
126+
selectedWRR = weightedRoundRobin;
127+
}
128+
totalWeight += weight;
61129
}
62-
int currentSequence = sequence.getAndIncrement();
63-
if (maxWeight > 0 && minWeight < maxWeight) {
64-
int mod = currentSequence % weightSum;
65-
for (int i = 0; i < maxWeight; i++) {
66-
for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
67-
final Invoker<T> k = each.getKey();
68-
final IntegerWrapper v = each.getValue();
69-
if (mod == 0 && v.getValue() > 0) {
70-
return k;
71-
}
72-
if (v.getValue() > 0) {
73-
v.decrement();
74-
mod--;
130+
if (updateLock.get() == false && invokers.size() != map.size()) {
131+
if (updateLock.compareAndSet(false, true)) {
132+
try {
133+
// copy -> modify -> update reference
134+
HashMap<String, WeightedRoundRobin> newMap = new HashMap<String, WeightedRoundRobin>();
135+
newMap.putAll(map);
136+
Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
137+
while (it.hasNext()) {
138+
Entry<String, WeightedRoundRobin> item = it.next();
139+
if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
140+
it.remove();
141+
}
75142
}
143+
methodWeightMap.put(key, newMap);
144+
} finally {
145+
updateLock.set(false);
76146
}
77147
}
78148
}
79-
// Round robin
80-
return invokers.get(currentSequence % length);
81-
}
82-
83-
private static final class IntegerWrapper {
84-
private int value;
85-
86-
public IntegerWrapper(int value) {
87-
this.value = value;
88-
}
89-
90-
public int getValue() {
91-
return value;
92-
}
93-
94-
public void setValue(int value) {
95-
this.value = value;
96-
}
97-
98-
public void decrement() {
99-
this.value--;
149+
if (selectedInvoker != null) {
150+
selectedWRR.sel(totalWeight);
151+
return selectedInvoker;
100152
}
153+
return null;
101154
}
102155

103156
}

dubbo-cluster/src/test/java/com/alibaba/dubbo/rpc/cluster/StickyTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ public int testSticky(String methodName, boolean check) {
114114

115115
given(invoker1.invoke(invocation)).willReturn(result);
116116
given(invoker1.isAvailable()).willReturn(true);
117-
given(invoker1.getUrl()).willReturn(url);
117+
given(invoker1.getUrl()).willReturn(url.setPort(1));
118118
given(invoker1.getInterface()).willReturn(StickyTest.class);
119119

120120
given(invoker2.invoke(invocation)).willReturn(result);
121121
given(invoker2.isAvailable()).willReturn(true);
122-
given(invoker2.getUrl()).willReturn(url);
122+
given(invoker2.getUrl()).willReturn(url.setPort(2));
123123
given(invoker2.getInterface()).willReturn(StickyTest.class);
124124

125125
invocation.setMethodName(methodName);
@@ -158,4 +158,4 @@ public Invoker<T> getSelectedInvoker() {
158158
return selectedInvoker;
159159
}
160160
}
161-
}
161+
}

0 commit comments

Comments
 (0)