Spring Cloud LoadBalancer 详解

        在分布式系统快速发展的当下,服务间的调用日益频繁且复杂。如何合理分配请求流量,避免单个服务节点过载,保障系统的稳定性与高效性,成为关键问题。负载均衡技术便是解决这一问题的重要手段。Spring Cloud LoadBalancer 作为 Spring Cloud 官方推出的负载均衡器,在微服务架构中发挥着至关重要的作用。本文将对其进行详细解析。

一、Spring Cloud LoadBalancer 基本概念

        Spring Cloud LoadBalancer 是 Spring Cloud 生态体系中的一款负载均衡器,它是 Spring Cloud 官方为了替代 Netflix Ribbon 而推出的。其主要功能是在微服务架构中,将客户端的请求均匀地分发到多个服务实例上,从而实现服务的负载均衡,提高系统的可用性和可靠性。

二、核心原理

(一)服务发现机制

        Spring Cloud LoadBalancer 依托于 Spring Cloud 的服务注册与发现组件(如 Eureka、Nacos、Consul 等)来获取服务实例列表。当服务启动时,会向注册中心注册自己的信息(包括服务名称、IP 地址、端口等)。Spring Cloud LoadBalancer 会定期从注册中心拉取服务实例列表,并将其缓存到本地。同时,它也会监听注册中心的服务实例变化事件,当有服务实例新增、下线或发生故障时,能及时更新本地的服务实例列表,以保证获取到的服务实例是可用的。

(二)负载均衡策略

        Spring Cloud LoadBalancer 提供了多种负载均衡策略,用于从服务实例列表中选择一个合适的服务实例来处理当前请求。常见的负载均衡策略如下:

  1. 轮询(Round Robin):按照服务实例的顺序,依次将请求分发到每个服务实例上。这种策略简单直观,适用于所有服务实例性能相近的场景。例如,有 3 个服务实例 A、B、C,请求会按照 A→B→C→A→B→C 的顺序进行分发。
  2. 随机(Random):随机从服务实例列表中选择一个服务实例来处理请求。该策略适用于服务实例性能差异不大,且希望请求分布相对均匀的场景。
  3. 权重(Weighted):为每个服务实例分配一个权重,权重越高的服务实例被选中的概率越大。这种策略可以根据服务实例的性能来分配权重,性能好的实例分配较高权重,适用于服务实例性能存在差异的场景。
  4. 最少连接数(Least Connections):选择当前连接数最少的服务实例来处理请求。该策略能够动态地根据服务实例的负载情况进行请求分发,适用于长连接场景,可有效避免服务实例过载。

三、使用方式

(一)引入依赖

        在 Spring Boot 项目的 pom.xml 文件中引入 Spring Cloud LoadBalancer 的相关依赖。如果使用的是 Spring Cloud Alibaba 生态,可引入如下依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>

同时,还需要引入对应的服务注册与发现组件依赖,如 Nacos 的依赖:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>

(二)进行配置

        在 application.yml 或 application.properties 文件中进行相关配置。主要包括服务注册中心的地址等配置,以 Nacos 为例:

spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848  # Nacos服务注册中心地址application:name: service-consumer  # 当前服务名称

        对于 Spring Cloud LoadBalancer 本身,也可以进行一些自定义配置,如负载均衡策略的配置。可以通过在配置类中定义对应的 Bean 来指定负载均衡策略。

(三)代码中使用

在代码中,可以使用@LoadBalanced注解修饰 RestTemplate,使其具备负载均衡的能力。示例如下:

@Configuration
public class RestTemplateConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}

然后在服务调用的地方,使用 RestTemplate 根据服务名称调用对应的服务:

@Service
public class ConsumerService {@Autowiredprivate RestTemplate restTemplate;public String callProvider() {// service-provider为服务提供者的服务名称return restTemplate.getForObject("http://service-provider/hello", String.class);}
}

四、几种负载均衡方式的代码举例

(一)轮询策略(Round Robin)

轮询策略是 Spring Cloud LoadBalancer 的默认策略之一,可通过如下配置类指定:

@Configuration
public class RoundRobinLoadBalancerConfig {// 配置轮询负载均衡器@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {// 获取服务名称String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 创建并返回轮询负载均衡器实例return new RoundRobinLoadBalancer(// 获取服务实例列表供应商loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),serviceName);}
}

        该配置会使请求按照服务实例的注册顺序依次分发,例如有实例 A、B、C,请求会按 A→B→C→A 的顺序循环分配。

(二)随机策略(Random)

随机策略通过随机选择服务实例处理请求,配置代码如下:

@Configuration
public class RandomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 使用随机负载均衡器return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),serviceName);}
}

        随机策略的核心逻辑是在服务实例列表中随机生成索引并选择对应实例,适用于实例性能相近且需分散请求的场景。

(三)权重策略(Weighted)

Spring Cloud LoadBalancer 默认未提供权重策略,需自定义实现。可通过服务实例元数据配置权重,再实现权重选择逻辑:

        1、服务注册时在元数据中添加权重(以 Nacos 为例,在服务提供者的 application.yml 中配置):

spring:cloud:nacos:discovery:metadata:weight: 3  # 权重值,可根据实例性能设置(如1-10)

        2、自定义权重负载均衡器:

public class WeightedLoadBalancer extends AbstractLoadBalancer<ServiceInstance> {private final String serviceId;private final ServiceInstanceListSupplier supplier;public WeightedLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {super(supplier);this.serviceId = serviceId;this.supplier = supplier;}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {// 获取服务实例列表return supplier.get().next().map(this::getWeightedInstance);}// 根据权重选择实例private Response<ServiceInstance> getWeightedInstance(List<ServiceInstance> instances) {if (instances.isEmpty()) {return new EmptyResponse();}// 计算总权重(此处使用了元数据中的weight)int totalWeight = 0;for (ServiceInstance instance : instances) {totalWeight += getWeight(instance); // 循环调用getWeight获取每个实例的权重}// 随机生成权重范围内的数值int randomWeight = new Random().nextInt(totalWeight) + 1;// 根据权重选择实例(再次使用元数据中的weight)int currentWeight = 0;for (ServiceInstance instance : instances) {currentWeight += getWeight(instance); // 累加每个实例的权重if (currentWeight >= randomWeight) {return new DefaultResponse(instance);}}// 兜底返回第一个实例return new DefaultResponse(instances.get(0));}// 从元数据中获取权重(核心:读取Nacos配置的weight)private int getWeight(ServiceInstance instance) {// 从服务实例的元数据中获取配置的weightString weightStr = instance.getMetadata().get("weight");// 如果未配置则默认权重为1,否则转换为整数return weightStr != null ? Integer.parseInt(weightStr) : 1;}
}

        3、配置自定义权重负载均衡器:

@Configuration
public class WeightedLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class);return new WeightedLoadBalancer(supplier, serviceName);}
}

权重使用说明

  • 元数据中配置的weight会被getWeight方法读取(instance.getMetadata().get("weight"))
  • 计算总权重时,循环调用getWeight累加所有实例的权重值
  • 选择实例时,通过累加权重与随机数对比,权重越高的实例被选中的概率越大

例如:若有两个实例,A 配置weight:3、B 配置weight:1,总权重为 4,A 被选中的概率是 3/4,B 是 1/4

(四)最少连接数策略(Least Connections)

最少连接数策略需跟踪实例的连接数,选择连接数最少的实例,实现如下:

        1、自定义连接数跟踪工具类:

// 连接数跟踪器(单例)
public class ConnectionCounter {private static final ConnectionCounter INSTANCE = new ConnectionCounter();// 存储实例ID与连接数的映射private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();private ConnectionCounter() {}public static ConnectionCounter getInstance() {return INSTANCE;}// 增加实例连接数public void increment(ServiceInstance instance) {String instanceId = getInstanceId(instance);connectionCounts.computeIfAbsent(instanceId, k -> new AtomicInteger(0)).incrementAndGet();}// 减少实例连接数public void decrement(ServiceInstance instance) {String instanceId = getInstanceId(instance);AtomicInteger count = connectionCounts.get(instanceId);if (count != null) {count.decrementAndGet();}}// 获取实例连接数public int getCount(ServiceInstance instance) {return connectionCounts.getOrDefault(getInstanceId(instance), new AtomicInteger(0)).get();}// 生成实例唯一标识(IP:端口)private String getInstanceId(ServiceInstance instance) {return instance.getHost() + ":" + instance.getPort();}
}

        2、自定义最少连接数负载均衡器:

public class LeastConnectionsLoadBalancer extends AbstractLoadBalancer<ServiceInstance> {private final String serviceId;private final ServiceInstanceListSupplier supplier;public LeastConnectionsLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {super(supplier);this.serviceId = serviceId;this.supplier = supplier;}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return supplier.get().next().map(this::getLeastConnectionInstance);}// 选择连接数最少的实例private Response<ServiceInstance> getLeastConnectionInstance(List<ServiceInstance> instances) {if (instances.isEmpty()) {return new EmptyResponse();}ServiceInstance leastInstance = instances.get(0);int minCount = ConnectionCounter.getInstance().getCount(leastInstance);// 遍历找到连接数最少的实例for (ServiceInstance instance : instances) {int count = ConnectionCounter.getInstance().getCount(instance);if (count < minCount) {minCount = count;leastInstance = instance;}}// 增加选中实例的连接数ConnectionCounter.getInstance().increment(leastInstance);return new DefaultResponse(leastInstance);}
}

        3、使用时需在请求完成后减少连接数(以 AOP 为例):

@Aspect
@Component
public class ConnectionCountAspect {// 拦截服务调用方法,在完成后减少连接数@AfterReturning("execution(* com.example.consumer.service.ConsumerService.callProvider(..)) && args(..)")public void afterCall() {// 实际应用中需通过上下文获取当前选中的实例// 此处简化处理,实际需结合负载均衡器的选择结果}
}

        4、配置最少连接数负载均衡器:

@Configuration
public class LeastConnectionsConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class);return new LeastConnectionsLoadBalancer(supplier, serviceName);}
}

五、与 Ribbon 的对比

  1. 出身与支持:Spring Cloud LoadBalancer 是 Spring Cloud 官方推出的,与 Spring Cloud 生态的融合度更高,后续会得到持续的更新和支持。而 Ribbon 是 Netflix 开源的组件,Netflix 已经宣布停止对部分组件的维护,虽然 Ribbon 目前仍可使用,但长期来看,Spring Cloud LoadBalancer 是更好的替代选择。
  2. 功能:两者都能实现基本的负载均衡功能。不过 Spring Cloud LoadBalancer 在设计上更加简洁,同时支持响应式编程,能更好地适配 Spring Cloud 的响应式生态。Ribbon 的功能相对丰富一些,提供了更多的负载均衡策略和配置选项,但也因此显得较为复杂。
  3. 性能:在性能方面,Spring Cloud LoadBalancer 由于设计简洁,在一些场景下表现可能更优。而 Ribbon 由于功能较多,可能会有一定的性能开销。

六、总结与建议

(一)总结

        Spring Cloud LoadBalancer 作为 Spring Cloud 官方的负载均衡器,具有与 Spring Cloud 生态融合度高、支持响应式编程、设计简洁等优势。它通过服务发现机制获取服务实例列表,再结合各种负载均衡策略将请求分发到合适的服务实例,有效实现了服务的负载均衡。

        它适用于大多数微服务架构场景,尤其是在采用 Spring Cloud 响应式生态(如使用 WebFlux)的项目中,能更好地发挥其优势。

(二)建议

  1. 对于新的 Spring Cloud 项目,建议优先选择 Spring Cloud LoadBalancer,以获得更好的官方支持和生态适配。
  2. 在选择负载均衡策略时,要根据实际的业务场景和服务实例的性能情况进行选择。如果服务实例性能相近,轮询或随机策略即可;如果服务实例性能差异较大,可考虑权重策略;如果是长连接场景,最少连接数策略可能更合适。
  3. 在使用过程中,要合理配置服务注册中心的相关参数,确保服务发现的及时性和准确性,从而保证负载均衡的效果。
  4. 自定义负载均衡策略时,需考虑线程安全(如使用 ConcurrentHashMap 存储连接数)和性能开销,避免因策略逻辑复杂导致服务响应延迟。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89217.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux内核内存管理相关的配置参数

Linux内核内存管理相关的配置参数&#xff08;主要位于/proc/sys/vm/目录下&#xff09;&#xff0c;用于调整内存分配、缓存管理、交换机制、OOM&#xff08;内存溢出&#xff09;策略等核心内存行为。以下是对每个参数的详细解释&#xff1a; admin_reserve_kbytes block_dum…

Web开发 01

先放一下自己写的手敲的第一个网站代码&#xff01;~虽然很简单但还是有点成就感&#xff01;&#xff01;开心&#x1f60a;<!DOCTYPE html> <html><head><title>Title!</title><link rel "stylesheet"href "style.css"…

Redis 生产实战 7×24:容量规划、性能调优、故障演练与成本治理 40 条军规

&#xff08;一&#xff09;写在前面&#xff1a;为什么需要“军规” Redis 在测试环境跑得飞快&#xff0c;一到线上就“莫名其妙”抖动&#xff1b;大促前扩容 3 倍&#xff0c;成本却翻 5 倍&#xff1b;一次主从切换&#xff0c;缓存雪崩导致下游 DB 被打挂&#xff1b;开发…

【DOCKER】综合项目 MonitorHub (监控中心)

文章目录1、项目架构图1.1 架构组件2、实际实施2.1 安装docker2.2 编写dockerfile文件2.2.1 Prometheus2.2.2 node_exporter2.2.3 nginxvts模块2.2.4 nginx_exporeter 服务发现文件2.2.5 maridb dockerfile文件2.2.6 镜像总数2.3 具体操作2.3.1 Prometheus组件2.3.2 nginx组件2…

Java List 集合详解:从基础到实战,掌握 Java 列表操作全貌

作为一名 Java 开发工程师&#xff0c;你一定在项目中频繁使用过 List 集合。它是 Java 集合框架中最常用、最灵活的数据结构之一。无论是从数据库查询出的数据&#xff0c;还是前端传递的参数列表&#xff0c;List 都是处理这些数据的首选结构。本文将带你全面掌握&#xff1a…

SGMD辛几何模态分解 直接替换Excel运行包含频谱图相关系数图 Matlab语言!

SGMD辛几何模态分解 直接替换Excel运行包含频谱图相关系数图 Matlab语言算法近几年刚提出&#xff0c;知网还没几个人用&#xff0c;你先用&#xff0c;你就是创新&#xff01;算法新颖小众&#xff0c;用的人很少&#xff0c;包含分解图、频谱图、相关系数图&#xff0c;效果如…

Oracle数据泵详解——让数据迁移像“点外卖”一样简单​

​今天我想和大家聊一个数据库领域的“万能搬运工”——Oracle数据泵&#xff08;Data Pump&#xff09;​。相信很多人都有过这样的经历&#xff1a;业务要上线新系统&#xff0c;得把旧库的数据搬到新环境&#xff1b;或者领导突然要一份3年前的历史数据&#xff0c;可不能影…

Leetcode 03 java

爬楼梯算法现在只看明白动态规划&#xff0c;也没有很难哟&#xff01;&#xff01;题目70. 爬楼梯假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f;java题解class Solution {public int climbStairs(…

怎么删除 wps 的右键菜单

打开 WPS 点击 WPS Office 选项卡&#xff0c;点击右侧全局配置》配置和修复工具点击高级功能定制下的都可以关闭和隐藏点击确定就可以了。

C++:list

一&#xff0c;list的介绍1&#xff0c;list初步&#xff08;1&#xff09;list是 C 标准模板库 (STL) 中的一个双向链表容器。它允许在常数时间内进行任意位置的插入和删除操作&#xff0c;但不支持随机访问。&#xff08;2&#xff09;list容器的底层数据结构为带头双向循环链…

深入理解Collections.addAll方法

文章目录深入理解Collections.addAll方法概述方法定义基本用法1. 向List添加元素2. 向Set添加元素3. 添加数组元素与传统add方法的比较使用传统add方法使用Collections.addAll性能考虑注意事项实际应用场景与Collection.addAll的区别最佳实践总结深入理解Collections.addAll方法…

CISP-PTE 练习题(完整一套)

目录 1、SQL注入 2、文件上传 3、文件包含 4、代码审计 5、命令执行 6、端口扫描 7、sql 写 webshell 8、3389 远程桌面利用 1、SQL注入 sqllabs-less-24 二次注入 2、文件上传 没有对文件后缀进行检测&#xff0c;但是对文件类型有检测&#xff0c;需要使用图片头绕…

Vue3入门-计算属性+监听器

&#x1f3e0;个人主页&#xff1a;Yui_ &#x1f351;操作环境&#xff1a;vscode\node.js &#x1f680;所属专栏&#xff1a;Vue3 文章目录1. 计算属性1.1 computed函数1.2 计算属性VS普通函数1.3 计算属性的完整写法2. 监听器3.总结1. 计算属性 计算属性&#xff08;compu…

Linux Swap区深度解析:为何禁用?何时需要?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Swap区&#xff1a;Linux的"内存救生圈"二、为什么要禁用Swap&#xff1f;性能的隐形杀手三、何时应该使用Swap&#xff1f;不可或缺的场景四、如…

用TensorFlow进行逻辑回归(三)

逻辑回归Logistic regression这个脚本展示如何用TensorFlow求解逻辑回归。 ()ysigmoid(Axb)我们使用低出生重量数据,特别地:y 0 or 1 low birth weightx demographic and medical history dataimport matplotlib.pyplot as pltimport numpy as npimport tensorflow as tfimp…

mingw 编译 assimp v6.0.2 解决编译报错

mingw 编译 assimp v6.0.2 理论上看这个就能满足&#xff1a;在Windows下使用CMakeMinGW64编译Assimp库 环境变量问题 i386 architecture of input file CMakeFiles\assimp.dir/objects.a(assimp.rc.obj)’ is incompatible with i386:x86-64 output collect2.exe: error: ld r…

Windows 11清理C盘方法大全:磁盘清理/禁用休眠/系统还原点/优化大师使用教程

Windows 11清理C盘方法1. 使用磁盘清理工具步骤&#xff1a;按 Win S 搜索“磁盘清理”&#xff0c;打开工具。选择C盘&#xff0c;点击“确定”。勾选需要清理的文件类型&#xff08;如临时文件、系统错误内存转储等&#xff09;&#xff0c;点击“确定”。确认删除操作&…

Rabbitmq Direct Exchange(直连交换机)多个消费者,配置相同的key ,队列,可以保证只有一个消费者消费吗

思考可以保证消费不被重复消费&#xff0c;因为通过轮询一个消息只会投递给一个消费者。但是不是一个消费者消费&#xff0c;而是多个轮询消费在 RabbitMQ 中&#xff0c;如果多个消费者&#xff08;Consumers&#xff09;同时订阅 同一个队列&#xff08;Queue&#xff09;&am…

设计模式是什么呢?

1.掌握设计模式的层次第一层&#xff1a;刚刚学编程不久&#xff0c;听说过什么是设计模式。第二层&#xff1a;有很长时间的编程经验&#xff0c;自己写过很多代码&#xff0c;其中用到了设计模式&#xff0c;但是自己不知道。第三层&#xff1a;学习过设计模式&#xff0c;发…

ThreadLocal使用详解-从源码层面分析

从demo入手看效果 代码Demostatic ThreadLocal tl1 new ThreadLocal();static ThreadLocal tl2 new ThreadLocal();static ThreadLocal tl3 new ThreadLocal();public static void main(String[] args) {tl1.set("123");tl2.set("456");tl3.set("4…