一. 概述
版本:2.7.8
解决问题
- 当有多个服务提供者时,避免请求集中到其中一个或多个,导致负载过高,服务不可用,需要做一定的负载均衡策略。
- Dubbo提供了多种均衡策略,默认为random,也就是每次加权随机调用一台服务提供者的服务。
二. Dubbo负载均衡模式
Random LoadBalance:加权随机
- 按照概率设置权重,比较均匀,并且可以动态调节提供者的权重。
RoundRobin LoadBalance:轮询
- 轮询,按公约后的权重设置轮询比率。会存在执行比较慢的服务提供者堆积请求的情况,比如一个机器执行得非常慢,但是机器没有宕机(如果宕机了,那么当前机器会从ZooKeeper 的服务列表中删除)。
- 当很多新的请求到达该机器后,由于之前的请求还没处理完,会导致新的请求被堆积,久而久之,消费者调用这台机器上的所有请求都会被阻塞。
LeastActive LoadBalance:最少活跃调用数
- 如果每个提供者的活跃数相同,则随机选择一个。
- 在每个服务提供者里维护着一个活跃数计数器,用来记录当前同时处理请求的个数,也就是并发处理任务的个数。这个值越小,说明当前服务提供者处理的速度越快或者当前机器的负载比较低,所以路由选择时就选择该活跃度最小的机器。
- 如果一个服务提供者处理速度很慢,由于堆积,同时处理的请求就比较多,也就是说活跃调用数较大,处理速度慢。这时,处理速度慢的提供者将收到更少的请求。
ConsistentHash LoadBalance一致性Hash策略
- 一致性Hash,可以保证相同参数的请求总是发到同一提供者,当某一台提供者机器宕机时,原本发往该提供者的请求,将基于虚拟节点平摊给其他提供者,这样就不会引起剧烈变动。
三. LoadBalance接口及实现类结构图
四. 源码解析
1. 接口LoadBalance
package org.apache.dubbo.rpc.cluster;
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
2. AbstractLoadBalance抽象类
- 使用模板模式执行公共业务
- 各个实现类实现抽象方法(doSelect)
- getWeight:从URL中获取各个服务提供者的权重
package org.apache.dubbo.rpc.cluster.loadbalance;
public abstract class AbstractLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
return doSelect(invokers, url, invocation);
}
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
/**
* Get the weight of the invoker's invocation which takes warmup time into account
* if the uptime is within the warmup time, the weight will be reduce proportionally
*
* @param invoker the invoker
* @param invocation the invocation of this invoker
* @return weight
*/
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);
}
}
3. RandomLoadBalance加权随机算法
RandomLoadBalance实现类
package org.apache.dubbo.rpc.cluster.loadbalance;
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
/**
* 加权随机从invokers中选择一个
* @param invokers invokers集合
* @param url URL
* @param invocation Invocation
* @param <T>
* @return 选择的invoker
*/
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
int length = invokers.size();
// Every invoker has the same weight?
boolean sameWeight = true;
// the weight of every invokers
int[] weights = new int[length];
// the first invoker's weight
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// The sum of weights
int totalWeight = firstWeight;
for (int i = 1; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// save for later use
weights[i] = weight;
// Sum
totalWeight += weight;
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 使用 ThreadLocalRandom.current().nextInt(length)从所有服务提供者里随机选择一个服务提供者进行调用。需要注意的是,这里没有使用Random而是使用了ThreadLocalRandom,这是出于性能上的考虑,因 为Random在高并发下会导致大量线程竞争同一个原子变量,导致大量线程原地自旋,从而浪费CPU资源
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
4. 其它模式(参考源码)
五. 实现自定义LoadBalance
1. 消费端使用
2. 功能:自定义负载均衡实现
3. 定义实现类
MyLoadBalance实现类
package org.apache.dubbo.rpc.cluster.loadbalance;
public class MyLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 自定义复杂均衡算法,从invokers中选择一个返回
}
}
引入SPI
- resources新增文件夹META-INF.dubbo
- 新建文件:org.apache.dubbo.rpc.cluster.LoadBalance
- 文件内容:myLoadBalance=org.apache.dubbo.rpc.cluster.loadbalance.MyLoadBalance
六. 使用自定义LoadBalance
public static void main(String[] args) {
// 1.创建服务引用对象实例
ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();
// 2.设置应用程序信息
referenceConfig.setApplication(new ApplicationConfig("dubbo-consumer"));
// 3.设置服务注册中心
referenceConfig.setRegistry(new RegistryConfig("ZKAddress"));
// 4.设置服务接口和超时时间
referenceConfig.setInterface(GreetingService.class);
referenceConfig.setTimeout(5000);
// 5.设置自定义负载均衡
referenceConfig.setLoadbalance("myLoadBalance");
// 6.设置服务分组与版本
referenceConfig.setVersion("1.0.0");
referenceConfig.setGroup("dubbo");
// 7.引用服务
GreetingService greetingService = referenceConfig.get();
}