滴滴面试题:一道“轮询算法”的面试题,让我意识到自己太天真了

如果要你自己实现一个轮询算法,你会有什么思路?🤔

面试官:如果让你自己实现一个轮询算法,你会怎么写?

我当时心想:"这不就是 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 计数或一致性哈希
⚙️ 动态扩容后请求不均 使用平滑轮询算法或动态调整权重
🔁 热点请求集中 结合一致性哈希或最少连接策略

面试官问"轮询算法",其实想看你能不能从**「算法思维 → 工程实现 → 业务优化」**这条链路说清楚。


七、一句话总结 🧩

轮询算法看似简单,但能看出一个后端开发者的层次:

🌱 初级:能写出循环算法。

⚙️ 中级:能考虑权重与线程安全。

🧠 高级:能结合业务场景优化性能与稳定性。


八、写在最后 ☕

轮询算法其实就像"发糖果":

  • 你可以一个人一个分(普通轮询)🍬
  • 多干活的人多分点(加权轮询)⚖️
  • 掉队的人先别分(健康检查)🩺
  • 最后保证全班都不吵架(负载均衡)😂

它不是背八股能搞懂的算法,而是一种"分配资源的哲学"。

面试技巧:遇到这种问题怎么办?

当面试官问你如何实现轮询算法时,可以按照这个思路回答:

  1. 「先确认需求」:"您指的是基础轮询还是加权轮询?"
  2. 「从简单开始」:先实现基础版本,再讨论优化
  3. 「考虑边界情况」:服务器列表为空、权重为0等
  4. 「提出改进思路」:健康检查、动态权重等进阶功能

记住,面试官不只是想看你的代码能力,更想了解你的**「思考过程和解决问题能力」**!


💬 「你在项目里用过哪些负载策略?有没有踩过轮询的坑?」 欢迎留言区分享你的经历,我会挑一些案例在下篇文章里分析😉


🧭 关注【Tech有道】,解锁更多面经


相关推荐
golang学习记3 小时前
Go 1.25 Flight Recorder:线上偶发问题的“时间回放”利器
后端
ZZHHWW4 小时前
Redis 主从复制详解
后端
ZZHHWW4 小时前
Redis 集群模式详解(上篇)
后端
EMQX4 小时前
技术实践:在基于 RISC-V 的 ESP32 上运行 MQTT over QUIC
后端
程序员蜗牛4 小时前
Java泛型里的T、E、K、V都是些啥玩意儿?
后端
CoderLemon4 小时前
一次因缺失索引引发的线上锁超时事故
后端
ZZHHWW4 小时前
Redis 集群模式详解(下篇)
后端
ZZHHWW4 小时前
Redis 哨兵模式详解
redis·后端
Mintopia4 小时前
🚀 Next.js Edge Runtime 实践学习指南 —— 从零到边缘的奇幻旅行
前端·后端·全栈