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")@Beanpublic 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)@Beanpublic 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@PrimaryDeferringLoadBalancerExchangeFilterFunction<LoadBalancedExchangeFilterFunction> reactorDeferringLoadBalancerExchangeFilterFunction(ObjectProvider<LoadBalancedExchangeFilterFunction> exchangeFilterFunctionProvider) {return new DeferringLoadBalancerExchangeFilterFunction<>(exchangeFilterFunctionProvider);}}// 将过滤器打包到后置处理器中@Beanpublic 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的标准接口,也是负载均衡策略选择服务器的入口方法@Overridepublic 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 {@Beanpublic 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

Spring Cloud微服务:Loadbalancer 实战相关推荐

  1. 《Spring Cloud微服务架构实战派》PDF样章

    内容摘要: 本书针对Spring Cloud Greenwich.SR2版本+Spring Boot的2.1.x.RELEASE版本.在编写过程中,不仅考虑到在企业任职所需的技能,还考虑到求职面试时可 ...

  2. 疯狂Spring Cloud微服务架构实战

    网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...

  3. Spring Cloud 微服务开发实战

    为什么要学习微服务? 1. 求职时增强技术自信. 微服务是非常热门的话题,企业招聘中也越来越多的要求有微服务开发.架构能力,不掌握一些微服务技能找工作时也缺乏亮点,可以说微服务架构已经成为中高级后端开 ...

  4. Spring Cloud 微服务项目实战 -

    文章目录 微服务"三大功能,两大特性" Spring Boot & Spring Cloud Spring Cloud 组件库一览 Spring Cloud 版本 毕业版本 ...

  5. Spring Cloud微服务实战:外卖订餐系统

    Spring Cloud微服务实战:外卖订餐系统 项目需求 客户端:针对普通用户, 用户登录.用户退出.菜品订购.我的订单. 后台管理系统:针对管理员, 管理员登录.管理员退出.添加菜品.查询菜品.修 ...

  6. Spring Cloud微服务实战:手把手带你整合eurekazuulfeignhystrix(附源码)

    Spring Cloud微服务实战:手把手带你整合eureka&zuul&feign&hystrix(附源码) Spring Cloud简介 Spring Cloud是一个基于 ...

  7. Spring Cloud 微服务项目操作实战流程(完结)

    Spring Cloud入门项目操作实战流程 Day01~02 〇.Service - 业务服务结构 商品服务 item service,端口 8001 用户服务 user service,端口 81 ...

  8. Spring Cloud微服务之loadbalancer负载平衡

    Spring Cloud微服务之loadbalancer负载平衡 小学时候,曾经做过这样的数学题. 说有一个水池子,上面有一个排水管,下面有一个进水管,开一个进水管,6个小时灌满水池,开一个排水管,1 ...

  9. 实战系列-Spring Cloud微服务中三把利器Feign、Hystrix、Ribbon

    导语   在之前的分享中分享过关于Fegin的底层实现原理,以及Spring Cloud OpenFegin的启动原理.在这次的分享中主要总结一下Spring Cloud 微服务架构的三把利器.对于F ...

  10. Spring Boot+Eureka+Spring Cloud微服务快速上手项目实战

    说明 我看了一些教程要么写的太入门.要么就是写的太抽象.真正好的文章应该是快速使人受益的而不是浪费时间.本文通过一个包括组织.部门.员工等服务交互的案例让刚接触spring cloud微服务的朋友快速 ...

最新文章

  1. Gulp 之图片压缩合并
  2. python监控服务器信息,Python监控服务器实现邮件微信报警
  3. 常见的Java审计代码函数关键字_转载:Java代码审计汇总系列(一)——SQL注入
  4. u8 和 char如何转化_EXCEL小知识——如何快速实现文本与数值的互相转化
  5. 动态生成圈形+文字的图片
  6. php可以在dw中吗,php新手求助,为什么在DW中设计里能看到运行php程序的结果,但…...
  7. soem移植 SOEM源码 基于STM32构建EtherCAT主站
  8. VS2008安装失败,组件安装失败的解决办法
  9. java web,添加删除文本框
  10. PHPNow 本地服务器安装
  11. 办公自动化工具-Excel文件、sheet合并工具
  12. 小编带你一起揭开DLL木马的神秘面纱(转)
  13. XSS Challenges xss-quiz.int21h.jp
  14. 七大视频剪辑软件,达人必备,你用过几个?
  15. 名编辑电子杂志大师教程 | 设置电子杂志的高宽比例
  16. 解决jrebe-JVMTI[FATAL] Couldnt write to C:\Users\启动报错
  17. js 根据id 取出数组中对应的对象
  18. 超级简单的自动刷新_144Hz刷新率真比60Hz快 谁用谁知道
  19. js中百度地图API获取本地搜索全部结果自定义显示效果
  20. 接口调用-【1】顺丰运单查询

热门文章

  1. java分发器 及(注解 + 反射机制)—————— 开开开山怪
  2. redis分布式锁-redisson
  3. uni-app 退出app操作
  4. RocketMQ入门(5.单向(Oneway)发送)
  5. 大型网站架构技术演进(史上最全)
  6. WinPE工具箱制作PE系统
  7. 【图解CAN总线】-6-classic CAN 2.0总线网络“负载率”计算
  8. 超宽带雷达P440?雷达的快时间慢时间是什么意思?
  9. Android studio2.0在app中设置背景图片及添加图片资源
  10. 数值微分的python实现