负载均衡算法的选择直接影响系统的吞吐量、尾延迟和可用性。一个看似简单的”Round Robin”在后端处理时间不均匀时会导致请求堆积;一个”Least Connection”在连接数不能反映真实负载时会做出错误决策。
本文从数学原理出发,分析每种算法的假设条件和失效场景,帮助你在具体场景下做出有依据的选择。
一、Round Robin 及其变体
1.1 简单轮询(Round Robin)
最简单的算法——按顺序将请求分配给后端服务器。假设有 N
台服务器,第 i 个请求分配给第 i % N 台。
class RoundRobin:
def __init__(self, servers):
self.servers = servers
self.index = 0
def next(self):
server = self.servers[self.index % len(self.servers)]
self.index += 1
return server
# 使用
rr = RoundRobin(["A", "B", "C"])
# 请求序列: A, B, C, A, B, C, A, ...假设条件:所有服务器的处理能力相同,所有请求的处理成本相同。
失效场景: - 服务器性能不同(新旧机器混部) - 请求处理时间差异大(查询列表 vs 导出报表) - 长连接场景(一个连接占用一台服务器很久)
1.2 加权轮询(Weighted Round Robin)
给每台服务器一个权重,处理能力强的分配更多请求:
class WeightedRoundRobin:
"""Nginx 的平滑加权轮询算法"""
def __init__(self, servers):
# servers: [(name, weight), ...]
self.servers = servers
self.weights = [w for _, w in servers]
self.current_weights = [0] * len(servers)
self.total_weight = sum(self.weights)
def next(self):
# 1. 每个节点的 current_weight 加上自身 weight
for i in range(len(self.servers)):
self.current_weights[i] += self.weights[i]
# 2. 选择 current_weight 最大的节点
max_idx = self.current_weights.index(max(self.current_weights))
# 3. 被选中节点的 current_weight 减去 total_weight
self.current_weights[max_idx] -= self.total_weight
return self.servers[max_idx][0]
# 权重 A:5, B:1, C:1 的调度序列
wrr = WeightedRoundRobin([("A", 5), ("B", 1), ("C", 1)])
# 序列: A, A, B, A, C, A, A (7 个请求中 A 得到 5 个)
# Nginx 的算法保证分布均匀,不会出现 A,A,A,A,A,B,C 这种连续分配Nginx 的平滑加权轮询(Smooth WRR)确保请求在所有服务器间平滑分布,避免某台服务器连续接收大量请求。这对缓存友好性和突发负载处理很重要。
来看一个具体的调度序列,理解 Smooth WRR 的行为。假设三台服务器权重为 A:5, B:1, C:1(总权重 7):
轮次 current_weight 累加后 选中 current_weight 减总权重后
1 [5, 1, 1] A [-2, 1, 1]
2 [3, 2, 2] A [-4, 2, 2]
3 [1, 3, 3] B [1, -4, 3]
4 [6, -3, 4] A [-1, -3, 4]
5 [4, -2, 5] C [4, -2, -2]
6 [9, -1, -1] A [2, -1, -1]
7 [7, 0, 0] A [0, 0, 0] ← 一个完整周期
序列: A, A, B, A, C, A, A
注意 B 和 C 均匀分散在 A 之间,而不是
A,A,A,A,A,B,C。这种平滑分布意味着任何时间窗口内的负载都相对均匀。
1.3 Round Robin 的工程问题
在 HTTP/2 和 gRPC 场景下,RR 有一个隐蔽的问题。HTTP/2 使用单连接多路复用,所有请求都在一个 TCP 连接上。如果 L4 LB 用 RR 调度 TCP 连接,某台 L7 LB 可能承担了大部分 HTTP/2 流量:
Client (HTTP/2, 单连接) → L4 LB (RR 调度连接)
→ 连接 1 → L7-A (承载 1000 RPS)
Client (HTTP/1.1, 多连接) → L4 LB (RR 调度连接)
→ 连接 1 → L7-A (10 RPS)
→ 连接 2 → L7-B (10 RPS)
→ 连接 3 → L7-C (10 RPS)
...
# L7-A 的负载远超 L7-B 和 L7-C
# 解决方案:在 L7 层用请求级 RR,不要在 L4 层用连接级 RR 调度 HTTP/2
二、Least Connection 算法
2.1 基本原理
最少连接(Least Connection,LC)选择当前活跃连接数最少的服务器。它的核心思想是:连接数少的服务器当前负载可能更低。
import heapq
from threading import Lock
class LeastConnection:
def __init__(self, servers):
# (active_connections, server_name)
self.heap = [(0, s) for s in servers]
heapq.heapify(self.heap)
self.lock = Lock()
self.conn_count = {s: 0 for s in servers}
def acquire(self):
with self.lock:
_, server = heapq.heappop(self.heap)
self.conn_count[server] += 1
heapq.heappush(self.heap, (self.conn_count[server], server))
return server
def release(self, server):
with self.lock:
self.conn_count[server] -= 1
# 重建堆(简化实现,实际用索引堆更高效)
self.heap = [(self.conn_count[s], s) for s in self.conn_count]
heapq.heapify(self.heap)2.2 加权最少连接(WLC)
将权重纳入计算,选择
active_connections / weight 最小的服务器:
overhead = active_connections / weight
Server A: 10 connections, weight=5 → overhead = 2.0
Server B: 8 connections, weight=2 → overhead = 4.0
Server C: 6 connections, weight=3 → overhead = 2.0
选择 overhead 最小的: A 或 C(如果相同,选第一个)
IPVS 的 WLC 实现使用整数运算避免浮点除法:
// 内核中的比较:a.conn * b.weight < b.conn * a.weight
// 等价于:a.conn/a.weight < b.conn/b.weight
// 但避免了除法运算
2.3 LC 的局限性
连接数 ≠ 负载。LC 算法假设每个连接的负载相同,但现实中:
| 场景 | 连接数 | 实际负载 | LC 的判断 |
|---|---|---|---|
| 10 个空闲 WebSocket | 10 | 低 | 以为负载高 |
| 1 个大文件上传 | 1 | 高 | 以为负载低 |
| 5 个 SELECT 查询 | 5 | 低 | 以为负载中 |
| 1 个大 JOIN 查询 | 1 | 高 | 以为负载低 |
长连接场景下的惊群效应。当一台新服务器加入时,它的连接数为 0,所有新请求都会涌向它,造成瞬间过载:
现有状态:
A: 100 connections
B: 95 connections
C: 0 connections ← 刚加入
LC 的行为: 所有新请求都去 C,直到 C 的连接数追上 A 和 B
实际效果: C 在启动后几秒内被打满
解决方案:
1. 慢启动 (Slow Start): 新加入的服务器权重从 0 逐渐增加
2. 连接数上限: 设置每台服务器的最大连接数
# Nginx 慢启动配置
upstream backend {
server 10.0.2.10:8080 slow_start=30s; # 30 秒内权重从 0 增加到设定值
server 10.0.2.11:8080;
server 10.0.2.12:8080;
}
三、哈希算法
3.1 源 IP 哈希
将客户端 IP 哈希后映射到后端服务器,保证同一客户端始终访问同一后端:
def ip_hash(client_ip, servers):
h = hash(client_ip) % len(servers)
return servers[h]问题:当服务器数量变化时,几乎所有客户端的映射都会改变——这对有状态服务(如购物车、登录 session)是灾难性的。
3.2 一致性哈希(Consistent Hashing)
一致性哈希解决了服务器增减时的映射变化问题。将服务器和键都映射到一个哈希环上:
import hashlib
import bisect
class ConsistentHash:
def __init__(self, servers, replicas=150):
self.ring = [] # 排序的哈希值列表
self.hash_to_server = {} # 哈希值 → 服务器映射
self.replicas = replicas # 每个服务器的虚拟节点数
for server in servers:
self.add_server(server)
def _hash(self, key):
return int(hashlib.md5(key.encode()).hexdigest(), 16)
def add_server(self, server):
for i in range(self.replicas):
h = self._hash(f"{server}:{i}")
self.ring.append(h)
self.hash_to_server[h] = server
self.ring.sort()
def remove_server(self, server):
for i in range(self.replicas):
h = self._hash(f"{server}:{i}")
self.ring.remove(h)
del self.hash_to_server[h]
def get_server(self, key):
if not self.ring:
return None
h = self._hash(key)
idx = bisect.bisect_right(self.ring, h) % len(self.ring)
return self.hash_to_server[self.ring[idx]]
# 当移除 Server B 时:
# 只有原本映射到 B 的键会重新映射到 B 的后继节点
# 其他键的映射不变
# 理论上只有 1/N 的键需要重新映射虚拟节点的重要性。如果每个服务器只有一个哈希值,负载分布会非常不均匀。150 个虚拟节点可以将负载差异控制在 10% 以内:
| 虚拟节点数 | 负载标准差 | 最大/最小比 |
|---|---|---|
| 1 | 85% | 10x+ |
| 10 | 30% | 3-4x |
| 50 | 12% | 1.5x |
| 150 | 7% | 1.2x |
| 500 | 4% | 1.1x |
3.3 一致性哈希在 LB 中的应用
一致性哈希最适合需要缓存亲和性的场景——同一个键的请求总是到达同一台服务器,最大化缓存命中率:
# Nginx 一致性哈希配置
upstream cache_backend {
hash $request_uri consistent; # 基于 URI 的一致性哈希
server 10.0.2.10:8080;
server 10.0.2.11:8080;
server 10.0.2.12:8080;
}
# 缓存代理场景
upstream cdn_origin {
hash $uri consistent;
server cache1:8080;
server cache2:8080;
server cache3:8080;
}
3.4 有界负载一致性哈希
Google 在 2017 年提出了有界负载一致性哈希(Consistent Hashing with Bounded Loads),解决一致性哈希中个别节点负载过高的问题:
class BoundedLoadConsistentHash(ConsistentHash):
"""有界负载一致性哈希"""
def __init__(self, servers, replicas=150, load_factor=1.25):
super().__init__(servers, replicas)
self.load_factor = load_factor
self.load = {s: 0 for s in servers}
self.total_load = 0
def _max_load(self):
"""每台服务器的负载上限"""
avg = self.total_load / len(self.load) if self.load else 0
return max(1, int(avg * self.load_factor))
def get_server(self, key):
h = self._hash(key)
idx = bisect.bisect_right(self.ring, h) % len(self.ring)
max_load = self._max_load()
# 沿环顺时针查找,跳过已超载的节点
for _ in range(len(self.ring)):
server = self.hash_to_server[self.ring[idx]]
if self.load.get(server, 0) < max_load:
self.load[server] = self.load.get(server, 0) + 1
self.total_load += 1
return server
idx = (idx + 1) % len(self.ring)
# fallback: 选负载最小的
return min(self.load, key=self.load.get)四、P2C:Power of Two Choices
4.1 超市收银台问题
假设你在超市结账,排队策略有三种:
- 随机选一个队:可能选到最长的队。
- 选最短的队:需要看所有队,信息获取成本高。
- 随机看两个队,选短的:只看两个队,效果接近最优。
这就是 P2C(Power of Two Random Choices)的核心思想——从全局最优退化到两个随机样本中选较好的,信息成本大幅降低,但效果几乎不变。
数学上,Michael Mitzenmacher 在 2001 年的论文中证明:
| 策略 | 最大队列长度(期望) |
|---|---|
| 随机选 1 个 | O(log n / log log n) |
| 随机选 2 个,取较好的 | O(log log n) |
| 选全局最优 | O(log log n) |
关键洞察:从 1 个选择增加到 2 个选择,带来的改善是指数级的(从 log n 到 log log n)。但从 2 增加到 3、4,改善很小。这就是为什么叫 “Power of Two Choices”。
用具体数字感受差距。假设 1000 台服务器:
随机选 1 个: max load ≈ log(1000) / log(log(1000))
≈ 6.9 / 1.93 ≈ 3.6(平均负载的 3.6 倍)
随机选 2 个: max load ≈ log(log(1000))
≈ log(6.9) ≈ 1.93(平均负载的 1.93 倍)
差距巨大: 3.6x → 1.93x,仅仅多看一个候选
4.2 P2C 在负载均衡中的实现
import random
class P2C:
"""Power of Two Choices 负载均衡"""
def __init__(self, servers):
self.servers = {s: 0 for s in servers} # server → active requests
def pick(self):
servers = list(self.servers.keys())
if len(servers) == 1:
return servers[0]
# 随机选两个不同的服务器
a, b = random.sample(servers, 2)
# 返回负载较低的
if self.servers[a] <= self.servers[b]:
self.servers[a] += 1
return a
else:
self.servers[b] += 1
return b
def done(self, server):
self.servers[server] = max(0, self.servers[server] - 1)4.3 P2C 的工程优势
为什么 P2C 在分布式系统中越来越受欢迎?
| 维度 | LC (全局最少连接) | P2C |
|---|---|---|
| 信息需求 | 所有服务器的连接数 | 只需 2 个服务器的信息 |
| 同步开销 | 全局同步(热点锁) | 无需同步 |
| 适合分布式 | 困难(信息不一致) | 天然适合 |
| 惊群效应 | 严重(新节点被涌入) | 轻微 |
| 尾延迟 | 好 | 接近最优 |
在分布式环境中,每个 LB 节点无法实时获取所有后端的真实负载。LC 需要全局视图,但全局视图有延迟——100ms 前的”最少连接”可能现在已经不是了。P2C 只需要局部信息,对信息延迟容忍度高。
4.4 gRPC 的 P2C 实现
gRPC 的 client-side load balancing 默认使用 P2C,且加入了延迟因子:
// gRPC pick_first → round_robin → P2C (with EWMA latency)
// P2C 结合 EWMA 延迟的伪代码
type Server struct {
Addr string
ActiveReqs int64
AvgLatency float64 // EWMA 延迟
}
func p2cPick(servers []*Server) *Server {
// 随机选两个
a := servers[rand.Intn(len(servers))]
b := servers[rand.Intn(len(servers))]
for b == a {
b = servers[rand.Intn(len(servers))]
}
// 综合考虑活跃请求数和延迟
scoreA := float64(a.ActiveReqs+1) * a.AvgLatency
scoreB := float64(b.ActiveReqs+1) * b.AvgLatency
if scoreA <= scoreB {
return a
}
return b
}4.5 P2C 的边界情况处理
P2C 在工程实现中需要处理几个边界情况:
class P2CRobust:
"""工程化的 P2C 实现"""
def __init__(self, servers):
self.servers = {s: {"active": 0, "latency": EWMA()} for s in servers}
self.last_picked = None
def pick(self):
healthy = [s for s in self.servers if self.is_healthy(s)]
if not healthy:
return None
if len(healthy) == 1:
return healthy[0]
a, b = random.sample(healthy, 2)
# 避免连续选同一个(减少突发负载)
if a == self.last_picked and self.score(a) == self.score(b):
a, b = b, a
chosen = a if self.score(a) <= self.score(b) else b
self.servers[chosen]["active"] += 1
self.last_picked = chosen
return chosen
def score(self, server):
s = self.servers[server]
latency = max(s["latency"].get(), 0.001) # 防止除零
return (s["active"] + 1) * latency
def is_healthy(self, server):
# 健康检查逻辑
return True五、Maglev 哈希
5.1 Google 的连接亲和性方案
Maglev 是 Google 在 2016 年发表的一致性哈希算法,专为网络负载均衡设计。与传统环形一致性哈希相比,Maglev 提供更均匀的负载分布和更快的查找速度:
传统一致性哈希: 哈希环 + 虚拟节点 → O(log n) 查找
Maglev: 查找表 + 置换序列 → O(1) 查找
Maglev 的核心是一个固定大小的查找表(通常为素数大小,如 65537)。每个后端通过两个哈希函数生成一个置换序列,然后按照优先级顺序填充查找表:
class MaglevHash:
"""简化的 Maglev 一致性哈希"""
def __init__(self, backends, table_size=65537):
self.backends = backends
self.table_size = table_size
self.table = self._generate_table()
def _generate_table(self):
n = len(self.backends)
# 每个后端生成偏移量和步长
permutations = []
for backend in self.backends:
offset = hash(backend + "_offset") % self.table_size
skip = hash(backend + "_skip") % (self.table_size - 1) + 1
permutations.append((offset, skip))
table = [-1] * self.table_size
filled = 0
idx = [0] * n # 每个后端的当前尝试位置
while filled < self.table_size:
for i in range(n):
offset, skip = permutations[i]
# 找到下一个空位
pos = (offset + idx[i] * skip) % self.table_size
while table[pos] != -1:
idx[i] += 1
pos = (offset + idx[i] * skip) % self.table_size
table[pos] = i
idx[i] += 1
filled += 1
if filled >= self.table_size:
break
return table
def lookup(self, key):
idx = hash(key) % self.table_size
return self.backends[self.table[idx]]Maglev 的优势:当一台后端故障时,只有 1/N 的连接需要重新映射(与一致性哈希相同),但查找性能是 O(1),负载分布更均匀。Envoy 将 Maglev 作为其一致性哈希的默认实现。
六、EWMA 与延迟感知
5.1 EWMA 延迟追踪
指数加权移动平均(Exponentially Weighted Moving Average,EWMA)用于平滑追踪后端响应延迟:
class EWMA:
"""指数加权移动平均"""
def __init__(self, decay=0.95):
self.value = 0.0
self.decay = decay
self.initialized = False
def update(self, sample):
if not self.initialized:
self.value = sample
self.initialized = True
else:
self.value = self.decay * self.value + (1 - self.decay) * sample
def get(self):
return self.value
class LatencyAwareLB:
"""延迟感知的 P2C 负载均衡"""
def __init__(self, servers):
self.servers = {}
for s in servers:
self.servers[s] = {
"active": 0,
"latency": EWMA(decay=0.9),
}
def pick(self):
keys = list(self.servers.keys())
a, b = random.sample(keys, 2)
sa = self.servers[a]
sb = self.servers[b]
# score = 活跃请求数 × 平均延迟
score_a = (sa["active"] + 1) * max(sa["latency"].get(), 0.001)
score_b = (sb["active"] + 1) * max(sb["latency"].get(), 0.001)
chosen = a if score_a <= score_b else b
self.servers[chosen]["active"] += 1
return chosen
def done(self, server, latency_ms):
self.servers[server]["active"] -= 1
self.servers[server]["latency"].update(latency_ms)5.2 延迟感知的工程价值
延迟感知算法在以下场景显著优于纯连接数算法:
- 异构后端:某些服务器因 GC、磁盘 IO 或 CPU throttling 临时变慢。
- 分级存储:热数据在内存,冷数据在磁盘,同一接口的响应时间差异大。
- 跨 AZ 部署:不同可用区的网络延迟不同。
七、算法选型指南
6.1 决策矩阵
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| 无状态 + 同构后端 | Round Robin | 最简单,无额外开销 |
| 无状态 + 异构后端 | Weighted Round Robin | 按处理能力分配 |
| 长连接(WebSocket/gRPC) | WLC 或 P2C | 需要考虑当前负载 |
| 缓存代理 | 一致性哈希 | 缓存亲和性 |
| 会话保持 | IP Hash 或 Cookie Hash | 同一用户到同一后端 |
| 分布式 client-side LB | P2C + EWMA | 无需全局信息 |
| 延迟敏感 | P2C + EWMA | 自动避开慢节点 |
| 混合场景 | P2C(默认推荐) | 综合表现最好 |
6.2 各 LB 软件的算法支持
| 算法 | IPVS | Nginx | HAProxy | Envoy | gRPC |
|---|---|---|---|---|---|
| Round Robin | ✓ | ✓ | ✓ | ✓ | ✓ |
| Weighted RR | ✓ | ✓ | ✓ | ✓ | ✓ |
| Least Conn | ✓ | ✓ | ✓ | ✓ | — |
| IP Hash | ✓ | ✓ | ✓ | — | — |
| 一致性哈希 | — | ✓ | ✓ | ✓ | — |
| P2C | — | — | — | ✓ | ✓ |
| Random | — | ✓ | ✓ | ✓ | — |
| Maglev | — | — | — | ✓ | — |
7.3 实战:算法切换的配置示例
# Nginx — 不同场景的算法配置
# 场景 1: 无状态 API,服务器同构
upstream api_stateless {
# 最简单的 RR
server 10.0.2.10:8080;
server 10.0.2.11:8080;
server 10.0.2.12:8080;
}
# 场景 2: 有状态服务,需要会话保持
upstream api_stateful {
ip_hash; # 基于客户端 IP 的哈希
server 10.0.2.10:8080;
server 10.0.2.11:8080;
server 10.0.2.12:8080;
}
# 场景 3: 缓存代理,需要键亲和
upstream cache_proxy {
hash $request_uri consistent;
server 10.0.2.10:8080;
server 10.0.2.11:8080;
server 10.0.2.12:8080;
}
# 场景 4: 长连接服务,服务器异构
upstream websocket_service {
least_conn;
server 10.0.2.10:8080 weight=3; # 高配机器
server 10.0.2.11:8080 weight=2;
server 10.0.2.12:8080 weight=1; # 低配机器
}
# HAProxy — 算法配置
backend api_rr
balance roundrobin
server s1 10.0.2.10:8080 check weight 1
server s2 10.0.2.11:8080 check weight 1
backend api_leastconn
balance leastconn
server s1 10.0.2.10:8080 check weight 3
server s2 10.0.2.11:8080 check weight 1
backend cache_consistent
balance uri # URI 哈希
hash-type consistent
server s1 10.0.2.10:8080 check
server s2 10.0.2.11:8080 check
backend api_random_p2c
balance random(2) # HAProxy 2.4+ 支持 P2C
server s1 10.0.2.10:8080 check
server s2 10.0.2.11:8080 check# Envoy — P2C + EWMA (默认)
clusters:
- name: api_cluster
lb_policy: LEAST_REQUEST # Envoy 的 LEAST_REQUEST 本质是 P2C
least_request_lb_config:
choice_count: 2 # 默认 2,即 P2C
load_assignment:
cluster_name: api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.0.2.10
port_value: 8080
- endpoint:
address:
socket_address:
address: 10.0.2.11
port_value: 80807.4 算法效果验证方法
选定算法后,必须验证实际效果:
# 1. 观察后端负载分布
# Nginx access log 统计各 upstream 的请求分布
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 如果某台服务器的请求数远超其他,说明算法选择或权重有问题
# 2. 监控后端响应时间差异
# 通过 Nginx $upstream_response_time 记录
log_format upstream_log '$remote_addr - [$time_local] '
'"$request" $status $upstream_addr $upstream_response_time';
# 分析各后端的 P50/P99
awk '{split($NF, a, ","); for(i in a) print $6, a[i]}' /var/log/nginx/upstream.log \
| sort | head -20
# 3. 检查连接复用率
# 如果 TIME_WAIT 很多,说明连接没有被复用
ss -tn state time-wait | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn
# 4. 基准测试
# 用 wrk 对比不同算法的吞吐量
wrk -t4 -c100 -d30s http://10.0.1.100/api/test
# 切换算法后重新测试,对比 RPS 和延迟分布以下是在 4 台后端(处理时间不均匀:10ms/20ms/50ms/100ms)下,各算法的表现(10000 请求,100 并发):
| 算法 | P50 延迟 | P99 延迟 | 最大延迟 | 吞吐量 | 负载均衡度 |
|---|---|---|---|---|---|
| Round Robin | 45ms | 100ms | 105ms | 2,200 RPS | 25%/25%/25%/25% |
| WRR (按处理速度) | 28ms | 85ms | 102ms | 3,100 RPS | 40%/25%/20%/15% |
| Least Conn | 25ms | 70ms | 98ms | 3,400 RPS | 45%/27%/18%/10% |
| P2C | 22ms | 55ms | 95ms | 3,600 RPS | 43%/28%/19%/10% |
| P2C + EWMA | 20ms | 48ms | 90ms | 3,800 RPS | 48%/26%/17%/9% |
关键观察:
- RR 的 P99 等于最慢后端的延迟。因为 25% 的请求一定会到最慢的服务器。
- P2C + EWMA 的 P99 最低。它自动将更多请求路由到快的服务器。
- 吞吐量差异可达 70%。从 RR 的 2200 到 P2C+EWMA 的 3800。
八、总结
不存在”最好的”算法,只有”最合适的”算法。RR 足够简单,在同构无状态场景下没必要用复杂算法。过度优化反而增加系统复杂度和调试难度。
P2C 是当前的”默认推荐”。它在信息成本和调度效果之间取得了最好的平衡。gRPC 和 Envoy 已经将其作为默认算法。如果你的系统还在用 RR,考虑切换到 P2C。
一致性哈希是缓存场景的唯一选择。但要注意虚拟节点数量(建议 100-200)和有界负载(避免热点键导致单节点过载)。
延迟感知(EWMA)是 P2C 的最佳搭档。纯连接数不能反映真实负载,加入延迟因子后,算法能自动感知后端的处理能力变化(GC、磁盘 IO、网络抖动)。
算法选择不如正确配置重要。权重设置错误、慢启动没开、健康检查太慢——这些配置问题比算法本身影响更大。先把基础做对,再考虑高级算法。
Maglev 是大规模 L4 LB 的利器。O(1) 查找性能和均匀分布让它成为 Google、Facebook 等公司在网络负载均衡器中的首选算法。Envoy 已经内置支持。
参考文献
- Mitzenmacher, M. (2001). The Power of Two Choices in Randomized Load Balancing, IEEE Trans. Parallel and Distributed Systems
- Vocking, B. (2003). How Asymmetry Helps Load Balancing, Journal of the ACM
- Karger, D. et al. (1997). Consistent Hashing and Random Trees, ACM STOC
- Mirrokni, V. et al. (2017). Consistent Hashing with Bounded Loads, Google Research
- Eisenbud, D. et al. (2016). Maglev: A Fast and Reliable Software Network Load Balancer, USENIX NSDI
- Nginx Documentation: HTTP Upstream Module (nginx.org/en/docs/http/ngx_http_upstream_module.html)
- HAProxy Documentation: Balance Algorithms (docs.haproxy.org/2.8/configuration.html#4.2-balance)
- Envoy Documentation: Load Balancing Policies (envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing)
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】HAProxy 工程:高级配置、ACL 与运维
系统讲解 HAProxy 的工程实践:Frontend/Backend/Listen 配置模型、ACL 规则引擎的高级用法、Runtime API 的动态管理能力、多线程模型与性能调优、SSL 终止与健康检查策略,建立 HAProxy 从配置到生产运维的完整体系。
【网络工程】L4 负载均衡:IPVS、LVS 与连接级调度
系统讲解 L4 负载均衡的内核实现:IPVS 的工作原理与三种转发模式(NAT/DR/TUN)、调度算法选择、LVS 高可用方案(Keepalived + VIP)、云环境中的 L4 LB(NLB/MetalLB),建立传输层负载均衡的工程能力。
【网络工程】L7 负载均衡:HTTP 感知路由与内容交换
深入讲解 L7 负载均衡的工程实现:HTTP 请求解析与路由决策、基于 Host/Path/Header 的高级路由规则、SSL Termination 的架构位置、L4+L7 混合架构设计,以及 L7 LB 的性能优化与故障排查。
【网络工程】健康检查与故障转移:主动探测、被动检测与优雅处理
系统讲解负载均衡中的健康检查工程:主动检查(TCP/HTTP/gRPC)与被动检查的机制差异、假阳性与假阴性的权衡、故障转移的连接排空与优雅下线、Envoy Outlier Detection 的异常点检测,以及健康检查策略的工程设计。