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

news2024/11/24 9:11:21

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

引入

在介绍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/2188300.html

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

相关文章

C/C++/EasyX——入门图形编程(3)

【说明】上一篇讲了基础图形的绘制&#xff0c;那么这一篇就来讲一下如何在窗口上绘制文字吧&#xff0c;友友们一起学习吧。&#xff08;&#xff1e;&#xff59;&#xff1c;&#xff09;&#xff08;&#xff3e;&#xff56;&#xff3e;&#xff09; 一&#xff1a;文字…

jQuery——对象的过滤

在 jQuery 对象中的元素对象数组中过滤出一部分元素来 ① first&#xff08;&#xff09; ② last&#xff08;&#xff09; ③ eq&#xff08;index / -index&#xff09; ④ filter&#xff08;selector&#xff09;&#xff1a;对当前元素提要求 ⑤ not&#xff08;sel…

电脑IP地址怎么换成二进制:详解转换过程与应用

在电脑网络的世界里&#xff0c;IP地址是每台设备独一无二的身份标识。而我们日常所见的IP地址&#xff0c;大多是以点分十进制的形式呈现。然而&#xff0c;在电脑内部&#xff0c;IP地址实际上是以二进制的形式进行存储和处理的。那么&#xff0c;电脑IP地址怎么换成二进制呢…

pygame入门(千字详细版)

千字赘述&#xff0c;万字总结&#xff0c;就为博客点一赞吧&#xff01; 1.安装pygame pip install pygame 安装完成后在python中输入检验有没有问题&#xff0c;回车键应该不会报错。 >>>import pygame >>>2.pygame模块预览 3.项目实战 3.1.0 hello w…

一键开启高清录屏:盘点Windows最火四款录屏工具

嘿&#xff0c;朋友们&#xff0c;今天咱们来聊聊那些让我在电脑前忙活得热火朝天的录屏神器究竟怎么样。作为一个经常需要录制教程、分享游戏精彩瞬间的普通用户&#xff0c;我可是对这几款软件有着满满的体验心得&#xff0c;现在就给你们一一道来。 一、福昕录屏大师 网址…

编码与解码

文章目录 编码与解码一、字节 & 字符二、编码 & 解码三、字符集 & 字符编码四、ASCII五、ISO-8859-1六、GB七、Unicode1、概述2、发展3、UTF-8 编码4、UTF-16 编码 八、Base64 编码1、概述2、原理3、代码示例 九、十六进制编码 编码与解码 一、字节 & 字符 字…

kali下编译AOSP报错(libncurses.so.5: cannot open shared object file)

编译报错信息&#xff1a;libncurses.so.5: cannot open shared object file: No such file or directory /bin/bash -c "PWD/proc/self/cwd prebuilts/clang/host/linux-x86/clang-3289846/bin/clang -Ifr ameworks/rs/script_api/include -Iexternal/clang/lib/Headers …

【AI知识点】小世界网络(Small-World Networks)

小世界网络&#xff08;Small-World Networks&#xff09; 是一种具有独特拓扑结构的网络模型&#xff0c;广泛应用于研究社交网络、神经网络、互联网以及其他复杂系统中的节点间连接方式。小世界网络的特点是节点之间的平均路径长度较短&#xff0c;并且大多数节点的局部连接较…

世邦通信股份有限公司IP网络对讲广播系统RCE

漏洞描述 SPON世邦IP网络广播系统采用的IPAudio™技术, 将音频信号以数据包形式在局域网和广域网上进行传送&#xff0c;是一套纯数字传输的双向音频扩声系统。传统广播系统存在的音质不佳&#xff0c;传输距离有限&#xff0c;缺乏互动等问题。该系统设备使用简便&#xff0c…

知识图谱入门——7:阶段案例:使用 Protégé、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop 搭建知识图谱

在 Windows 环境中结合使用 Protg、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop&#xff0c;可以高效地实现从自然语言处理&#xff08;NLP&#xff09;到知识图谱构建的全过程。本案例将详细论述环境配置、步骤实现以及一些扩展和不足之处。 文章目录 1. 环境准备1.1 Neo4j…

webGL入门(六)图形旋转

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

PIDM—— 物理正则化扩散模型

概述 论文地址&#xff1a;https://arxiv.org/pdf/2403.14404 源码地址&#xff1a;https://github.com/jhbastek/physicsinformeddiffusionmodels 扩散模型在逼近非常复杂的数据分布方面具有极高的性能和多功能性&#xff0c;近年来在自然科学领域的应用迅速扩展。鉴于其在科…

两数相除111

1.//给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 //整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。 // 例如&#xff0c;8.345 将被截断为 8 &#xff0…

基于SSM框架和Layui的学院课程安排系统的设计与实现(源码+定制+定制)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

WDG看门狗在stm32中的应用

一&#xff0c;WDG看门狗的介绍 看门狗可以监控程序的运行状态&#xff0c;当程序因为设计漏洞、硬件故障、电磁干扰等原因&#xff0c;出现卡死或跑飞现象时&#xff0c;看门狗能及时复位程序&#xff0c;避免程序陷入长时间的罢工状态&#xff0c;保证系统的可靠性和安全性看…

2款.NET开源且免费的Git可视化管理工具

Git是什么&#xff1f; Git是一种分布式版本控制系统&#xff0c;它可以记录文件的修改历史和版本变化&#xff0c;并可以支持多人协同开发。Git最初是由Linux开发者Linus Torvalds创建的&#xff0c;它具有高效、灵活、稳定等优点&#xff0c;如今已成为软件开发领域中最流行…

深入理解Dubbo源码核心原理-Part1

启动类&#xff1a; 进入EnableDubbo查看&#xff1a; ①EnableDubboConfig&#xff1a; ②DubboComponentScan&#xff1a; 请看核心方法registerServiceBeans() 到这里&#xff0c;Dubbo对于Bean的加载初始化就做完了&#xff0c;Spring容器读取这些BeanDefinition就要对其进…

【计算机体系结构】TLB和Cache

TLB的设计 在两级页表的虚拟存储系统中&#xff0c;需要访问两次物理内存才能得到虚拟地址所对应的物理地址&#xff0c;而物理内存的访问速度是远远慢于处理器的&#xff0c;为了对该过程进行加速&#xff0c;可以加入一个页表的缓存&#xff0c;该缓存将页表中最近使用的PTE…

<<迷雾>> 第7章 会变魔术的触发器(1)--连着两个按键开关的逻辑电路 示例电路

info::操作说明 鼠标单击开关切换开合状态 A 能使灯点亮并保持; B 则点亮的灯熄灭. 注: 此处使用的是 按钮开关, 松开鼠标后开关会自己断开, 类似于手机和电脑上的电源按钮 因系统原因, 此类开关与普通开关在外观上并无差别. primary::在线交互操作链接 https://cc.xiaogd.net/…