灰度发布思路与示例

阅读更多

 

灰度发布

中游服务部分:

ribbon:

 

自定义部分:

1,从nacos配置中获取配置

2,根据请求获取请求的实例名---对应成相应的ip(对应的配置列表中,配置的都是灰度机map)

3,把对应的ip和ribbon中服务列表匹配有放行,需要下的灰度注掉这个实例对应的ip

 

应用部分:

4,将自定义的rule纳入spring

5,业务的请求换成走ribbon的服务名形式,让ribbon转发即可负载----可用拦截器做(改造没有用注册中心的项目,或者没有经过zuul的项目)(zuul有一次负载,ribbon有一次负载)

 

 

 这样一来只要

开启了灰度测试开关,

配置ip列表中有灰度机器就,

灰度机器注册到nacos(配置的ip在nacos可以对应到)

用的是测试账号---测试账号ip的map的key会加:,所以解析的时候要根据:能解析出需要的是测试账号

就一直走的是灰度,之前的正式的就不会走,否则就走以前正式的服务

 

 

 

 

注入自定义的规则:

  @Bean

    public IRule ribbonRule() {

        CustomPredicateRule rule = new CustomPredicateRule(canaryServerManager);

        return rule;

    }

 

 

 

 

获取nacos的配置:

 

package com.houbank.xloan.core.config;

 

import org.springframework.beans.factory.annotation.Value;

import org.springframework.cloud.context.config.annotation.RefreshScope;

import org.springframework.stereotype.Component;

 

/**

 * @author wangdong

 * @Description: TODO

 * @date 2019/5/22 11:56

 */

@Component

@RefreshScope

public class CanaryConfig {

 

    @Value("${canary.open}")

    private Boolean open;

    @Value("${canary.telesaleBackAddr}")

    private String telesaleBackAddr;

 

    @Value("${canary.telsaleSeatAddr}")

    private String telesaleSeatAddr;

 

    @Value("${canary.telesaleWeixinAddr}")

    private String telesaleWeixinAddr;

    @Value("${canary.telesaleKuaiyipaiAddr}")

    private String telesaleKuaiyipaiAddr;

    @Value("${canary.telesaleProduct}")

    private String telesaleProduct;

 

    @Value("${canary.sessionIds}")

    private String sessionIds;

 

 

    public void setOpen(Boolean open) {

        this.open = open;

    }

 

 

 

    public boolean isOpen() {

        return open;

    }

 

 

    public String getSessionIds() {

        return sessionIds;

    }

 

 

    public String getServerHost(String key){

        switch (key){

            case "telesale-back":return telesaleBackAddr;

            case "telsale-seat":return telesaleSeatAddr;

            case "telesale-seat":return telesaleSeatAddr;

            case "v1":return telesaleWeixinAddr;

            case "kuaiyipai-api":return telesaleKuaiyipaiAddr;

            case "telesale-product":return telesaleProduct;

            default:return null;

        }

    }

}

 

 

选择服务的工具类:

 

CanaryServerManager

请求的服务实例名在配置的服务列表中就返回这,否则返回空

 

请求loadBalancerKey在ribbon的注册中就返回该服务转发------下架灰度机的时候就loadBalancerKey找到的主机地址和ribbon中注册的不一致即可

 

基于配置选择服务:

 

CustomPredicateRule

 

下架灰度机的时候就loadBalancerKey找到的主机地址和ribbon中注册的不一致即可

 

 

业务中:

 

 

将请求转化成zuul请求的url:

//建议将这块代码提出封装下

loadBalancerKey 请求的实例名,根据这个获取配置文件中的主机地址    case "telesale-back":return telesaleBackAddr;

 

 

private String   buildBackHost(String serviceName){

        checkCanaryRequest(serviceName);

        ServiceInstance serviceInstance = loadBalancerClient.choose(serviceName);

        String url = String.format("http://%s:%s/",serviceInstance.getHost(),serviceInstance.getPort());

        return url;

    }

 

     //判断是否开启金丝雀测试,如果开启则构造loadbalanceKey 构成为 应用名:filter

private void checkCanaryRequest(String serviceName) {

        if (!canaryServerManager.isOpen())

            return;

        ServletRequestAttributes requestAttributes= (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

        if (requestAttributes==null)

            return;

        HttpServletRequest httpServletRequest = requestAttributes.getRequest();

        String sessionId=httpServletRequest.getRequestedSessionId();

        StringBuilder loadBalancerKey=new StringBuilder();

        loadBalancerKey.append(serviceName).append(":");

        if (StringUtils.isNotBlank(sessionId)&&canaryServerManager.isSpecialAcct(sessionId)) {  //filter表示是否是测试账号访问

            loadBalancerKey.append("filter");

        }

        canaryServerManager.setLoadBalancerKey(loadBalancerKey.toString());

    }

 

 

中游服务部分:

 feign 参照ribbon

 

 自定义部分:

1,从nacos配置中获取配置

2,根据请求获取请求的实例名---对应成相应的ip

3,把对应的ip和ribbon中服务列表匹配有放行,需要下的灰度注掉这个实例对应的ip

 

应用部分:

4,将自定义的rule纳入spring

5,业务的请求换成走ribbon的服务名形式,让ribbon转发即可负载----这里用拦截器做

 

 

 

 

 

注意:自定义hystrix的并发策略,解决hystrix线程模式下RequestContextHolder丢失问题 (信号量模式不会),自定义的策略需要在init中注册-----用于降级使用

思路:将父的上下文属性传给子复制

 

 引入hystrix为了实现熔断降级:

自定义策略就是为了实现包装callable,

包装的目的就是为了能调用前cpoy  threadlocal变量,子线程被调用设置的就在子线程中

所以一个策略可以包装多个callable,在spring容器中,一次设置都有效---类似初始化思想

效果就是只要用了这个策略,里面的callable都是有自定义用自定义的,没有用默认的(总和就是自定义+默认的)

 

 

注册策略类的本质就是注册其中的callable:

 

HystrixConcurrencyStrategy target = new HystrixConcurrencyStrategyCustomize(canaryServerManager);

 HystrixPlugins.getInstance().registerConcurrencyStrategy(target);

 

 

1,定义策略类:

 

RequestContextHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy

引用定义的callable类

 

2,定义策略类中包装的callable类

 

static class RequestAttributeAwareCallable implements Callable

 

基于接口?????????

 

 

@Component

@Slf4j

public class HystrixConfig {

@PostConstruct

public void init() {

    HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestContextHystrixConcurrencyStrategy());

}

}

 

 

 

ThreadLocal:线程之间不共享

 

普通的RequestContextHolder就是threadlocal

 

在多线程的世界,多半是父子线程,并行的线程少数

 

 

 

inheritableThreadLocal:子线程继续传播父线程的上下文

 

ribbon上下文就是inheritableThreadLocal

 

  RibbonFilterContextHolder.getCurrentContext().get("TAG")

 

 

 

//父线程传入当前的ReuestContextHolder

 

@Slf4j

public class HystrixConcurrencyStrategyCustomize extends HystrixConcurrencyStrategy {

 

    private CanaryServerManager canaryServerManager;

 

    public HystrixConcurrencyStrategyCustomize(CanaryServerManager canaryServerManager) {

        this.canaryServerManager = canaryServerManager;

    }

 

    public Callable wrapCallable(Callable callable) {

        HystrixCustomizeCallable hystrixCustomizeCallable = new HystrixCustomizeCallable( (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(),callable);

        return hystrixCustomizeCallable;

    }

}

 

 

 

 

 

class HystrixCustomizeCallable implements Callable{

 

    private Callable callable;

 

    private ServletRequestAttributes attributes;

 

    public HystrixCustomizeCallable(ServletRequestAttributes attributes, Callable callable){=========父线程值传进来

        this.attributes = attributes;

        this.callable = callable;

    }

 

    public HystrixCustomizeCallable(Callable callable) {

        this.callable = callable;

    }

 

 

//子线程复制父线程中的attribute

 

 

    @Override

    public T call() throws Exception {============子线程才执行这个方法============

        try{

            //这里是为了feign拿到源请求的头信息

            if(null != this.attributes){

                RequestContextHolder.setRequestAttributes(this.attributes);

            }

            return this.callable.call();

        }finally {

            RequestContextHolder.resetRequestAttributes();

            //结束时清除loadbalancerkey的缓存

            CanaryServerManager.resetLoadBalancerKey();

        }

    }

}

 

 

 

 

 

hystrix两种模式---线程,信号量

https://blog.csdn.net/songhaifengshuaige/article/details/80345012

 

 

 

 

 api部分:

 zuul

 

参考上一篇zuul自定义负载原理

 

 

 

 

  • canary.zip (15.3 KB)
  • 下载次数: 0

你可能感兴趣的:(canary)