日期: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 处理,但列表在选择期间必须保持一致快照。
关键细节
- 随机和轮询简单,但无法感知实例当前负载。
- 最少连接不一定等于最少负载,需结合请求成本。
- 一致性哈希适合缓存分片和有状态路由。
- 客户端负载均衡要与连接池和服务发现协同。
面试官追问
- 为什么不能直接用
index++ % size? - 如何实现加权轮询?
- 实例列表变化时如何保证线程安全?
面试官追问参考答案
1. 为什么不能直接用 index++ % size?
普通 index++ 并发下会丢更新,整数溢出后 % 可能得到负数。使用 AtomicInteger 保证递增原子性,并用 Math.floorMod 处理负值;仍需处理空列表和快照变化。
2. 如何实现加权轮询?
可用平滑加权轮询:每轮给实例当前权重加有效权重,选择当前权重最大者,再减去总权重。失败时动态降低有效权重、恢复时缓慢增加,避免高权实例连续成团。
3. 实例列表变化时如何保证线程安全?
注册中心更新时构造新的不可变列表并通过 volatile/AtomicReference 原子替换,选择过程读取一次快照。不要在遍历过程中原地修改共享 ArrayList。
学习清单
- 会写线程安全的简单轮询。
- 理解权重、健康检查和服务发现。