上篇文章:

Spring Cloud系列—Eureka服务注册/发现https://blog.csdn.net/sniper_fandc/article/details/149937589?fromshare=blogdetail&sharetype=blogdetail&sharerId=149937589&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

1 如何在IDEA中启动一个服务的多个实例

2 负载均衡

3 Spring Cloud LoadBalancer实现负载均衡

3.1 添加注解@LoadBalanced

3.2 修改远程调用的ip:端口号为服务名称

4 Spring Cloud LoadBalancer负载均衡策略

5 Spring Cloud LoadBalancer负载均衡原理


        在Eureka篇章中,使用了如下代码获取服务的实例:

List<ServiceInstance> productService = discoveryClient.getInstances("product-service");EurekaServiceInstance serviceInstance = (EurekaServiceInstance) productService.get(0);

        由于只有一个服务实例,因此并不会有问题,但是如果一个服务有多个实例,就会出现问题。

1 如何在IDEA中启动一个服务的多个实例

        点击页面下方的Services:

        点击Add service,选择正在运行的SpringBoot服务:

        右键要复制实例的服务,点击复制:

        在打开的界面点击Modify options,选择Add VM options:

        输入-Dserver.port=端口号,这里的端口号注意不要重复,之后选中创建的实例右键运行即可:

2 负载均衡

        创建多个实例后,多次访问接口就会出现始终访问端口号为同一个的实例,这是因为服务发现时Eureka给我们提供随机的服务列表,但是每次都只获取其中下标为0的服务实例,这就会导致某个实例负载过大,因此需要负载均衡。

        如果不借助组件,可以用hash取余的方式来轮询访问每个服务实例:

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

        这里把discoveryClient.getInstances()放到了方法外面,类加载时只获取一次,防止每次获取的服务列表顺序都不一样,同时节省网络资源。由于多线程环境下,为避免线程安全问题,使用原子类来计算hash取余。这种方式就是一种负载均衡,是一种客户端负载均衡。

        但是上述代码有一些不足之处:服务一旦启动,服务发现一次,其余时间不再服务发现,因此对于服务的注册和下线是无感知的。于是需要一些专业实现负载均衡的组件,分为客户端负载均衡和服务端负载均衡:

        服务端负载均衡:在服务端进行负载均衡算法分配。比如使用Nginx作为负载均衡器,请求先进入Nginx再由Nginx进行负载均衡算法选择服务来进行访问。

        客户端负载均衡:由客户端服务发现后,根据负载均衡算法选择一个服务,并向该服务发送请求。比如Spring Cloud LoadBalancer(Spring Cloud维护)。

3 Spring Cloud LoadBalancer实现负载均衡

3.1 添加注解@LoadBalanced

        在负责远程调用的对象restTemplate上添加@LoadBalanced注解,表示客户端调用时开启负载均衡(即客户端负载均衡):

@Configurationpublic class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}}

3.2 修改远程调用的ip:端口号为服务名称

@Servicepublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//负载均衡String url = "http://product-service/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}}

        多次发送请求,发现请求被负载均衡到了各个服务上:

4 Spring Cloud LoadBalancer负载均衡策略

        LoadBalancer默认采用轮询方式进行负载均衡,但是也支持随机选择策略。要使用随机选择策略,需要自定义负载均衡策略器:

public class LoadBalancerConfig {@BeanReactorLoadBalancer<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);}}

        注意:该策略器不能加@Configuration注解,并且要在Spring组件扫描范围中(即默认和启动类同一级目录下)。

        接着,在RestTemplate配置类上面添加@LoadBalancerClient注解(一个服务提供者使用)或@LoadBalancerClients注解(多个服务提供者使用):

@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)@Configurationpublic class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}}

        @LoadBalancerClient的name表示服务名称,configuration则是定义的负载均衡策略器。

5 Spring Cloud LoadBalancer负载均衡原理

        LoadBalancer最关键的源码是LoadBalancerInterceptor类,该类定义拦截器,将所有请求进行拦截并解析处理。具体的调用流程图如下:

        具体是LoadBalancerInterceptor类的intercept()发挥作用:

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {URI originalUri = request.getURI();String serviceName = originalUri.getHost();//解析URL是否合法(.-等连接方式)Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);//execute()方法根据服务名称来对请求进行增强(负载均衡)return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}

        execute()的实现是BlockingLoadBalancerClient类,具体作用就是根据服务实例名称(serviceId)来服务发现,并选择合适的负载均衡策略来选择对应的服务实例:

    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);});//choose()是核心方法,就是获取服务实例并根据负载均衡策略来返回具体请求的实例。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);}}

        这个choose()方法也是BlockingLoadBalancerClient类实现的,内部调用了ReactiveLoadBalancer接口的choose()方法来进行负载均衡策略的选择:

    public <T> ServiceInstance choose(String serviceId, Request<T> request) {//获取服务实例列表loadBalancer,也就是负载均衡器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();}}

        loadBalancer.choose()的choose()方法是ReactiveLoadBalancer接口的choose()方法,该方法的实现有RandomLoadBalancer类实现的方法和RoundRobinLoadBalancer类实现的方法,这两个类实现的choose()方法分别对应随机选择策略和轮询策略。

        在RandomLoadBalancer类中,choose()方法调用processInstanceResponse()方法,processInstanceResponse()调用getInstanceResponse()方法,最终在getInstanceResponse()方法可以看到通过随机数来选择随机的服务实例进行访问,即随机选择策略:

    public Mono<Response<ServiceInstance>> 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<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else {//此处就是随机选择策略最关键的几行代码int index = ThreadLocalRandom.current().nextInt(instances.size());ServiceInstance instance = (ServiceInstance)instances.get(index);return new DefaultResponse(instance);}}

        RoundRobinLoadBalancer类的choose方法也采用了一样的方法调用链,最终在getInstanceResponse()方法中,实现了本文的“负载均衡”部分的hash取余来轮询选择服务实例的方式:

    public Mono<Response<ServiceInstance>> 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<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> 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 {//通过hash取余的方式来轮询选择服务实例int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());return new DefaultResponse(instance);}}

下篇文章:

Spring Cloud系列—Nacos服务注册/发现https://blog.csdn.net/sniper_fandc/article/details/149938785?fromshare=blogdetail&sharetype=blogdetail&sharerId=149938785&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用 pnpm创建Vue 3 项目

✅ 一、什么是 pnpm&#xff1f; pnpm 是一种更快、更高效的 Node 包管理工具&#xff0c;替代 npm 或 yarn&#xff0c;具有&#xff1a; 更快的安装速度更节省磁盘空间&#xff08;包复用&#xff09;严格的依赖管理二、使用 pnpm 创建 Vue 项目的完整流程 ✅ 第一步&#xf…

Vite vs. vue-cli 创建 Vue 3 项目的区别与使用场景

Vite vs. vue-cli 创建 Vue 3 项目的区别与使用场景 Vite 和 vue-cli 都是 Vue 官方推荐的脚手架工具&#xff0c;但它们的架构、构建方式和适用场景有所不同。以下是它们的对比&#xff1a;1. 核心区别对比项Vite (推荐&#x1f525;)vue-cli (传统)构建工具基于 ESM Rollup基…

VC6800智能相机:赋能智能制造,开启AI视觉新纪元

在工业自动化与智能化浪潮奔涌的今天&#xff0c;精准、高效、智能的视觉检测已成为提升生产力和品质的关键核心。VC6800智能相机应运而生&#xff0c;它不仅仅是一部相机&#xff0c;更是一个集强大视觉硬件与前沿AI算法于一身的 “工业智眼”&#xff0c;正深刻改变着各个领域…

(Python)Python爬虫入门教程:从零开始学习网页抓取(爬虫教学)(Python教学)

一、爬虫基础概念 什么是爬虫&#xff1f; 网络爬虫&#xff08;Web Crawler&#xff09;是一种自动获取网页内容的程序&#xff0c;它像蜘蛛一样在互联网上"爬行"&#xff0c;收集和提取数据。 爬虫应用场景&#xff1a; 搜索引擎&#xff08;Google、百度&#…

dify前端源码部署详细教程

这两天突发奇想&#xff0c;能不能dify源码部署我只部署个前端&#xff0c;后端、数据库什么的还是原来docker部署dify的本地部署和遇到的问题。按逻辑来说应该是行得通的&#xff0c;我就亲自操作了下试下。 我这边就以我以前使用docker部署好的1.3.1版本为例。docker安装参考…

Web地图服务规范,WMS服务是什么

Web地图服务规范&#xff0c;WMS服务是什么&#xff1f; WMS&#xff0c;全称 Web Map Service (网络地图服务)&#xff0c;是有OGC(开放地理空间信息联盟)制定的一项标准化协议。他的核心功能是允许客户端&#xff08;比如网页浏览器或者GIS桌面软件&#xff09;通过互联网或者…

北京手机基站数据分享:9.3万点位+双格式,解锁城市通信「基础设施地图」

今天分享的是——​​2023年7月北京市手机基站数据&#xff08;shpcsv双格式&#xff09;​​。92,785个基站点位&#xff08;覆盖全市16区&#xff09;&#xff0c;WGS84坐标系直接能用&#xff0c;shp格式适配GIS软件&#xff0c;csv格式方便Excel/Pandas分析&#xff01;文末…

Druid学习笔记 01、快速了解Druid中SqlParser实现

文章目录前言介绍Druid代码目录介绍模块一&#xff1a;Parser模块二&#xff1a;Druid_SQL_AST在Druid SQL Parser中有哪些AST节点类型?熟悉常用的AST节点组成常用的SQLExpr有哪些&#xff1f;常用的SQLStatemment&#xff1f;SQLTableSourceSQLSelect & SQLSelectQuerySQ…

Rust中生命周期的理解与应用

在学习Rust编程语言时,理解生命周期(Lifetime)是非常关键的,因为它直接影响到代码的安全性和性能。今天我们来深入探讨Rust中的一个常见问题——生命周期的误解和正确应用,结合实际代码实例来说明。 生命周期的基本概念 Rust中的生命周期是用来确保引用(Reference)在其…

智慧感知新体验:英飞凌雷达在智能家居的创新应用

随着智慧家居快速发展&#xff0c;感知技术成为实现高效、便捷生活的关键。雷达作为非接触、高精度的感测方案&#xff0c;正在家居应用中展现出巨大潜力。 本次研讨会将由英飞凌大中华区雷达应用产品经理 Tommy Wan主讲&#xff0c;分享他在智能门铃、门锁与安防摄像头等应用…

AI:新书预告—从机器学习避坑指南(分类/回归/聚类/可解释性)到大语言模型落地手记(RAG/Agent/MCP),一场耗时5+3年的技术沉淀—“代码可跑,经验可抄”—【一个处女座的程序猿】携两本AI

AI&#xff1a;新书预告—从机器学习避坑指南(分类/回归/聚类/可解释性)到大语言模型落地手记(RAG/Agent/MCP)&#xff0c;一场耗时53年的技术沉淀—“代码可跑&#xff0c;经验可抄”—【一个处女座的程序猿】携两本AI实战书终于正式来了&#xff01; 导读&#xff1a;大家好&…

数据结构:栈、队列

一、栈和队列与链表的区别1.链表可以在任意位置插入和删除元素2.栈和队列只允许在指定位置插入和删除元素3.栈只允许在栈顶位置入栈和出栈元素3.相同点&#xff1a;表、栈、队列都是一种线性结构&#xff08;一对一&#xff09;4.栈和队列是一种特殊的表状结构二、栈&#xff0…

cuda编程笔记(13)--使用CUB库实现基本功能

CUB 是 NVIDIA 提供的 高性能 CUDA 基础库&#xff0c;包含常用的并行原语&#xff08;Reduction、Scan、Histogram 等&#xff09;&#xff0c;可以极大简化代码&#xff0c;并且比手写版本更优化。CUB无需链接&#xff0c;只用包含<cub/cub.cuh>头文件即可需要先临时获…

LabVIEW滤波器测控系统

​基于LabVIEW 平台的高频滤波器测控系统&#xff0c;通过整合控制与测试功能&#xff0c;替代传统分离式测控模式。系统以 LabVIEW 为核心&#xff0c;借助标准化接口实现对滤波器的自动化参数调节与性能测试&#xff0c;显著提升测试效率与数据处理能力&#xff0c;适用于高频…

美团运维面试题及参考答案(上)

输入一个字符串,将其转换成数字时,需要考虑哪些情况(如字符串是否合法、是否为空、int 的范围、是否为 16 进制等)? 将字符串转换成数字时,需全面考虑多种边界情况和合法性问题,具体如下: 字符串基础状态:首先需判断字符串是否为空(长度为0)或仅包含空白字符(如空…

Spring-AI 深度实战:企业级 AI 应用开发指南与 Python 生态对比(高级篇)

为什么 Spring-AI 是企业级 AI 的“隐形冠军”&#xff1f;&#xff08;而不仅是另一个封装库&#xff09;在 Python 主导的 AI 世界中&#xff0c;Spring-AI 的诞生常被误解为“Java 的跟风之作”。但真正的企业级 AI 需求&#xff08;事务一致性、分布式追踪、安全审计&#…

OpenAI 回归开源领域突发两大推理模型,六强AI企业竞逐加剧军备竞赛态势!

获悉&#xff0c;OpenAI重回开源赛道&#xff0c;奥特曼深夜官宣两个分别名为GPT-oss-120b和GPT-oss-20b的模型将在AI软件托管平台Hugging Face上线&#xff0c;在用户输入指令后将能生成文本。两大推理模型上线GPT-oss-120b适用于需要高推理能力的生产级和通用型场景。在核心推…

嵌入式学习硬件(一)ARM体系架构

目录 1.SOC 2.内核架构的分类 3.冯诺依曼架构和哈佛架构 4.kernel 5.指令集 6.ARM处理器产品分类 7.编译的四个步骤​编辑 8.RAM和ROM​编辑 9.ARM处理器工作模式 10.异常处理 11.CPSR程序状态寄存器 1.SOC system on chip 片上系统&#xff0c;可以运行操作系统的一种高端的功…

OpenAI推出开源GPT-oss-120b与GPT-oss-20b突破性大模型,支持商用与灵活部署!

模型介绍OpenAI再次推出开源模型&#xff0c;发布了两款突破性的GPT-oss系列大模型&#xff0c;即GPT-oss-120b和GPT-oss-20b&#xff0c;为AI领域带来了巨大的创新和发展潜力。这两款模型不仅在性能上与现有的闭源模型媲美&#xff0c;而且在硬件适配性上具有明显优势&#xf…

【Unity Plugins】使用ULipSync插件实现人物唇形模拟

一、下载插件ULipSync&#xff1a; 1. 进入Github网址&#xff1a;https://github.com/hecomi/uLipSync/releases/tag/v3.1.4 2. 点击下载下方的unitypackage 3. 安装使用ULipSync的相关的插件 发行者也提到了&#xff0c;在使用的时候需要在Package Manager里安装Unity.B…