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 个。

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

相关内容

热门资讯

每日必备透视挂!微信微乐辅助免... 每日必备透视挂!微信微乐辅助免费,小程序微乐辅助器免费版-一直真的是有辅助器1、点击下载安装,微信微...
实测揭晓辅助!微信小程序微乐辅... 您好,微信小程序微乐辅助器教程这款游戏可以开挂的,确实是有挂的,需要了解加去威信【136704302...
1.9分钟了解透视挂!微信小程... 1.9分钟了解透视挂!微信小程序微乐辅助器教程,微信微乐挂有没有-一贯存在有辅助软件1、不需要AI权...
发现一款辅助!微信微乐辅助免费... 发现一款辅助!微信微乐辅助免费,微乐兰州麻将小程序辅助-确实是真的有辅助挂1、许多玩家不知道微信微乐...
重大来袭开挂!微乐小程序辅助工... 重大来袭开挂!微乐小程序辅助工具,微信微乐小程序辅助软件-好像真的有辅助app1)微乐小程序辅助工具...
玩家必备科技辅助!微信微乐小程... 玩家必备科技辅助!微信微乐小程序游戏辅助脚本,微乐山西威信小程序-总是是有辅助神器微信微乐小程序游戏...
盘点几款透视挂!小程序微乐游戏... 盘点几款透视挂!小程序微乐游戏辅助器,微乐小程序脚本-其实有辅助技巧1.小程序微乐游戏辅助器 选牌创...
分辨真假辅助!微乐小程序开挂黑... 分辨真假辅助!微乐小程序开挂黑科技,微信小程序微乐修改器-都是存在有辅助教程1、不需要AI权限,帮助...
查到实测透视挂!微信小程序微乐... 查到实测透视挂!微信小程序微乐辅助器,小程序跑得快的技巧-原来真的是有辅助教程1、超多福利:超高返利...
关于透视挂!微信小程序微乐辅助... 关于透视挂!微信小程序微乐辅助器,微乐自建房黑科技辅助工具下载-真是是有辅助技巧该软件可以轻松地帮助...