负载均衡模式:从轮询到一致性哈希,我踩过的那些坑

负载均衡,说白了就是「把请求分给谁干」的问题。我做了十几年后端,几乎每个项目都离不开它。今天咱们聊聊几种常见的负载均衡策略,以及我在Nginx和Ribbon里实际用过的经验。

为什么需要负载均衡?

想象一下,你只有一个服务器,突然来了1000个请求。它扛不住,直接挂了。这时候你加了三台服务器,但问题来了——请求该发给谁?

这就是负载均衡要解决的事。它像个聪明的调度员,知道哪台机器闲、哪台忙,把请求合理分配出去。

核心目标:提高系统吞吐量,避免单点过载,保证高可用。

五种常见策略,我一个个说

1. 轮询(Round Robin)

最简单的策略。请求按顺序轮流发给每台服务器。第一台、第二台、第三台……循环往复。

我在早期项目里用过这个。当时三台服务器配置一样,业务也简单,轮询完全够用。但后来发现一个问题——如果某台服务器处理慢,轮询还是会继续给它发请求,导致它越来越慢。

// Java 伪代码 - 轮询实现
public class RoundRobin {
    private List<String> servers = Arrays.asList("S1", "S2", "S3");
    private int index = 0;

    public String getServer() {
        String server = servers.get(index);
        index = (index + 1) % servers.size();
        return server;
    }
}

适用场景:服务器配置相同,请求处理时间相近。

2. 加权轮询(Weighted Round Robin)

轮询的升级版。给每台服务器分配一个权重,权重高的多分一些请求。

我记得有一次,公司新买了台高性能服务器,CPU和内存都比老机器强一倍。如果还用普通轮询,新机器就浪费了。加权轮询正好解决这个问题——新机器权重设2,老机器设1,新机器多处理一倍请求。

// Nginx 配置示例
upstream backend {
    server 192.168.1.10 weight=3;
    server 192.168.1.11 weight=2;
    server 192.168.1.12 weight=1;
}

注意:权重不是绝对的。如果某台服务器挂了,Nginx会自动把它摘掉,不会继续发请求。

3. 最小连接(Least Connections)

这个策略看的是「当前谁最闲」。每次请求来了,找当前活跃连接数最少的服务器。

我在一个长连接项目里用过这个。有些请求处理快,几毫秒就完事;有些请求处理慢,可能要几秒。轮询会导致慢请求堆积在某台机器上。最小连接就聪明多了——它会自动避开那些正在处理慢请求的机器。

// Nginx 配置
upstream backend {
    least_conn;
    server 192.168.1.10;
    server 192.168.1.11;
    server 192.168.1.12;
}

我的经验:最小连接适合请求处理时间差异大的场景。但如果所有请求处理时间差不多,它和轮询效果差别不大。

4. 一致性哈希(Consistent Hashing)

这个稍微复杂点。它把服务器和请求都映射到一个哈希环上,请求找离它最近的服务器。

为什么要用这个?因为有些场景需要「粘性会话」——同一个用户的请求必须发给同一台服务器。比如用户登录后,session存在某台机器上,下次请求还得去那台。

我曾经踩过一个坑:用普通哈希做路由,结果加了一台服务器后,几乎所有请求都重新分配了。缓存全部失效,数据库瞬间被打爆。一致性哈希的好处就是——加机器或减机器时,只有少量请求需要重新分配。

// 一致性哈希核心逻辑(简化版)
public class ConsistentHash {
    private TreeMap<Integer, String> ring = new TreeMap<>();
    private int virtualNodes = 100; // 虚拟节点数

    public void addServer(String server) {
        for (int i = 0; i < virtualNodes; i++) {
            int hash = hash(server + "#" + i);
            ring.put(hash, server);
        }
    }

    public String getServer(String key) {
        int hash = hash(key);
        Map.Entry<Integer, String> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            entry = ring.firstEntry();
        }
        return entry.getValue();
    }
}

建议:虚拟节点数设多少?我一般设100-200。太少会导致分布不均,太多浪费内存。

5. 随机(Random)

这个最简单——随机选一台。虽然听起来不靠谱,但在某些场景下效果不错。

比如请求量特别大,而且每台服务器处理能力差不多。随机策略的负载分布其实很均匀,而且实现成本最低。

Nginx vs Ribbon:两种不同的玩法

Nginx是服务器端的负载均衡,Ribbon是客户端的。两者各有千秋。

特性 Nginx Ribbon
位置 服务器端(反向代理) 客户端(集成在应用内)
配置方式 配置文件(nginx.conf) 代码或配置文件
支持策略 轮询、加权轮询、最小连接、IP哈希等 轮询、加权轮询、随机、可用性过滤等
健康检查 被动+主动 被动(通过心跳)
适用场景 HTTP/HTTPS反向代理 微服务内部调用

我在Spring Cloud项目里常用Ribbon。它和Eureka配合得很好——服务列表自动从注册中心拉取,不用手动维护。Nginx则更适合做网关层的负载均衡,比如把请求分发给多个后端服务集群。

避坑指南:我曾经犯过的错

我曾经在项目里用轮询,结果某台服务器内存泄漏,越来越慢。轮询还是傻傻地给它发请求,最终导致整个集群响应变慢。后来我加上了健康检查——如果某台服务器连续几次超时,就自动把它摘掉。

还有一个坑:一致性哈希的虚拟节点数设得太少,导致负载不均。我记得当时设了20个虚拟节点,结果某台服务器分到了40%的请求,其他几台只有15%左右。后来改成200个,分布就均匀多了。

重要提醒:没有万能的负载均衡策略。选哪种,取决于你的业务场景。如果拿不准,先从轮询开始,观察一段时间再调整。

核心知识体系

下面这张图总结了负载均衡的核心策略和它们的关系:

负载均衡策略 轮询 (Round Robin) 加权轮询 最小连接 一致性哈希 随机 (Random) 选择建议 • 服务器配置相同 → 轮询 • 服务器配置不同 → 加权轮询 • 请求处理时间差异大 → 最小连接 • 需要粘性会话 → 一致性哈希

总结一下

负载均衡不是什么高深的技术,但选错了策略,后果很严重。我个人习惯是:

  • 简单场景:轮询或随机,够用就好
  • 服务器配置不均:加权轮询
  • 请求处理时间差异大:最小连接
  • 需要会话保持:一致性哈希
  • 微服务内部:Ribbon + 注册中心
  • 网关层:Nginx

你想想看,其实每种策略都有它的脾气。摸透了,用对了,系统就稳了。

一句话记住:没有最好的策略,只有最合适的策略。先跑起来,再优化。

公众号:蓝海资料掘金营,微信deep3321