Dubbo 负载均衡算法源码学习加使用总结

负载均衡

回顾下上篇文章Dubbo集群容错策略中介绍 AbstractClusterInvoker 代码会initLoadBalance 获取对应负载均衡算法,

具体的负载均衡逻辑 select 方法在各自Cluster实现类对应的Invoker中如 FailfastClusterInvoker

   AbstractClusterInvoker.java
  
   public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }

        List<Invoker<T>> invokers = list(invocation);
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

图片

select 实现

  AbstractClusterInvoker.java 

 protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();

        boolean sticky = invokers.get(0).getUrl()
                .getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);

        //ignore overloaded method
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            stickyInvoker = null;
        }
        //ignore concurrency problem
        if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
            if (availablecheck && stickyInvoker.isAvailable()) {
                return stickyInvoker;
            }
        }

        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);

        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

粘滞连接

再执行负载均衡算法时,首先判断请求参数中是sticky 参数,该参数为true 则开启粘滞连接,否则使用负载均衡算法选择一个服务提供者。

粘滞连接:让客户端总是向同一提供者发起调用,除非该提供者挂了才会重新选一台服务提供者进行调用。粘滞连接是有状态的,stickyInvoker记录上次使用的invoker。

使用方式如下:

<dubbo:reference id="xxxService" interface="com.xxx.XxxService" sticky="true" />

负载均衡算法核心

doSelect 真正从多个服务提供者选择一个提供者进行调用,doSelect 大致流程

  • 如果只有一个Invoker , 直接返回(别无选择)

  • 如果选择的invoker 不可用,则需要重新选择(reselect

    reselect 重新选择,排除上次selected,尽量在未选的invoker列表中重新选择。 核心都是调用loadbalance.select(reselectInvokers, getUrl(), invocation); 选择一个invoker

图片

private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck) throws RpcException {

        //Allocating one in advance, this list is certain to be used.
        List<Invoker<T>> reselectInvokers = new ArrayList<>(
                invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());

        // First, try picking a invoker not in `selected`.
        for (Invoker<T> invoker : invokers) {
            if (availablecheck && !invoker.isAvailable()) {
                continue;
            }

            if (selected == null || !selected.contains(invoker)) {
                reselectInvokers.add(invoker);
            }
        }

        if (!reselectInvokers.isEmpty()) {
            return loadbalance.select(reselectInvokers, getUrl(), invocation);
        }

        // Just pick an available invoker using loadbalance policy
        if (selected != null) {
            for (Invoker<T> invoker : selected) {
                if ((invoker.isAvailable()) // available first
                        && !reselectInvokers.contains(invoker)) {
                    reselectInvokers.add(invoker);
                }
            }
        }
        if (!reselectInvokers.isEmpty()) {
            return loadbalance.select(reselectInvokers, getUrl(), invocation);
        }

        return null;
    }

负载均衡接口 LoadBalance

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

    /**
     * select one invoker in list.
     *
     * @param invokers   invokers.
     * @param url        refer url
     * @param invocation invocation.
     * @return selected invoker.
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

常见几种实现

  • RandomLoadBalance   带权重的随机算法,可用动态调整权重,这是默认策略。

默认权重都一样,这种情况下适用于各个服务提供者性能都差不多情况,当然可以指定各个服务器的权重,使得性能好的机器权重大,性能差的机器权重小,从而使得总体性能最优。

  • RoundRobinLoadBalance  按公约后的权重设置轮询比率

如果某台服务提供者执行较慢,这台服务的请求就会逐渐堆积,堆积一定程度,后面路由到这台机器上请求无法及时处理。

当然这是教科书上说法,我们该如何避免呢?一般接口我们都会设置一个合理的超时时间,对于很慢的机器必然有大量接口超时,介入相关告警机制,及时告警。

  • LeastActive LoadBalance 在调用服务时,优先选择当前活跃调用数(正在处理的请求数)最少的服务提供者。 这种策略能够使慢的提供者收到更少请求

    因此该策略一般适用于服务提供者性能差异较大,或者某些服务提供者容易出现性能瓶颈的场景

  • ConsistentHash LoadBalance

    一致性 Hash。当服务提供者数量动态变化时,能够保持请求稳定性。

    通常情况下服务提供者不会经常变化,因此大部分情况, 相同参数的请求会被路由到同一提供者。

    当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

重要源码阅读

AbstractLoadBalance  权重计算

基于权重的负载均衡策略有 RandomLoadBalance,RoundRobinLoadBalance。计算权重时会考虑将系统启动时间,即预热时间,默认预热时间10分钟。

预热时间:如果服务提供者启动时长,达不到预热时间,对应权重需要进行折算。

calculateWarmupWeight  根据预热时间计算权重

AbstractLoadBalance.java

 int getWeight(Invoker<?> invoker, Invocation invocation) {
        int weight;
        URL url = invoker.getUrl();
        // Multiple registry scenario, load balance among multiple registries.
        if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
            weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
        } else {
            weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
            if (weight > 0) {
                long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
                if (timestamp > 0L) {
                    long uptime = System.currentTimeMillis() - timestamp;
                    if (uptime < 0) {
                        return 1;
                    }
                    int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                    if (uptime > 0 && uptime < warmup) {
                        weight = calculateWarmupWeight((int)uptime, warmup, weight);
                    }
                }
            }
        }
        return Math.max(weight, 0);
    }

使用方式

<dubbo:reference interface="..." loadbalance="roundrobin" />

<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值