一、前言在微服务架构中服务间的通信是核心环节而负载均衡能有效提升服务的可用性和并发能力声明式服务调用则能简化服务间调用的代码编写。本文将详细讲解 Spring Cloud 中 Ribbon负载均衡和 Feign声明式服务调用的使用从自定义负载均衡实现到整合 Ribbon、Feign 的实战操作全方位解析核心用法。二、负载均衡 Ribbon2.1 什么是负载均衡通俗来讲负载均衡就是将工作任务如接口访问请求分摊到多个操作单元服务器、组件上执行避免单个节点过载提升系统整体性能。以微服务场景为例就是将服务消费者consumer的请求平均分发到多台服务提供者provider上。2.2 自定义实现负载均衡2.2.1 创建服务提供者创建工程拷贝已有的 nacos_provider 工程作为基础模板。配置 application.yml准备两个服务提供者实例端口分别为 9090 和 9091注册到 Nacosyaml# 实例19090端口 server: port: 9090 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: ribbon-provider # 实例29091端口 server: port: 9091 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: ribbon-provider2.2.2 创建服务消费者创建工程拷贝已有的 nacos_consumer 工程。配置 application.ymlyamlserver: 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. 通过服务名称获取所有可用的服务实例列表 ListServiceInstance 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 什么是 RibbonSpring 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/** * Ribbon 负载均衡配置类 * 配置 RestTemplate 和负载均衡策略 */ Configuration public class ConfigBean { /** * 配置 RestTemplate 并开启负载均衡功能 * LoadBalanced 注解的工作原理 * 1. Ribbon 会为 RestTemplate 添加一个拦截器LoadBalancerInterceptor * 2. 拦截器会拦截所有 HTTP 请求获取该服务的所有可用实例列表 ListServiceInstance * 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 会自动根据配置的策略如随机分发请求到不同的服务提供者实例。三、声明式服务调用 Feign3.1 背景使用 RestTemplate 调用远程服务时需手动拼接 URL 和参数参数较多时效率低、易出错。Feign 作为声明式 HTTP 客户端可让远程调用像调用本地方法一样简单。3.2 Feign 概述Feign 是 Spring Cloud 提供的声明式、模板化 HTTP 客户端支持 Spring MVC 注解。Feign 默认集成 Ribbon天然支持负载均衡。Feign RestTemplate RibbonFeign 的启动器 spring-cloud-starter-openfeign3.3 Feign 入门实战3.3.1 创建服务提供者创建工程拷贝 ribbon_provider_1 工程作为 Feign 的服务提供者。配置 application.ymlyamlserver: port: 9090 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: feign-provider3.3.2 创建 Feign 接口工程配置 pom.xmlxml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd parent artifactIdspringcloud_parent/artifactId groupIdcom.hg/groupId version1.0-SNAPSHOT/version /parent modelVersion4.0.0/modelVersion artifactIdfeign_interface/artifactId dependencies !--Spring Cloud OpenFeign Starter -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency dependency groupIdcom.hg/groupId artifactIdspringcloud_common/artifactId version1.0-SNAPSHOT/version /dependency /dependencies /project编写 Feign 接口javapackage 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(valuefeign-provider) RequestMapping(value /provider) public interface UserFeign { RequestMapping(value /getUserById/{id}) public User getUserById(PathVariable(valueid) Integer id); }3.3.3 创建服务消费者创建工程拷贝 ribbon_consumer 工程。配置 pom.xml引入 Feign 接口工程依赖xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd parent artifactIdspringcloud_parent/artifactId groupIdcom.hg/groupId version1.0-SNAPSHOT/version /parent modelVersion4.0.0/modelVersion artifactIdribbon_consumer/artifactId dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.hg/groupId artifactIdspringcloud_common/artifactId version1.0-SNAPSHOT/version /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency !-- Feign接口依赖 -- dependency groupIdcom.hg/groupId artifactIdfeign_interface/artifactId version1.0-SNAPSHOT/version /dependency /dependencies /project配置 application.ymlyamlserver: port: 80 spring: cloud: nacos: discovery: server-addr: 192.168.209.129:8848 application: name: feign-consumer编写 Controller注入 Feign 接口直接调用方法javapackage 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); } }启动类开启 Feignjava运行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 负载均衡由 ClientURLConnection/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 后拼接?keyvalue形式的参数常用于 GET 请求。使用方式Feign 接口中通过RequestParam(参数名)传递支持多个参数、可选参数和默认值。示例FeignClient(feign-provider) RequestMapping(/provider) public interface UserFeign { GetMapping(/user/search) ListUser 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 默认超时时间较短可通过配置调整yamlribbon: ConnectTimeout: 5000 # 请求连接超时时间毫秒 ReadTimeout: 5000 # 请求处理超时时间毫秒四、总结Ribbon 是消费端负载均衡工具支持自定义策略Nacos 已集成开箱即用。Feign 基于声明式编程简化远程调用默认集成 Ribbon兼顾易用性和负载均衡能力。实际开发中FeignRibbon 的组合是微服务间通信的主流选择既能简化代码又能保证服务的高可用。通过本文的实战操作可快速掌握 Ribbon 和 Feign 的核心用法解决微服务架构中服务调用和负载均衡的核心问题。