目录

  • 前言
  • 基本的负载算法
  • 平滑加权轮询算法
  • 一致性哈希算法
  • 最小活跃数算法
  • 最优响应算法
  • 总结

前言

负载均衡这个概念,几乎在所有支持高可用的技术栈中都存在,例如微服务、分库分表、各大中间件(MQ、Redis、MyCat、Nginx、ES)等,也包括云计算、云调度、大数据中也是炙手可热的词汇。

负载均衡策略主要分为静态与动态两大类:

  • 静态调度算法:指配置后只会依据配置好的策略进行请求分发的算法。
  • 动态调度算法:指配置后会根据线上情况(网络/CPU 负载/磁盘 IO 等)来分发请求。

但负载均衡算法数量并不少,本篇主要对于一些常用且高效的负载策略进行剖析。

基本的负载算法

如果聊到最基本的负载均衡算法,那么相信大家多少都有了解,例如:轮询、随机、权重等这类算法。特点就在于实现简单,先来快速过一遍基本的算法实现。

| 轮询算法

轮询算法是最为简单、也最为常见的算法,也是大多数集群情况下的默认调度算法,这种算法会按照配置的服务器列表,按照顺序依次分发请求,所有服务器都分发一遍后,又会回到第一台服务器循环该步骤。

Java 代码实现如下:

// 服务类:主要用于保存配置的所有节点
public class Servers {

// 模拟配置的集群节点
public static List<String> SERVERS = Arrays.asList(
"44.120.110.001:8080",
"44.120.110.002:8081",
"44.120.110.003:8082",
"44.120.110.004:8083",
"44.120.110.005:8084"
);
}

// 轮询策略类:实现基本的轮询算法
public class RoundRobin{
// 用于记录当前请求的序列号
private static AtomicInteger requestIndex = new AtomicInteger(0);

// 从集群节点中选取一个节点处理请求
public static String getServer(){
// 用请求序列号取余集群节点数量,求得本次处理请求的节点下标
int index = requestIndex.get() % Servers.SERVERS.size();
// 从服务器列表中获取具体的节点IP地址信息
String server = Servers.SERVERS.get(index);
// 自增一次请求序列号,方便下个请求计算
requestIndex.incrementAndGet();
// 返回获取到的服务器IP地址
return server;
}
}

// 测试类:测试轮询算法
public class Test{
public static void main(String[] args){
// 使用for循环简单模拟10个客户端请求
for (int i = 1; i <= 10; i++){
System.out.println("第"+ i + "个请求:" + RoundRobin.getServer());
}
}
}

/******输出结果*******/1个请求:44.120.110.001:80802个请求:44.120.110.002:80813个请求:44.120.110.003:80824个请求:44.120.110.004:80835个请求:44.120.110.005:80846个请求:44.120.110.001:80807个请求:44.120.110.002:80818个请求:44.120.110.003:80829个请求:44.120.110.004:808310个请求:44.120.110.005:8084
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.

上述案例中,整个算法的实现尤为简单,就是通过一个原子计数器记录当前请求的序列号,然后直接通过 % 集群中的服务器节点总数,最终得到一个具体的下标值,再通过这个下标值,从服务器 IP 列表中获取一个具体的 IP 地址。

轮询算法的优势:

  • 算法实现简单,请求分发效率够高。
  • 能够将所有请求均摊到集群中的每个节点上。
  • 易于后期弹性伸缩,业务增长时可以拓展节点,业务萎靡时可以缩减节点。

轮询算法的劣势:

  • 对于不同配置的服务器无法合理照顾,无法将高配置的服务器性能发挥出来。
  • 由于请求分发时,是基于请求序列号来实现的,所以无法保证同一客户端的请求都是由同一节点处理的,因此需要通过 session 记录状态时,无法确保其一致性。

轮询算法的应用场景:

  • 集群中所有节点硬件配置都是相同的情况。
  • 只读不写,无需保持状态的情景。

| 随机算法

随机算法的实现也非常简单,也就是当客户端请求到来时,每次都会从已配置的服务器列表中随机抽取一个节点处理。

实现如下:

// 随机策略类:随机抽取集群中的一个节点处理请求
public class Random {
// 随机数产生器,用于产生随机因子
static java.util.Random random = new java.util.Random();

public static String getServer(){
// 从已配置的服务器列表中,随机抽取一个节点处理请求
return Servers.SERVERS.get(random.nextInt(Servers.SERVERS.size()));
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

上述该算法的实现,非常明了,通过 java.util 包中自带的 Random 随机数产生器,从服务器列表中随机抽取一个节点处理请求,该算法的结果也不测试了,大家估计一眼就能看明白。

随机算法的优势:个人看来该算法单独使用的意义并不大,一般会配合下面要讲的权重策略协同使用。

随机算法的劣势:

  • 无法合理地将请求均摊到每台服务器上。
  • 由于处理请求的目标服务器不明确,因此也无法满足需要记录状态的请求。
  • 能够在一定程度上发挥出高配置的机器性能,但充满不确定因素。