一、引言
在微服务与分布式架构日益普及的今天,负载均衡(Load Balancing) 已经成为了系统架构中不可或缺的一环。当我们在面试中被问到"如何实现高并发系统的流量分发"时,相信很多同学都会脱口而出"Nginx"和"Ribbon"。
但你是否仔细思考过:为什么Nginx和Ribbon都能做负载均衡,它们在架构中处于不同的位置? 面试中如果被问到"Nginx是服务端负载还是客户端负载?Ribbon又是基于什么协议?",你能否准确答出?
作为一名在校的后端开发者,在搭建职业技能学习助手或智慧养老平台这类项目时,合理利用负载均衡不仅能提升系统可用性,更是面试中的加分项。
今天这篇文章,将带你从架构定位、实现原理、适用场景三个维度,深度拆解 Nginx(服务端负载) 与 Ribbon(客户端负载) 的核心区别,并结合实战案例总结二者的选型策略。
二、单体架构下的负载均衡
单体本身没有 "内置负载均衡",负载均衡是靠【部署多个单体实例 + 前置反向代理】实现的。
2.1 为什么要负载均衡
单体应用所有业务逻辑打包在一个进程中,部署在单台服务器上:
- 单节点性能上限:并发量、CPU / 内存 / 带宽瓶颈明显,无法应对高流量
- 可用性差:单节点宕机 = 整个服务不可用
- 扩容困难:只能垂直扩容(升级服务器硬件),成本高、上限低
- 只启动一个实例 → 单点故障,挂了全挂
- 并发高了 → 单机扛不住,CPU / 内存打满
2.2 流程
用户 → Nginx(反向代理/负载均衡) → 单体应用实例1/实例2/实例3
- 用户访问
http://域名 - 请求到达 Nginx
- Nginx 看配置里的
upstream节点列表 - 根据负载均衡算法选一个 Tomcat
- 把请求转发过去
- Tomcat 执行 Controller、Service、DAO
- 结果原路返回给用户
2.3 策略
-
轮询(默认)
upstream my_java_app {
server 127.0.0.1:8080; # 单体实例1
server 127.0.0.1:8081; # 单体实例2
server 127.0.0.1:8082; # 单体实例3
} -
加权
upstream my_java_app {
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
}
3.哈希
upstream my_java_app {
ip_hash;
server ...;
}
同一个 IP 永远访问同一个 Tomcat
-
最少连接
upstream my_java_app {
least_conn;
server ...;
}
三、Nginx
3.1 反向代理
正向代理(翻墙那种)
- 客户端知道目标服务器是谁
- 代理替客户端去访问
反向代理(Nginx 这种)
- 客户端不知道真正服务器是谁
- 客户端只认 Nginx
- Nginx 替后端服务 "出面"
3.2 作用
Nginx 是一个高性能的 HTTP 反向代理服务器。它干的事就两件:
- 接收用户请求
- 转发给后端 Java(Tomcat) 并在转发时做负载均衡、限流、缓存、黑白名单、SSL等。
3.3 线程结构
传统模式:一个连接 = 一个线程
- 来 1000 个请求 → 开 1000 个线程
- 来 10000 个请求 → 开 10000 个线程
问题爆炸:
- 线程本身很重每个线程栈 1MB 左右,1 万线程就是 10GB 内存。
- CPU 大量时间浪费在线程切换线程切换一次要保存上下文、恢复上下文,成本极高。
- 阻塞等待 线程等数据库、等网络、等磁盘时,整个线程就干等着,啥也不干。
Nginx进程结构:Master + Worker
- Master 进程:管理 Worker,加载配置,热重启
- Worker 进程 :真正处理请求,数量一般 = CPU 核心数
比如 4 核 CPU → 4 个 Worker 进程。
3.4 单线程的好处
**每个 Worker 是单线程的。**单线程 = 没有线程切换 = 没有锁竞争 = CPU 效率拉满。
原因 1:无锁竞争
多线程必须加锁:synchronized、lock、CAS...锁竞争、上下文切换,会吃掉大量 CPU。
Nginx Worker 之间几乎不共享数据,每个 Worker 独立处理自己的连接,零锁、零竞争。
原因 2:CPU 缓存命中率极高
单线程一直跑一段逻辑,指令、数据都在 CPU 缓存里,速度比内存快 10~100 倍。
多线程频繁切换 → 缓存频繁失效 → 速度暴跌。
3.5 epoll
Nginx采用异步非阻塞 + epoll 事件驱动
epoll是Linux 提供的高性能 IO 多路复用工具。
可以把 epoll 理解成一个:事件监听清单 + 事件通知器。
- Nginx 把所有客户端连接丢给 epoll 管理
- Nginx 主线程调用
epoll_wait() - 然后就睡觉,啥也不干
- 一旦某个连接可读 / 可写→ 内核主动通知 epoll→ epoll 唤醒 Nginx
- Nginx 只处理已经就绪的连接
不轮询、不等待、不浪费 CPU
3.6 零拷贝
传统流程:磁盘 → 内核缓冲区 → 用户缓冲区 → socket 缓冲区 → 网卡
Nginx 零拷贝:磁盘 → 内核缓冲区 → 直接发给网卡
少两次拷贝,少两次用户态 / 内核态切换
3.7 负载均衡效率
- 负载均衡只是**内存里选一个下标,**轮询就是计数器 + 1,ip_hash 就是一次哈希计算
- 计算极快,几乎不消耗 CPU
- Nginx 不做业务逻辑,只做转发
- 转发时也是异步非阻塞,不等 Tomcat
四、微服务的负载均衡
微服务将单体拆分为多个独立部署的服务,服务间通过网络调用(RPC/HTTP)通信,因此负载均衡分为两层:
4.1 网关层:Nginx(服务端负载均衡)
- 作用:作为用户请求的统一入口,将前端请求分发到对应的微服务网关(如 Spring Cloud Gateway)或服务实例
- 核心价值:统一入口、反向代理、动静分离、限流熔断、安全防护
- 架构位置:用户 → Nginx → 微服务网关 → 微服务实例
4.2 服务调用层:Ribbon(客户端负载均衡)
- 作用:在服务消费者(调用方)的进程内,实现服务调用的负载均衡,消费者直接根据服务列表选择实例发起调用,无需经过中间代理
- 核心价值:去中心化、低延迟、高可用,适配微服务动态扩缩容
- 架构位置:服务消费者 → Ribbon(负载均衡) → 服务提供者实例 1 / 实例 2 / 实例 3
五、Ribbon
Ribbon 是 Netflix 开源的客户端负载均衡器 ,现在虽然被 Spring Cloud LoadBalancer 替代,但原理完全一样,是微服务负载均衡的鼻祖。
5.1 定位
- 运行在调用方服务内部(作为 jar 包依赖)
- 不独立部署,不是中间件,就是个工具类
- 只做一件事:帮你从一堆服务实例里选一个
- 不负责发送请求(发送是 RestTemplate / Feign 做的)
5.2 流程
从注册中心拿到服务列表 → 本地缓存 → 按算法选一个实例 → 直接调用
当服务(比如订单服务)启动时:
- Ribbon 去 注册中心(Eureka/Nacos) 拉取目标服务(比如 user-service)的所有实例地址
- 把实例列表 缓存到本地内存
- 后台定时刷新(默认 30s)
当用 @LoadBalanced RestTemplate 或 Feign 调用时:Ribbon 开始工作
Ribbon 内部有一个 IRule 接口,根据策略选择一个实例:
- 轮询(默认)
- 随机
- 权重
- 最低并发
- 重试
然后把服务名替换成真实 IP: 端口
http://user-service/user/1
变成
http://192.168.1.10:8080/user/1
最后交给HTTP 工具发起真实请求(RestTemplate / Feign 真正发送请求),如果调用失败,Ribbon 可以按策略重试其他节点。
5.3 组件
5.3.1 ServerList<Server>
- 负责:从注册中心获取服务实例列表
- 实现:NacosServerList、EurekaServerList
5.3.2 ServerListFilter
- 过滤掉不健康、下线的实例
5.3.3 IRule
负载均衡选择策略,最核心:
- RoundRobinRule 轮询(默认)
- RandomRule 随机
- WeightedResponseTimeRule 权重(响应越快权重越高)
- BestAvailableRule 选并发最低的
- RetryRule 重试
5.3.4 ILoadBalancer
负载均衡器入口,整合上面所有组件:
- 持有服务列表
- 调用 IRule 选择 server
- 提供 chooseServer () 方法
5.3.5 IPing
心跳检查实例是否存活(默认不主动 ping,依赖注册中心状态)
5.4 对比
| 对比维度 | Nginx | Ribbon |
|---|---|---|
| 负载均衡类型 | 服务端负载均衡(Server-Side LB) | 客户端负载均衡(Client-Side LB) |
| 架构位置 | 独立的反向代理服务器,独立部署 | 嵌入在服务消费者进程中,随应用一起启动 |
| 流量路径 | 用户 → Nginx → 服务实例(中间有代理层) | 服务消费者 → Ribbon → 服务实例(无中间代理) |
| 核心原理 | 基于反向代理,维护后端服务实例列表,按策略转发请求 | 基于服务发现(如 Eureka/Nacos),拉取服务实例列表,在客户端本地做负载均衡 |
| 性能特点 | 所有流量经过 Nginx,有额外网络开销,Nginx 易成瓶颈 | 本地计算,无额外网络开销,性能更高 |
| 健康检查 | 需主动配置后端实例健康检查,故障实例剔除有延迟 | 结合服务发现,实时感知实例上下线,自动剔除故障实例 |
| 适用场景 | 入口流量分发、反向代理、跨服务 / 跨集群流量调度 | 微服务内部服务调用、同集群内服务间通信 |
| 协议支持 | 支持 HTTP/HTTPS/TCP/UDP 等多种协议 | 主要支持 HTTP/RESTful 调用(Spring Cloud 生态) |
| 配置方式 | 独立配置文件(nginx.conf),集中式配置 | 代码 / 配置文件(application.yml),分布式配置 |
六、Spring Cloud LoadBalancer
Spring Cloud LoadBalancer(SCLB) 是 Spring Cloud 官方推出的新一代客户端负载均衡器 ,用来彻底替代已停止维护的 Netflix Ribbon。它和 Ribbon 核心思想一样(客户端负载均衡),但架构更现代、更轻量、支持响应式编程。
6.1 对比
| 维度 | Ribbon | Spring Cloud LoadBalancer |
|---|---|---|
| 维护状态 | 停更(仅修 bug) | 官方活跃维护、持续迭代 |
| 编程模型 | 仅同步阻塞 | 同步 + 响应式(WebFlux/WebClient) |
| 架构 | 臃肿、依赖多(Netflix 全家桶) | 轻量级、纯 Spring、依赖极少 |
| 核心接口 | ILoadBalancer、IRule |
ReactorLoadBalancer、ServiceInstanceListSupplier |
| 健康检查 | 依赖 IPing(被动) |
主动检查(可对接 /actuator/health) |
| 生态 | 绑定 Netflix 组件 | 原生 Spring Cloud 全家桶(Gateway、Feign、WebClient) |
| 默认策略 | 轮询、随机、权重、最低并发等 | 轮询、随机(干净、可灵活扩展) |
七、无客户端负载均衡
正常微服务:
订单服务(客户端LB) → 直连 用户服务实例
无客户端负载均衡:
订单服务 **→ Nginx →**用户服务
7.1 Nginx的局限
Nginx的IP写死在配置里面
- 加一个用户服务实例 → 要改 Nginx 配置 → reload
- 下线一个 → 改 Nginx → reload
- 服务一重启 IP 变了 → Nginx 直接转发到死节点
微服务变成 "静态集群",完全失去弹性。
所有内部调用全部走 Nginx 转发
后果:
- Nginx 压力爆炸,内网流量全走它
- Nginx 一挂,整个微服务集群全部挂掉
- 多一跳转发,性能下降、延迟增加
- 连接数暴涨,Nginx 成最大瓶颈
这完全违背微服务去中心化的设计思想。
7.2 无服务发现
微服务必须有:
- 服务注册
- 服务发现
- 自动感知上下线
没有 Ribbon/SCLB,你的服务:
- 不知道其他服务有多少实例
- 不知道哪些是活的,哪些是挂的
- 不知道新扩的服务在哪
"服务注册、发现、上下线感知是由 注册中心(Nacos/Eureka)实现的,它负责维护动态的服务列表。但是 ,没有 Ribbon 或 Spring Cloud LoadBalancer,虽然你能拿到服务列表,但你无法通过服务名(如 http://user-service)进行远程调用,也无法在本地实现轮询、权重等高级负载均衡策略。它们是'消费'服务发现结果的关键组件。"
7.3 策略
Nginx 只有:
- 轮询
- ip_hash
- 权重
- least_conn
微服务需要的策略:
- 同区域优先(AZ 亲和)
- 最低延迟
- 权重动态调整
- 灰度路由
- 版本路由
- 动态熔断
- 实例故障自动剔除
- 重试其他节点
以上大部分不是 Ribbon 原生做的,但它们都依赖 Ribbon 提供的 "客户端负载均衡" 能力才能实现。
Nginx 一概做不到,或者做起来极其痛苦。
7.4无法实现 Feign / 事务 / 链路追踪
微服务生态很多能力依赖客户端 LB:
- Feign 远程调用
- 负载均衡重试
- 流量染色
- 灰度发布
- 服务熔断降级(Sentinel、Resilience4j)
- 链路追踪传递 TraceId
一旦全部走 Nginx:
- 无法感知实例状态
- 无法重试其他节点
- 无法做精细化流量管控
- 熔断、降级、限流全部失效或很难做
八、Spring Cloud Alibaba 环境下的客户端负载均衡
Spring Cloud Alibaba 自身不实现负载均衡,它只做服务注册发现(Nacos),负载均衡直接用 Spring Cloud 官方的 Spring Cloud LoadBalancer。
8.1 流程
服务启动 → 注册到 Nacos
user-service、order-service 启动时,把自己的 IP、端口上报 Nacos。
调用方(order-service)从 Nacos 拉取服务列表
order-service 会定时从 Nacos 拉取 user-service 所有实例
列表交给 Spring Cloud LoadBalancer
LoadBalancer 把列表缓存到本地。
把服务名替换成真实 IP,直接调用。
直连,不经过 Nginx,不经过任何中间代理。
8.2 分工
✔ Spring Cloud Alibaba(Nacos)负责:
- 服务注册
- 服务发现
- 自动感知上下线
- 健康检查
- 给 LoadBalancer 提供服务列表
✔ Spring Cloud LoadBalancer 负责:
- 从列表中选一个实例
- 服务名 → 真实 IP
- 负载均衡策略(轮询 / 随机)
九、其他
关于微服务请求的内容感兴趣的小伙伴可以看以下这篇博客