Dubbo 睡前小故事之RandomLoadBalance
RandomLoadBalance 是Dubbo的负载均衡提供的策略之一。
如何设计一个随机算法
答案很多~假如在这样一个场景,A、B、C、D四个物品权重分别为1、2、3、4,如何在考虑权重的情况下,设计一个随机获取某个物品的算法。有如下的方式:
- 对四个物品做一次遍历,按序进行累加,用线段可表示如下,线段越长表示落入该区域的概率越大。
- 然后随机获取一个1到10之间的整数,线段越长,表示落在这个范围的整数概率越搞,即可在考虑权重的情况下,达到随机的目的。
RandomLoadBalance
明白上诉原理,RandomLoadBalance就不过如此了:
- 在进行第一次遍历时顺带判断权重是否一致,一致的话随机获取一个即可。
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
int length = invokers.size();
// Every invoker has the same weight?
boolean sameWeight = true;
// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
int[] weights = new int[length];
// The sum of weights
int totalWeight = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// Sum
totalWeight += weight;
// save for later use
weights[i] = totalWeight;
// 比较权重是否都一致
if (sameWeight && totalWeight != weight * (i + 1)) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {
// 返回第一个大于权重的invoker
return invokers.get(i);
}
}
}
// 如果所有invoker都一致, 直接返回一个临近随机权重的invoker
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
- 关于ThreadLocalRandom,它是JDK 1.7出现的一个获取随机数的类,根据其解释,可以解决并发下其他随机函数竞争统一资源时产生多余的消耗,咋样的消耗?
关于ThreadLocalRandom
先看Random#nextInt方法下的具体实现,可以看到每次获取随机数,都是依赖随机种子的,那么在并发的情况下,由于多个线程访问同一个随机种子,就有可能导致计算出来的结果是一样的。
咋办,可以看到代码里加了CAS,但又引入一个问题,在争抢资源中失败的线程就进入CPU空转了,降低性能。
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
ThreadLocalRandom使用方式:
ThreadLocalRandom.current().nextInt(totalWeight);
在使用时先通过current方法,为每个第一次调用该方法的线程分配了SEED
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
/**
* Initialize Thread fields for the current thread. Called only
* when Thread.threadLocalRandomProbe is zero, indicating that a
* thread local seed value needs to be generated. Note that even
* though the initialization is purely thread-local, we need to
* rely on (static) atomic generators to initialize the values.
*/
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
呃呃,对了Dubbo的几个版本对RandomLoadBalance的可能不太一样,但原理大径相同。上面是现在master分支的实现方式。(截止至2021年5月22日 02:15)