openfeign 转发header 实现全链路灰度发布
引言
网关层看这里
之前写了网关层实现灰度发布,但是这个只能被路由一次
像是这样:
客户端->网关->根据版本号路由到应用
之后应用再调用其他服务,调用链的路由就没办法了,它只会被路由一次,因为服务调用的openfeign默认不带header转发。
实现
因为我们要实现全链路转发,所以需要在所有服务中加入之前在网关层写好的
rule
和predicate
,predicate
需要改一下,因为web中没有zuul的RequestContext
,改为使用(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
其他的具体代码去看网关层文章,我这里不重写了。
在服务中修改Predicate
import com.google.common.base.Optional;
import com.netflix.loadbalancer.AbstractServerPredicate;
import com.netflix.loadbalancer.PredicateKey;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.List;
public class MyPredicate extends AbstractServerPredicate {
@Override
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null) {
return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList();
for (Server server: servers) {
//这里是下面的apply方法
if (this.apply(new PredicateKey(loadBalancerKey, server))) {
results.add(server);
}
}
//这里我们需要重写
//如果results.size为0,说明所有服务都没有写metadata,或者和headers不匹配,所以这时候我们就得忽略这个规则,否则servers返回一个空集,就会找不到服务报错。
if (results.size()>0)
return results;
else
return servers;
}
}
@Override
public boolean apply(@NullableDecl PredicateKey predicateKey) {
Server server = predicateKey.getServer();
//Object loadBalancerKey = predicateKey.getLoadBalancerKey();
//仅仅允许元数据中有"version"的服务
if (server instanceof DiscoveryEnabledServer){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//从http头获取version
final String contextVersion = attributes.getRequest().getHeader("version");
final String metaVersion = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata().get("version");
return StringUtils.isEmpty(contextVersion) || contextVersion.equals(metaVersion);
}
return true;
}
@Override
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
return super.chooseRoundRobinAfterFiltering(servers, loadBalancerKey);
}
}
加一个Interceptor
利用
Feign
的ReqeustTemplate
实现全链路Header转发,因为无论是gateway还是zuul,它们虽然自带了header转发,但是Feign默认是没有header转发的,所以我们在服务中需要统一加入一个Interceptor
来转发header
.
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Component
public class AddHeaderInterceptor implements RequestInterceptor {
@Autowired
HttpServletRequest request;
@Override
public void apply(RequestTemplate template) {
Enumeration<String> headerNames = request.getHeaderNames();
String hdrName = null;
while(headerNames.hasMoreElements()){
hdrName = headerNames.nextElement();
template.header(hdrName,request.getHeader(hdrName));
}
}
}
配置ribbon规则,使用我们自己的规则
- 指定服务才具有灰度发布全链路转发
@RibbonClient(name="服务名",configuration = MyPredicateRule.class)
如果想使全局都直接支持,可以不用上面的方法,直接全局即可。
官网的例子显示@RibbonClient
的confiuration
是必须带有@Configuration
的配置类,但是如果带有@Configuration
会被Spring扫描到,注册成全局Bean,经过我测试,可以不用写配置类,直接让configuration
为Rule
即可。
- 全局都支持
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
@AutoConfigureBefore(RibbonClientConfiguration.class)
@ConditionalOnProperty(value = "ribbon.filter.metadata.enabled", matchIfMissing = true)
public class RuleConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public MyPredicateRule metaDataAwareRule() {
return new MyPredicateRule();
}
}