美文网首页
SpringCloud-灰度发布

SpringCloud-灰度发布

作者: 麦大大吃不胖 | 来源:发表于2021-01-18 15:49 被阅读0次

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()即可

相关文章

网友评论

      本文标题:SpringCloud-灰度发布

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