opnefeign
用法
声明式
通过
@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback.class)
public interface UserSecurityServiceFeign {
/**
* 验证token,返回对应的权限信息
*
* @param authenticateTokenCmd token身份验证命令
* @return 登录
*/
@PostMapping("/auth/token/authentication")
ResultDTO<UserPermissionInfoCO> authenticateToken(AuthenticateTokenCmd authenticateTokenCmd);
}
复制代码
继承式
feigin允许继承,通过继承我们可以覆盖一些配置进行自定义FeignClient
。例如UserSecurityServiceFeign
在API module中,然后在其他module我们可以引用API module然后定义一个FeignClient
来继承它,然后进行自定义。
@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback2.class)
public interface SelfUserSecurityServiceFeign extends UserSecurityServiceFeign{
}
复制代码
原理分析
入口
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//需要扫描的包
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
//默认配置例如feign.codec.Decoder 、 feign.codec.Encoder 、 feign.Contract 这些。
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
复制代码
从@EnableFeignClients
开始,其用@Import
注解导入了FeignClientsRegistrar
。FeignClientsRegistrar
实现了ImportBeanDefinitionRegistrar
也就意味着在BeanFactoryPostProcessor
进行配置类解析时,会执行registerBeanDefinitions
方法去注册@FeignClient
对应的BeanDefintion。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册默认的feign配置类
registerDefaultConfiguration(metadata, registry);
//注册FeignClients
registerFeignClients(metadata, registry);
}
复制代码
注册默认的feign配置类
可以看到根据@EnableFeignClients
注解配置的defaultConfiguration,构成一个**FeignClientSpecification
**BeanDefinition然后注册到容器中
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}
复制代码
注册FeignClients
最为核心的一步,BeanDefintion注册到Spring 容器中我们才能依赖注入FeignClient。根据注解配置扫描对应的包,如果class被@FeignClient
注解标注那么就成为一个候选BeanDefinition,然后遍历候选的BeanDefinition
读取其@FeignClient
的配置元数据attributes
生成一个个**FeignClientFactoryBean
**-BeanDefinition注册到容器中。
//attributes @FeignClient中读取的配置信息
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//重点!!!BeanDefinition类型为FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
//type 设置的FactoryBean 返回的 类型
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
复制代码
FeignClient的创建
根据上面的注册FeignClient的代码,可知FeignClient所注册的BeanDefinition
是**FeignClientFactoryBean
一个FactoryBean
实现类,也就是在依赖注入时注入的是其getObject
方法所返回的对象。源码里可以看到FeignClient是一个单例bean**,最终由**Feign.Builder
创建Feign
通过Feign
(ReflectiveFeign
)来创建一个JDK动态代理对象**-也就是FeignClient 实例
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//1、获得Feign.Builder
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//2、如果没有配置url则配置时服务名需要进行负载均衡
if (!StringUtils.hasText(url)) {
//...省略url拼接
//使用负载均衡client
return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
}
//...省略url拼接
Client client = getOptional(context, Client.class);
//此时有完整的url则需要去掉负载均衡的功能 即直接拿到装饰模式内部的Client
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
//targeter创建client target方法最终返回的都是feign.target tartet类型是@FeignClient的Interface
return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));
}
@Override
public boolean isSingleton() {
//feignClient是单例
return true;
}
//Feign.builder 的target方法 创建一个动态代理对象
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
//负载均衡
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
//容器中获取Client
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
复制代码
Feign.Builder
feign的构建器,它是feign的核心决定了Feigin的构建方式(也确定了整体逻辑流程),通过它可以进行扩展例如整合Hystrix、sentinel都是暴露一个Feign.Builder
Bean来整合自己的逻辑。可以看到Feign.Builder确实是从容器中获取的(FeignContext是NamedContextFactory
)。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// 从容器中获取builder
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
//Feign.Builder 所预留的扩展
public static class Builder {
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
//方法上注解解析
private Contract contract = new Contract.Default();
//client 不同功能的客户端 例如负载均衡
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
//解码和编码
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
//异常处理
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
//核心:生成代理对象的拦截器 FeignClient 时JDKdongd
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList<>();
public Feign build() {
//其他代码省略
//生成Feign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
复制代码
动态代理
ReflectiveFeign
中创建了JDK代理对象,根据方法上的注解信息(Contract
解析)创建MethodHandler
,在InvocationHandler中根据Method对应的MethodHandler去执行对应的逻辑。
//目标
public <T> T newInstance(Target<T> target) {
//解析方法上注解创建对应的MethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
//Oject中方法忽略
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
//默认方法处理
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//其他需要代理的方法
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//InvocationHandler feign中InvocationHandlerFactory
InvocationHandler handler = factory.create(target, methodToHandler);
//JDK动态代理
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
复制代码
InvocationHandlerFactory
InvocationHandler
工厂,扩展时需要覆盖其Feign.Bulider中invocationHandlerFactory。
public interface InvocationHandlerFactory {
//dispatch 分发根据Method找对对应的MethodHandler
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
复制代码
FeignContext
feign上下文继承**NamedContextFactory
,它为每个客户端都创建一个ApplicationContext
,并从这个ApplicationContext
中获取需要的Bean。它实现了ApplicationContextAware
所以能够拿到父容器**
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
// name 为客服端名称,也就是服务名
protected AnnotationConfigApplicationContext getContext(String name) {
//double check+synchronized 保证每个client对应的ApplicationContext只有一个 参考单例模式
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
//如果没有则创建加入map中
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
//省略其他
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
//拿到父容器
this.parent = parent;
}
}
复制代码
创建SpringApplicationContext
创建一个新的AnnotationConfigApplicationContext
,将自动装配得到的FeignClientSpecification
注册进新的ApplicationContext
,然后setParent
组成父子层次ApplicationContext,最后启动容器(context.refresh())。
protected AnnotationConfigApplicationContext createContext(String name) {
//创建新的ApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//将自动配置的FeignClientSpecification
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
//关键组成父子层次上下文
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
//启动容器
context.refresh();
return context;
}
复制代码
扩展
如果需要自定义Feign,我们只需要配置一个自定义Feign.Builder重新build 方法。其他扩展请查看Feign.Builder中各个属性。
限流熔断扩展
Sentinel、Hystrix对feign的支持,主要是覆盖了invocationHandlerFactory
使用了自己的SentinelInvocationHandler
在feign代理对象的执行时加入Sentinel的核心代码。以下为Sentinel对feign的支持。
public final class SentinelFeign {
public static Builder builder() {
return new Builder();
}
//自定义Feign.Builder
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
//重写build方法
@Override
public Feign build() {
//设置自定义invocationHandlerFactory
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {
//省略其他配置读取代码
return new SentinelInvocationHandler(target, dispatch);
}
//其他invocationHandlerFactory代理码
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
//省略其他
}
//SentinelInvocationHandler 核心 代理模式拦截方法执行加入sentinel核心逻辑
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)throws Throwable {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
//sentinel 模板代码
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
//具体执行逻辑
result = methodHandler.invoke(args);
}catch (Throwable ex) {
//异常处理
}
finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
//其他代码省略
return result;
}
复制代码
负载均衡扩展
feign默认继承Ribbon做负载均衡,其原理是在FeignClientFactoryBean
创建FeignClient时如果发现配置为服务名时则进行负载均衡。从容器中获取Client
Bean对象,然后设置到Builder中。Client中包含负载均衡逻辑。
//判断是否需要是需要负载均衡
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//创建负载均衡的feignClient
return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
}
//负载均衡
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
//
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
复制代码
LoadBalancer负载均衡
FeignLoadBalancerAutoConfiguration
feign 负载均衡自动装配,根据当前feign客户端类型装饰不同的负载均衡Client。
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
复制代码
以OkHttp为例当feign.okhttp.enabled=true
,将okHttpClient先装饰为OkHttpClient
在装饰为FeignBlockingLoadBalancerClient
(包含负载均衡client)。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(okhttp3.OkHttpClient okHttpClient,BlockingLoadBalancerClient loadBalancerClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
//装饰为 FeignBlockingLoadBalancerClient 带有负载均衡功能
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnBean(LoadBalancedRetryFactory.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
havingValue = "true", matchIfMissing = true)
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
okhttp3.OkHttpClient okHttpClient,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
loadBalancedRetryFactories.get(0));
}
}
复制代码
BlockingLoadBalancerClientAutoConfiguration
自动装配BlockingLoadBalancerClient
Ribbon负载均衡
feign默认的负载均衡器
FeignRibbonClientAutoConfiguration
fegin的Ribbon负载均衡器自动装配
//如果正在使用 Feign 并且需要使用 Ribbon 作为负载均衡器,则将激活自动配置
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
//默认使用Ribbon
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
//对不同的Http请求Client 做不同的装饰
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
//省略其他配置
}
复制代码
以OkHttp为例,当feign.okhttp.enabled=true
触发以下的自动装配配置类-将okhttp3.OkHttpClient
委托给LoadBalancerFeignClient
(装饰模式)以获得负载均衡的功能。
SpringClientFactory
为创建客户端、负载均衡器和客户端配置实例的工厂。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
复制代码
代码配置 Ribbon
配置 Ribbon 最简单的方式就是通过配置文件实现。当然我们也可以通过代码的方式来配置。
通过代码方式来配置之前自定义的负载策略,首先需要创建一个配置类,初始化自定义的策略,代码如下所示。
@Configurationpublic class BeanConfiguration {
@Bean
public MyRule rule() {
return new MyRule();
}
}
复制代码
创建一个 Ribbon 客户端的配置类,关联 BeanConfiguration,用 name 来指定调用的服务名称,代码如下所示。
@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {
}
复制代码
配置文件方式配置 Ribbon
除了使用代码进行 Ribbon 的配置,我们还可以通过配置文件的方式来为 Ribbon 指定对应的配置:
格式:<clientName>.<nameSpace>.<propertyName>=<value>
<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer(负载均衡器操作接口)
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule(负载均衡算法)
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing(服务可用性检查)
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList(服务列表获取)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter(服务列表的过滤)
复制代码
clientName在feign中就是服务名称,ribbon为命名空间
配置读取原理-IClientConfig
在IClientConfig
默认实现中(DefaultClientConfigImpl
),根据restClientName读取restClientName前缀的properties
@Override
public void loadProperties(String restClientName){
enableDynamicProperties = true;
setClientName(restClientName);
//加载默认配置
loadDefaultValues();
//读取前缀为restClientName所对应的属性
Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
String key = keys.next();
String prop = key;
try {
if (prop.startsWith(getNameSpace())){
prop = prop.substring(getNameSpace().length() + 1);
}
//设置到IClientConfig中
setPropertyInternal(prop, getStringValue(props, key));
} catch (Exception ex) {
throw new RuntimeException(String.format("Property %s is invalid", prop));
}
}
}
复制代码
具体的key查看IClientConfigKey
的实现类CommonClientConfigKey
例如:
//LoadBalancer Related
public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){};
public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){};
public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};
public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};
public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};
public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};
复制代码
默认Spring-cloud-netfix-ribbon配置
在DefaultClientConfigImpl.loadDefaultValues
中,例如
public void loadDefaultValues() {
//省略其他配置只贴出来了负载均衡器。
//ZoneAwareLoadBalancer
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
//默认的负载规则 AvailabilityFilteringRule 但是RibbonClientConfiguration覆盖这个默认
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
}
复制代码
ZoneAwareLoadBalancer
默认的负载均衡器
@Override
public Server chooseServer(Object key) {
//如果sh
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
复制代码
ribbon自带7种负载均衡规则实现:
- com.netflix.loadbalancer.RoundRobinRule – 轮询
- com.netflix.loadbalancer.RandomRule – 随机
- com.netflix.loadbalancer.RetryRule – 重试,先按RoundRobinRule进行轮询,如果失败就在指定时间内进行重试
- com.netflix.loadbalancer.WeightedResponseTimeRule – 权重,响应速度越快,权重越大,越容易被选中。
- com.netflix.loadbalancer.BestAvailableRule – 先过滤掉不可用的处于断路器跳闸转态的服务,然后选择一个并发量最小的服务
- com.netflix.loadbalancer.AvailabilityFilteringRule -先过滤掉故障实例,再选择并发量较小的实例
- com.netflix.loadbalancer.ZoneAvoidanceRule – Spring-cloud-netfix-ribbon默认规则 复合判断server所在区域的性能和server的可用性进行服务的选择。
ZoneAvoidanceRule
Spring-cloud-netfix-ribbon默认的负载均衡策略,在RibbonClientConfiguration
中如果配置文件中没有对应负载均衡规则配置则创建一个**ZoneAvoidanceRule
**,其实它使用的负载均衡算法就是轮询
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
//默认实现
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//轮询之后过滤
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
//得到有资格的server
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
//incrementAndGetModulo 有资格的server进行轮询 即cas获取下一个下标值
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
//自增并取模 并cas替换直到成功即得到轮询后下标
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
复制代码
总结
1、Spring cloud openfeign它是一个基于feign的Http请求框架,它可以集成Sentinel
或者hystrix
做限流熔断也可以集成Ribbon
(默认)/loadbalancer
做负载均衡。feign支持OkHttp
和HttpClient
(默认)去发起Http请求。
2、Spring-cloud-netfix-ribbon
默认负载均衡算法为轮询。
3、Spring cloud openfeign为每个服务(客户端)都创建一个Spring ApplicationContext()和当前应用ApplicationContext
组成父子关系,需要的Bean都从其中获取。
4、实际上负载均衡也会和openfeign一样为每个客户端创建一个Spring ApplicationContext
,设置parent为当前应用上下文。具体查看NamedContextFactory
的子类
ps:上面的ApplicationContex
t为**AnnotationConfigApplicationContext
**
问题
1、为什么需要对每个负载均衡客户端、feign客户端都创建一个ApplicationContext
?
我想因该是为了配置隔离吧,父context共享公用的configuration,然后不同的子context有不同的configuration。至于问我为什么不直接从放在在对应的Client实例中,当配置中心修改配置可能需要重新创建Client(@RefreshCope支持),放在Client中无法重新创建一个client,且配置应该和client实例之间隔离。望万能的网友解答
结尾
第一次在掘金上写文章,也是最近新学的openfeign。如有错误(肯定会有错误QAQ)请在评论区留言。