threeperson
发布于 2022-04-22 / 2 阅读
0
0

springcloud 灰度发布

我们的服务是基于springcloud搭建的微服务框架。通过微服务拆分业务后,服务边界更加清晰,服务修改引入的风险更加集中、影响范围更小,也有利于团队的开发。

但是微服务上线、发布要比单体服务复杂很多。单体服务我们只需通过nginx做简单的预上线环境和线上环境的切换就可以实现服务平滑上线,用了微服务后就没那么容易。

#### 问题

如何保证指定的设备访问到预上线

如何保证预上线调用链优选预上线服节点

预上线服务如何切换到正式环境

#### 解决思路

1. 通过gateway 过滤器增加预上线白名单

2. 通过gateway 过滤器增加请求头

3. 通过拦截器优先匹配指定环境的服务

#### 请求头version提取并本地保存

```

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.web.servlet.ModelAndView;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class CoreHeaderInterceptor extends HandlerInterceptorAdapter {

private static final Logger logger = LoggerFactory.getLogger(CoreHeaderInterceptor.class);

public static final String HEADER_VERSION_KEY = "version";

public static final String HEADER_VERSION_GRAY="gray";

public static final String HEADER_VERSION_PROD="prod";

//为了网络线程和业务调用线程数据正常传递,用了HystrixRequestVariableDefault

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

public static void initVersion(String version) {

logger.info("version:{}",version);

if (!HystrixRequestContext.isCurrentThreadInitialized()) {

HystrixRequestContext.initializeContext();

}

CoreHeaderInterceptor.version.set(version);

}

public static void shutdownHystrixRequestContext() {

if (HystrixRequestContext.isCurrentThreadInitialized()) {

HystrixRequestContext.getContextForCurrentThread().shutdown();

}

}

//截取路由携带的header value

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

CoreHeaderInterceptor.initVersion(request.getHeader(CoreHeaderInterceptor.HEADER_VERSION_KEY));

return true;

}

//释放该请求header value

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

CoreHeaderInterceptor.shutdownHystrixRequestContext();

}

}

```

#### Feign请求透传version

```

import feign.RequestInterceptor;

import feign.RequestTemplate;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class CoreFeignRequestInterceptor implements RequestInterceptor {

private static final Logger logger = LoggerFactory.getLogger(CoreFeignRequestInterceptor.class);

@Override

public void apply(RequestTemplate template) {

//feign透传请求header version值

String version = CoreHeaderInterceptor.version.get();

template.header(CoreHeaderInterceptor.HEADER_VERSION_KEY, version);

}

}

```

#### 设置启动配置

```

import feign.Feign;

import org.springframework.cloud.netflix.ribbon.DefaultPropertiesFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

@EnableWebMvc

public class CoreAutoConfiguration implements WebMvcConfigurer {

@Bean

public DefaultPropertiesFactory defaultPropertiesFactory() {

return new DefaultPropertiesFactory();

}

@Bean

public Feign.Builder feignBuilder() {

return Feign.builder().requestInterceptor(new CoreFeignRequestInterceptor());

}

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new CoreHeaderInterceptor());

}

}

```

#### 服务选择

```

import com.netflix.loadbalancer.Server;

import com.netflix.loadbalancer.ZoneAvoidanceRule;

import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;

import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;

import org.springframework.util.StringUtils;

import java.util.List;

import java.util.Map;

public class GrayMetadataRule extends ZoneAvoidanceRule {

@Override

public Server choose(Object key) {

//1.从线程变量获取version信息

String version = RibbonFilterContextHolder.getCurrentContext().get(CoreHeaderInterceptor.HEADER_VERSION_KEY);

//2.获取服务实例列表

List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);

//3.循环serverList,选择version匹配的服务并返回

for (Server server : serverList) {

Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();

String metaVersion = metadata.get(CoreHeaderInterceptor.HEADER_VERSION_KEY);

if (!StringUtils.isEmpty(metaVersion)) {

if (metaVersion.equals(version)) {

return server;

}

}

}

return super.choose(key);

}

}

```

#### 灰度请求头设置

```

ctx.addZuulRequestHeader(CoreHeaderInterceptor.HEADER_VERSION_KEY, CoreHeaderInterceptor.HEADER_VERSION_GRAY)

RibbonFilterContextHolder.getCurrentContext().add(CoreHeaderInterceptor.HEADER_VERSION_KEY, CoreHeaderInterceptor.HEADER_VERSION_GRAY)

```

####

```

package org.springframework.cloud.netflix.ribbon;

import com.netflix.client.config.IClientConfig;

import com.netflix.config.ConfigurationManager;

import com.netflix.loadbalancer.*;

import org.apache.commons.configuration.Configuration;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.core.env.Environment;

import org.springframework.util.StringUtils;

import java.util.HashMap;

import java.util.Map;

import static org.springframework.cloud.netflix.ribbon.SpringClientFactory.NAMESPACE;

public class DefaultPropertiesFactory extends PropertiesFactory {

@Autowired

private Environment environment;

private Map<Class<?>, String> classToProperty = new HashMap<Class<?>, String>();

public DefaultPropertiesFactory() {

super();

classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");

classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");

classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");

classToProperty.put(ServerList.class, "NIWSServerListClassName");

classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");

}

@SuppressWarnings("rawtypes")

@Override

public String getClassName(Class clazz, String name) {

String className = super.getClassName(clazz, name);

// 读取全局配置

if(!StringUtils.hasText(className) && this.classToProperty.containsKey(clazz)){

String classNameProperty = this.classToProperty.get(clazz);

className = environment.getProperty(NAMESPACE + "." + classNameProperty);

}

return className;

}

public static String getClientConfig(IClientConfig clientConfig, String key){

String value = (String) clientConfig.getProperties().get(key);

// 读取全局配置

if(!StringUtils.hasText(value)){

Configuration subset = ConfigurationManager.getConfigInstance().subset(clientConfig.getNameSpace());

value = subset.getString(key);

}

return value;

}

}

```


评论