Skip to content

负载均衡策略:轮询、随机、哈希、最小连接、加权

你有没有想过这个问题:

服务 A 有 3 个实例:

  • 实例 1:4 核 CPU,扛得住
  • 实例 2:8 核 CPU,性能更强
  • 实例 3:2 核 CPU,弱一点

如果请求都平均分配给 3 个实例,那实例 3 肯定先扛不住。但实例 1 和实例 2 的能力又没充分利用。

这就是负载均衡策略要解决的问题。

为什么需要负载均衡策略

传统轮询的缺点:

- 不考虑服务器性能差异
- 不考虑当前负载情况
- 不考虑请求的处理时间

负载均衡策略的目标:

- 充分利用服务器资源
- 避免单点过载
- 提升整体吞吐量

七种负载均衡策略

1. 轮询(Round Robin)

最简单也最常用的策略,顺序循环分配请求。

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

    public String select(List<String> servers) {
        int i = index.getAndIncrement() % servers.size();
        return servers.get(i);
    }
}

适用场景:服务器性能相近的简单场景。

2. 随机(Random)

随机选择一个服务器。

java
public class RandomLoadBalancer {
    public String select(List<String> servers) {
        Random random = new Random();
        int index = random.nextInt(servers.size());
        return servers.get(index);
    }
}

适用场景:请求量足够大时,效果接近轮询。

3. 加权轮询(Weighted Round Robin)

根据服务器性能分配权重,权重高的分配更多请求。

java
public class WeightedRoundRobinLoadBalancer {
    // 权重映射
    private Map<String, Integer> weights = Map.of(
        "server1", 4,  // 4 核
        "server2", 8,  // 8 核
        "server3", 2   // 2 核
    );

    public String select(List<String> servers) {
        int totalWeight = servers.stream()
            .mapToInt(s -> weights.getOrDefault(s, 1))
            .sum();

        int random = new Random().nextInt(totalWeight);
        int cumulative = 0;

        for (String server : servers) {
            cumulative += weights.getOrDefault(server, 1);
            if (random < cumulative) {
                return server;
            }
        }
        return servers.get(0);
    }
}

适用场景:服务器性能差异较大的场景。

4. 加权随机(Weighted Random)

java
public class WeightedRandomLoadBalancer {
    private Map<String, Integer> weights = Map.of(
        "server1", 4,
        "server2", 8,
        "server3", 2
    );

    public String select(List<String> servers) {
        // 构建权重池
        List<String> weightedPool = new ArrayList<>();
        servers.forEach(s -> {
            int weight = weights.getOrDefault(s, 1);
            for (int i = 0; i < weight; i++) {
                weightedPool.add(s);
            }
        });
        return weightedPool.get(new Random().nextInt(weightedPool.size()));
    }
}

5. 最少连接(Least Connections)

选择当前连接数最少的服务器。

java
public class LeastConnectionsLoadBalancer {
    private Map<String, AtomicInteger> connections = new ConcurrentHashMap<>();

    public String select(List<String> servers) {
        // 找到连接数最少的服务器
        return servers.stream()
            .min(Comparator.comparingInt(
                s -> connections.getOrDefault(s, new AtomicInteger(0)).get()
            ))
            .orElse(servers.get(0));
    }

    public void addConnection(String server) {
        connections.computeIfAbsent(server, k -> new AtomicInteger(0))
            .incrementAndGet();
    }

    public void removeConnection(String server) {
        AtomicInteger count = connections.get(server);
        if (count != null) {
            count.decrementAndGet();
        }
    }
}

适用场景:长连接场景,如数据库连接池、HTTP Keep-Alive。

6. 一致性哈希(Consistent Hash)

相同请求 key 打到同一服务器,适合缓存场景。

java
public class ConsistentHashLoadBalancer {
    private final TreeMap<Long, String> ring = new TreeMap<>();
    private final HashFunction hashFunction = Hashing.murmur3_128();

    public ConsistentHashLoadBalancer(List<String> servers) {
        for (String server : servers) {
            // 每个服务器对应 1000 个虚拟节点
            for (int i = 0; i < 1000; i++) {
                long hash = hashFunction.hashString(server + "#" + i, Charsets.UTF_8).asLong();
                ring.put(hash, server);
            }
        }
    }

    public String select(String key) {
        long hash = hashFunction.hashString(key, Charsets.UTF_8).asLong();

        // 找到顺时针方向的第一个节点
        Map.Entry<Long, String> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            entry = ring.firstEntry();
        }
        return entry.getValue();
    }
}

适用场景:缓存服务、Session 保持。

7. 源地址哈希(Source IP Hash)

相同 IP 的请求打到同一服务器。

java
public class SourceIpHashLoadBalancer {
    public String select(List<String> servers, String clientIp) {
        int hash = clientIp.hashCode();
        int index = Math.abs(hash) % servers.size();
        return servers.get(index);
    }
}

适用场景:需要 Session 保持但无法使用 Cookie 的场景。

Dubbo LoadBalance 实现

Dubbo 内置了 5 种负载均衡策略:

java
public enum LoadBalance {
    RANDOM,      // 加权随机(默认)
    ROUNDROBIN,  // 加权轮询
    LEASTACTIVE, // 最少连接
    CONSISTENTHASH, // 一致性哈希
    SHORTESTRESPONSE // 最短响应时间
}
xml
<!-- Dubbo 配置 -->
<dubbo:reference interface="com.example.UserService"
    loadbalance="roundrobin" />

负载均衡策略对比

策略复杂度考虑因素适用场景
轮询简单性能相近的服务器
随机简单请求量大
加权轮询中等权重性能不均
加权随机中等权重性能不均
最少连接复杂连接数长连接
一致性哈希复杂Key缓存、Session
最短响应复杂响应时间实时响应

总结

负载均衡策略是服务治理的核心组件:

  • 轮询/随机:简单高效,适合无状态服务
  • 加权:考虑服务器性能差异
  • 最少连接:考虑服务器当前负载
  • 一致性哈希:保证同一请求路由到同一服务器

选对策略,能显著提升系统的吞吐量和稳定性。

面试追问方向:

  • 一致性哈希的虚拟节点是什么?解决什么问题?
  • 最少连接策略如何实现?有什么缺点?
  • 如何设计一个加权轮询算法?
  • 负载均衡和 Ribbon 有什么关系?

基于 VitePress 构建