Dubbo的负载均衡算法

目录

  • 1 简介

    • 1.1 自适应默认算法
    • 1.2 抽象基类
      • 1.2.1 选择Invoker
      • 1.2.2 计算权重
  • 2 负载均衡算法实现
    • 2.1 加权随机算法
    • 2.2 最小活跃数算法
    • 2.3 一致性哈希
    • 2.4 加权轮询算法

1 简介

Dubbo提供了4种负载均衡机制:

  • 权重随机算法:RandomLoadBalance
  • 最少活跃调用数算法:LeastActiveLoadBalance
  • 一致性哈希算法:ConsistentHashLoadBalance
  • 加权轮询算法:RoundRobinLoadBalance

Dubbo的负载均衡算法均实现自LoadBalance接口,其类图结构如下:

1.1 自适应默认算法

Dubbo的负载均衡算法利用Dubbo的自适应机制,默认的实现为RandomLoadBalance(即:权重随机算法),接口如下:

// 默认为RandomLoadBalance算法
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
    // 该方法为自适应方法
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

1.2 抽象基类

1.2.1 选择Invoker

抽象基类针对select()方法判断invokers集合的数量,如果集合中只有一个Invoker,则无需使用算法,直接返回即可:

@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    if (invokers == null || invokers.isEmpty())
        return null;
    // 如果只有一个元素,无需负载均衡算法
    if (invokers.size() == 1)
        return invokers.get(0);
    // 负载均衡算法:该方法为抽象方法,由子类实现
    return doSelect(invokers, url, invocation);
}

1.2.2 计算权重

抽象基类除了对select()方法进行了重写,还封装了服务提供者的权重计算逻辑。Dubbo的权重计算,考虑到服务预热(服务器启动后,以低功率方式运行一段时间,当服务器运行效率逐渐达到最佳状态):

若当前的服务器运行时长小于服务预热时间,对服务器降权,让服务器的权重降低。

protected int getWeight(Invoker<?> invoker, Invocation invocation) {
    // 从URL中获取服务器Invoker的权重信息
    int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
    if (weight > 0) {
        // 获取服务器Invoker的启动时间戳
        long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
        if (timestamp > 0L) {
            int uptime = (int) (System.currentTimeMillis() - timestamp);
            // 获取服务器Invoker的预热时长(默认10分钟)
            int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
            if (uptime > 0 && uptime < warmup) {
                weight = calculateWarmupWeight(uptime, warmup, weight);
            }
        }
    }
    return weight;
}
// (uptime / warmup) * weight,即:(启动时长 / 预热时长) * 原权重值
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
    int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
    return ww < 1 ? 1 : (ww > weight ? weight : ww);
}

2 负载均衡算法实现

2.1 加权随机算法

该算法思想简单:假如有服务器:

  • A,权重weight=2
  • B,权重weight=3
  • C,权重weight=5

这些服务器totalWeight = AWeight + BWeight + CWeight = 10。这样生成10以内的随机数

  • [0, 2):属于服务器A
  • [2, 5):属于服务器B
  • [5, 10):属于服务器C

加权随机算法由RandomLoadBalance,其核心源码实现如下:

private final Random random = new Random();

@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size();
    // 总权重值
    int totalWeight = 0;
    // 是否所有的Invoker服务器的权重值都相等,若都相等,则在Invoker列表中随机选中一个即可
    boolean sameWeight = true;
    for (int i = 0; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        totalWeight += weight; // Sum
        if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {
            sameWeight = false;
        }
    }
    // 生成totalWeight以内的随机数offset,然后该offset挨个减Invoker的权重,一旦小于0,就选中当前的Invoker
    if (totalWeight > 0 && !sameWeight) {
        int offset = random.nextInt(totalWeight);
        for (int i = 0; i < length; i++) {
            offset -= getWeight(invokers.get(i), invocation);
            if (offset < 0) {
                return invokers.get(i);
            }
        }
    }
    return invokers.get(random.nextInt(length));
}

2.2 最小活跃数算法

活跃调用数越小,表明该Provider的效率越高,单位时间内可以处理更多的请求。此时如果有新的请求,应该将请求优先分配给该ProviderDubbo中,每个Provider都会保持一个active,表示该Provider的活跃数:

没收到一个请求,active的值加1,请求处理完成后,active的值减1。

Dubbo使用LeastActiveLoadBalance实现最小活跃数算法,LeastActiveLoadBalance引入了权重,若多个Provider的活跃数相同

则权重较高的Provider被选中的几率更大
若权重相同,则随机选取一个Provider

该算法的核心思想:

  • 循环遍历所有Invoker,找出活跃数最小的所有Invoker
  • 如果有多个Invoker具有多个相同的最小活跃数,此时需要记录这些Invoker。Dubbo使用额外的int数组,来记录这些Invoker在原invokers列表中的索引位置,这种情况下,则需要依据随机权重算法,最终决定该选择的Invoker
private final Random random = new Random();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size(); // Number of invokers
    // “最小活跃数”的值
    int leastActive = -1;
    // 具有相同“最小活跃数”的Invoker(即Provider)的数量
    int leastCount = 0;
    // “最小活跃数”的Invoker可能不止一个,因此需要记录所有具有“最小活跃数”的Invoker在invokers列表中的索引位置
    int[] leastIndexs = new int[length];
    // 记录所有“最小活跃数”的Invoker总的权重值
    int totalWeight = 0;
    // 记录第一个“最小活跃数”的Invoker的权重值
    int firstWeight = 0;
    // 所有的“最小活跃数”的Invoker的权重值是否都相等
    boolean sameWeight = true;
    for (int i = 0; i < length; i++) {
        Invoker<T> invoker = invokers.get(i);
        // 活跃数
        int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
        // 权重
        int afterWarmup = getWeight(invoker, invocation);
        if (leastActive == -1 || active < leastActive) {
            leastActive = active;
            leastCount = 1;
            leastIndexs[0] = i;
            totalWeight = afterWarmup;
            firstWeight = afterWarmup;
            sameWeight = true;
        } else if (active == leastActive) {
            leastIndexs[leastCount++] = i;
            totalWeight += afterWarmup;
            if (sameWeight && i > 0 && afterWarmup != firstWeight) {
                sameWeight = false;
            }
        }
    }
    if (leastCount == 1) {
        return invokers.get(leastIndexs[0]);
    }
    if (!sameWeight && totalWeight > 0) {
        int offsetWeight = random.nextInt(totalWeight) + 1;
        for (int i = 0; i < leastCount; i++) {
            int leastIndex = leastIndexs[i];
            offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
            if (offsetWeight <= 0)
                return invokers.get(leastIndex);
        }
    }
    return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}

2.3 一致性哈希

该算法起初用于缓存系统的负载均衡,算法过程如下:

① 根据IP或其他信息为缓存节点生成一个hash值,将该hash值映射到[0, 2^32-1]的圆环中。
② 当查询或写入缓存时,为缓存的key生成一个hash值,查找第一个大于等于该hash值的缓存节点,以应用该缓存
③ 若当前节点挂了,则依然可以计算得到一个新的缓存节点

Dubbo通过引入虚拟节点,让所有的Invoker在圆环上分散开,避免数据倾斜(由于节点不够分散,导致大量请求落在同一个节点,而其他节点只会接受到少量请求)。Dubbo对一个Invoker默认使用160个虚拟节点:

如:Invoker1-1, invoker1-2, invoker1-3... invoker1-160

Dubbo使用ConsistentHashLoadBalance来实现一致性哈希算法,其内部定义ConsistentHashSelector内部类完成节点选择。Dubbo中使用方法的入参数值进行哈希,来选择落点到哪一个Invoker上。

public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    // key:group+version+methodName,即调用方法的完整名称
    // value:ConsistentHashSelector,利用该类完成节点选择
    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors =  new ConcurrentHashMap<>();

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 获取调用方法名
        String methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;

        // 获取invokers的hashcode
        int identityHashCode = System.identityHashCode(invokers);
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        // 如果 invokers 是一个新的 List 对象,意味着服务提供者数量发生了变化
        // 此时 selector.identityHashCode != identityHashCode 条件成立
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 创建新的 ConsistentHashSelector
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }

        // 利用选择器,选择节点
        return selector.select(invocation);
    }
    private static final class ConsistentHashSelector<T> {...}
}

ConsistentHashLoadBalance#doSelect()方法主要是做前置工作,每个方法的一致性哈希选择具体由ConsistentHashSelector来实现:

private static final class ConsistentHashSelector<T> {
    // 使用TreeMap存储Invoker虚拟节点,保证查询效率
    private final TreeMap<Long, Invoker<T>> virtualInvokers;
    // 虚拟节点数量
    private final int replicaNumber;
    // invokers的hashCode值
    private final int identityHashCode;
    // 参数索引数组
    private final int[] argumentIndex;

    ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
        this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
        this.identityHashCode = identityHashCode;
        URL url = invokers.get(0).getUrl();
        this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
        // 获取参与 hash 计算的参数下标值,默认对方法的第一个参数进行 hash 运算
        String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
        argumentIndex = new int[index.length];
        for (int i = 0; i < index.length; i++) {
            argumentIndex[i] = Integer.parseInt(index[i]);
        }
        // 如下方法创建虚拟节点,每个invoker生成replicaNumber个虚拟结点,
        for (Invoker<T> invoker : invokers) {
            String address = invoker.getUrl().getAddress();
            for (int i = 0; i < replicaNumber / 4; i++) {
                byte[] digest = md5(address + i);  // digest是一个长度为16的数组
                // 对 digest 部分字节进行4次 hash 运算,得到四个不同的 long 型正整数
                for (int h = 0; h < 4; h++) {
                    // 根据h的值,对digest进行位运算。h=0,则0~3字节位运算;h=1,则4~7字节位运算。
                    long m = hash(digest, h);
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }
}    

选择节点,使用TreeMap#tailMap方法,可以获取到TreeMap中大于Key的子Map,子Map中第一个Entry即为选择的Invoker。因为TreeMap不是环形结构,因此如果没有取到任何值,则认为是第一个Key的值。如下:

public Invoker<T> select(Invocation invocation) {
    // 根据argumentIndex,决定方法的哪几个参数值作为Key
    String key = toKey(invocation.getArguments());
    byte[] digest = md5(key);
    return selectForKey(hash(digest, 0));
}
private Invoker<T> selectForKey(long hash) {
    /*
        TreeMap<Integer, String> tree_map = new TreeMap<Integer, String>();
        tree_map.put(10, "Geeks");
        tree_map.put(15, "4");
        tree_map.put(20, "Geeks");
        tree_map.put(25, "Welcomes");
        tree_map.put(30, "You");
        System.out.println("The tailMap is " + tree_map.tailMap(15));
        // The tailMap is {15=4, 20=Geeks, 25=Welcomes, 30=You}
    */
    Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
    if (entry == null) {
        entry = virtualInvokers.firstEntry();
    }
    return entry.getValue();
}

private String toKey(Object[] args) {
    StringBuilder buf = new StringBuilder();
    for (int i : argumentIndex) {
        if (i >= 0 && i < args.length) {
            buf.append(args[i]);
        }
    }
    return buf.toString();
}

2.4 加权轮询算法

待补充

原文地址:https://www.cnblogs.com/wolfdriver/p/10660601.html

时间: 2024-10-28 10:05:06

Dubbo的负载均衡算法的相关文章

Dubbo加权轮询负载均衡算法应用之推荐产品

Dubbo加权轮询负载均衡算法,核心点:weight(固定的权重),currentWeight(当前权重,动态变化的),算法逻辑:轮询服务提供者(每个服务提供者都有weight和currentWeight),currentWeight增加weight,取最大的currentWeight,然后取对应的服务提供者,最后将取到的服务提供者的currentWeight减去总的权重(所有服务提供者的weight之和).示例如下: 服务器 [A, B, C] 对应权重 [5, 1, 1] ,现在有7个请求依

Dubbo负载均衡算法

[图文并茂]一文讲透Dubbo负载均衡之最小活跃数算法  看源码思路 1.遍历 invokers 列表,寻找活跃数最小的 Invoker 2.如果有多个 Invoker 具有相同的最小活跃数,此时记录下这些 Invoker 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等 3.如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可 4.如果有多个 Invoker 具有最小活跃数,且它们的权重不相等,此时处理方式和 RandomLoadB

互联网研发中负载均衡算法一点探索

负载均衡在线上服务中有着很重要作用,因为一台web服务比如tomcat,能够处理qps(每秒处理请求数) 是有限的.那么就需要有有前端负载均衡服务将大的流量分发为多个后端服务进行处理. 负载均衡产品有硬件F5.有软件,早之前使用Apache较多,目前是使用Nginx多,Nginx架构实现简洁优 雅性能高.LVS.HAProxy是著名软负载工具.说到LVS是由原淘宝章文蒿(目前在滴滴公司)博士领导开发, 是到目前为止Linux内核中网络核心部分,也是国人开Linux内核最高贡献,章博士在国内技术圈

负载均衡算法 - 基本实现

? 最近在比赛一个项目 , 是给Dubbo写一个负载均衡接口 , 其实dubbo已经实现了下面四种, 所以他做的不是这个单面负载均衡, 需要做双向负载均衡 , 负载均衡的权重取决于服务端,所以有些时候我们不知道如何计算权重, 权重受到很多因素影响 ,所以就需要动态考虑了. ? Dubbo 提供了4种负载均衡实现,分别是基于权重随机算法的 RandomLoadBalance.基于最少活跃调用数算法的 LeastActiveLoadBalance.基于 hash 一致性的 ConsistentHas

Citrix Netscaler负载均衡算法

众所周知,作为新一代应用交付产品的Citrix Netscaler具有业内领先的数据控制.应用交付的能力,然而作为根本内容之一的ADC功能,如果不具备强大的.多元化的均衡算法是不可能适应如此众多的应用场景,更无法做到好的应用交付产品.因此我们在此讨论一下比较常用的负载均衡算法就很有必要. 目前最新版本的Netscaler支持17种均衡算法,目前先讨论最常用的12种 1.轮询算法(Round Robin) 当NetScaler 使用轮询的负载均衡算法时,它会将来自客户端的请求轮流分配给后台中的服务

F5负载均衡算法及基本原理

原文:Intro to Load Balancing for Developers – The Algorithms 转载:http://blog.gesha.net/archives/205/ posted on Tuesday, March 31, 2009 11:02 PM Random: This load balancing method randomly distributes load across the servers available, picking one via ra

负载均衡算法及手段

负载均衡器 可以是专用设备,也可以是在通用服务器上运行的应用程序. 分散请求到拥有相同内容或提供相同服务的服务器. 专用设备一般只有以太网接口,可以说是多层交换机的一种. 负载均衡器一般会被分配虚拟IP地址,所有来自客户端的请求都是针对虚拟IP地址完成的.负载均衡器通过负载均衡算法将来自客户端的请求转发到服务器的实际IP地址上. 负载均衡算法 private Map<String,Integer> serverMap = new HashMap<String,Integer>(){

几种简单的负载均衡算法及其Java代码实现

什么是负载均衡 负载均衡,英文名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求.负载均衡能够平均分配客户请求到服务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能. 负载均衡分为软件负载均衡和硬件负载均衡,前者的代

说一说负载均衡算法

    负载均衡算法可以分为两类:静态负载均衡算法,基于服务器的容量,就是高配置的服务器比低配置的服务器分配更多的请求:动态负载均衡算法根据服务器的状况,比如通过监控可以得到平均响应时间,或者CPU占用率,内存占用率按照某种方式进行计算,作为权重值 静态负载均衡算法 随机(Random):利用随机数生成算法选取一个服务器,然后把连接发送给他.虽然许多负载产品都支持该算法,但是它的有效性一直受到质疑,除非把服务器的可运行时间看的很重. 轮询(Round Robin):按顺序把每个新的连接请求分配给