SpringCloud-灰度发布

by shihang.mai

zuul过滤器+ribbon自定义路由规则+aop

1. 代码

表结构

  • id
  • serverId
  • userId
  • meta-version

例子以不同的用户,访问不同的服务举例

  1. 在eureka-client先设置meta-map的version

新服务A

eureka:
  instance:
    metadataMap:
      version: v2

旧服务A

eureka:
  instance:
    metadataMap:
      version: v1
  1. 在zuul中自定义一个过滤器
@Component
public class GrayFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        int userId = Integer.parseInt(request.getHeader("userId"));
        // 这里不写具体的调用,根据userId去db获取规则,可能获取不到,即存储中并没对应数据
        String versioned="v2";

        if(null!=versioned){
            //请求访问到新服务上
            RibbonFilterContextHolder.getCurrentContext().add("version",versioned);
        }
        //代码还需完善..



        return null;
    }
}

引入jar

<dependency>
            <groupId>io.jmnarloch</groupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
            <version>2.1.0</version>
</dependency>

到此,可以完成网关到服务则灰度.还需要做服务之间的灰度.

  1. 在服务之间我们通过自定义ribbon规则实现,在服务中加入路由Rule
@Component
public class GrayRule extends AbstractLoadBalancerRule {
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object o) {
        return null;
    }


    public Server choose(ILoadBalancer lb, Object o) {

        //获取所有的可达的服务
        List<Server> reachableServers = lb.getReachableServers();

        //获取当前线程的userId-----见第4步
        Map<String,String> map = RibbonParam.get();
        String userId = map.get("userId");
        //根据用户id,到db查找version,不写查找db代码
        String version = "v2";
        Server returnServer = null;
        //根据version路由
        for (int i = 0; i < reachableServers.size(); i++) {
            Server server = reachableServers.get(i);
            //这里找到这个类,是因为自己慢慢试的
            DiscoveryEnabledServer des=(DiscoveryEnabledServer)server;
            Map<String, String> metadata = des.getInstanceInfo().getMetadata();
            String eurukaClientVersion = metadata.get("version");
            if(eurukaClientVersion.equals(version)){
                returnServer = server;
                break;
            }
        }

        return null;
    }
}

这里用reachableServers获取单一个server时,需要转为DiscoveryEnabledServer才能获取到自定义的meta-map信息

  1. 请求进来服务,然后经过ribbon路由,它们是一个线程的,故用ThreadLocal存储信息
public class RibbonParam {
    private  static final ThreadLocal tl = new ThreadLocal();

    public static <T> T get(){
        return (T)tl.get();
    }

    public static <T> void set(T t){
        tl.set(t);
    }
  1. 那什么时候把信息set进ThreadLocal呢,当然是用aop
@Aspect
@Component
public class RequestAspect {

    @Pointcut("execution(* com.shihangmai.eurekaprovider.*.*(..))")
    private void allMethod(){

    }

    @Before(value = "allMethod()")
    public void before(JoinPoint joinPoint){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String userId = request.getHeader("userId");
        Map<String,String> map = new HashMap<>(2);
        map.put("userId",userId);
        RibbonParam.set(map);
    }

}
  1. 注册bean
public class GrayRibbonConfiguration {

    @Bean
    public IRule ribbonRule(){
        return new GrayRule();
    }

}

这个类并不需要加注解,原因见第7步

  1. 在springboot启动类上加上注解
@RibbonClient(name="eureka-provider",configuration = GrayRibbonConfiguration.class)

这样做的话,只有请求这个服务的时候利用自定义的路由规则.

到此,网关到服务,服务到服务间的灰度都做完了


  1. 实际上,3-7步有一个框架已经做了,直接删除便是,就是
<dependency>
            <groupId>io.jmnarloch</groupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
            <version>2.1.0</version>
</dependency>
  1. 在启动类上的注解RibbonClient也删除

  2. 当我们引入了这个starter后,只需要在aop中直接加入逻辑即可

@Aspect
@Component
public class RequestAspect {

    @Pointcut("execution(* com.shihangmai.eurekaprovider.*.*(..))")
    private void allMethod(){

    }

    @Before(value = "allMethod()")
    public void before(JoinPoint joinPoint){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String userId = request.getHeader("userId");
        //根据userId到db中获取version
        String dbVersion = "v2";
        if(null!= dbVersion){
            RibbonFilterContextHolder.getCurrentContext().add("version", dbVersion);
        }
        
        
    }

}

2. 解析

灰度发布简图
  1. 新服务A、旧服务A,新服务B、旧服务B均需要注册meta-map:version信息
  2. 当请求过来,我们在zuul中自定义一个过滤器,从HttpServlet中获取到token并解析出userId,并将userId到数据库中查出路由的版本。当然这个数据库可以换为redis。
  3. 这样,我们网关就可以根据userId找到库中的路由版本,然后因为服务都注册了meta-map:version,路由到对应的服务器上
    以上是zuul到服务,灰度
  4. 首先,请求经过ribbon再从服务出去,都是同一个线程。在zuul调用服务后,在服务侧用aop在调用方法前进行加强,从HttpServletRequest获取userId并放入ThreadLocal
  5. 在服务侧自定义一个路由规则并注入到spring上下文,在路由规则中,获取ThreadLocal中的userId,然后也是到db找出对应的version,再循环遍历所有可达的服务列表的meta-data,找到后返回Server即可
  6. 在服务启动类中指定请求类和对应的自定义路由规则即可。
    以上是服务l到服务,灰度

4-6可直接用框架代替为

  1. 首先,请求经过ribbon再从服务出去,都是同一个线程。在zuul调用服务后,在服务侧用aop在调用方法前进行加强,将从HttpServletRequest获取userId,然后userId到库中查找对应的version,直接add入RibbonFilterContextHolder.getCurrentContext()即可
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,064评论 19 139
  • 灰度方案 目前现状分析 zuul做网关,统一所有内部服务的入口 目前没有用 eureka 注册中心 【目前公司架...
    jey恒阅读 7,965评论 0 4
  • 灰度发布、蓝绿发布、金丝雀发布各是什么意思,可以看这篇http://www.appadhoc.com/blog/p...
    staconfree阅读 11,966评论 0 7
  • 推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...
    Jenaral阅读 11,032评论 0 5
  • 昨天,在回家的路上,坐在车里悠哉悠哉地看着三毛的《撒哈拉沙漠的故事》,我被里面的内容深深吸引住了,尽管上学时...
    夜阑晓语阅读 9,212评论 2 9

友情链接更多精彩内容