背景:

当我们使用微服务时,若想在本地联调就需要启动多个服务,为了避免本地启动过多服务,现将注册中心等基础服务共用。当我们在服务A开发时,都是注册到同一个nacos,这样本地和开发环境的服务A就会同时存在,当调用服务时就会使用负载均衡选择服务,导致我们无法正常调试接口。这时我们可以选择使用灰度版本来进行服务的选择。

具体实现步骤如下:

1、我们在本地配置文件中添加版本头

这样我们服务注册到nacos中点击服务列表会发现服务中都会带VERSION

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. metadata:
  6. VERSION: zhangsan

2、添加灰度服务接口

  1. public interface GrayLoadBalancer {
  2. /**
  3. * 根据serviceId 筛选可用服务
  4. * @param serviceId 服务ID
  5. * @param request 当前请求
  6. * @return ServiceInstance
  7. */
  8. ServiceInstance choose(String serviceId, ServerHttpRequest request);
  9. }

3、灰度过滤器

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.apache.http.util.Asserts;
  3. import org.springframework.cloud.client.ServiceInstance;
  4. import org.springframework.cloud.client.loadbalancer.DefaultResponse;
  5. import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
  6. import org.springframework.cloud.client.loadbalancer.Response;
  7. import org.springframework.cloud.gateway.config.LoadBalancerProperties;
  8. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  9. import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
  10. import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
  11. import org.springframework.cloud.gateway.support.NotFoundException;
  12. import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
  13. import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  14. import org.springframework.stereotype.Component;
  15. import org.springframework.web.server.ServerWebExchange;
  16. import reactor.core.publisher.Mono;
  17. import java.net.URI;
  18. @Slf4j
  19. @Component
  20. public class GrayReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
  21. private final static String SCHEME = "lb";
  22. private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
  23. private final GrayLoadBalancer grayLoadBalancer;
  24. private final LoadBalancerProperties loadBalancerProperties;
  25. public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties loadBalancerProperties, GrayLoadBalancer grayLoadBalancer) {
  26. super(clientFactory, loadBalancerProperties);
  27. this.loadBalancerProperties = loadBalancerProperties;
  28. this.grayLoadBalancer = grayLoadBalancer;
  29. }
  30. @Override
  31. public int getOrder() {
  32. return LOAD_BALANCER_CLIENT_FILTER_ORDER;
  33. }
  34. @Override
  35. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  36. URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
  37. String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
  38. // 直接放行
  39. if (url == null || (!SCHEME.equals(url.getScheme()) && !SCHEME.equals(schemePrefix))) {
  40. return chain.filter(exchange);
  41. }
  42. // 保留原始url
  43. ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
  44. if (log.isTraceEnabled()) {
  45. log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
  46. }
  47. return choose(exchange).doOnNext(response -> {
  48. if (!response.hasServer()) {
  49. throw NotFoundException.create(loadBalancerProperties.isUse404(),
  50. "Unable to find instance for " + url.getHost());
  51. }
  52. URI uri = exchange.getRequest().getURI();
  53. // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
  54. // if the loadbalancer doesn't provide one.
  55. String overrideScheme = null;
  56. if (schemePrefix != null) {
  57. overrideScheme = url.getScheme();
  58. }
  59. DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(),
  60. overrideScheme);
  61. URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
  62. if (log.isTraceEnabled()) {
  63. log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
  64. }
  65. exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
  66. }).then(chain.filter(exchange));
  67. }
  68. /**
  69. * 获取实例
  70. * @param exchange ServerWebExchange
  71. * @return ServiceInstance
  72. */
  73. private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
  74. URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
  75. Asserts.notNull(uri, "uri");
  76. ServiceInstance serviceInstance = grayLoadBalancer.choose(uri.getHost(), exchange.getRequest());
  77. return Mono.just(new DefaultResponse(serviceInstance));
  78. }
  79. }

4、基于客户端版本号灰度路由

当我们调用服务带版本号时会优先匹配带版本号的服务,若找不到则会随机选择一个服务

  1. import cn.hutool.core.collection.CollUtil;
  2. import cn.hutool.core.map.MapUtil;
  3. import cn.hutool.core.util.RandomUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import lombok.RequiredArgsConstructor;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.cloud.client.ServiceInstance;
  8. import org.springframework.cloud.client.discovery.DiscoveryClient;
  9. import org.springframework.cloud.gateway.support.NotFoundException;
  10. import org.springframework.http.server.reactive.ServerHttpRequest;
  11. import org.springframework.stereotype.Component;
  12. import java.util.List;
  13. import java.util.stream.Collectors;
  14. @Slf4j
  15. @RequiredArgsConstructor
  16. @Component
  17. public class VersionGrayLoadBalancer implements GrayLoadBalancer {
  18. private final DiscoveryClient discoveryClient;
  19. /**
  20. * 根据serviceId 筛选可用服务
  21. * @param serviceId 服务ID
  22. * @param request 当前请求
  23. * @return ServiceInstance
  24. */
  25. @Override
  26. public ServiceInstance choose(String serviceId, ServerHttpRequest request) {
  27. List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
  28. // 注册中心无实例 抛出异常
  29. if (CollUtil.isEmpty(instances)) {
  30. log.warn("No instance available for {}", serviceId);
  31. throw new NotFoundException("No instance available for " + serviceId);
  32. }
  33. // 获取请求version,无则随机返回可用实例
  34. String reqVersion = request.getHeaders().getFirst(CommonConstant.VERSION);
  35. if (StrUtil.isBlank(reqVersion)) {
  36. return instances.get(RandomUtil.randomInt(instances.size()));
  37. }
  38. // 遍历可以实例元数据,若匹配则返回此实例
  39. List<ServiceInstance> availableList = instances.stream()
  40. .filter(instance -> reqVersion
  41. .equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstant.VERSION)))
  42. .collect(Collectors.toList());
  43. if (CollUtil.isEmpty(availableList)) {
  44. return instances.get(RandomUtil.randomInt(instances.size()));
  45. }
  46. return availableList.get(RandomUtil.randomInt(availableList.size()));
  47. }
  48. }

springcloud + nacos实现共用基础服务(灰度版本)的更多相关文章

  1. SpringCloud+Nacos实现服务配置中心(Hoxton版本)

    关于 Nacos Spring Cloud 的详细文档请参看:Nacos Config和Nacos Discovery. 通过 Nacos Server 和 spring-cloud-starter- ...

  2. 如何基于 Nacos 和 Sentinel ,实现灰度路由和流量防护一体化

    基于Alibaba Nacos和Sentinel,实现灰度路由和流量防护一体化的解决方案,发布在最新的 Nepxion Discovery 5.4.0 版,具体参考: 源码主页,请访问 源码主页指南主 ...

  3. SpringCloud实战 | 第五篇:SpringCloud整合OpenFeign实现微服务之间的调用

    一. 前言 微服务实战系列是基于开源微服务项目 有来商城youlai-mall 版本升级为背景来开展的,本篇则是讲述SpringCloud整合OpenFeign实现微服务之间的相互调用,有兴趣的朋友可 ...

  4. 朱晔的互联网架构实践心得S2E7:漫谈平台架构的工作(基础架构、基础服务、基础平台、基础中间件等等)

    前言 程序开发毕竟还不是搬砖这种无脑体力劳动,需要事先有标准,有架构,有设计,绝对不是新公司今天创立,明天就可以开始编码的.其实很多公司在起步的时候没有财力和资源建设独立的基础架构或平台架构部门,甚至 ...

  5. springcloud费话之Eureka基础

    目录: springcloud费话之Eureka基础 springcloud费话之Eureka集群 springcloud费话之Eureka服务访问(restTemplate) springcloud ...

  6. Nacos 发布 1.0.0 GA 版本,可大规模投入到生产环境

    经过 3 个 RC 版本的社区体验之后,Nacos 正式发布 1.0.0 GA 版本,在架构.功能和 API 设计上进行了全方位的重构和升级. 1.0.0 版本的发布标志着 Nacos 已经可以大规模 ...

  7. Nacos配置中心和服务的注册发现

    在上一篇中,我们已经把Nacos的集群搭建好了,那么既然已经搭建好了,就要在咱们的项目中去使用.Nacos既可以做配置中心,也可以做注册中心.我们先来看看在项目中如何使用Nacos做配置中心. Nac ...

  8. 🏆【Alibaba中间件技术系列】「Nacos技术专题」服务注册与发现相关的原理分析

    背景介绍 前几篇文章介绍了Nacos配置中心服务的能力机制,接下来,我们来介绍Nacos另一个非常重要的特性就是服务注册与发现,说到服务的注册与发现相信大家应该都不陌生,在微服务盛行的今天,服务是非常 ...

  9. Nacos源码系列—服务端那些事儿

    点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 项目源码地址:公众号回复 nacos,即可免费获取源码 前言 在上节课中,我们讲解了客户端注册服 ...

随机推荐

  1. Linux 环境下如何查找哪个线程使用 CPU 最长?

    1.获取项目的 pid,jps 或者 ps -ef | grep java,这个前面有讲过 2.top -H -p pid,顺序不能改变

  2. 是否可以从一个静态(static)方法内部发出对非静态 (non-static)方法的调用?

    不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在 调用静态方法时可能对象并没有被初始化.

  3. IOC——Spring的bean的管理(xml配置文件)

    Bean实例化(三种方式) 1.使用类的无参构造进行创建(大多数情况下) <bean id="user" class="com.bjxb.ioc.User" ...

  4. springboot项目如何添加热部署

    环境jdk1.8.maven3.6.使用工具为idea 1.在pom.xml文件中添加依赖 <dependency> <groupId>org.springframework. ...

  5. JDBC和桥接模式

    本文参考 网上对于JDBC与桥接模式的理解各有不同,在这片文章里提出的是我个人对于二者的理解,本文参考的其它博文如下: https://blog.csdn.net/paincupid/article/ ...

  6. 【Android开发】用WebView访问证书有问题的SSL网页

    Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点. ...

  7. 【Android开发】【第三方SDK】 安卓版分词功能

    功能介绍: 获取剪切板内容,进行分词: 点击分解后的词,填入输入框: 点击叉号将地址拼接起来返回主界面 用途: 增加用户的体验效果,可以直接在微信上复制地址,然后通过此功能确认地址. 附上git地址 ...

  8. java中自动插入一个默认的构造函数,这到底怎么回事?

    1.2 当没有任何构造函数,java编译器,会插入一个默认的构造函数    见下面的例子: class Line {     double x = 0.02;     double y; } publ ...

  9. mint-ui中messagebox的使用

    效果图: 代码: // 安装 # Vue 1.x npm install mint-ui@1 -S # Vue 2.0 npm install mint-ui -S // 引入全部组件 import ...

  10. No package XXX available 解决方法

    当前yum源找不到所需包,安装epel源即可 yum install -y epel-release EPEL 的全称叫 Extra Packages for Enterprise Linux.EPE ...