java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java负载均衡算法

从零带你手写Java七种负载均衡算法实现方案

作者:大黄评测

在分布式系统,微服务架构以及高并发场景中,负载均衡是一项至关重要的技术本文将带你纯手写实现七种常见的负载均衡算法,全部使用 Java 编写,不依赖任何第三方框架,帮助你深入理解其核心原理与适用场景

在分布式系统、微服务架构以及高并发场景中,负载均衡(Load Balancing) 是一项至关重要的技术。它能够将请求合理地分发到多个服务节点上,从而提升系统整体的吞吐量、可用性和容错能力。

本文将带你纯手撸实现七种常见的负载均衡算法,全部使用 Java 编写,不依赖任何第三方框架,帮助你深入理解其核心原理与适用场景。

准备工作

首先定义一个通用的服务节点接口:

public class Server {
    private String host;
    private int port;
    private int weight; // 权重,用于加权类算法

    public Server(String host, int port) {
        this(host, port, 1);
    }

    public Server(String ​​host, int port, int weight) {
        this.host = host;
        this.port = port;
        this.weight = weight;
    }

    // getters & setters
    public String getHost() { return host; }
    public int getPort() { return port; }
    public int getWeight() { return weight; }
    public void setWeight(int weight) { this.weight = weight; }

    @Override
    public String toString() {
        return host + ":" + port + "(w=" + weight + ")";
    }
}

所有算法都将实现以下接口:

public interface LoadBalancer {
    Server select(List<Server> servers);
}

1. 随机(Random)

最简单的策略:从可用节点中随机选择一个。

public class RandomLoadBalancer implements LoadBalancer {
    private final Random random = new Random();

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty()) return null;
        int index = random.nextInt(servers.size());
        return servers.get(index);
    }
}

优点:简单、无状态

缺点:无法保证请求分布均匀(尤其在短时间窗口内)

2. 轮询(Round Robin)

按顺序依次选择节点,循环往复。

public class RoundRobinLoadBalancer implements LoadBalancer {
    private AtomicInteger index = new AtomicInteger(0);

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty()) return null;
        int i = index.getAndIncrement() % servers.size();
        // 处理负数(虽然 unlikely)
        if (i < 0) i += servers.size();
        return servers.get(i);
    }
}

优点:请求分布均匀

缺点:未考虑服务器性能差异

3. 加权轮询(Weighted Round Robin)

为每个节点分配权重,高权重节点被选中的频率更高。

实现思路:采用“最大公约数 + 当前轮次”方式,避免预生成列表(节省内存)。

public class WeightedRoundRobinLoadBalancer implements LoadBalancer {
    private AtomicInteger currentPos = new AtomicInteger(0);

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty()) return null;

        // 计算总权重
        int totalWeight = servers.stream().mapToInt(Server::getWeight).sum();
        if (totalWeight <= 0) {
            // 退化为普通轮询
            return new RoundRobinLoadBalancer().select(servers);
        }

        int current = currentPos.getAndIncrement() % totalWeight;
        if (current < 0) current += totalWeight;

        // 遍历找到对应节点
        for (Server server : servers) {
            if (current < server.getWeight()) {
                return server;
            }
            current -= server.getWeight();
        }

        // 理论上不会走到这里
        return servers.get(servers.size() - 1);
    }
}

注意:上述实现是简化版。工业级实现(如 Nginx)通常使用更复杂的平滑加权轮询(Smooth Weighted Round Robin),以避免连续选中高权重节点。

4. 平滑加权轮询(Smooth Weighted Round Robin)

由 Nginx 提出,解决加权轮询中“高权重节点连续被选中”的问题。

public class SmoothWeightedRoundRobinLoadBalancer implements LoadBalancer {

    private final Map<Server, Integer> currentWeights = new ConcurrentHashMap<>();

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty()) return null;

        int totalWeight = 0;
        Server best = null;
        int max = Integer.MIN_VALUE;

        for (Server server : servers) {
            int weight = server.getWeight();
            if (weight <= 0) weight = 1;

            totalWeight += weight;
            int current = currentWeights.getOrDefault(server, 0) + weight;
            currentWeights.put(server, current);

            if (current > max) {
                max = current;
                best = server;
            }
        }

        if (best != null) {
            currentWeights.put(best, max - totalWeight);
        }

        return best;
    }
}

优点:权重分配更平滑,高权重节点不会连续被选中

示例:A(w=5), B(w=1) → 顺序为 A,A,A,A,A,B,... 而非 A,A,A,A,A,A,...

5. 最少连接(Least Connections)

将请求分发给当前连接数最少的节点。

为简化,我们用一个 Map<Server, AtomicInteger> 模拟连接计数。

public class LeastConnectionsLoadBalancer implements LoadBalancer {
    private final Map<Server, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty()) return null;

        Server best = null;
        int minConn = Integer.MAX_VALUE;

        for (Server server : servers) {
            int conn = connectionCounts.computeIfAbsent(server, k -> new AtomicInteger(0)).get();
            if (conn < minConn) {
                minConn = conn;
                best = server;
            }
        }

        // 模拟增加连接(实际使用需配合请求完成后的 decrement)
        if (best != null) {
            connectionCounts.get(best).incrementAndGet();
        }

        return best;
    }

    // 供外部调用:请求完成后减少连接数
    public void release(Server server) {
        AtomicInteger count = connectionCounts.get(server);
        if (count != null) {
            count.decrementAndGet();
        }
    }
}

适用于长连接或处理时间差异大的场景

需要维护连接状态,有额外开销

6. 源地址哈希(IP Hash / Source Hash)

根据客户端 IP(或其他唯一标识)做哈希,保证同一客户端始终路由到同一节点。

public class SourceHashLoadBalancer implements LoadBalancer {
    private String source; // 可通过构造函数传入 client IP

    public SourceHashLoadBalancer(String source) {
        this.source = source;
    }

    @Override
    public Server select(List<Server> servers) {
        if (servers == null || servers.isEmpty() || source == null) return null;
        int hash = source.hashCode();
        int index = (hash & 0x7FFFFFFF) % servers.size(); // 避免负数
        return servers.get(index);
    }
}

优点:会话保持(Session Stickiness)

缺点:节点增减会导致大量映射失效(可改用一致性哈希)

7. 一致性哈希(Consistent Hashing)

解决普通哈希在节点动态变化时缓存/会话大量失效的问题。

public class ConsistentHashingLoadBalancer implements LoadBalancer {
    private final SortedMap<Integer, Server> circle = new TreeMap<>();
    private final int virtualNodes; // 虚拟节点数

    public ConsistentHashingLoadBalancer(int virtualNodes) {
        this.virtualNodes = virtualNodes;
    }

    public void addServer(Server server) {
        for (int i = 0; i < virtualNodes; i++) {
            int hash = hash(server.getHost() + ":" + server.getPort() + "#" + i);
            circle.put(hash, server);
        }
    }

    public void removeServer(Server server) {
        for (int i = 0; i < virtualNodes; i++) {
            int hash = hash(server.getHost() + ":" + server.getPort() + "#" + i);
            circle.remove(hash);
        }
    }

    private int hash(String key) {
        return key.hashCode(); // 简化,生产建议用 MD5 或 MurmurHash
    }

    @Override
    public Server select(List<Server> servers) {
        if (circle.isEmpty()) {
            // 动态构建环(实际应提前构建)
            servers.forEach(this::addServer);
        }
        if (circle.isEmpty()) return null;

        // 假设 source 为请求 ID 或 IP
        String requestKey = "request_" + System.nanoTime(); // 实际应由调用方提供
        int hash = hash(requestKey);

        if (!circle.containsKey(hash)) {
            SortedMap<Integer, Server> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }
}

节点增减只影响局部数据

广泛用于缓存、分布式存储系统

总结对比

算法是否考虑权重是否有状态适用场景
随机简单快速分发
轮询✅(位置)请求均匀、节点同质
加权轮询节点性能不同
平滑加权轮询更公平的加权分发
最少连接❌(但看负载)长连接、异构任务
源地址哈希会话保持
一致性哈希❌(可扩展支持)✅(哈希环)缓存、分布式存储

结语

通过手写这七种负载均衡算法,我们不仅掌握了其实现细节,也理解了它们各自的优劣和适用边界。在真实项目中,可根据业务需求灵活选择或组合使用(例如:先一致性哈希定位节点组,再在组内轮询)。

提示:生产环境建议使用成熟组件(如 Ribbon、Spring Cloud LoadBalancer、Nginx、LVS),但理解底层原理永远是工程师的核心竞争力。

以上就是从零带你手写Java七种负载均衡算法实现方案的详细内容,更多关于Java负载均衡算法的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文