Java 实现按比例分流执行

发布于:2025-02-10 ⋅ 阅读:(141) ⋅ 点赞:(0)

Nginx常用的负载均衡模式有:

  1. 轮询(Round Robin)‌:

    • 这是Nginx默认的负载均衡算法。它将请求逐个转发到后端服务器。这种方法简单且易于实现,适用于后端服务器配置相同、处理能力相当的场景。
  2. 最少连接(Least Connections)‌:

    • Nginx会统计每个后端服务器当前的活动连接数,并将请求分配给活动连接数最少的服务器。这种方法旨在减少每个服务器的负载,提高整体性能。适用于后端服务器配置和处理能力不同、连接持续时间不均衡的场景。
  3. IP哈希(IP Hash)‌:

    • Nginx根据客户端的IP地址进行哈希运算,并根据计算结果将请求分配给固定的后端服务器。这种算法保证了相同的客户端IP每次请求都会被分配到相同的服务器,适用于需要保持会话状态的应用。
  4. 加权轮询(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);
        }
    }
}

执行结果: