2. 负载均衡算法基础:轮询、加权轮询、最小连接

聊负载均衡,绕不开的就是算法。说白了,算法决定了流量怎么分、分给谁。我这些年调过的交换机、做过的项目,踩坑最多的往往不是硬件性能,而是算法选型没想清楚。今天咱们就把三个最基础的算法掰开揉碎了讲清楚。

2.1 轮询(Round Robin)

轮询是最简单的算法。它的逻辑就是:挨个来,一个接一个,循环往复。你想想看,就像食堂打饭,窗口1、窗口2、窗口3,轮流叫号,谁也不多打一份。

在交换架构里,轮询的实现非常轻量。每个后端节点维护一个计数器,来一个请求就加1,然后取模。代码写出来就几行:

# 伪代码示例:轮询调度
servers = ["S1", "S2", "S3"]
index = 0

def get_server():
    global index
    server = servers[index % len(servers)]
    index += 1
    return server

轮询的优点很明显:实现简单、无状态、CPU开销极低。但缺点也致命——它假设所有节点性能一样。我在项目中遇到过,三台服务器配置不同,一台是64核,两台是16核,轮询下去,64核那台闲得发慌,16核的却快被压垮了。嗯,这就是轮询的局限性。

适用场景:后端节点配置完全一致、请求处理时间基本相等的场景。比如静态文件服务器集群。
注意:如果后端节点性能差异大,或者请求处理时间波动明显,轮询会导致严重的负载不均。我曾经见过一个生产事故,就是因为轮询导致某台机器CPU打满,整个集群雪崩。

2.2 加权轮询(Weighted Round Robin)

加权轮询,说白了就是给轮询加了个权重。你能力强,就多分点活;能力弱,就少分点。这个逻辑很符合直觉,对吧?

我习惯把权重理解为“配额”。比如三台服务器,权重分别是5、3、2,那每10个请求里,S1处理5个,S2处理3个,S3处理2个。实现上,常见的做法是“平滑加权轮询”,它能避免短时间内的请求集中打到同一台机器上。

# 伪代码示例:平滑加权轮询
servers = [
    {"name": "S1", "weight": 5, "current": 0},
    {"name": "S2", "weight": 3, "current": 0},
    {"name": "S3", "weight": 2, "current": 0}
]
total_weight = 10

def get_server():
    # 每个节点增加自己的权重
    for s in servers:
        s["current"] += s["weight"]
    
    # 选current最大的
    best = max(servers, key=lambda s: s["current"])
    
    # 减去总权重
    best["current"] -= total_weight
    
    return best["name"]

加权轮询比普通轮询灵活多了。我记得有一次做CDN边缘节点的负载均衡,后端机器有不同代际的硬件,老机器权重设1,新机器设3,效果立竿见影。不过要注意,权重不是越大越好,得结合实际压测数据来调。

调优建议:权重值建议用整数,且总和不要太大。我一般控制在10-100之间,太大容易导致调度周期过长,影响实时性。

2.3 最小连接(Least Connections)

最小连接算法,看名字就知道——谁当前处理的连接数最少,就把新请求发给谁。这个算法很聪明,它不依赖预设的权重,而是动态感知后端负载。

你想想看,如果某个请求处理特别慢,那它占着连接的时间就长,连接数自然就高。最小连接算法会自动避开这个“慢节点”,把新请求发给更空闲的机器。这比加权轮询更智能,因为它能应对请求处理时间不一致的情况。

# 伪代码示例:最小连接调度
servers = [
    {"name": "S1", "connections": 12},
    {"name": "S2", "connections": 8},
    {"name": "S3", "connections": 15}
]

def get_server():
    # 选连接数最少的
    best = min(servers, key=lambda s: s["connections"])
    best["connections"] += 1
    return best["name"]

def release_server(server_name):
    # 请求完成后释放连接
    for s in servers:
        if s["name"] == server_name:
            s["connections"] -= 1
            break

最小连接算法在长连接场景下特别好用。比如数据库连接池、WebSocket服务。我曾经在一个即时通讯项目里用最小连接做网关负载均衡,效果比轮询好了不止一个量级。但要注意,短连接场景下,连接数频繁波动,反而可能导致调度抖动。

核心优势:动态感知后端负载,自动适应请求处理时间的变化。适合处理时间差异大的场景。
避坑指南:我曾经遇到过一个问题——最小连接算法配合连接复用,导致连接数统计不准。因为复用连接不算新建,但实际负载并不低。解决方案是改用“请求数”而非“连接数”作为调度依据。

2.4 三种算法对比

我把这三个算法放在一起对比一下,方便你选型时参考:

算法 核心逻辑 优点 缺点 推荐场景
轮询 循环分配 实现简单、无状态 不考虑节点差异 同配置、短连接
加权轮询 按权重分配 支持异构节点 权重需手动调优 异构集群、静态负载
最小连接 选连接最少 动态感知负载 短连接场景抖动 长连接、处理时间不均

2.5 核心逻辑流程图

下面这张图,我把三种算法的决策逻辑画出来了。你看一眼就能明白它们之间的区别:

三种负载均衡算法决策流程 新请求到达 轮询 计数器取模 加权轮询 权重+平滑调度 最小连接 选连接数最少 S1 → S2 → S3 → S1... S1(5) → S2(3) → S3(2) 选当前连接最少 均匀分配 按权重分配 动态分配 选型建议 同配置→轮询 | 异构→加权轮询 | 长连接→最小连接

这张图把三种算法的决策路径画得很清楚。从新请求到达开始,根据你选择的算法,走不同的分支,最终落到后端节点上。我个人建议,刚开始做负载均衡设计时,先把这三种算法吃透,再考虑更复杂的算法。

好了,这一章的内容就到这里。三种算法各有千秋,没有银弹。关键是根据你的业务场景,选最合适的那个。

专注资料整理