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

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

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-user
  cloud:
    nacos:
      discovery:
        server-addr: icoolkj-mall-nacos-server:8848
        namespace: dev  # 指定 dev 开发环境命名空间
        #group: group1 # 指定 group1 分组
        cluster-name: BJ # 指定集群名称 北京机房
        username: nacos
        password: nacos


    loadbalancer:
      nacos:
        enabled: true  # 官方建议开启 nacos 负载均衡策略(NacosLoadBalancer)
        #enabled: false  # 默认使用轮询负载策略(RoundRobinLoadBalancer)

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

@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public 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
    @ConditionalOnMissingBean
    public 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")
    @Override
    public 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
    @ConditionalOnMissingBean
    public 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{

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public 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 {

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

}

6. 小结

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


http://www.kler.cn/a/517713.html

相关文章:

  • Python “字典” 实战案例:5个项目开发实例
  • 将本地项目上传到 GitLab/GitHub
  • 力扣-数组-977 有序数组的平方
  • MySQL命令及用法(精华版)
  • 【EXCEL_VBA_实战】多工作薄合并深入理解
  • H3C-无线WLAN配置案例(二层隧道转发)
  • QT QTableWidget控件 全面详解
  • 【阿里云】使用docker安装nginx后可以直接访问
  • 用wordpress搭建跨境电商独立站后没有询盘该怎么办
  • 深度解析:哪种心磁图技术是心脏检查的精准之选?
  • 【Qt 常用控件】显示类控件2(QLCDNumber、QProgressBar、QCalenderWidget)
  • 【优选算法】6----查找总价格为目标值的两个商品
  • Android OpenGL(八)转场特效
  • Java 异常处理介绍
  • OpenCV imread函数读取图像__实例详解
  • Solon Cloud Gateway 开发:Route 的过滤器与定制
  • uni-app 程序打包 Android apk、安卓夜神模拟器调试运行
  • VScode使用笔记
  • YOLO11改进-模块-引入Restormer模块
  • OpenCV:在图像中添加高斯噪声、胡椒噪声
  • freeswtch目录下modules.conf各个模块的介绍【freeswitch版本1.6.8】
  • 使用 C++ 在深度学习中的应用:如何通过 C++20 构建高效神经网络
  • vue3 中如何监听 props 中的值的变化
  • 自定义脚手架
  • Rust使用tokio(一)
  • 蓝桥杯3520 翻转 | 贪心+分类讨论