Spring Cloud Gateway 擴展支持多版本控制及灰度發佈

灰度發佈

什麼是灰度發佈,概念請參考,咱們來簡單的經過下圖來看下,通俗的講:爲了保證服務升級過程的平滑過渡提升客戶體驗,會一部分用戶 一部分用戶遞進更新,這樣生產中會同時出現多個版本的客戶端,爲了保證多個版本客戶端的可用須要對應的多個版本的服務端版本。灰度發佈就是經過必定策略保證 多個版本客戶端、服務端間可以正確對應。
圖片nginx

所謂灰度發佈,即某個服務存在多個實例時,而且實例版本間的版本並不一致,經過web

實現方案

nginx + lua (openresty)

圖片

Netflix Zuul

只須要自定義ribbon 的斷言便可,核心是經過TTL 獲取上下請求header中的版本號spring

 
 
  1. @Slf4j編程

  2. public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {app


  3.    @Overrideide

  4.    public AbstractServerPredicate getPredicate() {微服務

  5.        return new AbstractServerPredicate() {lua

  6.            @Overridespa

  7.            public boolean apply(PredicateKey predicateKey) {debug

  8.                String targetVersion = RibbonVersionHolder.getContext();

  9.                RibbonVersionHolder.clearContext();

  10.                if (StrUtil.isBlank(targetVersion)) {

  11.                    log.debug("客戶端未配置目標版本直接路由");

  12.                    return true;

  13.                }


  14.                DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer();

  15.                final Map<String, String> metadata = server.getInstanceInfo().getMetadata();

  16.                if (StrUtil.isBlank(metadata.get(SecurityConstants.VERSION))) {

  17.                    log.debug("當前微服務{} 未配置版本直接路由");

  18.                    return true;

  19.                }


  20.                if (metadata.get(SecurityConstants.VERSION).equals(targetVersion)) {

  21.                    return true;

  22.                } else {

  23.                    log.debug("當前微服務{} 版本爲{},目標版本{} 匹配失敗", server.getInstanceInfo().getAppName()

  24.                            , metadata.get(SecurityConstants.VERSION), targetVersion);

  25.                    return false;

  26.                }

  27.            }

  28.        };

  29.    }

  30. }

維護請求中的版本號

 
 
  1. public class RibbonVersionHolder {

  2.    private static final ThreadLocal<String> context = new TransmittableThreadLocal<>();


  3.    public static String getContext() {

  4.        return context.get();

  5.    }


  6.    public static void setContext(String value) {

  7.        context.set(value);

  8.    }


  9.    public static void clearContext() {

  10.        context.remove();

  11.    }

  12. }

Spring Cloud Gateway 中實現

第一反應,參考zuul 的實現,自定義斷言,而後從上下中獲取版本信息便可。但因爲 spring cloud gateway 是基於webflux 的反應式編程,因此傳統的TTL或者 RequestContextHolder 都不能正確的維護上下文請求。

先來看 spring clou的 gateway 默認的lb 策略實現 LoadBalancerClientFilter

 
 
  1. public class LoadBalancerClientFilter implements GlobalFilter, Ordered {

  2.    @Override

  3.    public int getOrder() {

  4.        return LOAD_BALANCER_CLIENT_FILTER_ORDER;

  5.    }


  6.    @Override

  7.    @SuppressWarnings("Duplicates")

  8.    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

  9.        return chain.filter(exchange);

  10.    }


  11.    protected ServiceInstance choose(ServerWebExchange exchange) {

  12.        return loadBalancer.choose(

  13.                ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());

  14.    }

  15. }

咱們只須要重寫 choose 方法,把上下文請求傳遞到路由斷言中便可,以下

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

而後在路由斷言中經過 PredicateKey獲取到便可

 
 
  1. public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate {


  2.    /**

  3.     * {@inheritDoc}

  4.     */

  5.    @Override

  6.    public boolean apply(@Nullable PredicateKey input) {

  7.        return input != null

  8.                && input.getServer() instanceof NacosServer

  9.                && apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey());

  10.    }

  11. }

最後根據版原本計算

 
 
  1.    public class GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate {


  2.    @Override

  3.    protected boolean apply(NacosServer server, HttpHeaders headers) {

  4.        PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);


  5.        if (!ribbonProperties.isGrayEnabled()) {

  6.            log.debug("gray closed,GrayMetadataAwarePredicate return true");

  7.            return true;

  8.        }


  9.        final Map<String, String> metadata = server.getMetadata();

  10.        String version = metadata.get(CommonConstants.VERSION);

  11.        // 判斷Nacos服務是否有版本標籤

  12.        if (StrUtil.isBlank(version)) {

  13.            log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true");

  14.            return true;

  15.        }


  16.        // 判斷請求中是否有版本

  17.        String target = headers.getFirst(CommonConstants.VERSION);

  18.        if (StrUtil.isBlank(target)) {

  19.            log.debug("request headers version is blank,GrayMetadataAwarePredicate return true");

  20.            return true;

  21.        }


  22.        log.debug("請求版本:{} ,當前服務版本:{}", target, version);

  23.        return target.equals(version);

  24.    }


  25. }

整合nacos

結合nacos的動態配置能夠很是方便的實現灰度圖片

總結

  • 以上源碼參考我的項目 基於Spring Cloud、OAuth2.0開發基於Vue先後分離的開發平臺

  • QQ: 2270033969 一塊兒來聊聊大家是咋用 spring cloud 的吧。歡迎關注咱們得到更多的好玩JavaEE 實踐

相關文章
相關標籤/搜索