轮询需用AtomicInteger避免并发问题,随机应选ThreadLocalRandom防竞争,加权轮询须累积权重再比较而非简单除法,生产环境优先使用Spring Cloud或Dubbo内置实现。

轮询算法怎么写才不掉坑
轮询(Round Robin)本质就是按顺序循环取服务器,但直接用 ArrayList 索引自增再取模容易出错——比如并发请求下多个线程同时读写同一个计数器,结果跳过节点或重复选中同一台机器。
真正安全的做法是用 AtomicInteger 做原子递增,再对服务器列表长度取模:
private final AtomicInteger counter = new AtomicInteger(0);
private final List<String> servers = Arrays.asList("192.168.1.10", "192.168.1.11", "192.168.1.12");
<p>public String select() {
int idx = Math.abs(counter.getAndIncrement()) % servers.size();
return servers.get(idx);
}- 别用
int普通变量 +synchronized—— 锁粒度大,压测时吞吐明显下降 - 注意
Math.abs()是为了防止Integer.MIN_VALUE取反溢出导致负索引 - 如果后端列表动态变更(如服务上下线),这个实现不会自动感知,得配合监听机制重置计数器
随机选择为什么不能直接用 Random.nextInt()
看起来最简单的随机算法,实际在流量分布上常被低估:单次调用 Random.nextInt(servers.size()) 没问题,但短时间高并发下,JVM 默认的 Random 是共享种子,多个线程争用同一实例会导致生成序列趋同,某些节点被集中打爆。
更稳的方式是每个调用线程用本地 ThreadLocal<Random>,或者直接用 ThreadLocalRandom:
立即学习“Java免费学习笔记(深入)”;
public String select() {
int idx = ThreadLocalRandom.current().nextInt(servers.size());
return servers.get(idx);
}-
ThreadLocalRandom比普通Random快 3–5 倍,且无竞争 - 别在循环里反复新建
Random实例——构造开销大,还可能因系统时间相近导致种子重复 - 纯随机不保证均匀,100 次请求里某台机器被选中 40 次也正常;真要强均衡,得上带状态的算法(比如带计数的加权)
加权轮询的权重不是“除法”而是“累积+比较”
很多人以为加权轮询就是把权重当概率,用 Random.nextDouble() * totalWeight 找区间——那是加权随机,不是加权轮询。真正的加权轮询(Weighted Round Robin)核心是让高权重机器多走几轮,低权重少走,但整体仍保持“轮”的节奏。
推荐实现是维护每个节点的当前权重 currentWeight,每次选节点前先加上其原始权重,再选 currentWeight 最大的那个,最后减去总权重:
static class Server {
String addr;
int weight;
int currentWeight;
}
<p>public String select(List<Server> servers) {
int total = 0;
for (Server s : servers) {
s.currentWeight += s.weight;
total += s.weight;
}
Server selected = servers.get(0);
for (Server s : servers) {
if (s.currentWeight > selected.currentWeight) {
selected = s;
}
}
selected.currentWeight -= total;
return selected.addr;
}- 这个算法能保证长期来看,各节点被选中的次数比 ≈ 权重比,且单次请求延迟稳定
- 别用浮点数做权重(比如 0.3、0.7),整数更安全、无精度误差
- 如果权重差异极大(比如 1 和 1000),小权重节点可能连续几十次不被选中——这不是 bug,是算法特性;需要“平滑”就得换 WRR 的变种(如 Nginx 的 smooth weighted round robin)
生产环境别自己从零写负载均衡逻辑
上面三个算法看似简单,但真放到微服务网关或 RPC 客户端里,还要处理健康检查、熔断降级、配置热更新、一致性哈希 fallback……这些边界情况叠加起来,自己写的几行代码很容易成为故障点。
更现实的选择是:
- Spring Cloud LoadBalancer 已内置
RoundRobinLoadBalancer、RandomLoadBalancer,支持权重扩展,且和@LoadBalanced集成自然 - 如果用 Dubbo,直接配
loadbalance=roundrobin或weight属性就行,底层已做线程安全和权重归一化 - 自研场景若必须手写,至少把服务器列表抽象为接口(
ServerList),把选择逻辑抽成策略类(LoadBalanceStrategy),否则后续加故障剔除时改得头皮发麻
权重配置写死在代码里、没考虑服务注册中心下线通知、忽略 GC 导致 ThreadLocal 泄漏——这些才是上线后最常翻车的地方。










