背景:
当我们使用nacos为注册中心注册微服务时,想本地环境和测试环境公用一个nacos,即注册中心等基础服务共用。当我们在服务A开发时,本地服务和测试环境服务都是注册到同一个nacos,由于nacos自带负载均衡策略,调用服务时就会使用负载均衡选择服务,导致我们无法正常调试接口。这时我们可以选择使用灰度版本来进行服务的选择。
废话不多说直接开干
首先声明一点我springcloud环境如下:
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
nacos版本是2.1.4,
具体实现步骤如下:
1、我们在配置文件中添加版本头(开发环境版本dev,我本地环境设置xf)
spring.cloud.nacos.discovery.metadata.VERSION=xf
2、添加灰度服务接口
/**
* @author xf
* @Title:
* @Description:
* @date 2023/1/12 17:17
*/
public interface GrayLoadBalancer {
/**
* 根据serviceId 筛选可用服务
* @param serviceId 服务ID
* @param request 当前请求
* @param exchange 请求不到目标实例时,走(父级)默认逻辑所需的参数
* @param clientFactory 请求不到目标实例时,走(父级)默认逻辑所需的参数
* @return Mono<Response<ServiceInstance>>
*/
Mono<Response<ServiceInstance>> choose(String serviceId, ServerHttpRequest request, ServerWebExchange exchange, LoadBalancerClientFactory clientFactory);
}
3、灰度过滤器
(过滤器主要是重写选择实例的策略)
import com.digital.cnzz.gateway.service.GrayLoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.Asserts;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.reactive.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;
/**
* @author xf
* @Title:
* @Description:
* @date 2023/1/12 17:16
*/
@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 LoadBalancerProperties loadBalancerProperties;
private final LoadBalancerClientFactory clientFactory;
public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties loadBalancerProperties, GrayLoadBalancer grayLoadBalancer) {
super(clientFactory, loadBalancerProperties);
this.loadBalancerProperties = loadBalancerProperties;
this.clientFactory = clientFactory;
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");
return grayLoadBalancer.choose(uri.getHost(), exchange.getRequest(),exchange,clientFactory);
}
}
4、基于客户端版本号灰度路由
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import com.digital.cnzz.gateway.service.GrayLoadBalancer;
import com.github.pagehelper.util.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author xf
* @Title:
* @Description:
* @date 2023/1/12 17:18
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class VersionGrayLoadBalancer implements GrayLoadBalancer {
private final DiscoveryClient discoveryClient;
/**
* 根据serviceId 筛选可用服务
* @param serviceId 服务ID
* @param request 当前请求
* @param exchange 请求不到目标实例时,走(父级)默认逻辑所需的参数
* @param clientFactory 请求不到目标实例时,走(父级)默认逻辑所需的参数
* @return return Mono<Response<ServiceInstance>>
*/
@Override
public Mono<Response<ServiceInstance>> choose(String serviceId, ServerHttpRequest request, ServerWebExchange exchange, LoadBalancerClientFactory clientFactory) {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
// 注册中心无实例 抛出异常
if (CollectionUtils.isEmpty(instances)) {
log.warn("No instance available for {}", serviceId);
throw new NotFoundException("No instance available for " + serviceId);
}
// 获取请求version,无则调用父类的策略调用可用实例
String reqVersion = request.getHeaders().getFirst("VERSION");
if (StringUtil.isEmpty(reqVersion)) {
//走父级默认负载均衡策略
return chooseSuper(exchange,clientFactory);
}
// 遍历可以实例元数据,若匹配则返回此实例
List<ServiceInstance> availableList = instances.stream()
.filter(instance -> reqVersion
.equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), "VERSION")))
.collect(Collectors.toList());
if (CollUtil.isEmpty(availableList)) {
//走父级默认负载均衡策略
return chooseSuper(exchange,clientFactory);
}
return Mono.just(new DefaultResponse(availableList.get(RandomUtil.randomInt(availableList.size()))));
}
public Mono<Response<ServiceInstance>> chooseSuper(ServerWebExchange exchange, LoadBalancerClientFactory clientFactory) {
//父级逻辑,具体实现可参考 org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.choose 方法
URI uri = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
ReactorLoadBalancer<ServiceInstance> loadBalancer = (ReactorLoadBalancer)clientFactory.getInstance(uri.getHost(), ReactorLoadBalancer.class, new Class[]{ServiceInstance.class});
if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + uri.getHost());
} else {
return loadBalancer.choose(this.createRequest());
}
}
private Request createRequest() {
return ReactiveLoadBalancer.REQUEST;
}
}