【SpringCloud】Ribbon(LoadBalancer ) 和 Feign

文章目录

负载均衡

峰值调用,通过网速汇总,调整网络的速率吞吐,其中LVS是典型的应用

Ribbon简介

SpringCloud Ribbon 是基于NetFlix Ribbon实现的一套客户端负载均衡的工具

Ribbon将负载均衡的位置放在了消费方,和Eureka挂钩,找到合适的节点进行使用;

经调查发现,SpringCloud使用的2024.0.0,对应SpringBoot3.4.5,官方已经完全移除了Ribbon,现在转用loadbalancer,

Ribbon的使用(jdk1.8的环境下是可以的)

三件套:

  • 导入依赖启动器
  • 编写配置文件
  • 启动配置
  • 编写配置类(视情况而定)
  1. 导入依赖:Ribbon、Eureka-Client
xml 复制代码
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-ribbon</artifactId>
  <version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka-server</artifactId>
  <version>1.4.6.RELEASE</version>
</dependency>
  1. 编写配置文件
  2. 启动配置
  3. 测试

LoadBalancer 负载均衡器

Ribbon和LoadBalancer相差不大,大多数内容一致;

LoadBalancer翻译过来的意思是 负载均衡器

正常使用

  1. 导入依赖
xml 复制代码
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-commons</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  <version>4.2.0</version>
</dependency>
  1. 编写配置文件,localhost换成域名
yaml 复制代码
server:
    port: 8080

eureka:
  client:
      register-with-eureka: false # 是否注册自己,消费者不需要向注册中心注册自己
      service-url:
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  1. 配置类 ,使用注解@LoadBalanced
java 复制代码
@Configuration
public class ConfigBean { // 相当于 application.xml

    @Bean
    @LoadBalanced // 开启负载均衡功能
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  1. 启动配置,检查问题,理论上是正常启用

扩展学习

  1. 更改服务消费方的访问地址,在最开始是使用RestTemplate的时候使用的http://localhost:8001/,现在改成服务消费者的名字,之后再在Controller中将对应的url改成服务名http://SPRINGCLOUD-PROVIDER-DEPT
  1. 再次开启所有的服务,查看三个集群是否都可以访问 服务供应者;
  2. 可以之后,访问消费者,使用消费者的Request请求路径访问,测试成功。

实现负载均衡

创建三个服务提供者,使用一样的数据库,数据库Database不一样;

每个服务(8001/8002/8003)都关联多个注册中心(7001/7002/7003);

这样子就会使LoadBalance 进行负载均衡,默认使用轮询调用的方式;

场景:

serve port database Eureka注册中心
8001 8001 DB01 7001/7002/7003
8002 8002 DB02 7001/7002/7003
8003 8003 DB03 7001/7002/7003

这样子在注册中心调用分配的时候会调用8001,之后再调8002,8003;消费者方发出请求后也会从7001/7002/7003中选择一个合适的节点,进行调用;

默认理解的是:

服务消费者 >> 随机的Eureka注册中心 >> 随机的server服务器 >> 返回调用内容数据;

特别的占内存,测试时启动的微服务 使用 3~5个就可以了

自定义负载均衡的算法(LoadBalancer)

详细流程

  1. 确保依赖已完全导入
xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
  1. 实现自定义负载均衡器 ReactorServiceInstanceLoadBalancer
java 复制代码
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.*;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Random;

public class CustomRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public CustomRandomLoadBalancer(
        ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
        String serviceId) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
        .getIfAvailable(NoopServiceInstanceListSupplier::new);

        return supplier.get(request).next()
        .map(serviceInstances -> processInstanceResponse(serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }

        // 自定义逻辑:随机选择
        int index = new Random().nextInt(instances.size());
        ServiceInstance instance = instances.get(index);

        System.out.println("【CustomRandomLoadBalancer】选择了实例: " + instance.getHost() + ":" + instance.getPort());

        return new DefaultResponse(instance);
    }
}
  1. 注册 Spring Bean(按服务名绑定)
java 复制代码
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@LoadBalancerClient(
    value = "springcloud-provider-dept", // ← 指定服务名
    configuration = CustomLoadBalancerConfig.class
)
public class LoadBalancerConfiguration {
    // 可留空
}

class CustomLoadBalancerConfig {

    @Bean
    ReactorServiceInstanceLoadBalancer customLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new CustomRandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
                serviceId
        );
    }
}

这样只有调用 springcloud-provider-dept 时才使用随机算法,其他服务仍用默认轮询算法。

  1. 使用 @LoadBalanced 调用
java 复制代码
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// 调用
restTemplate.getForObject("http://springcloud-provider-dept/dept/list", String.class);

Ribbon的方式(Spring新版不支持)

java 复制代码
// Ribbon 方式(已过时)
public class CustomRandomRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        List<Server> servers = lb.getAllServers();
        // 随机选择...
        return servers.get(new Random().nextInt(servers.size()));
    }
}

// 配置类
@RibbonClient(name = "springcloud-provider-dept", configuration = RibbonConfig.class)
public class ConsumerApp { }

class RibbonConfig {
    @Bean
    public IRule ribbonRule() {
        return new CustomRandomRule();
    }
}

不要混合使用 Ribbon 和 LoadBalancer

Feign简介

在 Spring Cloud 2024.0.0(基于 Spring Boot 3.4.5) 的现代微服务架构中,Feign 依然是实现 声明式 HTTP 客户端 的核心组件,但其底层已从 Ribbon 切换为 Spring Cloud LoadBalancer,并全面适配 Jakarta EE(jakarta.* 包)。

Feign能做些什么

Feign的目的是在编写Java Http客户端更加容易;

Feign是声明式的web Service客户端,再次简化了微服务之间的调用,类似于Controller调用Service,SpringCloud会同时集成Eureka和LoadBalance,再使用Feign作为负载均衡的HTTP的客户端;只需要创建一个接口,就可以添加注解;

面向接口编程是很多开发时候的规范,调用微服务访问有两种方法:

  1. 微服务名字(Ribbon/LoadBalance)
  2. 接口和注解(Feign)

Feign的特点

  • 声明式 REST 客户端:用接口 + 注解代替手动写 RestTemplate 或 WebClient
  • 自动集成负载均衡:通过 @LoadBalanced 机制,结合服务注册中心(如 Eureka/Nacos)实现服务调用。
  • 底层依赖:Spring Cloud OpenFeign(基于 Netflix Feign 封装,但已移除 Ribbon)。

Feigen的使用

前提准备

  1. 新建一个service在api中
  1. 新建一个Feign的服务,copy 80消费端的内容
  2. 删除原来Ribbon/LoadBalance的内容
  3. 在springcloud-api的com.cloud.springcloud.service创建一个service作为远程调用的service
java 复制代码
package com.cloud.springcloud.service;

import com.cloud.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

/**
 *
 * 公用接口 Service
 * FeignClient: 服务调用方
 * name: 服务提供者的服务名
 */
@Service
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {

    @RequestMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id);

    @RequestMapping("/dept/list")
    public List<Dept> list();

    @RequestMapping("/dept/add")
    public boolean add(Dept dept);

    @RequestMapping("/dept/delete/{id}")
    public boolean delete(@PathVariable("id") Long id);

    @RequestMapping("/dept/update")
    public boolean update(Dept dept);
}
  1. 在该service中调用到Mapping是服务提供者那边的,需要再加一个注解@Feign,在这个地方的value是在Eureka中设置的服务名

使用流程

组件导入流程:

  1. 导入依赖
  2. 编写配置文件
  3. 启动配置类
  4. 编写配置类

导入依赖

  • Feign的依赖
  • Eureka的依赖
  • LoadBalancer的依赖(Feign中包含这个,可以不导)
xml 复制代码
<!-- OpenFeign 核心依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- 服务注册发现(以 Eureka 为例) -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- LoadBalancer(Feign 默认依赖,但显式声明更安全) -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

编写逻辑代码,使用注入的方式将Service导入进去

代码如下,注意要给api路径下的DeptClientService加入Component(@Service注解),确保能被扫描到

java 复制代码
@RestController
public class DeptConsumerController {

    // idea 不建议使用Autowired
    //    @Autowired
    private final DeptClientService deptClientService;

    public DeptConsumerController(DeptClientService deptClientService) {
        this.deptClientService = deptClientService;
    }


    // 消费者的请求地址
    @RequestMapping("/consumer/dept/findAll")
    public List<Dept> findAll(){
        return deptClientService.list();
    }

    @RequestMapping("/consumer/dept/add")
    public Object addDept(Dept dept) {
        return deptClientService.add(dept);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Object findById(@PathVariable("id") Long id) {
        return deptClientService.get(id);
    }

    @RequestMapping("/consumer/dept/update")
    public Object updateDept(Dept dept) {
        return deptClientService.update(dept);
    }

    @RequestMapping("/consumer/dept/delete/{id}")
    public Object deleteDept(@PathVariable("id") Long id) {
        return deptClientService.delete(id);
    }

}

编写配置文件

沿用的配置都是之前Eureka所配置完的,不需要改动,注意不要和Ribbon、LoadBalance的内容冲突

PS:Feign就是再对于LoadBalance的再次封装,Feign的底层依旧是沿用的Ribbon/LoadBalance的处理逻辑,使用java思想对其进行了封装。

启动配置,进行测试

  1. 在SpringBoot服务消费者 的启动类上启用 EnableFeignClients
java 复制代码
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.cloud.springcloud.service"})
public class SpringCloudDeptConsumerFeign_80 {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudDeptConsumerFeign_80.class, args);
    }
}
  1. 服务注册启动成功,Eureka页面正常显示,调用内容正常显示;
  2. 服务提供者启动正常,直接调用提供者Service的DB查询正常;
  3. 服务消费者(Feign组件)启动正常,调用URL显示内容查询正常;
相关推荐
卓怡学长1 天前
w269基于spring boot + vue 候鸟监测数据管理系统
java·数据库·spring boot·spring·intellij-idea
Listen·Rain1 天前
向量化详解
java·人工智能·spring·机器学习·ai
添砖java_8571 天前
网页聊天室测试报告
java·spring boot·spring·maven
xixingzhe21 天前
Spring 依赖注入
java·后端·spring
闲猫2 天前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
醇氧2 天前
Spring 容器 Map 注入机制详解
java·后端·spring
ylscode2 天前
Cloudflare Workers Cache 深度解析:边缘缓存如何重塑无服务器性能边界
java·spring·mybatis
沉静的小伙2 天前
Spring事务
java·数据库·spring
微三云 - 廖会灵 (私域系统开发)2 天前
电商系统微服务拆分实战:从单体到Spring Cloud的分布式事务踩坑与补偿方案
分布式·spring cloud·微服务
Wang's Blog2 天前
Java框架快速入门: Spring Bean核心配置与实例化详解
java·开发语言·spring