负载均衡是指将来自客户端的请求分摊到多个服务器上进行处理,从而有效地提高系统性能、可用性和可扩展性。常见的负载均衡算法包括轮询法、加权轮询法、随机法、加权随机法、源地址哈希法和最小连接数法等。
在实际应用中有很多工具和框架使用了这些算法来解决服务器负载均衡的问题。下面我整理出了一些常见的工具和框架:
-
Nginx
:Nginx 是一款高性能的 Web 服务器,同时也是一款反向代理服务器。Nginx 的负载均衡模块支持多种算法,包括轮询法、加权轮询法、IP_HASH 等。 -
Apache
:Apache 是一款流行的 Web 服务器。Apache 也提供了负载均衡模块,支持多种算法,包括轮询法、加权轮询法、最小连接数法等。 -
HAProxy
:HAProxy 是一款高性能的 TCP/HTTP 负载均衡器,支持多种负载均衡算法,包括轮询法、加权轮询法、IP_HASH、最少连接数法、最短响应时间法等。 -
Spring Cloud Ribbon
:Ribbon 是一款基于 HTTP 和 TCP 客户端的负载均衡器,是 Spring Cloud 生态系统中的一员。Ribbon 支持多种负载均衡算法,包括轮询法、加权轮询法、随机法等。 -
ZooKeeper
:ZooKeeper 是一款分布式协调服务,在分布式系统中广泛应用。ZooKeeper 的客户端库 Curator 提供了一种基于 ZooKeeper 的负载均衡算法,称为 Dynamic Server List Load Balancing。
这些工具和框架都应用了负载均衡算法来实现服务器的负载均衡,它们的实现方式也各不相同,但是尽管如此,我们可以从中学习到很多有价值的经验和思路。
划重点 Java 架构师必备技能-我们要深入学习各类负载均衡算法实现方式
Java负载均衡算法也是分布式系统中的重要组成部分,用于将来自客户端的请求分配到不同的后端服务器上,以达到提高系统吞吐量、减轻服务器负担、提高系统可用性等目的。本文将介绍常见的Java负载均衡算法,轮询法、加权随机法……一次性让你了解 6 种常见负载均衡
算法。
一、轮询法(Round Robin)
轮询法是最简单、最常见的负载均衡算法之一,其实现思路也非常简单:按照事先规定的顺序依次将请求转发至后端服务器。例如,若有3台服务器,则第1个请求会被分配到第1台服务器上,第2个请求会被分配到第2台服务器上,第3个请求会被分配到第3台服务器上,第4个请求又会被分配到第1台服务器上,以此类推。
这种算法的优点是实现简单、可靠性高,但是它并没有考虑服务器的实际负载情况,导致某些服务器可能会承受过多的负载,而其他服务器则处于空闲状态。
轮询法(Round Robin)写个简单的实现代码让你感觉一下:
//定义一个全局计数器,每次调用累加
private static AtomicInteger atomicInteger = new AtomicInteger(0);
//定义服务器列表
private static List<String> serverList = new ArrayList<>();
public static String roundRobin() {
//获取服务器数量
int serverCount = serverList.size();
//获取当前请求应该转发到哪台服务器
int currentServerIndex = atomicInteger.incrementAndGet() % serverCount;
//返回对应的服务器地址
return serverList.get(currentServerIndex);
}
二、加权轮询法(Weight Round Robin)
加权轮询法是在轮询法的基础上进行改进,其思路是在服务器的选择中,根据服务器的处理能力或负载情况分配不同的权重,以使处理能力较强或负载较轻的服务器获得更多的请求。例如,若存在2台服务器,其中第1台服务器负载比较重,则应当将更多的请求分配给第2台服务器。
举个例子,如果服务器A的权重是2,服务器B的权重是1,那么在两个请求中,有一个请求会被发送到服务器A,另一个请求将被发送到服务器B。
这种算法的优点是可以根据服务器的实际负载情况来分配请求,但是还是存在服务器负载不均衡的问题,因为它只是根据权值进行分配,并没有考虑服务器的实际负载情况。
加权轮询法按自己的思路写一下如下示例:
//定义一个全局计数器,每次调用累加
private static AtomicInteger atomicInteger = new AtomicInteger(0);
//定义服务器列表及服务器权重值
private static Map<String, Integer> serverMap = new ConcurrentHashMap<>();
//记录服务器权重总和
private static int totalWeight = 0;
public static String weightRoundRobin() {
//获取服务器数量
int serverCount = serverMap.size();
//如果没有可用的服务器返回null
if (serverCount == 0) {
return null;
}
//在此处为避免多线程并发操作造成错误,在方法内部进行锁操作
synchronized (serverMap) {
//计算服务器权重总和
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
totalWeight += entry.getValue();
}
//获取当前请求应该转发到哪台服务器
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;
}
}
}
//默认返回null
return null;
}
这是源码中的一个小小的摘抄,供你观赏一下:
public class WeightRoundRobinLoadBalancer implements LoadBalancer {
private List<String> servers = new ArrayList<>();
private Map<String, Integer> weightMap = new HashMap<>();
private int currentWeightIndex = -1;
public WeightRoundRobinLoadBalancer(Map<String, Integer> servers) {
this.servers.addAll(servers.keySet());
for (String server : servers.keySet()) {
int weight = servers.get(server);
weightMap.put(server, weight);
}
}
@Override
public synchronized String chooseServer() {
int weightSum = weightMap.values().stream().reduce(Integer::sum).orElse(0);
while (true) {
currentWeightIndex = (currentWeightIndex + 1) % servers.size();
String server = servers.get(currentWeightIndex);
int weight = weightMap.get(server);
if (weight >= weightSum) {
return server;
}
weightSum -= weight;
}
}
}
三、随机法(Random)
随机法是指将请求随机分配至后端服务器的负载均衡算法。该算法实现简单,但分配效果不可控,难以保证后端服务器的负载均衡。因此,随机法通常被用作测试或压力测试等临时场景下的负载均衡算法。
随机法实现代码如下:
// 1、思路参考:-----------------------------------------------
//定义服务器列表
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);
}
// 2、源码参考:-----------------------------------------------
public class RandomLoadBalancer implements LoadBalancer {
private List<String> servers = new ArrayList<>();
public RandomLoadBalancer(List<String> servers) {
this.servers = servers;
}
@Override
public String chooseServer() {
int randomIndex = ThreadLocalRandom.current().nextInt(servers.size());
return servers.get(randomIndex);
}
}
四、加权随机法(Weight Random)
加权随机法是在随机法的基础上进行改进,其思路是在服务器的选择中,根据服务器的处理能力或负载情况分配不同的权重,以使处理能力较强或负载较轻的服务器获得更多的请求。
加权随机法实现代码如下:
// 1、思路参考:-----------------------------------------------------------
//定义服务器列表及服务器权重值
private static Map<String, Integer> serverMap = new ConcurrentHashMap<>();
//记录服务器权重总和
private static int totalWeight = 0;
public static String weightRandom() {
//获取服务器数量
int serverCount = serverMap.size();
//如果没有可用的服务器返回null
if (serverCount == 0) {
return null;
}
//在此处为避免多线程并发操作造成错误,在方法内部进行锁操作
synchronized (serverMap) {
//计算服务器权重总和
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;
}
// 2、源码参考:-----------------------------------------------------------
public class WeightRandomLoadBalancer implements LoadBalancer {
private List<String> servers = new ArrayList<>();
private Map<String, Integer> weightMap = new HashMap<>();
public WeightRandomLoadBalancer(Map<String, Integer> servers) {
this.servers.addAll(servers.keySet());
for (String server : servers.keySet()) {
int weight = servers.get(server);
weightMap.put(server, weight);
}
}
@Override
public String chooseServer() {
int weightSum = weightMap.values().stream().reduce(Integer::sum).orElse(0);
int randomWeight = ThreadLocalRandom.current().nextInt(weightSum) + 1;
for (String server : servers) {
int weight = weightMap.get(server);
if (randomWeight <= weight) {
return server;
}
randomWeight -= weight;
}
return null;
}
}
五、源地址哈希法(Hash)
源地址哈希法是一种基于请求源IP地址的负载均衡算法,其思路是将每个请求的源IP地址通过哈希函数计算出一个值,然后根据该值与可用服务器总数取模的结果来确定该请求应当转发到哪台服务器上。
换言之,源地址哈希算法就是使用客户端 IP 地址作为哈希键。负载均衡器将哈希值映射到可用服务器中的一个,然后将请求发送到这个服务器处理。如果客户端 IP 地址发生改变(比如重启后重新分配 IP 地址),那么将会被分配到其他服务器上。
这种算法的优点是可以避免某些客户端被重定向到不同的服务器,对于同一IP地址的请求,总是会被分配到同一台服务器上,因此可以在一定程度上提高缓存命中率等性能指标,但是它也有一些缺点。例如,如果有很多请求来自相同的 IP 地址,那么可能会导致某个服务器负载过高。另外,由于服务器数量的变化,哈希值映射也会发生变化,这可能会导致缓存无效,并且需要重新分配所有请求。
源地址哈希法实现代码示例如下:
// 1、思路参考:-----------------------------------------------------------
//定义服务器列表
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);
}
// 2、源码参考:-----------------------------------------------------------
public class HashLoadBalancer implements LoadBalancer {
private List<String> servers = new ArrayList<>();
public HashLoadBalancer(List<String> servers) {
this.servers = servers;
}
@Override
public String chooseServer() {
String clientIp = getClientIp();
int hashCode = Math.abs(clientIp.hashCode());
return servers.get(hashCode % servers.size());
}
private String getClientIp() {
// 获取客户端IP地址的代码省略
return "1.1.1.1";
}
}
六、最小连接数法(Least Connections)
最小连接数法是一种动态调整的负载均衡算法,其思路是尽可能地将请求分配给当前空闲连接数最少的后端服务器,以达到负载均衡的效果。在实现过程中,通常需要定期检测各个服务器的连接数并进行动态调整。
最小连接数算法是根据当前连接数来选择一个可用服务器。负载均衡器会查询可用服务器的连接数,然后选择一个连接数最小的服务器。这种算法保证了服务器不会被过度负载,并且还允许负载均衡器根据实际情况动态分配请求。
需要注意的是,如果服务器挂掉了或者网络链路中断了,那么负载均衡器就需要重新计算服务器的连接数,这将延长响应时间并且影响性能。
最小连接数法实现代码示例如下:
// 1、思路参考:-----------------------------------------------------------
//定义服务器列表
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;
}
// 2、源码参考:-----------------------------------------------------------
public class LeastConnectionsLoadBalancer implements LoadBalancer {
private List<String> servers = new ArrayList<>();
private Map<String, Integer> connectionsMap = new HashMap<>();
public LeastConnectionsLoadBalancer(List<String> servers) {
this.servers = servers;
for (String server : servers) {
connectionsMap.put(server, 0);
}
}
@Override
public synchronized String chooseServer() {
int minConnections = Integer.MAX_VALUE;
String targetServer = null;
for (String server : servers) {
int connections = connectionsMap.get(server);
if (connections < minConnections) {
minConnections = connections;
targetServer = server;
}
}
connectionsMap.put(targetServer, connectionsMap.get(targetServer) + 1);
return targetServer;
}
public void releaseConnection(String server) {
connectionsMap.put(server, connectionsMap.get(server) - 1);
}
}
以上便是常见的Java负载均衡算法,这些算法都有其自身的优缺点和适用场景。
七、小结一下
Java 架构师面临的挑战越来越大,我们需要在不断发展的技术中保持敏锐的触觉,并掌握越来越广泛的知识。而在当前互联网架构中,负载均衡算法是一个至关重要的领域。它是实现服务的高可用性和可伸缩性的重要手段。因此,Java 架构师必须深入学习各类负载均衡算法的实现方式,并且理解它们的优劣之处,以便为公司设计出更好的网络架构。
如果你想成为一名 Java 架构师或者网络工程师,那么希望你多多了解底层知识对你将大有益处。让我们一起努力吧!