Nginx常用的负载均衡模式有:
轮询(Round Robin):
- 这是Nginx默认的负载均衡算法。它将请求逐个转发到后端服务器。这种方法简单且易于实现,适用于后端服务器配置相同、处理能力相当的场景。
最少连接(Least Connections):
- Nginx会统计每个后端服务器当前的活动连接数,并将请求分配给活动连接数最少的服务器。这种方法旨在减少每个服务器的负载,提高整体性能。适用于后端服务器配置和处理能力不同、连接持续时间不均衡的场景。
IP哈希(IP Hash):
- Nginx根据客户端的IP地址进行哈希运算,并根据计算结果将请求分配给固定的后端服务器。这种算法保证了相同的客户端IP每次请求都会被分配到相同的服务器,适用于需要保持会话状态的应用。
加权轮询(Weighted Round Robin):
- 这是对轮询算法的扩展,允许管理员为后端服务器分配权重。权重较高的服务器将处理更多请求,权重较低的服务器将处理更少请求。这种方法可以根据服务器的处理能力进行负载均衡,适用于后端服务器之间配置不同、处理能力不同的情况。
今天我们使用Java来实现一个加权分流的一个案例,使用场景是有一类函数,希望每个函数执行的权重不一样,即A函数20%的概率得到执行,B函数30%的概率得到执行,C函数50%的概率得到执行。代码如下:
函数实例
/**
* 函数实例
*/
public class TargetElement {
private String element;
private Double weight;
public TargetElement(String element, Double weight) {
this.element = element;
this.weight = weight;
}
public String getElement() {
return element;
}
public void setElement(String element) {
this.element = element;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
/**
* 模拟函数执行
*/
public void exec() {
System.out.println("element:" + element + "执行了.....");
}
}
负载均衡器
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* 负载均衡器(累积权重法)
* 适用于分流元素少的情况
* 在实际应用中,如果元素数量很少,线性查找可能更简单且性能足够.
* 然而,对于大量元素,二分查找将显著提高性能,二分查找用于在累积权重数组中找到随机值所在的位置,从而提高性能.
* 由于累积权重数组是单调递增的,因此二分查找是有效的
*/
public class LoadBalancer {
/**
* 获取随机元素
* @param elements
* @return
*/
public static TargetElement getRandomElement(List<TargetElement> elements) {
double totalWeight = 0;
for (TargetElement element : elements) {
totalWeight += element.getWeight();
}
ThreadLocalRandom random = ThreadLocalRandom.current();
// random.nextDouble()生成[0.0,1.0)区间的随机double类型数值
double randomValue = random.nextDouble() * totalWeight;
// 累计权重
double cumulativeWeight = 0;
// 或使用二分法提高性能
for (TargetElement element : elements) {
cumulativeWeight += element.getWeight();
if (randomValue < cumulativeWeight) {
return element;
}
}
// 理论上这里不会被执行到,除非有浮点误差,可以返回一个默认值或抛出异常
throw new IllegalStateException("Unexpected error in proportional random selection");
}
}
单元测试
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class LoadBalancerTest {
public static void main(String[] args) {
List<TargetElement> elements = new ArrayList<>();
elements.add(new TargetElement("A", 1.0)); // A的比例为1/4 = 25%
elements.add(new TargetElement("B", 1.0)); // B的比例为1/4 = 25%
elements.add(new TargetElement("C", 2.0)); // C的比例为2/4 = 50%
int totalTrials = 1000000; // 进行大量试验以接近理论比例
// Map<TargetElement, Long> targetElementExecCount = elements.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 函数累计执行次数,key函数,value初始值为0
Map<TargetElement, Long> targetElementExecCount = elements.stream().collect(Collectors.toMap(Function.identity(), // 键映射函数,这里使用元素自身作为键
element -> 0L, // 值映射函数,这里所有值都初始化为0
(element, replacement) -> element // 合并函数,这里不需要,因为不会有键冲突
));
for (int i = 0; i < totalTrials; i++) {
TargetElement element = LoadBalancer.getRandomElement(elements);
element.exec();
Long exec_count = targetElementExecCount.get(element);
targetElementExecCount.put(element, ++exec_count);
}
System.out.println("打印各函数执行占比....");
for (TargetElement element : elements) {
System.out.println(element.getElement() + ":" + (double) targetElementExecCount.get(element) / totalTrials);
}
}
}
执行结果: