【Spring Cloud】负载均衡 Ribbon & 声明式服务调用 Feign

一、负载均衡 Ribbon

1.1 什么是负载均衡

通俗来讲,负载均衡就是将工作任务、访问请求等负担,分摊到多个服务器、组件等操作单元上执行,核心目标是提升系统吞吐量、降低单节点压力。

举个简单例子:将服务消费者(consumer)的请求平均分发到多台服务提供者(provider)上,避免单一提供者过载。

1.2 自定义实现负载均衡

1.2.1 创建服务提供者
(1)创建工程

拷贝已有的nacos_provider工程,准备搭建多实例的服务提供者。

(2)配置 application.yml

创建两个不同端口的配置文件,注册到同一个 Nacos 服务中心:

  • 实例 1(9090 端口):

yaml:

复制代码
server:
  port: 9090
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.209.129:8848
  application:
    name: ribbon-provider
  • 实例 2(9091 端口):

yaml:

复制代码
server:
  port: 9091
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.209.129:8848
  application:
    name: ribbon-provider
1.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 复制代码
package com.hg.controller;

import com.hg.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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 java.util.List;
import java.util.Random;

@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	private DiscoveryClient discoveryClient;

	private int currentIndex;

	@RequestMapping(value="/getUserById/{id}")
	public User getUserById(@PathVariable Integer id){
		// 获取服务提供者列表
		List<ServiceInstance> serviceList = discoveryClient.getInstances("ribbon-provider");

		// 随机策略:int currentIndex = new Random().nextInt(serviceList.size());
		// 轮询策略
		currentIndex = (currentIndex + 1) % serviceList.size();
		
		ServiceInstance instance = serviceList.get(currentIndex);
		String serviceUrl = instance.getHost() + ":" + instance.getPort();
        System.out.println("serviceUrl:"+serviceUrl);
		String url = "http://"+serviceUrl+"/provider/getUserById/"+id;
		return restTemplate.getForObject(url, User.class);
	}
}
1.2.3 测试

分别启用随机 / 轮询策略,调用接口可看到请求被分发到 9090/9091 不同端口的提供者实例。

1.3 Ribbon 核心介绍

1.3.1 什么是 Ribbon
  • Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的消费端负载均衡工具,无需手动引入依赖(Nacos 已集成)。
  • Ribbon 默认提供多种负载均衡算法(轮询、随机等),开箱即用。
1.3.2 负载均衡策略

Ribbon 的核心负载均衡接口为com.netflix.loadbalancer.IRule,核心实现类:

策略类 功能说明
RandomRule 从服务清单中随机选择一个实例
RoundRobinRule 按照线性轮询方式依次选择实例

1.4 基于 Ribbon 实现负载均衡

1.4.1 修改消费者配置
(1)配置 RestTemplate(开启 Ribbon)
java 复制代码
package com.hg.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;

@Configuration
public class ConfigBean {

	@Bean
	@LoadBalanced // 开启Ribbon负载均衡
	public RestTemplate getRestTemplate(){
		return new RestTemplate();
	}

	// 自定义负载均衡策略(示例:随机)
	@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) {
        // 直接使用服务名,Ribbon自动解析并负载均衡
        String serviceUrl = "ribbon-provider";
        return restTemplate.getForObject("http://" + serviceUrl + "/provider/getUserById/" + id, User.class);
    }
}
1.4.2 测试

启动多实例提供者和消费者,调用接口可看到 Ribbon 自动按配置的策略(随机 / 轮询)分发请求。

二、声明式服务调用 Feign

2.1 背景

使用RestTemplate调用远程接口时,需手动拼接 URL 和参数,参数多时代码繁琐、易出错。Feign 解决了这一问题,让远程调用像调用本地方法一样简单。

2.2 Feign 概述

  • Feign 是 Spring Cloud 提供的声明式、模板化 HTTP 客户端,支持 Spring MVC 注解。
  • Feign 默认集成 Ribbon,开箱即支持负载均衡。

2.3 Feign 快速入门

2.3.1 搭建服务提供者

复用 Ribbon 的提供者工程,修改服务名为feign-provider(9090 端口):

yaml:

复制代码
server:
  port: 9090
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.209.129:8848
  application:
    name: feign-provider
2.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>
        <!-- OpenFeign核心依赖 -->
        <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);
}
2.3.3 搭建 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-consumer</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>
(2)application.yml 配置

yaml:

复制代码
server:
  port: 80
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.209.129:8848
  application:
    name: feign-consumer
(3)编写 Controller
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);
    }
}
(4)启动类开启 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);
    }
}
2.3.4 测试

启动提供者、消费者,调用http://localhost/consumer/getUserById/1,可正常获取数据,且 Feign 自动集成 Ribbon 实现负载均衡。

2.4 Feign 核心原理

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

2.5 Feign 参数传递

参数类型 注解使用 示例
Restful 风格 @PathVariable @RequestMapping("/getUserById/{id}") User getUserById(@PathVariable("id") Integer id);
URL 拼接参数 @RequestParam @RequestMapping("/getUser") User getUser(@RequestParam("name") String name);
POJO 参数 @RequestBody 提供者:@PostMapping("/addUser") void addUser(@RequestBody User user);Feign 接口:同提供者注解

2.6 Feign 请求超时配置

Feign 默认超时时间较短(1 秒),需手动配置:

yaml:

复制代码
ribbon:
  ConnectTimeout: 5000  # 连接超时时间(ms)
  ReadTimeout: 5000     # 数据读取超时时间(ms)

三、总结

  • Ribbon :消费端负载均衡工具,支持自定义策略,通过@LoadBalanced整合RestTemplate
  • Feign:声明式 HTTP 客户端,简化远程调用,默认集成 Ribbon,让接口调用像本地方法一样简单。

两者结合使用,可高效实现微服务间的负载均衡调用,是 Spring Cloud 微服务架构中服务消费的核心组件。

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