Spring Cloud微服务:Loadbalancer 实战

2023-10-27

nacos维护一个列表,但是我们请求服务不可能一个服务所有的都请求一遍,比如我做一笔转账,我找到其中一个做一次转账就够了,而不是看到有多个转账服务,都去转一次。那这个就需要“选择”,选择这个靠谁来做呢,其实就是客户端负载均衡组件 Spring Cloud Loadbalancer。

均衡负载

首先作为均衡负载的组件,要知道均衡负载是在做什么的?
说白了,我们有对应的服务集群,我们只能把所有的服务指定到对应的一台机或其中的几台机,要是这样集群也没啥特别大的意义。大家都参与进来,别可着一个用就往死里用,一核有难,八核围观的情况。

这里涉及到一个均衡负载的技术,大概就是两种:网关层均衡负载和客户端层均衡负载

网关层想我之前公司有用的F5,就是在网管层做均衡负载,就是你只管请求我,我来负责维护服务地址列表,你也不需要搞什么服务发现这些。(坏处是涉及到网关层维护,网络消耗和复杂度维护角度不简单)

客户端层就好比我们这个LoadBalancer,就是我作为一个客户端我自己去不断去发现更新维护自己的一套服务列表,我自己去定义服务的均衡负载策略,不管是随机还是计数甚至可以搞小流量的金丝雀。

LoadBalancer的工作原理

Nacos 实战部分使用 WebClient 发起服务调用的过程。加了一个注解@Loadbalanced 这个注解就是开启负载均衡功能的玄机

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

实际上相当于给这个WebClient创建一个过滤器,帮助WebClient去请求那个主机的服务。


Builder filter(ExchangeFilterFunction filter);

主要的过程分成三步:
第一步,声明负载均衡过滤器
ReactorLoadBalancerClientAutoConfiguration 是一个自动装配器类,我们在项目中引入了 WebClient 和 ReactiveLoadBalancer 类之后,自动装配流程就开始忙活起来了。

它会初始化一个实现了 ExchangeFilterFunction 的实例,在后面的步骤中,该实例将作为过滤器被注入到 WebClient。(看下面的代码,是通过注解来实现的)

@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);
   }
   
    // 如果关闭了Loadbalancer的重试功能
    // 则初始化ReactorLoadBalancerExchangeFilterFunction对象
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false",
        matchIfMissing = true)
    @Bean
    public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
        ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, LoadBalancerProperties properties) {
        return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties);
    }
   // ...省略部分代码
}

第二步,声明后置处理器。LoadBalancerBeanPostProcessorAutoConfiguration 是第二个登场的自动装配器,它的主要作用是将第一步中创建的 ExchangeFilterFunction 拦截器实例添加到一个后置处理器(LoadBalancerWebClientBuilderBeanPostProcessor)中


// 省略部分代码
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);
   }
}

第三步,添加过滤器到 WebClient。LoadBalancerWebClientBuilderBeanPostProcessor 后置处理器开始发挥作用,将过滤器添加到 WebClient 中。注意不是所有的 WebClient 都会被注入过滤器,只有被 @Loadbalanced 注解修饰的 WebClient 实例才能享受这个待遇

通过LoadBalancer实现金丝雀测试

金丝雀测试是灰度测试的一种,就是我要在线上测试一下,但是我发的请求是给指定的或者有限范围,影响小的几台机上,其他照样是正常的业务。

参与测试的几台机都是金丝雀,只有打过标的,带有“测试流量标记”才能到这些机子上,不影响其他服务器的日常服务

在这里插入图片描述
用LoadBalancer来实现金丝雀测试,默认的两种均衡负载策略:一个是随机拼概率,一个是计数器大家都参与雨露均沾。这两个肯定是不行的,要自己定义均衡负载的策略。

编写 CanaryRule 负载均衡
创建了一个叫 CanaryRule 的负载均衡规则类,它继承自 Loadbalancer 项目的标准接口 ReactorServiceInstanceLoadBalancer。

CanaryRule 借助 Http Header 中的属性和 Nacos 服务节点的 metadata 完成测试流量的负载均衡。在这个过程里,它需要准确识别哪些请求是测试流量,并且把测试流量导向到正确的目标服务。

CanaryRule 如何识别测试流量
识别那些是测试流量,这是金丝雀测试的一个要点。

如果 WebClient 发出一个请求,其 Header 的 key-value 列表中包含了特定的流量 Key:traffic-version,那么这个请求就被识别为一个测试请求,只能发送到特定的金丝雀服务器上。

CanaryRule 如何对测试流量做负载均衡:
包含了新的代码改动的服务器就是这个金丝雀,我会在这台服务器的 Nacos 元数据中插入同样的流量密码:traffic-version。如果 Nacos 元数据中的 traffic-version 值与测试流量 Header 中的一样,那么这个 Instance 就是我们要找的那只金丝雀。


// 可以将这个负载均衡策略单独拎出来,作为一个公共组件提供服务
@Slf4j
public class CanaryRule implements ReactorServiceInstanceLoadBalancer {
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private String serviceId;
    // 定义一个轮询策略的种子
    final AtomicInteger position;
    
    // ...省略构造器代码
    
    // 这个服务是Loadbalancer的标准接口,也是负载均衡策略选择服务器的入口方法
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, request));
    }
    
    // 省略该方法内容,本方法主要完成了对getInstanceResponse的调用
    private Response<ServiceInstance> processInstanceResponse(
    }
    
    // 根据金丝雀的规则返回目标节点
    Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
        // 注册中心无可用实例 返回空
        if (CollectionUtils.isEmpty(instances)) {
            log.warn("No instance available {}", serviceId);
            return new EmptyResponse();
        }
        // 从WebClient请求的Header中获取特定的流量打标值
        // 注意:以下代码仅适用于WebClient调用,使用RestTemplate或者Feign则需要额外适配
        DefaultRequestContext context = (DefaultRequestContext) request.getContext();
        RequestData requestData = (RequestData) context.getClientRequest();
        HttpHeaders headers = requestData.getHeaders();
        // 获取到header中的流量标记
        String trafficVersion = headers.getFirst(TRAFFIC_VERSION);
        
        // 如果没有找到打标标记,或者标记为空,则使用RoundRobin规则进行查找
        if (StringUtils.isBlank(trafficVersion)) {
            // 过滤掉所有金丝雀测试的节点,即Nacos Metadaba中包含流量标记的节点
            // 从剩余的节点中进行RoundRobin查找
            List<ServiceInstance> noneCanaryInstances = instances.stream()
                    .filter(e -> !e.getMetadata().containsKey(TRAFFIC_VERSION))
                    .collect(Collectors.toList());
            return getRoundRobinInstance(noneCanaryInstances);
        }
        
        // 如果WelClient的Header里包含流量标记
        // 循环每个Nacos服务节点,过滤出metadata值相同的instance,再使用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);
    }
    
    // 使用RoundRobin机制获取节点
    private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
        // 如果没有可用节点,则返回空
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + serviceId);
            return new EmptyResponse();
        }
        
        // 每一次计数器都自动+1,实现轮询的效果
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}

确定测试流量,例如通过查看header里面有没有定义的TRAFFIC_VERSION标记 headers.getFirst(TRAFFIC_VERSION)
测试流量的均衡负载,就是找到那些nacos上medata有TRAFFIC_VERSION的节点。

配置负载均衡策略
我们写好了金丝雀的论衡负载的策略代码,但是不能@Configuration注解,因为这样或部署到全局


// 注意这里不要写上@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);
    }
}

写好配置类之后,我们需要在 coupon-customer-serv 的启动类上添加一个 @LoadBalancerClient 注解,将 Configuration 类和目标服务关联起来


// 发到coupon-template-serv的调用,使用CanaryRuleConfiguration中定义的负载均衡Rule
@LoadBalancerClient(value = "coupon-template-serv", configuration = CanaryRuleConfiguration.class)
public class Application {
   // xxx省略方法
}

测试流量打标
掐你按方案定好了,要给数据打标了

测试流量打标的方法有很多种,比如添加一个特殊的 key-value 到 Http header,或者塞一个值到 RPC Context 中。为了方便演示,我这里采用了一种更为简单的方式,直接在用户领券接口的请求参数对象 RequestCoupon 中添加了一个 trafficVersion 成员变量,用来标识测试流量。


public class RequestCoupon {
  //.. 省略其他成员变量

  // Loadbalancer - 用作测试流量打标
  private String trafficVersion;
}

@Override
public Coupon requestCoupon(RequestCoupon request) {
    CouponTemplateInfo templateInfo = webClientBuilder.build().get()
            .uri("http://coupon-template-serv/template/getTemplate?id=" + request.getCouponTemplateId())
            // 将流量标记传入WebClient请求的Header中
            .header(TRAFFIC_VERSION, request.getTrafficVersion())
            .retrieve()
            .bodyToMono(CouponTemplateInfo.class)
            .block();
 
    // xxx 省略以下代码           
}

nacos也要设置medata
在这里插入图片描述
设置traffic-version
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Cloud微服务:Loadbalancer 实战 的相关文章

随机推荐

  • ARM三级流水线

    ARM处理器三级流水操作 PC寄存器 ARM处理器3级流水线 取指 gt 译码 gt 执行 这3级流水线循环执行 在R15 PC 中 我们一般以正在执行的指令作为参考点 在执行当前指令的同时也对第二条指令译码 第三条指令在取指 实际来说PC
  • 深度学习论文笔记(增量学习)——Large Scale Incremental Learning

    文章目录 前言 主要工作 算法介绍 总体流程 步骤一 训练CNN模型 loss函数介绍 阶段二 训练偏置纠正层 实验 分类器是否出现分类偏好 猜测 实验 引入Bias Correction Layer 实验 Ablation Study 前
  • HIVE 分组排序查询

    笔者在参加面试时遇到的一个关于hive数据库查询的的问题题意大概是这个样子的 有如下图结构的一个表 表中的数据是长这个样子的 面试官要求我查询的结果是长这个样子的 我第一印象觉得这个题还蛮简单的 就写了一个 select col1 col2
  • 运输层---概述

    目录 运输层主要内容 一 概述和传输层服务 1 1 概述 1 2 传输服务和协议 1 3 传输层 vs 网络层 1 4 Internet传输层协议 二 多路复用与多路分解 解复用 2 1 概述 2 2 无连接与面向连接的多路分解 解复用 2
  • 关于以太网没有有效的ip配置问题解决方法

    目录 错误提示 解决方法 一 检查IP地址是否为自动获取 二 重置网络环境 三 卸载以太网模块 重启电脑自动重新安装新的模块 四 重启网卡驱动程序 五 检查外部设备 错误提示 解决方法 一 检查IP地址是否为自动获取 1 首先右键任务栏右下
  • Vue.js 如何进行打包部署

    Vue js 中的打包部署 Vue js 是一款流行的前端框架 它提供了一种简单 灵活的方式来构建用户界面 在开发完成后 需要对 Vue js 应用程序进行打包部署 以便在生产环境中使用 本文将介绍 Vue js 中的打包部署以及如何进行打
  • C++11标准库(STL)使用总结

    STL定义了强大的 基于模板的 可复用的组件 实现了许多通用的数据结构及处理这些数据结构的算法 其中包含三个关键组件 容器 container 流行的模板数据结构 迭代器 iterator 和算法 algorithm 组件 描述 容器 容器
  • 初始操作系统(Operation System)

    目录 操作系统的概念和功能 操作系统的四大特征 操作系统给发展过程 操作系统的运行机制 操作系统的概念和功能 操作系统这个词语对我们既熟悉又陌生 生活中会接触到 但具体的实现我么不从得知 生活中直接接触的操作系统有 windows linu
  • hadoop 常用缺少包错误

    package wc import java io IOException import java util StringTokenizer import org apache hadoop conf Configuration impor
  • 说说你对 Object.defineProperty 的理解?

    说说你对 Object defineProperty 的理解 什么是 为什么能实现响应式 小结 什么是 Object defineProperty 方法会直接在一个对象上定义一个新属性 或者修改一个对象的现有属性 并返回此对象 为什么能实现
  • printf输出%f %lld区别

    https blog csdn net u011497904 article details 42454483 utm source blogxgwz2
  • shell中的for循环示例

    1 利用for循环打印 示例代码 bin bash for i 0 i lt 3 i do for j 0 j lt 5 j 每行打印5个 打印三行 do echo n done echo done 2 利用for循环计算1到100的和 示
  • python读取xlsx格式的excle

    python读取excle的xlsx和xls格式代码略有不同 import pandas as pd from pandas import DataFrame if name main 读取excle表中的数据 file path r D
  • 【华为OD机试】数字反转打印【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 小华是个很有对数字很敏感的小朋友 他觉得数字的不同排列方式有特殊美感 某天 小华突发奇想 如果数字多行排列 第一行1个数 第二行2个 第三行3个 即第n行有n个数字
  • Java高级开发必知必会——反射

    title Java高级开发必知必会 反射 author rocklei123 tags Java 反射 categories Java date 2018 09 16 08 20 57 1 目标与意义 反射是Java开发中一个非常重要的概
  • Linux 之 shell 比较运算符

    运算符 描述 示例 文件比较运算符 e filename 如果 filename 存在 则为真 e var log syslog d filename 如果 filename 为目录 则为真 d tmp mydir f filename 如
  • 日期子组件

    日期子组件 如图
  • MySQL忘记密码的处理方法(MySQL重置密码)

    1 关闭正在运行的MySQL服务 在任务管理器中找到MySQL对应的服务 然后将其停止 2 打开CMD命令行窗口 转到mysql bin目录 3 输入mysqld skip grant tables 回车 mysqld skip grant
  • 《Collaborative Filtering for Implicit...》论文阅读

    论文题目 Collaborative Filtering for Implicit Feedback Datasets 链接 link 1 Introduction 随着电商的快速发展 为用户提供商品的排序很重要 推荐系统就是为用户提供符合
  • Spring Cloud微服务:Loadbalancer 实战

    nacos维护一个列表 但是我们请求服务不可能一个服务所有的都请求一遍 比如我做一笔转账 我找到其中一个做一次转账就够了 而不是看到有多个转账服务 都去转一次 那这个就需要 选择 选择这个靠谁来做呢 其实就是客户端负载均衡组件 Spring