Dubbo负载均衡详解

发布时间:2023-05-18

Dubbo负载均衡的几种方式

Dubbo支持以下几种负载均衡方式:

  • Random LoadBalance
  • RoundRobin LoadBalance
  • LeastActive LoadBalance
  • ConsistentHash LoadBalance
  • Forking LoadBalance
  • Failover LoadBalance
  • Failfast LoadBalance
  • Failsafe LoadBalance 具体说明如下:
  • Random LoadBalance:随机选择一个服务提供者。
  • RoundRobin LoadBalance:轮询选择服务提供者。
  • LeastActive LoadBalance:选取活跃调用数最小的服务提供者。
  • ConsistentHash LoadBalance:一致性哈希负载均衡算法。
  • Forking LoadBalance:并行调用多个服务提供者,只要有一个成功就返回。
  • Failover LoadBalance:失败自动切换到其他服务提供者,通常用于非幂等性的远程调用。
  • Failfast LoadBalance:失败快速失败,通常用于幂等性的远程调用。
  • Failsafe LoadBalance:失败安全,总是调用所有服务提供者,通常用于写入审计日志等操作。

Dubbo负载均衡策略

Dubbo负载均衡有两种策略,即随机和权重。下面给出Dubbo配置文件中的负载均衡配置示例:

<dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" loadbalance="random" />

这里设置负载均衡策略为随机,也可以设置为权重策略,示例如下:

<dubbo:reference interface="com.alibaba.dubbo.demo.DemoService">
    <dubbo:method name="sayHello" loadbalance="roundrobin" />
</dubbo:reference>

Dubbo负载均衡源码

Dubbo负载均衡的源码在Dubbo的GitHub仓库中,查看地址为:
https://github.com/apache/dubbo/tree/master/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance

Ribbon负载均衡

Ribbon是Netflix开源的负载均衡组件,Dubbo可以集成Ribbon实现负载均衡。Ribbon的负载均衡算法可以自定义实现,Dubbo使用的是RoundRobin策略,示例配置如下:

<dubbo:provider protocol="dubbo" loadbalance="roundrobin" server="netty4" />

Dubbo协议

Dubbo协议是Dubbo RPC通信协议的实现,其主要特点如下:

  • Dubbo协议采用NIO异步通信、多路复用技术,可以提高吞吐量。
  • Dubbo协议支持长连接,减少连接建立的开销。
  • Dubbo协议支持多协议,可以在dubbo://rest://等多种协议间切换。

Dubbo接口

Dubbo接口是Dubbo服务的提供方和消费方进行通信的契约。Dubbo服务提供者和消费者都必须引用相同的接口。 示例代码:

public interface DemoService {
    String sayHello(String name);
}

Dubbo负载均衡算法

Dubbo负载均衡算法有多种实现方式,例如RandomLoadBalanceRoundRobinLoadBalance等。下面给出RoundRobinLoadBalance的算法简述:

  1. 将所有可用服务提供者存入一个有序列表list
  2. 如果服务提供者列表为空,则返回空;如果只有一个服务提供者,则返回该服务提供者。
  3. 如果服务提供者列表不为空,则获取下一个服务提供者并返回。 其Java代码实现如下:
public class RoundRobinLoadBalance extends AbstractLoadBalance {
    private final AtomicPositiveInteger sequences = new AtomicPositiveInteger();
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // 调用的服务数
        int maxWeight = 0;
        int minWeight = Integer.MAX_VALUE;
        final LinkedHashMap<Invoker<T>, IntegerWrapper> invokersWightMap = new LinkedHashMap<>();
        int weightSum = 0;
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), "weight", 100);
            invokersWightMap.put(invoker, new IntegerWrapper(weight));
            weightSum += weight;
            maxWeight = Math.max(maxWeight, weight);
            minWeight = Math.min(minWeight, weight);
        }
        // 不同的权重转换为相同的权重
        int weightDiff = maxWeight - minWeight;
        if (weightDiff > 0) {
            for (Map.Entry<Invoker<T>, IntegerWrapper> entry : invokersWightMap.entrySet()) {
                entry.setValue(new IntegerWrapper(entry.getValue().get() - minWeight));
            }
            weightSum -= minWeight * length;
        }
        // 循环列表进行轮转
        int sequence = sequences.getAndIncrement();
        if (weightSum > 0 && sequence < Integer.MAX_VALUE) {
            int currentWeight = -1;
            Invoker<T> selectedInvoker = null;
            int currentSequence = -1;
            for (int i = 0; i < length; i++) {
                sequence++;
                if (sequence > weightSum) {
                    sequence = weightSum;
                }
                for (Map.Entry<Invoker<T>, IntegerWrapper> entry : invokersWightMap.entrySet()) {
                    selectedInvoker = entry.getKey();
                    currentWeight = entry.getValue().get();
                    currentSequence += currentWeight;
                    if (currentSequence >= sequence) {
                        return selectedInvoker;
                    }
                }
                currentSequence = 0;
            }
        }
        // 相同权重,随机选择
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
    static class IntegerWrapper {
        private int value;
        public IntegerWrapper(int value) {
            this.value = value;
        }
        public int get() {
            return value;
        }
        public void set(int value) {
            this.value = value;
        }
    }
}

Dubbo负载均衡原理

Dubbo负载均衡实现的原理如下:

  1. 获取所有可用服务提供者的列表,计算出他们的权重。
  2. 根据负载均衡策略选择一个服务提供者。
  3. 如果服务提供者无法响应,返回到第2步重新选择。
  4. 返回选取的服务提供者。

Dubbo负载均衡在哪层

Dubbo负载均衡在服务层,属于网络通信层。

Dubbo负载均衡策略如何配置选取

Dubbo负载均衡策略可以在Dubbo配置文件中进行配置,例如:

<dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" loadbalance="random" />

也可以通过代码进行动态设置,例如:

public class DemoConsumer {
    public static void main(String[] args) {
        ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
        reference.setInterface(DemoService.class);
        reference.setLoadbalance("roundrobin");
        DemoService demoService = reference.get();
        String hello = demoService.sayHello("world");
        System.out.println(hello);
    }
}