【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显示内容查询正常;
相关推荐
木易 士心3 小时前
MyBatis 与 Spring Data JPA 核心对比:选型指南与最佳实践
java·spring·1024程序员节
遥远_4 小时前
电商履约大促峰值应对:核心业务数据预热方案详解
java·spring·1024程序员节·电商大促·数据预热
RoboWizard4 小时前
电脑效能跃升利器 金士顿KVR内存焕新机
java·spring·智能手机·电脑·金士顿
库库8397 小时前
Spring AI 知识点总结
java·人工智能·spring
m0_674031437 小时前
GitHub等平台形成的开源文化正在重塑林语堂
windows·mysql·spring
我叫张土豆8 小时前
Neo4j 版本选型与 Java 技术栈深度解析:Spring Data Neo4j vs Java Driver,如何抉择?
java·人工智能·spring·neo4j
lang201509288 小时前
Spring环境配置与属性管理完全指南
java·python·spring
lang201509288 小时前
Spring Bean作用域全解析
java·后端·spring
秋千码途8 小时前
Spring的@Cacheable取缓存默认实现
java·spring·缓存