Zuul 网关层的灰度发布 使用及手动实现

Zuul 网关层的灰度发布

  • 引言
  • 简单使用
  • 自己实现(原理)
    • Ribbon 中 ILoadBalancer 如何获取元数据
    • ILoadBalancer::chooseServer(key)
  • 自己实现(代码篇)
    • 写一个Predicate
    • 写自定义Rule
    • 将规则配置进Zuul,写个配置类
    • 测试

引言

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

目前有2种思路

  1. 一种是请求头上带上flag,也就是说http header中带上如version.
  2. 权重路由,这和nginx的权重类似。不需要多解释。

本文写的是第一种。

网关层只能路由一次,如果想实现全链路,看完本篇文章后看这里:
openfeign 转发header 实现全链路灰度发布

简单使用

ribbon-discovery-filter-spring-cloud-starter

  1. 引入 https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter 的依赖

pom.xml

	<dependency>
            <groupId>io.jmnarlochgroupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starterartifactId>
            <version>2.1.0version>
	 dependency>
  1. 写个filter,将需要匹配的信息(Http header)写入RibbonFilterContextHolder,这个方式很简单

github上有例子这里就不多说了。

自己实现(原理)

只要搞明白原理,实现起来就不难,这样无论你使用gateway还是zuul,区别都不大。

Ribbon 中 ILoadBalancer 如何获取元数据

可以在任意一个服务中引入下面的代码

 	@Autowired
    private SpringClientFactory factory;

    @GetMapping("/service_list")
    private List<Server> serviceList(){
        //获取LoadBalancer
        ILoadBalancer lb =  factory.getLoadBalancer("服务名");
        //获取所有服务列表
        List<Server> allServers = lb.getAllServers();
        //获取正常的服务列表
        List<Server> upServers = lb.getReachableServers();

        //打印
        System.out.println(allServers);
        System.out.println(upServers);
        
        //注意 想要获得元数据(不是metainfo,是metadata)
        //跟踪源码你可以看到必须强转成DiscoveryEnabledServer
        //获取metadata中的version
        upServers.forEach(s->{
            System.out.println("version:" + ((DiscoveryEnabledServer)s).getInstanceInfo().getMetadata().get("version"));
        });
        return upServers;

    }

ILoadBalancer::chooseServer(key)

其实返回的是IRule::choose(key);
所以如果要实现灰度发布,其实我们只需要自定义Rule即可。

自己实现(代码篇)

这里我尽量将代码写的直观一些,虽然为了方便的移植到gateway写一个ContextHolder更好,但是为了代码直观,省略掉。
因为我们省略掉了ContextHolder所以连Filter都不用写了

写一个Predicate

Predicate大家都懂得,我们继承AbstractServerPredicate,重写apply即可。
原因是为了后面写规则,规则需要继承PredicateBasedRule,其中的choose方法内,会调用到getPredicate

import com.google.common.base.Optional;
import com.netflix.loadbalancer.AbstractServerPredicate;
import com.netflix.loadbalancer.PredicateKey;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import org.springframework.util.StringUtils;

import java.util.List;

public class MyPredicate extends AbstractServerPredicate {
    @Override
    public boolean apply(@NullableDecl PredicateKey predicateKey) {
        Server server = predicateKey.getServer();
        //Object loadBalancerKey = predicateKey.getLoadBalancerKey();
        
        if (server instanceof DiscoveryEnabledServer){
            HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
            //从http头获取version
            final String contextVersion = request.getHeader("version");
            
            //获取服务注册进注册中心的metadata
            final String metaVersion = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata().get("version");
			
            return StringUtils.isEmpty(contextVersion) || StringUtils.isEmpty(metaVersion) || metaVersion.equals(contextVersion);
        }

        return true;
    }

    @Override
    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
    	//有需要的话连这也可以重写掉,具体下面写规则的时候会说
        return super.chooseRoundRobinAfterFiltering(servers, loadBalancerKey);
    }
}

写自定义Rule

自定义规则直接继承PredicateBasedRule即可。
还记得上面说到lbchooseServer时会调用Rulechoose方法吧?
PredicateBasedRulechoose方法关键部分在这:
Optional server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
我们写自定义规则事实上就是让getPredicate时,返回我们上面写好的Predicate
然后会调用chooseRoundRobinAfterFiltering

public class MyPredicateRule extends PredicateBasedRule {
    private final MyPredicate predicate;

    public MyPredicateRule() {
        this.predicate = new MyPredicate();
    }
    public MyPredicateRule(MyPredicate predicate) {
        this.predicate = predicate;
    }
    @Override
    public AbstractServerPredicate getPredicate() {
        return this.predicate;
    }
}

将规则配置进Zuul,写个配置类

看一下autoconfiguration抄一份

@Configuration
@AutoConfigureBefore(RibbonClientConfiguration.class)
@ConditionalOnProperty(value = "ribbon.filter.metadata.enabled", matchIfMissing = true)
public class RuleConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    //@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public MyPredicateRule metaDataAwareRule() {
        return new MyPredicateRule();
    }

}

测试

随便搭2个相同的服务,yml中metadata的version写不同版本

eureka:
  instance:
    prefer-ip-address: true
    metadata-map:
      version: 1 # 2个服务 版本不要相同
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/

在zuul中定义routes,然后测试,这里我就不写了,直接看图
Zuul 网关层的灰度发布 使用及手动实现_第1张图片
如果请求header没有包含version或version为空,就会回到轮询的规则。

你可能感兴趣的:(Distributed,Java)