Spring Cloud Ribbon 与 Feign 实战:负载均衡与声明式服务调用

一、前言

在微服务架构中,服务间的通信是核心环节,而负载均衡能有效提升服务的可用性和并发能力,声明式服务调用则能简化服务间调用的代码编写。本文将详细讲解 Spring Cloud 中 Ribbon(负载均衡)和 Feign(声明式服务调用)的使用,从自定义负载均衡实现到整合 Ribbon、Feign 的实战操作,全方位解析核心用法。

二、负载均衡 Ribbon

2.1 什么是负载均衡

通俗来讲,负载均衡就是将工作任务(如接口访问请求)分摊到多个操作单元(服务器、组件)上执行,避免单个节点过载,提升系统整体性能。以微服务场景为例,就是将服务消费者(consumer)的请求平均分发到多台服务提供者(provider)上。

2.2 自定义实现负载均衡

2.2.1 创建服务提供者
  1. 创建工程 :拷贝已有的 nacos_provider 工程,作为基础模板。

  2. 配置 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 创建服务消费者
  1. 创建工程 :拷贝已有的 nacos_consumer 工程。

  2. 配置 application.yml

    yaml

    复制代码
    server:
      port: 80
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.209.129:8848
      application:
        name: ribbon-consumer
  3. 编写 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 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,核心实现类:

  1. 随机策略(RandomRule):从服务清单中随机选择一个实例。
  2. 轮询策略(RoundRobinRule):线性轮询,依次选择服务实例(默认策略)。

2.4 基于 Ribbon 实现负载均衡

2.4.1 修改 ribbon_consumer 工程
  1. 配置 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();
        }
    }
  2. 修改 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 创建服务提供者
  1. 创建工程 :拷贝 ribbon_provider_1 工程,作为 Feign 的服务提供者。

  2. 配置 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 接口工程
  1. 配置 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>
  2. 编写 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 创建服务消费者
  1. 创建工程 :拷贝 ribbon_consumer 工程。

  2. 配置 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>
  3. 配置 application.yml

    yaml

    复制代码
    server:
      port: 80
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.209.129:8848
      application:
        name: feign-consumer
  4. 编写 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);
        }
    }
  5. 启动类开启 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 核心原理

  1. 注入 Feign 接口到 Spring 容器@EnableFeignClients注解触发 FeignClientsRegistrar 扫描@FeignClient注解的接口,生成动态代理类并注入 IOC 容器。
  2. 封装请求信息(RequestTemplate):调用 Feign 接口方法时,动态代理会创建 RequestTemplate,封装 URL、参数、请求方式等信息。
  3. 发起请求:通过 RequestTemplate 生成 Request,结合 Ribbon 负载均衡,由 Client(URLConnection/HttpClient/OkHttp)发起 HTTP 请求。

3.5 Feign 参数传递

Feign 支持多种参数传递方式,适配不同场景:

  1. RESTful 风格 :使用@PathVariable拼接 URL。
  2. URL 参数(? 拼接) :使用@RequestParam传递参数。
  3. 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 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 的核心用法,解决微服务架构中服务调用和负载均衡的核心问题。

相关推荐
逆向编程6 小时前
MySQL 负载均衡完整入门实战教程
mysql·adb·负载均衡
Token炼金师1 天前
服务的骨架:网关、负载均衡、容灾、弹性与多机房 —— 推理系统架构五柱
人工智能·系统架构·llm·负载均衡·容灾·kv cache·prefix cache
微三云 - 廖会灵 (私域系统开发)2 天前
电商系统微服务拆分实战:从单体到Spring Cloud的分布式事务踩坑与补偿方案
分布式·spring cloud·微服务
k↑2 天前
SpringCloud + React19 集成Scalar的API文档
后端·spring·spring cloud
章老师说2 天前
NGINX官方谈Lua风险:这其实是两条网关技术路线之争
运维·nginx·负载均衡·lua·openresty
LiaoWL1232 天前
【SpringCloud合集-02】Spring Cloud LoadBalancer 负载均衡器学习笔记
学习·spring cloud·负载均衡
姚不倒3 天前
F5 SSL Profile 证书卸载深入篇
运维·网络协议·负载均衡·ssl·f5
oscar9993 天前
3.4 Nginx 负载均衡——动态再平衡的反人性纪律
nginx·github·负载均衡·财富源代码
姚不倒3 天前
F5 健康检查 Monitor 深入篇
运维·负载均衡·f5
9i编程4 天前
5.Nacos 配置中心化改造与短信 Debug 模式动态切换实践
后端·spring cloud