从底层逻辑到实战落地:服务端与客户端负载均衡器的深度拆解
在分布式系统架构中,负载均衡器就像 “交通指挥官”,决定着请求如何分配到后端服务节点。随着微服务、云原生技术的普及,服务端负载均衡器(如 Nginx)和客户端负载均衡器(如 Spring Cloud LoadBalancer)的应用场景越来越广泛,但很多开发者仅停留在 “会用” 层面,对两者的底层差异、选型逻辑和实战细节理解不深。本文将从定义、原理、算法、实战四个维度,用通俗语言讲透核心差异,搭配可直接运行的代码案例和架构图,帮你既夯实基础又解决实际问题。
一、先搞懂基础:什么是负载均衡器?
在分布式系统中,单个服务节点的处理能力有限(如 CPU、内存、网络带宽),当请求量超过节点承载上限时,会出现响应延迟、服务宕机等问题。负载均衡器(Load Balancer) 的核心作用是:将海量请求 “均匀” 分配到多个后端服务节点,实现 “削峰填谷”,同时保证服务的高可用、高并发和扩展性。
从 “决策位置” 来看,负载均衡器主要分为两类:
- 服务端负载均衡器:决策逻辑在独立的 “中间服务” 中(如 Nginx、HAProxy),客户端无需感知后端节点,所有请求先发送到负载均衡器,再由其转发。
- 客户端负载均衡器:决策逻辑在 “客户端代码” 中(如 Spring Cloud LoadBalancer),客户端先获取后端所有节点列表,再自行决定将请求发送到哪个节点。
为了更直观理解两者的定位,先看一张架构对比图:

从架构图可见,两者的核心差异在于 “负载均衡决策的执行位置” 和 “是否依赖服务注册中心”。接下来,我们分别深入拆解两者的底层逻辑。
二、服务端负载均衡器:集中式的 “交通枢纽”
服务端负载均衡器是最传统、最广泛的负载均衡方案,本质是一个 “集中式调度中心”。客户端完全无需关心后端节点,只需将请求发送到负载均衡器,剩下的转发逻辑全由负载均衡器处理。
2.1 核心原理:三步完成请求转发
服务端负载均衡器的工作流程可拆解为 “接收请求→选择节点→转发请求” 三步,具体逻辑如下:

关键特点:
- 集中式决策:所有请求的转发逻辑由单个(或集群)负载均衡器统一处理,客户端无感知。
- 无需服务注册中心:后端节点列表通常配置在负载均衡器中(如 Nginx 的
upstream),无需额外组件。 - 存在单点风险:单个负载均衡器故障会导致整个服务不可用,因此生产环境需部署集群(如 Nginx+Keepalived)。
2.2 典型组件:Nginx 与 HAProxy
服务端负载均衡器的主流组件有 Nginx(轻量、高性能)和 HAProxy(支持 TCP/UDP,适合复杂场景),两者均开源且稳定,以下为实战配置案例。
2.2.1 Nginx 实战:HTTP 请求负载均衡
Nginx 是目前最流行的服务端负载均衡器,支持 HTTP/HTTPS 协议,常用负载均衡算法包括轮询、权重、IP 哈希等。以下是基于 Nginx 1.25.3(最新稳定版)的配置案例,实现对 3 个后端 Java 服务的负载均衡。
1. 安装 Nginx
# 以CentOS 8为例
yum install -y nginx
systemctl start nginx
systemctl enable nginx
2. 配置负载均衡(/etc/nginx/nginx.conf)
# 全局配置
worker_processes auto; # 自动匹配CPU核心数
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;events {worker_connections 1024; # 每个worker进程的最大连接数
}http {include /etc/nginx/mime.types;default_type application/octet-stream;# 日志格式log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /var/log/nginx/access.log main;sendfile on;keepalive_timeout 65;# 后端服务节点列表(upstream是Nginx负载均衡的核心模块)upstream java-service {# 1. 轮询(默认):按顺序依次转发server 192.168.1.101:8080;server 192.168.1.102:8080;server 192.168.1.103:8080;# 2. 权重(weight):权重越高,接收请求越多(适合节点性能不均场景)# server 192.168.1.101:8080 weight=5;# server 192.168.1.102:8080 weight=3;# server 192.168.1.103:8080 weight=2;# 3. IP哈希(ip_hash):同一IP的请求始终转发到同一节点(解决会话共享问题)# ip_hash;# 4. 健康检查:失败3次后标记为宕机,30秒后重试max_fails 3;fail_timeout 30s;}# 虚拟主机配置server {listen 80;server_name localhost;# 所有请求转发到upstream配置的节点列表location / {proxy_pass http://java-service;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}}
}
3. 验证配置并启动
# 检查配置文件语法
nginx -t
# 重新加载配置(无需重启服务)
nginx -s reload
4. 测试负载均衡效果在 3 个后端节点的8080端口部署一个简单的 Java 接口(返回当前节点 IP),通过curl http://localhost多次请求,观察返回的 IP 是否符合配置的算法(如轮询则依次返回 101、102、103)。
2.2.2 HAProxy 实战:TCP 协议负载均衡
HAProxy 支持 HTTP 和 TCP 协议,适合需要对数据库(如 MySQL 主从)、消息队列(如 RabbitMQ)进行负载均衡的场景。以下是基于 HAProxy 2.9.6(最新稳定版)的 TCP 负载均衡配置,实现对 2 个 MySQL 节点的读写分离。
1. 安装 HAProxy
yum install -y haproxy
systemctl start haproxy
systemctl enable haproxy
2. 配置负载均衡(/etc/haproxy/haproxy.cfg)
# 全局配置
globallog 127.0.0.1 local2 infochroot /var/lib/haproxypidfile /var/run/haproxy.pidmaxconn 4000user haproxygroup haproxydaemon# 默认配置
defaultsmode tcp # 协议模式:tcp(适用于MySQL)log globaloption tcplogoption dontlognulloption redispatchretries 3timeout queue 1mtimeout connect 10stimeout client 1mtimeout server 1mtimeout check 10smaxconn 3000# 监控页面(可通过http://localhost:8080/haproxy?stats访问)
listen statsbind :8080stats uri /haproxy?statsstats auth admin:123456 # 用户名密码# MySQL读写分离配置:读请求转发到从库,写请求转发到主库
listen mysql-writebind :3306 # 对外暴露3306端口(与MySQL默认端口一致)server mysql-master 192.168.1.201:3306 check inter 1000 rise 2 fall 3 # 主库(写)listen mysql-readbind :3307server mysql-slave1 192.168.1.202:3306 check inter 1000 rise 2 fall 3 # 从库1(读)server mysql-slave2 192.168.1.203:3306 check inter 1000 rise 2 fall 3 # 从库2(读)
3. 测试 MySQL 负载均衡
- 写请求:通过
mysql -h localhost -P 3306 -u root -p连接,执行INSERT语句,数据会写入主库(192.168.1.201)。 - 读请求:通过
mysql -h localhost -P 3307 -u root -p连接,执行SELECT语句,请求会轮询转发到从库 202 和 203。
2.3 服务端负载均衡器的优缺点
| 优点 | 缺点 |
|---|---|
| 客户端无感知:无需修改客户端代码,接入成本低 | 存在性能瓶颈:所有请求需经过负载均衡器,高并发下可能成为瓶颈 |
| 配置集中:后端节点变更只需修改负载均衡器配置,无需重启客户端 | 单点风险:单个负载均衡器故障会导致服务不可用,需额外部署集群(如 Keepalived) |
| 支持多种协议:可对 HTTP、TCP、UDP 等协议进行负载均衡 | 无法感知客户端状态:无法根据客户端的网络延迟、负载情况动态调整转发策略 |
| 自带健康检查:无需额外开发,负载均衡器可自动过滤宕机节点 | 转发延迟:多一次网络跳转(客户端→负载均衡器→后端节点),增加毫秒级延迟 |
三、客户端负载均衡器:分布式的 “自主决策”
客户端负载均衡器是微服务架构下的产物,核心是 “将负载均衡逻辑嵌入客户端代码”。客户端先从服务注册中心(如 Nacos、Eureka)获取后端节点列表,再通过内置算法选择节点,直接发送请求,无需经过中间负载均衡器。
3.1 核心原理:四步完成自主决策
客户端负载均衡器的工作流程比服务端多一步 “获取节点列表”,具体逻辑如下:

关键特点:
- 分布式决策:每个客户端独立执行负载均衡逻辑,无集中式瓶颈。
- 依赖服务注册中心:节点列表动态从注册中心获取,支持服务自动发现(如节点新增 / 下线后,客户端无需重启)。
- 低延迟:客户端直接与后端节点通信,减少一次网络跳转,降低延迟。
3.2 典型组件:Spring Cloud LoadBalancer 与 Dubbo LoadBalancer
客户端负载均衡器的主流组件有 Spring Cloud LoadBalancer(Spring Cloud 官方推荐,替代 Ribbon)和 Dubbo LoadBalancer(Dubbo 框架内置),以下为基于 JDK 17、Spring Boot 3.2.0(最新稳定版)的实战案例。
3.2.1 前置准备:搭建服务注册中心(Nacos)
客户端负载均衡器依赖服务注册中心,这里选择 Alibaba Nacos 2.3.2(最新稳定版)作为注册中心,步骤如下:
- 下载并启动 Nacos
# 下载Nacos(Linux版)
wget https://github.com/alibaba/nacos/releases/download/2.3.2/nacos-server-2.3.2.tar.gz
# 解压
tar -zxvf nacos-server-2.3.2.tar.gz
# 启动Nacos(单机模式)
cd nacos/bin
sh startup.sh -m standalone
- 访问 Nacos 控制台打开浏览器访问
http://localhost:8848/nacos,默认用户名 / 密码为nacos/nacos,登录后即可管理服务。
3.2.2 Spring Cloud LoadBalancer 实战:微服务接口调用
Spring Cloud LoadBalancer(SCL)是 Spring Cloud 官方提供的客户端负载均衡器,与 Spring Boot、Nacos 无缝集成,支持轮询、随机、权重等算法。以下实现 “订单服务(客户端)调用用户服务(后端节点)” 的负载均衡案例。
1. 项目结构
spring-cloud-lb-demo/
├── order-service(客户端,集成SCL)
└── user-service(后端服务,部署3个节点)
2. 后端服务:user-service(提供用户查询接口)
2.1 依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>user-service</artifactId><version>1.0.0</version><name>user-service</name><dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Nacos服务注册与发现 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2023.0.0.0</version> <!-- 与Spring Boot 3.2匹配的最新版 --></dependency><!-- Lombok(@Slf4j) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency><!-- Swagger3(接口文档) --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!-- Spring工具类(ObjectUtils、StringUtils) --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>6.1.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.2.0</version><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
2.2 配置文件(application.yml)
server:port: ${PORT:8081} # 端口可通过环境变量指定,方便部署多节点spring:application:name: user-service # 服务名(注册到Nacos的名称)cloud:nacos:discovery:server-addr: localhost:8848 # Nacos注册中心地址# Swagger3配置
springdoc:api-docs:path: /api-docsswagger-ui:path: /swagger-ui.html
2.3 启动类(UserServiceApplication.java)
package com.example.userservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;/*** 用户服务启动类* 作者:ken*/
@SpringBootApplication
@EnableDiscoveryClient // 开启服务注册与发现
@OpenAPIDefinition(info = @Info(title = "用户服务接口文档",version = "1.0.0",description = "提供用户查询、新增等接口")
)
public class UserServiceApplication {public static void main(String[] args) {SpringApplication.run(UserServiceApplication.class, args);}
}
2.4 控制器(UserController.java)
package com.example.userservice.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;/*** 用户控制器* 作者:ken*/
@RestController
@RequestMapping("/user")
@Slf4j
@Tag(name = "用户接口", description = "用户查询相关接口")
public class UserController {@Value("${server.port}")private String serverPort; // 当前服务端口(用于区分不同节点)/*** 根据用户ID查询用户信息* @param userId 用户ID(不能为空)* @return 包含用户ID和当前服务端口的字符串*/@GetMapping("/{userId}")@Operation(summary = "查询用户信息", description = "根据用户ID查询用户,返回当前处理请求的服务端口")public String getUserById(@Parameter(description = "用户ID", required = true)@PathVariable String userId) {// 校验用户ID非空(遵循阿里巴巴开发手册:字符串判空用StringUtils.hasText)StringUtils.hasText(userId, "用户ID不能为空");String result = String.format("用户ID:%s,处理节点端口:%s", userId, serverPort);log.info("查询用户信息:{}", result);return result;}
}
2.5 部署多节点
通过指定不同端口,启动 3 个 user-service 节点:
# 节点1:端口8081
java -jar user-service-1.0.0.jar --PORT=8081
# 节点2:端口8082
java -jar user-service-1.0.0.jar --PORT=8082
# 节点3:端口8083
java -jar user-service-1.0.0.jar --PORT=8083
启动后,登录 Nacos 控制台(http://localhost:8848/nacos),在 “服务管理→服务列表” 中可看到user-service有 3 个实例,状态为 “健康”。
3. 客户端:order-service(集成 SCL 调用 user-service)
3.1 依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>order-service</artifactId><version>1.0.0</version><name>order-service</name><dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Nacos服务注册与发现 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2023.0.0.0</version></dependency><!-- Spring Cloud LoadBalancer(客户端负载均衡) --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId><version>4.1.0</version> <!-- 与Spring Boot 3.2匹配的最新版 --></dependency><!-- RestTemplate(用于调用HTTP接口) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><!-- Lombok(@Slf4j) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency><!-- Swagger3 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!-- Spring工具类 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>6.1.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.2.0</version><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
3.2 配置文件(application.yml)
server:port: 8090 # 订单服务端口spring:application:name: order-servicecloud:nacos:discovery:server-addr: localhost:8848loadbalancer:nacos:enabled: true # 启用Nacos作为负载均衡的服务发现源clients:user-service: # 针对user-service的负载均衡配置nlb:enabled: falsehealth-check:enabled: true # 启用健康检查interval: 5000 # 健康检查间隔(5秒)
3.3 配置 RestTemplate(集成 SCL)
package com.example.orderservice.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;/*** RestTemplate配置类(集成Spring Cloud LoadBalancer)* 作者:ken*/
@Configuration
public class RestTemplateConfig {/*** 创建RestTemplate实例,并添加@LoadBalanced注解启用客户端负载均衡* @return RestTemplate实例*/@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}
3.4 订单服务控制器(OrderController.java)
package com.example.orderservice.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;/*** 订单控制器(调用用户服务)* 作者:ken*/
@RestController
@RequestMapping("/order")
@Slf4j
@Tag(name = "订单接口", description = "订单相关接口,包含调用用户服务的逻辑")
public class OrderController {@Autowiredprivate RestTemplate restTemplate;// 用户服务的服务名(无需写IP和端口,SCL会自动解析为具体节点)private static final String USER_SERVICE_URL = "http://user-service/user/%s";/*** 根据订单ID查询订单(内部调用用户服务)* @param orderId 订单ID* @param userId 用户ID* @return 订单信息+用户服务处理节点*/@GetMapping("/{orderId}/user/{userId}")@Operation(summary = "查询订单及用户信息", description = "根据订单ID和用户ID查询,内部调用用户服务")public String getOrderWithUser(@Parameter(description = "订单ID", required = true)@PathVariable String orderId,@Parameter(description = "用户ID", required = true)@PathVariable String userId) {// 校验参数StringUtils.hasText(orderId, "订单ID不能为空");StringUtils.hasText(userId, "用户ID不能为空");// 调用用户服务(使用服务名而非IP,SCL会自动进行负载均衡)String userResult = restTemplate.getForObject(String.format(USER_SERVICE_URL, userId),String.class);String result = String.format("订单ID:%s,%s", orderId, userResult);log.info("查询订单信息:{}", result);return result;}
}
3.5 启动类(OrderServiceApplication.java)
package com.example.orderservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;/*** 订单服务启动类* 作者:ken*/
@SpringBootApplication
@EnableDiscoveryClient
@OpenAPIDefinition(info = @Info(title = "订单服务接口文档",version = "1.0.0",description = "提供订单查询接口,内部调用用户服务")
)
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}
}
3.6 测试负载均衡效果
- 启动 order-service(端口 8090)。
- 通过 Swagger3 接口文档测试:访问
http://localhost:8090/swagger-ui.html,找到/order/{orderId}/user/{userId}接口,多次调用(如 orderId=1001,userId=2001)。 - 观察返回结果:处理节点端口会依次为 8081、8082、8083(SCL 默认使用轮询算法),说明负载均衡生效。
3.2.3 Dubbo LoadBalancer 实战:RPC 接口负载均衡
Dubbo 是阿里巴巴开源的 RPC 框架,内置了客户端负载均衡器,支持轮询、随机、一致性哈希等算法。以下实现 “商品服务(客户端)调用库存服务(后端节点)” 的 RPC 负载均衡案例,基于 Dubbo 3.3.0(最新稳定版)。
1. 项目结构
dubbo-lb-demo/
├── product-service(客户端,Dubbo消费者)
└── stock-service(后端服务,Dubbo提供者,部署2个节点)
2. 后端服务:stock-service(Dubbo 提供者)
2.1 依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/></parent><groupId>com.example</groupId><artifactId>stock-service</artifactId><version>1.0.0</version><name>stock-service</name><dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Dubbo Spring Boot Starter --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>3.3.0</version></dependency><!-- Nacos注册中心适配Dubbo --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-nacos</artifactId><version>3.3.0</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency><!-- Swagger3 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.2.0</version><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
2.2 配置文件(application.yml)
server:port: ${PORT:8084}spring:application:name: stock-servicedubbo:application:name: stock-serviceregistry:address: nacos://localhost:8848 # 注册中心地址protocol:name: dubbo # 协议名称port: ${DUBBO_PORT:20880} # Dubbo服务端口(默认20880)scan:base-packages: com.example.stockservice.service # 扫描Dubbo服务实现类
2.3 Dubbo 服务接口(StockService.java)
package com.example.stockservice.service;/*** 库存服务接口(Dubbo服务)* 作者:ken*/
public interface StockService {/*** 根据商品ID查询库存* @param productId 商品ID* @return 库存数量+处理节点端口*/String getStockByProductId(String productId);
}
2.4 Dubbo 服务实现类(StockServiceImpl.java)
package com.example.stockservice.service.impl;import com.example.stockservice.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;/*** 库存服务实现类(Dubbo服务提供者)* 作者:ken*/
@DubboService(version = "1.0.0") // 标记为Dubbo服务,指定版本
@Slf4j
public class StockServiceImpl implements StockService {@Value("${server.port}")private String serverPort;@Value("${dubbo.protocol.port}")private String dubboPort;/*** 查询商品库存* @param productId 商品ID(不能为空)* @return 库存信息*/@Overridepublic String getStockByProductId(String productId) {StringUtils.hasText(productId, "商品ID不能为空");// 模拟库存数量(随机100-200之间)int stock = (int) (Math.random() * 100 + 100);String result = String.format("商品ID:%s,库存数量:%d,处理节点(HTTP端口:%s,Dubbo端口:%s)",productId, stock, serverPort, dubboPort);log.info("查询库存:{}", result);return result;}
}
2.5 启动类(StockServiceApplication.java)
package com.example.stockservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;/*** 库存服务启动类* 作者:ken*/
@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "库存服务接口文档",version = "1.0.0",description = "Dubbo库存服务提供者")
)
public class StockServiceApplication {public static void main(String[] args) {SpringApplication.run(StockServiceApplication.class, args);}
}
2.6 部署多节点
启动 2 个 stock-service 节点,指定不同的 HTTP 端口和 Dubbo 端口:
# 节点1:HTTP端口8084,Dubbo端口20880
java -jar stock-service-1.0.0.jar --PORT=8084 --DUBBO_PORT=20880
# 节点2:HTTP端口8085,Dubbo端口20881
java -jar stock-service-1.0.0.jar --PORT=8085 --DUBBO_PORT=20881
登录 Nacos 控制台,在 “服务管理→服务列表” 中可看到providers:com.example.stockservice.service.StockService:1.0.0有 2 个实例。
3. 客户端:product-service(Dubbo 消费者)
3.1 依赖配置(pom.xml)
与 stock-service 一致,只需确保 Dubbo 和 Nacos 依赖版本匹配。
3.2 配置文件(application.yml)
server:port: 8091spring:application:name: product-servicedubbo:application:name: product-serviceregistry:address: nacos://localhost:8848consumer:timeout: 3000 # 调用超时时间(3秒)loadbalance: roundrobin # 负载均衡算法(roundrobin=轮询,random=随机,consistenthash=一致性哈希)
3.3 引入 Dubbo 服务接口(需与提供者一致)
直接复制 stock-service 的StockService.java接口到 product-service 的相同包路径下(com.example.stockservice.service)。
3.4 商品服务控制器(ProductController.java)
package com.example.productservice.controller;import com.example.stockservice.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;/*** 商品控制器(调用库存服务)* 作者:ken*/
@RestController
@RequestMapping("/product")
@Slf4j
@Tag(name = "商品接口", description = "商品相关接口,内部调用库存服务(Dubbo)")
public class ProductController {// 引用Dubbo服务(version需与提供者一致)@DubboReference(version = "1.0.0")private StockService stockService;/*** 根据商品ID查询商品及库存* @param productId 商品ID* @return 商品信息+库存信息*/@GetMapping("/{productId}/stock")@Operation(summary = "查询商品及库存", description = "根据商品ID查询,内部调用Dubbo库存服务")public String getProductWithStock(@Parameter(description = "商品ID", required = true)@PathVariable String productId) {StringUtils.hasText(productId, "商品ID不能为空");// 调用Dubbo库存服务(负载均衡由Dubbo自动处理)String stockResult = stockService.getStockByProductId(productId);String result = String.format("商品查询成功,%s", stockResult);log.info("查询商品及库存:{}", result);return result;}
}
3.5 启动类(ProductServiceApplication.java)
package com.example.productservice;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;/*** 商品服务启动类* 作者:ken*/
@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "商品服务接口文档",version = "1.0.0",description = "Dubbo库存服务消费者")
)
public class ProductServiceApplication {public static void main(String[] args) {SpringApplication.run(ProductServiceApplication.class, args);}
}
3.6 测试负载均衡效果
访问http://localhost:8091/swagger-ui.html,调用/product/{productId}/stock接口(如 productId=3001),多次请求后观察返回结果:处理节点会在 8084(Dubbo 20880)和 8085(Dubbo 20881)之间轮询,说明 Dubbo LoadBalancer 生效。
3.3 客户端负载均衡器的优缺点
| 优点 | 缺点 |
|---|---|
| 无集中式瓶颈:每个客户端独立决策,支持高并发 | 客户端侵入性:需在客户端集成负载均衡逻辑(如引入 SCL、Dubbo 依赖) |
| 低延迟:直接与后端节点通信,减少一次网络跳转 | 配置分散:每个客户端需单独配置负载均衡算法,变更成本高 |
| 动态感知节点:通过服务注册中心实时获取节点列表,支持自动扩缩容 | 健康检查依赖客户端:需客户端自行实现或依赖框架的健康检查逻辑 |
| 支持个性化算法:可根据客户端需求自定义负载均衡策略(如基于延迟的算法) | 客户端版本依赖:需确保所有客户端的负载均衡组件版本一致,避免兼容性问题 |
四、核心差异对比:一张表讲透关键区别
服务端与客户端负载均衡器的差异体现在多个维度,以下从 “决策位置”“依赖组件”“性能” 等 10 个核心维度进行对比,帮你快速理清两者的定位:
| 对比维度 | 服务端负载均衡器(如 Nginx) | 客户端负载均衡器(如 SCL) |
|---|---|---|
| 决策位置 | 独立的中间服务(集中式) | 客户端代码(分布式) |
| 依赖组件 | 无需服务注册中心(节点列表配置在负载均衡器) | 必须依赖服务注册中心(如 Nacos、Eureka) |
| 网络跳转 | 2 次(客户端→负载均衡器→后端节点) | 1 次(客户端→后端节点) |
| 性能瓶颈 | 有(所有请求经过负载均衡器,需部署集群) | 无(分布式决策,水平扩展) |
| 客户端侵入性 | 无(客户端无需修改代码) | 有(需集成负载均衡组件) |
| 配置管理 | 集中式(修改负载均衡器配置即可) | 分散式(每个客户端需单独配置) |
| 健康检查 | 负载均衡器自带(如 Nginx 的max_fails) | 客户端或框架实现(如 SCL 的健康检查) |
| 适用场景 | 1. 非微服务架构(如传统单体服务集群)2. 对客户端无侵入要求的场景3. TCP/UDP 协议负载均衡(如 MySQL、RabbitMQ) | 1. 微服务架构(如 Spring Cloud、Dubbo)2. 高并发、低延迟要求的场景3. 需要动态扩缩容的场景 |
| 典型组件 | Nginx、HAProxy、F5(硬件) | Spring Cloud LoadBalancer、Dubbo LoadBalancer、Ribbon(已淘汰) |
| 故障影响 | 负载均衡器集群故障会导致整个服务不可用 | 单个客户端故障仅影响自身,不影响其他客户端 |
五、选型建议:什么时候用哪种?
负载均衡器的选型没有 “绝对正确”,需结合业务场景、架构特点和性能需求综合判断,以下为具体选型建议:
5.1 优先选服务端负载均衡器的场景
- 传统单体服务集群:如 Java 单体服务部署多个节点,客户端为 Web 前端或移动端,无需侵入客户端代码,Nginx 是最佳选择。
- TCP/UDP 协议负载均衡:如 MySQL 主从读写分离、RabbitMQ 集群负载均衡,HAProxy 支持 TCP 协议,比客户端负载均衡器更合适。
- 对客户端无侵入要求的场景:如第三方服务调用(无法修改第三方客户端代码),只能通过服务端负载均衡器转发请求。
- 需要统一流量控制的场景:如限流、熔断、SSL 卸载(HTTPS 转 HTTP),Nginx 可通过插件(如 ngx_http_limit_req_module)统一实现,无需在每个客户端开发。
5.2 优先选客户端负载均衡器的场景
- 微服务架构:如 Spring Cloud、Dubbo 微服务,服务节点动态扩缩容频繁,客户端需实时获取节点列表,SCL/Dubbo LoadBalancer 更适配。
- 高并发、低延迟要求的场景:如秒杀系统、实时交易系统,客户端直接与后端节点通信,减少一次网络跳转,降低延迟。
- 需要个性化负载均衡算法的场景:如基于客户端网络延迟、节点负载的动态算法,客户端负载均衡器可自定义策略(如 SCL 可实现
ReactorServiceInstanceLoadBalancer接口)。 - 分布式部署的客户端集群:如多个微服务客户端(订单、商品、支付)调用同一后端服务,客户端负载均衡器可实现分布式决策,避免集中式瓶颈。
5.3 混合使用场景
在复杂架构中,服务端与客户端负载均衡器可混合使用,实现 “多层负载均衡”,例如:
- 场景:Spring Cloud 微服务架构,前端 Web 请求先经过 Nginx(服务端负载均衡),再由微服务客户端通过 SCL 调用后端服务(客户端负载均衡)。
- 优势:Nginx 负责前端请求的限流、SSL 卸载和第一层负载均衡,SCL 负责微服务间的动态负载均衡,兼顾 “无侵入” 和 “低延迟”。
六、常见问题与避坑指南
在实际使用中,负载均衡器可能会遇到 “会话共享”“健康检查失效” 等问题,以下为常见问题及解决方案:
6.1 服务端负载均衡器:Nginx 会话共享问题
问题描述:使用 Nginx 的轮询算法时,同一用户的多次请求可能转发到不同节点,导致会话(Session)丢失(如登录状态失效)。
解决方案:
- IP 哈希算法:在 Nginx 的
upstream中配置ip_hash,同一 IP 的请求始终转发到同一节点(适合客户端 IP 固定的场景,如企业内部系统)。 - 会话共享组件:使用 Redis、Memcached 等分布式缓存存储 Session,所有节点共享 Session 数据(适合公网场景,如电商网站)。
- 示例:Spring Boot 集成 Redis 实现 Session 共享(需引入
spring-session-data-redis依赖)。
- 示例:Spring Boot 集成 Redis 实现 Session 共享(需引入
6.2 客户端负载均衡器:SCL 健康检查失效
问题描述:后端节点宕机后,SCL 仍会将请求转发到宕机节点,导致调用失败。
解决方案:
- 启用 SCL 健康检查:在
application.yml中配置spring.cloud.loadbalancer.health-check.enabled=true,SCL 会定期检查节点健康状态,过滤宕机节点。 - 配合 Nacos 健康检查:在 Nacos 中启用服务健康检查(默认开启),宕机节点会被 Nacos 标记为 “不健康”,SCL 从 Nacos 获取节点列表时会自动排除。
6.3 服务端负载均衡器:Nginx 高并发瓶颈
问题描述:单台 Nginx 在高并发下(如 10 万 QPS)会出现 CPU、内存占用过高,导致响应延迟。
解决方案:
- 部署 Nginx 集群:使用 Keepalived 实现 Nginx 主从切换,避免单点故障,同时通过 DNS 轮询将请求分发到多台 Nginx。
- 优化 Nginx 配置:
- 调整
worker_processes为 CPU 核心数(如worker_processes 8)。 - 增大
worker_connections(如worker_connections 10240)。 - 启用
gzip压缩,减少网络传输量。
- 调整
七、总结
服务端负载均衡器是 “集中式的交通枢纽”,适合传统架构和无侵入场景;客户端负载均衡器是 “分布式的自主决策”,适配微服务和低延迟需求。两者没有 “优劣之分”,只有 “场景之别”。
在实际项目中,建议结合架构特点选择合适的负载均衡器,必要时可混合使用,实现 “多层负载均衡”,兼顾性能、可用性和扩展性。
