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<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,核心实现类:

  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<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 的核心用法,解决微服务架构中服务调用和负载均衡的核心问题。

相关推荐
武超杰2 小时前
Ribbon 负载均衡 + Feign 声明式调用 从入门到实战
spring cloud·ribbon·负载均衡
StackNoOverflow2 小时前
SpringCloud的负载均衡
spring cloud·ribbon·负载均衡
博风2 小时前
nginx:负载均衡
运维·nginx·负载均衡
tsyjjOvO2 小时前
【Spring Cloud】负载均衡 Ribbon & 声明式服务调用 Feign
spring cloud·ribbon·负载均衡
Alex艾力的IT数字空间10 小时前
在 Kylin(麒麟)操作系统上搭建 Docker 环境
大数据·运维·缓存·docker·容器·负载均衡·kylin
我学上瘾了10 小时前
Spring Cloud的前世今生
后端·spring·spring cloud
StackNoOverflow19 小时前
Spring Cloud的注册中心和配置中心(Nacos)
后端·spring cloud
大罗LuoSir1 天前
分布式微服务全貌了解-整体架构、特征和需关注解决的问题
java·缓存·微服务·zookeeper·容器·服务发现·负载均衡
前端技术1 天前
负载均衡组件 -loadBalancer 无法获取服务端信息问题
运维·负载均衡