Dubbo提供了哪些负载均衡机制?如何实现的?html
LoadBalance接口:能够看出,经过SPI机制默认为RandomLoadBalance,生成的适配器类执行select()方法。node
1 /**
2 * LoadBalance. (SPI, Singleton, ThreadSafe) 3 * <p> 4 * <a href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load-Balancing</a> 5 * 6 * @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory) 7 */ 8 @SPI(RandomLoadBalance.NAME) 9 public interface LoadBalance { 10 11 /** 12 * select one invoker in list. 13 * 14 * @param invokers invokers. 15 * @param url refer url 16 * @param invocation invocation. 17 * @return selected invoker. 18 */ 19 @Adaptive("loadbalance") 20 <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; 21 22 }
实现类的基本类图以下所示:算法
0、AbstractLoadBalance是LoadBalance接口的默认实现抽象类,为子类提供了实现框架。咱们来看看此类具体的实现:数组
(1)主要方法固然是select(),经过选择实现了负载均衡策略。实现主要是调用doSelect()方法,它是个抽象方法,留给具体子类实现不一样的负载均衡策略;app
(2)getWeight()方法计算出invoker权重,计算公式为:weight = (int) (uptime(提供者正常运行时间) / warmup(升温时间) /weight(设定权重)))负载均衡
1 protected int getWeight(Invoker<?> invoker, Invocation invocation) {
2 int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); 3 if (weight > 0) { 4 long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L); 5 if (timestamp > 0L) { 6 int uptime = (int) (System.currentTimeMillis() - timestamp); 7 int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP); 8 if (uptime > 0 && uptime < warmup) { 9 weight = calculateWarmupWeight(uptime, warmup, weight); 10 } 11 } 12 } 13 return weight; 14 }
一、Random LoadBalance:随机,按权重设置随机几率。框架
在一个截面上碰撞的几率高,但调用量越大分布越均匀,并且按几率使用权重后也比较均匀,有利于动态调整提供者权重。 dom
RandomLoadBalance子类,主要经过doSelect()实现按权重的随机算法,实现逻辑为:ide
(1)计算总权重;ui
(2)若是没有设置权重或者全部权重都同样,直接从invokers列表随机返回一个;
(3)不然:使用总权重随机计算一个offset(偏移量),循环invokers列表,offset=offset -(当前invoker权重),即剩余权重,而后返回第一个大于offset权重的invoker;此算法兼顾了权重和轮询(千重相同则轮询,权重不一样则从大到小的节点顺序轮询选中)两个因素。
具体实现以下:
1 protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
2 int length = invokers.size(); // Number of invokers
3 int totalWeight = 0; // The sum of weights
4 boolean sameWeight = true; // Every invoker has the same weight?
5 for (int i = 0; i < length; i++) { 6 int weight = getWeight(invokers.get(i), invocation); 7 totalWeight += weight; // Sum 8 if (sameWeight && i > 0 9 && weight != getWeight(invokers.get(i - 1), invocation)) { 10 sameWeight = false; 11 } 12 } 13 if (totalWeight > 0 && !sameWeight) { 14 // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight. 15 int offset = random.nextInt(totalWeight); 16 // Return a invoker based on the random value. 17 for (int i = 0; i < length; i++) { 18 offset -= getWeight(invokers.get(i), invocation); 19 if (offset < 0) { 20 return invokers.get(i); 21 } 22 } 23 } 24 // If all invokers have the same weight value or totalWeight=0, return evenly. 25 return invokers.get(random.nextInt(length)); 26 }
二、RoundRobin LoadBalance:轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,好比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,长此以往,全部请求都卡在调到第二台上。
RoundRobinLoadBalance子类,用doSelect()实现了按公约后的权重设置轮训比率,
1 protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
//一个service接口的一个方法为一个key
2 String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName(); 3 int length = invokers.size(); // Number of invokers 4 int maxWeight = 0; // The maximum weight 5 int minWeight = Integer.MAX_VALUE; // The minimum weight 6 final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>(); 7 int weightSum = 0;
//轮训计算总权重值、最大权重值、最小权重值 8 for (int i = 0; i < length; i++) { 9 int weight = getWeight(invokers.get(i), invocation); 10 maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight 11 minWeight = Math.min(minWeight, weight); // Choose the minimum weight 12 if (weight > 0) { 13 invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight)); 14 weightSum += weight; 15 } 16 }
//给每一个请求方法设置一个原子Integer 17 AtomicPositiveInteger sequence = sequences.get(key); 18 if (sequence == null) { 19 sequences.putIfAbsent(key, new AtomicPositiveInteger()); 20 sequence = sequences.get(key); 21 } 22 int currentSequence = sequence.getAndIncrement(); 23 if (maxWeight > 0 && minWeight < maxWeight) { 24 int mod = currentSequence % weightSum; 25 for (int i = 0; i < maxWeight; i++) { 26 for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) { 27 final Invoker<T> k = each.getKey(); 28 final IntegerWrapper v = each.getValue(); 29 if (mod == 0 && v.getValue() > 0) { 30 return k; 31 } 32 if (v.getValue() > 0) { 33 v.decrement(); 34 mod--; 35 } 36 } 37 } 38 } 39 // Round robin 40 return invokers.get(currentSequence % length); 41 }
算法原理及实现讨论另外写了一篇博客,见:《负载均衡算法WeightedRoundRobin(加权轮询)简介及算法实现》
三、LeastActive LoadBalance:最少活跃调用数,相同活跃数的随机,活跃数指调用先后计数差,即响应一次请求所花费的时长。
使慢的提供者收到更少请求,由于越慢的提供者的调用先后计数差会越大。
算法实现逻辑为:
假如节点活跃数依次为{Node0=3ms,Node1=6ms,Node2=2ms,Node3=2ms,Node4=4ms},
(1)没有设置权重,或者权重都同样的状况下,遍历全部节点,找出节点中最小活跃数的节点,结果为{Node2=2ms,Node3=2ms};
(2)按照算法约束:相同活跃数的随机取,则从{Node2,Node3}中随机取出一个节点返回;
(3)设置了权重,且权重不同的状况下,从最小活跃数子集{Node2,Node3}中取出权重大的一个节点返回。具体实现与随机访问算法Random LoadBalance相似,构造一个考虑了权重和轮询(多个相同权重的节点轮询选择)两个因素的算法,使用总权重随机计算一个offset(偏移量),循环invokers列表,offset = offset -(当前invoker权重),即剩余权重,而后返回第一个大于offset权重的invoker。
具体算法实现以下:
1 protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
2 int length = invokers.size(); // Number of invokers
3 int leastActive = -1; // 记录最少的活跃数
4 int leastCount = 0; // 拥有最少活跃数,且活跃数相同的节点个数
5 int[] leastIndexs = new int[length]; // 最少活跃数节点索引数组(数组内节点的活跃数相同)
6 int totalWeight = 0; // The sum of weights
7 int firstWeight = 0; // Initial value, used for comparision
8 boolean sameWeight = true; // Every invoker has the same weight value?
9 for (int i = 0; i < length; i++) { 10 Invoker<T> invoker = invokers.get(i); 11 int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); //从上下文记录中取得方法的活跃数 Active number 12 int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // Weight 13 if (leastActive == -1 || active < leastActive) { // 当找到一个更小的活跃数节点时,重置变量Restart, when find a invoker having smaller least active value. 14 leastActive = active; // Record the current least active value 15 leastCount = 1; // Reset leastCount, count again based on current leastCount 16 leastIndexs[0] = i; // Reset 17 totalWeight = weight; // Reset 18 firstWeight = weight; // Record the weight the first invoker 19 sameWeight = true; // Reset, every invoker has the same weight value? 20 } else if (active == leastActive) { // If current invoker's active value equals with leaseActive, then accumulating. 21 leastIndexs[leastCount++] = i; // Record index number of this invoker 22 totalWeight += weight; // Add this invoker's weight to totalWeight. 23 // If every invoker has the same weight? 24 if (sameWeight && i > 0 25 && weight != firstWeight) { 26 sameWeight = false; 27 } 28 } 29 } 30 // assert(leastCount > 0) 31 if (leastCount == 1) { 32 // If we got exactly one invoker having the least active value, return this invoker directly. 33 return invokers.get(leastIndexs[0]); 34 } 35 if (!sameWeight && totalWeight > 0) { 36 // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
//考虑了权重和轮询(多个相同权重的节点轮询选择)两个因素的算法,使用总权重随机计算一个offset(偏移量),循环invokers列表,offset = offset -(当前invoker权重),即剩余权重,而后返回第一个大于offset权重的invoker 37 int offsetWeight = random.nextInt(totalWeight); 38 // Return a invoker based on the random value. 39 for (int i = 0; i < leastCount; i++) { 40 int leastIndex = leastIndexs[i]; 41 offsetWeight -= getWeight(invokers.get(leastIndex), invocation); 42 if (offsetWeight <= 0) 43 return invokers.get(leastIndex); 44 } 45 } 46 // If all invokers have the same weight value or totalWeight=0, return evenly. 47 return invokers.get(leastIndexs[random.nextInt(leastCount)]); 48 }
四、ConsistentHash LoadBalance:一致性哈希。适用场景为:相同参数的请求始终发送到同一个提供者。
<dubbo:parameter key="hash.arguments" value="0,1" />
<dubbo:parameter key="hash.nodes" value="320" />
具体dubbo实现以下:
1 private static final class ConsistentHashSelector<T> {
2
3 private final TreeMap<Long, Invoker<T>> virtualInvokers; 4 5 private final int replicaNumber; 6 7 private final int identityHashCode; 8 9 private final int[] argumentIndex; 10 11 ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) { 12 this.virtualInvokers = new TreeMap<Long, Invoker<T>>(); 13 this.identityHashCode = identityHashCode; 14 URL url = invokers.get(0).getUrl();
//没有设置,默认虚拟节点(分片)数160个 15 this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160); 16 String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0")); 17 argumentIndex = new int[index.length]; 18 for (int i = 0; i < index.length; i++) { 19 argumentIndex[i] = Integer.parseInt(index[i]); 20 } 21 for (Invoker<T> invoker : invokers) { 22 String address = invoker.getUrl().getAddress(); 23 for (int i = 0; i < replicaNumber / 4; i++) { 24 byte[] digest = md5(address + i); 25 for (int h = 0; h < 4; h++) { 26 long m = hash(digest, h); 27 virtualInvokers.put(m, invoker); 28 } 29 } 30 } 31 } 32 33 public Invoker<T> select(Invocation invocation) { 34 String key = toKey(invocation.getArguments()); 35 byte[] digest = md5(key); 36 return selectForKey(hash(digest, 0)); 37 } 38 39 private String toKey(Object[] args) { 40 StringBuilder buf = new StringBuilder(); 41 for (int i : argumentIndex) { 42 if (i >= 0 && i < args.length) { 43 buf.append(args[i]); 44 } 45 } 46 return buf.toString(); 47 } 48 49 private Invoker<T> selectForKey(long hash) { 50 Invoker<T> invoker; 51 Long key = hash; 52 if (!virtualInvokers.containsKey(key)) { 53 SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key); 54 if (tailMap.isEmpty()) { 55 key = virtualInvokers.firstKey(); 56 } else { 57 key = tailMap.firstKey(); 58 } 59 } 60 invoker = virtualInvokers.get(key); 61 return invoker; 62 } 63 64 private long hash(byte[] digest, int number) { 65 return (((long) (digest[3 + number * 4] & 0xFF) << 24) 66 | ((long) (digest[2 + number * 4] & 0xFF) << 16) 67 | ((long) (digest[1 + number * 4] & 0xFF) << 8) 68 | (digest[number * 4] & 0xFF)) 69 & 0xFFFFFFFFL; 70 } 71 72 private byte[] md5(String value) { 73 MessageDigest md5; 74 try { 75 md5 = MessageDigest.getInstance("MD5"); 76 } catch (NoSuchAlgorithmException e) { 77 throw new IllegalStateException(e.getMessage(), e); 78 } 79 md5.reset(); 80 byte[] bytes; 81 try { 82 bytes = value.getBytes("UTF-8"); 83 } catch (UnsupportedEncodingException e) { 84 throw new IllegalStateException(e.getMessage(), e); 85 } 86 md5.update(bytes); 87 return md5.digest(); 88 } 89 90 }
配置:
服务端服务级别:<dubbo:service interface="..." loadbalance="roundrobin" />
服务端方法级别:<dubbo:service interface="..."><dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
客户端服务级别:<dubbo:
reference
interface="..." loadbalance="roundrobin" />
客户端方法级别:<dubbo:
reference
interface="..."><dubbo:method name="..." loadbalance="roundrobin"/></dubbo:
reference
>