负载均衡模式:从轮询到一致性哈希,我踩过的那些坑
负载均衡,说白了就是「把请求分给谁干」的问题。我做了十几年后端,几乎每个项目都离不开它。今天咱们聊聊几种常见的负载均衡策略,以及我在Nginx和Ribbon里实际用过的经验。
为什么需要负载均衡?
想象一下,你只有一个服务器,突然来了1000个请求。它扛不住,直接挂了。这时候你加了三台服务器,但问题来了——请求该发给谁?
这就是负载均衡要解决的事。它像个聪明的调度员,知道哪台机器闲、哪台忙,把请求合理分配出去。
核心目标:提高系统吞吐量,避免单点过载,保证高可用。
五种常见策略,我一个个说
1. 轮询(Round Robin)
最简单的策略。请求按顺序轮流发给每台服务器。第一台、第二台、第三台……循环往复。
我在早期项目里用过这个。当时三台服务器配置一样,业务也简单,轮询完全够用。但后来发现一个问题——如果某台服务器处理慢,轮询还是会继续给它发请求,导致它越来越慢。
// Java 伪代码 - 轮询实现
public class RoundRobin {
private List<String> servers = Arrays.asList("S1", "S2", "S3");
private int index = 0;
public String getServer() {
String server = servers.get(index);
index = (index + 1) % servers.size();
return server;
}
}
适用场景:服务器配置相同,请求处理时间相近。
2. 加权轮询(Weighted Round Robin)
轮询的升级版。给每台服务器分配一个权重,权重高的多分一些请求。
我记得有一次,公司新买了台高性能服务器,CPU和内存都比老机器强一倍。如果还用普通轮询,新机器就浪费了。加权轮询正好解决这个问题——新机器权重设2,老机器设1,新机器多处理一倍请求。
// Nginx 配置示例
upstream backend {
server 192.168.1.10 weight=3;
server 192.168.1.11 weight=2;
server 192.168.1.12 weight=1;
}
注意:权重不是绝对的。如果某台服务器挂了,Nginx会自动把它摘掉,不会继续发请求。
3. 最小连接(Least Connections)
这个策略看的是「当前谁最闲」。每次请求来了,找当前活跃连接数最少的服务器。
我在一个长连接项目里用过这个。有些请求处理快,几毫秒就完事;有些请求处理慢,可能要几秒。轮询会导致慢请求堆积在某台机器上。最小连接就聪明多了——它会自动避开那些正在处理慢请求的机器。
// Nginx 配置
upstream backend {
least_conn;
server 192.168.1.10;
server 192.168.1.11;
server 192.168.1.12;
}
我的经验:最小连接适合请求处理时间差异大的场景。但如果所有请求处理时间差不多,它和轮询效果差别不大。
4. 一致性哈希(Consistent Hashing)
这个稍微复杂点。它把服务器和请求都映射到一个哈希环上,请求找离它最近的服务器。
为什么要用这个?因为有些场景需要「粘性会话」——同一个用户的请求必须发给同一台服务器。比如用户登录后,session存在某台机器上,下次请求还得去那台。
我曾经踩过一个坑:用普通哈希做路由,结果加了一台服务器后,几乎所有请求都重新分配了。缓存全部失效,数据库瞬间被打爆。一致性哈希的好处就是——加机器或减机器时,只有少量请求需要重新分配。
// 一致性哈希核心逻辑(简化版)
public class ConsistentHash {
private TreeMap<Integer, String> ring = new TreeMap<>();
private int virtualNodes = 100; // 虚拟节点数
public void addServer(String server) {
for (int i = 0; i < virtualNodes; i++) {
int hash = hash(server + "#" + i);
ring.put(hash, server);
}
}
public String getServer(String key) {
int hash = hash(key);
Map.Entry<Integer, String> entry = ring.ceilingEntry(hash);
if (entry == null) {
entry = ring.firstEntry();
}
return entry.getValue();
}
}
建议:虚拟节点数设多少?我一般设100-200。太少会导致分布不均,太多浪费内存。
5. 随机(Random)
这个最简单——随机选一台。虽然听起来不靠谱,但在某些场景下效果不错。
比如请求量特别大,而且每台服务器处理能力差不多。随机策略的负载分布其实很均匀,而且实现成本最低。
Nginx vs Ribbon:两种不同的玩法
Nginx是服务器端的负载均衡,Ribbon是客户端的。两者各有千秋。
| 特性 | Nginx | Ribbon |
|---|---|---|
| 位置 | 服务器端(反向代理) | 客户端(集成在应用内) |
| 配置方式 | 配置文件(nginx.conf) | 代码或配置文件 |
| 支持策略 | 轮询、加权轮询、最小连接、IP哈希等 | 轮询、加权轮询、随机、可用性过滤等 |
| 健康检查 | 被动+主动 | 被动(通过心跳) |
| 适用场景 | HTTP/HTTPS反向代理 | 微服务内部调用 |
我在Spring Cloud项目里常用Ribbon。它和Eureka配合得很好——服务列表自动从注册中心拉取,不用手动维护。Nginx则更适合做网关层的负载均衡,比如把请求分发给多个后端服务集群。
避坑指南:我曾经犯过的错
我曾经在项目里用轮询,结果某台服务器内存泄漏,越来越慢。轮询还是傻傻地给它发请求,最终导致整个集群响应变慢。后来我加上了健康检查——如果某台服务器连续几次超时,就自动把它摘掉。
还有一个坑:一致性哈希的虚拟节点数设得太少,导致负载不均。我记得当时设了20个虚拟节点,结果某台服务器分到了40%的请求,其他几台只有15%左右。后来改成200个,分布就均匀多了。
重要提醒:没有万能的负载均衡策略。选哪种,取决于你的业务场景。如果拿不准,先从轮询开始,观察一段时间再调整。
核心知识体系
下面这张图总结了负载均衡的核心策略和它们的关系:
总结一下
负载均衡不是什么高深的技术,但选错了策略,后果很严重。我个人习惯是:
- 简单场景:轮询或随机,够用就好
- 服务器配置不均:加权轮询
- 请求处理时间差异大:最小连接
- 需要会话保持:一致性哈希
- 微服务内部:Ribbon + 注册中心
- 网关层:Nginx
你想想看,其实每种策略都有它的脾气。摸透了,用对了,系统就稳了。
一句话记住:没有最好的策略,只有最合适的策略。先跑起来,再优化。