网关
- 需要重写filter、LoadBalancerClientFactory、LoadBalancerClientConfiguration、LoadBalancer
- 再将filter、LoadBalancerClientFactory配置到spring中
- LoadBalancerClientConfiguration需配到spring中,但是不要主动被扫描到(也就是说不要被全局加载)
- 由filter做路由拦截,调用LoadBalancerClientFactory获取一个局部的LoadBalancer实例进行服务的筛选
- 服务待提供者metadata添加 VERSIONL:版本号 关键信息做区分
- 前端调用增加请求头 VERSION=版本号
feign
- 需要重写LoadBalancerClientFactory、LoadBalancerClientConfiguration、LoadBalancer
- 再将LoadBalancerClientFactory配置到spring中
- LoadBalancerClientConfiguration需配到spring中,但是不要主动被扫描到(也就是说不要被全局加载)
- 重新注入BlockingLoadBalancerClient,使用新的LoadBalancerClientFactory替换掉原本的Factory
- 由原本你的逻辑调用新的LoadBalancerClientFactory获取一个局部的LoadBalancer实例进行服务的筛选
- 服务待提供者metadata添加 VERSIONL:版本号 关键信息做区分
- feign接口在方法的@RequestMapping注解加上 headers={"VERSION=版本号"} 参数作为筛选
实操
公用的LoadBalancerClientFactory、LoadBalancerClientConfiguration、LoadBalancer
VersionGrayLoadBalancer.java
基于版本控制过滤的轮询,参照原org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer修改的
主要增加选择服务实例前对实例的metadata进行筛选
import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.*; import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.http.HttpHeaders; import reactor.core.publisher.Mono; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Slf4j public class VersionGrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; private String serviceId; private final AtomicInteger position; public VersionGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) { this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000)); } public VersionGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.position = new AtomicInteger(seedPosition); } @Override public Mono<Response<ServiceInstance>> choose(Request request) { HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders(); ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, headers)); } private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances, HttpHeaders headers) { if (serviceInstances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } else { String reqVersion = headers.getFirst("VERSION"); if (!StrUtil.isEmpty(reqVersion)) { List<ServiceInstance> serviceInstances2 = serviceInstances.stream() .filter(instance -> reqVersion.equals(instance.getMetadata().get("VERSION"))) .collect(Collectors.toList()); if (serviceInstances2.size() > 0) { serviceInstances = serviceInstances2; } } Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } } private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) { int pos = Math.abs(this.position.incrementAndGet()); ServiceInstance instance = instances.get(pos % instances.size()); return new DefaultResponse(instance); } }
GrayLoadBalancerClientConfiguration.java
获取VersionGrayLoadBalancer实例的配置类,此配置类不能在全局中注册,全局中注册会导致全局中就有一个VersionGrayLoadBalancer实例,但是这个实例并没有对应的serverId,会导致获取不到微服务实例导致报错,如果跟启动类的路径下需要啊启动类上增加扫码注解排除这个类的扫描
import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import com.mobk.base.common.gray.loadbalancer.VersionGrayLoadBalancer; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled public class GrayLoadBalancerClientConfiguration extends LoadBalancerClientConfiguration { @Bean public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, GrayLoadBalancerClientFactory grayLoadBalancerClientFactory) { String name = environment.getProperty(GrayLoadBalancerClientFactory.PROPERTY_NAME); return new VersionGrayLoadBalancer(grayLoadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }
GrayLoadBalancerClientFactory.java
这个主要参照原本的org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory
主要改的是其获取LoadBalancer实例的配置类为GrayLoadBalancerClientConfiguration,本来是想不用重写的,但是原本的LoadBalancerClientFactory是直接把这个配置类写死了,不得不重写
import com.mobk.base.common.gray.config.GrayLoadBalancerClientConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import org.springframework.cloud.context.named.NamedContextFactory; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.core.env.Environment; @Slf4j public class GrayLoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> implements ReactiveLoadBalancer.Factory<ServiceInstance> { public static final String NAMESPACE = "loadbalancer"; public static final String PROPERTY_NAME = NAMESPACE + ".client.name"; private final LoadBalancerClientsProperties properties; public GrayLoadBalancerClientFactory(LoadBalancerClientsProperties properties) { //主要修改的是这行代码,使用重写后的获取LoadBalancer配置类 super(GrayLoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME); this.properties = properties; } public static String getName(Environment environment) { return environment.getProperty(PROPERTY_NAME); } @Override public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) { return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class); } @Override public LoadBalancerProperties getProperties(String serviceId) { if (properties == null) { if (log.isWarnEnabled()) { log.warn("LoadBalancerClientsProperties is null. Please use the new constructor."); } return null; } if (serviceId == null || !properties.getClients().containsKey(serviceId)) { return properties; } return properties.getClients().get(serviceId); } }
网关的使用
GrayReactiveLoadBalancerClientFilter.java
网关需要重写路由拦截器,并注册到spring。参考原本的org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter修改的
主要修改原本的路由方式前缀 lb->grayLb 灰度理由前缀做区分、
LoadBalancerClientFactory->GrayLoadBalancerClientFactory 修改获取LoadBalancer的工厂为重写后的
import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.*; import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; 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.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.URI; import java.util.Map; import java.util.Set; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*; @Slf4j public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered { /** * Order of filter. */ public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150; //使用重写后的LoadBalancerClient工厂,获取重写后的LoadBalancer实例 private final GrayLoadBalancerClientFactory clientFactory; private final GatewayLoadBalancerProperties properties; public GrayReactiveLoadBalancerClientFilter(GrayLoadBalancerClientFactory clientFactory, GatewayLoadBalancerProperties properties) { this.clientFactory = clientFactory; this.properties = properties; } @Override public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); //修改路由规则前缀 if (url == null || (!"grayLb".equals(url.getScheme()) && !"grayLb".equals(schemePrefix))) { return chain.filter(exchange); } // preserve the original url addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) { log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); } URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); String serviceId = requestUri.getHost(); Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class); DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>( new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId))); return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> { if (!response.hasServer()) { supportedLifecycleProcessors.forEach(lifecycle -> lifecycle .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response))); throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost()); } ServiceInstance retrievedInstance = response.getServer(); 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 = retrievedInstance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, overrideScheme); URI requestUrl = reconstructURI(serviceInstance, uri); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); } exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response)); }).then(chain.filter(exchange)) .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>( CompletionContext.Status.FAILED, throwable, lbRequest, exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR))))) .doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>( CompletionContext.Status.SUCCESS, lbRequest, exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest())))))); } protected URI reconstructURI(ServiceInstance serviceInstance, URI original) { return LoadBalancerUriTools.reconstructURI(serviceInstance, original); } private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId, Set<LoadBalancerLifecycle> supportedLifecycleProcessors) { ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class); if (loadBalancer == null) { throw new NotFoundException("No loadbalancer available for " + serviceId); } supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); return loadBalancer.choose(lbRequest); } private String getHint(String serviceId) { LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId); Map<String, String> hints = loadBalancerProperties.getHint(); String defaultHint = hints.getOrDefault("default", "default"); String hintPropertyValue = hints.get(serviceId); return hintPropertyValue != null ? hintPropertyValue : defaultHint; } }
注册GrayLoadBalancerClientFactory和GrayReactiveLoadBalancerClientFilter
import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.util.Collections; import java.util.List; @Configuration(proxyBeanMethods = false) public class GrayLoadBalancerAutoConfiguration { private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations; public GrayLoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) { this.configurations = configurations; } /** * 注册GrayLoadBalancerClientFactory并用@Primary标记为主要的 */ @Bean @Primary public GrayLoadBalancerClientFactory grayLoadBalancerClientFactory(LoadBalancerClientsProperties properties) { GrayLoadBalancerClientFactory clientFactory = new GrayLoadBalancerClientFactory(properties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; } /** * 注册拦截器 */ @Bean public GlobalFilter grayReactiveLoadBalancerClientFilter(GrayLoadBalancerClientFactory clientFactory, GatewayLoadBalancerProperties properties) { return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties); } }
修改网关配置,增加灰度路由配置
spring:
cloud:
gateway:
routes:
- id: admin-gray
#使用灰度路由前缀
uri: grayLb://admin
predicates:
- Path=/gray/**
修改微服务配置,增加metadata数据,或者可以去nacos修改微服务的metadata数据,不过重启服务后可能会失效
# 服务配置文件
spring:
cloud:
nacos:
discovery:
metadata:
VERSION: v2
最后调用接口增加请求头 VERSION:v2即可请求到指定的服务
feign使用
注册GrayLoadBalancerClientFactory
重新注册BlockingLoadBalancerClient,使用GrayLoadBalancerClientFactory替换原来的LoadBalancerClientFactory
import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.util.Collections; import java.util.List; @Configuration(proxyBeanMethods = false) public class GrayLoadBalancerAutoConfiguration { private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations; public GrayLoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) { this.configurations = configurations; } /** * 注册GrayLoadBalancerClientFactory并用@Primary标记为主要的 */ @Bean @Primary public GrayLoadBalancerClientFactory grayLoadBalancerClientFactory(LoadBalancerClientsProperties properties) { GrayLoadBalancerClientFactory clientFactory = new GrayLoadBalancerClientFactory(properties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; } /** * 注册BlockingLoadBalancerClient,注入重写后的GrayLoadBalancerClientFactory */ @Bean public LoadBalancerClient blockingLoadBalancerClient(GrayLoadBalancerClientFactory loadBalancerClientFactory) { return new BlockingLoadBalancerClient(loadBalancerClientFactory); } }
在feign接口的
方法上的RequestMapping注解添加headers={"VERSION=v2"}
@PostMapping(value = "/log",headers = {"VERSION=v2"})
去微服务提供者修改配置添加metadata数据VERSION:v2
# 服务配置文件
spring:
cloud:
nacos:
discovery:
metadata:
VERSION: v2
直接调用feign接口即可
将以上所有配置和类提取到统一模块下进行配置
分拆上面的两个GrayLoadBalancerAutoConfiguration类为3个
GrayLoadBalancerAutoConfiguration.java 这个只注册工厂类
package com.mobk.base.common.gray.config; import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.util.Collections; import java.util.List; @Configuration(proxyBeanMethods = false) public class GrayLoadBalancerAutoConfiguration { private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations; public GrayLoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) { this.configurations = configurations; } @Bean @Primary public GrayLoadBalancerClientFactory grayLoadBalancerClientFactory(LoadBalancerClientsProperties properties) { GrayLoadBalancerClientFactory clientFactory = new GrayLoadBalancerClientFactory(properties); clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; } }
GrayLoadBalancerAutoConfigurationFeign.java
对Feign的单独配置类,只在Feign环境下启用
package com.mobk.base.common.gray.config; import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import feign.Feign; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Feign.class) public class GrayLoadBalancerAutoConfigurationFeign { /** * 在feign环境下启用 * @param loadBalancerClientFactory * @return */ @Bean public LoadBalancerClient blockingLoadBalancerClient(GrayLoadBalancerClientFactory loadBalancerClientFactory) { return new BlockingLoadBalancerClient(loadBalancerClientFactory); } }
GrayLoadBalancerAutoConfigurationGateway.java
对Gateway的单独配置类,只在网关环境下启动
package com.mobk.base.common.gray.config; import com.mobk.base.common.gray.factory.GrayLoadBalancerClientFactory; import com.mobk.base.common.gray.filter.GrayReactiveLoadBalancerClientFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @ConditionalOnBean(GatewayAutoConfiguration.class) @Configuration(proxyBeanMethods = false) public class GrayLoadBalancerAutoConfigurationGateway { /** * 在gateway环境下启用 * @param clientFactory * @param properties * @return */ @Bean public GlobalFilter grayReactiveLoadBalancerClientFilter(GrayLoadBalancerClientFactory clientFactory, GatewayLoadBalancerProperties properties) { return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties); } }
将几个配置加入到自动配置中
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.mobk.base.common.gray.config.GrayLoadBalancerAutoConfiguration,
com.mobk.base.common.gray.config.GrayLoadBalancerAutoConfigurationFeign,
com.mobk.base.common.gray.config.GrayLoadBalancerAutoConfigurationGateway