热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于springcloud的灰度发布实践_基于springcloudgateway+nacos实现灰度发布(reactive版)

什么是灰度发布?灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行ABtesting,即让一部分用户继

什么是灰度发布?

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

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

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/

https://nacos.io/zh-cn/docs/quick-start.html

实现的整体思路:

  • 编写带权重的灰度路由
  • 编写自定义filter
  • nacos服务配置需要灰度发布的服务的元数据信息以及权重
  • 灰度路由从nacos服务拉取元数据信息以及权重,然后根据权重算法,返回符合要求的服务实例给自定义的filter
  • 网关配置文件配置需要灰度路由的服务(因为本文代码没有网关实现动态路由,不然灰度路由可以配置在配置中心,从配置中心拉取)
  • filter通过责任链模式,把服务实例透传给其他filter比如NettyRoutingFilter

下边进入实战

正文

1、所使用的开发版本

1.8Hoxton.SR32.2.5.RELEASE2.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 &#61; LogFactory.getLog(GrayLoadBalancer.class); private ObjectProvider serviceInstanceListSupplierProvider; private String serviceId; public GrayLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId) { this.serviceId &#61; serviceId; this.serviceInstanceListSupplierProvider &#61; serviceInstanceListSupplierProvider; } &#64;Override public Mono> choose(Request request) { HttpHeaders headers &#61; (HttpHeaders) request.getContext(); if (this.serviceInstanceListSupplierProvider !&#61; null) { ServiceInstanceListSupplier supplier &#61; (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); } } /** * 根据版本进行分发 * &#64;param instances * &#64;param headers * &#64;return */ private Response getServiceInstanceResponseByVersion(List instances, HttpHeaders headers) { String versionNo &#61; headers.getFirst("version"); System.out.println(versionNo); Map versionMap &#61; new HashMap<>(); versionMap.put("version",versionNo); final Set> attributes &#61; Collections.unmodifiableSet(versionMap.entrySet()); ServiceInstance serviceInstance &#61; null; for (ServiceInstance instance : instances) { Map metadata &#61; instance.getMetadata(); if(metadata.entrySet().containsAll(attributes)){ serviceInstance &#61; instance; break; } } if(ObjectUtils.isEmpty(serviceInstance)){ return getServiceInstanceEmptyResponse(); } return new DefaultResponse(serviceInstance); } /** * * 根据在nacos中配置的权重值&#xff0c;进行分发 * &#64;param instances * * &#64;return */ private Response getServiceInstanceResponseWithWeight(List instances) { Map weightMap &#61; new HashMap<>(); for (ServiceInstance instance : instances) { Map metadata &#61; instance.getMetadata(); System.out.println(metadata.get("version")&#43;"-->weight:"&#43;metadata.get("weight")); if(metadata.containsKey("weight")){ weightMap.put(instance,Integer.valueOf(metadata.get("weight"))); } } WeightMeta weightMeta &#61; WeightRandomUtils.buildWeightMeta(weightMap); if(ObjectUtils.isEmpty(weightMeta)){ return getServiceInstanceEmptyResponse(); } ServiceInstance serviceInstance &#61; 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: " &#43; this.serviceId); return new EmptyResponse(); }

4、自定义filter

public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered { private static final Log log &#61; LogFactory.getLog(ReactiveLoadBalancerClientFilter.class); private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER &#61; 10150; private final LoadBalancerClientFactory clientFactory; private LoadBalancerProperties properties; public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) { this.clientFactory &#61; clientFactory; this.properties &#61; properties; } &#64;Override public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; } &#64;Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url &#61; (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String schemePrefix &#61; (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); if (url !&#61; null && ("grayLb".equals(url.getScheme()) || "grayLb".equals(schemePrefix))) { ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) { log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() &#43; " url before: " &#43; url); } return this.choose(exchange).doOnNext((response) -> { if (!response.hasServer()) { throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " &#43; url.getHost()); } else { URI uri &#61; exchange.getRequest().getURI(); String overrideScheme &#61; null; if (schemePrefix !&#61; null) { overrideScheme &#61; url.getScheme(); } DelegatingServiceInstance serviceInstance &#61; new DelegatingServiceInstance((ServiceInstance)response.getServer(), overrideScheme); URI requestUrl &#61; this.reconstructURI(serviceInstance, uri); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url chosen: " &#43; 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 &#61; (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); GrayLoadBalancer loadBalancer &#61; new GrayLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost()); if (loadBalancer &#61;&#61; null) { throw new NotFoundException("No loadbalancer available for " &#43; uri.getHost()); } else { return loadBalancer.choose(this.createRequest(exchange)); } } private Request createRequest(ServerWebExchange exchange) { HttpHeaders headers &#61; exchange.getRequest().getHeaders(); Request request &#61; new DefaultRequest<>(headers); return request; }}

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

&#64;Configurationpublic class GrayGatewayReactiveLoadBalancerClientAutoConfiguration { public GrayGatewayReactiveLoadBalancerClientAutoConfiguration() { } &#64;Bean &#64;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: &#39;*&#39;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&#61;/hello/**

uri中的grayLb配置&#xff0c;代表该服务需要进行灰度发布

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

d9552bb64972e8a26f43737c60d005a8.png

123.jpg

weight代表权重&#xff0c;version代表版本

总结

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

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

https://github.com/Nepxion/Discovery

demo链接

https://github.com/lyb-geek/gateway




推荐阅读
author-avatar
荆灬樊小姐_526
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有