背景:

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

具体实现步骤如下:

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

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

spring:
cloud:
nacos:
discovery:
metadata:
VERSION: zhangsan

2、添加灰度服务接口

public interface GrayLoadBalancer {

	/**
* 根据serviceId 筛选可用服务
* @param serviceId 服务ID
* @param request 当前请求
* @return ServiceInstance
*/
ServiceInstance choose(String serviceId, ServerHttpRequest request); }

3、灰度过滤器

import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.Asserts;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import java.net.URI; @Slf4j
@Component
public class GrayReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { private final static String SCHEME = "lb"; private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private final GrayLoadBalancer grayLoadBalancer;
private final LoadBalancerProperties loadBalancerProperties; public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties loadBalancerProperties, GrayLoadBalancer grayLoadBalancer) {
super(clientFactory, loadBalancerProperties);
this.loadBalancerProperties = loadBalancerProperties;
this.grayLoadBalancer = grayLoadBalancer;
} @Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); // 直接放行
if (url == null || (!SCHEME.equals(url.getScheme()) && !SCHEME.equals(schemePrefix))) {
return chain.filter(exchange);
}
// 保留原始url
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) {
log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
} return choose(exchange).doOnNext(response -> { if (!response.hasServer()) {
throw NotFoundException.create(loadBalancerProperties.isUse404(),
"Unable to find instance for " + url.getHost());
} URI uri = exchange.getRequest().getURI(); // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = null;
if (schemePrefix != null) {
overrideScheme = url.getScheme();
} DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(),
overrideScheme); URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri); if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
}).then(chain.filter(exchange));
} /**
* 获取实例
* @param exchange ServerWebExchange
* @return ServiceInstance
*/
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
Asserts.notNull(uri, "uri");
ServiceInstance serviceInstance = grayLoadBalancer.choose(uri.getHost(), exchange.getRequest());
return Mono.just(new DefaultResponse(serviceInstance));
} }

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

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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component; import java.util.List;
import java.util.stream.Collectors; @Slf4j
@RequiredArgsConstructor
@Component
public class VersionGrayLoadBalancer implements GrayLoadBalancer { private final DiscoveryClient discoveryClient; /**
* 根据serviceId 筛选可用服务
* @param serviceId 服务ID
* @param request 当前请求
* @return ServiceInstance
*/
@Override
public ServiceInstance choose(String serviceId, ServerHttpRequest request) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId); // 注册中心无实例 抛出异常
if (CollUtil.isEmpty(instances)) {
log.warn("No instance available for {}", serviceId);
throw new NotFoundException("No instance available for " + serviceId);
} // 获取请求version,无则随机返回可用实例
String reqVersion = request.getHeaders().getFirst(CommonConstant.VERSION);
if (StrUtil.isBlank(reqVersion)) {
return instances.get(RandomUtil.randomInt(instances.size()));
} // 遍历可以实例元数据,若匹配则返回此实例
List<ServiceInstance> availableList = instances.stream()
.filter(instance -> reqVersion
.equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstant.VERSION)))
.collect(Collectors.toList()); if (CollUtil.isEmpty(availableList)) {
return instances.get(RandomUtil.randomInt(instances.size()));
}
return availableList.get(RandomUtil.randomInt(availableList.size())); }
}

最新文章

  1. ASP.NET Aries 入门开发教程7:DataGrid的行操作(主键操作区)
  2. IL接口和类的属性
  3. 【Java每日一题】20161227
  4. cant create oci environment
  5. local认证
  6. 新安装loadrunner无法录制脚本的原因之一及解决方案
  7. (分享)FreeVideo1.6.1 VIP视频播放器(支持下载)
  8. 【转】jQuery中.bind() .live() .delegate() .on()的区别
  9. Android开发工具之Dash
  10. 学习WPF——了解路由事件
  11. Servlet 小试牛刀(doGet,doPost)
  12. HtmlAttribute HTML属性处理类
  13. loadlibrary(xxx.dll) 失败 返回14001 由于应用程序配置不正确 应用程序未能启动.重新安装应用程序可能会纠正这个问 .
  14. c++中类模版中的static数据成员的定义
  15. CloudNotes
  16. Pro/TOOLKIT入门教程汇总
  17. Identity Service - 解析微软微服务架构eShopOnContainers(二)
  18. OpenCV1.0在VC ++6.0下的配置
  19. C语言,链表操作
  20. abstract class和interface的异同

热门文章

  1. 用 Java 实现阻塞队列 ?
  2. 你是如何调用 wait()方法的?使用 if 块还是循环?为什么?
  3. Spark学习摘记 —— Pair RDD行动操作API归纳
  4. Altium Designer 原理图的绘制前导
  5. 安装Yarn
  6. Java/C++实现模板方法模式---数据库操作
  7. Mysql中如何开启慢查询功能?
  8. Python 图_系列之纵横对比 Bellman-Ford 和 Dijkstra 最短路径算法
  9. MySQL入门学习day3随笔3
  10. 那些年uniapp踩过的坑之-------搜索框插件uni-search-bar字体和图标居中的问题