服务提供者对象构建类
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