微服务学习-负载均衡器 LoadBalancer 实战

ops/2025/2/1 20:16:29/

1. LoadBalancer 是什么?

Spring Cloud LoadBalancer 是 Spring Cloud 官方自己提供的客户端负载均衡器,用来替代 Ribbon。

官方文档:Spring Cloud LoadBalancer :: Spring Cloud Commons

2. LoadBalancer 作用

从注册中心拉去服务列表,例如拉取库存服务列表,让订单根据负载均衡算法(例如轮询)调用某个库存服务。

3. 负载均衡器如何使用

3.1. 订单服务 pom.xml 中添加 LoadBalancer 的依赖

<!-- loadbalancer 负载均衡器依赖-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

3.2. 订单服务的 application.yml 中添加配置,启用 nacos 本地集群优先的负载均衡策略

spring:application:name: icoolkj-mall-usercloud:nacos:discovery:server-addr: icoolkj-mall-nacos-server:8848namespace: dev  # 指定 dev 开发环境命名空间#group: group1 # 指定 group1 分组cluster-name: BJ # 指定集群名称 北京机房username: nacospassword: nacosloadbalancer:nacos:enabled: true  # 官方建议开启 nacos 负载均衡策略(NacosLoadBalancer)#enabled: false  # 默认使用轮询负载策略(RoundRobinLoadBalancer)

3.3. RestTemplate 通过添加 @LoadBalanced 注解接入 LoadBalancer

@Configuration
public class RestTemplateConfig {@LoadBalanced@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}

3.4. 订单服务调用逻辑

RestTemplate 远程调用

String url = "http://localhost:8560/api/order/getOrderByUserId?userId=" + userId

可以改为:

String url = "http://icoolkj-mall-order01/api/order/getOrderByUserId?userId=" + userId

使用微服务名称 icoolkj-mall-order01 代替 localhost:8560(localhost:8561)

4. 负载均衡器策略配置

4.1. 内置的负载均衡器策略

4.1.1. 轮询(默认)

4.1.2. 随机

4.1.3. NacosLoadBalancer

本地集群优先的原则,会先根据 cluster 配置优先选择本地集群,再根据权重选择具体的实例。

注意:如果配置了 spring.cloud.loadbalancer.nacos.enabled=true, 才会选择 NacosLoabalancer 负载均衡算法。

4.1.4. 测试

启动两个及其以上的订单服务,会员服务发起调用,查看负载均衡结果。

4.1.4.1. 将 spring.cloud.loadbalancer.nacos.enabled=false,测试是否按照轮询效果
4.1.4.2. 将 spring.cloud.loadbalancer.nacos.enabled=true,配置 cluster,测试本地集群优先是否生效。

4.2. 如何修改负载均衡策略为随机策略

注意,需要先将 spring.cloud.loadbalancer.nacos.enabled=false,或者去除该项配置。

4.2.1. 通过 @Bean 的方式配置随机的负载均衡策略

可以参考轮询策略的 @Bean 实现,定义 LoadBalancerConfig 配置类但是不要加 @Configuration 注解

// 注意,不需要加 @Configuration
public class LoadBalancerConfig {@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class),name);}
}
4.2.2. 配置随机策略生效方式

方式1:全局生效,所有调用的接口都生效(配置再服务启动类上)

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

方式2:局部生效,只对调用的接口生效(配置在 openFeign 的接口上)


@FeignClient(name = "icoolkj-mall-account", path = "/api/account")
// 设置局部的负载均衡策略
@LoadBalancerClient(name = "icoolkj-mall-account", configuration = LoadBalancerConfig.class)
public interface AccountServiceFeignClient {@PostMapping("/reduce-balance")Result<?> reduceBalance(@RequestBody AccountRequest accountRequest);}

同上,同样的方式也可以配置其他的内置负载均衡器。

4.3. 自定义负载均衡

需求:实现基于 ip hash 的负载均衡

4.3.1. 实现 ip hash 策略的负载均衡器 MyCustomLoadBalancer,可以参考 RandomLoadBalancer 实现
// 定制的负载均衡策略
public class MyCustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);private final String serviceId;private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;/*** @param serviceInstanceListSupplierProvider a provider of* {@link ServiceInstanceListSupplier} that will be used to get available instances* @param serviceId id of the service for which to choose an instance*/public MyCustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;}@SuppressWarnings("rawtypes")@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + serviceId);}return new EmptyResponse();}// 自定义负载均衡策略//获取 Request 对象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String ipAddress = request.getRemoteAddr();log.info("用户 IP:" + ipAddress);int hash = ipAddress.hashCode();// IP 哈希负载均衡算法int index = hash % instances.size();// 得到服务实例的方法ServiceInstance instance = instances.get(index);return new DefaultResponse(instance);}}
4.3.2. 通过 @Bean 的方式配置 ip hash 的负载均衡器策略
// 注意,不需要加 @Configuration
public class LoadBalancerConfig {@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {return new MyCustomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class),name);}
}
4.3.3. 配置 ip hash 负载均衡器生效
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class)
@SpringBootApplication
public class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class, args);}
}

测试,会员服务调用多个订单服务,是否走自定义的 ip hash 策略。

5. RestTempalte + @LoadBalanced 注解使用不当导致的 bug 及其解决方案

问题重现:某些特殊场景,微服务启动期间需要从其他微服务获取一些初始化数据,可能会选择在 Bean 的初始化方法中去调用其他微服务获取数据。

注意:在微服务架构中,通常推荐在服务启动后的业务逻辑中调用其他微服务,而不是在 Bean 的初始化中进行。这是因为 Bean 的初始化阶段可能发生在服务完全就绪之前,此时调用其他微服务可能会遇到各种问题,例如服务尚未注册、网络未就绪等。

演示:在会员服务的 Controller 的初始化方法调用订单服务,会出现什么问题?

@RestController
@RequestMapping("/api/user")
public class UserController implements InitializingBean{@Autowiredprivate RestTemplate restTemplate;@Overridepublic void afterPropertiesSet() throws Exception {String url = "http://icoolkj-mall-order01/api/order/getOrderByUserId?userId=1";Result<List<OrderResponse>> result = restTemplate.getForObject(url, Result.class);log.info("result =>> {}", result.getData().stream().findFirst().toString());}
}

启动会员服务,会启动失败,找不到要调用的微服务名:

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://icoolkj-mall-order01/api/order/getOrderByUserId": icoolkj-mall-order01

原因分析:业务 Bean 初始化期间使用 @LoadBalanced 修饰的 RestTemplate,还没有负载均衡能力,简单理解:此时负载均衡器功能还没生效,不能去调用其他微服务

问题关键:@LoadBalanced 修饰的 RestTemplate 是什么时候具有负载均衡能力的

解决思路:

思路1:在服务启动后的业务逻辑中调用其他微服务,而不是在 Bean 的初始化方法中进行。

思路2:如果不使用 @LoadBalanced 注解,也可以通过添加 LoadBalancerInterceptor 拦截器让 RestTemplate 起到负载均衡的作用。

@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {RestTemplate restTemplate = new RestTemplate();//注入loadBalancerInterceptor拦截器(具有负载均衡的能力)restTemplate.setInterceptors(Arrays.asList(loadBalancerInterceptor));return restTemplate;}}

6. 小结

负载均衡的作用,负载均衡的策略及配置。


http://www.ppmy.cn/ops/154847.html

相关文章

FFmpeg(7.1版本)的基本组成

1. 前言 FFmpeg 是一个非常流行的开源项目&#xff0c;它提供了处理音频、视频以及其他多媒体内容的强大工具。FFmpeg 包含了大量的库&#xff0c;可以用来解码、编码、转码、处理和播放几乎所有类型的多媒体文件。它广泛用于视频和音频的录制、转换、流媒体传输等领域。 2. F…

深入 Rollup:从入门到精通(三)Rollup CLI命令行实战

准备阶段&#xff1a;初始化项目 初始化项目&#xff0c;这里使用的是pnpm&#xff0c;也可以使用yarn或者npm # npm npm init -y # yarn yarn init -y # pnpm pnpm init安装rollup # npm npm install rollup -D # yarn yarn add rollup -D # pnpm pnpm install rollup -D在…

【leetcode详解】T3175(一点反思)

解题心得 要写出一个好的程序&#xff0c;有效解决问题&#xff0c;思路上就不能“太乖” —— 不能被题目的叙述过程所束缚&#xff0c;而是力求细思问题&#xff0c;抽象化问题&#xff0c;并找到背后的逻辑&#xff1b;最后抓住核心对象&#xff0c;去除多余项&#xff0c;…

hive:基本数据类型,关于表和列语法

基本数据类型 Hive 的数据类型分为基本数据类型和复杂数据类型 加粗的是常用数据类型 BOOLEAN出现ture和false外的其他值会变成NULL值 没有number,decimal类似number 如果输入的数据不符合数据类型, 映射时会变成NULL, 但是数据本身并没有被修改 创建表 创建表的本质其实就是在…

深入解析:一个简单的浮动布局 HTML 示例

深入解析&#xff1a;一个简单的浮动布局 HTML 示例 示例代码解析代码结构分析1. HTML 结构2. CSS 样式 核心功能解析1. 浮动布局&#xff08;Float&#xff09;2. 清除浮动&#xff08;Clear&#xff09;3. 其他样式 效果展示代码优化与扩展总结 在网页设计中&#xff0c;浮动…

JAVA实战开源项目:房屋租赁系统(Vue+SpringBoot) 附源码

本文项目编号 T 040 &#xff0c;文末自助获取源码 \color{red}{T040&#xff0c;文末自助获取源码} T040&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 用例设计 六、核…

Django基础之ORM

一.前言 上一节简单的讲了一下orm&#xff0c;主要还是做个了解&#xff0c;这一节将和大家介绍更加细致的orm&#xff0c;以及他们的用法&#xff0c;到最后再和大家说一下cookie和session&#xff0c;就结束了全部的django基础部分 二.orm的基本操作 1.settings.py&#x…

新年新挑战:如何用LabVIEW开发跨平台应用

新的一年往往伴随着各种新的项目需求&#xff0c;而跨平台应用开发无疑是当前备受瞩目的发展趋势。在众多开发工具中&#xff0c;LabVIEW 以其独特的图形化编程方式和强大的功能&#xff0c;为开发跨平台应用提供了有效的途径。本文将深入探讨如何运用 LabVIEW 开发能够在不同操…