环境支持:
- Spring Boot 2.x
- Maven 3.x
- Java 8
- Lombok
- transmittable-thread-local
- spring-cloud-starter-netflix-zuul
- 微服务客户端添加配置
eureka.instance.metadata-map.version=v1 #当前微服务版本
- zuul添加配置
zuul.ribbon.canary.enabled=true
- 自定义ribbon负载均衡server选择
import com.frdscm.gateway.SecurityConstants;
import com.frdscm.gateway.util.RibbonVersionHolder;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.xiaoleilu.hutool.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {
@Override
public Server choose(Object key) {
List<Server> eligibleServers = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
if (eligibleServers == null || eligibleServers.size() < 1) {
return null;
}
String targetVersion = RibbonVersionHolder.getContext();
if (StrUtil.isBlank(targetVersion)) {
log.info("Client Not config version");
List<Server> roundRobinNotVersionServer = eligibleServers.stream().filter(server -> {
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
String metaVersion = metadata.get("version");
return StrUtil.isBlank(metaVersion);
}).collect(Collectors.toList());
if (roundRobinNotVersionServer.size() < 1) {
return eligibleServers.get(0);
}
if (roundRobinNotVersionServer.size() == 1) {
return roundRobinNotVersionServer.get(0);
}
Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(roundRobinNotVersionServer, key);
return server.isPresent() ? server.get() : null;
} else {
List<Server> roundRobinVersionServer = eligibleServers.stream().filter(server -> {
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
String metaVersion = metadata.get("version");
return targetVersion.equals(metaVersion);
}).collect(Collectors.toList());
if (roundRobinVersionServer.size() < 1) {
return null;
}
if (roundRobinVersionServer.size() == 1) {
return roundRobinVersionServer.get(0);
}
Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(roundRobinVersionServer, key);
return server.isPresent() ? server.get() : null;
}
}
}
- 版本上下文Alibaba TTL
import com.alibaba.ttl.TransmittableThreadLocal;
public class RibbonVersionHolder {
private static final ThreadLocal<String> context = new TransmittableThreadLocal<>();
public static String getContext() {
return context.get();
}
public static void setContext(String value) {
context.set(value);
}
public static void clearContext() {
context.remove();
}
}
- 初始化灰度发布
import com.frdscm.gateway.handler.MetadataCanaryRuleHandler;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
@ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
@AutoConfigureBefore(RibbonClientConfiguration.class)
@ConditionalOnProperty(value = "zuul.ribbon.canary.enabled")
public class RibbonMetaFilterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ZoneAvoidanceRule metadataAwareRule() {
return new MetadataCanaryRuleHandler();
}
}
- zuul中添加AccessFilter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.smartcomma.scaffolding.gateway.util.RibbonVersionHolder;
import com.xiaoleilu.hutool.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER;
@Component
public class AccessFilter extends ZuulFilter {
@Value("${zuul.ribbon.canary.enabled:false}")
private boolean canary;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FORM_BODY_WRAPPER_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
String version = requestContext.getRequest().getHeader("version");
if (canary && StrUtil.isNotBlank(version)) {
RibbonVersionHolder.setContext(version);
} else {
RibbonVersionHolder.clearContext();
}
return null;
}
}
- 客户端调用时header传入版本,以axios为例
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.common['version'] = 'v1'; #对应eureka metadata 配置
网友评论