我们的服务是基于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;
}
}
```