当前位置: 首页 > news >正文

3.Spring Cloud LoadBalancer 入门与使用

3.Spring Cloud LoadBalancer 入门与使用

  • 1.什么是 LoadBalancer?
    • 1.1负载均衡分类
    • 1.2 常见负载均衡策略
  • 2.为什么要学 LoadBalancer?
  • 3.如何使用?
  • 4.默认负载均衡策略
  • 5.随机负载均策略
    • 5.1 创建随机负载均衡器
    • 5.2 设置随机负载均衡器 (局部设置)
    • 5.3 设置全局负载均衡器
  • 6.Nacos 权重负载均器
    • 6.1 创建 Nacos 负载均衡器
    • 6.2 设置负载均衡器
  • 7.自定义负载均衡器
    • 7.1 创建自定义负载均衡器
    • 7.2 封装自定义负载均衡器
    • 7.3 设置自定义负载均器
  • 8.缓存
    • 关闭缓存
  • 9.执行原理
      • 底层执行原理
        • 1. `ServiceInstanceListSupplier`
        • 2. `LoadBalancerClient`
        • 3. `LoadBalancer`
      • 执行流程
      • 源码示例
      • 总结

1.什么是 LoadBalancer?

LoadBalancer(负载均衡器)是一种网络设备或软件机制,用于分发传入的网络流量负载(请求)到多个后端目标服务器上,从而实现系统资源的均衡利用和提高系统的可用性和性能。

1.1负载均衡分类

负载均衡分为服务器端负载均衡和客户端负载均衡。

  1. 服务器端负载均衡指的是存放在服务器端的负载均衡器,例如 Nginx、HAProxy、F5 等.
  2. 客户端负载均衡指的是嵌套在客户端的负载均衡器,例如 Ribbon、Spring Cloud LoadBalancer。

1.2 常见负载均衡策略

在这里插入图片描述
但无论是服务器端负载均衡和客户端负载均衡,它们的负载均衡策略都是相同的,因为负载均衡策略本质上是一种思想。
常见的负载均衡策略有以下几个:

  1. 轮询(Round Robin):轮询策略按照顺序将每个新的请求分发给后端服务器,依次循环。这是一种最简单的负载均衡策略,适用于后端服务器的性能相近,且每个请求的处理时间大致相同的情况。
  2. 随机选择(Random):随机选择策略随机选择一个后端服务器来处理每个新的请求。这种策略适用于后端服2务器性能相似,且每个请求的处理时间相近的情况,但不保证请求的分发是均的。
  3. 最少连接(Least Connections):最少连接策略将请求分发给当前连接数最少的后端服务器。这可以确保负载均衡在后端服务器的连接负载上均衡,但需要维护连接计数。
  4. IP 哈希(IP Hash):IP 哈希策略使用客户端的 IP 地址来计算哈希值,然后将请求发送到与哈希值对应的后端服务器。这种策略可用于确保来自同一客户端的请求都被发送到同一台后端服务器,适用于需要会话保持的情况。
  5. 加权轮询(Weighted Round Robin):加权轮询策略给每个后端服务器分配一个权重值,然后按照权重值比例来分发请求。这可以用来处理后端服务器性能不均衡的情况,将更多的请求分发给性能更高的服务器。
  6. 加权随机选择(Weighted Random):加权随机选择策略与加权轮询类似,但是按照权重值来随机选择后端服务器。这也可以用来处理后端服务器性能不均衡的情况,但是分发更随机。
  7. 最短响应时间(Least Response Time):最短响应时间策略会测量每个后端服务器的响应时间,并将请求发送到响应时间最短的服务器。这种策略可以确保客户端获得最快的响应,适用于要求低延迟的应用。

2.为什么要学 LoadBalancer?

作为早期版本中内置的负载均衡器 Ribbon,在 Spring Cloud 2020.0.0 中已经被移除了,更新日志详见,https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2020.0-Release-Notes取而代之的是 Spring Cloud LoadBalancer,并日它也是 Spring cloud 官方提供的负载均衛器,所以咱们的课程就要学习最新最主流的机制栈,而 Spring Cloud LoadBalancer 则是绕不过去的必学知识。

3.如何使用?

在项目中添加 Spring Cloud OpenFeign 和注册中心如 Nacos 之后,再添加 Spring Cloud LoadBalancer 则会在进行接口调用时直接使用 Spring Cloud LoadBalancer。

4.默认负载均衡策略

Spring Cloud LoadBalancer 负载均衡策略默认的是轮询,这一点可以通过 Spring Cloud LoadBalancer 的配置类LoadBalancerClientConfiguration 中发现,它的部分源码如下:

public class LoadBalancerClientConfiguration {private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}

继续查看 RoundRobinLoadBalancer 核心实现源码如下:

	private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + serviceId);}return new EmptyResponse();}// Do not move position when there is only 1 instance, especially some suppliers// have already filtered instancesif (instances.size() == 1) {return new DefaultResponse(instances.get(0));}// Ignore the sign bit, this allows pos to loop sequentially from 0 to// Integer.MAX_VALUEint pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}

在这里插入图片描述

5.随机负载均策略

Spring Cloud LoadBalancer 内置了两种负载均衡策略

  1. 轮询负载均衡策略,默认负载均衡策略。
  2. 随机负载均衡策略

而要实现随机负载均衡策略的步骤如下:

  1. 创建随机负载均衡策略。
  2. 设置随机负载均衡策略。
    在这里插入图片描述
    在这里插入图片描述

5.1 创建随机负载均衡器

public class RandomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}

5.2 设置随机负载均衡器 (局部设置)

package com.example.consumer.service;import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.NacosLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;@Service
@FeignClient("loadbalancer-service")
// 设置局部负载均衡策略
@LoadBalancerClient(name = "loadbalancer-service",configuration = RandomLoadBalancerConfig.class)
public interface UserService {@RequestMapping("/user/getname")String getName(@RequestParam("id") Integer id);
}

5.3 设置全局负载均衡器

package com.example.consumer;import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =RandomLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}

6.Nacos 权重负载均器

Nacos 中支持两种负载均衡器,一种是权重负载均衡器,另一种是第三方 CMDB(地域就近访问)标签负载均後器,我们可以将 Spring Cloud Loadbalancer 直接配置为 Nacos 的负载均衡器,它默认就是权重负载均衡策略。它的配置有以下两步:

  1. 创建 Nacos 负载均衡器
  2. 设置负载均衡器
    在这里插入图片描述

6.1 创建 Nacos 负载均衡器

@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {@Resourceprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Beanpublic ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name,nacosDiscoveryProperties);}
}

6.2 设置负载均衡器

@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =NacosLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}

7.自定义负载均衡器

实现自定义负载均衡策略需要以下 3步:

  1. 创建自定义负载均衡器
  2. 封装自定义负载均衡器
  3. 为服务设置自定义负载均衡器

7.1 创建自定义负载均衡器

package com.example.consumer.config;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import reactor.core.publisher.Mono;import java.util.List;public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);private final String serviceId;private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public CustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;}public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map((serviceInstances) -> {return this.processInstanceResponse(supplier, serviceInstances);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else {// 核心:自定义随机策略// 获取 Request 对象ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String ipAddress = request.getRemoteAddr();System.out.println("用户 IP:" + ipAddress);int hash = ipAddress.hashCode();// 自定义负载均衡策略【这行代码是关键】int index = hash % instances.size();// 得到服务实例方法ServiceInstance instance = (ServiceInstance) instances.get(index);return new DefaultResponse(instance);}}
}

7.2 封装自定义负载均衡器

public class CustomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> customLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new CustomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}

7.3 设置自定义负载均器

@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =CustomLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}

8.缓存

Spring Cloud LoadBalancer 在获取实例时有两种选择:

  1. 即时获取:每次从注册中心得到最新健康的实例,效果好、开销太大。
  2. 缓存服务列表:每次得到服务列表之后,缓存一段时间,这样既能保证性能,同时也能兼容一定的及时性。而 Spring Cloud LoadBalancer 中默认开启了缓存服务列表的功能。Spring

Cloud LoadBalancer 默认缓存的重要特性有两项:

  1. 缓存的过期时间为 35s。
  2. 缓存保存个数为 256 个
    我们可以通过以下配置来改变这些配置:

关闭缓存

    loadbalancer:cache:enabled: true # 关闭 loadbalancer 缓存ttl: 10 # 缓存存活时间capacity: 1000 # 缓存存储容量

9.执行原理

OpenFeign 底层是通过 HTTP 客户端对象 RestTemplate 实现接口请求的,而负载均衡器的作用只是在请求客户端发送请求之前,得到一个服务的地址给到 RestTemplate 对象,而 Spring Cloud LoadBalancer 的整体类图如下:
在这里插入图片描述

通过查看 Spring Cloud LoadBalancer 源码我们可以发现,@LoadBalanced 注解出 spring-cloud-commons 实现查看实现逻辑我们发现, spring-cloud-commons 存在自动配置类 LoadBalancerAutoConfiquration,当满足条件时将自动创建 LoadBalancerInterceptor 并注入到 RestTemplate 中,部分源码如下:

Spring Cloud LoadBalancer 是 Spring Cloud 提供的一种客户端负载均衡解决方案,用于替代 Netflix Ribbon。它通过将负载均衡逻辑从服务端移到客户端,使得每个客户端实例都可以独立地选择要调用的服务实例,从而实现更灵活和高效的负载均衡。

底层执行原理

Spring Cloud LoadBalancer 的核心组件包括 ServiceInstanceListSupplierLoadBalancerClientLoadBalancer。下面结合源码来详细说明其执行原理。

1. ServiceInstanceListSupplier

ServiceInstanceListSupplier 是一个接口,用于提供服务实例列表。它的实现类负责从服务注册中心(如 Eureka、Consul 等)获取可用的服务实例列表。

public interface ServiceInstanceListSupplier {Flux<List<ServiceInstance>> get();
}

Flux 是 Reactor 库中的一个类,表示一个异步序列。ServiceInstanceListSupplierget 方法返回一个 Flux,它会异步地提供服务实例列表。

2. LoadBalancerClient

LoadBalancerClient 是一个接口,定义了负载均衡客户端的基本操作。它的主要方法是 choose,用于选择一个服务实例。

public interface LoadBalancerClient {<T> ServiceInstance choose(String serviceId, Request<T> request);
}

choose 方法接受服务 ID 和请求信息,返回一个 ServiceInstance 对象,表示选择的服务实例。

3. LoadBalancer

LoadBalancer 是负载均衡的核心接口,定义了负载均衡的策略。它的主要方法是 choose,用于根据负载均衡策略选择一个服务实例。

public interface LoadBalancer<T> {Mono<Response<T>> choose(Request request);
}

choose 方法返回一个 Mono<Response<T>>,其中 Mono 是 Reactor 库中的另一个类,表示一个异步的单值序列。

执行流程

  1. 获取服务实例列表

    • ServiceInstanceListSupplier 从服务注册中心获取可用的服务实例列表,并返回一个 Flux<List<ServiceInstance>>
  2. 选择服务实例

    • LoadBalancer 使用负载均衡策略(如轮询、随机等)从服务实例列表中选择一个服务实例。
    • LoadBalancerClient 调用 LoadBalancerchoose 方法,获取选择的服务实例。
  3. 执行请求

    • LoadBalancerClient 使用选择的服务实例执行请求,并返回结果。

源码示例

以下是一个简单的 ServiceInstanceListSupplier 实现示例:

public class SimpleServiceInstanceListSupplier implements ServiceInstanceListSupplier {private final List<ServiceInstance> instances;public SimpleServiceInstanceListSupplier(List<ServiceInstance> instances) {this.instances = instances;}@Overridepublic Flux<List<ServiceInstance>> get() {return Flux.just(instances);}
}

以下是一个简单的 LoadBalancer 实现示例:

public class RoundRobinLoadBalancer implements LoadBalancer<ServiceInstance> {private final AtomicInteger position;private final ServiceInstanceListSupplier supplier;public RoundRobinLoadBalancer(ServiceInstanceListSupplier supplier) {this.supplier = supplier;this.position = new AtomicInteger(0);}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return supplier.get().next().map(instances -> {if (instances.isEmpty()) {return new EmptyResponse();}int pos = Math.abs(this.position.incrementAndGet());ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);});}
}

总结

Spring Cloud LoadBalancer 通过 ServiceInstanceListSupplier 获取服务实例列表,通过 LoadBalancer 选择服务实例,并通过 LoadBalancerClient 执行请求。其核心思想是将负载均衡逻辑从服务端移到客户端,使得每个客户端实例都可以独立地选择要调用的服务实例,从而实现更灵活和高效的负载均衡。

相关文章:

  • ivySCI:最好的文献阅读管理软件!
  • C语言动态内存分配
  • Debug-012-el-popover 使用 doClose() 关闭窗口不生效的处理方案
  • IPv4组播——组播IP,MAC地址,组播网络基本架构
  • 四数相加Ⅱ-力扣
  • 深入理解 Python 中的 `os.walk()`
  • es索引的性能优化配置
  • Linux安装Nginx脚本
  • 【C语言】9.C语言函数栈帧的创建和销毁
  • 大模型蒸馏:高效AI的秘诀
  • ping不通ip的解决方法
  • 2024.05.27【读书笔记】丨生物信息学与功能基因组学(第九章 蛋白质结构与结构基因组学 上)【AI测试版】
  • 【最优化方法】实验三 无约束最优化方法的MATLAB实现
  • STM32Cube系列教程11:使用STM32 RNG硬件随机数模块生成彩票号码
  • Keras深度学习框架第二十四讲:KerasNLP概述
  • ES6指北【2】—— 箭头函数
  • 时间复杂度分析经典问题——最大子序列和
  • 自己简单写的 事件订阅机制
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【mysql】环境安装、服务启动、密码设置
  • Android Volley源码解析
  • Angular 4.x 动态创建组件
  • co模块的前端实现
  • css选择器
  • docker容器内的网络抓包
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • spring security oauth2 password授权模式
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 微信小程序填坑清单
  • 用Visual Studio开发以太坊智能合约
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ​决定德拉瓦州地区版图的关键历史事件
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #QT(TCP网络编程-服务端)
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (C11) 泛型表达式
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (备忘)Java Map 遍历
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (十六)串口UART
  • (转)Linux下编译安装log4cxx
  • ****三次握手和四次挥手
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET Core引入性能分析引导优化
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • @entity 不限字节长度的类型_一文读懂Redis常见对象类型的底层数据结构
  • @font-face 用字体画图标
  • @property括号内属性讲解
  • [1159]adb判断手机屏幕状态并点亮屏幕
  • [2669]2-2 Time类的定义
  • [5] CUDA线程调用与存储器架构
  • [Android View] 可绘制形状 (Shape Xml)