如果要你自己实现一个轮询算法,你会有什么思路?🤔
❝
面试官:如果让你自己实现一个轮询算法,你会怎么写?
我当时心想:"这不就是
index++嘛?小菜一碟!"😏结果他接着说:"那如果有权重呢?节点挂了怎么办?你还能保证分配均匀吗?"
嗯...好像不太对劲。😅
相信很多后端开发者在面试中遇到这种"手写底层算法"的问题都会心头一紧。今天我们就来聊聊这个话题,不仅告诉你思路,还附上Java和Golang两种版本的代码实现!
❞
一、轮询的本质到底是什么?🕵♂
什么是轮询算法?它解决了什么问题?
先来个灵魂拷问:为什么需要轮询?🤷♂️
想象一下,你开了一家网红奶茶店,只有一个服务员(单服务器)。突然生意爆火,顾客排起了长队(高并发请求),服务员忙得团团转。
这时候你会怎么办?「加人手(加服务器)」 呗!
但问题来了:怎么把顾客**「公平地」分配给不同的服务员呢?这就是轮询算法要解决的核心问题------「将请求依次分配给每个服务器,实现负载均衡」**。
轮询(Round Robin)的核心思想其实一句话:
❝
「把请求平均分配到多个节点上,依次循环使用。」
❞
比如有三台服务器:
arduino
Server A
Server B
Server C
那请求的分配顺序大概就是👇:
css
Request1 → A
Request2 → B
Request3 → C
Request4 → A
Request5 → B
Request6 → C
这就是最基础的"普通轮询(Simple Round Robin)"。
二、第一步:普通轮询 ✅
其实实现非常简单,我们只需要一个计数器即可。
🧩 Java 版本
java
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class RoundRobin {
private final List<String> servers;
private final AtomicInteger index = new AtomicInteger(0);
public RoundRobin(List<String> servers) {
this.servers = servers;
}
public String nextServer() {
int i = Math.abs(index.getAndIncrement() % servers.size());
return servers.get(i);
}
public static void main(String[] args) {
RoundRobin rr = new RoundRobin(List.of("A", "B", "C"));
for (int i = 0; i < 6; i++) {
System.out.println(rr.nextServer());
}
}
}
输出结果:
css
A
B
C
A
B
C
🧩 Go 版本
go
package main
import (
"fmt"
"sync/atomic"
)
type RoundRobin struct {
servers []string
index uint32
}
func (r *RoundRobin) Next() string {
i := atomic.AddUint32(&r.index, 1)
return r.servers[int(i-1)%len(r.servers)]
}
func main() {
r := &RoundRobin{servers: []string{"A", "B", "C"}}
for i := 0; i < 6; i++ {
fmt.Println(r.Next())
}
}
是不是很轻松?这就是最经典的"原始轮询"。 但面试官往往不会让你这么容易过关😏。
三、升级版:带权重的轮询 ⚖️
面试官这时候一般会问一句:
❝
"那如果每台服务器性能不一样呢?A 是 8 核,B 是 4 核,C 是 2 核,你怎么分配?"
❞
这时候我们就需要 ------ 「加权轮询(Weighted Round Robin)」。
🧠 核心思路:
每个节点分配一个权重(weight),代表它能处理的请求数:
| 节点 | 权重 |
|---|---|
| A | 5 |
| B | 3 |
| C | 2 |
我们希望请求分布如下:
css
A, A, A, A, A, B, B, B, C, C
🧩 Go 实现(简单加权版)
go
package main
import "fmt"
type Server struct {
name string
weight int
}
type WeightedRR struct {
servers []Server
index int
}
func (w *WeightedRR) Next() string {
total := 0
for _, s := range w.servers {
total += s.weight
}
server := w.servers[w.index%total]
w.index++
return server.name
}
func main() {
w := &WeightedRR{
servers: []Server{
{"A", 5},
{"B", 3},
{"C", 2},
},
}
for i := 0; i < 10; i++ {
fmt.Println(w.Next())
}
}
不过这只是"暴力重复"的权重方式。 生产中更优雅的是 ------ 「平滑加权轮询(Smooth Weighted RR)」 , 这也是 「Nginx」 默认使用的负载策略。
🧩 Java 实现(平滑加权版)
ini
import java.util.*;
class WeightedServer {
String name;
int weight;
int currentWeight = 0;
public WeightedServer(String name, int weight) {
this.name = name;
this.weight = weight;
}
}
public class SmoothWeightedRR {
private final List<WeightedServer> servers;
private final int totalWeight;
public SmoothWeightedRR(List<WeightedServer> servers) {
this.servers = servers;
this.totalWeight = servers.stream().mapToInt(s -> s.weight).sum();
}
public String next() {
WeightedServer best = null;
for (WeightedServer s : servers) {
s.currentWeight += s.weight;
if (best == null || s.currentWeight > best.currentWeight) {
best = s;
}
}
best.currentWeight -= totalWeight;
return best.name;
}
public static void main(String[] args) {
SmoothWeightedRR rr = new SmoothWeightedRR(List.of(
new WeightedServer("A", 5),
new WeightedServer("B", 3),
new WeightedServer("C", 2)
));
for (int i = 0; i < 10; i++) {
System.out.println(rr.next());
}
}
}
输出结果大致是:
css
A
B
A
C
A
B
A
C
B
A
平滑又稳定,是不是比简单循环更优雅?✨
四、真实场景:轮询算法在哪些地方用?
其实"轮询算法"不是算法题,而是整个分布式系统的底层支撑。
你在很多地方都能见到它👇:
| 应用场景 | 实现方式 |
|---|---|
| 「Nginx 负载均衡」 | 平滑加权轮询 |
| 「gRPC Balancer」 | Round Robin |
| 「Dubbo、Spring Cloud」 | 加权随机 / 轮询 |
| 「K8s kube-proxy」 | iptables 的轮询分发 |
| 「数据库连接池」 | 连接分配轮询 |
换句话说,只要你在"分配资源", 轮询几乎无处不在。
五、实战思考:轮询算法的优缺点
✅ 优点:
- 「实现简单」:逻辑清晰,容易理解和实现
- 「绝对公平」:基础轮询确保每个服务器获得相等请求
- 「无状态」:不需要记录每个服务器的当前状态
❌ 缺点:
- 「忽略服务器状态」:不管服务器是否过载,都继续分配请求
- 「会话保持问题」:如果需要保持用户会话,基础轮询不太适合
- 「配置固定」:加权轮询需要预先配置权重,无法自适应调整
六、别以为轮询没坑 ⚠️
有几个实际项目中会踩到的点👇:
| 问题 | 解决方案 |
|---|---|
| ❌ 节点挂了还在分配 | 加健康检查,定期剔除宕机节点 |
| ⚠️ 多节点计数器不同步 | 分布式场景可用 Redis 计数或一致性哈希 |
| ⚙️ 动态扩容后请求不均 | 使用平滑轮询算法或动态调整权重 |
| 🔁 热点请求集中 | 结合一致性哈希或最少连接策略 |
❝
面试官问"轮询算法",其实想看你能不能从**「算法思维 → 工程实现 → 业务优化」**这条链路说清楚。
❞
七、一句话总结 🧩
轮询算法看似简单,但能看出一个后端开发者的层次:
❝
🌱 初级:能写出循环算法。
⚙️ 中级:能考虑权重与线程安全。
🧠 高级:能结合业务场景优化性能与稳定性。
❞
八、写在最后 ☕
轮询算法其实就像"发糖果":
- 你可以一个人一个分(普通轮询)🍬
- 多干活的人多分点(加权轮询)⚖️
- 掉队的人先别分(健康检查)🩺
- 最后保证全班都不吵架(负载均衡)😂
它不是背八股能搞懂的算法,而是一种"分配资源的哲学"。
面试技巧:遇到这种问题怎么办?
当面试官问你如何实现轮询算法时,可以按照这个思路回答:
- 「先确认需求」:"您指的是基础轮询还是加权轮询?"
- 「从简单开始」:先实现基础版本,再讨论优化
- 「考虑边界情况」:服务器列表为空、权重为0等
- 「提出改进思路」:健康检查、动态权重等进阶功能
记住,面试官不只是想看你的代码能力,更想了解你的**「思考过程和解决问题能力」**!
💬 「你在项目里用过哪些负载策略?有没有踩过轮询的坑?」 欢迎留言区分享你的经历,我会挑一些案例在下篇文章里分析😉
❝
🧭 关注【Tech有道】,解锁更多面经
❞