【微服务】负载均衡 - LoadBalancer(day4)

news2024/11/27 16:27:28

下述所有代码都是在订单服务中修改的,商品服务并不需要修改,只需要启动多个实例即可。

引入

在介绍Eureka组件的最后,留下了一个问题就是,无论启动多少个实例,只能调用第一个。原因是因为服务调用时获取的是一个实例数组,但是我们进行了硬编码,只get第一个实例进行了调用。找到原因之后,我们就要找到修改代码的方式,进而合理分配调用的主机,减少单机的压力。

但是,如何修改代码,进行合理分配成了一个难点。我想到一个解决方法就是在项目启动之初,去注册中心拉取被调用服务的数量,这样每次进行调用时使用 计数器 % 实例数 的方法来进行分配每次远程调用访问的主机。但是,由于拉取被调用服务的数量这个操作也是远程调用,因此可能每次拉取的实例顺序是不同的,这个我们可以在项目启动时把数量和主机内容都进行记录,没有啥问题;不过,更严重的一个问题是,在运行过程中,主机是可能宕机的,并且对于一个大促是可能进行动态扩缩容的,这是对被调用方进行一个改变,调用方并不会重启,这就导致调用方启动时获取的实例数量不会改变,从而导致扩缩容添加的主机起不到作用,因此微服务自己出了一个类似于Nginx的组件来进行负载均衡。

@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    // 版本三:自定义负载均衡版本
    private static final String PRODUCT_SERVICE_NAME = "cloud-provider-payment8001";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    // 使用原子类来表示调用次数
    private AtomicInteger count = new AtomicInteger(1);
    
    // 初始化方法,在项目启动时调取实例
    private List<ServiceInstance> instances;
    @PostConstruct
    private void init() {
        instances = discoveryClient.getInstances(PRODUCT_SERVICE_NAME);
        log.info("查看被调用方实例的数量:" + instances.size());
    }

    @Override
    public Order getOrderById(Integer id) {
        // 获取订单
        Order order = this.getById(id);
        // 获取应该调用的实例下标
        int index = this.count.getAndIncrement() % instances.size();
        // 获取该实例的内容
        ServiceInstance serviceInstance = this.instances.get(index);
        // 拼接URL
        String url = serviceInstance.getUri() + "/product/query/" + order.getProductId();
        log.info("被调用服务的URI:" + serviceInstance.getUri());
        // 远程调用
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        // 返回结果
        return order;
    }

}

 如上图所示,订单服务在启动时,就会去拉取被调用方存在几个实例。

如何启动个实例?

如下图,想要启动哪个服务的多实例,直接右击该服务。

右击之后,点击Copy Configuration选项。

点击之后,会出现如下弹窗,首先将Name改换成自己想要的,我一般会改一下端口号,表示我复制实例之后的端口号,然后点击Modify options。

点击之后,会出现一堆选项,然后点击Add VM options。

点击之后,会在原来的弹框中多出现一行内容,然后配置自己的信息即可。我一般不会修改其他内容,只会把端口号改变,这样不会造成端口冲突。

再次重启商品服务,由于之前我已经启动了三个实例,因此在Eureka服务列表中会出现四个实例,如下图:

如下图,在启动订单服务之后,也表示商品服务现在有四个实例,因此启动成功。

 负载均衡介绍

概念

负载均衡 Load Balance,简称LB:负载就是压力、流量,均衡就是公平的、合理的将流量进行分配。负载均衡是高并发、高可用必不可少的关键组件。

当服务流量增大时,通常会通过增加机器数量的方式来进行扩容,负载均衡就是按照一定的算法把客户端的请求发送到这些机器中,完成流量分发。

分类

服务端负载均衡

在服务端进行负载均衡的算法分配,比较有名的服务端负载均衡器是Nginx。客户端发送请求,请求首先到达负载均衡器,然后负载均衡器通过负载均衡算法,在多个服务器之间选择一个进行调用。

客户端负载均衡

在客户端进行负载均衡的算法分配。把负载均衡的功能以库的方式集中到客户端,而不是再由一台指定的负载均衡设备集中提供,比如SpringCloud的LoadBalancer,请求到达客户端,客户端从Eureka中拉取服务列表。然后在发送请求到服务器之前,客户端自己通过负载均衡选择一个服务器,进行访问。

对于客户端和服务器来说,并不是服务器就一直是服务器,客户端一直就是客户端。对于上述来说,客户端请求到达服务端,但是服务端又要调用另一个服务端。因此这个中间的服务端也可以称作客户端,如果针对前端请求来说,他就是服务端;如果对于被调用方来说,他就是客户端。因此,不要太纠结这个东西。

 SpringCloudLoadBalancer

在早期,负载均衡是SpringCloud的Ribbon来实现的,但是由于它不进行维护了。因此SpringCloud官方又实现了一个SpringCloudLoadBalancer来进行维护使用。作为客户端负载均衡器来说,使用还是比较简单的。

引入依赖

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

修改业务类

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced // 负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    // 版本一:没有引入微服务组件时
    /*@Resource
    private RestTemplate restTemplate;

    @Override
    public Order getOrderById(Integer id) {
        Order order = this.getById(id);
        String url = "http://127.0.0.1:8001/product/query/" + order.getProductId();
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        return order;
    }*/

    // 版本二:引入了服务注册与发现
    /*private final String PRODUCT_SERVICE_NAME = "cloud-provider-payment8001";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    @Override
    public Order getOrderById(Integer id) {
        // 获取订单
        Order order = this.getById(id);

        // 获取商品服务的实例
        List<ServiceInstance> instances = discoveryClient.getInstances(this.PRODUCT_SERVICE_NAME);
        // 输出日志查看获取到的实例有哪些内容
        log.info(instances.toString());
        // 获取第一个商品实例的uri
        String uri = instances.get(0).getUri().toString();
        log.info(uri);
        // 拼接url
        String url = uri + "/product/query/" + order.getProductId();
        // 远程调用
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        // 返回结果
        return order;
    }*/

    // 版本三:自定义负载均衡版本
    /*private static final String PRODUCT_SERVICE_NAME = "cloud-provider-payment8001";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    private AtomicInteger count = new AtomicInteger(1);

    private List<ServiceInstance> instances;
    @PostConstruct
    private void init() {
        instances = discoveryClient.getInstances(PRODUCT_SERVICE_NAME);
        log.info("查看被调用方实例的数量:" + instances.size());
    }

    @Override
    public Order getOrderById(Integer id) {
        // 获取订单
        Order order = this.getById(id);
        // 获取应该调用的实例下标
        int index = this.count.getAndIncrement() % instances.size();
        // 获取该实例的内容
        ServiceInstance serviceInstance = this.instances.get(index);
        // 拼接URL
        String url = serviceInstance.getUri() + "/product/query/" + order.getProductId();
        log.info("被调用服务的URI:" + serviceInstance.getUri());
        // 远程调用
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        // 返回结果
        return order;
    }*/


    // 版本四:负载均衡版本
    // 不需要自己获取实例,只需要调用服务名就可以自动进行负载均衡获取实例
    @Resource
    private RestTemplate restTemplate;

    private final String PRODUCT_SERVICE_NAME = "cloud-provider-payment8001";

    @Override
    public Order getOrderById(Integer id) {
        // 获取订单
        Order order = this.getById(id);
        // 拼接url
        String url = "http://" + this.PRODUCT_SERVICE_NAME + "/product/query/" + order.getProductId();
        // 远程调用
        Product product = this.restTemplate.getForObject(url, Product.class);
        order.setProduct(product);
        // 返回结果
        return order;
    }

}

测试服务

启动服务之后,输入URL:127.0.0.1:80/order/query/80,多点击几次,就会发现订单服务在进行调用时,会调用不同实例的商品服务,从而证明实现了负载均衡器。

负载均衡策略

策略概述

负载均衡是一种思想,无论是哪种负载均衡器,他们的负载均衡策略都是相似的。SpringCloudBalancer支持两种负载均衡策略:轮询策略和随机选择策略。

轮询策略(Round-Robin):表示服务器轮流处理客户端发来的请求,这是一种最简单的、也是最常用的负载均衡策略。例如学生值日轮流打扫卫生。

随机选择(Random):表示随机一个服务器来执行客户端发来的请求。

自定义策略:SpringCloud默认负载均衡策略是轮询策略,实现是RoundRobinLoadBalancer;如果服务的消费者想要采用随机的负载均衡策略,实现是RandomLoadBalancer;当然,也可以自己实现一个负载均衡策略,例如非公平负载均衡策略(权重)。

1. 定义随机算法对象,通过@Bean将其加载到Spring容器中。

// 该类需要满足:不用@Configuration注释、在组件扫描范围内

public class CustomLoadBalancerConfig {

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
    
}

2. 添加注解

@Configuration
// 添加@LoadBalancerClient注解
// 可以对不同的服务提供⽅配置不同的客⼾端负载均衡算法策略
// name:该负载均衡策略对哪个服务生效(服务提供方)
// configuration:该负载均衡策略用哪个负载均衡策略实现
@LoadBalancerClient(name = "cloud-consumer-order80", configuration = CustomLoadBalancerConfig.class)
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

 实现原理

SpringCloudLoadBalancer的实现,主要是依赖于LoadBalancerInterceptor来实现的。顾名思义,这是一个拦截器,会拦截RestTemplate远程调用的请求,下述内容具体看源码进行解析:

下述图片是LoadBalancerInteceptor拦截器中的一个类,就算用来进行拦截远程调用请求的。可以看到,在这个类中,第一行代码获取了服务的URI,即http://cloud-provider-product8001。第二行代码是获取URI中的主机,即cloud-provider-product8001。稍后判断一下名字是否存在,存在的话就进去进行负载均衡处理。

点击excute方法之后,先进去一个接口,然后再找到接口的实现类,即如下类。该类中会做一系列的处理,然后又进去一个choose方法。

在chosse方法中,是根据服务名和负载均衡策略,选择处理的服务。首先,会根据服务名找到实现的负载均衡策略,然后根据负载均衡算法,在列表中选择一个服务实例。

对于下面这个方法,再点进去对应的choose方法。

在choose方法点进去之后,就是一个接口,接口对应的实现类则是具体的负载均衡策略,对实现的负载均衡策略感兴趣的可以点进去看看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189818.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LM74912-Q1用作电源开关

LM74912电路设计及开发 LM74912-Q1&#xff0c;此芯片集成过压和短路保护以及故障输出功能的汽车理想二极管。正常的型号如下&#xff1a;LM74912QRGERQ1。 注&#xff1a; Q1的后缀指示此器件满足车规级器件/芯片要求。 一、原理框图 如下为芯片的简单应用框图&#xff1b;…

【可答疑】基于51单片机的数字时钟(含仿真、代码、报告等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

脱口秀演员调侃王楚钦引争议

听说脱口秀演员调侃王楚钦输球&#xff0c;野生喜剧回应暂停演出合作&#xff0c;这不仅引发了关于脱口秀表演冒犯边界的讨论&#xff0c;也让我们反思言论自由与尊重他人之间的界限。 脱口秀作为一种艺术形式&#xff0c;其核心在于通过幽默、讽刺的方式&#xff0c;对社会现象…

畅享免费服务:PDF 转图片在线转换软件的魅力

为了方便在社交媒体上分享文档内容&#xff0c;还为了更好地适应特定的编辑需求&#xff0c;将 PDF 文件转换为图片格式都具有重要的意义。而如今&#xff0c;幸运的是&#xff0c;有许多pdf转图片在线转换免费工具为我们提供了便捷、高效的 PDF 转图片服务。接下来&#xff0c…

如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue

TOC ssm779基于SSM的宠物服务平台的设计与实现vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#x…

浅谈模型量化:非对称 vs 对称

模型量化的背后究竟做了什么&#xff1f;本文将以 INT8 为例&#xff0c;结合计算和代码演示&#xff0c;向你展示其中的一些原理。 相关论文: LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale 相关文章: 《07. 模型参数与显存的关系&#xff0c;以及不同精…

【机器学习】探索GRU:深度学习中门控循环单元的魅力

目录 &#x1f354; GRU介绍 &#x1f354; GRU的内部结构图 2.1 GRU结构分析 2.2 GRU工作原理 2.4 Bi-GRU介绍 2.3 使用Pytorch构建GRU模型 2.5 GRU优缺点 &#x1f354; 小结 学习目标 &#x1f340; 了解GRU内部结构及计算公式. &#x1f340; 掌握Pytorch中GRU工具…

map和 set

[本节目标] 关联式容器 键值对 树形结构的关联式容器 底层结构 &#x1f3f7;️ 关联式容器 序列式容器&#xff1a;vector list 栈 队列 (类似以前学习的线性表)… 关联式容器&#xff1a; map set … 关联式容器&#xff0c;数据与数据之间有很强的关联&#xff0c;并…

C++函数指针类型

// // Created by 徐昌真 on 2024/10/5. // #include <iostream>//函数指针类型 指针变成了一个类型 类似int这种 用于反复调用这个函数指针的情况 避免频繁创建一堆的函数指针using namespace std;typedef void (*fptr)(int a, double b, char c); //typedef 将fptr定义…

LLaVA-MoLE:解决多模态大模型指令微调中的数据冲突问题

人工智能咨询培训老师叶梓 转载标明出处 多模态大模型&#xff08;MLLMs&#xff09;通过指令微调&#xff08;instruction finetuning&#xff09;&#xff0c;能够执行各种任务&#xff0c;如理解图表、处理文档和回答基于图像的问题。但是&#xff0c;当从不同领域混合指令…

29 基于51单片机的汽车倒车防撞报警器系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 本课题基于微控制器控制器&#xff0c; 设计一款汽车倒车防撞报警器系统。 要求&#xff1a; 要求&#xff1a;1.配有距离&#xff0c; 用于把车和障碍物之间的距离信号送入控制器。 2.配有报警系…

MyBatis-Plus 字段对应不上或字段在MySQL中为关键字

MyBatis-Plus 名称对应不上比如在新增时如果名字对应不上或者改字段字段在MySQL中为关键子&#xff0c;在执行SQL操作的时候都会报错 解决方法 问题&#xff1a;如果是表名出现对应不上 解决方法&#xff1a;在Java实体类上加TableName("数据库表名") 问题&#…

家具行业数字化转型利器:三品PLM系统全生命周期管理方案

家具行业数字化转型利器&#xff1a;三品PLM系统全生命周期管理方案 在当今竞争激烈的家具行业中&#xff0c;面对设计图纸版本混乱、成本估算不准确、生产流程不透明等挑战&#xff0c;传统的研发管理模式显得力不从心。 而PLM产品生命周期管理系统的引入&#xff0c;为行业…

P1088 [NOIP2004 普及组] 火星人

思路就是 全排列中找到题目所给的组合 然后加上的最小数就是往后面数几个组合 就是要求的那个排列 然后输出 我写的那一份代码ac了两个点 其他 全部tle 估计是比较的时间复杂度太高了暴力写法的时间复杂度比内置函数要大很多 暴力208ms 内置31ms 暴力 #include<bits/std…

C语言复习概要(二)

本文目录 C语言中的数组与函数详解1. 引言2. 数组2.1. 什么是数组&#xff1f;语法&#xff1a;示例&#xff1a; 2.2. 数组的初始化示例 1&#xff1a;在声明时初始化示例 2&#xff1a;部分初始化示例 3&#xff1a;运行时赋值 2.3. 数组的访问与修改示例&#xff1a; 2.4. 多…

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)

1 准备工作 由于创建数据中心需要安装很多服务器&#xff0c;这些服务器要耗费很所物理物理计算资源、存储资源、网络资源和软件资源&#xff0c;作为穷学生只有几百块的n手笔记本&#xff0c;不可能买十几台服务器来搭建数据中心&#xff0c;也不愿意跑实验室&#xff0c;想躺…

pytest(三)——参数化@pytest.mark.parametrize

目录 前言 参数化场景 实际Web UI自动化中的开发场景&#xff0c;比如是一个登录框 parametrize单参数 “笛卡尔积”&#xff0c;多个参数化装饰器 重点知识 参考文献 前言 pytest.mark.parametrize 允许在测试函数或类中定义多组参数和fixtures pytest_generate_tests 允…

利士策分享,探寻中华民族的精神纽带

利士策分享&#xff0c;探寻中华民族的精神纽带 在历史的长河中&#xff0c;中华民族以其独特的文化魅力和坚韧不拔的民族精神&#xff0c;屹立于世界民族之林。 这份力量&#xff0c;源自何处&#xff1f;或许&#xff0c;正是那份纯真的情&#xff0c;如同纽带一般&#xff…

带环链表找入环结点及结论证明

文章目录 前言1.带环链表1.1 带环链表介绍1.2 判断链表是否带环代码实现1.4 入环结点相关问题1.4.1 结论证明1.4.2 找入环结点代码实现1.4.2.1 代码实现11.4.2.2 代码实现2 总结 前言 1.带环链表 1.1 带环链表介绍 如下图中题目所述&#xff0c;带环结点尾结点的next指针域存…

Llama 3.2 视觉能力评估

Meta 发布了 Llama 3 模型的新版本&#xff1b;这次&#xff0c;有四种模型用于不同的目的&#xff1a;两个多模态模型&#xff0c;Llama 3.2 11B 和 90B&#xff0c;以及两个用于边缘设备的小型语言模型&#xff0c;1B 和 3B。 这些是 Meta AI 的首批多模态模型&#xff0c;基…