springcloud+nacos+mq灰度发布

 一、前提

1、所有系统前后端都要至少部署双节点

2、灰度的前端域名最好和生产的域名不一致

3、所有项目是springcloud+nacos+rocketmq架构

二、底层jar包升级

1、类文件GrayReqInterceptor:

package com.ms.roster.gray.aspect;
​
import javax.servlet.http.HttpServletRequest;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
​
import com.ms.common.utils.StringUtils;
​
import feign.RequestInterceptor;
import feign.RequestTemplate;
​
/**
 * 
 * @ClassName: GrayReqInterceptor
 * @Description: 灰度拦截器
 * @author dingjy
 * @date 2023年9月6日 下午4:28:11
 */
@Component
public class GrayReqInterceptor implements RequestInterceptor {
​
    private Logger logger = LoggerFactory.getLogger(GrayReqInterceptor.class);
​
    private static final String VERSION_KEY = "versionId";
​
    /**
     * 
     * Title: apply Description: 处理请求头参数携带问题
     * 
     * @param requestTemplate
     * @see feign.RequestInterceptor#apply(feign.RequestTemplate)
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
​
        String versionId = request.getHeader(VERSION_KEY);
        logger.info("apply_versionId:{}",versionId);
        if (StringUtils.isNotBlank(versionId)) {
            requestTemplate.header(VERSION_KEY, versionId);
        }
    }
}

2、类文件GrayRule:

package com.ms.roster.gray.aspect;

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

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.fastjson.JSONArray;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;

/**
 * 
 * @ClassName: GrayRule
 * @Description: 灰度规则
 * @author dingjy
 * @date 2023年9月6日 下午4:29:11
 */
@Configuration
@RibbonClients(defaultConfiguration = GrayRule.class)
public class GrayRule extends ZoneAvoidanceRule {
	
	private Logger log = LoggerFactory.getLogger(GrayRule.class);
	
	private static final String VERSION_KEY = "versionId";
	
	@Override
    public Server choose(Object key) {
        // 根据灰度路由规则,过滤出符合规则的服务 this.getServers()
        // 再根据负载均衡策略,过滤掉不可用和性能差的服务,然后在剩下的服务中进行轮询  getPredicate().chooseRoundRobinAfterFiltering()
		List<Server> serverList = this.getServers();
		Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(serverList, key);
		return server.isPresent() ? server.get() : null;
    }

    /**
     * 灰度路由过滤服务实例
     *
     * 如果设置了期望版本, 则过滤出所有的期望版本 ,然后再走默认的轮询 如果没有一个期望的版本实例,则不过滤,降级为原有的规则,进行所有的服务轮询。(灰度路由失效) 如果没有设置期望版本
     * 则不走灰度路由,按原有轮询机制轮询所有
     * @throws Exception 
     */
    protected List<Server> getServers() {
        // 获取spring cloud默认负载均衡器
        // 获取所有待选的服务
        List<Server> allServers = getLoadBalancer().getReachableServers();
        log.info("choose_allServers:{}", JSONArray.toJSONString(allServers));
        if (CollectionUtils.isEmpty(allServers)) {
            log.error("没有可用的服务实例");
        }

        //获取版本号
    	HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
    	String versionId = request.getHeader(VERSION_KEY);
        // 如果没有设置要访问的版本,则不过滤,返回所有,走原有默认的轮询机制
        if (StringUtils.isBlank(versionId)) {
            //这里需要过滤掉灰度服务实例
            List<Server> list = allServers.stream().filter(f -> {
                // 获取服务实例在注册中心上的元数据
                Map<String, String> metadata = ((NacosServer) f).getMetadata();
                // 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功
                if (null != metadata && StringUtils.isNotBlank(metadata.get(VERSION_KEY))) {
                    return false;
                }
                return true;
            }).collect(Collectors.toList());
            return list;
        }

        // 开始灰度规则匹配过滤
        List<Server> filterServer = new ArrayList<>();
        for (Server server : allServers) {
            // 获取服务实例在注册中心上的元数据
            Map<String, String> metadata = ((NacosServer) server).getMetadata();
            // 如果注册中心上服务的版本标签和期望访问的版本一致,则灰度路由匹配成功
            if (null != metadata && StringUtils.equals(versionId, metadata.get(VERSION_KEY))) {
                filterServer.add(server);
            }
        }
        // 如果没有匹配到期望的版本实例服务,为了保证服务可用性,让灰度规则失效,走原有的轮询所有可用服务的机制
        if (CollectionUtils.isEmpty(filterServer)) {
            log.error("灰度路由规则失效,没有找到期望的版本实例,versionId={},走原先的非灰度版本", versionId);
            return allServers;
        }
        log.info("choose_filterServer:{}", JSONArray.toJSONString(filterServer));
        return filterServer;
    }
}

3、类文件RequestAttributeHystrixConcurrencyStrategy:

package com.ms.roster.gray.aspect;
​
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
​
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
​
/**
 * 
 * @ClassName: RequestHeaderHystrixConcurrencyStrategy
 * @Description: 断路器策略
 * @author dingjy
 * @date 2023年9月6日 下午5:56:31
 */
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
​
    private Logger log = LoggerFactory.getLogger(RequestAttributeHystrixConcurrencyStrategy.class);
​
    private HystrixConcurrencyStrategy delegate;
​
    public RequestAttributeHystrixConcurrencyStrategy() {
        try {
            log.info("加载RequestAttributeHystrixConcurrencyStrategy");
            this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegate instanceof RequestAttributeHystrixConcurrencyStrategy) {
                // Welcome to singleton hell...
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
            this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
            log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
        }
    }
​
    private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
            HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
        if (log.isDebugEnabled()) {
            log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "],"
                    + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "],"
                    + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
            log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
        }
    }
​
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return new WrappedCallable<>(callable, requestAttributes);
    }
​
    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize,
            HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue) {
        return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue);
    }
​
    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
            HystrixThreadPoolProperties threadPoolProperties) {
        return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
    }
​
    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return this.delegate.getBlockingQueue(maxQueueSize);
    }
​
    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return this.delegate.getRequestVariable(rv);
    }
​
    static class WrappedCallable<T> implements Callable<T> {
​
        private final Callable<T> target;
        private final RequestAttributes requestAttributes;
​
        public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
            this.target = target;
            this.requestAttributes = requestAttributes;
        }
​
        @Override
        public T call() throws Exception {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                return target.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
}

4、将GrayReqInterceptor、GrayRule、RequestAttributeHystrixConcurrencyStrategy 集成进ops-cloud底层项目中,并将要引用的项目升级至ops-cloud的相关的版本,比如:

<parent>
        <groupId>ms.platform</groupId>
        <artifactId>ms-dependency</artifactId>
        <version>1.1.9-SNAPSHOT</version>
    </parent>

三、配置中心新增灰度域名

找到对应dataId下的配置文件,编辑Beta发布,填写需要灰度的服务器,并添加灰度标识:

spring.cloud.nacos.discovery.metadata.versionId=2

注意:versionId=2为灰度标识,后面所有灰度服务的灰度topic创建和消费者,都是要基于此灰度标识,示例如下,一定要改写生产和消费代码,读取nacos配置信息,topic、tag、消费者组上都需要加上_2来表示灰度环境

灰度topic:ms-app-econtract-2022_2

灰度消费者组:ms-app-roster-2020_consumer_2

四、前端添加灰度标识:

1、在nginx入口处,给header添加versionId属性:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值