package com.easy.api.utils.other;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
import java.util.Random;
/**
* @author muchi
*/
public class LoadBalancerUtils<T> {
public static class WeightedItem<T> {
T item;
int weight;
int currentWeight;
public WeightedItem(T item, int weight) {
this.item = item;
this.weight = weight;
this.currentWeight = 0;
}
}
private final List<WeightedItem<T>> items;
private int currentIndex = -1;
private int currentWeight = 0;
private final int maxWeight;
private final int gcdWeight;
private final Random random = new Random();
private int totalWeight = 0;
public LoadBalancerUtils(List<WeightedItem<T>> items) {
this.items = items;
this.maxWeight = getMaxWeight(items);
this.gcdWeight = getGcdWeight(items);
for (WeightedItem<T> item : items) {
totalWeight += item.weight;
}
}
// 1. 简单轮询
public T roundRobin() {
currentIndex = (currentIndex + 1) % items.size();
return items.get(currentIndex).item;
}
// 2. 随机选择
public T randomSelection() {
int index = random.nextInt(items.size());
return items.get(index).item;
}
// 3. 加权轮询
/**
* 加权轮询算法。
* <p>该算法的思想是维护一个当前索引和当前权重,每次选择元素时,将当前索引加上 1,当前索引取模 items.size(),同时将当前权重减去 gcdWeight,
* 当当前索引为 0 时,将当前权重设为 maxWeight,当前权重小于等于 0 时,将当前权重置为 maxWeight。
* 如果当前元素的权重大于等于当前权重,则返回当前元素,否则继续选择下一个元素。
*
* @return 被选中的元素
*/
public T weightedRoundRobin() {
while (true) {
currentIndex = (currentIndex + 1) % items.size();
if (currentIndex == 0) {
currentWeight -= gcdWeight;
if (currentWeight <= 0) {
currentWeight = maxWeight;
}
}
if (items.get(currentIndex).weight >= currentWeight) {
return items.get(currentIndex).item;
}
}
}
// 4. 平滑加权轮询
/**
* 平滑加权轮询算法
*
* <p>该算法的思想是给每个元素添加一个 currentWeight 属性,每次选择元素时,将当前元素的 currentWeight 加上其 weight 属性,
* 当 currentWeight 大于所有元素的最大 weight 时,找到最大的 currentWeight 对应的元素,并将其 currentWeight 减去所有元素的总 weight 值。
* 这样可以使每个元素被选中的概率与其 weight 成正比。
*
* @return 返回被选中的元素,若没有找到,则返回 null
*/
public T smoothWeightedRoundRobin() {
WeightedItem<T> selected = null;
int maxWeight = Integer.MIN_VALUE;
for (WeightedItem<T> item : items) {
item.currentWeight += item.weight;
if (item.currentWeight > maxWeight) {
maxWeight = item.currentWeight;
selected = item;
}
}
if (selected != null) {
selected.currentWeight -= totalWeight;
return selected.item;
}
return null;
}
private int getMaxWeight(List<WeightedItem<T>> items) {
int max = 0;
for (WeightedItem<T> item : items) {
max = Math.max(max, item.weight);
}
return max;
}
private int getGcdWeight(List<WeightedItem<T>> items) {
int gcd = items.get(0).weight;
for (WeightedItem<T> item : items) {
gcd = gcd(gcd, item.weight);
}
return gcd;
}
private int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
@Data
@AllArgsConstructor
static class MxbcTest {
private String id;
private String token;
}
public static void main(String[] args) {
List<WeightedItem<MxbcTest>> servers = List.of(
new WeightedItem<>(new MxbcTest("1", "权重1"), 5),
new WeightedItem<>(new MxbcTest("2", "权重2"), 1),
new WeightedItem<>(new MxbcTest("3", "权重3"), 1)
);
LoadBalancerUtils<MxbcTest> loadBalancerUtils = new LoadBalancerUtils<>(servers);
for (int i = 1; i < 8; i++) {
// System.out.println("简单轮训第" + i +"次,"+ loadBalancerUtils.roundRobin());
System.out.println("随机选择" + i +"次,"+ loadBalancerUtils.randomSelection());
// System.out.println("加权轮训" + i +"次,"+ loadBalancerUtils.weightedRoundRobin());
// System.out.println("平滑加权轮询" + i +"次,"+ loadBalancerUtils.smoothWeightedRoundRobin());
// System.out.println("==================================================================");
}
}
}