一、前言
在微服务架构中,服务间的通信是核心环节,而负载均衡能有效提升服务的可用性和并发能力,声明式服务调用则能简化服务间调用的代码编写。本文将详细讲解 Spring Cloud 中 Ribbon(负载均衡)和 Feign(声明式服务调用)的使用,从自定义负载均衡实现到整合 Ribbon、Feign 的实战操作,全方位解析核心用法。
二、负载均衡 Ribbon
2.1 什么是负载均衡
通俗来讲,负载均衡就是将工作任务(如接口访问请求)分摊到多个操作单元(服务器、组件)上执行,避免单个节点过载,提升系统整体性能。以微服务场景为例,就是将服务消费者(consumer)的请求平均分发到多台服务提供者(provider)上。

2.2 自定义实现负载均衡
2.2.1 创建服务提供者
-
创建工程 :拷贝已有的 nacos_provider 工程,作为基础模板。

-
配置 application.yml :准备两个服务提供者实例,端口分别为 9090 和 9091,注册到 Nacos:
yaml
# 实例1:9090端口 server: port: 9090 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: ribbon-provider # 实例2:9091端口 server: port: 9091 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: ribbon-provider
2.2.2 创建服务消费者
-
创建工程 :拷贝已有的 nacos_consumer 工程。

-
配置 application.yml :
yaml
server: port: 80 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: ribbon-consumer -
编写 Controller 实现自定义负载均衡 :通过 DiscoveryClient 获取服务列表,分别实现随机、轮询两种负载均衡策略:
java
/**
-
消费者控制器
-
负责处理来自客户端的请求,并通过负载均衡调用提供者服务
*/
@RestController
@RequestMapping("/consumer")
public class consumerController {/** RestTemplate用于发起HTTP请求 */
@Autowired
private RestTemplate restTemplate;/** DiscoveryClient用于服务发现,获取可用服务实例列表 */
@Autowired
private DiscoveryClient discoveryClient;/** 轮询算法的当前索引,用于记录上一次访问的服务实例位置 */
private int currentIndex;/**
-
根据用户ID查询用户信息
-
通过负载均衡策略从多个服务实例中选择一个进行调用
-
@param id 用户ID
-
@return 用户对象
*/
@RequestMapping("/findUserById/{id}")
public User findUserById(@PathVariable Integer id){// ==================== 方式一:硬编码方式(已废弃)====================
// 直接指定IP、端口和URL,不具备灵活性和可扩展性
// String url = "http://127.0.0.1:90/provider/findUserById/" + id;
// return restTemplate.getForObject(url,User.class);// ==================== 方式二:服务发现但未实现负载均衡(已废弃)====================
// 固定选择第一个服务实例,无法实现负载分担
// ServiceInstance serviceInstance = discoveryClient.getInstances("nacos-provider").get(0);// ==================== 方式三:基于服务发现的负载均衡(当前使用)====================
// 1. 通过服务名称获取所有可用的服务实例列表
List<ServiceInstance> instanceList = discoveryClient.getInstances("ribbon-provider");// 2. 负载均衡策略1:随机选择一个服务实例
//currentIndex = new Random().nextInt(instanceList.size());// 3. 负载均衡策略2:轮询选择服务实例(交替使用)
// 每次调用时索引递增,超过实例数量后从头开始
currentIndex = (currentIndex + 1) % instanceList.size();// 4. 根据选定的索引获取具体的服务实例
ServiceInstance serviceInstance = instanceList.get(currentIndex);// 5. 构建完整的服务调用URL
String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/provider/findUserById/" + id;// 6. 发起HTTP GET请求并返回结果
return restTemplate.getForObject(url,User.class);
}
-
}
-
2.2.3 测试
分别启用随机、轮询策略,调用接口测试,可看到请求被分发到不同端口的服务提供者实例。

2.3 Ribbon 介绍
2.3.1 什么是 Ribbon
- Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的消费端(comsumer)负载均衡工具,无需手动引入依赖(Nacos 已集成 Ribbon)。
- Ribbon 默认提供多种负载均衡算法(轮询、随机等)

2.3.2 负载均衡策略
Ribbon 的负载均衡核心接口为com.netflix.loadbalancer.IRule,核心实现类:
- 随机策略(RandomRule):从服务清单中随机选择一个实例。
- 轮询策略(RoundRobinRule):线性轮询,依次选择服务实例(默认策略)。
2.4 基于 Ribbon 实现负载均衡
2.4.1 修改 ribbon_consumer 工程
-
配置 ConfigBean :开启 RestTemplate 的负载均衡,自定义负载均衡策略:
java
java/** * Ribbon 负载均衡配置类 * 配置 RestTemplate 和负载均衡策略 */ @Configuration public class ConfigBean { /** * 配置 RestTemplate 并开启负载均衡功能 * @LoadBalanced 注解的工作原理: * 1. Ribbon 会为 RestTemplate 添加一个拦截器(LoadBalancerInterceptor) * 2. 拦截器会拦截所有 HTTP 请求,获取该服务的所有可用实例列表 List<ServiceInstance> * 3. 使用配置的负载均衡算法(IRule)从实例列表中选择一个目标实例 * 4. 将 URL 中的服务名称替换为选中实例的实际 IP 和端口号 * 5. 使用替换后的真实 URL 发起 HTTP 请求 * * 示例: * 原始 URL: http://ribbon-provider/provider/findUserById/1 * 替换后 URL: http://192.168.1.100:9091/provider/findUserById/1 * * @return 开启了负载均衡功能的 RestTemplate 实例 */ @Bean @LoadBalanced // 开启负载均衡,使 RestTemplate 具备服务发现和负载均衡能力 public RestTemplate restTemplate(){ return new RestTemplate(); } /** * 配置负载均衡策略 * Ribbon 提供了多种负载均衡算法,常用的包括: * - RandomRule: 随机策略,从可用服务列表中随机选择一个 * - RoundRobinRule: 轮询策略,按顺序依次选择(默认策略) * 当前配置:使用随机策略(RandomRule) * * @return 负载均衡规则实例 */ @Bean public IRule iRule(){ // 使用随机负载均衡策略 return new RandomRule(); } } -
修改 Controller :无需手动获取服务实例,直接使用服务名调用:
java
运行
package com.hg.controller; import com.hg.pojo.User; import org.springframework.beans.factory.annotation.Autowired; 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; @RestController @RequestMapping(value = "/consumer") public class ConsumerController { @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/getUserById/{id}") public User getUserById(@PathVariable Integer id) { // 直接使用服务名(Nacos注册的名称),无需拼接IP:Port String serviceUrl = "ribbon-provider"; return restTemplate.getForObject("http://" + serviceUrl + "/provider/getUserById/" + id, User.class); } }
2.4.2 测试
启动服务后调用接口,Ribbon 会自动根据配置的策略(如随机)分发请求到不同的服务提供者实例。
三、声明式服务调用 Feign
3.1 背景
使用 RestTemplate 调用远程服务时,需手动拼接 URL 和参数,参数较多时效率低、易出错。Feign 作为声明式 HTTP 客户端,可让远程调用像调用本地方法一样简单。
3.2 Feign 概述
- Feign 是 Spring Cloud 提供的声明式、模板化 HTTP 客户端,支持 Spring MVC 注解。
- Feign 默认集成 Ribbon,天然支持负载均衡。
- Feign = RestTemplate + Ribbon

Feign 的启动器: spring-cloud-starter-openfeign
3.3 Feign 入门实战
3.3.1 创建服务提供者
-
创建工程 :拷贝 ribbon_provider_1 工程,作为 Feign 的服务提供者。

-
配置 application.yml :
yaml
server: port: 9090 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: feign-provider
3.3.2 创建 Feign 接口工程
-
配置 pom.xml :
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> <artifactId>springcloud_parent</artifactId> <groupId>com.hg</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>feign_interface</artifactId> <dependencies> <!--Spring Cloud OpenFeign Starter --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.hg</groupId> <artifactId>springcloud_common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project> -
编写 Feign 接口 :
java
package com.hg.feign; import com.hg.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient(value="feign-provider") @RequestMapping(value = "/provider") public interface UserFeign { @RequestMapping(value = "/getUserById/{id}") public User getUserById(@PathVariable(value="id") Integer id); }
3.3.3 创建服务消费者
-
创建工程 :拷贝 ribbon_consumer 工程。

-
配置 pom.xml :引入 Feign 接口工程依赖:
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> <artifactId>springcloud_parent</artifactId> <groupId>com.hg</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ribbon_consumer</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.hg</groupId> <artifactId>springcloud_common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- Feign接口依赖 --> <dependency> <groupId>com.hg</groupId> <artifactId>feign_interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project> -
配置 application.yml :
yaml
server: port: 80 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: feign-consumer -
编写 Controller :注入 Feign 接口,直接调用方法:
java
package com.hg.controller; import com.hg.feign.UserFeign; import com.hg.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/consumer") public class ConsumerController { @Autowired private UserFeign userFeign; @RequestMapping(value = "/getUserById/{id}") public User getUserById(@PathVariable Integer id) { // 像调用本地方法一样调用远程服务 return userFeign.getUserById(id); } } -
启动类开启 Feign :
java
运行
package com.hg; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients // 开启Feign接口扫描 public class ConsumerApp { public static void main(String[] args) { SpringApplication.run(ConsumerApp.class); } }
3.3.4 测试
启动所有服务,调用消费者接口,可看到请求通过 Feign 转发到服务提供者,且自动实现负载均衡。
3.4 Feign 核心原理
- 注入 Feign 接口到 Spring 容器 :
@EnableFeignClients注解触发 FeignClientsRegistrar 扫描@FeignClient注解的接口,生成动态代理类并注入 IOC 容器。 - 封装请求信息(RequestTemplate):调用 Feign 接口方法时,动态代理会创建 RequestTemplate,封装 URL、参数、请求方式等信息。
- 发起请求:通过 RequestTemplate 生成 Request,结合 Ribbon 负载均衡,由 Client(URLConnection/HttpClient/OkHttp)发起 HTTP 请求。
3.5 Feign 参数传递
Feign 支持多种参数传递方式,适配不同场景:
- RESTful 风格 :使用
@PathVariable拼接 URL。 - URL 参数(? 拼接) :使用
@RequestParam传递参数。 - POJO 参数 :服务提供者使用
@RequestBody接收 JSON 格式的 POJO。
1. RESTful 风格(@PathVariable)
用于 URL 路径中传递参数,例如 /user/{id}。
-
使用方式 :Feign 接口中通过
@PathVariable("参数名")绑定路径变量,参数名必须和服务提供者保持一致。 -
示例:
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Integer id);
}
2. URL 参数(? 拼接,@RequestParam)
用于 URL 后拼接 ?key=value 形式的参数,常用于 GET 请求。
-
使用方式 :Feign 接口中通过
@RequestParam("参数名")传递,支持多个参数、可选参数和默认值。 -
示例:
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@GetMapping("/user/search")
List<User> searchUser(
@RequestParam("name") String name,
@RequestParam(value = "age", required = false) Integer age
);
}
3. POJO 参数(@RequestBody)
用于传递复杂对象,自动序列化为 JSON,服务提供者用 @RequestBody 接收。
-
使用方式 :仅支持 POST/PUT 请求,一个方法只能有一个
@RequestBody参数。 -
示例:
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@PostMapping("/user/save")
Result saveUser(@RequestBody User user);
}
💡 关键注意事项:
- Feign 接口中,
@PathVariable和@RequestParam必须指定value属性,否则可能无法正确绑定参数。 - GET 请求不能使用
@RequestBody,如需传递对象,可改用@RequestParam或@SpringQueryMap。
3.6 Feign 请求超时配置
Feign 默认超时时间较短,可通过配置调整:
yaml
ribbon:
ConnectTimeout: 5000 # 请求连接超时时间(毫秒)
ReadTimeout: 5000 # 请求处理超时时间(毫秒)
四、总结
- Ribbon 是消费端负载均衡工具,支持自定义策略,Nacos 已集成,开箱即用。
- Feign 基于声明式编程简化远程调用,默认集成 Ribbon,兼顾易用性和负载均衡能力。
- 实际开发中,Feign+Ribbon 的组合是微服务间通信的主流选择,既能简化代码,又能保证服务的高可用。
通过本文的实战操作,可快速掌握 Ribbon 和 Feign 的核心用法,解决微服务架构中服务调用和负载均衡的核心问题。