前言
一、学习目的与目标
1.从Dubbo应用层级较小的角度去理解软件负载均衡的几种通用的负载均衡算法与实现
2.简单了解大神的优化思路
强调:这是一篇笔记,可能存在纰漏,如发现,请勿喷,烦请指正,谢谢。强烈推荐官方文档:
Dubbo官方文档-负载均衡
二、负载均衡概述
关于负载均衡,这里简单聊聊对这个概念的理解。这个概念其实是对流量通过一定的手段进行重新分配,避免单一机器直接被较为集中的流量击穿,或者避免部分机器由于没有合理分配流量导致过于空闲。
一般来说,负载均衡分为硬件级的负载均衡或者软件负载均衡,对于开发人员来说,我们基本都是面向软件层的负载均衡。
硬件层级的负载均衡解决方案是在服务器和外网架设专门的负载均衡设备,由这些设备进行专门的流量分发。一些比较主流的产品比如:F5、思科等。这里并不打算展开来讨论。可能会在后续会专门开一篇文章来讨论。
软件层级的负载均衡解决方案比较流行的有LVS、Nginx、HAProxy等,对于这些方案本文也并不打算扩展开来,只是作为简单地了解。
对于Dubbo的负载均衡,我们可以先看下Dubbo本身的负载均衡的类图
在Dubbo的负载均衡相关的代码中,可以看到它主要提供了四种负载均衡算法:
- 基于权重随机算法
- 基于一致性哈希算法
- 基于随机算法
- 基于加权轮训算法
除了了解这四种算法,我们应该还需要知道几个问题:
- Dubbo是通过怎么样一个手段去统计这些流量的?
- 统计完流量后,如何保证下一个请求按照以上这几个算法算出来的结果分发到对应的机器去?
三、DUbbo负载均衡的四种算法
3.0 基于权重随机算法的 RandomLoadBalance
3.0.1 实现原理
该算法是Dubbo负载均衡的缺省算法。比较简单易懂。
假设有三台服务器[A,B,C],我们设置权重分别是5,3,2,权重总和为10,对于这三台服务器,可以认为A服务器在[0,5),B服务器在[5,8),C服务器在[8,10],此时提供一个随机数生成器(此处认为该随机数生成器算法足够好,产生的数据足够随机),那么让其生成一个[0,10)的随机数,这个数据将会落在其中一个区段,此时就将请求分发到对应的服务器。
3.0.2 源码解读
package org.apache.dubbo.rpc.cluster.loadbalance;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* random load balance.
*/
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Dubbo多个节点都会包装成invoker模型的形式
int length = invokers.size();
boolean sameWeight = true;
// 每一个invoker的权重
int[] weights = new int[length];
// 尝试获取第一个invoker的权重
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) {
// 基于总权重,随机一个数据出来
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 求随机出来的数据落在哪个区间段
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
//如果权重相等,则随机一个数出来
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
/**
*
* 获取invoker的权重,如果部分耗费在warm过程中时,这部分权重要适当地减少
*
* @param invoker the invoker
* @param invocation the invocation of this invoker
* @return weight
*/
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter(REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
int uptime = (int) (System.currentTimeMillis() - timestamp);
int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight >= 0 ? weight : 0;
}
/**
*
* 根据运行部分与warm up时间占比来计算权重
*
* @param uptime the uptime in milliseconds
* @param warmup the warmup time in milliseconds
* @param weight the weight of an invoker
* @return weight which takes warmup into account
*/
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);
}
关于WarmUp的理解
这里值得一提的时关于WarmUp的理解。系统在刚开始启动的时候都会有一段WarmUp的过程,这过程主要对一些线程池、数据库连接池等等的初始化,这些连接的创建、建立连接是十分消耗性能。另外本身JVM也有自己的预热策略。
如果对于这部分机器在处于服务预热的过程中就加以较大的流量&#