opnefeign
usage
declarative
The interface is declared FeignClient by annotating @FeignClient
@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback.class)
public interface UserSecurityServiceFeign {
/** * Verify the token and return the corresponding permission information **@paramAuthenticateTokenCmd Token Authentication command *@returnLogin * /
@PostMapping("/auth/token/authentication")
ResultDTO<UserPermissionInfoCO> authenticateToken(AuthenticateTokenCmd authenticateTokenCmd);
}
Copy the code
Inheriting type
Feigin allows inheritance, through which we can override some configurations to customize FeignClient. For example, UserSecurityServiceFeign in the API Module, and then in other modules we can reference the API Module and define a FeignClient to inherit it and then customize it.
@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback2.class)
public interface SelfUserSecurityServiceFeign extends UserSecurityServiceFeign{}Copy the code
The principle of analysis
The entrance
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
// Packets to be scanned
String[] value() default {};
String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
// Default configurations such as feign.codec.Decoder, feign.codec.Encoder, feign.contract, etc.Class<? >[] defaultConfiguration()default{}; Class<? >[] clients()default {};
}
Copy the code
Start with @enableFeignClients, which imports FeignClientsRegistrar with the @import annotation. FeignClientsRegistrar implements ImportBeanDefinitionRegistrar means in spring BeanFactoryPostProcessor configure class, The registerBeanDefinitions method is executed to register @feignClient’s BeanDefintion.
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware.EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Register the default Feign configuration class
registerDefaultConfiguration(metadata, registry);
/ / register FeignClients
registerFeignClients(metadata, registry);
}
Copy the code
Register the default Feign configuration class
Can see depending on the defaultConfiguration @ EnableFeignClients annotations, constitute a FeignClientSpecificationBeanDefinition then register into the container
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());
}
Copy the code
Registered FeignClients
The core step is to register BeanDefintion with the Spring container so we can dependency inject FeignClient. Scanning the corresponding package according to the annotation configuration, if the class is annotated by the @FeignClient annotation then it is a candidate BeanDefinition, It then iterates through the candidate BeanDefinition and reads its @FeignClient configuration metadata attributes to generate one feignClientFactoryBean-beanDefinition registered to the container.
// Configuration information read by attributes @feignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// key!! The BeanDefinition type is 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 Sets the type returned by the 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);
}
Copy the code
The creation of FeignClient
The BeanDefinition registered by FeignClient is FeignClientFactoryBean, a FactoryBean implementation. That is, the object returned by its getObject method is injected during dependency injection. FeignClient is a singleton bean. Feign is created by Feign.Builder, and Feign(ReflectiveFeign) is used to create a JDK dynamic proxy object, namely FeignClient instance
@Override
public Object getObject(a) throws Exception {
return getTarget();
}
<T> T getTarget(a) {
// Get feign.builder
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//2. If the URL is not configured, load balancing is required for the service name
if(! StringUtils.hasText(url)) {/ /... Omitted URL concatenation
// Use load balancing client
return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
}
/ /... Omitted URL concatenation
Client client = getOptional(context, Client.class);
// If you have a complete URL, you need to remove the load balancing function
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);
The target method returns feign.target tartet is an Interface whose type is @feignClient
return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));
}
@Override
public boolean isSingleton(a) {
/ / feignClient is a singleton
return true;
}
// The target method of feign.builder creates a dynamic proxy object
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
// Load balancing
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
// Get Client from container
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?");
}
Copy the code
Feign.Builder
Feign’s builder, which is at the heart of Feign and determines how Feigin is built (and the overall logic flow), allows extensions such as Hystrix and Sentinel to integrate their logic by exposing a Feign.builderBean. You can see that Feign.Builder is indeed fetched from the container (FeignContext is NamedContextFactory).
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// Get builder from container
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 reserved extension
public static class Builder {
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
// annotation parsing on the method
private Contract contract = new Contract.Default();
//client Client with different functions, such as load balancing
private Client client = new Client.Default(null.null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
// Decode and encode
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
// Exception handling
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
// core: JDKdongd when generating the interceptor FeignClient for the proxy object
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(a) {
// Other code omitted
/ / generated Feign
return newReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }}Copy the code
A dynamic proxy
ReflectiveFeign creates the JDK proxy object, creates a MethodHandler based on the annotation information on the Method (Contract resolution), and executes the corresponding logic in the InvocationHandler based on the Method’s MethodHandler.
/ / target
public <T> T newInstance(Target<T> target) {
// Annotations on parse methods create corresponding 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()) {
// method ignored in Oject
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
// Default method processing
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// Other methods that require proxiesmethodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); }}/ / InvocationHandler InvocationHandlerFactory feign
InvocationHandler handler = factory.create(target, methodToHandler);
//JDK dynamic proxy
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
Copy the code
InvocationHandlerFactory
InvocationHandler factory, which needs to be overridden by the invocationHandlerFactory in feign. Bulider.
public interface InvocationHandlerFactory {
//dispatch finds the corresponding MethodHandler based on Method
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 newReflectiveFeign.FeignInvocationHandler(target, dispatch); }}}Copy the code
FeignContext
FeignContext inherits the NamedContextFactory, which means that it creates an ApplicationContext for each client and gets the required beans from this ApplicationContext. It implements the ApplicationContextAware Bean lifecycle interface, so it can take the parent container and form a parent container.
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean.ApplicationContextAware {
// name indicates the name of the customer service
protected AnnotationConfigApplicationContext getContext(String name) {
// Double check+synchronized ensures that each client's ApplicationContext has only one reference singleton
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// If not, create a map and add it to the map
this.contexts.put(name, createContext(name)); }}}return this.contexts.get(name);
}
// omit others
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
// Get the parent container
this.parent = parent; }}Copy the code
Create SpringApplicationContext
Create a new AnnotationConfigApplicationContext, will get the autowire FeignClientSpecification registered into new ApplicationContext, The setParent then forms the parent-child ApplicationContext and finally launches the context.refresh() container.
protected AnnotationConfigApplicationContext createContext(String name) {
// Create a new ApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// FeignClientSpecification to be automatically configured
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)));
// Key components of parent-child level context
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));
// Start the container
context.refresh();
return context;
}
Copy the code
extension
If we need to customize Feign, we just need to configure a custom Feign.Builder rebuild method. For other extensions, see the properties in Feign.Builder.
Current limiting fuse extension
Sentinel, Hystrix support to feign, mainly covering the invocationHandlerFactory USES its own SentinelInvocationHandler in feign proxy object is added in the execution of the Sentinel at the core of the code. Here is Sentinel’s support for Feign.
public final class SentinelFeign {
public static Builder builder(a) {
return new Builder();
}
// Customize feign.builder
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
// Override the build method
@Override
public Feign build(a) {
// Set up a custom invocationHandlerFactory
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {
// Omit other configuration read code
return new SentinelInvocationHandler(target, dispatch);
}
// Other invocationHandlerFactory proxy code
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
// omit others
}
/ / SentinelInvocationHandler proxy mode intercept method execution join sentinel core logic
@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 template code
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
// Execute logic
result = methodHandler.invoke(args);
}catch (Throwable ex) {
// Exception handling
}
finally {
if(entry ! =null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
// Other code omitted
return result;
}
Copy the code
Load Balancing expansion
By default, Feign integrates the Ribbon to perform load balancing. The principle is to perform load balancing when FeignClientFactoryBean creates FeignClient if it is configured as a service name. Get the ClientBean object from the container and set it in the Builder. Client contains load balancing logic.
// Check whether load balancing is required
if(! StringUtils.hasText(url)) {if(! name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// Create feignClient for load balancing
return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
}
// Load balancing
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?");
}
Copy the code
LoadBalancer Load balancing
FeignLoadBalancerAutoConfiguration
Feign load balancing is automatically assembled to decorate different feIGN load balancing clients according to the current FEIGN Client type.
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}Copy the code
OkHttp, for example when feign. OkHttp. Enabled = true, the first okHttpClient decoration for okHttpClient in adornment to FeignBlockingLoadBalancerClient (including load balancing 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);
/ / decoration for FeignBlockingLoadBalancerClient with load balancing function
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
loadBalancedRetryFactories)
{
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
loadBalancedRetryFactories.get(0)); }}Copy the code
BlockingLoadBalancerClientAutoConfiguration
Automatic assembly BlockingLoadBalancerClient
Ribbon Load Balancing
Feign Default load balancer
FeignRibbonClientAutoConfiguration
Fegin’s Ribbon load balancer automates assembly
// If you are using Feign and need to use the Ribbon as a load balancer, automatic configuration is enabled
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
// The Ribbon is used by default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Do different decorations for different Http request clients
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
// Omit other configurations
}
Copy the code
In the case of OkHttp, feign.okhttp.enabled=true triggers the following auto-assembly configuration class – delegating okHttp3. OkHttpClient to LoadBalancerFeignClient (decorator mode) for load balancing.
SpringClientFactory is a factory for creating clients, load balancers, and client configuration instances.
@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 newLoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }}Copy the code
Configuring the Ribbon
The easiest way to configure the Ribbon is through a configuration file. Of course, we can also configure it in code.
To configure a custom load policy in code, you need to create a configuration class to initialize the custom policy, as shown in the code below.
@Configurationpublic class BeanConfiguration {
@Bean
public MyRule rule(a) {
return newMyRule(); }}Copy the code
Create a configuration class for the Ribbon client, associate it with BeanConfiguration, and use name to specify the name of the service to be invoked, as shown below.
@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {}Copy the code
Configure the Ribbon in configuration file mode
In addition to configuring the Ribbon using code, you can also configure the Ribbon using configuration files:
Format: < clientName >. < nameSpace >. < propertyName > = < value >
<clientName>.ribbon.NFLoadBalancerClassName: Should Implement ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should Implement IRule load balancing algorithm
<clientName>.ribbon.NFLoadBalancerPingClassName: Should Implement IPing(Service Availability check)
<clientName>.ribbon.NIWSServerListClassName: Should Implement ServerList(Service List fetching)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerList-filter
Copy the code
ClientName is the service name in Feign, and ribbon is the namespace
Configuration Reading Principle -IClientConfig
In the default implementation of IClientConfig (DefaultClientConfigImpl), the properties of the restClientName prefix are read based on the restClientName
@Override
public void loadProperties(String restClientName){
enableDynamicProperties = true;
setClientName(restClientName);
// Load the default configuration
loadDefaultValues();
// Read the property whose prefix is 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);
}
// Set to IClientConfig
setPropertyInternal(prop, getStringValue(props, key));
} catch (Exception ex) {
throw new RuntimeException(String.format("Property %s is invalid", prop)); }}}Copy the code
For a specific key, see the IClientConfigKey implementation class CommonClientConfigKey for example:
//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") {};Copy the code
Default spring-cloud-netfix-ribbon configuration
In DefaultClientConfigImpl. LoadDefaultValues, for example
public void loadDefaultValues(a) {
// omit other configurations and post only load balancers.
//ZoneAwareLoadBalancer
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
/ / the default rules of load AvailabilityFilteringRule but RibbonClientConfiguration override this default
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
}
Copy the code
ZoneAwareLoadBalancer
Default load balancer
@Override
public Server chooseServer(Object key) {
/ / if 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.2 d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage".0.99999 d);
}
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); }}Copy the code
The Ribbon provides seven load balancing rules:
- Com.net flix. Loadbalancer. RoundRobinRule – polling
- Com.net flix. Loadbalancer. RandomRule – random
- Com.net flix. Loadbalancer. RetryRule – try again, according to the first RoundRobinRule poll, if failure will retry within a specified time
- Com.net flix. Loadbalancer. WeightedResponseTimeRule – weight, the faster response speed, the greater the weight, the easier it is selected.
- Com.net flix. Loadbalancer. BestAvailableRule – to filter out, not with the first turn in the circuit breaker tripped state service, the minimum amount and then select a concurrent service
- Com.net flix. Loadbalancer. AvailabilityFilteringRule – to filter out the first example, choose the instance of concurrency value smaller again
- Com.net flix. Loadbalancer. ZoneAvoidanceRule – Spring – the cloud – netflix – ribbon default rules composite judge the performance of the server area and server service availability of choice.
ZoneAvoidanceRule
Spring – the cloud – netflix – ribbon the default load balancing strategy, if there is no corresponding configuration file in RibbonClientConfiguration load balance rule configuration is created a ZoneAvoidanceRule * * * *, The load balancing algorithm it uses is polling
@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;
}
// Default implementation
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// Filter after polling
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) {
// Get the qualified server
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
//incrementAndGetModulo The qualified server polls that cas gets the next subscript value
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
// incrementing and modulo and cas replace until successful to get the post-poll subscript
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
returncurrent; }}Copy the code
conclusion
Spring Cloud OpenFeign is a Feign-based Http request framework that integrates with Sentinel or Hystrix for fuses and Ribbon(default)/ Loadbalancer for load balancing. Feign supports OkHttp and HttpClient (the default) to initiate Http requests.
2, Spring-cloud-netfix-ribbon default load balancing algorithm is polling.
Spring Cloud OpenFeign creates a Spring ApplicationContext() for each service (client) and a parent-child relationship with the current application ApplicationContext, from which the required beans are fetched.
4. Load balancers, like openfeign, create a Spring ApplicationContext for each client and set parent to the current ApplicationContext. Look specifically at a subclass of NamedContextFactory
Ps: the above ApplicationContext AnnotationConfigApplicationContext * * * *
The problem
1. Why create an ApplicationContext for every load balancing client and FeIGN client?
The parent context shares a common configuration, and then different child contexts have different configurations. As for why I don’t put it directly in the corresponding Client instance, when the configuration center changes the configuration, it may need to create a new Client (@refreshcope support), it cannot create a new Client in the Client, and the configuration should be isolated from the Client instance. Hope universal net friend answer
At the end
I wrote my first article for the Nuggets and also recently for openFeign. If there is an error (and there will be an error QAQ) please leave it in the comments section.