spring cloud loadbalancer实现机房感知的负载均衡

1 概述

在同城多机房情景下,各个机房各自部署一套微服务集群,正常情况下微服务调用在本机房闭环。在如下某些灾难情景,可以尝试拉远调用以最大程度维持业务连续性,这些情景例如:

  • A机房多个服务器宕机。
  • 应用由于BUG发生OOM导致暂时性应用不可用、或者被kubelet重启,等应用重新正常运行需要5分钟以上。

为了实现拉远调用,进程的负载均衡逻辑需要感知机房位置,因此微服务注册到服务注册中心时需要夹带额外的元数据。

2 spring cloud loadbalancer

Spring Cloud LoadBalancer是Spring Cloud提供的一个用于微服务架构中的客户端负载均衡解决方案。它旨在取代Netflix Ribbon,提供了更现代化的API和更好的与Spring生态系统的集成。

2.1 主要特性

  • 简化配置:
    Spring Cloud LoadBalancer提供了简化的配置选项,并且可以通过应用程序属性文件轻松配置。
  • 自动配置支持:
    它能够自动与RestTemplate和Feign客户端集成,无需手动设置负载均衡逻辑。
  • 反应式编程支持:
    支持基于 WebFlux 的非阻塞 I/O 操作,对于构建高性能、响应式的微服务非常重要。
  • 灵活的负载均衡策略:
    内置多种负载均衡算法(如轮询、随机选择等),并且可以自定义实现以满足特定需求。
  • 服务发现集成:
    与Spring Cloud DiscoveryClient接口兼容,可以与Eureka、Consul等服务发现工具无缝协作。

2.2 自定义负载均衡的套路

2.2.1 步骤1

编写自定义负载均衡逻辑的类,内容如下:

package com.example.consumer.balancer;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
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.NoopServiceInstanceListSupplier;
import  org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;public class MyNewLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log =  LogFactory.getLog(MyNewLoadBalancer.class);private final String serviceId;private ObjectProvider<ServiceInstanceListSupplier>  serviceInstanceListSupplierProvider;private final String localDataCenter;/**
*      * @param serviceInstanceListSupplierProvider a provider of
*             * {@link ServiceInstanceListSupplier} that will be used to get available instances
*                    * @param serviceId id of the service for which to choose an  instance
*                           */public MyNewLoadBalancer(ObjectProvider<ServiceInstanceListSupplier>  serviceInstanceListSupplierProvider,String serviceId, String localDataCenter) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider =  serviceInstanceListSupplierProvider;this.localDataCenter = localDataCenter;}@SuppressWarnings("rawtypes")@Override// 核心方法,负载均衡的逻辑就是从choose()开始public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier =  serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(serviceInstances ->  processInstanceResponse(supplier, serviceInstances));}private Response<ServiceInstance>  processInstanceResponse(ServiceInstanceListSupplier supplier,List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse =  getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback &&  serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)  supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance>  getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " +  serviceId);}return new EmptyResponse();}// 同机房的服务实例List<ServiceInstance> sameDcInstances = instances.stream().filter(instance -> localDataCenter.equals(instance.getMetadata().get("DATA_CENTER"))).collect(Collectors.toList());// 其他机房的服务实例List<ServiceInstance> otherDcInstances = instances.stream().filter(instance -> !localDataCenter.equals(instance.getMetadata().get("DATA_CENTER"))).collect(Collectors.toList());// 两个服务实例列表,选择一个  List<ServiceInstance> selectedInstances = sameDcInstances.isEmpty() ?otherDcInstances : sameDcInstances;// 选好实例列表后,再使用随机方式挑选出一个int index =  ThreadLocalRandom.current().nextInt(selectedInstances.size());ServiceInstance instance = selectedInstances.get(index);return new DefaultResponse(instance);}
}

2.2.2 步骤2

编写工厂类,不需要添加@Configuration:

package com.example.consumer.balancer;
import org.springframework.cloud.client.ServiceInstance;
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 MyLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment  environment, LoadBalancerClientFactory loadBalancerClientFactory){String name =  environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 本地机房的信息,从环境变量中获取即可String localDataCenter = environment.getProperty("spring.cloud.nacos.discovery.metadata.DATA_CENTER");return new MyNewLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,  ServiceInstanceListSupplier.class), name, localDataCenter);}
}

2.2.3 步骤3

在main类中使用@LoadBalancerClient或@LoadBalancerClients来指定刚刚创建工厂类:

package com.example.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import com.example.consumer.balancer.MyLoadBalancerConfig;@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@LoadBalancerClient(name = "service-provider", configuration =  MyLoadBalancerConfig.class)
// @LoadBalancerClients(defaultConfiguration = MyLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}
}

2.2.4 完整代码

https://gitee.com/handsomeboylj/spring-cloud-nacos-demo

3 容灾方案

两边机房都正常时:
在这里插入图片描述
DC1机房的Provider应用临时不可用时,拉远调用另外机房的Provider应用:
在这里插入图片描述

4 测试

本次测试中,namespace dc1作为dc1机房,namespace dc2作为dc2机房,所有微服务实例都注册到同一个nacos服务中,所有微服务实例在网络层都是扁平的、可直接调用的(对应到现实里,就是是两个机房通过VPN或专线打通,容器网络使用underlay模式)。

git clone https://gitee.com/handsomeboylj/spring-cloud-nacos-demo.git
kubectl apply -f doc/k8s/dc-awareness/

部署成功后,如下:
在这里插入图片描述
dc1机房的一个消费者的IP是10.0.13.96,其工作端口是8082,接口是/consumer/call,调用可以看见结果,消费者和生产者都会响应自己所在的机房:
在这里插入图片描述
将dc1机房的生产者关闭后,再访问dc1机房的消费者的接口,可以看见响应是dc2,说明调用了机房2的生产者。
在这里插入图片描述

将dc1机房的生产者重新上线后,dc1的消费者从拉远调用转变成本机房调用
在这里插入图片描述

5 小结

本文介绍拉远调用可临时维持业务系统的连续性,并且使用spring cloud loadbalancer来实现感知机房,优先本机房闭环调用,次之拉远调用。

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

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

相关文章

vue中,created和mounted两个钩子之间调用时差值受什么影响

在 Vue 中&#xff0c;created 和 mounted 是两个生命周期钩子&#xff0c;它们之间的调用时差主要受以下几个因素影响&#xff1a; &#x1f7e2; 1. 模板复杂度与渲染耗时&#xff08;最主要因素&#xff09; mounted 的触发时间是在组件的 DOM 被挂载之后&#xff08;也就是…

Linux篇 第2章Linux基础指令

Linux篇 第2章Linux基础指令 文章目录 前言一、基础的一些命令1.pwd2.mkdir3.ls4.cd5.clear 二、ls1.ls -l2.ls -a3.ls -l -a 三、touch四、 cd1.cd /2.cd ..3.cd ~4. cd - 五、tree1. Linux系统文件的结构2.绝对路径和相对路径 六、mkdir -p七、rmdir&#xff08;没啥用&#…

Scrapyd 详解:分布式爬虫部署与管理利器

Scrapyd 是 Scrapy 官方提供的爬虫部署与管理平台&#xff0c;支持分布式爬虫部署、定时任务调度、远程管理爬虫等功能。本文将深入讲解 Scrapyd 的核心功能、安装配置、爬虫部署流程、API 接口使用&#xff0c;以及如何结合 Scrapy-Redis 实现分布式爬虫管理。通过本文&#x…

国产免费工作流引擎star 6.5k,Warm-Flow升级1.7.2(新增案例和修复缺陷)

文章目录 主要更新内容项目介绍功能思维导图设计器流程图演示地址官网Warm-Flow视频 主要更新内容 [feat] 开启流程实例&#xff0c;新增流程定义是否存在校验[feat] 新增合同签订流程案例[feat] 新增企业采购流程案例[update] mybatis-plus逻辑删除&#xff0c;删除值和未删除…

数据仓库Hive

1.数据仓库 1.1数据仓库的概念 数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理决策。 面向主题。操作型数据库的数据组织面向事务处理任务&#xff0c;而数据仓库中的数据按照一定的…

dify 连接不上ollama An error occurred during credentials validation:

三大报错 An error occurred during credentials validation: HTTPConnectionPool(hosthost.docker.internal, port11434): Max retries exceeded with url: /api/chat (Caused by NameResolutionError("<urllib3.connection.HTTPConnection object at 0x7f26fc3c00b0&…

uniapp 生成海报二维码 (微信小程序)

先下载qrcodenpm install qrcode 调用 community_poster.vue <template><view class"poster-page"><uv-navbar title"物业推广码" placeholder autoBack></uv-navbar><view class"community-info"><text clas…

如何理解编程中的递归、迭代与回归?

作为编程初学者&#xff0c;递归、迭代和回归这三个概念常常让人感到困惑。本文将通过生活化的比喻、Python代码示例和直观的对比&#xff0c;帮助你彻底理解这三个重要概念及其应用场景。 一、从生活比喻理解核心概念 1. 递归&#xff08;Recursion&#xff09;—— 俄罗斯套…

Android Studio 模拟器配置方案

Android Studio 模拟器配置方案 1.引言2.使用Android Studio中的模拟器3.使用国产模拟器1.引言 前面介绍【React Native基础环境配置】的时候需要配置模拟器,当时直接使用了USB调试方案,但是有些时候可能不太方便连接手机调试,比如没有iPhone调不了ios。接下来说明另外两种可…

uniapp(vue3)动态计算swiper高度封装自定义hook

// useCalculateSwiperHeight.ts import { ref, onMounted } from vue;export function useCalculateSwiperHeight(headerSelector: string .header-search, tabsWrapperSelector: string .u-tabs .u-tabs__wrapper) {const swiperHeight ref<number>(0);// 封装uni.g…

从代码学习深度学习 - 转置卷积 PyTorch版

文章目录 前言基本操作填充、步幅和多通道填充 (Padding)步幅 (Stride)多通道总结前言 在卷积神经网络(CNN)的大家族中,我们熟悉的卷积层和汇聚(池化)层通常会降低输入特征图的空间维度(高度和宽度)。然而,在许多应用场景中,例如图像的语义分割(需要对每个像素进行分…

c语言第一个小游戏:贪吃蛇小游戏06

实现贪吃蛇四方向的风骚走位 实现代码 #include <curses.h> #include <stdlib.h> struct snake{ int hang; int lie; struct snake *next; }; struct snake *head; struct snake *tail; int key; int dir; //全局变量 #define UP 1 //这个是宏定义&a…

django的权限角色管理(RBAC)

在 Django 中&#xff0c;User、Group 和 Permission 是权限系统的核心组件。下面通过代码示例演示它们的 CRUD&#xff08;创建、读取、更新、删除&#xff09; 操作&#xff1a; 一、User 模型 CRUD from django.contrib.auth.models import User# 创建用户 user User.obje…

解决docker alpine缺少字体的问题 Could not initialize class sun.awt.X11FontManager

制作的springboot项目镜像&#xff0c;缺少字体报错Could not initialize class sun.awt.X11FontManager 原因镜像中缺少字体 解决&#xff1a; 制作镜像时&#xff0c;添加字体库&#xff0c;Dockerfile文件 中添加如下内容 注意&#xff1a; jdk版本一定要使用&#xff0…

MQTT 在Spring Boot 中的使用

在 Spring Boot 中使用 MQTT 通常会借助 Spring Integration 项目提供的 MQTT 支持。这使得 MQTT 的集成可以很好地融入 Spring 的消息驱动和企业集成模式。 以下是如何在 Spring Boot 中集成和使用 MQTT 的详细步骤&#xff1a; 前提条件&#xff1a; MQTT Broker&#xff…

养生:为健康生活注入活力

在快节奏的现代生活中&#xff0c;养生不再是老年人的专属&#xff0c;而是每个人维持身心健康的必修课。从饮食到运动&#xff0c;从睡眠到心态&#xff0c;全方位的养生方式能帮助我们抵御压力&#xff0c;拥抱充满活力的生活。 饮食养生&#xff1a;合理搭配&#xff0c;滋…

Axure设计之内联框架切换页面、子页面间跳转问题

在Axure中&#xff0c;你可以通过以下步骤实现主页面中的内联框架在点击按钮时切换页面内容&#xff0c;从A页面切换到B页面。&#xff08;误区&#xff1a;子页面之间切换不要设置“框架中打开链接”然后选“父级框架”这个交互&#xff09; 主框架页面&#xff08;左侧导航展…

[思维模式-38]:看透事物的关系:什么是事物的关系?事物之间的关系的种类?什么是因果关系?如何通过数学的方式表达因果关系?

一、什么是事物的关系&#xff1f; 事物的关系是指不同事物之间存在的各种联系和相互作用&#xff0c;它反映了事物之间的相互依存、相互影响、相互制约等特性。以下从不同维度为你详细阐述&#xff1a; 1、关系的类型 因果关系 定义&#xff1a;一个事件&#xff08;原因&a…

OJ判题系统第6期之判题逻辑开发——设计思路、实现步骤、代码实现(策略模式)

在看这期之前&#xff0c;建议先看前五期&#xff1a; Java 原生实现代码沙箱&#xff08;OJ判题系统第1期&#xff09;——设计思路、实现步骤、代码实现-CSDN博客 Java 原生实现代码沙箱之Java 程序安全控制&#xff08;OJ判题系统第2期&#xff09;——设计思路、实现步骤…

行业趋势与技术创新:驾驭工业元宇宙与绿色智能制造

引言 制造业发展的新格局&#xff1a;创新势在必行 当今制造业正经历深刻变革&#xff0c;面临着供应链波动、个性化需求增长、可持续发展压力以及技能人才短缺等多重挑战。在这样的背景下&#xff0c;技术创新不再是可有可无的选项&#xff0c;而是企业保持竞争力、实现可持…