常见的负载均衡算法
- 轮询法(Round Robin)
- 加权轮询(Weight Round Robin)
- 随机算法(Random)
- 源地址HASH算法(当同一IP地址客户端后端服务器列表不变时,每次都会路由到相同的服务器hashCode % serverListSize)
- 加权随机法(Weight Random)
- 最小连接数法(Least Connections)
随机(Random)算法
通过系统随机函数,根据后端服务器列表的大小值来随机选取其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果
public class RandomTest {
static List<String> list = Arrays.asList("192.168.0.101","192.168.0.102", "192.168.0.103", "192.168.0.104");
public static void main(String[] args) throws InterruptedException {
while (true){
System.err.println(get());
TimeUnit.SECONDS.sleep(1);
}
}
private static synchronized String get(){
// 拷贝服务列表避免出现由于服务器上线和下线导致的并发问题
List<String> result = new ArrayList<>();
result.addAll(list);
Random random = new Random();
int randomPos = random.nextInt(result.size());
return result.get(randomPos);
}
}
基于概率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果。因此基本可以替代轮询算法
加权随机(Weight Random)法
与加权轮询法类似,加权随机法也根据后端服务器不同的配置和负载情况,配置不同的权重。不同的是,它是按照权重来随机选取服务器的,而非顺序
/**
* 实现方法一
*/
public static String testWeightRandom() {
// 重新创建一个 map,避免出现由于服务器上线和下线导致的并发问题
Map<String, Integer> serverMap = new HashMap<>();
serverMap.putAll(serverWeightMap);
// 取得 IP 地址 list
Set<String> keySet = serverMap.keySet();
Iterator<String> it = keySet.iterator();
List<String> serverList = new ArrayList<>();
while (it.hasNext()) {
String server = it.next();
Integer weight = serverMap.get(server);
for (int i = 0; i < weight; i++) {
serverList.add(server);
}
}
Random random = new Random();
int randomPos = random.nextInt(serverList.size());
String server = serverList.get(randomPos);
return server;
}
/**
* 实现方法二
*/
public static String testWeightRandom() {
// 重新创建一个 map,避免出现由于服务器上线和下线导致的并发问题
Map<String, Integer> serverMap = new HashMap<>();
serverMap.putAll(serverWeightMap);
// 计算权重和
long weightSum = 0;
for (String key : serverMap.keySet()) {
weightSum += serverMap.get(key);
}
// 产生随机数
long random = Math.round(Math.random() * weightSum);
long weight = 0;
for (String server : serverMap.keySet()) {
weight += serverMap.get(server);
if (weight >= random) {
return server;
}
}
return serverMap.keySet().iterator().next();
}
轮询算法(Round-Robin)
轮询算法是最简单的一种负载均衡算法。它的原理是把来自用户的请求轮流分配给内部的服务器:从服务器1开始,直到服务器N,然后重新开始循环。
算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度;
轮询算法假设所有服务器的处理性能都相同,不关心每台服务器的当前连接数和响应速度。当请求服务间隔时间变化比较大时,轮询算法容易导致服务器间的负载不平衡。所以此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况
伪代码:
public class LoadBalanceTest {
static int roundPos = 0;
static List<String> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while (true){
System.err.println(loadBalanceOfRound());
TimeUnit.SECONDS.sleep(1);
}
}
private static synchronized String loadBalanceOfRound(){
if (roundPos >= list.size() ){
roundPos = 0;
}
return list.get(roundPos++);
}
static {
list.add("192.168.0.101");
list.add("192.168.0.102");
list.add("192.168.0.103");
list.add("192.168.0.104");
}
}
加权轮询算法(WeightedRound-Robin)
轮询算法并没有考虑每台服务器的处理能力,实际中可能并不是这种情况。由于每台服务器的配置、安装的业务应用等不同,其处理能力会不一样。所以加权轮询算法的原理就是:根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求;
加权轮询算法要生成一个服务器序列,该序列中包含n个服务器。n是所有服务器的权重之和。在该序列中,每个服务器的出现的次数,等于其权重值。并且,生成的序列中,服务器的分布应该尽可能的均匀。比如序列{a, a, a, a, a, b, c}中,前五个请求都会分配给服务器a,这就是一种不均匀的分配方法,更好的序列应该是:{a, a, b, a, c, a, a}。
加权轮询算法又可分为:
- 普通加权轮询算法
- 平滑的加权轮询
源地址哈希Hash算法
源地址哈希的思想是获取客户端访问的 IP 地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果边是要访问的服务器的序号。采用哈希法进行负载均衡,同一 IP 地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问
public class RandomTest {
static List<String> list = Arrays.asList("192.168.0.101", "192.168.0.102", "192.168.0.103", "192.168.0.104");
public static void main(String[] args) throws InterruptedException {
while (true) {
System.err.println(get("10.168.0.101"));
TimeUnit.SECONDS.sleep(1);
}
}
private static synchronized String get(String ip) {
// 拷贝服务列表避免出现由于服务器上线和下线导致的并发问题
List<String> result = new ArrayList<>();
result.addAll(list);
int hashCode = System.identityHashCode(ip);
int size = result.size();
int index = hashCode % size;
return result.get(index);
}
}
通过参数传入的客户端 remoteip 参数,取得它的哈希值,对服务器列表的大小取模,结果便是选用的服务器在服务器列表中的索引值。该算法保证了相同的客户端 IP 地址将会被“哈希”到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的 session 会话
hash算法中,存在以下的几个问题
1.当一台服务器宕机了或者新添加一台机器之后,这个时候hashCode % servers.size()需要重新计算hash值, 如果在缓存的环境中,所有的请求都会涌向数据库服务器,给数据库服务器带来巨大的压力,可能导致整个系统不可用,形成雪崩效应;
2 .当新增了一台性能强的机器后,利用上述的hash算法无法让,新增的性能强的服务器多承担压力;
基于上面的几个问题,提出了hash算法的改进:一致性hash算法
最小连接数(Least Connections)法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最小的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数涉及服务器连接数的汇总和感知,设计与实现比较繁琐。