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

Maglev Hashing

发布于 2026-06-08 09:03 · 最后编辑于 2026-07-31 15:54 · 字数 1,832 👁 91 次阅读

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):

后端offsetskip偏好序列
B034[3, 0, 4, 1, 5, 2, 6]
B102[0, 2, 4, 6, 1, 3, 5]
B231[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 CHRendezvousMaglev
查询复杂度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 内部CDNL4 负载均衡

实际应用

Google GFE

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

  • 将 TCP/UDP 连接哈希到后端 GFE 集群
  • 保证同一客户端的多个连接路由到同一后端(连接亲和性)
  • 服务器上下线时最小化连接重建

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

Facebook Katran

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

  • 在 XDP 层(数据包到达即处理)完成路由
  • 延迟约 50μs,远低于用户态方案

ECMP 路由

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

参考资料

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论