springcloud项目实战之自定义负载均衡器

系列文章 。

写在前面

本部分看下如何自定义负载均衡器从而满足业务对于负载均衡特定的需求。
源码 。

1:负载均衡的知识点

1.1:什么是负载均衡?

多台服务器随机的选择一台处理请求的技术,叫做负载均衡自己总结的,非官方回答

1.2:为什么需要负载均衡?

如果老逮着一只羊薅羊毛,这只羊会被薅秃噜皮了,同理如果所有的请求都由一台服务器处理,这台服务器也将不堪重负,也会制约系统的吞吐量,因此我们需要负载均衡技术。

1.3:负载均衡的都有哪些类型?

服务端负载均衡(或者叫网关层负载均衡)和客户端负载均衡,前者参考下图:
在这里插入图片描述
这种方式的缺点和优点如下:

  • 优点
对于客户端是透明的,不需要关心负载均衡的逻辑,和调用单服务没有任何差别
  • 缺点
需要维护网关组件,所以会提高系统的复杂度和故障率。而且多一层网关调用会增加10ms到20ms的网络延时,在高QPS的场景中这十几毫秒的延时将会被无限放大,成为系统的性能瓶颈。

对于网关负载均衡的不足,客户端负载均衡可以很好地解决,不足之处就是客户端需要服务发现,以及自己实现负载均衡方案。客户端负载均衡可参考下图:
在这里插入图片描述

1.4:springcloud的负载均衡器是什么?

Loadbalancer。

2:LoadBalancer的工作原理

我们在类dongshi.daddy.loadbalance.Configuration中使用了@LoadBalancer注解,如下:

@Bean
@LoadBalanced
public WebClient.Builder register() {
    return WebClient.builder();
}

在使用了该注解后,就会生成一个具有负载均衡能力的WebClient,原理是偷摸的在WebClient塞了了一个特殊的Fitler,实现的方式是,首先看一个自动配置类ReactorLoadBalancerClientAutoConfiguration :

@Configuration(proxyBeanMethods = false)
// 只要Path路径上能加载到WebClient和ReactiveLoadBalancer
// 则开启自动装配流程
@ConditionalOnClass(WebClient.class)
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
public class ReactorLoadBalancerClientAutoConfiguration {

   // 如果开启了Loadbalancer重试功能(默认开启)
   // 则初始化RetryableLoadBalancerExchangeFilterFunction
   @ConditionalOnMissingBean
   @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
   @Bean
   public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction(
         ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties,
         LoadBalancerRetryPolicy retryPolicy) {
      return new RetryableLoadBalancerExchangeFilterFunction(retryPolicy, loadBalancerFactory, properties);
   }
   

   // ...省略部分代码

当存在类WebClient以及存在类型为ReactiveLoadBalancer.Factory.class的bean时,开启自动装配,会生成beanRetryableLoadBalancerExchangeFilterFunction,接着看自动装配类LoadBalancerBeanPostProcessorAutoConfiguration:

// 省略部分代码
public class LoadBalancerBeanPostProcessorAutoConfiguration {

   // 内部配置类
   @Configuration(proxyBeanMethods = false)
   @ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
   protected static class ReactorDeferringLoadBalancerFilterConfig {
      
      // 将第一步中创建的ExchangeFilterFunction实例封装到另一个名为
      // DeferringLoadBalancerExchangeFilterFunction的过滤器中
      @Bean
      @Primary
      DeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction> reactorDeferringLoadBalancerExchangeFilterFunction(
            ObjectProvider<LoadBalancedExchangeFilterFunction> exchangeFilterFunctionProvider) {
         return new DeferringLoadBalancerExchangeFilterFunction<>(exchangeFilterFunctionProvider);
      }
   }
   
   // 将过滤器打包到后置处理器中
   @Bean
   public LoadBalancerWebClientBuilderBeanPostProcessor loadBalancerWebClientBuilderBeanPostProcessor(
         DeferringLoadBalancerExchangeFilterFunction deferringExchangeFilterFunction, ApplicationContext context) {
      return new LoadBalancerWebClientBuilderBeanPostProcessor(deferringExchangeFilterFunction, context);
   }
}

首先生成beanDeferringLoadBalancerExchangeFilterFunction然后将该bean封装到LoadBalancerWebClientBuilderBeanPostProcessor中,这是一个后置bean处理器,就是在该后置bean处理器中完成向WebClient中添加特殊的fitler的工作的:

public class LoadBalancerWebClientBuilderBeanPostProcessor implements BeanPostProcessor {
   // ... 省略部分代码
   
   // 对过滤器动手脚
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      // 如果满足以下条件,则将过滤器添加到WebClient中
      // 1) 当前Bean是WebClient.Builder实例
      // 2) WebClient被@LoadBalanced注解修饰
      if (bean instanceof WebClient.Builder) {
         if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
            return bean;
         }
         // 添加过滤器
         ((WebClient.Builder) bean).filter(exchangeFilterFunction);
      }
      return bean;
   }

}

只有满足类型为web Client.Builder,并且使用了@Balancer注解的才会处理,通过代码((WebClient.Builder) bean).filter(exchangeFilterFunction);添加filter。

3:自定义负载均衡器

我们通过自定义负载均衡器来实现金丝雀发布,即只让一部分用户使用新上线的功能,正常的功能调用如下图:
在这里插入图片描述
其中服务B有3个节点,假定新功能上线,我们只替换其中的一个节点,如下图:
在这里插入图片描述
这样只有其中一部分流量会打到部署了新功能的节点上,这就是金丝雀发布,但是这里我们为了更好的控制哪些请求执行到新功能节点,就需要自己来实现一个负载均衡器,这里我们实现的方式是通过请求中的一个特殊标记traffic-version当该值为test001时就把这个请求作为我们的金丝雀处理,那么接下来就可以正式开始我们的工作了。

LoadBalancer组件的顶层接口是ReactiveLoadBalancer,我们这里可以实现其子接口ReactorServiceInstanceLoadBalancer,来定义金丝雀的规则类CanaryRule,源码如下:

// 可以将这个负载均衡策略单独拎出来,作为一个公共组件提供服务
@Slf4j
public class CanaryRule implements ReactorServiceInstanceLoadBalancer {

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private String serviceId;

    // 定义一个轮询策略的种子
    final AtomicInteger position;

    public CanaryRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                      String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        position = new AtomicInteger(new Random().nextInt(1000));
    }

    ...

    Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
       ...
        // 从请求Header中获取特定的流量打标值
        // 注意:以下代码仅适用于WebClient调用,如果使用RestTemplate或者Feign则需要额外适配
        DefaultRequestContext context = (DefaultRequestContext) request.getContext();
        RequestData requestData = (RequestData) context.getClientRequest();
        HttpHeaders headers = requestData.getHeaders();

        String trafficVersion = headers.getFirst(TRAFFIC_VERSION);

        // 如果没有找到打标标记,或者标记为空,则使用RoundRobin规则进行轮训
        if (StringUtils.isBlank(trafficVersion)) {
            // 过滤掉所有金丝雀测试的节点(Metadaba有值的节点)
            List<ServiceInstance> noneCanaryInstances = instances.stream()
                    .filter(e -> !e.getMetadata().containsKey(TRAFFIC_VERSION))
                    .collect(Collectors.toList());
            return getRoundRobinInstance(noneCanaryInstances);
        }

        // 如果某台机器的traffic-version元数据和请求中的traffic-version相等,则可作为金丝雀服务器
        // 找出所有金丝雀服务器,用RoundRobin算法挑出一台
        List<ServiceInstance> canaryInstances = instances.stream().filter(e -> {
            String trafficVersionInMetadata = e.getMetadata().get(TRAFFIC_VERSION);
            return StringUtils.equalsIgnoreCase(trafficVersionInMetadata, trafficVersion);
        }).collect(Collectors.toList());
        return getRoundRobinInstance(canaryInstances);
    }
    ...
}

接着基于Java config方式配置CanaryRule为bean:

// 注意这里不要写上@Configuration注解
public class CanaryRuleConfiguration {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 在Spring上下文中声明了一个CanaryRule规则
        return new CanaryRule(loadBalancerClientFactory.getLazyProvider(name,
                ServiceInstanceListSupplier.class), name);
    }
}

最后使用@LoadBalancerClient加载CanaryRuleConfiguration,从而加载CanaryRule,如下:

...
// value 是要代理的服务类的服务名,这里是template模块的服务名
@LoadBalancerClient(value = "coupon-template-serv-loadbalance", configuration = CanaryRuleConfiguration.class)
public class Loadbalance_CustomerApplication {
    ...
}

这里我们以给用户发放优惠券来测试,按照如下步骤来进行修改,首先修改RequestCoupon增加属性private String trafficVersion;,用作测试流量打标(客户端传入,即由外部调用决定是否走金,丝雀服务器),接着在WebClient头里添加traffic-version,供CanaryRule中使用,如下:

 /**
     * 用户领取优惠券
     */
    @Override
    public Coupon requestCoupon(RequestCoupon request) {
        CouponTemplateInfo templateInfo = webClientBuilder.build()
                // 声明了这是一个GET方法
                .get()
                .uri("http://coupon-template-serv-loadbalance/template/getTemplate?id=" + request.getCouponTemplateId())
                // 是否走金丝雀服务器的标记
                .header(TRAFFIC_VERSION, request.getTrafficVersion())
                ...
                .block();
        ...
}

做完这些工作后,就可以启动一个customer,一个template了,如下:
在这里插入图片描述
为了便于区分测试的效果,我们来修改dongshi.daddy.loadbalance.service.CouponTemplateServiceImpl#loadTemplateInfo代码,模拟新发布的内容:

@Override
public CouponTemplateInfo loadTemplateInfo(Long id) {
    System.out.println("金丝雀getTemplate。。。");
    Optional<CouponTemplate> template = templateDao.findById(id);
    // 模拟金丝雀代码
    template.get().setShopId(1000L);
    return template.isPresent() ? CouponTemplateConverter.convertToTemplateInfo(template.get()) : null;
}

这样如果是最终在表coupon中插入数据的shopId值等于1000则就是走了新代码逻辑了,也就是被金丝雀了,修改后我们再来启动一个template实例,作为部署了新功能的节点:
在这里插入图片描述
接着我们还需要为新上线的节点添加traffic-version:test0011,如下操作:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接着我们就可以来测试了,如果是如下的请求,则会被金丝雀
在这里插入图片描述
连续执行几次,会发现shopId都是1000,如下:
在这里插入图片描述

写在后面

参考文章列表

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jstat虚拟机统计信息监控工具

jstat虚拟机统计信息监控工具 1、jstat&#xff08;JVM Statistics Monitorning Tool&#xff09; 用于监控虚拟机各种运行状态信息的命令行工具。 它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据&#xff0c;它是运行期定位虚拟机 性能问题…

VRRP协议详解

目录 一、基础概念 1、概念 2、VRRP的基本结构 状态机 二、VRRP主备备份工作过程 1、备份工作过程 2、VRRP的负载分担工作 三、实验 一、基础概念 1、概念 VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的I…

StarCCM+ 导入STL几何模型进行仿真

使用 StarCCM 进行仿真时&#xff0c;通常都是用 3D-CAD Model 导入 CAD 类型的几何模型。但对于一些特殊情况&#xff0c;例如通过三维重建等方法获得的几何模型是 STL 文件而非 CAD 文件&#xff0c;这种情况下可以通过 Import Surface Mesh 的方法导入 STL 文件进行仿真&…

浮在文字上面的下划线的设置css

话不多说直接贴代码 前5行css就是实现的代码&#xff1a; p {text-decoration-line: underline;text-underline-offset: -7.5px;text-decoration-thickness: 5px;text-underline-position: under;text-decoration-color: #FFF200;color: #666;font-size: 20px;line-height: 2…

【源码系列】外卖系统外卖平台外卖软件开发外卖小程序APP

系统概念 外卖系统是一套类似美团和饿了么的外卖送餐解决方案&#xff0c;可以在PC、H5、APP、小程序、微信等全平台进行操作。系统配备用户端、商户端、配送端、管理端&#xff0c;可供消费者、商家、骑手&#xff0c;平台多角色使用。同时提供调度中心、分站管理等增值功能。…

openoffice安装

安装openoffice 下载安装包 建议去官网下载&#xff0c;这里给出官网地址&#xff1a;https://www.openoffice.org/download/ 这里以linux为例&#xff0c;所以我下载的是unbantu的版本(deb) 解压安装包 tar -zxvf Apache_OpenOffice_4.1.13_Linux_x86-64_install-deb_zh-…

【lesson15】MySQL表基本查询 (retrieve(2))

文章目录 表的基本操作介绍retrieve结果排序建表基本测试 筛选分页结果建表测试 表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; retrieve 结果排序 建表 建表这里就不建了&#xff0c;因为…

【期末复习向】n元gram的应用

当 n 1 时&#xff0c; 即出现 在 第 i 位 上 的基 元 w i 独 立于 历 史 。 一元文法也 被 写 为 uni-gram 或 monogram&#xff1b; 当 n 2 时 , 2-gram ( bi-gram ) 被称 为 1 阶 马 尔 可夫 链&#xff1b; 当 n 3 时 , 3-gram( tri-gram ) 被称为 2 阶马尔 可 夫 链 &am…

上门回收小程序开发,让环保发展成收益

如今资源回收&#xff0c;保护环境已成为全球人民共同追求的目标&#xff0c;人们的环保意识也逐渐提高&#xff0c;回收行业受到了大众的关注。 在移动互联网的发展下&#xff0c;传统回收行业也逐渐向线上智能化转型&#xff0c;线上上门回收小程序也逐渐出现在了大众生活中…

C语言的system函数简介

函数原型 包含在头文件 “stdlib.h” 中 int system(const char * command) 函数功能 执行 dos(windows系统) 或 shell(Linux/Unix系统) 命令&#xff0c;参数字符串command为命令名。另&#xff0c;在windows系统下参数字符串不区分大小写。 说明&#xff1a;在windows系统中&…

基于ssm服装定制系统源码和论文

idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 环境&#xff1a; jdk8 tomcat8.5 开发技术 ssm 基于ssm服装定制系统源码和论文751 1.1项目研究的背景 困扰管理层的许多问题当中,服装定制将是广大用户们不可忽视的一块。但是管理好服装定制又面临很多麻…

VINS-MONO代码解读6----pose_graph

开始pose_graph部分&#xff0c;本部分记住一句话无论是快速重定位还是正常重定位&#xff0c;求出 T w 1 w 2 T_{w_1w_2} Tw1​w2​​就是终极目标。 还剩一个整体Pipeline~~ 1. pose_graph_node.cpp 注意&#xff0c;定义全局变量时即实例化了一个对象 PoseGraph posegra…

26种主流的神经网络偏微分方程求解方法汇总

偏微分方程&#xff08;PDE&#xff09;是数学中一门重要的分支&#xff0c;应用范围广泛涉及自然科学、工程技术、生物学领域等。然而我们都知道&#xff0c;偏微分方程的求解过程异常艰难&#xff0c;如果碰上了特别复杂的&#xff0c;传统的计算方法可能需要数百万个CPU小时…

[多线程]一篇文章带你看懂Java中的synchronized关键字(线程安全)锁的深入理解

目录 1.前言 2.synchronized的特性 2.1synchronized前言 2.2乐观锁和悲观锁 2.3重量级锁和轻量级锁 重量级锁 &#xff1a; 轻量级锁&#xff1a; 2.4自旋锁和挂起等待锁 2.5 公平锁和非公平锁 公平锁&#xff1a; 非公平锁&#xff1a; 2.6可重入锁和不可重入锁 可…

bat 脚本的常用特殊符号

1、 命令行回显屏蔽符 2、% 批处理变量引导符 3、> 重定向符 4、>> 重定向符 5、<、>&、<& 重定向符 6、| 命令管道符 7、^ 转义字符 8、& 组合命令 9、&& 组合命令 10、|| 组合命令 11、"" 字符串界定符 12、, 逗号…

报错“找不到mfc100u.dll,程序无法继续执行”的解决方法,完美解决

在软件操作过程中&#xff0c;部分用户可能遇到"计算机缺失mfc140u.dll导致无法启动程序"的困扰。这种情况常常发生在启动某特定应用&#xff0c;特别是需要VC Redistributable支持的软件时。以下为详尽解决策略&#xff0c;让用户轻松应对这类技术难题&#xff0c;重…

React中的setState执行机制

我这里今天下雨了&#xff0c;温度一下从昨天的22度降到今天的6度&#xff0c;家里和学校已经下了几天雪了&#xff0c;还是想去玩一下的&#xff0c;哈哈&#xff0c;只能在图片里看到了。 一. setState是什么 它是React组件中用于更新状态的方法。它是类组件中的方法&#x…

人工智能与天文:技术前沿与未来展望

人工智能与天文&#xff1a;技术前沿与未来展望 一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;在各个领域的应用越来越广泛。在天文领域&#xff0c;AI也发挥着越来越重要的作用。本文将探讨人工智能与天文学的结合&#xff0c;以及这种结合带…

SpringBoot之JSON参数,路径参数的详细解析

1.6 JSON参数 在学习前端技术时&#xff0c;我们有讲到过JSON&#xff0c;而在前后端进行交互时&#xff0c;如果是比较复杂的参数&#xff0c;前后端通过会使用JSON格式的数据进行传输。 &#xff08;JSON是开发中最常用的前后端数据交互方式&#xff09; 我们学习JSON格式参…

在VS2010上使用C#调用非托管C++生成的DLL文件(图文讲解)

背景 在项目过程中&#xff0c;有时候你需要调用非C#编写的DLL文件&#xff0c;尤其在使用一些第三方通讯组件的时候&#xff0c;通过C#来开发应用软件时&#xff0c;就需要利用DllImport特性进行方法调用。本篇文章将引导你快速理解这个调用的过程。 步骤 1. 创建一个CSharp…