服务提供者对象构建类
public class Refer {
private String serverName;
/**
* 当前使用该referer的调用数
*
* @return
*/
private int activeRefererCount;
/**
* 链接是否可用
*/
private boolean isAvailable;
/**
* 类路径
*/
private String serviceKey;
/**
* 方法名
*/
private String method;
/**
* 提供权重占比
*/
private int weight;
public int getActiveRefererCount() {
return activeRefererCount;
}
public void setActiveRefererCount(int activeRefererCount) {
this.activeRefererCount = activeRefererCount;
}
public boolean isAvailable() {
return isAvailable;
}
public void setAvailable(boolean available) {
isAvailable = available;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServiceKey() {
return serviceKey;
}
public void setServiceKey(String serviceKey) {
this.serviceKey = serviceKey;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Refer{" +
"serverName=" + serverName +
", activeRefererCount=" + activeRefererCount +
", isAvailable=" + isAvailable +
", serviceKey='" + serviceKey + '\'' +
", method='" + method + '\'' +
", weight=" + weight +
'}';
}
}
方案一:轮询权重算法实现类
public class RoundRobinWeightLoadBalance {
public static List<Refer> refers = new ArrayList<Refer>();
private static final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<>();
private static int[] weights = new int[] {5, 1, 1};
private static String[] names = new String[] {"A", "B", "C"};
static {
for (int i = 1; i < 4; i++) {
Refer refer = new Refer();
refer.setServerName(names[i-1]);
refer.setActiveRefererCount(ThreadLocalRandom.current().nextInt(11));
refer.setAvailable(ThreadLocalRandom.current().nextInt(2) == 1 ? true : false);
refer.setMethod("sayHello");
refer.setServiceKey("com.zzx.DemoService");
refer.setWeight(weights[i-1]);
refers.add(refer);
}
}
private static Refer roundRobinWeight() {
String key = refers.get(0).getServiceKey() + "." + refers.get(0).getMethod();
int length = refers.size();
//最大权重
int maxWeight = 0;
//最小权重
int minWeight = Integer.MAX_VALUE;
final LinkedHashMap<Refer, IntegerWrapper> referToWeightMap = new LinkedHashMap<>();
//权重总和
int weightSum = 0;
//下面这个循环主要用于查找最大和最小权重,计算权重总和
for (int i = 0; i < length; i++) {
int weight = refers.get(i).getWeight();
//获取权重最大和最小值
maxWeight = Math.max(maxWeight, weight);
minWeight = Math.min(minWeight, weight);
if(weight > 0) {
referToWeightMap.put(refers.get(i), new IntegerWrapper(weight));
//累加权重
weightSum += weight;
}
}
//获取当前服务对应的调用序列对象 AtomicPositiveInteger,默认为0
AtomicPositiveInteger sequence = sequences.get(key);
if(sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
//获取当前的调用编号
int currentSequence = sequence.getAndIncrement();
//如果最小权重小于最大权重,表明服务提供者之间的权重是不相等的
if(maxWeight > 0 && minWeight < maxWeight) {
//使用调用编号对权重总和进行取余操作
int mod = currentSequence % weightSum;
//进行maxWeight次遍历
for(int i = 0; i < maxWeight; i++) {
//遍历 invokerToWeightMap
for (Map.Entry<Refer, IntegerWrapper> each : referToWeightMap.entrySet()) {
final Refer k = each.getKey();
//获取权重包装类数据
final IntegerWrapper v = each.getValue();
//如果 mod = 0, 且权重大于0, 此时返回相应的Invoker
if(mod == 0 && v.getValue() > 0) {
return k;
}
//mod != 0,且权重大于0,此时权重和mod分别进行自减操作
if(v.getValue() > 0) {
v.decrement();
mod--;
}
}
}
}
return refers.get(currentSequence%length);
}
private static final class IntegerWrapper {
private int value;
public IntegerWrapper(int value) {
this.value = value;
}
public void decrement(){
this.value--;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public static void main(String[] args) {
for (Refer refer : refers) {
System.out.println(refer);
}
System.out.println("---------------------------------------------");
for (int i = 0; i < 8; i++) {
System.out.println("获取按权重轮询Refer:" + roundRobinWeight());
}
}
}
测试结果:
总结:该算法权重设置比较大,mod比较大,会导致循环次数比较多,严重影响性能,获得Refer对象的性能。
方案二轮询权重算法如下:
public class RoundRobinWeightLoadBalance2 {
public static List<Refer> refers = new ArrayList<Refer>();
private static final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, AtomicPositiveInteger> indexSeqs = new ConcurrentHashMap<String, AtomicPositiveInteger>();
private static int[] weights = new int[] {5, 1, 1};
private static String[] names = new String[] {"A", "B", "C"};
static {
for (int i = 1; i < 4; i++) {
Refer refer = new Refer();
refer.setServerName(names[i-1]);
refer.setActiveRefererCount(ThreadLocalRandom.current().nextInt(11));
refer.setAvailable(ThreadLocalRandom.current().nextInt(2) == 1 ? true : false);
refer.setMethod("sayHello");
refer.setServiceKey("com.zzx.DemoService");
refer.setWeight(weights[i-1]);
refers.add(refer);
}
}
private static Refer roundRobinWeight() {
String key = refers.get(0).getServiceKey() + "." + refers.get(0).getMethod();
int length = refers.size();
//最大权重
int maxWeight = 0;
//最小权重
int minWeight = Integer.MAX_VALUE;
final List<Refer> invokerToWeightList = new ArrayList<>();
//下面这个循环主要用于查找最大和最小权重,计算权重总和
for (int i = 0; i < length; i++) {
int weight = refers.get(i).getWeight();
//获取权重最大和最小值
maxWeight = Math.max(maxWeight, weight);
minWeight = Math.min(minWeight, weight);
if(weight > 0) {
invokerToWeightList.add(refers.get(i));
}
}
//获取当前服务对应的调用序列对象 AtomicPositiveInteger,默认为0
AtomicPositiveInteger sequence = sequences.get(key);
if(sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
// 获取下标序列对象 AtomicPositiveInteger
AtomicPositiveInteger indexSeq = indexSeqs.get(key);
if (indexSeq == null) {
// 创建 AtomicPositiveInteger,默认值为 -1
indexSeqs.putIfAbsent(key, new AtomicPositiveInteger(-1));
indexSeq = indexSeqs.get(key);
}
if (maxWeight > 0 && minWeight < maxWeight) {
length = invokerToWeightList.size();
while (true) {
//通过循环,依次获取list的下标
int index = indexSeq.incrementAndGet() % length;
//获得每一个请求,当前权重值
int currentWeight = sequence.get() % maxWeight;
// 每循环一轮(index = 0),重新计算 currentWeight
if (index == 0) {
currentWeight = sequence.incrementAndGet() % maxWeight;
}
// 检测 Invoker 的权重是否大于 currentWeight,大于则返回
if (invokerToWeightList.get(index).getWeight() > currentWeight) {
return invokerToWeightList.get(index);
}
}
}
return refers.get(sequence.incrementAndGet() % length);
}
private static final class IntegerWrapper {
private int value;
public IntegerWrapper(int value) {
this.value = value;
}
public void decrement(){
this.value--;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
public static void main(String[] args) {
for (Refer refer : refers) {
System.out.println(refer);
}
System.out.println("---------------------------------------------");
for (int i = 0; i < 8; i++) {
System.out.println("获取按权重轮询Refer:" + roundRobinWeight());
}
}
}
测试结果
总结
虽然该方案解决权重大时,产生性能的问题。但是从测试结果发现,会导致ServiceA在某一个时刻大量请求并发,增大服务器A的压力。
针对方案二的问题可参考nignx,轮询权重算法实现,使用平滑加权实现。
Nginx 的平滑加权轮询负载均衡。每个服务器对应两个权重,分别为 weight 和 currentWeight。其中 weight 是固定的,currentWeight 会动态调整,初始值为0。当有新的请求进来时,遍历服务器列表,让它的 currentWeight 加上自身权重。遍历完成后,找到最大的 currentWeight,并将其减去权重总和,然后返回相应的服务器即可。
上面描述不是很好理解,下面还是举例进行说明。这里仍然使用服务器 [A, B, C] 对应权重 [5, 1, 1] 的例子说明,现在有7个请求依次进入负载均衡逻辑,选择过程如下:
参考链接:
http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html