Spring Cloud Gateway 扩展支持多版本控制及灰度发布

假装没事ソ 提交于 2019-11-29 23:40:39

灰度发布

什么是灰度发布,概念请参考,我们来简单的通过下图来看下,通俗的讲: 为了保证服务升级过程的平滑过渡提高客户体验,会一部分用户 一部分用户递进更新,这样生产中会同时出现多个版本的客户端,为了保证多个版本客户端的可用需要对应的多个版本的服务端版本。灰度发布就是通过一定策略保证 多个版本客户端、服务端间能够正确对应。

所谓灰度发布,即某个服务存在多个实例时,并且实例版本间的版本并不一致,通过

实现方案

nginx + lua (openresty)

Netflix Zuul

只需要自定义ribbon 的断言即可,核心是通过TTL 获取上下请求header中的版本号

@Slf4j public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {      @Override     public AbstractServerPredicate getPredicate() {         return new AbstractServerPredicate() {             @Override             public boolean apply(PredicateKey predicateKey) {                 String targetVersion = RibbonVersionHolder.getContext();                 RibbonVersionHolder.clearContext();                 if (StrUtil.isBlank(targetVersion)) {                     log.debug("客户端未配置目标版本直接路由");                     return true;                 }                  DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer();                 final Map<string, string> metadata = server.getInstanceInfo().getMetadata();                 if (StrUtil.isBlank(metadata.get(SecurityConstants.VERSION))) {                     log.debug("当前微服务{} 未配置版本直接路由");                     return true;                 }                  if (metadata.get(SecurityConstants.VERSION).equals(targetVersion)) {                     return true;                 } else {                     log.debug("当前微服务{} 版本为{},目标版本{} 匹配失败", server.getInstanceInfo().getAppName()                             , metadata.get(SecurityConstants.VERSION), targetVersion);                     return false;                 }             }         };     } } 

维护请求中的版本号

public class RibbonVersionHolder {     private static final ThreadLocal<string> context = new TransmittableThreadLocal&lt;&gt;();      public static String getContext() {         return context.get();     }      public static void setContext(String value) {         context.set(value);     }      public static void clearContext() {         context.remove();     } } 

Spring Cloud Gateway 中实现

第一反应,参考zuul 的实现,自定义断言,然后从上下中获取版本信息即可。但由于 spring cloud gateway 是基于webflux 的反应式编程,所以传统的TTL或者 RequestContextHolder 都不能正确的维护上下文请求。

先来看 spring clou的 gateway 默认的lb 策略实现 LoadBalancerClientFilter

public class LoadBalancerClientFilter implements GlobalFilter, Ordered { 	@Override 	public int getOrder() { 		return LOAD_BALANCER_CLIENT_FILTER_ORDER; 	}  	@Override 	@SuppressWarnings("Duplicates") 	public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 		return chain.filter(exchange); 	}  	protected ServiceInstance choose(ServerWebExchange exchange) { 		return loadBalancer.choose( 				((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); 	} } 

我们只需要重写 choose 方法,把上下文请求传递到路由断言中即可,如下

@Override protected ServiceInstance choose(ServerWebExchange exchange) { 	HttpHeaders headers = exchange.getRequest().getHeaders(); 	return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), headers); } 

然后在路由断言中通过 PredicateKey获取到即可

public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate {  	/** 	 * {@inheritDoc} 	 */ 	@Override 	public boolean apply(@Nullable PredicateKey input) { 		return input != null 				&amp;&amp; input.getServer() instanceof NacosServer 				&amp;&amp; apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey()); 	} } 

最后根据版本来计算

    public class GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate {  	@Override 	protected boolean apply(NacosServer server, HttpHeaders headers) { 		PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);  		if (!ribbonProperties.isGrayEnabled()) { 			log.debug("gray closed,GrayMetadataAwarePredicate return true"); 			return true; 		}  		final Map<string, string> metadata = server.getMetadata(); 		String version = metadata.get(CommonConstants.VERSION); 		// 判断Nacos服务是否有版本标签 		if (StrUtil.isBlank(version)) { 			log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true"); 			return true; 		}  		// 判断请求中是否有版本 		String target = headers.getFirst(CommonConstants.VERSION); 		if (StrUtil.isBlank(target)) { 			log.debug("request headers version is blank,GrayMetadataAwarePredicate return true"); 			return true; 		}  		log.debug("请求版本:{} ,当前服务版本:{}", target, version); 		return target.equals(version); 	}  } 

整合nacos

结合nacos的动态配置可以非常方便的实现灰度

总结

欢迎关注我们获得更多的好玩JavaEE 实践</string,></void></string></string,>

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!