Nginx vs Ribbon:负载均衡的两种核心范式(反向代理 vs 客户端负载)

一、引言

在微服务与分布式架构日益普及的今天,负载均衡(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 策略

  1. 轮询(默认)

    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
    }

  2. 加权

    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

  1. 最少连接

    upstream my_java_app {
    least_conn;
    server ...;
    }

三、Nginx

3.1 反向代理

正向代理(翻墙那种)

  • 客户端知道目标服务器是谁
  • 代理替客户端去访问

反向代理(Nginx 这种)

  • 客户端不知道真正服务器是谁
  • 客户端只认 Nginx
  • Nginx 替后端服务 "出面"

3.2 作用

Nginx 是一个高性能的 HTTP 反向代理服务器。它干的事就两件:

  1. 接收用户请求
  2. 转发给后端 Java(Tomcat) 并在转发时做负载均衡、限流、缓存、黑白名单、SSL等。

3.3 线程结构

传统模式:一个连接 = 一个线程

  • 来 1000 个请求 → 开 1000 个线程
  • 来 10000 个请求 → 开 10000 个线程

问题爆炸:

  1. 线程本身很重每个线程栈 1MB 左右,1 万线程就是 10GB 内存。
  2. CPU 大量时间浪费在线程切换线程切换一次要保存上下文、恢复上下文,成本极高。
  3. 阻塞等待 线程等数据库、等网络、等磁盘时,整个线程就干等着,啥也不干

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 流程

从注册中心拿到服务列表 → 本地缓存 → 按算法选一个实例 → 直接调用

当服务(比如订单服务)启动时:

  1. Ribbon 去 注册中心(Eureka/Nacos) 拉取目标服务(比如 user-service)的所有实例地址
  2. 把实例列表 缓存到本地内存
  3. 后台定时刷新(默认 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、依赖极少
核心接口 ILoadBalancerIRule ReactorLoadBalancerServiceInstanceListSupplier
健康检查 依赖 IPing(被动) 主动检查(可对接 /actuator/health
生态 绑定 Netflix 组件 原生 Spring Cloud 全家桶(Gateway、Feign、WebClient)
默认策略 轮询、随机、权重、最低并发等 轮询、随机(干净、可灵活扩展)

七、无客户端负载均衡

正常微服务:

订单服务(客户端LB) → 直连 用户服务实例

无客户端负载均衡:

订单服务 **→ Nginx →**用户服务

7.1 Nginx的局限

Nginx的IP写死在配置里面

  • 加一个用户服务实例 → 要改 Nginx 配置 → reload
  • 下线一个 → 改 Nginx → reload
  • 服务一重启 IP 变了 → Nginx 直接转发到死节点

微服务变成 "静态集群",完全失去弹性。

所有内部调用全部走 Nginx 转发

后果:

  1. Nginx 压力爆炸,内网流量全走它
  2. Nginx 一挂,整个微服务集群全部挂掉
  3. 多一跳转发,性能下降、延迟增加
  4. 连接数暴涨,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
  • 负载均衡策略(轮询 / 随机)

九、其他

关于微服务请求的内容感兴趣的小伙伴可以看以下这篇博客

微服务请求全链路解析:从前端到微服务,一次看懂 Nginx、Nacos、网关与 MVC 拦截器到底干了啥_我知道nginx转发8089端口的请求,那么我如果在nacos加微服务架构下排查8089是哪个-CSDN博客https://blog.csdn.net/l7_yuqi/article/details/159427571?spm=1001.2014.3001.5501

相关推荐
sp42a2 小时前
安卓原生 MQTT 通讯 Java 实现
android·java·mqtt
花千树-0102 小时前
用 Java 实现 RAG 组件化:从 PDF 加载到智能问答全流程
java·开发语言·人工智能·langchain·pdf·aigc·ai编程
赫瑞2 小时前
Java中的进制转换
java·开发语言
我不是程序猿儿2 小时前
【嵌入式】第2讲:USB CDC 从“插上电脑”到“出现 COM 口”,枚举过程到底发生了什么
服务器·stm32·单片机·嵌入式硬件·电脑·负载均衡
jwt7939279374 小时前
RabbitMQ HAProxy 负载均衡
rabbitmq·负载均衡·ruby
2601_949816227 小时前
Redis 配置日志
java
遇见你...8 小时前
A01-Spring概述
java·后端·spring
Via_Neo10 小时前
JAVA中以2为底的对数表示方式
java·开发语言
野生技术架构师11 小时前
一线大厂Java面试八股文全栈通关手册(含源码级详解)
java·开发语言·面试