Spring Cloud Loadbalancer 的使用
创始人
2024-12-27 01:03:34
0

一、默认负载均衡策略

Spring Cloud LoadBalancer 默认的负载均衡策略是轮询。

轮询效果示例

我们需要示例一个请求分发到不同的模块上,所以我们需要创建多模块项目。

新建 Spring Boot (3.0.2)的 Maven 项目(JDK 17)(父模块),添加依赖 Spring Web、Nacos Service Discovery、OpenFeign、Cloud LoadBalancer,然后删除 src 与 HELP.md 文档,修改 pom.xml (如删除模块运行)

创建子模块,然后在子模块的 pom.xml 中配置父类,再删除父 pom.xml 包含的声明,最后在父模块中配置子类声明。此时就可以在子模块的配置文件中配置配置中心。

我们先在子模块写一个服务UserController。

package org.example.provider.controller;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  @RestController @RequestMapping("/user") public class UserController {     @Autowired     private ServletWebServerApplicationContext context;      @RequestMapping("/getname")     public String getName(@RequestParam("id") Integer id) {         return "Provider-name-" + id +                 " | port: " + context.getWebServer().getPort();     } } 

由于Loadbalancer默认是轮询的所以我们创建两个实例运行。

接着我们用同样的方式新建consumer子模块,然后在运行类中添加 OpenFeign 的注解(开启OpenFeign),再写声明服务(Service)与调用服务(Controller),然后我们就可以访问对应的服务,可以看到它的轮询效果。

package org.example.consumer.service;  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") public interface UserService {     @RequestMapping("/user/getname")     String getName(@RequestParam("id") Integer id); } 
package org.example.consumer.controller;  import org.example.consumer.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  @RestController public class CallController {     @Autowired     private UserService userService;      @RequestMapping("/getname")     public String getName(@RequestParam("id") Integer id) {         return userService.getName(id);     }  } 

Loadbalancer 轮询的源码

我们需要看 Spring Cloud LoadBalancer 的配置类 LoadBalancerClientConfiguration 的部分源码:

其中 ReactorLoadBalancer 方法非常重要:

    @Bean     @ConditionalOnMissingBean     public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {         String name = environment.getProperty("loadbalancer.client.name");         return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);     }

可以看到默认设置的负载均衡策略是 RoundRobinLoadBalancer (可以直接看最下面的三行代码,即轮询负载均衡算法的实现)。

    public RoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId, int seedPosition) {         this.serviceId = serviceId;         this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;         this.position = new AtomicInteger(seedPosition);     }      public Mono> 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 processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) {         Response serviceInstanceResponse = this.getInstanceResponse(serviceInstances);         if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {             ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());         }          return serviceInstanceResponse;     }      private Response getInstanceResponse(List instances) {         if (instances.isEmpty()) {             if (log.isWarnEnabled()) {                 log.warn("No servers available for service: " + this.serviceId);             }              return new EmptyResponse();         } else if (instances.size() == 1) {             return new DefaultResponse((ServiceInstance)instances.get(0));         } else {             int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;             ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());             return new DefaultResponse(instance);         }     }

二、随机负载均衡策略

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

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

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

  1. 创建随机负载均衡策略。
  2. 设置随机负载均衡策略。

创建随机负载均衡器

和源码类似的,返回ReactorLoadBalancer,通过 new 随机负载均衡 创建出来。那么我们可以直接复制源码然后修改方法名就是创建了随机负载均衡器:

package com.example.comsumer.config;  import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment;  public class RandomLoadBalancerConfig {     @Bean     public ReactorLoadBalancer randomLoadBalancer(             Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {         String name = environment.getProperty("loadbalancer.client.name");         return new RandomLoadBalancer(                 loadBalancerClientFactory.getLazyProvider(name,                         ServiceInstanceListSupplier.class), name);     } } 

设置局部负载均衡策略

找到 comsumer.service 的接口,加注解 @LoadBalancerClient,然后设置对应的参数:

但是设置局部策略在一些版本是无效的,所以我们可以设置全局的负载均衡器

设置全局负载均衡策略

在启动类上设置注解 @LoadBalancerClients,然后设置对应的参数:

三、Nacos 权重负载均衡策略

新建 Nacos 负载均衡器

package com.example.comsumer.config;  import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer; import jakarta.annotation.Resource; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment;  @LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class) public class NacosLoadBalancerConfig {     @Resource     private NacosDiscoveryProperties nacosDiscoveryProperties;      @Bean     public ReactorLoadBalancer nacosLoadBalancer(             Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {         String name = environment.getProperty("loadbalancer.client.name");         return new NacosLoadBalancer(                 loadBalancerClientFactory.getLazyProvider(name,                         ServiceInstanceListSupplier.class), name                 , nacosDiscoveryProperties);     } } 

设置局部负载均衡策略

关掉前面开启的随机全局负载均衡策略。

再在service中修改为nacos局部负载均衡策略 

此时要查看 Nacos 负载均衡策略的效果的话需要在 Nacos中设置权重。

四、自定义负载均衡

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

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

新建负载均衡类(创建自定义负载均衡器)

自定义的我们需要实现顶级类的接口 ReactorServiceInstanceLoadBalancer。

复制顶级类的代码,修改核心策略即可。

package com.example.comsumer.config;  import jakarta.servlet.http.HttpServletRequest; 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; import java.util.concurrent.ThreadLocalRandom;  public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {     private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);     private final String serviceId;     private ObjectProvider serviceInstanceListSupplierProvider;      public CustomLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId) {         this.serviceId = serviceId;         this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;     }      public Mono> 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 processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) {         Response serviceInstanceResponse = this.getInstanceResponse(serviceInstances);         if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {             ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());         }          return serviceInstanceResponse;     }      private Response getInstanceResponse(List instances) {         if (instances.isEmpty()) {             if (log.isWarnEnabled()) {                 log.warn("No servers available for service: " + this.serviceId);             }              return new EmptyResponse();         } else {             // 核心: 自定义随机策略             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);         }     } } 

新建一个负载均衡对象(封装自定义负载均衡器)

package com.example.comsumer.config;  import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment;  public class CustomLoadBalancerConfig {     @Bean     public ReactorLoadBalancer customLoadBalancer(             Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {         String name = environment.getProperty("loadbalancer.client.name");         return new CustomLoadBalancer(                 loadBalancerClientFactory.getLazyProvider(name,                         ServiceInstanceListSupplier.class), name);     } } 

设置负载均衡策略()为服务设置自定义负载均衡器

为了防止冲突,注释掉Nacos的策略。

五、缓存 

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

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

而 Spring Cloud LoadBalancer 中默认开启了缓存服务列表的功能。

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

  1. 缓存的过期时间为 35s。
  2. 缓存保存个数为 256 个。

我们可以通过以下配置来改变这些配置:

相关内容

热门资讯

这一现象值得深思!wepoke... 这一现象值得深思!wepoker俱乐部辅助,epoker底牌透视(透视)模块教程(一贯是真的挂)该软...
这一现象值得深思!德州局脚本,... 这一现象值得深思!德州局脚本,aapoker透视方法(透视)练习教程(一直有挂)1、aapoker透...
据统计!hhpoker脚本,w... 据统计!hhpoker脚本,wepoker辅助是真的吗(透视)教材教程(切实真的是有挂)1、下载好w...
最终!来玩app破解版,aap... 最终!来玩app破解版,aapoker如何设置胜率(透视)指南书教程(真是是有挂);1、许多玩家不知...
据统计!hhpoker德州挂真... 据统计!hhpoker德州挂真的有吗,wejoker辅助机器人(透视)资料教程(一直存在有挂);1、...
近期!xpoker透视辅助,w... 近期!xpoker透视辅助,wpk私人辅助(透视)模块教程(切实真的是有挂)1、xpoker透视辅助...
据相关数据显示!hhpoker... 据相关数据显示!hhpoker德州有挂吗,有哪些免费的wpk透视码(透视)法门教程(都是真的是有挂)...
长期以来!aapoker真的假... 长期以来!aapoker真的假的,大菠萝辅助器(透视)教材教程(其实真的有挂)运aapoker真的假...
截至目前!wepoker新号好... 截至目前!wepoker新号好一点吗,德普之星透视(透视)要领教程(果然真的是有挂)小薇(辅助器软件...
现有说明如下!德普之星透视辅助... 现有说明如下!德普之星透视辅助软件,德普之星透视辅助(透视)大纲教程(一直有挂)1、上手简单,内置详...