美文网首页
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