SpringCloudGateway实现灰度
⼀、什么是灰度发布?
灰度发布(⼜名⾦丝雀发布)是指在⿊与⽩之间,能够平滑过渡的⼀种发布⽅式。在其上可以进⾏A/B testing,即让⼀部分⽤户继续⽤产品特性A,⼀部分⽤户开始⽤产品特性B,如果⽤户对B没有什么反对意见,那么逐步扩⼤范围,把所有⽤户都迁移到B上⾯来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
实现的整体思路:
编写灰度路由
编写⾃定义filter
nacos服务配置需要灰度发布的服务的元数据信息以及权重
灰度路由从nacos服务拉取元数据信息以及权重,然后根据权重算法,返回符合要求的服务实例给⾃定义的filter
⽹关配置⽂件配置需要灰度路由的服务(因为本⽂代码没有⽹关实现动态路由,不然灰度路由可以配置在配置中⼼,从配置中⼼拉取)filter通过责任链模式,把服务实例透传给其他filter⽐如NettyRoutingFilter
⼆、SpringCloud Gateway集成
灰度路由
import org.apachemons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.active.DefaultResponse;
import org.springframework.cloud.active.EmptyResponse;
import org.springframework.cloud.active.Request;
import org.springframework.cloud.active.Response;
import org.springframework.NoopServiceInstanceListSupplier;
import org.springframework.ReactorServiceInstanceLoadBalancer;
import org.springframework.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
publisher.Flux;
publisher.Mono;
import java.util.List;
import java.util.Random;
import urrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author XIAXINYU3
* @date 2021/11/3
*/
public class VersionGrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private String serviceId;
private final AtomicInteger position;
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 = (HttpHeaders) Context();
ServiceInstanceListSupplier supplier = IfAvailable(NoopServiceInstanceListSupplier::new);
c语言常用函数速查手册
return ((Flux) ()).next().map(list -> processInstanceResponse((List<ServiceInstance>) list, headers));
}
private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) {
if (instances.isEmpty()) {
return new EmptyResponse();
} else {
theater中文谐音怎么读
String reqVersion = First("version");
if (StringUtils.isEmpty(reqVersion)) {
return processRibbonInstanceResponse(instances);
}
List<ServiceInstance> serviceInstances = instances.stream()
.filter(instance -> reqVersion.Metadata().get("version")))
.List());
if (serviceInstances.size() > 0) {
return processRibbonInstanceResponse(serviceInstances);
} else {
return processRibbonInstanceResponse(instances);
}
}
}
private Response<ServiceInstance> processRibbonInstanceResponse(List<ServiceInstance> instances) {
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = (pos % instances.size());
return new DefaultResponse(instance);
}
}
⾃定义filter
import org.apachemons.logging.Log;
import org.apachemons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.active.DefaultRequest;
import org.springframework.cloud.active.Request;
import org.springframework.cloud.active.Response;
import org.springframework.fig.LoadBalancerProperties;
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.gateway.support.ServerWebExchangeUtils;
import org.springframework.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
publisher.Mono;
import java.URI;
/**
* @author XIAXINYU3
* @date 2021/11/3
*/
public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
private static final Log log = Log(ReactiveLoadBalancerClientFilter.class);
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private final LoadBalancerClientFactory clientFactory;
private LoadBalancerProperties properties;
public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties 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 = Attribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = Attribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url != null && ("grayLb".Scheme()) || "grayLb".equals(schemePrefix))) {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
}
return this.choose(exchange).doOnNext((response) -> {
if (!response.hasServer()) {
ate(this.properties.isUse404(), "Unable to find instance for " + Host());
} else {
URI uri = Request().getURI();
String overrideScheme = null;
if (schemePrefix != null) {
overrideScheme = Scheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((Server(), overrideScheme);                    URI requestUrl = structURI(serviceInstance, uri);
if (log.isTraceEnabled()) {
}
}
}).then(chain.filter(exchange));
} else {
return chain.filter(exchange);
网页素材网站大全
}
}
protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
structURI(serviceInstance, original);
}
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
URI uri = (Attribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
VersionGrayLoadBalancer loadBalancer = new Host(), ServiceInstanceListSupplier.class), ur        if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + Host());
} else {
return loadBalancer.ateRequest(exchange));
}properties是什么文件
}
private Request createRequest(ServerWebExchange exchange) {
HttpHeaders headers = Request().getHeaders();
Request<HttpHeaders> request = new DefaultRequest<>(headers);
return request;
}
}
配置⾃定义filter给spring管理
import org.springframework.dition.ConditionalOnMissingBean;面向对象什么意思
import org.springframework.fig.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import t.annotation.Bean;
import t.annotation.Configuration;
/**
* @author XIAXINYU3
* @date 2021/11/3
*/
@Configuration
public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean({GrayReactiveLoadBalancerClientFilter.class})
public GrayReactiveLoadBalancerClientFilter grayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
LoadBalancerProperties properties) {
return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties);
}
}
配置application.yaml
#注意:lb 改成 grayLb
spring:
main:
allow-bean-definition-overriding: true
application:
name: gateway
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: # 127.0.0.1:9000/actuator/gateway/routes
- id: provider  # 路由 ID,保持唯⼀
uri: grayLb://provider # uri指⽬标服务地址,lb代表从注册中⼼获取服务
predicates:
- Path=/provider/**  # 127.0.0.1:9000/provider/port 会转发到 localhost:9001/provider/port, 和预期不符合, 需要StripPrefix来处理
filters:
- StripPrefix=1 # StripPrefix=1就代表截取路径的个数为1, 这样请求 127.0.0.1:9000/provider/test/port 会转发到 localhost:9001/test/port 三、provider 微服务集
# application.yaml 添加eureka配置,关键点metadata-map
eureka:
instance:
preferIpAddress: true
leaseRenewalIntervalInSeconds: 10
leaseExpirationDurationInSeconds: 30
metadata-map:
version: v1
client:
serviceUrl:
defaultZone: localhost:8761/eureka/
registryFetchIntervalSeconds: 10
disable-delta: true
四、测试以及源码

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。