专栏文章
专栏文章
网络算法系列
1. 网络算法 #01:轮询与加权轮询 2. 网络算法 #02:最小连接数 3. 网络算法 #03:Jump Consistent Hash 4. 网络算法 #04:Rendezvous Hashing 5. 网络算法 #05:TCP 拥塞控制(CUBIC 与 BBR) 6. 网络算法 #06:QUIC 丢包恢复与拥塞控制 7. 网络算法 #07:经典一致性哈希(虚拟节点) 8. 网络算法 #08:P2C(Power of Two Choices) 9. 网络算法 #09:TCP 滑动窗口与流量控制 10. 网络算法 #10:Maglev Hashing

网络算法 #10:Maglev Hashing

发布于 2026-06-08 09:03 👁 7 次阅读
#负载均衡#网络算法#一致性哈希#哈希算法#google

Maglev Hashing 通过预计算一张固定大小的查找表将路由操作降至 O(1),同时保持与一致性哈希相同的最小迁移量(1/N),是 Google GFE(Google Frontend)负载均衡器的核心算法,也是 ECMP 路由的工业标准之一。

相关文章Jump Consistent Hash · Rendezvous Hashing · 经典一致性哈希(虚拟节点)

maglev lookup table

目录

章节 说明
问题背景 为什么 Google 需要新的哈希方案
核心数据结构:查找表 M 大小的查找表,O(1) 查询
偏好序列生成 offset + skip 的双哈希构造
查找表填充算法 轮流填充,先到先得
扩缩容时的迁移 1/N 迁移量证明
与其他一致性哈希对比 四种方案横向比较
实际应用 GFE、ECMP、Katran
参考资料 论文与文档

问题背景

Google GFE 每秒处理数百万个连接,对负载均衡提出了苛刻要求:

  1. 极快的查询速度:经典一致性哈希 O(log N) 在百万 QPS 下仍有开销
  2. 最小连接迁移:服务器上下线时,只应迁移必要的连接(TCP 长连接不能断)
  3. 均匀分布:每台后端承受相同的负载

Jump Consistent Hash 满足 1 和 3,但不支持随机删除节点。经典一致性哈希满足 2 和 3,但查询较慢。Maglev 同时满足全部三点


核心数据结构:查找表

Maglev 的核心是一张大小为 M 的数组 lookup[0..M-1],每个槽位存储一个后端编号:

lookup = [B0, B1, B2, B0, B1, B2, B0]  // M=7,3个后端

查询:
  backend = lookup[hash(key) % M]       // O(1),直接数组索引

M 的选择:必须是质数,且 M >> N(后端数量)。生产环境通常使用 M=65537。 质数保证了偏好序列能均匀覆盖所有槽位,避免周期性分布。


偏好序列生成

每台后端服务器 b 都有一个长度为 M 的「偏好序列」(permutation),表示它希望占据的槽位顺序:

def generate_permutation(backend_name, M):
    # 双哈希:两个不同的哈希函数
    offset = hash1(backend_name) % M
    skip   = hash2(backend_name) % (M - 1) + 1   # skip ∈ [1, M-1],保证遍历所有槽位

    permutation = []
    cur = offset
    for _ in range(M):
        permutation.append(cur)
        cur = (cur + skip) % M
    return permutation

数学保证:当 M 是质数时,任意 skip ∈ [1, M-1] 都能生成 0..M-1 的完整排列(费马小定理)。

示例(M=7):

后端 offset skip 偏好序列
B0 3 4 [3, 0, 4, 1, 5, 2, 6]
B1 0 2 [0, 2, 4, 6, 1, 3, 5]
B2 3 1 [3, 4, 5, 6, 0, 1, 2]

查找表填充算法

所有后端轮流按偏好序列填充查找表,已被占用的槽位跳过(先到先得):

def build_lookup(backends, M):
    lookup  = [-1] * M
    filled  = 0
    ptrs    = [0] * len(backends)   # 每个后端当前偏好序列的指针

    while filled < M:
        for i, b in enumerate(backends):
            # 找到该后端偏好序列中第一个未被占用的槽位
            j = ptrs[i]
            while lookup[b.perm[j]] != -1:
                j += 1
            ptrs[i] = j + 1
            slot = b.perm[j]
            lookup[slot] = i
            filled += 1
            if filled == M:
                break
    return lookup

填充结果(M=7,3个后端):

轮次1: B0→槽3, B1→槽0, B2→槽3(占用)→槽4
轮次2: B0→槽0(占用)→槽4(占用)→槽1, B1→槽2, B2→槽5
轮次3: B0→槽5(占用)→槽2(占用)→槽6, B1→槽6(占用)→...

lookup = [B1, B0, B1, B0, B2, B2, B0]
每个后端占 2~3 个槽位,分布均匀

时间复杂度:O(M × log M) 预处理,O(1) 查询。


扩缩容时的迁移

增加后端

新增后端 B3 后,重新计算查找表。由于 B3 会「抢占」原本属于其他后端的部分槽位,迁移量约为:

迁移比例 ≈ 1/(N+1)

与经典一致性哈希完全相同——这是理论最优值,不可能更少。

删除后端

删除某台后端后,其占用的槽位被重新分配给其他后端,迁移量约为 1/N

⚠️ Maglev 不支持增量更新:每次拓扑变化都需要重新计算整张查找表(O(M × log M))。M=65537 时计算耗时约 1ms,对生产系统可接受。


与其他一致性哈希对比

维度 经典一致性哈希 Jump CH Rendezvous Maglev
查询复杂度 O(log N) O(ln N) O(N) O(1)
空间复杂度 O(NK) O(1) O(1) O(M)(M≈65537)
支持随机删除 ✅(重建表)
负载均匀性 K 足够大时好 完美 非常好
迁移量 1/(N+1) 1/(N+1) 1/(N+1) 1/(N+1)
重建开销 无需重建 无状态 无状态 O(M log M)
适用场景 KV 集群 Google 内部 CDN L4 负载均衡

实际应用

Google GFE

Google Frontend(GFE)是所有 Google 服务的入口负载均衡器。Maglev 用于:

原始论文(2016 NSDI)报告:Maglev 在 10Gbps NIC 上以软件实现达到全线速转发。

Facebook Katran

Facebook 开源的 L4 负载均衡器 Katran 基于 eBPF,其哈希算法借鉴了 Maglev 的查找表思想:

ECMP 路由

ECMP(Equal-Cost Multi-Path)路由器使用 Maglev 风格的查找表对 IP 五元组做哈希,保证同一流的所有数据包经过同一条路径(避免乱序)。


参考资料

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论