dubbo负载均衡策略
前言:在上一篇博客中,介绍了zookeeper作为dubbo的注册中心是如何工作的,有一个很重要的点,我们的程序是分布式应用,服务部署在几个节点(服务器)上,当消费者调用服务时,zk返回给dubbo的是一个节点列表,但是dubbo只会选择一台服务器,那么它究竟会选择哪一台呢?这就是dubbo的负载均衡策略了,本篇博客就来聚焦dubbo的负载均衡策略。
本篇博客的目录
一:负载均衡介绍
1.1:负载均衡简介
以下是wikipedia对负载均衡的定义:
负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件
1.2:简单解释
这个概念如何理解呢?通俗点来说假如一个请求从客户端发起,比如(查询订单列表),要选择服务器进行处理,但是我们的集群环境提供了5个服务器A\B\C\D\E,每个服务器都有处理这个请求的能力,此时客户端就必须选择一个服务器来进行处理(不存在先选择A,处理一会又选择C,又跳到D).说白了就是一个选择的问题。当请求多了的话,就要考虑各服务器的负载,一共5个服务器,不可能每次都让一个服务器都来处理吧,比如把让其他服务器来分压。这就是负载均衡的优点:避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题。
二:dubbo的loadBalance接口
1.1:loadBalance
dubbo的负载均衡策略,主体向外暴露出来是一个接口,名字叫做loadBlace,位于com.alibaba.dubbo.rpc.cluster包下,很明显根据包名就可以看出它是用来管理集群的:
这个接口就一个方法,select方法的作用就是从众多的调用的List选择出一个调用者,Invoker可以理解为客户端的调用者,dubbo专门封装一个类来表示,URL就是调用者发起的URL请求链接,从这个URL中可以获取很多请求的具体信息,Invocation表示的是调用的具体过程,dubbo用这个类模拟调用具体细节过程:
1.2:AbstractLoadBlance
该接口在下面的子类都会对其进行实现。接口下是一个抽象类AbstractLoadBlance
package com.alibaba.dubbo.rpc.cluster;
public interface LoadBalance {
@Adaptive("loadbalance") <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}AbstractLoadBlance抽象类继承自LoadBalance,其中有个static方法表明它在类加载的时候就会运行,它表示的含义是计算预热加载权重,参数是uptime,这里可以理解为服务启动的时间,warmup就是预热时间,weight是权重的值,下面会对比进行详细解释:
public abstract class AbstractLoadBalance implements LoadBalance{
static int calculateWarmupWeight(int uptime, int warmup, int weight){
//
}
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url,
Invocation invocation){
//
}
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>>
invokers, URL url, Invocation invocation);
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
}
}1.2.1:select方法
抽象类方法中有个有方法体的方法select,先判断调用者组成的List是否为null,如果是null就返回null。再判断调用者的大小,如果只有一个就返回那个唯一的调用者(试想,如果服务调用另一个服务时,当服务的提供者机器只有一个,那么就可以返回那一个,因为没有选择了!)如果这些都不成立,就继续往下走,走doSelect方法:
@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:doSelect方法
该方法是抽象的,交给具体的子类去实现,由此也可以思考出一个问题就是:dubbo为什么要将一个接口首先做出一个实现抽象类,再由不同的子类去实现?原因是抽象类中的非抽象方法,再子类中都是必须要实现的,而他们子类的不同点就是具体做出选择的策略不同,将公共的逻辑提取出来放在抽象类里,子类不用写多余的代码,只用维护和实现最终要的自己的逻辑
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
1.2.3:getWeight方法
顾名思义,这个方法的含义就是获取权重,首先通过URL(URL为dubbo封装的一个实体)获取基本的权重,如果权重大于0,会获取服务启动时间,再用当前的时间-启动时间就是服务到目前为止运行了多久,因此这个upTime就可以理解为服务启动时间,再获取配置的预热时间,如果启动时间小于预热时间,就会再次调用获取权重。这个预热的方法其实dubbo针对JVM做出的一个很契合的优化,因为JVM从启动到起来都运行到最佳状态是需要一点时间的,这个时间叫做warmup,而dubbo就会对这个时间进行设定,然后等到服务运行时间和warmup相等时再计算权重,这样就可以保障服务的最佳运行状态!
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
int uptime = (int) (System.currentTimeMillis() - timestamp);
int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight;
}三:dubbo的几种负载均衡策略
3.1:整体架构图
可以看出抽象的负载均衡下的类分为4个,这4个类表示了4种负载均衡策略,分别是一致性Hash均衡算法、随机调用法、轮询法、最少活动调用法

3.2:RandomLoadBalance
随机调用负载均衡,该类实现了抽象的AbstractLoadBalance接口,重写了doSelect方法,看方法的细节就是首先遍历每个提供服务的机器,获取每个服务的权重,然后累加权重值,判断每个服务的提供者权重是否相同,如果每个调用者的权重不相同,并且每个权重大于0,那么就会根据权重的总值生成一个随机数,再用这个随机数,根据调用者的数量每次减去调用者的权重,直到计算出当前的服务提供者随机数小于0,就选择那个提供者!另外,如果每个机器的权重的都相同,那么权重就不会参与计算,直接选择随机算法生成的某一个选择,完全随机。可以看出,随机调用法,
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
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 totalWeight = 0; // The sum of weights
boolean sameWeight = true; // Every invoker has the same weight?
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;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = random.nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= getWeight(in原创不易,完成人机校验,阅读全文