Yihui’s Blog

可以用几行代码实现一个负载均衡器吗?

日期:2026-07-11
标签:#面试 #八股 #后端 #负载均衡 #场景题

一句话答案

最简轮询只需一个原子计数器对实例列表取模,但生产级负载均衡还要处理实例变化、健康、权重、并发、重试和连接复用。

代码示例

final class RoundRobin<T> {
    private final java.util.concurrent.atomic.AtomicInteger cursor =
            new java.util.concurrent.atomic.AtomicInteger();

    T select(java.util.List<T> servers) {
        if (servers.isEmpty()) throw new IllegalStateException("no server");
        return servers.get(Math.floorMod(cursor.getAndIncrement(), servers.size()));
    }
}

面试口语版

这段代码展示了轮询算法核心,但不能直接用于生产。实例列表应是注册中心推送的不可变快照,选择前过滤不健康实例;异构实例用加权轮询,长连接或请求耗时差异大时可用最少活跃数。请求失败不能无脑重试,需幂等、换节点、退避和 Deadline。计数器溢出通过 floorMod 处理,但列表在选择期间必须保持一致快照。

关键细节

  • 随机和轮询简单,但无法感知实例当前负载。
  • 最少连接不一定等于最少负载,需结合请求成本。
  • 一致性哈希适合缓存分片和有状态路由。
  • 客户端负载均衡要与连接池和服务发现协同。

面试官追问

  1. 为什么不能直接用 index++ % size?
  2. 如何实现加权轮询?
  3. 实例列表变化时如何保证线程安全?

面试官追问参考答案

1. 为什么不能直接用 index++ % size?

普通 index++ 并发下会丢更新,整数溢出后 % 可能得到负数。使用 AtomicInteger 保证递增原子性,并用 Math.floorMod 处理负值;仍需处理空列表和快照变化。

2. 如何实现加权轮询?

可用平滑加权轮询:每轮给实例当前权重加有效权重,选择当前权重最大者,再减去总权重。失败时动态降低有效权重、恢复时缓慢增加,避免高权实例连续成团。

3. 实例列表变化时如何保证线程安全?

注册中心更新时构造新的不可变列表并通过 volatile/AtomicReference 原子替换,选择过程读取一次快照。不要在遍历过程中原地修改共享 ArrayList。

学习清单

  • 会写线程安全的简单轮询。
  • 理解权重、健康检查和服务发现。
维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章