【Spring Cloud】负载均衡-LoadBalance

系列文章目录

文章目录


一、负载均衡介绍

1.1、问题描述

上篇文章中远程调用的代码如下:

java 复制代码
List<ServiceInstance> instances =discoveryClient.getInstances("productservice");
//服务可能有多个, 获取第⼀个
EurekaServiceInstance instance = (EurekaServiceInstance)instances.get(0);
  1. 根据应用名称获取了服务实例列表
  2. 从列表中选择了一个服务实例

**思考:**如果一个服务对应多个实例呢? 流量是否可以合理的分配到多个实例呢?
现象观察:

我们再启动2个product-service实例

选中要启动的服务, 右键选择 Copy Configuration

在弹出的框中, 选择 Modify options -> Add VM options

添加 VM options : -Dserver.port=9091

9091 为服务启动的端口号, 根据自己的情况进行修改

现在IDEA的Service窗口就会多出来⼀个启动配置, 右键启动服务就可以

同样的操作, 再启动1个实例, 共启动3个服务

观察Eureka, 可以看到product-service下有三个实例:

访问日志:

访问: http://127.0.0.1:8080/order/1

通过观察日志我们发现:请求多次访问, 都是同⼀台机器。这肯定不是我们想要的结果, 我们启动多个实例, 是希望可以分担其他机器的负荷, 那么如何实现呢?

解决方案:

我们可以对上述代码进行简单修改:

java 复制代码
 private static AtomicInteger atomicInteger = new AtomicInteger(1);
    private static List<ServiceInstance> instances;
    @PostConstruct
    public void init(){
        //根据应⽤名称获取服务列表
        instances = discoveryClient.getInstances("product-service");
    }
    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        //String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
        //服务可能有多个, 轮询获取实例
        int index = atomicInteger.getAndIncrement() % instances.size();
        ServiceInstance instance =instances.get(index);
        log.info(instance.getInstanceId());
        //拼接url
        String url = instance.getUri()+"/product/"+ orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url,
                ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

修改代码后,我们再次启动发现请求被均衡的分配在了不同的实例上, 这就是负载均衡。

1.2、什么是负载均衡

负载均衡(Load Balance,简称 LB) , 是高并发, 高可用系统必不可少的关键组件.

当服务流量增大时, 通常会采用增加机器的方式进行扩容, 负载均衡就是用来在多个机器或者其他资源中, 按照⼀定的规则合理分配负载.

⼀个团队最开始只有⼀个⼈, 后来随着工作量的增加, 公司又招聘了几个⼈. 负载均衡就是: 如何把工作量均衡的分配到这几个人⾝上, 以提高整个团队的效率

1.3、负载均衡的一些实现

上面的例子中, 我们只是简单的对实例进行了轮询, 但真实的业务场景会更加复杂. 比如根据机器的配置进行负载分配, 配置高的分配的流量高, 配置低的分配流量低等.

类似企业员工: 能力强的员工可以多承担一些工作.服务多机部署时, 开发人员都需要考虑负载均衡的实现, 所以也出现了⼀些负载均衡器, 来帮助我们实现负载均衡.

负载均衡分为服务端负载均衡和客户端负载均衡

服务端负载均衡

在服务端进行负载均衡的算法分配.

比较有名的服务端负载均衡器是Nginx. 请求先到达Nginx负载均衡器, 然后通过负载均衡算法, 在多个服务器之间选择一个进行访问.

客户端负载均衡

在客户端进行负载均衡的算法分配.

把负载均衡的功能以库的方式集成到客户端, 而不再是由一台指定的负载均衡设备集中提供.

比如Spring Cloud的Ribbon, 请求发送到客户端, 客户端从注册中心(比如Eureka)获取服务列表, 在发送请求前通过负载均衡算法选择⼀个服务器,然后进行访问.

Ribbon是Spring Cloud早期的默认实现, 由于不维护了, 所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方维护)

客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置

二、Spring Cloud LoadBalancer

2.1、快速上手

2.1.1、使用Spring Cloud LoadBalancer实现负载均衡

1 . 给 RestTemplate 这个Bean添加 @LoadBalanced 注解就可以

java 复制代码
@Configuration
public class BeanConfig {
  @Bean
  @LoadBalanced
  public RestTemplate restTemplate(){
     return new RestTemplate();
 }
}

2.修改IP端口号为服务名称

java 复制代码
  public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        //String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
        String url = "http://product-service/product/"+ orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

2.1.2、启动多个product-service实例

按照上面的方式,启动多个实例。

2.1.3、测试负载均衡

启动后,我们观察日志,发现请求被分配到三个实例上了

2.2、负载均衡策略

负载均衡策略是⼀种思想, ⽆论是哪种负载均衡器, 它们的负载均衡策略都是相似的. Spring CloudLoadBalancer 仅⽀持两种负载均衡策略: 轮询策略 和 随机策略

  1. 轮询(Round Robin): 轮询策略是指服务器轮流处理用户的请求. 这是⼀种实现最简单, 也最常用的策略. 生活中也有类似的场景, 比如学校轮流值日, 或者轮流打扫卫⽣.
  2. 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处理新的请求.

自定义负载均衡策略:

Spring Cloud LoadBalancer 默认负载均衡策略是轮询策略, 实现是 RoundRobinLoadBalancer, 如果服务的消费者如果想采用随机的负载均衡策略, 也非常简单.

  1. 定义随机算法对象, 通过 @Bean 将其加载到 Spring 容器中
    此处使用Spring Cloud LoadBalancer提供的 RandomLoadBalancer
java 复制代码
 public class LoadBalancerConfig {
        @Bean
        ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment
                      environment,LoadBalancerClientFactory loadBalancerClientFactory) {
            String name =
                    environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            System.out.println("=============="+name);
            return new
                    RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
                    ServiceInstanceListSupplier.class), name);
        }
    }

注意: 该类需要满足:

1 . 不用 @Configuration 注释

2 . 在组件扫描范围内

  1. 使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解在 RestTemplate 配置类上方, 使用@LoadBalancerClient 或 @LoadBalancerClients 注解, 可以对不同的服务提供方配置不同的客户端负载均衡算法策略.由于项目中只有⼀个服务提供者, 所以使用@LoadBalancerClient
java 复制代码
    @LoadBalancerClient(name = "product-service", configuration =LoadBalancerConfig.class)
    @Configuration
    public class BeanConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }

@LoadBalancerClient 注解说明

  1. name: 该负载均衡策略对哪个服务生效(服务提供方)
  2. configuration : 该负载均衡策略 用哪个负载均衡策略实现.

2.3、LoadBalancer 原理

LoadBalancer 的实现, 主要是 LoadBalancerInterceptor , 这个类会对 RestTemplate 的请求进行拦截, 然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id

我们来看看源码实现:

java 复制代码
 public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
        public ClientHttpResponse intercept(final HttpRequest request, final
        byte[] body, final ClientHttpRequestExecution execution) throws IOException {
            URI originalUri = request.getURI();
            String serviceName = originalUri.getHost();
            Assert.state(serviceName != null, "Request URI does not contain a 
                    valid hostname: " + originalUri);
            return (ClientHttpResponse)this.loadBalancer.execute(serviceName,
                    this.requestFactory.createRequest(request, body, execution));
        }
    }

可以看到这里的intercept方法, 拦截了用户的HttpRequest请求,然后做了几件事:

1 . request.getURI() 从请求中获取uri

2 . service/product/1001 originalUri.getHost() 从uri中获取路径的主机名, 也就是服务id, product-service

3 . loadBalancer.execute 根据服务id, 进行负载均衡, 并处理请求

后续代码:

java 复制代码
 public class BlockingLoadBalancerClient implements LoadBalancerClient {

        public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
                throws IOException {
            String hint = this.getHint(serviceId);
            LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new
                    LoadBalancerRequestAdapter(request, this.buildRequestContext(request, hint));
            Set<LoadBalancerLifecycle> supportedLifecycleProcessors =
                    this.getSupportedLifecycleProcessors(serviceId);
            supportedLifecycleProcessors.forEach((lifecycle) -> {
                lifecycle.onStart(lbRequest);
            });
            //根据serviceId,和负载均衡策略, 选择处理的服务
            ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
            if (serviceInstance == null) {
                supportedLifecycleProcessors.forEach((lifecycle) -> {
                    lifecycle.onComplete(new CompletionContext(Status.DISCARD,
                            lbRequest, new EmptyResponse()));
                });
                throw new IllegalStateException("No instances available for " +
                        serviceId);
            } else {
                return this.execute(serviceId, serviceInstance, lbRequest);
            }
        }

        /**
         * 根据serviceId,和负载均衡策略, 选择处理的服务
         *
         */
        public <T> ServiceInstance choose(String serviceId, Request<T> request) {
            //获取负载均衡器
            ReactiveLoadBalancer<ServiceInstance> loadBalancer =
                    this.loadBalancerClientFactory.getInstance(serviceId);
            if (loadBalancer == null) {
                return null;
            } else {
                //根据负载均衡算法, 在列表中选择⼀个服务实例
                Response<ServiceInstance> loadBalancerResponse =
                        (Response)Mono.from(loadBalancer.choose(request)).block();
                return loadBalancerResponse == null ? null :
                        (ServiceInstance)loadBalancerResponse.getServer();
            }
        }
    }

三、总结

以上就是本文全部内容,本文主要为大家介绍了负载均衡-LoadBalance以及实际应用。感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持

相关推荐
h64648564h5 分钟前
CANN 性能剖析与调优全指南:从 Profiling 到 Kernel 级优化
人工智能·深度学习
数据与后端架构提升之路7 分钟前
论系统安全架构设计及其应用(基于AI大模型项目)
人工智能·安全·系统安全
忆~遂愿11 分钟前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
Liue6123123115 分钟前
YOLO11-C3k2-MBRConv3改进提升金属表面缺陷检测与分类性能_焊接裂纹气孔飞溅物焊接线识别
人工智能·分类·数据挖掘
小韩学长yyds20 分钟前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化
仟濹22 分钟前
【Java基础】多态 | 打卡day2
java·开发语言
Re.不晚22 分钟前
JAVA进阶之路——无奖问答挑战2
java·开发语言
一切尽在,你来23 分钟前
第二章 预告内容
人工智能·langchain·ai编程
23遇见27 分钟前
基于 CANN 框架的 AI 加速:ops-nn 仓库的关键技术解读
人工智能
Codebee36 分钟前
OoderAgent 企业版 2.0 发布的意义:一次生态战略的全面升级
人工智能