基于ribbon实现灰度发布

参考1:https://gitee.com/wangxinqiao/springcloud-gray
参考2:https://zhuanlan.zhihu.com/p/152217968

流程

1. 外部请求进入网关
2. 灰度拦截器根据灰度规则向线程变量中添加版本号(prod/test)
3. Feign拦截器从线程变量中取出版本号,并存入Feign请求头中,目的是为了让下游服务拿到版本号
4. Ribbon根据自定义负载均衡策略调用对应的服务
5. 下游服务的灰度拦截器从请求头中取出版本号,存入线程变量
6. 下游服务如果需要调用更下游的灰度服务,Feign拦截器从线程变量中取出版本号,并存入Feign请求头中;如果下游服务不再调用更下游的灰度服务,就不再需要Feign拦截器
7. 返回第4步
1
2
3
4
5
6
7
模拟场景

service-common(公共服务)、service-zuul(网关服务)、service-A(灰度服务1)、service-B(灰度服务2)
外部调用:请求 --> service-zuul --> service-A
内部调用:请求 --> service-zuul --> service-A --> service-B(A --> B通过Fegin调用,Resttemplate方式同理)
灰度规则:如果请求头的userId为0,自动分发请求、userId为1请求v1服务,userId为2请求v2服务
灰度规则根据实际情况制定,这里做测试用
service-common

注意:以下代码均不能添加@Configuration注解

GrayMetadataRule

package com.cloud.common.gray;

import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 自定义负载均衡策略
 */

public class GrayMetadataRule extends ZoneAvoidanceRule {
    public static final String META_DATA_KEY_VERSION = "version";

    @Override
    public Server choose(Object key) {
        // 获取服务列表
        List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
        if (CollectionUtils.isEmpty(serverList)) {
            return null;
        }
        
        // 线程变量中的版本号
        String hystrixVer = CoreHeaderInterceptor.version.get();
        //不是灰度服务,或者不做灰度分发的服务列表
        List<Server> noMetaServerList = new ArrayList<>();
        
        for (Server server : serverList) {
            // 获取metadata
            Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();

            // metadata中的版本号
            String metaVersion = metadata.get(META_DATA_KEY_VERSION);
            
            if (!StringUtils.isEmpty(metaVersion)) {
                if (metaVersion.equals(hystrixVer)) {
                    return server;
                }
            } else {
                //如果metadata中没有版本号,说明此服务不是灰度服务
                noMetaServerList.add(server);
            }
        }

        if (!noMetaServerList.isEmpty()) {
            return originChoose(noMetaServerList, key);
        }

        return null;
    }

    private Server originChoose(List<Server> noMetaServerList, Object key) {
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }
}

MySelRule

package com.cloud.common.gray;

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;

/**
 * 自定义负载均衡策略配置类
 * Created by ${lyt} on 2021/3/9 14:56
 */
public class MySelRule {
    @Bean
    public IRule MyRule() {
        return new GrayMetadataRule();
    }
}

CoreHeaderInterceptor

package com.cloud.common.gray;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义HandlerInterceptorAdapter拦截器,
 * 在到达服务之前将请求头中的信息存入到线程变量HystrixRequestVariableDefault中
 */
public class CoreHeaderInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger(CoreHeaderInterceptor.class);

    public static final String HEADER_VERSION = "version";

    public static final HystrixRequestVariableDefault<String> version = new HystrixRequestVariableDefault<>();

    public static void initHystrixRequestContext(String headerVer) {
        logger.debug("headerVer:{}", headerVer);
        if (!HystrixRequestContext.isCurrentThreadInitialized()) {
            HystrixRequestContext.initializeContext();
        }

        if (!StringUtils.isEmpty(headerVer)) {
            CoreHeaderInterceptor.version.set(headerVer);
        } else {
            CoreHeaderInterceptor.version.set("");
        }
    }

    public static void shutdownHystrixRequestContext() {
        if (HystrixRequestContext.isCurrentThreadInitialized()) {
            HystrixRequestContext.getContextForCurrentThread().shutdown();
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        CoreHeaderInterceptor.initHystrixRequestContext(request.getHeader(CoreHeaderInterceptor.HEADER_VERSION));
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        CoreHeaderInterceptor.shutdownHystrixRequestContext();
    }
}

service-zuul

启动类加入@RibbonClients(defaultConfiguration = MySelRule.class) //使用RibbonClients,避免配置成全局ribbonServerList

GrayFilter

package com.cloud.gateway.filter;

import com.cloud.common.gray.CoreHeaderInterceptor;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 灰度服务过滤器
 */

@Component
public class GrayFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return -10;
    }

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

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();

        //userId
        String userId = request.getHeader("userId");

        if ("0".equals(userId)) {
        
                // 将版本信息存入到线程变量中
                CoreHeaderInterceptor.initHystrixRequestContext("");
                // 将版本信息存入请求头中
                context.addZuulRequestHeader(CoreHeaderInterceptor.HEADER_VERSION, "");
                
        }else if ("1".equals(userId)) {
        
            // 将版本信息存入到线程变量中
            CoreHeaderInterceptor.initHystrixRequestContext("v1");
            // 将版本信息存入请求头中
            context.addZuulRequestHeader(CoreHeaderInterceptor.HEADER_VERSION, "v1");
            
        }else if ("2".equals(userId)) {
        
            // 将版本信息存入到线程变量中
            CoreHeaderInterceptor.initHystrixRequestContext("v2");
            // 将版本信息存入请求头中
            context.addZuulRequestHeader(CoreHeaderInterceptor.HEADER_VERSION, "v2");
            
        }
            
        return null;
    }
}

CoreFeignRequestInterceptor

package com.cloud.gateway.filter;

import com.cloud.common.gray.CoreHeaderInterceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

/**
 * feign拦截器,目的是让下游服务拿到版本号
 * 如果下游服务不再使用Feign调用其它服务,比如service-B不再调用其它灰度服务的话,service-A就不需要这个类
 */
@Configuration
public class CoreFeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        //从线程变量中拿到版本号
        String hystrixVer = CoreHeaderInterceptor.version.get();
        
        //向请求头加入版本号
        template.header(CoreHeaderInterceptor.HEADER_VERSION, hystrixVer);
    }

}

service-A 和 service-B都启动两个,并在eureka中加入版本号,版本号分别为v1和v2

service-A

启动类加入@RibbonClients(defaultConfiguration = MySelRule.class) //使用RibbonClients,避免配置成全局ribbonServerList

bootstrap.yml

eureka:
  instance:
    metadata-map:
      version: v1/v2

GrayFilter

package com.cloud.service-A.filter;

import com.cloud.common.gray.CoreHeaderInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 灰度发布过滤器(将版本信息存入到线程变量中)
 * Created by ${lyt} on 2021/3/10 9:18
 */
@Slf4j
@Component
public class GrayFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        // 从请求头中取出版本号
        String version = httpServletRequest.getHeader(CoreHeaderInterceptor.HEADER_VERSION);

        // 将版本信息存入到线程变量中
        CoreHeaderInterceptor.initHystrixRequestContext(version);

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

service-B

bootstrap.yml

eureka:
  instance:
    metadata-map:
      version: v1/v2
1
2
3
4
因为service-B不再调用其它灰度服务,所以ribbon不用自定义的负载均衡策略,也就不用在启动类中添加@RibbonClients注解,也不用配置灰度拦截器和Feign拦截器
如果service-B的下游服务需要调用其它灰度服务,就需要在service-B中配置灰度拦截器和Feign拦截器,下游服务的启动类需要添加@RibbonClients注解
————————————————
版权声明:本文为CSDN博主「lyt1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43625121/article/details/114638532

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值