灰度发布

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

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

实现方案

nginx + lua (openresty)

Netflix Zuul

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

  1. @Slf4j

  2. public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {


  3.    @Override

  4.    public AbstractServerPredicate getPredicate() {

  5.        return new AbstractServerPredicate() {

  6.            @Override

  7.            public boolean apply(PredicateKey predicateKey) {

  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 实践


©著作权归作者所有:来自51CTO博客作者轮子工厂的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Spring Cloud OAuth 微服务内部Token传递的源码实现解析
  2. 基于oauth 2.0 开发第三方开放平台
  3. 扩展 jwt 解决 oauth2 性能瓶颈
  4. 2021年激活码激活PhpStorm全教程
  5. kafka查看版本
  6. MGR用哪个版本?5.7 vs 8.0
  7. MySQL产品的生命周期
  8. 【asp.net core 系列】 1 带你了解一下asp.net core
  9. MySQL 数据库事务及隔离级别,多版本控制

随机推荐

  1. SSDP协议的Android实现以及使用
  2. 用Eneter实现Android与.NET间通讯
  3. 活动与任务
  4. 打造android ORM框架opendroid(四)——优
  5. PHP android ios相互兼容的AES加密算法
  6. Android界面加载完成后自动弹出软键盘的
  7. 快速体验Android(安卓)2.3
  8. android map的使用方法
  9. Android打包jar 和使用第三方库
  10. [转]Android的Camera架构介绍[有图]