java灰度发布系统_基于springcloud gateway + nacos实现灰度发布(reactive版)

什么是灰度发布?

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

本文以springcloud gateway + nacos来演示如何实现灰度发布,如果对springcloud gateway和nacos还不熟悉的朋友,可以先阅读如下文章,然后再阅读本文。

springcloud gateway官方介绍

nacos官方介绍

实现的整体思路:

编写带权重的灰度路由

编写自定义filter

nacos服务配置需要灰度发布的服务的元数据信息以及权重

灰度路由从nacos服务拉取元数据信息以及权重,然后根据权重算法,返回符合要求的服务实例给自定义的filter

​网关配置文件配置需要灰度路由的服务(因为本文代码没有网关实现动态路由,不然灰度路由可以配置在配置中心,从配置中心拉取)​

filter通过责任链模式,把服务实例透传给其他filter比如NettyRoutingFilter

下边进入实战

正文

1、所使用的开发版本

1.8

Hoxton.SR3

2.2.5.RELEASE

2.2.1.RELEASE复制代码

2、pom.xml引入

org.springframework.cloud

spring-cloud-starter-gateway

org.springframework.boot

spring-boot-starter-webflux

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

org.springframework.cloud

spring-cloud-starter-netflix-ribbon

org.springframework.cloud

spring-cloud-loadbalancer

org.apache.commons

commons-lang3

复制代码

ps:nacos的jar注意排除ribbon依赖,不然loadbalancer无法生效

3、编写权重路由

public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);

private ObjectProvider serviceInstanceListSupplierProvider;

private String serviceId;

public GrayLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId) {

this.serviceId = serviceId;

this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;

}

@Override

public Mono> choose(Request request) {

HttpHeaders headers = (HttpHeaders) request.getContext();

if (this.serviceInstanceListSupplierProvider != null) {

ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);

return ((Flux)supplier.get()).next().map(list->getInstanceResponse((List)list,headers));

}

return null;

}

private Response getInstanceResponse(List instances,HttpHeaders headers) {

if (instances.isEmpty()) {

return getServiceInstanceEmptyResponse();

} else {

return getServiceInstanceResponseWithWeight(instances);

}

}

/**

* 根据版本进行分发

* @param instances

* @param headers

* @return

*/

private Response getServiceInstanceResponseByVersion(List instances, HttpHeaders headers) {

String versionNo = headers.getFirst("version");

System.out.println(versionNo);

Map versionMap = new HashMap<>();

versionMap.put("version",versionNo);

final Set> attributes =

Collections.unmodifiableSet(versionMap.entrySet());

ServiceInstance serviceInstance = null;

for (ServiceInstance instance : instances) {

Map metadata = instance.getMetadata();

if(metadata.entrySet().containsAll(attributes)){

serviceInstance = instance;

break;

}

}

if(ObjectUtils.isEmpty(serviceInstance)){

return getServiceInstanceEmptyResponse();

}

return new DefaultResponse(serviceInstance);

}

/**

*

* 根据在nacos中配置的权重值,进行分发

* @param instances

*

* @return

*/

private Response getServiceInstanceResponseWithWeight(List instances) {

Map weightMap = new HashMap<>();

for (ServiceInstance instance : instances) {

Map metadata = instance.getMetadata();

System.out.println(metadata.get("version")+"-->weight:"+metadata.get("weight"));

if(metadata.containsKey("weight")){

weightMap.put(instance,Integer.valueOf(metadata.get("weight")));

}

}

WeightMeta weightMeta = WeightRandomUtils.buildWeightMeta(weightMap);

if(ObjectUtils.isEmpty(weightMeta)){

return getServiceInstanceEmptyResponse();

}

ServiceInstance serviceInstance = weightMeta.random();

if(ObjectUtils.isEmpty(serviceInstance)){

return getServiceInstanceEmptyResponse();

}

System.out.println(serviceInstance.getMetadata().get("version"));

return new DefaultResponse(serviceInstance);

}

private Response getServiceInstanceEmptyResponse() {

log.warn("No servers available for service: " + this.serviceId);

return new EmptyResponse();

}

复制代码

4、自定义filter

public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {

private static final Log log = LogFactory.getLog(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 filter(ServerWebExchange exchange, GatewayFilterChain chain) {

URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);

if (url != null && ("grayLb".equals(url.getScheme()) || "grayLb".equals(schemePrefix))) {

ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);

if (log.isTraceEnabled()) {

log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);

}

return this.choose(exchange).doOnNext((response) -> {

if (!response.hasServer()) {

throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());

} else {

URI uri = exchange.getRequest().getURI();

String overrideScheme = null;

if (schemePrefix != null) {

overrideScheme = url.getScheme();

}

DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((ServiceInstance)response.getServer(), overrideScheme);

URI requestUrl = this.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));

} else {

return chain.filter(exchange);

}

}

protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {

return LoadBalancerUriTools.reconstructURI(serviceInstance, original);

}

private Mono> choose(ServerWebExchange exchange) {

URI uri = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

GrayLoadBalancer loadBalancer = new GrayLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost());

if (loadBalancer == null) {

throw new NotFoundException("No loadbalancer available for " + uri.getHost());

} else {

return loadBalancer.choose(this.createRequest(exchange));

}

}

private Request createRequest(ServerWebExchange exchange) {

HttpHeaders headers = exchange.getRequest().getHeaders();

Request request = new DefaultRequest<>(headers);

return request;

}

}

复制代码

5、配置自定义filter给spring管理

@Configuration

public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {

public GrayGatewayReactiveLoadBalancerClientAutoConfiguration() {

}

@Bean

@ConditionalOnMissingBean({GrayReactiveLoadBalancerClientFilter.class})

public GrayReactiveLoadBalancerClientFilter grayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {

return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties);

}

}

复制代码

6、编写网关application.yml配置

server:

port: 9082

# 配置输出日志

logging:

level:

org.springframework.cloud.gateway: TRACE

org.springframework.http.server.reactive: DEBUG

org.springframework.web.reactive: DEBUG

reactor.ipc.netty: DEBUG

#开启端点

management:

endpoints:

web:

exposure:

include: '*'

spring:

application:

name: gateway-reactor-gray

cloud:

nacos:

discovery:

server-addr: localhost:8848

gateway:

discovery:

locator:

enabled: true

lower-case-service-id: true

routes:

- id: hello-consumer

uri: grayLb://hello-consumer

predicates:

- Path=/hello/**复制代码

uri中的grayLb配置,代表该服务需要进行灰度发布​

7、在注册中心nacos配置灰度发布的服务版本以及权重值

java灰度发布系统_基于springcloud gateway + nacos实现灰度发布(reactive版)_第1张图片

weight代表权重,version代表版本​

总结

上述就是实现灰度发布的过程,实现灰度发布的方法有很多种,文章中只是提供一种思路。虽然springcloud官方推荐使用loadbalancer来代替ribbon。因为ribbon是阻塞的,但从官方的loadbalancer的负载均衡算法来看,目前loadbalancer默认只支持轮询算法,要其他算法得自己扩展实现,而ribbon默认支持7种算法,用默认的算法基本上就可以满足我们的需求了。其次ribbon支持懒加载处理,超时以及重试与断路器hystrix集成等配置,loadbalancer目前就支持重试。所以如果正式环境要自己实现灰度发布,可以考虑对ribbon进行扩展。本文的实现只是作为一种扩展补充,毕竟springcloud推荐loadbalancer,索性就写个demo实现下。

最后灰度发布的实现,业内也有开源的实现--Discovery,感兴趣的朋友可以通过如下链接进行查看

github.com/Nepxion/Dis…

demo链接

github.com/lyb-geek/ga…

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[基于springcloud gateway + nacos实现灰度发布(reactive版)]http://www.zyiz.net/tech/detail-130788.html

你可能感兴趣的:(java灰度发布系统)