美文网首页
Spring Cloud 爬坑记---Zuul灰度发布

Spring Cloud 爬坑记---Zuul灰度发布

作者: March_CD | 来源:发表于2020-05-27 14:50 被阅读0次

    环境支持:

    • Spring Boot 2.x
    • Maven 3.x
    • Java 8
    • Lombok
    • transmittable-thread-local
    • spring-cloud-starter-netflix-zuul
    1. 微服务客户端添加配置
    eureka.instance.metadata-map.version=v1 #当前微服务版本
    
    1. zuul添加配置
    zuul.ribbon.canary.enabled=true
    
    1. 自定义ribbon负载均衡server选择
    import com.frdscm.gateway.SecurityConstants;
    import com.frdscm.gateway.util.RibbonVersionHolder;
    import com.google.common.base.Optional;
    import com.netflix.loadbalancer.Server;
    import com.netflix.loadbalancer.ZoneAvoidanceRule;
    import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
    import com.xiaoleilu.hutool.util.StrUtil;
    import lombok.extern.slf4j.Slf4j;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @Slf4j
    public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {
        @Override
        public Server choose(Object key) {
            List<Server> eligibleServers = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
            if (eligibleServers == null || eligibleServers.size() < 1) {
                return null;
            }
            String targetVersion = RibbonVersionHolder.getContext();
            if (StrUtil.isBlank(targetVersion)) {
                log.info("Client Not config version");
                List<Server> roundRobinNotVersionServer = eligibleServers.stream().filter(server -> {
                    Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
                    String metaVersion = metadata.get("version");
                    return StrUtil.isBlank(metaVersion);
                }).collect(Collectors.toList());
                if (roundRobinNotVersionServer.size() < 1) {
                    return eligibleServers.get(0);
                }
                if (roundRobinNotVersionServer.size() == 1) {
                    return roundRobinNotVersionServer.get(0);
                }
                Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(roundRobinNotVersionServer, key);
                return server.isPresent() ? server.get() : null;
            } else {
                List<Server> roundRobinVersionServer = eligibleServers.stream().filter(server -> {
                    Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
                    String metaVersion = metadata.get("version");
                    return targetVersion.equals(metaVersion);
                }).collect(Collectors.toList());
                if (roundRobinVersionServer.size() < 1) {
                    return null;
                }
                if (roundRobinVersionServer.size() == 1) {
                    return roundRobinVersionServer.get(0);
                }
                Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(roundRobinVersionServer, key);
                return server.isPresent() ? server.get() : null;
            }
        }
    }
    
    1. 版本上下文Alibaba TTL
    import com.alibaba.ttl.TransmittableThreadLocal;
    
    public class RibbonVersionHolder {
    
        private static final ThreadLocal<String> context = new TransmittableThreadLocal<>();
    
        public static String getContext() {
            return context.get();
        }
    
        public static void setContext(String value) {
            context.set(value);
        }
    
        public static void clearContext() {
            context.remove();
        }
    }
    
    1. 初始化灰度发布
    import com.frdscm.gateway.handler.MetadataCanaryRuleHandler;
    import com.netflix.loadbalancer.ZoneAvoidanceRule;
    import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    import org.springframework.boot.autoconfigure.AutoConfigureBefore;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    
    @Configuration
    @ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
    @AutoConfigureBefore(RibbonClientConfiguration.class)
    @ConditionalOnProperty(value = "zuul.ribbon.canary.enabled")
    public class RibbonMetaFilterAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        public ZoneAvoidanceRule metadataAwareRule() {
            return new MetadataCanaryRuleHandler();
        }
    
    }
    
    1. zuul中添加AccessFilter
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.smartcomma.scaffolding.gateway.util.RibbonVersionHolder;
    import com.xiaoleilu.hutool.util.StrUtil;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.stereotype.Component;
    import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
    
    @Component
    public class AccessFilter extends ZuulFilter {
    
        @Value("${zuul.ribbon.canary.enabled:false}")
        private boolean canary;
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return FORM_BODY_WRAPPER_FILTER_ORDER - 1;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext requestContext = RequestContext.getCurrentContext();
            String version = requestContext.getRequest().getHeader("version");
            if (canary && StrUtil.isNotBlank(version)) {
                RibbonVersionHolder.setContext(version);
            } else {
                RibbonVersionHolder.clearContext();
            }
            return null;
        }
    }
    
    1. 客户端调用时header传入版本,以axios为例
    axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    axios.defaults.headers.common['version'] = 'v1';  #对应eureka metadata 配置
    

    相关文章

      网友评论

          本文标题:Spring Cloud 爬坑记---Zuul灰度发布

          本文链接:https://www.haomeiwen.com/subject/ksamahtx.html