负载均衡是指将来自客户端的请求分配到多个服务器上进行处理,从而有效地提高系统性能、可用性和可扩展性。
常见的负载均衡算法包括轮询、加权轮询、随机、加权随机、源IP哈希和最少连接等。下面将逐一介绍它们。
轮询算法(Round Robin)
轮询算法是最简单和最常见的负载均衡算法之一,其实现思路也非常直接:按预定顺序将请求依次转发到后端服务器。通常要求服务实例是无状态的。
如下图所示:
图片
- 第一个请求首先发送到第一个服务器A;
- 第二个请求发送到下一个服务器,即第二个服务器B;
- 第三个请求发送到再下一个服务器,即第三个服务器C;对于第四个请求,由于三个服务器都已经依次发送过一次请求,所以需要从头开始,先发送到第一个服务器A;
- 依此类推……
该算法的优点是实现简单且可靠性高。然而,它没有考虑服务器的实际负载情况,可能导致一些服务器承担过重的负载,而其他服务器则处于空闲状态。
下面是轮询算法的一个简单实现代码,让你对其有个大致了解:
public class RoundRobinDemo {
// 定义一个全局计数器,每次调用时递增
private static AtomicInteger index = new AtomicInteger(-1);
// 定义一个服务器列表
private static List<String> serverList = new ArrayList<>();
public static String roundRobin() {
// 获取服务器数量
int serverCount = serverList.size();
// 确定当前请求应转发到哪个服务器
int currentServerIndex = index.incrementAndGet() % serverCount;
// 返回相应的服务器地址
return serverList.get(currentServerIndex);
}
public static void main(String[] args) {
serverList.add("Server A");
serverList.add("Server B");
serverList.add("Server C");
System.out.println(roundRobin());
System.out.println(roundRobin());
System.out.println(roundRobin());
System.out.println(roundRobin());
}
}
输出结果:
Server A
Server B
Server C
Server A
加权轮询算法(Weighted Round Robin)
加权轮询算法是在轮询算法的基础上进行改进。其思路是在服务器选择过程中,根据服务器的处理能力或负载情况为服务器分配不同的权重,以便处理能力更强或负载较轻的服务器能够接收更多请求。
如下图所示:
图片
服务器A、B和C的权重分别为4、3和1。那么服务器A将接收并处理更多请求。可以看到,前三个请求被路由到服务器A,而第四个请求被路由到服务器B。
该算法的优点是可以根据服务器的实际负载情况分配请求。然而,仍然存在服务器负载不均衡的问题,因为它仅根据权重值进行分配,而没有考虑服务器的实际负载情况。
以下是加权轮询算法的示例:
public class WeightRoundRobinDemo {
// 定义一个全局计数器,每次调用时递增
private static AtomicInteger atomicInteger = new AtomicInteger(0);
// 定义一个服务器及其对应权重值的映射
private static Map<String, Integer> serverMap = new TreeMap<>();
// 记录所有服务器的总权重
private static int totalWeight = 0;
public static void main(String[] args) {
serverMap.put("Server A", 4);
serverMap.put("Server B", 3);
serverMap.put("Server C", 1);
// 计算所有服务器的总权重
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
totalWeight += entry.getValue();
}
// 模拟四个请求
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
}
public static String weightRoundRobin() {
// 获取服务器数量
int serverCount = serverMap.size();
// 如果没有可用服务器,返回null
if (serverCount == 0) {
return null;
}
// 为避免多线程环境中并发操作导致的错误,在方法内部执行锁操作
synchronized (serverMap) {
// 确定当前请求应转发到哪个服务器
int currentServerIndex = atomicInteger.incrementAndGet() % totalWeight;
// 遍历服务器列表并根据服务器权重值选择相应地址
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
String serverAddress = entry.getKey();
Integer weight = entry.getValue();
currentServerIndex -= weight;
if (currentServerIndex < 0) {
return serverAddress;
}
}
}
return null;
}
}
输出结果:
Server A
Server A
Server A
Server B
随机算法(Random)
随机算法是一种将请求随机分配到后端服务器的负载均衡算法。该算法实现简单,但分配效果不可控,难以确保后端服务器的负载均衡。因此,随机算法通常在测试或压力测试等临时场景中用作负载均衡算法。
如下图所示:
图片
- 第一个请求随机分配到服务器A;
- 第二个和第四个请求随机分配到服务器C;
- 第三个请求随机分配到服务器B。
实现随机化方法的代码可以如下:
// 定义一个服务器列表
private static List<String> serverList = new ArrayList<>();
public static String random() {
// 获取服务器数量
int serverCount = serverList.size();
// 如果没有可用服务器,返回null
if (serverCount == 0) {
return null;
}
// 生成一个随机数
int randomIndex = new Random().nextInt(serverCount);
// 返回相应的服务器地址
return serverList.get(randomIndex);
}
加权随机算法(Weighted Random)
加权随机算法是在随机算法的基础上进行改进。其思路是在服务器选择过程中,根据服务器的处理能力或负载情况为服务器分配不同的权重,以便处理能力更强或负载较轻的服务器能够获得更多请求。
图片
加权随机算法的实现代码如下:
// 定义一个服务器及其对应权重值的映射
private static Map<String, Integer> serverMap = new ConcurrentHashMap<>();
public static String weightRandom() {
// 获取服务器数量
int serverCount = serverMap.size();
// 如果没有可用服务器,返回null
if (serverCount == 0) {
return null;
}
// 为避免多线程环境中并发操作导致的错误,在方法内部执行锁操作
synchronized (serverMap) {
// 计算所有服务器的总权重
int totalWeight = 0;
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
totalWeight += entry.getValue();
}
}
// 生成一个随机数
int randomWeight = new Random().nextInt(totalWeight);
// 遍历服务器列表并根据服务器权重值选择相应地址
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
String serverAddress = entry.getKey();
Integer weight = entry.getValue();
randomWeight -= weight;
if (randomWeight < 0) {
return serverAddress;
}
}
// 默认返回null
return null;
}
源IP哈希算法(Hash)
源IP哈希算法是一种基于请求源IP地址的负载均衡算法。其思路是通过哈希函数为每个请求的源IP地址计算一个值,然后根据该值与可用服务器总数取模的结果来确定请求应转发到哪个服务器。
换句话说,源IP哈希算法使用客户端IP地址作为哈希键。负载均衡器将哈希值映射到可用服务器之一,然后将请求发送到该服务器进行处理。如果客户端IP地址发生变化(例如,重启后重新分配了新的IP地址),那么它将被分配到另一个服务器。
如下图所示:
图片
来自客户端A的所有请求都分配到服务器A;来自客户端B的所有请求都分配到服务器C。
该算法的优点是可以避免某些客户端被重定向到不同的服务器。来自同一IP地址的请求将始终分配到同一服务器,因此可以在一定程度上提高缓存命中率等性能指标。然而,它也有一些缺点。例如,如果来自同一IP地址的请求很多,可能会导致某个服务器负载过重。
此外,由于服务器数量的变化,哈希值映射也会改变,这可能导致缓存失效,需要重新分配所有请求。
源IP哈希算法的实现代码示例如下:
// 定义一个服务器列表
private static List<String> serverList = new ArrayList<>();
public static String hash(String clientIP) {
// 获取服务器数量
int serverCount = serverList.size();
// 如果没有可用服务器,返回null
if (serverCount == 0) {
return null;
}
// 计算客户端IP地址的哈希码
int hashCode = clientIP.hashCode();
// 根据哈希码确定请求应转发到哪个服务器
int serverIndex = hashCode % serverCount;
// 返回相应的服务器地址
return serverList.get(serverIndex);
}
最少连接算法(Least Connections)
最少连接算法是一种动态调整的负载均衡算法。其思路是尽可能将请求分配到当前空闲连接数最少的后端服务器,以达到负载均衡的效果。在实现过程中,通常需要定期检测每个服务器的连接数并进行动态调整。
如下图所示:
图片
由于服务器C当前的连接数最少,所有请求都将分配给它。
最少连接算法的实现代码示例如下:
// 定义一个服务器列表
private static List<String> serverList = new ArrayList<>();
// 记录每个服务器的连接数
private static Map<String, Integer> connectionsMap = new ConcurrentHashMap<>();
public static String leastConnections() {
// 获取服务器数量
int serverCount = serverList.size();
// 如果没有可用服务器,返回null
if (serverCount == 0) {
return null;
}
// 默认选择第一个服务器
String selectedServerAddress = serverList.get(0);
// 获取第一个服务器的连接数
int minConnections = connectionsMap.getOrDefault(selectedServerAddress, 0);
// 遍历服务器列表以找到连接数最少的服务器
for (int i = 1; i < serverCount; i++) {
String serverAddress = serverList.get(i);
int connections = connectionsMap.getOrDefault(serverAddress, 0);
if (connections < minConnections) {
selectedServerAddress = serverAddress;
minConnections = connections;
}
}
// 返回连接数最少的服务器的地址
return selectedServerAddress;
}
需要注意的是,如果服务器宕机或网络链路中断,负载均衡器将需要重新计算服务器的连接数,这将延长响应时间并影响性能。
本文转载自微信公众号「程序猿技术充电站」,可以通过以下二维码关注。转载本文请联系程序猿技术充电站公众号。